#!/usr/bin/env python3 """Fork the CGB boot ROM, swapping the "GAME BOY" boot wordmark for "SL0PBOY". The stock CGB boot ROM (bios/gbc_bios.bin) draws its on-screen "GAME BOY" wordmark from a 192-byte blob at ROM $0607 (the routine at $03F0 copies it to VRAM tiles $08-$37). The blob is 48 tiles (16 wide x 3 tall, tile-sequential), 4 bytes per tile - each byte is one 8px row that the copy loop doubles vertically (plane-1 stays 0). The cart's own Nintendo logo ($104) is untouched, so the boot ROM's logo-integrity check still passes and hands off normally. We just re-encode that blob with a "SL0PBOY" bitmap of the same size and patch it in place - no code changes, no relocation. Re-run this to tweak the logo. tools/mk_sl0pboy_bios.py # -> bios/sl0pboy_bios.bin (+ a preview) Needs Pillow and a bold-italic TTF (LiberationSans-BoldItalic by default). """ import os, sys from PIL import Image, ImageFont, ImageDraw HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) SRC = os.path.join(ROOT, "bios/gbc_bios.bin") OUT = os.path.join(ROOT, "bios/sl0pboy_bios.bin") FONT = os.environ.get("SL0P_FONT", "/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf") LOGO_OFF, LOGO_LEN = 0x607, 0xC0 # 192-byte "GAME BOY" blob TEXT = os.environ.get("SL0P_TEXT", "SL0PBOY") # tile grid: 16 wide x 3 tall, 4 logical rows/tile -> 128 px wide x 12 logical # rows (the copy loop doubles each row, so the on-screen art is 128x24). W, LROWS = 128, 12 def render_logical(): """1bpp 128x12 bitmap of TEXT, bold-italic, fit to the logo box.""" big = Image.new("L", (1600, 320), 0) ImageDraw.Draw(big).text((10, 10), TEXT, fill=255, font=ImageFont.truetype(FONT, 240)) crop = big.crop(big.getbbox()) fit = crop.resize((W - 2, LROWS), Image.LANCZOS) # 1px side margins logical = Image.new("L", (W, LROWS), 0) logical.paste(fit, (1, 0)) return logical.point(lambda p: 255 if p > 100 else 0) def encode(bw): """48 tiles (row-major 16x3), 4 bytes/tile, MSB = leftmost pixel.""" px = bw.load() out = bytearray() for t in range(48): tx, ty = (t % 16) * 8, (t // 16) * 4 for r in range(4): b = 0 for x in range(8): if px[tx + x, ty + r] >= 128: b |= 1 << (7 - x) out.append(b) return bytes(out) def main(): if not os.path.exists(SRC): sys.exit("missing %s" % SRC) if not os.path.exists(FONT): sys.exit("missing font %s (set SL0P_FONT)" % FONT) bw = render_logical() blob = encode(bw) assert len(blob) == LOGO_LEN, len(blob) bios = bytearray(open(SRC, "rb").read()) bios[LOGO_OFF:LOGO_OFF + LOGO_LEN] = blob open(OUT, "wb").write(bios) # a doubled preview PNG next to the binary, for eyeballing prev = Image.new("L", (W, LROWS * 2)) pp, bp = prev.load(), bw.load() for y in range(LROWS): for x in range(W): pp[x, 2 * y] = pp[x, 2 * y + 1] = bp[x, y] prev.resize((W * 4, LROWS * 8), Image.NEAREST).save(OUT + ".preview.png") print("wrote %s (patched $%04X-$%04X) + %s.preview.png" % (OUT, LOGO_OFF, LOGO_OFF + LOGO_LEN - 1, OUT)) if __name__ == "__main__": main()