== Getting Started === Setting Up the Toolchains JoeyLib cross-compiles for five machines and for DVX, a DOS desktop whose programs run in windows (see <<_dvx_programs>>), builds native programs for the machine you work on through SDL3 (see <<_sdl3_programs>>), builds web pages through Emscripten (see <<_web_programs>>), and builds PSP homebrew through pspdev (see <<_psp_programs>>). It builds on Linux x86-64, and on Windows inside WSL2. One script installs every cross toolchain, pinned to a known-good version and checked against a published checksum, into `toolchains/` in the repository. The only things it installs anywhere else are ordinary packages from your distribution (apt on Debian and Ubuntu, dnf on Fedora), and only when you ask it to. Two commands set up a new machine: [source,console] ---- $ ./install.sh deps toolchains --install $ ./install.sh install toolchains ---- The first installs the distribution packages the toolchains need; it runs `sudo` once. The second downloads or builds every cross toolchain. The first run takes about an hour, almost all of it compiling the Apple IIgs and Amiga compilers. To start working sooner, install only the ports you need: [source,console] ---- $ ./install.sh install toolchain:dos toolchain:atarist ---- Running `install` again is cheap. Components that are already current are skipped, and a component is rebuilt only when its pinned version changes. No environment setup is needed afterwards. `make` reads the tool paths the installer records in `toolchains/config.mk`, and the scripts in `scripts/` load the same settings themselves. To use a cross compiler by hand in a shell, `source toolchains/env.sh` sets the same variables. If a port's toolchain is missing, its build stops at once and says what to install: [source,console] ---- $ make amiga make/amiga.mk:13: *** toolchain:amiga is not installed; run: ./install.sh install toolchain:amiga. Stop. ---- Check what the build can see: [source,console] ---- $ make status Toolchain availability: iigs: yes amiga: yes atarist: yes dos: yes x68000: yes dvx: yes ---- ==== Installer reference `./install.sh [options] COMMAND [TARGETS...]`, run from the top of the repository. [cols="1,3"] |=== | Command | What it does | `list` | Every component, the groups it belongs to, and whether it is current, outdated or missing. | `install TARGETS` | Installs or updates the targets and everything they depend on, and smoke-tests each one it installs. A failure does not stop the rest; the failures are listed at the end. | `deps TARGETS` | Lists the distribution packages the targets need and which are missing. With `--install`, installs the missing ones. | `doctor` | Checks the host, then smoke-tests every installed component (each cross compiler compiles a file, and every prebuilt program must find its shared libraries, for example). It reports anything out of date, any component whose files have gone, and any part of a port you installed only partly, such as FS-UAE without a Kickstart. | `import PATH...` | Installs the files that are not downloaded (see <>) from files, folders, `.zip` archives or Amiga `.adf` disk images, matched by checksum. | `bundle FILE` | Packs those files, as present on this machine, into `FILE` (a `.tar.gz`) for a project to host. |=== A target is a component name from `list`, or a group: [cols="1,3"] |=== | Target | Means | `toolchain:PORT` | Everything needed to build for one port: `iigs`, `amiga`, `atarist`, `dos`, `x68000`, `dvx`, `sdl3` (SDL 3, built as a static library that every SDL3 program links in, so it runs without SDL installed), `web` (Emscripten, through emsdk, and SDL 3 built with it), `win32` (llvm-mingw; its emulator is a portable Wine), `win16` (Open Watcom; its emulator is Windows for Workgroups 3.11 in DOSBox Staging, set up from your own install disc, see <>) or `psp` (pspdev's prebuilt release, and SDL 3 built with it; its emulator is PPSSPP's headless build, patched for the gates). | `emulator:PORT` | Everything needed to run that port's programs: the emulator and its firmware. | `PORT` | Both of the above. | `toolchains` | Every port's toolchain. | `emulators` | Every port's emulator. | `all` | Everything except optional components (`dvx`, which unpacks DVX from a DVX tree of yours), which are installed by name. `toolchains` and `emulators` leave them out too. |=== [cols="1,3"] |=== | Option | Effect | `-y`, `--yes` | Install missing distribution packages without stopping to ask. | `--install` | With `deps`: install the missing packages. | `--force` | Reinstall components even when they are current. | `--jobs N` | Parallel build jobs. The default is every core. | `--keep-build` | Keep the scratch build trees (normally deleted after a successful install). | `--llvm816-dir PATH` | Use an existing checkout of llvm816, the IIgs compiler, instead of cloning it. For working on the compiler and JoeyLib together. The setting is remembered; `--llvm816-dir clone` goes back to a managed clone. | `--dvx-dir PATH` | The DVX tree the `dvx` component takes DVX and its SDK from: its distribution, `dist/dvx.zip` (DVX as it installs) and `dist/sdk.zip` (the SDK). The default is a `dvxgui` checkout beside this repository; the setting is remembered. The component only reads that tree: it unpacks both zips into `toolchains/opt/dvx`, and a new zip there makes the component out of date, so `install dvx` unpacks it again. (A tree whose `dist/` has no `sdk.zip` yet gives its built SDK, `bin/sdk`, instead.) |=== Everything the installer creates stays inside `toolchains/`: [cols="1,3"] |=== | Path | Contents | `toolchains/opt/` | The installed components, one directory each. | `toolchains/firmware/` | ROM and system-disk images for the emulators. | `toolchains/cache/` | Downloads, kept and re-verified by checksum on reuse, and scratch build trees. | `toolchains/state/` | Which version of each component is installed, and settings such as `--llvm816-dir` and `--dvx-dir`. | `toolchains/config.mk`, `toolchains/env.sh` | The tool paths, for `make` and for shells. |=== Nothing in `toolchains/` is part of the repository: delete the whole folder to return the machine to a clean state, and `./install.sh` re-creates it. The installer itself lives in `install.sh` and `installer/` (its component recipes, patches and package lists), and the build helpers the makefiles call live in `make/support/`. === Building [source,console] ---- $ make # every target whose toolchain is installed $ make iigs # Apple IIgs only $ make amiga # Commodore Amiga only $ make atarist # Atari ST only $ make dos # MS-DOS only $ make x68000 # Sharp X68000 only $ make dvx # DVX only $ make sdl3 # native programs through SDL3 only $ make web # web pages through Emscripten only $ make win32 # Windows programs only $ make win16 # Windows 3.x programs only $ make psp # PSP homebrew (EBOOT.PBP) only $ make haiku # Haiku x86_64 programs only $ make haiku32 # Haiku x86 (gcc 2.95, the BeOS ABI) programs only $ make beos # BeOS R5 x86 programs only $ make clean # remove all build outputs ---- Built programs land under `build//bin/`. The IIgs additionally produces a bootable disk image: [source,console] ---- $ make iigs-disk # build/iigs/bin/joey.2mg ---- [TIP] ==== Never judge a retro build by its exit status alone. Check that the file you expect actually appeared and has a recent timestamp. `make iigs` builds the library but *no examples* -- `make iigs-disk` is what builds and packages them. ==== === Hello, JoeyLib The smallest complete program. This is `examples/hello/hello.c`: [source,c] ---- #include #include int main(void) { jlConfigT config; config.codegenBytes = 8 * 1024; if (!jlInit(&config)) { fprintf(stderr, "jlInit failed: %s\n", jlLastError()); return 1; } printf("JoeyLib %s\n", jlVersionString()); printf("Platform: %s\n", jlPlatformName()); printf("Hello from JoeyLib.\n"); jlShutdown(); return 0; } ---- Three things to notice, because they apply to every JoeyLib program: . *One header.* `#include ` pulls in everything except the optional serial add-on. . *`jlInit` can fail*, and when it does `jlLastError()` tells you why. Always check it. . *`jlShutdown` is not optional.* It restores the display mode, releases the Memory Manager blocks or DOS memory the library took, and on several ports un-hooks interrupt vectors. Skipping it can leave the machine in an unusable state. === Something on the Screen `hello.c` prints text but never draws. Here is the smallest program that puts graphics on screen and waits for a keypress: [source,c] ---- #include int main(void) { jlConfigT config; jlSurfaceT *stage; uint16_t colors[SURFACE_COLORS_PER_PALETTE]; uint8_t i; config.codegenBytes = 8 * 1024; if (!jlInit(&config)) { return 1; } stage = jlStageGet(); // the library-owned back buffer // Build palette 0. Entry 0 is forced black whatever we put here. for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) { colors[i] = 0x000; } colors[1] = 0xFFF; // white colors[2] = 0xF00; // red colors[3] = 0x00F; // blue jlPaletteSet(stage, 0, colors); // Every scanline uses palette 0 (this is also the default). jlScbSetRange(stage, 0, SURFACE_HEIGHT - 1, 0); jlSurfaceClear(stage, 0); jlFillRect(stage, 40, 30, 240, 140, 3); jlDrawRect(stage, 40, 30, 240, 140, 1); jlDrawLine(stage, 0, 0, SURFACE_WIDTH - 1, SURFACE_HEIGHT - 1, 2); jlStagePresent(); // nothing is visible until this runs jlWaitForAnyKey(); jlShutdown(); return 0; } ---- The critical line is `jlStagePresent()`. All drawing goes to an off-screen back buffer called the *stage*; nothing reaches the display until you present. === Running What You Built `joey-run`, at the top of the repository, starts a built program in the right emulator (an SDL3 program needs none: it runs on your machine). The emulators, and the firmware they boot, come from the installer: [source,console] ---- $ ./install.sh install emulators # every machine $ ./install.sh install emulator:dos # or just one ---- MAME (for the IIgs and X68000) is always built from source, because the test gates need JoeyLib's patches to it. DOSBox Staging is upstream's own Linux release. Hatari and FS-UAE are distribution packages on Debian and Ubuntu; Fedora does not ship the versions the gates expect, so there the installer builds both from source. Point `joey-run` at a program, or name an example together with `--port`: [source,console] ---- $ ./joey-run build/dos/bin/DRAW.EXE $ ./joey-run build/amiga/bin/Draw $ ./joey-run --port atarist draw $ ./joey-run build/iigs/bin/DRAW.2mg $ ./joey-run --port dvx draw $ ./joey-run --port sdl3 draw $ ./joey-run --port web draw $ ./joey-run --port win32 draw $ ./joey-run --port win16 draw $ ./joey-run --port psp --headless --snapshot draw.png draw $ ./joey-run --port haiku draw $ ./joey-run --port beos draw ---- It recognises the machine from the file itself. Interactive runs use the program's own folder or disk image, so saves and logs persist from one run to the next. Nothing is written to your home directory. [cols="1,3"] |=== | Option | Effect | `--port PORT` | The machine, when naming an example rather than a file. | `--data DIR` | Copy `DIR`'s files into the program's `DATA` folder for this run, for example the game an interpreter plays. | `--headless` | No window and no sound, for scripts and continuous integration. Stops after 60 seconds unless `--seconds` says otherwise. | `--seconds N` | Stop after `N` seconds. | `--snapshot FILE` | Save a PNG of the screen when the run stops. | `--machine rom01` | Apple IIgs: the ROM 01 machine instead of ROM 03. | `--machine st` | Atari: Hatari's machine, `st`, `megast`, `ste` or `megaste`. By default an ST-build program (`make atarist`) gets a plain ST and an STe-build one (`make ste`) an STe; the STe build runs only on `ste` and `megaste`. | `--machine a1200` | Amiga: FS-UAE's machine, `a500` (the default), `a1200` (68EC020, AGA) or `a4000` (68040, AGA), each on its own Kickstart 3.1. The AGA ones need their Kickstarts (`./install.sh install emulator:aga`). | `--ram SIZE` | Apple IIgs: MAME's memory size. The default is `4M`. | `--debug` | Apple IIgs and X68000: start MAME's debugger. | `--serial HOST:PORT` | Connect the machine's serial port to the TCP server at `HOST:PORT`. See <>. | `--serial-listen PORT` | The machine's serial port waits for a TCP connection on `PORT` instead. | `--serial-baud N` | Apple IIgs and X68000: the rate MAME's end of the wire runs at. The default is 9600. | `-- ARGS` | Pass `ARGS` to the program. DOS, SDL3, Win32 and Win16 only: the other machines' autostart cannot pass arguments, and a page and a PSP program have none. |=== ==== DVX `joey-run` runs a DVX program in DOSBox Staging on a copy of the DVX the installer took from a DVX tree (`./install.sh install dvx`), set up so DVX boots straight into the program: it is DVX's desktop program, in its own window on a 1024x768 desktop (640x480 with `--headless`), with a Sound Blaster 16. The DVX of interactive runs is kept in `build/dvx/run`, so what the program saves, in DVX's `CONFIG` folder, is there the next time; closing DOSBox ends the run. When the program ends, DVX stays up with an empty desktop. ==== SDL3 An SDL3 program is a native program, so `joey-run` simply starts it, in a window; running the file yourself does the same. `--headless` runs it on a private virtual display at one window pixel per stage pixel, so a `--snapshot` is exactly the 320x200 stage. The window's size, shape and full screen are set by environment variables (<<_sdl3_programs>>). The SDL3 port has no serial port. ==== Web A web program is a page: `joey-run` serves its folder over HTTP on this machine (to this machine only) and opens it in your browser; Ctrl+C stops the server. With `--headless` it runs in headless Chromium instead, at one canvas pixel per stage pixel, so a `--snapshot` is the stage. Its `DATA/` is packed in when it is built, so `--data` does not apply. ==== Win32 A Windows program runs under Wine: in a window on your display, with its Wine settings kept in the project's `build/win32/prefix` so they are set up once (nothing goes in `~/.wine`; a run with `--serial` uses a private one, so the kept COM1 is left alone). The window's size, shape and full screen are set by the same environment variables as SDL3's, and Alt+Enter switches. With `--headless` it runs on a private virtual display at one pixel per stage pixel, so a `--snapshot` is the stage. ==== Win16 A Windows 3.x program runs on Windows for Workgroups 3.11 in DOSBox Staging, on a fresh copy, each run, of the Windows the installer set up from your own disc (`./install.sh install emulator:win16`): 800x600 in 256 colours through the S3 Trio64V+ driver, with Windows' Sound Blaster driver and two drivers of JoeyLib's own: KPJOY.DRV, the joystick driver Windows 3.1 lacks (DOSBox's game port, two sticks), and KPCOMM.DRV in place of Windows' COMM.DRV (COM1 up to 115200 baud). Their components, `kpjoy` and `kpcomm`, build them from the kpjoy and mscomm projects, checkouts beside JoeyLib's (`../kpjoy`, `../mscomm/drv`; `KPJOY_DIR` and `KPCOMM_DIR` name others). The program is Windows' shell, so Windows ends when the program does, and its folder is drive D:, so what it saves stays there. The window's size, shape and full screen are set by the same environment variables as SDL3's: `joey-run` puts them in the DOS environment Windows starts from. Alt+Enter is DOSBox's own full-screen key, so the program switches with Alt and the keypad's Enter. With `--headless` it runs on a private virtual display at one window pixel per stage pixel, and a `--snapshot` is DOSBox's picture of the whole 800x600 screen. ==== PSP A PSP program runs only with `--headless` here, under PPSSPP's headless build (`./install.sh install emulator:psp`), from a copy of its folder on a private memory stick, `PSP/GAME/NAME`, as on a PSP. PPSSPP checks every memory access as a PSP does: an unaligned one stops the run, and `joey-run` names the function it was in. A `--snapshot` is the PSP's whole 480x272 screen. To play the program, copy `build/psp/bin/NAME` into `PSP/GAME/` on a PSP's memory stick, or open its `EBOOT.PBP` in PPSSPP. The PSP has no serial port. ==== Haiku and BeOS A Haiku (`haiku`, `haiku32`) or BeOS R5 (`beos`) program runs in that port's VM under QEMU (`./install.sh install emulator:haiku`, `emulator:haiku32` or `emulator:beos`), in QEMU's window, from a copy of its folder on a second disk. The machine boots from a disk it never writes to, runs the program, and powers off when the program ends; what the program wrote in its folder (saves, its log) is then copied back. The window's size, shape and full screen are set by the same environment variables as SDL3's. Haiku's VM has sound; BeOS's has none, and is taken through R5's boot loader menu to its fail-safe video mode (1024x768, 16-bit colour), as R5 has no driver for any of QEMU's video cards. With `--headless` it runs without a window at one window pixel per stage pixel; `--seconds` counts from when the program starts, after the boot, and a `--snapshot` is QEMU's picture of the whole screen. Neither has a serial port here. A headless run with a snapshot is the quickest way to check a change, and on SDL3 it takes seconds, not the minutes an emulated machine needs: [source,console] ---- $ ./joey-run --headless --seconds 5 --snapshot draw.png --port sdl3 draw $ ./joey-run --headless --seconds 30 --snapshot draw.png --port dos draw ---- ==== The Apple IIgs The IIgs runs programs from disk images. `make -f make/iigs.mk iigs-game-disk GAME=DRAW` builds a bootable one, `build/iigs/bin/DRAW.2mg`, which is also how a game ships. Given a data disk instead, such as `joey.2mg`, `joey-run` boots GS/OS beside it and you open the program in the Finder. Given a bare program, `build/iigs/bin/DRAW`, it builds both disks itself, which needs the IIgs toolchain. ==== Serial Ports `--serial HOST:PORT` plugs the emulated machine's serial port into a TCP connection: to a BBS, a program of your own, `nc -l PORT`, or another `joey-run` started with `--serial-listen PORT`, which waits for the connection instead. `HOST` is a name or an IPv4 address, and a listening port accepts connections on every network interface. The link carries raw bytes, with no telnet negotiation. `examples/sertest` sends a banner and then echoes whatever it receives, which makes it a quick test of a link: [source,console] ---- $ nc -l 2323 # in one terminal $ ./joey-run --serial localhost:2323 --port dos sertest ---- The port is each machine's usual one: [cols="1,2,3",options="header"] |=== | Machine | Port | How the link is made | Apple IIgs | The modem port | MAME connects by itself. To listen, `socat` holds the TCP port and MAME connects to it. The printer port and a Super Serial Card are not connected. | X68000 | The RS-232C port | As on the IIgs. | Atari ST | The Modem port | Hatari's serial port can only be a device, so `socat` joins a pseudo-terminal to the network both ways. | Amiga | `serial.device` unit 0 | FS-UAE listens by itself, and holds the whole machine until the connection arrives. To connect out, `socat` joins a pseudo-terminal to the network. | DOS | COM1 | DOSBox does both by itself. | DVX | COM1 | As on DOS; DVX's serial library drives the port. | Win16 | COM1 | As on DOS; Windows' own communications driver drives the port. | Win32 | COM1 | As on the ST: Wine's COM1 can only be a device, so `socat` joins a pseudo-terminal to the network both ways. | SDL3, Web, PSP | None | These ports have no serial port; `jlSerialOpen` fails there. |=== `socat` comes from your distribution: `./install.sh deps host --install` fetches it with the other host tools, and `joey-run` says so when a run needs it and it is missing. A few differences are worth knowing: * On the IIgs and X68000 MAME's null modem clocks the wire, at 9600 baud, 8N1, unless `--serial-baud` says otherwise. A program that opens the port at another rate receives garbage. The other emulators pass bytes at whatever rate the program sets. * When nothing answers at `HOST:PORT`, `joey-run` stops, except on the IIgs and X68000, where MAME prints `Unable to load image` and runs with the port unplugged. * While a port is listening, what the program sends waits for the connection, except on DOS, DVX and Win16, where DOSBox drops it just as a cable with nothing on the other end would. `examples/sertest` sends its banner at once, so there a client that connects late sees only the echo. ==== Firmware that is not downloaded Almost everything the emulators need is downloaded: EmuTOS for the Atari ST, the Apple IIgs ROMs, GS/OS 6.0.4 and the CFFA2 card's firmware, and Sharp's own free release of the X68000 IPL-ROM and Human68k 3.02. A few files are not, because they were never released for free: the Amiga Kickstart 3.1 ROM, the Amiga `serial.device` and `lowlevel.library` from Workbench 3.1 (Kickstart 3.1 has neither in ROM, and a program cannot open the serial port without the one or see a joystick without the other), the A1200's and A4000's Kickstart 3.1 (optional: only for running the Amiga programs on those machines), the X68000 CG (font) ROM, for the Win16 port the Windows for Workgroups 3.11 install disc (as an `.iso`) and the S3 Trio64V+ 1.70.04 Windows 3.1 display driver, from which the installer sets Windows up unattended, and for the BeOS port the BeOS 5 Professional CD (its CloneCD `.img`, or the `.7z` holding it), from which the installer takes R5's headers and libraries and makes the VM. Supply your copies once: [source,console] ---- $ ./install.sh import ~/my-roms ---- `import` takes files, folders, `.zip` and `.7z` archives and Amiga `.adf` disk images (point it at your Workbench 3.1 disk for `serial.device` and `lowlevel.library`, and at the S3 driver's `.zip` for its three files), identifies each file by its checksum (the list is `installer/files.sha1`) and puts it in place. A project that hands its setup to testers can host these files instead: `./install.sh bundle joeylib-private.tar.gz` packs the ones present on a machine that has them, and setting `JOEY_BUNDLE_URL` to where it is hosted, in `installer/bundle.conf`, makes every `install` fetch it automatically. === The Example Programs The `examples/` directory is the best available reference, and every example is built for every platform: [cols="1,4", options="header"] |=== |Example |What it shows |`hello` |Minimal init, version query, shutdown. |`draw` |Every drawing primitive, laid out in four quadrants. |`pattern` |Palette and per-scanline palette (SCB) work. |`keys` |Typed-character input and the key predicates. |`joy` |Joystick axes, buttons and recalibration. |`sprite` |Sprite loading, compiling, save-under and restore. |`audio` |Tracker music, `.MOD` playback, digital sound effects, a streamed sound and the noise generator. |`save` |Save files and the disk-space query. |`tilebank` |Loading a baked tile bank both ways, and a bank for another machine refused. |`serial` |The optional RS-232 add-on. |`adventure` |A small complete game. |`uber` |The validation and benchmark harness. |=== Two complete games built on JoeyLib live in repositories of their own and show the library under real load: Space Taxi, a port of the 1984 Commodore 64 game, and JoeyAGI, an interpreter for Sierra's AGI adventures. Each builds from its own directory against a JoeyLib checkout, as any program of yours would (<<_building_your_program>>). On DVX every example is a folder of its own under `build/dvx/bin/apps/joeylib/` with its help file (<<_dvx_programs>>). On SDL3 every example is a native program in `build/sdl3/bin/`, beside the shared `DATA/` (<<_sdl3_programs>>), on the web a page in a folder of its own, `build/web/bin/NAME/` (<<_web_programs>>), and on the PSP a folder for `PSP/GAME/`, `build/psp/bin/NAME/` with `EBOOT.PBP` and its `DATA/` (<<_psp_programs>>).