# DOS (DJGPP) build rules. include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk $(call requireTool,$(DOS_CC),toolchain:dos) $(call requireTool,$(DOS_AS),toolchain:dos) $(call requireTool,$(DOS_CWSDSTUB),toolchain:dos) $(call requireTool,$(LIBXMP_DIR)/include/libxmp-lite/xmp.h,toolchain:dos) PLATFORM := dos BUILD := $(REPO_DIR)/build/$(PLATFORM) LIBDIR := $(BUILD)/lib BINDIR := $(BUILD)/bin # -fomit-frame-pointer: flag parity with the ST/Amiga CFLAGS (both had # it; DOS never did). Frees EBP for allocation in the hot loops under # DOSBox's per-instruction-charged core (perf-hunt-2 rank 2). # so flag experiments never edit a tracked file (perf-hunt-2 rank 17). CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(SRC_X86) -I$(LIBXMP_DIR)/include -MMD -MP ASFLAGS := -f coff LDFLAGS := # libxmp-lite (MIT, libxmp.org) -- 4-channel-Protracker-and-friends # replayer + SMIX SFX overlay. Installed by ./install.sh # (LIBXMP_DIR, from config.mk). Built as a static archive that the DOS # audio HAL links against. LIBXMP_SRC := $(filter-out %/win32.c, $(wildcard $(LIBXMP_DIR)/src/*.c) $(wildcard $(LIBXMP_DIR)/src/loaders/*.c)) LIBXMP_OBJDIR := $(BUILD)/obj/libxmp-lite LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP_SRC)) LIBXMP_AR := $(LIBDIR)/libxmplite.a LIBXMP_CFLAGS := -DJOEY_LIBXMP_LITE -DLIBXMP_CORE_PLAYER -DHAVE_FNMATCH=0 -I$(LIBXMP_DIR)/include -I$(LIBXMP_DIR)/include/libxmp-lite -I$(LIBXMP_DIR)/src -Wno-error -w # The x86 code DOS shares with DVX (the sprite emitter, the whole-map tile # walker, the set-1 scan code table) lives in src/x86 and builds into obj/x86; # only the CPU-independent sprite dispatch stays in src/codegen. PORT_C_SRCS := $(wildcard $(SRC_DIR)/dos/*.c) PORT_S_SRCS := $(wildcard $(SRC_DIR)/dos/*.asm) X86_C_SRCS := $(wildcard $(SRC_X86)/*.c) CODEGEN_DIR := $(REPO_DIR)/src/codegen LIB_OBJS := \ $(patsubst $(SRC_CORE)/%.c,$(BUILD)/obj/core/%.o,$(CORE_C_SRCS)) \ $(patsubst $(SRC_DIR)/generic/%.c,$(BUILD)/obj/generic/%.o,$(GENERIC_C_SRCS)) \ $(patsubst $(SRC_DIR)/dos/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \ $(patsubst $(SRC_DIR)/dos/%.asm,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \ $(patsubst $(SRC_X86)/%.c,$(BUILD)/obj/x86/%.o,$(X86_C_SRCS)) \ $(BUILD)/obj/codegen/spriteCompile.o \ $(BUILD)/obj/codegen/spriteStage.o LIB := $(LIBDIR)/libjoey.a HELLO_SRC := $(EXAMPLES)/hello/hello.c HELLO_BIN := $(BINDIR)/HELLO.EXE PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c PATTERN_BIN := $(BINDIR)/PATTERN.EXE DRAW_SRC := $(EXAMPLES)/draw/draw.c DRAW_BIN := $(BINDIR)/DRAW.EXE KEYS_SRC := $(EXAMPLES)/keys/keys.c KEYS_BIN := $(BINDIR)/KEYS.EXE SERIAL_SRC := $(EXAMPLES)/serial/serial.c SERIAL_BIN := $(BINDIR)/SERIAL.EXE SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c SERTEST_BIN := $(BINDIR)/SERTEST.EXE SAVE_SRC := $(EXAMPLES)/save/save.c SAVE_BIN := $(BINDIR)/SAVE.EXE TILEBANK_SRC := $(EXAMPLES)/tilebank/tilebank.c TILEBANK_BIN := $(BINDIR)/TILEBANK.EXE JOY_SRC := $(EXAMPLES)/joy/joy.c JOY_BIN := $(BINDIR)/JOY.EXE SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c SPRITE_BIN := $(BINDIR)/SPRITE.EXE AUDIO_SRC := $(EXAMPLES)/audio/audio.c AUDIO_BIN := $(BINDIR)/AUDIO.EXE UBER_SRC := $(EXAMPLES)/uber/uber.c UBER_BIN := $(BINDIR)/UBER.EXE ADV_SRC := $(EXAMPLES)/adventure/adventure.c ADV_BIN := $(BINDIR)/ADV.EXE ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c ADV2_BIN := $(BINDIR)/ADV2.EXE AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c AGI_BIN := $(BINDIR)/AGI.EXE STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c # JoeyLib install convention: each app lives in its own bin subdir # with a DATA/ folder alongside the binary for runtime assets. STAXI_INSTALL_DIR := $(BINDIR)/STAXI STAXI_BIN := $(STAXI_INSTALL_DIR)/STAXI.EXE # Game data lives under bin/DATA/, alongside the binaries DOSBox picks # up when bin/ is mounted as C:. audio.c fopens "DATA/test.mod" etc. DATA_DIR := $(BINDIR)/DATA DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx # TILEBANK's DATA/tilebank: this port's bake of the tile-bank fixture, the # other targets' bakes it must refuse, and the expected pixels (common.mk). TILEBANK_DATA := $(DATA_DIR)/tilebank/own.tbk # Space Taxi asset pipeline. # # Build artifacts go to examples/spacetaxi/generated/ -- safe to # nuke; recreated by this makefile. What the game loads is then copied # into the DATA tree beside its binary: the levels, the cel bank, the # per-level cel banks and the speech. Tiles and the font come from the # C64 data compiled into the binary (stC64Data.c). STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/dos STAXI_RUN_DIR := $(STAXI_INSTALL_DIR)/DATA # Level data is extracted from the C64 ROM by # examples/spacetaxi/assets/romToLevel.py: 24 lettered levels (A..X) from scenes # 0..23 plus the title from scene 24. The script runs the captured # decompressor through a Python 6502 emulator and writes STL2 .dat # files directly. Input is the raw RAM dump. mkstlevel + title.txt # is no longer used: the C64 title encodes its screen with the game's # custom charset (C64 data in stC64Data.c), so a hand-authored tile # layout with a different glyph mapping renders as garbage. STAXI_ROM_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \ $(STAXI_GEN_DIR)/levels/title.dat STAXI_LEVEL_GEN := $(STAXI_ROM_LEVEL_GEN) # The runtime tree beside the binary. STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN)) STAXI_ASSET_DSTS := $(STAXI_LEVEL_RUN) # Pre-compiled sprite banks (.spc): baked from the .spr by the host spritebake # tool for THIS target and staged in DATA/sprites, so the runtime loads # compiled routines with no startup JIT (jlSpriteBankLoadPrecompiled). SPRITEBAKE_BIN := $(REPO_DIR)/build/tools/spritebake-dos # The C64-exact cel bank, generated by staxibake (from the shared cel table # stCels.c + C64 bitmaps) and compiled to a .spc by the host spritebake, so # STAXI loads its cels already compiled instead of JIT-compiling them at boot. STAXIBAKE_BIN := $(REPO_DIR)/build/tools/staxibake # Per-level hook-sprite banks: the cels stLevelCelList names for each # level that has hook sprites, baked from its level file and pre-compiled # for this target, so no cel is JIT-compiled mid-ride. Levels without # hook sprites have no bank; the game JIT-compiles at level entry when a # bank is missing. STAXI_CEL_LEVELS := 07 08 09 10 12 16 17 21 STAXILEVEL_SPR_GEN := $(foreach n,$(STAXI_CEL_LEVELS),$(STAXI_GEN_DIR)/sprites/level$(n).spr) STAXILEVEL_SPC_GEN := $(STAXILEVEL_SPR_GEN:.spr=.spc) STAXILEVEL_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXILEVEL_SPC_GEN)) STAXI_ASSET_DSTS += $(STAXILEVEL_SPC_RUN) # Speech baked to sample bytes per passenger pitch (assets/genSpeechPcm.py): # expanding bits at play time is what keeps "HEY TAXI" from stalling the # ride; the C64 run-length source stays in stSpeechData.c as the input. STAXI_SPEECH_GEN := $(STAXI_GEN_DIR)/speech.bin STAXI_SPEECH_RUN := $(STAXI_RUN_DIR)/speech.bin STAXI_ASSET_DSTS += $(STAXI_SPEECH_RUN) STAXICELS_SPR_GEN := $(STAXI_GEN_DIR)/sprites/staxicels.spr STAXICELS_SPC_GEN := $(STAXICELS_SPR_GEN:.spr=.spc) STAXICELS_SPC_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXICELS_SPC_GEN)) # Added HERE, not up with the other asset destinations: this variable is # defined below that point, and the := there expanded it to nothing, so # the compiled cel bank was never a prerequisite of the port and never # got copied into the build tree. Every port shipped whatever .spc was # staged the day the file was first created, and edits to stCels.c did # not reach the game (found 2026-09-23 when a new cel made the runtime # reject the bank as too short). STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN) MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel .PHONY: all dos dos-staxi-data clean-dos clean all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(TILEBANK_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA) $(call staxiData,$(STAXI_ASSET_DSTS)) # Space Taxi's DATA tree alone: make/dvx.mk stages this same bake, since DVX # runs the DOS stage and the same x86 code. dos-staxi-data: $(call staxiData,$(STAXI_ASSET_DSTS)) $(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) -c $< -o $@ $(BUILD)/obj/generic/%.o: $(SRC_DIR)/generic/%.c @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) -c $< -o $@ $(BUILD)/obj/port/%.o: $(SRC_DIR)/dos/%.c @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@ $(BUILD)/obj/port/%.o: $(SRC_DIR)/dos/%.asm @mkdir -p $(dir $@) $(DOS_AS) $(ASFLAGS) $< -o $@ $(BUILD)/obj/x86/%.o: $(SRC_X86)/%.c @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@ $(BUILD)/obj/codegen/%.o: $(CODEGEN_DIR)/%.c @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@ $(LIB): $(LIB_OBJS) @mkdir -p $(dir $@) $(DOS_AR) rcs $@ $^ $(LIBXMP_OBJDIR)/%.o: $(LIBXMP_DIR)/src/%.c @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $(LIBXMP_CFLAGS) -c $< -o $@ $(LIBXMP_AR): $(LIBXMP_OBJS) @mkdir -p $(dir $@) $(DOS_AR) rcs $@ $^ $(HELLO_BIN): $(HELLO_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(DRAW_BIN): $(DRAW_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(KEYS_BIN): $(KEYS_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(SERIAL_BIN): $(SERIAL_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(SERTEST_BIN): $(SERTEST_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(SAVE_BIN): $(SAVE_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(TILEBANK_BIN): $(TILEBANK_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(JOY_BIN): $(JOY_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(SPRITE_BIN): $(SPRITE_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(AUDIO_BIN): $(AUDIO_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(UBER_BIN): $(UBER_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(ADV_BIN): $(ADV_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(ADV2_BIN): $(ADV2_SRC) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(AGI_BIN): $(AGI_SRCS) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $(AGI_SRCS) $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ $(STAXI_BIN): $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) @mkdir -p $(dir $@) $(DOS_CC) $(CFLAGS) $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) -o $@ $(DOS_EMBED_DPMI) $@ # Stage A: generate artifacts into examples/spacetaxi/generated/. # Mark these .SECONDARY so make doesn't auto-delete them after the # stage-B copy into bin/ -- they're the canonical distribution # artifacts. .SECONDARY: $(STAXI_LEVEL_GEN) $(STAXICELS_SPC_GEN) $(STAXILEVEL_SPR_GEN) $(STAXILEVEL_SPC_GEN) # Bulk-extract all 24 canonical levels plus the title from the C64 # ROM in one invocation. Grouped target (&:) means make treats the # recipe as producing the whole list together -- it runs once even # though many targets depend on it. Requires GNU Make 4.3+. $(STAXI_ROM_LEVEL_GEN) &: $(STAXI_RAW_DUMP) $(ROMTOLEVEL_BIN) @mkdir -p $(STAXI_GEN_DIR)/levels python3 $(ROMTOLEVEL_BIN) $(STAXI_RAW_DUMP) $(STAXI_GEN_DIR)/levels $(STAXI_GEN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spr $(SPRITEBAKE_BIN) @mkdir -p $(dir $@) $(SPRITEBAKE_BIN) $< $@ $(SPRITEBAKE_BIN): FORCE $(MAKE) -f $(REPO_DIR)/make/tools.mk spritebake-dos $(STAXIBAKE_BIN): FORCE $(MAKE) -f $(REPO_DIR)/make/tools.mk staxibake # The cel bank is generated from the game's cel table (stCels.c). $(STAXICELS_SPR_GEN): $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stC64Data.c @mkdir -p $(dir $@) $(STAXIBAKE_BIN) $@ # Stage B: copy generated/ into the runtime tree beside the binary $(STAXI_RUN_DIR)/levels/%.dat: $(STAXI_GEN_DIR)/levels/%.dat @mkdir -p $(dir $@) cp $< $@ $(STAXI_RUN_DIR)/%.spc: $(STAXI_GEN_DIR)/%.spc @mkdir -p $(dir $@) cp $< $@ $(MKSTLEVEL_BIN): FORCE $(MAKE) -f $(REPO_DIR)/make/tools.mk mkstlevel $(DATA_DIR)/test.mod: $(REPO_DIR)/assets/test.mod @mkdir -p $(DATA_DIR) cp $< $@ $(DATA_DIR)/test.sfx: $(REPO_DIR)/assets/test.sfx @mkdir -p $(DATA_DIR) cp $< $@ $(TILEBANK_DATA): $(TILEBANK_BAKE_DEPS) python3 $(TILEBANK_STAGE) $(PLATFORM) $(TILEBANK_PNG) $(dir $@) clean-dos: rm -rf $(BUILD) # `clean` as an alias for `clean-dos`. Without it a bare # `make -f make/dos.mk clean` FAILS -- and with stderr discarded that # looks like success while leaving stale objects the next build links. clean: clean-dos # Nuke regenerable Space Taxi artifacts. Sources under # examples/spacetaxi/assets/ are untouched; everything under # generated/ is recreated by the next build. .PHONY: clean-spacetaxi clean-spacetaxi: find $(STAXI_GEN_DIR) -mindepth 1 ! -name '.gitignore' -delete 2>/dev/null || true # Pull in per-object header-dependency files generated by gcc -MMD/-MP. # Without this, editing a header (e.g. surfaceInternal.h) doesn't rebuild # the .c files that include it, leaving a frankenstein binary where # different TUs see different struct layouts. -include $(LIB_OBJS:.o=.d) # The per-level cel banks, baked from each level's file. $(STAXI_GEN_DIR)/sprites/level%.spr: $(STAXI_GEN_DIR)/levels/level%.dat $(STAXIBAKE_BIN) $(EXAMPLES)/spacetaxi/stLevelCels.c @mkdir -p $(dir $@) $(STAXIBAKE_BIN) --level $< $@ # Speech bake rules live down here so they never become the default goal. $(STAXI_SPEECH_GEN): $(EXAMPLES)/spacetaxi/stSpeechData.c $(EXAMPLES)/spacetaxi/assets/genSpeechPcm.py @mkdir -p $(dir $@) python3 $(EXAMPLES)/spacetaxi/assets/genSpeechPcm.py $< $@ $(STAXI_SPEECH_RUN): $(STAXI_SPEECH_GEN) @mkdir -p $(dir $@) cp $< $@