Compiling Raptor: Call of the Shadows from Source on a Modern Mac


I recently took on a challenge that turned out to be far more interesting than I expected: compiling Raptor: Call of the Shadows — a classic 1994 DOS shoot-‚em-up by Cygnus Studios — from its original source code on an Apple Silicon Mac, and getting it running in DOSBox-X. What started as a straightforward „just compile it“ exercise turned into a deep dive through decades-old toolchain quirks, cross-platform headaches, and a particularly sneaky data file mismatch that took real detective work to crack.

The Goal

The Raptor source code was released publicly on GitHub (skynettx/dosraptor). The original build environment was DOS-based, using Borland TASM for assembly, Watcom C for the C code, and a DOS-native Makefile. My goal was to compile this on macOS (ARM64) using Open Watcom v2 and run the resulting executable in DOSBox-X — no Windows VM, no DOS machine, just a Mac.

Setting Up the Toolchain

Open Watcom v2 provides native macOS ARM64 binaries in their nightly snapshots, which was the key enabler. I wrote a scripts/setup.sh that extracts the toolchain and prepares the environment. But even getting the source to compile required solving several problems:

  • DOS EOF markers: 62 source files contained \x1a (Ctrl-Z) EOF markers — a DOS convention that modern compilers choke on. The setup script strips them automatically using Python.
  • Backslash include paths: The code uses DOS-style paths like #include <sys\types.h>. On macOS, the backslash is literal, not a path separator. The fix: create symlinks with literal backslash characters in the filename inside the Watcom include directory.
  • Makefile incompatibilities: The original Makefile uses .BEFORE directives and backslash paths that don’t work on macOS. Rather than fight wmake, scripts/build.sh bypasses it entirely and compiles each file directly.
  • Object file extensions: wcc386 on macOS outputs .o files by default, but the linker expects .OBJ. Solved with the -fo=STEM.OBJ flag.
  • Assembly syntax: Two assembly files (TILE_A.ASM and MOVIE_A.ASM) used bare ENDP directives without the procedure name, which WASM (Watcom Assembler) requires. A quick edit fixed those.

After all that, the build produced a working rap.exe — a 416 KB DOS/4G protected-mode executable.

The Crash: GLB_FetchItem Failed

The game compiled. It linked. DOSBox-X loaded it. And then… it crashed during the intro sequence:

GLB_FetchItem: failed on 0 bytes, mode=2.

This error meant the game was trying to lock a GLB resource item that had zero bytes — essentially, it was looking up the wrong item in the game’s data files. mode=2 corresponds to FI_LOCK, and the call was coming from SND_PlaySong() trying to load APOGEE_MUS (the Apogee theme music).

I traced through the initialization with debug printf statements to pinpoint the exact moment of failure. The game made it all the way through graphics init, palette loading, shadow tables, flame effects, input setup, and object initialization — only to die the instant it tried to play the first piece of music.

The Root Cause: Off-by-One in the GLB Data Files

This is where it got interesting. Raptor stores all its game assets (sprites, music, text, palettes) in encrypted .GLB archive files. The source code references items by index via #define constants in FILE0000.INC and FILE0001.INC. For example:

#define APOGEE_MUS   0x00000061 //ITEM:097

This says „APOGEE_MUS is item 97 in FILE0000.GLB.“ But when I wrote a Python script to decrypt and parse the actual GLB file header (using the game’s own encryption key "32768GLB" with seed 0x19), I discovered:

  • Item 96 in the GLB file was APOGEE_MUS (size: 1,267 bytes — valid music data)
  • Item 97 was START_SFX (size: 0 bytes — a label/marker with no data)

Every single item after index 0 was off by one.

The INC file expected LASTSCR3_TXT at index 3, but the GLB data file didn’t contain that item at all. The GLB went straight from LASTSCR2_TXT (index 2) to FILE_ID_DIZ (index 3), while the INC file had FILE_ID_DIZ at index 4. This single missing item shifted every subsequent index by one.

The same pattern appeared in FILE0001.GLB, where RAPLOG2_PIC (a Taiwan version logo) was missing, along with two other Taiwan-specific items. There the shift grew to 3 or even 4 positions deep into the file. In total, both GLB files had items missing compared to what the source code expected — the data files were simply from a slightly different build of the game than the source code.

The Fix

The solution was to regenerate the .INC files directly from the actual GLB data. I wrote a Python script that:

  1. Reads each GLB file and decrypts its header entries using the game’s rolling cipher
  2. Extracts the real item name, index, and size for every entry
  3. Generates corrected #define statements with the actual indices
  4. Maps any items referenced in the source but missing from the GLB to EMPTY (~0) — these turned out to be exclusively Taiwan/LCR localization items behind #ifdef guards
  5. Deduplicates animation frame entries (e.g., 30 GLB items all named CHASE_AGX — only the first frame gets the #define)

After replacing both INC files and rebuilding, the game booted cleanly — Apogee logo, Cygnus logo, intro sequence, main menu, and gameplay all working in DOSBox-X on Apple Silicon.

Lessons Learned

  • Data and code versioning matter: The source code and the data files came from different builds of the game. Even a single missing item in a packed archive cascades into every subsequent lookup being wrong.
  • Debug printf is still king: Adding strategic printf/fflush calls throughout the initialization sequence let me pinpoint exactly where the crash occurred, even running inside a DOS emulator.
  • Don’t trust header files blindly: The .INC files looked perfectly reasonable. The bug was invisible until you compared them byte-by-byte against the actual data.
  • Encrypted formats need custom tooling: The GLB files use a simple rolling cipher. Writing a small Python decryptor to inspect the archive headers was essential — without it, I’d have been guessing at indices forever.

The Result

Raptor: Call of the Shadows, compiled from source on an M-series Mac, running in DOSBox-X. The full build pipeline — setup.sh, build.sh, run.sh — takes the project from a fresh clone to a playable game in under a minute. Not bad for a 30-year-old DOS game that was never meant to leave its Borland/Watcom DOS toolchain.

Tools used: Open Watcom v2 (macOS ARM64), DOSBox-X, Python 3 (for GLB decryption and INC generation), and a lot of patience.