# Previous: what it needs from its host

Source: Previous trunk, SVN revision 1858, last changed 2026-10-10 09:49:05 +0200. Checked out from `https://svn.code.sf.net/p/previous/code/trunk` on 2026-10-21. All paths are relative to the trunk root. Line numbers refer to that revision. "Read in full" means the whole file was read; "searched" means only lines found by a search were read, and such claims are marked.

## 1. Summary

Previous is a C and C++ program of about 164,000 lines (excluding the separate `ditool` utility). It is written against SDL3 and a small set of POSIX and libc calls. The code already separates the host from the emulation on purpose: `src/includes/host.h:19` and `:27` say "These types must be provided through host or cross-platform API" and "These functions must be provided through host or cross-platform API", and the same sentence appears in `src/includes/audio.h:11`, `src/includes/screen.h:24`, `src/includes/event.h:24` and `src/includes/keymap.h:11`. Five headers therefore list almost everything a different host has to supply.

What is not behind those headers is file and directory access through stdio and POSIX (`file.c`, `paths.c`, `scandir.c`), the configuration GUI (written against SDL surfaces), the built-in network stack (BSD sockets plus its own threads), and a handful of libc services (`time`, `getenv`, `getcwd`, `readline`).

There is no JIT in the build: `src/cpu/sysconfig.h:49` reads `//#define JIT /* JIT compiler support */`. The 68k core is C, generated at build time by two host tools. The i860 core is C++ and runs either on its own thread or on the 68k thread.

## 2. Build system and libraries

- CMake 3.12 or newer (`CMakeLists.txt:1`). Subdirectories built into the binary: `debug`, `softfloat`, `gui-sdl`, `cpu`, `dsp`, `dimension`, `slirp` (`src/CMakeLists.txt:49-56`). `ditool` is a separate executable (`src/ditool/CMakeLists.txt:3-5`).
- Link line: `Debug GuiSdl UaeCpu DSP Dimension Slirp SoftFloat ${SDL3_LIBRARIES}` (`src/CMakeLists.txt:118`).
- SDL3 is required, with no minimum version given: `find_package(SDL3 REQUIRED COMPONENTS SDL3)` (`CMakeLists.txt:118`).
- Optional libraries, each found with `find_package` and switched by a `HAVE_*` define in `cmake/config-cmake.h:4-10`:
  - libpng, for PNG screenshots and printer output (`CMakeLists.txt:130-133`, used in `src/grab.c:23,43-135`). Without it the code falls back to other output (TIFF writer at `src/grab.c:284`).
  - libpcap, for bridged networking (`CMakeLists.txt:135-138`, `src/enet_pcap.c`).
  - GNU readline or libedit, for the debugger prompt (`CMakeLists.txt:125-128`, `src/debug/debugui.c:20-21`).
  - The math library (`CMakeLists.txt:123`, linked at `src/CMakeLists.txt:120-122`).
- zlib is used unconditionally by `src/file.c:22` (`#include <zlib.h>`) with `gzopen`/`gzread`/`gzwrite` (`src/file.c:196,204,218,342`), but no `find_package` for zlib appears in either CMake file I read. How it is found at link time is open.
- libslirp is not used. The user-mode network stack is an old SLiRP copy inside the tree (`src/slirp`, 23,721 lines) with an NFS server (`src/slirp/nfs`) and an RPC layer (`src/slirp/rpc`).
- Compiler flags: `-std=gnu99`, `-g -O0` are added for GCC and Clang (`CMakeLists.txt:246,254`). The Release optimization level comes from CMake's own `CMAKE_C_FLAGS_RELEASE`, which I did not inspect.
- Feature switches (`CMakeLists.txt:54-57,96-103`): `ENABLE_DSP_EMU` (default 1), `ENABLE_TRACING` (default 1), `ENABLE_RENDERING_THREAD` (default 1 on Apple, 0 elsewhere).
- Build-time host tools: `build68k` and `gencpu` are compiled and run during the build to generate `cpudefs.c`, `cpustbl.c`, `cpuemu_31.c` and `cpuemu_32.c` (`src/cpu/CMakeLists.txt:5,43-51`). A cross build compiles them with the host `cc` (`src/cpu/CMakeLists.txt:15-39`).
- Host feature checks that matter for a port (`CMakeLists.txt:166-192`): `byteswap.h`, `sys/xattr.h`, `arpa/inet.h`, `netinet/in.h`, `bswap_16/32`, `select`, `gettimeofday`, `nanosleep`, `scandir`, `fseeko`, `ftello`, `flock`, `posix_memalign`, `aligned_alloc`. A missing function selects a fallback or a no-op (for example `src/file.c:745-749` turns `flock` into a no-op).

## 3. Threads, and what runs where

On Linux the default is `ENABLE_RENDERING_THREAD=0` (`CMakeLists.txt:96-103`). That selects the following layout. Every claim below was read in the cited source.

| Thread | Created at | Role |
|---|---|---|
| Main (process) thread | `src/main.c:301-303` | `GuiEvent_EventHandler()` loop: SDL events, window handling, repaint requests, rendering (`src/gui-sdl/sdlevent.c:272-454`) |
| 68k thread | `src/main.c:298`, body at `src/main.c:257-271` | `M68000_Start()` which calls `m68k_go(true)` (`src/m68000.c:51-53`). All device emulation, the DSP and (optionally) the i860 run on it. Priority "normal" (`src/main.c:258`, `src/gui-sdl/sdlhost.c:50-60`) |
| i860 thread, one per NeXTdimension board | `src/dimension/i860.cpp:546` | `i860_cpu_device::run()` (`src/dimension/i860.cpp:578-597`). Priority "low" (`:82`). Only when `ConfigureParams.Dimension.bI860Thread` is true, whose default is `host_num_cpus() != 1` (`src/configuration.c:549,607`) |
| SLiRP tick thread | `src/enet_slirp.c:194` | `select()` loop every 1230 microseconds (`:101,104-129`) |
| PCAP tick thread | `src/enet_pcap.c:182` | polls `pcap_next` every 1230 microseconds (`:69-72`) |
| NFS and RPC server threads | `src/slirp/nfs/TCPServerSocket.cpp:56`, `src/slirp/nfs/CSocket.cpp:61`, `src/slirp/rpc/tcpsocket.c:94`, `src/slirp/rpc/csocket.c:84` | one thread per server socket and per client connection (found by search, bodies not read) |

With `ENABLE_RENDERING_THREAD=1` (the macOS default) the 68k code runs on the main thread (`src/main.c:286-291`), one repaint thread per window renders with VSync on (`src/gui-sdl/sdlscreen.c:282-294,584-588,605-607`), and each NeXTdimension window gets its own repaint thread (`src/dimension/nd_sdl.cpp:16-35,71-76`).

Thread hand-over between the main thread and the 68k thread (non-rendering-thread build):

- Events: a 16-entry ring buffer guarded by an SDL spinlock (`src/gui-sdl/sdlevent.c:36-93`). The main thread writes (`:55-71`), the 68k thread reads in `GuiEvent_EventQueueHandler` (`:95-122`), called from `Main_EventHandler` (`src/main.c:244`).
- Remote commands to the main thread use a registered SDL user event (`src/gui-sdl/sdlevent.c:129-142`). The kinds are repaint, ND display repaint, pause, unpause and halt (`src/includes/event.h:16-22`). The 68k thread sends them from the video interrupt handler (`src/video.c:80-81`) and from the NeXTdimension handler (`src/dimension/dimension.cpp:322-329`).
- Pause: `Main_PauseEmulation` clears `bEmulationActive` and waits on a semaphore for up to 1000 ms until the 68k thread acknowledges (`src/main.c:109-117`). The 68k thread signals the semaphore and then sleeps in 20 ms steps while paused (`src/main.c:215-221`).
- The main thread repaints directly while the emulation is paused (`src/gui-sdl/sdlscreen.c:302-306,853-857`).

i860 interaction with the rest (read in full, `src/dimension/dimension.cpp` and `src/dimension/i860.cpp`):

- The 68k thread sends messages to the i860 through one `atomic_int` using a compare-and-swap loop (`src/dimension/dimension.cpp:183-194`). The i860 thread drains it in `handle_msgs` (`:307-316`).
- A cycle budget `i860cycles` (an `atomic_int`) is refilled to `(1000*1000*33)/136` at every ND display VBL (`src/dimension/dimension.cpp:337`). The i860 thread runs 16 cycles per pass while the budget is positive and subtracts 16 (`src/dimension/i860.cpp:587-593`). With an empty budget it sleeps 1 ms (`:594`). A halted i860 sleeps 100 ms per pass (`:582-585`).
- Without a thread, the 68k thread runs the i860 cycles inline: `cycles = nHostCycles * 33 / nCpuFreq` per call (`src/dimension/i860.cpp:39-57`).
- The i860 reads and writes the board memory through function pointers with big-endian and little-endian variants (`src/dimension/dimension.cpp:198-304`). Those accesses use the shared board memory without locks. Whether that is safe on a weakly ordered CPU is open; I did not analyze it.

Synchronization primitives used (all behind `src/includes/host.h:19-50`): spinlock, trylock, atomic set/get/add/CAS, thread create/wait/priority, semaphore create/signal/wait-with-timeout/destroy, mutex create/lock/unlock/destroy, sleep in ms and in microseconds, performance counter and its frequency, and CPU count. The SDL implementation of all 27 functions is 118 lines (`src/gui-sdl/sdlhost.c`, read in full). The only user of the semaphore API is the pause hand-over above. Direct SDL synchronization outside `host.h` exists in `src/gui-sdl/sdlscreen.c:50,59` (an `SDL_AtomicInt` and an `SDL_SpinLock` guarding the UI buffer) and `src/gui-sdl/sdlevent.c:38`.

## 4. Timing

Read in full: `src/timing.c`, `src/cycInt.c`, `src/main.c`.

- Emulated time is driven by a cycle counter `nCyclesMainCounter` (`src/cycInt.c:36`). `M68000_AddCycles` adds to it (`src/includes/m68000.h:85-87`), and `CycInt_AddCycles` fires queued events whose cycle time has passed (`src/cycInt.c:115-123`).
- Two event kinds exist: cycle-based and microsecond-based (`src/cycInt.c:38-42`). Microsecond events are cycle-based unless `ConfigureParams.System.bRealtime` is set, in which case they are tied to the host clock (`src/cycInt.c:199-215`). `bRealtime` defaults to false (`src/configuration.c:541`); `nCpuFreq` defaults to 25 (MHz) (`:539`).
- Realtime source: `host_get_counter()` and `host_get_counter_frequency()` (`src/timing.c:50,274,276`), converted to microseconds with an integer divisor if the frequency is a multiple of 1 MHz, otherwise a double multiplier (`:292-294`, `:49-57`). On SDL3 this is `SDL_GetPerformanceCounter` (`src/gui-sdl/sdlhost.c:108-114`).
- Realtime switching: the emulation uses the host clock only while realtime is enabled and the m68k is in user mode or the guest is the "darkmatter" kernel (`src/timing.c:72-75`). The darkmatter test reads guest memory at 0x04000246 (`:172`).
- Sleeping happens in exactly one place for pacing: `Timing_Sync` computes `hostTime - realTime` and, if positive, calls `host_sleep_us` (`src/timing.c:110-121`). `Timing_Sync` is called from `Main_EventHandler` (`src/main.c:247`), which is re-armed every 5000 microseconds of emulated time ("Poll events at 200 Hz", `src/main.c:249`). A second sleep or spin happens when switching from cycle time to real time (`src/timing.c:90-97`, spin if the gap is under 10,000 microseconds).
- Wall-clock time for the guest clock chip: `time(NULL)` at reset (`src/timing.c:277`), after that offset by emulated seconds (`:137-139`). The RTC reads `gmtime` through `Timing_GetUnixTimeStruct` (`src/timing.c:145-148`) and writes through `timegm` (`:151`). The guest's valid range is 1970 to 2037 (`:243-269`).
- Other threads read the emulated time through a spinlock-protected copy (`Timing_GetSaveTime`, `src/timing.c:123-130`), used by the SLiRP tick thread (`src/enet_slirp.c:106,117`).
- VBL: the main display interrupt runs at 68 Hz (`src/video.c:25`). In the non-rendering-thread build the handler toggles twice per frame, repaint request on one edge, interrupt on the other (`:75-85`). NeXTdimension uses 136 Hz toggled (68 Hz) and 120 Hz toggled (60 Hz) (`src/dimension/dimension.cpp:342-343,359-360`).
- Wall-clock calls outside `host.h`: `SDL_GetTicks` in the statusbar for LED expiry (`src/gui-sdl/sdlstatusbar.c:154,459`) and in the DSP only when `DSP_COUNT_IPS` is defined, which is 0 (`src/dsp/dsp_cpu.c:147,151,777,933`). `srand(time(NULL))` at start (`src/main.c:488`).
- Vsync: `SDL_SetRenderVSync(sdlRenderer, 1)` is only called with `ENABLE_RENDERING_THREAD` (`src/gui-sdl/sdlscreen.c:605-607`). In the Linux build the renderer is created with default vsync, and `SDL_RenderPresent` is called on repaint events (`:232,269`). The comment at `:231` ("Sleeps until next VSYNC if enabled in ScreenInit") does not apply to that build.

## 5. Video

Read in full: `src/gui-sdl/sdlscreen.c`, `src/dimension/nd_sdl.cpp`, `src/video.c`, `src/includes/screen.h`.

- Fixed guest resolution 1120 x 832 (`src/gui-sdl/sdlscreen.c:26-27`). The window grows by the statusbar height (`:474`) and by multiples of that size in group mode (`:436-458`).
- One SDL window and one SDL renderer, created with `SDL_CreateWindowAndRenderer` and the flags `SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY` (`:601-602`).
- Textures are `SDL_TEXTUREACCESS_STREAMING`, format `SDL_PIXELFORMAT_BGRA32`, blend mode none for the framebuffer and blend for the UI overlay (`:407-417,426,515-519`).
- Frame path for the 68k framebuffer: `blitScreen` (`:167-193`) reads `NEXTVideo` and converts into the locked texture. Monochrome (2 bit per pixel) goes through a 256-entry table of four pixels (`blitBW`, `:84-104`); color (4 bit per channel, 16 bit per pixel) through a 65,536-entry table (`blitColor`, `:109-128`). The tables are built with `SDL_MapSurfaceRGB` (`:69-79,613-621`).
- NeXTdimension frames: `SDL_ConvertPixels` from BGRA32 into the texture format (`:133-151`), or a fill with the "blank" color (`:156-162`).
- UI overlay: the configuration GUI and the statusbar draw into an `SDL_Surface` (`sdlscrn`, `:530`). A "green screen" copy replaces the mask color with transparent pixels (`:823-858`) into `uiBuffer`, which is uploaded to `uiTexture` (`:198-207`). The compositing is done by `SDL_RenderTexture` of the framebuffer, then the UI texture (`:227-230`).
- Present: `SDL_RenderClear`, two `SDL_RenderTexture`, `SDL_RenderPresent` (`:227-232`).
- Scaling: `SDL_SetRenderLogicalPresentation` with stretch in a window and letterbox in full screen (`:502,505,677,713`). Aspect ratio is locked with `SDL_SetWindowAspectRatio` (`:497`).
- Other window services: fullscreen (`:678`), window size and position queries (`:355-401`), usable display bounds (`:378`), border size (`:381`), bordered on/off (`:794`), title text (`:313-318`), raise/restore (`:770-771`), cursor show/hide (`:869-881`), warp (`:888`), relative mouse mode, keyboard grab and mouse grab (`:743-745`).
- NeXTdimension in "all screens" mode opens one extra window and renderer per board (`src/dimension/nd_sdl.cpp:63-70`).
- Host memory read by the blit: the guest video RAM and the ND VRAM (`src/dimension/dimension.cpp:44`, 4 MB per board).
- A framebuffer-only port needs: a way to show a 1120 x 832 image from a 2-bit, 12-bit-in-16 or 32-bit source; the UI overlay (a second layer, or a software blend); and mode switches. The scaling, aspect and multi-window code has no equivalent on a framebuffer.

## 6. Audio

Read in full: `src/gui-sdl/sdlaudio.c`, `src/includes/audio.h`, `src/snd.c`.

- Interface: 12 functions in `src/includes/audio.h:12-27`.
- Output: stereo 16-bit big-endian (`SDL_AUDIO_S16BE`) at 44,100 Hz (`src/snd.c:25,327`, `src/gui-sdl/sdlaudio.c:156`). Opened with `SDL_OpenAudioDeviceStream` on `SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK` without a callback (`:167,178`). This is a push model: the emulator writes with `SDL_PutAudioStreamData` (`:53`), and SDL3 resamples and converts to what the device accepts.
- Flow control: `Audio_Output_Queue_Size` returns the queued byte count only if more than the device buffer size is available (`:57-64`). `SND_Out_Handler` waits `8 microseconds * (queued sample frames)` when audio is queued, and otherwise requests the next DMA block (`src/snd.c:459-490`). Audio therefore paces the sound DMA events.
- Sound DMA block size for the codec is not defined in `snd.c`; `SND_BUFFER_SIZE` is in `src/includes/snd.h`, which I did not read.
- Recording: microphone at 8,012 Hz mono (`src/snd.c:26,336,391`) and a second recording path at 44,100 Hz stereo that feeds the DSP's serial port (`:340,412,548-565`), both via `SDL_AUDIO_DEVICE_DEFAULT_RECORDING` (`src/gui-sdl/sdlaudio.c:183,188`). Both can be absent: `Audio_Data_Get` returns silence with no stream (`:98-100`).
- Device hot-plug is handled through three SDL events (`src/gui-sdl/sdlevent.c:305-315`, `src/gui-sdl/sdlaudio.c:220-251`). A bare-metal port can ignore them.
- Mixing in the emulator: volume, de-emphasis, 2x upsampling and u-law conversion are done in plain C in `src/snd.c:46-213`. Only `pow` from libm is needed (`:149`).
- The sound is also copied to the AIFF recorder under a spinlock (`src/snd.c:210`, `src/grab.c:788-823`).
- On Linux the default SDL audio driver and the audio hardware are what the emulator depends on. On a Raspberry Pi 5 there is no analog jack; that applies to any port and is not a Previous issue.

## 7. Input

Read: `src/gui-sdl/sdlevent.c` in full. `src/gui-sdl/sdlkeymap.c` (681 lines) was searched for its structure only (function list at `:27,39,65,284,492,528,543,571,595,619,635,651,666,675`); its tables were not read, so the exact size of the mapping is open.

- Keyboard: SDL scancode and keycode events go to `Keymap_KeyDown/KeyUp` (`src/gui-sdl/sdlevent.c:396-420`, `src/gui-sdl/sdlkeymap.c:571,595`). Translation is by scancode (`:65`) and by symbol (`:284`), then to NeXT key codes posted to the ADB/keyboard emulation (`:528`).
- Shortcuts are checked before the guest sees the key. The modifier is Ctrl and Alt together (`src/gui-sdl/sdlevent.c:261-264`). Defaults: F12 for options, F11 for full screen, Ctrl-Alt with letters for the rest (`src/gui-sdl/sdlkeymap.c:39-56`). A port needs a way to open the configuration dialog from the keyboard, for example F12.
- `GuiEvent_ResetKeys` calls `SDL_ResetKeyboard()` and pushes synthetic key events to avoid stuck keys (`:230-255`).
- Mouse: relative motion with two optional scale factors, an exponential and a linear one (`:190-207`), a fractional remainder carried between events (`:209-215`), and a sensitivity factor 0.75 when the non-ADB mouse is selected (`:194`). SDL3 relative mouse mode and mouse grab are set in `src/gui-sdl/sdlscreen.c:743-745`. Wheel events are forwarded (`:388-394`). Left button with Ctrl can act as right button (`:326-332`).
- Tablet mode uses absolute window coordinates converted by `SDL_RenderCoordinatesFromWindow` (`:186-188`) and the screen size (`src/tablet.c:531-536`, searched).
- The event loop blocks with `SDL_WaitEventTimeout(&event, 100)` in the non-rendering-thread build (`:287`).
- Joysticks: `sdlgui.c:1231` calls `SDL_GetJoysticks`; the joystick subsystem is not initialized by `UI_Init`, which only passes `SDL_INIT_VIDEO` (`:464`).

## 8. File I/O

`src/file.c` was searched for its OS calls, and its function list was read; the I/O functions listed here were read in full.

- Disk images (SCSI hard disks, magneto-optical disks, floppy images) are opened as `FILE*` through `File_Open` (`src/file.c:681-720`, plain `fopen`) in `rb+` mode, falling back to `rb` (`src/scsi.c:1101,1105`, `src/mo.c:1498,1502`, `src/floppy.c:1154,1158`). Reads and writes go through `File_Read`/`File_Write`, which are `fseeko` followed by `fread`/`fwrite` of one block per call (`src/file.c:792-833`). Image sizes come from `File_Length` (`fseeko`/`ftello`, `src/file.c:376-411`).
- Large file support is required for images of 2 GiB and more (`CMakeLists.txt:141,300`).
- Locking: `File_Lock` takes `flock(LOCK_EX|LOCK_NB)` on the image file and falls back to a no-op when `flock` is missing (`src/file.c:743-785`).
- ROM files and the NeXTdimension ROM are read once with `fread` (`src/rom.c:57-65`, `src/dimension/nd_rom.cpp:91-102`). The ROMs are shipped as `.BIN` files installed into a data directory (`src/CMakeLists.txt:154-159`).
- Compressed input: `File_ZlibRead` uses zlib (`src/file.c:190-233`), `File_Load` picks the loader by extension (`:275`).
- The configuration file is a text file read and written with `fopen`, `fgets`, `fprintf` and a temporary file (`src/cfgopts.c:155-198,268-277,305-463`), with `unlink` on error (`:463`).
- Paths: `Paths_Init` (`src/paths.c:285-336`, read in full) uses `getenv("PATH")`, `getenv("HOME")`, `getcwd`, `mkdir`, and on Linux `readlink("/proc/self/exe")` (`:92,154-171,205,262,269,291`). The user directory is `~/.config/previous` (`:29,240-273`). The data directory is relative to the executable (`BIN2DATADIR`, `:320`; `CMakeLists.txt:315-326` gives `../share/previous`). The global config file path is `CONFDIR/previous.cfg`, `/etc` by default (`src/main.c:396-399`, `CMakeLists.txt:351-353`).
- Directory listing for the file chooser: `scandir`/`alphasort` through `opendir`/`readdir`/`fstat` (`src/scandir.c:23-83`, searched); `stat` for existence and directory tests (`src/file.c:424-447`).
- Screenshots, printer output and sound recordings: `File_Open(..., "wb")` (`src/grab.c:410-414,761`). The AIFF recording uses `File_Write` (`:719,765,792`). Printer output goes to `Grab_Print` (`src/printer.c:61`). PNG needs libpng (`src/grab.c:23,43-135`).
- Debug and logging: `File_Open` for the log and trace files (`src/debug/log.c:156-157`), `fopen` for the Ethernet log (`src/ethernet.c:820`) and the ND memory dump (`src/dimension/nd_devs.cpp:543-551`).
- NFS export: `sys/xattr.h` is used to netboot from a folder (`CMakeLists.txt:393-395`, `src/slirp/rpc/vfs.c` and the NFS directory search shows 14 file system calls in `vfs.c`, bodies not read).
- NVRAM: the NVRAM content is held in memory and written into the configuration; a separate NVRAM file was not found. Open: where `rtc.ram` is persisted (searched `src/rtcnvram.c`; the print routine at `:1102-1136` only prints).

## 9. Networking

Read in full: `src/enet_slirp.c`. Searched: `src/enet_pcap.c`, `src/ethernet.c`, the SLiRP tree.

- Two backends behind four function pointers each (`src/ethernet.c:782-794`): PCAP and SLiRP.
- SLiRP backend: the tick thread calls `select()` on the sockets SLiRP exposes, with a 500 microsecond timeout when SLiRP asks for none (`src/enet_slirp.c:56-85`). Packets from the guest go in through `slirp_input` (`:146-153`). Packets to the guest wait in a queue guarded by a mutex (`:42-52,132-144`).
- Port redirections to the guest: 42320 to 20, 42321 to 21, 42322 to 22, 42323 to 23, 42380 to 80 (`:181-185`).
- The SLiRP code needs BSD sockets, `select`, `fcntl`/`ioctl`, name lookup and threads. Included system headers found by search: `sys/socket.h`, `netinet/in.h`, `arpa/inet.h`, `sys/select.h`, `unistd.h`, `sys/un.h`, `sys/uio.h`, `sys/statvfs.h`, `sys/xattr.h`, and `winsock2.h` for Windows. File counts for socket-related calls: `slirp/misc.c` 36, `slirp/udp.c` 23, `slirp/socket.c` 20, `slirp/tcp_subr.c` 17, `slirp/rpc/vfs.c` 14 (file system), the NFS socket classes 7 to 9 each.
- PCAP backend: `pcap_lookupdev`, `pcap_open_live` with immediate mode, a BPF filter, non-blocking reads (`src/enet_pcap.c:131-182`). It needs promiscuous access to a real NIC.
- Without a TCP/IP stack, a bare-metal port has no networking unless it brings its own stack and either replaces the SLiRP socket layer or provides a BSD socket API. A port can disable Ethernet by leaving `ethernet.c` configured to no backend; whether the guest then boots and what the config dialog does are not verified.
- Signals: `signal(SIGPIPE, SIG_IGN)` and `signal(SIGFPE, SIG_IGN)` (`src/main.c:438-443`). SIGPIPE only matters for sockets.

## 10. Configuration GUI

Searched, not read in full: `src/gui-sdl/sdlgui.c` (1507 lines), `src/gui-sdl/dlg*.c` (24 files, 6,491 lines), `src/dialog.c`. The statements below come from the lines cited.

- It is a software-rendered dialog toolkit. Two bitmap fonts are compiled in as XBM arrays (`src/gui-sdl/sdlgui.c:23-24,116-117`; `font5x8.h`, `font10x16.h`) and drawn onto an `SDL_Surface` with palette, color key and RLE flags (`:65-134`).
- Dialogs run a nested event loop (`SDLGui_DoDialog`, `:1093-1290`): `SDL_GetWindowSize`, `SDL_CreateSurface` for the background save, `SDL_GetKeyboardState`, `SDL_GetMouseState`, `SDL_ConvertEventToRenderCoordinates` (`:1093,1152,1189,1199,1257,1290`), and `SDL_SetTextInputArea` for text entry (`:621`).
- Direct SDL events and delays in dialogs: `dlgKeyboard.c:79-124` (a key-capture dialog with `SDL_PollEvent`, `SDL_WaitEvent`, `SDL_PushEvent`), `dlgFileSelect.c` (10 SDL calls, `SDL_GetMouseState` at `:310`, `SDL_Delay(10)` at `:1043-1051`).
- File chooser: `scandir` at `src/gui-sdl/dlgFileSelect.c:786` plus `File_*` helpers.
- The dialog opens at start if no config file exists or when the option is set (`src/main.c:313-318`), and on the options shortcut. A fatal CPU error and quit confirmation use `DlgAlert_Query` (`src/main.c:172,195`), so the GUI is also needed for error messages.
- The GUI needs a drawing surface with pixel access, keyboard and mouse events, and a way to show it on top of the emulated screen.
- If a port removes the dialog, the configuration must come from the config file. `Main_StartMenu` calls `Dialog_DoProperty` when the file does not exist (`:314`), so a first run without a prepared config file needs that path handled.

## 11. 68k CPU core

Read: `src/cpu/CMakeLists.txt`, `src/cpu/hatari-glue.c`, `src/cpu/winuae_readme.txt` (head), `src/cpu/uae/vm.h`, `src/cpu/memory.c` (allocation part), `src/cpu/maccess.h` (allocation part). Others searched.

- Origin: "based on WinUAE 6.1.0 beta16+ (2026/09/30)" (`src/cpu/winuae_readme.txt:1-2`). Most files stay close to WinUAE; `custom.c` and `memory.c` are trimmed down (`:9-11`).
- Size: the CPU directory is 57,716 lines. Of that, 10,826 lines are the generators `gencpu.c` and `build68k.c` (run on the build machine, not part of the program), and `table68k` is 533 lines of data. The runtime core is 46,890 lines. Split: the CPU, MMU, memory and glue files are 21,951 lines, the debugger, disassembler and log writer 11,749 lines, and the FPU (`fpp.c`, `fpp_softfloat.c`, `fpp.h`) 4,855 lines. `softfloat/` adds 10,691 lines.
- The instruction handlers `cpuemu_31.c` and `cpuemu_32.c` are generated by `gencpu` during the build (`src/cpu/CMakeLists.txt:5,51`). They are not in the tree, so their size is not counted here and is open. A bare-metal build must run the two generators on a build machine and cross-compile their output.
- No JIT: `JIT` is commented out (`src/cpu/sysconfig.h:49`). JIT-only code in `newcpu.c` (`cpu_thread_run_jit`, with `setjmp` at `:6071`, and `longjmp` at `:8867`) is compiled only under `JIT`/`JIT_HAS_BUS_ERROR_RECOVERY` (`:54,6064-6072`, `:8863-8867`). I read only the lines around those.
- No host assembly in the built files. `fpp_native.c` contains x87 inline assembly and a call to `uae_vm_alloc` (`src/cpu/fpp_native.c:81-174`), but it is not in the CMake source list (`src/cpu/CMakeLists.txt:8-10,95`: `fpp.c fpp_softfloat.c` only). The 68881/68882/68040 FPU therefore runs on `softfloat/`.
- Host dependencies of the core: `malloc_aligned` with `posix_memalign` or `aligned_alloc` (`src/cpu/maccess.h:126-139`), 64 KB alignment; `bswap_16/32` (`src/cpu/maccess.h:110,115`, `src/includes/endianswap.h:17-52`); an unaligned-access switch (`src/cpu/maccess.h:21-31,56`); `fprintf(stderr)` for logs (`src/cpu/hatari-glue.c:76`); `getenv("INSNCOUNT")` (`src/cpu/newcpu.c:188`). `-fwrapv` is needed for the generated code (`src/cpu/CMakeLists.txt:59,74`).
- The core is entered with `m68k_go(true)` (`src/m68000.c:53`, `src/cpu/newcpu.c:7473`) and stopped by a special flag (`src/m68000.c:41-44`). It initializes `uae_sem` objects only if `cpu_thread` is set (`src/cpu/newcpu.c:5700-5710`); whether it is set is open.
- Host-visible memory the core uses: 65,536-entry tables of function pointers per access size (`src/cpu/memory.c:1000`, `bank_bget` and siblings; each entry is a pointer, so 512 KB per table on a 64-bit target).

## 12. NeXTdimension and i860 core

Read in full: `src/dimension/dimension.cpp`, `src/dimension/i860.cpp`, `src/dimension/nd_sdl.cpp`, `src/NextBus.cpp`. Searched: the rest of `src/dimension`.

- `src/dimension` is 9,085 lines; the i860 core itself (`i860*.cpp/.hpp/.h`) is 6,601 lines. `i860dec.cpp` (4,026 lines) is included into `i860.cpp` (`:641`) and the debugger `i860dbg.cpp` as well (`:646`).
- Origin: Jason Eckhardt's i860 emulator, MAME license, changed by Simon Schubiger (`src/dimension/i860.cpp:1-16`).
- Host requirements: a little-endian host. `i860_cpu_device::init` checks endianness with byte probes and exits with an error otherwise (`:459-541`). It uses host `float` and `double` through pointer casts of the register file (`:128-144`), so it needs IEEE 754 single and double arithmetic from the host FPU or a software float library. Whether `i860dec.cpp` uses other host math functions is open.
- Memory per NeXTdimension board allocated in the constructor: 64 MB RAM, 4 MB VRAM, 128 KB ROM, 512 bytes `dmem` (all `malloc_aligned`, `src/dimension/dimension.cpp:42-45`) plus a 65,536-entry bank pointer array (`:41`).
- Boards: slot numbers 2, 4, 6 ... (`ND_SLOT(num) = num*2+2`, `src/dimension/dimension.hpp:16`); up to `ND_MAX_BOARDS` (`src/NextBus.cpp:180`, value in `dimension.hpp`, not read).
- Host services used: threads and atomics as in section 3; sleep; `SDL` windows for the extra displays (section 5); `File_Open`/`fread` for the ROM (`nd_rom.cpp:91-102`); `fopen`/`fwrite` for a RAM dump (`nd_devs.cpp:543-551`); `Log_Printf`.
- The i860 can be disabled for a single-core host. `bI860Thread` is false on a one-core machine (`src/configuration.c:549`), and the emulator then warns "expect slow emulation" (`src/dimension/i860.cpp:442-444`).

## 13. DSP

`src/dsp` is 14,422 lines (`dsp.c`, `dsp_core.c`, `dsp_cpu.c`, `dsp_disasm.c`). It runs on the 68k thread. The one host call is under `#if DSP_COUNT_IPS` with the value 0 (`src/dsp/dsp_cpu.c:147-151,777,933`); note that line 151 includes `<SDL_timer.h>`, an SDL2 header path, but this code is compiled out. No threads or I/O were found in a search of the directory.

## 14. Memory

Allocation sizes read from `src/cpu/memory.c:104-144,1000-1048` and `src/dimension/dimension.cpp:42-45`.

| Item | Size | Source |
|---|---|---|
| Main RAM, 68040 mono or color non-turbo | 4 banks x 16 MB = 64 MB | `memory.c` `NEXT_RAM_BANK_SIZE` 0x01000000, `NEXT_RAM_ALLOC` |
| Main RAM, color machine | 4 x 8 MB = 32 MB | `NEXT_RAM_BANK_SIZE_C` |
| Main RAM, Turbo | 4 x 32 MB = 128 MB | `NEXT_RAM_BANK_SIZE_T` |
| Video RAM | 256 KB mono, 2 MB color | `NEXT_VRAM_ALLOC`, `NEXT_VRAM_COLOR_ALLOC` |
| I/O space | 128 KB | `NEXT_IO_ALLOC` |
| Boot ROM | 128 KB | `NEXT_EPROM_ALLOC` |
| Per NeXTdimension | 64 MB RAM + 4 MB VRAM + 128 KB ROM + 512 B + bank table | `dimension.cpp:42-45,41` |
| UI overlay | width x height x 4 bytes (about 3.7 MB at 1120 x 832 plus statusbar), twice (surface and copy) | `sdlscreen.c:530,543` |
| Textures | 1120 x 832 x 4 = 3.7 MB each, host side | `sdlscreen.c:569` |

The allocation is the maximum for the machine type, not the configured bank sizes. So a NeXTcube (mono 68040) takes 64 MB, and a Cube with one NeXTdimension takes 64 MB + about 68 MB. All are single `posix_memalign` calls with 64 KB alignment (`src/cpu/maccess.h:126-139`). No `mmap`, no `mprotect` and no `uae_vm_*` call is made in the built files (the `uae_vm_alloc` use is in the unbuilt `fpp_native.c:101`).

## 15. Other OS touch points

- `signal`: two calls (`src/main.c:440,442`).
- `getenv`: `PATH`, `HOME`, and Windows variables (`src/paths.c:92,205,216,219`), `LINES` and `COLUMNS` for the debugger (`src/debug/debugui.c:898-900`), `INSNCOUNT` (`src/cpu/newcpu.c:188`).
- `exit()`: `Main_ErrorExit` (`src/main.c:477`) and the i860 endianness check (`src/dimension/i860.cpp:541`).
- `__DATE__`/`__TIME__` in the log (`src/main.c:335`).
- `srand/rand`: seeded with `time(NULL)` (`src/main.c:488`).
- Debugger: the console debugger reads from the terminal with `readline` if available (`src/debug/debugui.c:20-21,868`). It is 9,407 lines in `src/debug` plus the 9,088-line UAE debugger `src/cpu/debug.c`. The debugger is not needed to run the emulation.
- App metadata: `SDL_SetAppMetadataProperty` (`src/gui-sdl/sdlevent.c:460`).
- Window icon: `Previous-icon.bmp` is installed (`src/CMakeLists.txt:154`); no load call was found in a search of `*.c`, so it may be unused.

## 16. Lines of code

Counts are physical lines of `.c`, `.cpp`, `.h`, `.hpp` and `.m` files, from `wc -l`, including comments and blank lines. No `cloc` was installed.

| Part | Lines |
|---|---|
| Whole `src` | 170,487 |
| Whole `src` without `ditool` and `ditool_cpp` (not linked into Previous) | 164,245 |
| 68k CPU directory, runtime part | 46,890 |
| Generators `gencpu.c`, `build68k.c` (build tools) | 10,826 |
| FPU emulation `softfloat/` | 10,691 |
| SLiRP, NFS and RPC (`src/slirp`) | 23,721 (core 13,138, NFS 4,144, RPC 6,439) |
| DSP 56001 | 14,422 |
| NeXTdimension and i860 | 9,085 (i860 core 6,601) |
| Debugger (`src/debug`) | 9,407 |
| Device emulation in `src/*.c` (SCSI, MO, floppy, DMA, ESP, ADB, KMS, SCC, RTC, printer, video chips and so on) | 17,591 |
| Host-facing top-level files (`file.c`, `paths.c`, `cfgopts.c`, `configuration.c`, `dialog.c`, `grab.c`, `main.c`, `scandir.c`, `shortcut.c`, `str.c`, `timing.c`) | 5,852 |
| Network backends `enet_slirp.c`, `enet_pcap.c` | 384 |
| SDL layer: `sdl*.c/.h` (host, screen, audio, event, keymap, statusbar, GUI toolkit) | 4,655 |
| Configuration dialogs `dlg*.c` | 6,491 |
| Bitmap fonts | 540 |
| Headers in `src/includes` | 2,898 |

The part that touches the host is about 4,655 lines in `gui-sdl/sdl*` plus 161 + 56 lines in `nd_sdl` plus 384 lines of network backends plus the file and path handling (`file.c` 1,176, `paths.c` 345, `scandir.c` 286, `cfgopts.c` 467), together roughly 7,600 lines. Adding the configuration dialogs gives about 14,100 lines, and adding the built-in network stack gives about 37,800 lines. Against 164,245 lines, the SDL layer plus file handling is under 5 percent, and with dialogs and the network stack it is about 23 percent. The emulation (CPU, FPU, DSP, i860, devices) is the rest and does not call the OS directly except through the `host.h`, `audio.h`, `screen.h`, `event.h` and `keymap.h` functions and the stdio calls above.

## 17. What a bare-metal port must replace

"Call sites" counts source locations that call into the service, from the greps cited above. They are rough, and slirp counts are by file.

| Host service | Previous files that use it | Call sites (rough) |
|---|---|---|
| Threads, create/wait/priority | `main.c`, `dimension/i860.cpp`, `enet_slirp.c`, `enet_pcap.c`, `slirp/nfs/*`, `slirp/rpc/*`, `gui-sdl/sdlscreen.c`, `dimension/nd_sdl.cpp` | 14 |
| Mutexes, spinlocks, atomics, semaphores | `enet_slirp.c`, `enet_pcap.c`, `grab.c`, `main.c`, `dimension/*`, `timing.c`, `slirp/*`, `gui-sdl/sdlscreen.c`, `gui-sdl/sdlevent.c` | about 100 |
| Monotonic high-resolution counter | `timing.c` | 6 |
| Sleep in ms or microseconds | `timing.c`, `main.c`, `i860.cpp`, `enet_*.c`, `nd_sdl.cpp`, `slirp/misc.c` | 10 |
| Wall-clock time and calendar | `timing.c`, `main.c`, `rtcnvram.c` through `timing.c` | 6 |
| Window, renderer, textures, present, vsync, full screen, scaling | `gui-sdl/sdlscreen.c`, `dimension/nd_sdl.cpp` | about 165 SDL references |
| UI surface and overlay blend | `gui-sdl/sdlscreen.c`, `sdlstatusbar.c`, `sdlgui.c` | about 175 SDL references |
| Keyboard and mouse events, grab, relative mode | `gui-sdl/sdlevent.c`, `sdlkeymap.c`, `sdlscreen.c` | about 290 SDL references |
| Audio output and two recording streams | `gui-sdl/sdlaudio.c`, `snd.c` | 12 API functions, 32 SDL references |
| Event loop and cross-thread special events | `gui-sdl/sdlevent.c`, `main.c`, `video.c`, `dimension/dimension.cpp` | 8 functions in `event.h`, about 15 uses |
| Configuration dialogs | `gui-sdl/dlg*.c`, `sdlgui.c`, `dialog.c` | 22 direct SDL references in dialogs, all drawing through `sdlgui.c` |
| File access to disk, MO and floppy images: open, seek, read, write, length, lock | `file.c`, `scsi.c`, `mo.c`, `floppy.c` | 7 open sites, 11 read/write sites, 3 length sites |
| Storage backend for the images | same | large-file seek, up to multiple GB |
| Config file and ROM files | `cfgopts.c`, `configuration.c`, `rom.c`, `dimension/nd_rom.cpp`, `paths.c` | about 20 |
| Directory listing and path services | `scandir.c`, `file.c`, `paths.c`, `gui-sdl/dlgFileSelect.c` | about 30 |
| Environment, cwd, `/proc/self/exe` | `paths.c`, `debug/debugui.c`, `cpu/newcpu.c` | 8 |
| Screenshot, printer and AIFF output | `grab.c`, `printer.c` | 6 file opens, optional libpng |
| zlib decompression | `file.c` | 6 |
| Aligned heap allocation of up to about 200 MB | `cpu/maccess.h`, `cpu/memory.c`, `dimension/dimension.cpp` | 8 |
| BSD sockets and `select` | `slirp/*`, `enet_slirp.c` | over 100 |
| Raw NIC access | `enet_pcap.c` | about 15 libpcap calls |
| Signals | `main.c` | 2 |
| Terminal, readline | `debug/debugui.c` | optional |
| Build-time code generators (run on the build host) | `cpu/build68k.c`, `cpu/gencpu.c`, `cpu/readcpu.c` | 2 programs |
| libc and libm: stdio, string, `pow`, `malloc`, `qsort`-style helpers, `setjmp` (libpng only) | everywhere | not counted |
| Little-endian host, IEEE float and double, 64-bit integers | `dimension/i860.cpp` and the CPU core | checked at start |

What is not in the table because the code does not use it: `mmap`, `mprotect`, `fork`, `exec`, `dlopen`, `pthread` directly (all thread use goes through `host.h` or SDL), a JIT, host assembly in the built files, and a GPU API beyond what SDL renders with.

## 18. Open points

- The generated sources `cpuemu_31.c` and `cpuemu_32.c` are not in the tree, so their size and whether they use any host feature beyond what the headers show is not known.
- How zlib is located by CMake is not shown by any file I read.
- `src/gui-sdl/sdlkeymap.c` tables, `src/gui-sdl/dlg*.c` bodies and `src/dialog.c`, `src/slirp/**`, and `src/dimension/i860dec.cpp` were not read in full. Claims about them come from the greps cited.
- Where the NVRAM content is persisted was not found.
- Whether `i860` memory sharing between the 68k thread and the i860 thread is correct on a weakly ordered CPU was not analyzed.
- Which `SND_BUFFER_SIZE` the sound DMA uses (in `src/includes/snd.h`) was not read.
- Whether `currprefs.cpu_thread` is ever set (`src/cpu/newcpu.c:5702`) was not checked.
- The release optimization level of the Previous build was not checked.
