#!/usr/bin/env python3 # genC64Data.py -- emit the C64 game data the JoeyLib Space Taxi port # needs at runtime as C source: the custom charset (rendering AND the # exact sprite-to-background collision bits), the standard sprite # bitmaps (cab, passenger, flame, warp, death, intro star, leaving # cels), the demo-mode RNG table ($446A), the SID SFX programs (the # 9-byte blocks fed to $42E9), the engine-flame pointer table ($6DB0) # and the level-intro star velocity tables ($450C/$4513). # # Everything comes straight out of stuff/spacetaxi/raw.bin (a VICE # "bank ram" dump, 2-byte load-address header) so the port has one # source of truth: the original bytes. Output: # examples/spacetaxi/stC64Data.c / stC64Data.h (committed, do not edit) import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) RAW = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin") OUT_C = os.path.join(HERE, "..", "stC64Data.c") OUT_H = os.path.join(HERE, "..", "stC64Data.h") SPRITE_PTR_FIRST = 0xC0 SPRITE_PTR_LAST = 0xEC # $E9-$EC = passengers-leaving cels SPRITE_BYTES = 63 # (enum name, address of the 9-byte program, comment) SFX = [ ("ST_SFX_GEAR", 0x6410, "landing gear toggle ($63DD)"), ("ST_SFX_CASH", 0x6DBB, "refuel ka-ching ($6E5B)"), ("ST_SFX_FUEL_LOW", 0x6DC4, "fuel below 3 cells ($6EBD)"), ("ST_SFX_FUEL_FULL", 0x6DCD, "tank full ($6E9E)"), ("ST_SFX_LAND_HARD", 0x6DD6, "hard landing ($6520)"), ("ST_SFX_IMPACT", 0x6DDF, "wreck hits the floor ($6AE1)"), ("ST_SFX_LAND_SOFT", 0x6DE8, "soft landing ($650F)"), ("ST_SFX_CRASH", 0x6DF1, "crash start ($6A56)"), ] def load_raw(): with open(RAW, "rb") as fp: raw = fp.read() if len(raw) == 0x10002: raw = raw[2:] if len(raw) != 0x10000: sys.exit(f"{RAW}: unexpected size {len(raw)}") return raw def c_bytes(data, per_line=16, indent=" "): lines = [] for i in range(0, len(data), per_line): chunk = data[i:i + per_line] lines.append(indent + ", ".join(f"0x{b:02X}" for b in chunk) + ",") return "\n".join(lines) # The cross-segment accessors the IIgs needs (see the block this emits # into stC64Data.c). These used to be hand-added to the GENERATED file, # which meant re-running this script silently deleted them and broke the # IIgs build -- the header kept declaring them and nothing defined them. # Prototype and return expression per accessor; the header prints the # prototypes aligned, so keep the spacing. ACCESSORS = [ ("const uint8_t *stC64Charset(void);", "&kStCharset[0][0]"), ("const uint8_t *stC64SpriteBitmap(uint8_t idx);", "kStSpriteBitmaps[idx]"), ("uint8_t stC64RngByte(uint8_t idx);", "kStRngTable[idx]"), ("const uint8_t *stC64SfxProgram(uint8_t idx);", "kStSfxPrograms[idx]"), ("uint8_t stC64FlameCel(uint8_t dirMask);", "kStFlameCelByDirMask[dirMask]"), ] def main(): raw = load_raw() sprite_count = SPRITE_PTR_LAST - SPRITE_PTR_FIRST + 1 h = [] h.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.") h.append("// Do not hand-edit; re-run the generator.") h.append("") h.append("#ifndef ST_C64_DATA_H") h.append("#define ST_C64_DATA_H") h.append("") h.append("#include ") h.append("") h.append("// The game's custom character set ($2800-$2FFF): 256 glyphs x 8 rows,") h.append("// bit 7 = leftmost pixel. Used to paint the screen AND for the") h.append("// sprite-to-background collision test (a set bit is background data).") h.append("#define ST_CHARSET_CHARS 256u") h.append("extern const uint8_t kStCharset[ST_CHARSET_CHARS][8];") h.append("") h.append("// Standard sprite bitmaps (VIC bank 0, block ptr * 64). 21 rows x 3") h.append("// bytes. Index = ptr - ST_SPRITE_PTR_FIRST.") h.append(f"#define ST_SPRITE_PTR_FIRST 0x{SPRITE_PTR_FIRST:02X}u") h.append(f"#define ST_SPRITE_PTR_LAST 0x{SPRITE_PTR_LAST:02X}u") h.append(f"#define ST_SPRITE_COUNT {sprite_count}u") h.append(f"#define ST_SPRITE_BYTES {SPRITE_BYTES}u") h.append("extern const uint8_t kStSpriteBitmaps[ST_SPRITE_COUNT][ST_SPRITE_BYTES];") h.append("") h.append("// Demo-mode RNG lookup ($446A): rnd = table[t1] + t2 (see stSim.c).") h.append("#define ST_RNG_TABLE_SIZE 64u") h.append("extern const uint8_t kStRngTable[ST_RNG_TABLE_SIZE];") h.append("") h.append("// SID SFX programs, 9 bytes each, in $42E9 order: freq lo, freq hi,") h.append("// pulse lo, pulse hi, control, AD, SR, release ticks, voice index.") h.append("typedef enum {") for name, addr, comment in SFX: h.append(f" {name}, // ${addr:04X} {comment}") h.append(" ST_SFX_COUNT") h.append("} StSfxE;") h.append("#define ST_SFX_PROGRAM_BYTES 9u") h.append("extern const uint8_t kStSfxPrograms[ST_SFX_COUNT][ST_SFX_PROGRAM_BYTES];") h.append("") h.append("// Engine-flame cel pointer by direction mask ($6DB0, 1=UP 2=DOWN 4=LEFT") h.append("// 8=RIGHT); 0 = no cel for that combination.") h.append("extern const uint8_t kStFlameCelByDirMask[16];") h.append("") h.append("// Cross-segment-safe accessors (see stC64Data.c): reach the tables above") h.append("// by an inter-segment call on the IIgs, where a direct 2-byte data") h.append("// reference from another bank is not relocatable.") for decl in ACCESSORS: h.append(decl[0]) h.append("") h.append("#endif") c = [] c.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.") c.append("// Do not hand-edit; re-run the generator.") c.append("") c.append('#include "stC64Data.h"') c.append("") c.append("const uint8_t kStCharset[ST_CHARSET_CHARS][8] = {") for ch in range(256): rows = raw[0x2800 + ch * 8:0x2800 + ch * 8 + 8] c.append(" { " + ", ".join(f"0x{b:02X}" for b in rows) + " }, // $" + f"{ch:02X}") c.append("};") c.append("") c.append("const uint8_t kStSpriteBitmaps[ST_SPRITE_COUNT][ST_SPRITE_BYTES] = {") for ptr in range(SPRITE_PTR_FIRST, SPRITE_PTR_LAST + 1): base = ptr * 64 c.append(f" {{ // ptr ${ptr:02X}") for row in range(21): b = raw[base + row * 3:base + row * 3 + 3] c.append(" " + ", ".join(f"0x{x:02X}" for x in b) + ",") c.append(" },") c.append("};") c.append("") c.append("const uint8_t kStRngTable[ST_RNG_TABLE_SIZE] = {") c.append(c_bytes(raw[0x446A:0x446A + 64])) c.append("};") c.append("") c.append("const uint8_t kStSfxPrograms[ST_SFX_COUNT][ST_SFX_PROGRAM_BYTES] = {") for name, addr, comment in SFX: b = raw[addr:addr + 9] c.append(" { " + ", ".join(f"0x{x:02X}" for x in b) + " }, // " + name) c.append("};") c.append("") flame = list(raw[0x6DB0:0x6DB0 + 11]) + [0] * 5 c.append("const uint8_t kStFlameCelByDirMask[16] = {") c.append(c_bytes(flame)) c.append("};") c.append("") c.append("") c.append("// ---------------------------------------------------------------------------") c.append("// Accessors. On the IIgs the program is split across banks and a 2-byte") c.append("// data reference cannot cross a segment (only a 3-byte JSL/JML can). These") c.append("// tiny functions live in the same segment as the tables, so their reads") c.append("// are intra-segment; callers in other segments reach them by a supported") c.append("// inter-segment call. On the other ports they inline away to nothing.") c.append("// ---------------------------------------------------------------------------") first = True for decl, body in ACCESSORS: c.append("") if not first: c.append("") first = False # The header pads return types to align its prototypes; the # definitions here must not inherit that padding. c.append(" ".join(decl[:-1].split()) + " {") c.append(" return " + body + ";") c.append("}") with open(OUT_H, "w", encoding="ascii") as fp: fp.write("\n".join(h) + "\n") with open(OUT_C, "w", encoding="ascii") as fp: fp.write("\n".join(c) + "\n") print(f"wrote {OUT_H} and {OUT_C}") if __name__ == "__main__": main()