├── rom.sha1 ├── .gitignore ├── gfx ├── commonTiles1.png ├── commonTiles2.png ├── commonTiles3.png ├── menuTiles1.2bpp ├── menuTiles1.png ├── menuTiles2.2bpp ├── menuTiles2.png ├── commonTiles1.2bpp ├── commonTiles2.2bpp ├── commonTiles3.2bpp ├── enemiesWorld1.2bpp ├── enemiesWorld1.png ├── enemiesWorld2.2bpp ├── enemiesWorld2.png ├── enemiesWorld3.2bpp ├── enemiesWorld3.png ├── enemiesWorld4.2bpp ├── enemiesWorld4.png ├── backgroundWorld1.2bpp ├── backgroundWorld1.png ├── backgroundWorld2.2bpp ├── backgroundWorld2.png ├── backgroundWorld3.2bpp ├── backgroundWorld3.png ├── backgroundWorld4.2bpp └── backgroundWorld4.png ├── macros.asm ├── Makefile ├── music_macros.asm ├── README.md ├── charmap.asm ├── sound_constants.asm ├── dump_text.py ├── identifynote.py ├── hram.asm ├── wram.asm ├── dump_gfx.py ├── enemies.asm ├── dump_levels_and_enemies.py ├── gbhw.asm ├── coverage.py ├── bank1.asm ├── dump_music.py ├── levels ├── levels.asm └── enemy_locations.asm ├── bank2.asm ├── music.asm └── bank3.asm /rom.sha1: -------------------------------------------------------------------------------- 1 | 418203621b887caa090215d97e3f509b79affd3e mario.gb 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage_map.png 2 | *.sn? 3 | *.o 4 | *.gb 5 | *.sym 6 | *.map 7 | -------------------------------------------------------------------------------- /gfx/commonTiles1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/commonTiles1.png -------------------------------------------------------------------------------- /gfx/commonTiles2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/commonTiles2.png -------------------------------------------------------------------------------- /gfx/commonTiles3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/commonTiles3.png -------------------------------------------------------------------------------- /gfx/menuTiles1.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/menuTiles1.2bpp -------------------------------------------------------------------------------- /gfx/menuTiles1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/menuTiles1.png -------------------------------------------------------------------------------- /gfx/menuTiles2.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/menuTiles2.2bpp -------------------------------------------------------------------------------- /gfx/menuTiles2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/menuTiles2.png -------------------------------------------------------------------------------- /gfx/commonTiles1.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/commonTiles1.2bpp -------------------------------------------------------------------------------- /gfx/commonTiles2.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/commonTiles2.2bpp -------------------------------------------------------------------------------- /gfx/commonTiles3.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/commonTiles3.2bpp -------------------------------------------------------------------------------- /gfx/enemiesWorld1.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld1.2bpp -------------------------------------------------------------------------------- /gfx/enemiesWorld1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld1.png -------------------------------------------------------------------------------- /gfx/enemiesWorld2.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld2.2bpp -------------------------------------------------------------------------------- /gfx/enemiesWorld2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld2.png -------------------------------------------------------------------------------- /gfx/enemiesWorld3.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld3.2bpp -------------------------------------------------------------------------------- /gfx/enemiesWorld3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld3.png -------------------------------------------------------------------------------- /gfx/enemiesWorld4.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld4.2bpp -------------------------------------------------------------------------------- /gfx/enemiesWorld4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/enemiesWorld4.png -------------------------------------------------------------------------------- /gfx/backgroundWorld1.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld1.2bpp -------------------------------------------------------------------------------- /gfx/backgroundWorld1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld1.png -------------------------------------------------------------------------------- /gfx/backgroundWorld2.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld2.2bpp -------------------------------------------------------------------------------- /gfx/backgroundWorld2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld2.png -------------------------------------------------------------------------------- /gfx/backgroundWorld3.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld3.2bpp -------------------------------------------------------------------------------- /gfx/backgroundWorld3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld3.png -------------------------------------------------------------------------------- /gfx/backgroundWorld4.2bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld4.2bpp -------------------------------------------------------------------------------- /gfx/backgroundWorld4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaspermeerts/supermarioland/HEAD/gfx/backgroundWorld4.png -------------------------------------------------------------------------------- /macros.asm: -------------------------------------------------------------------------------- 1 | WAIT_FOR_HBLANK: MACRO 2 | .loop\@ 3 | ldh a, [rSTAT] 4 | and a, %11 5 | jr nz, .loop\@ 6 | ENDM 7 | 8 | FAR_CALL: MACRO 9 | SAVE_AND_SWITCH_ROM_BANK \1 10 | call \2 11 | RESTORE_ROM_BANK 12 | ENDM 13 | 14 | SAVE_AND_SWITCH_ROM_BANK: MACRO 15 | ldh a, [hActiveRomBank] 16 | ldh [hSavedRomBank], a 17 | ld a, \1 18 | ldh [hActiveRomBank], a 19 | ld [MBC1RomBank], a 20 | ENDM 21 | 22 | RESTORE_ROM_BANK: MACRO 23 | ldh a, [hSavedRomBank] 24 | ldh [hActiveRomBank], a 25 | ld [MBC1RomBank], a 26 | ENDM 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean check 2 | .SUFFIXES: 3 | .SUFFIXES: .asm .o .gb 4 | 5 | objects := bank0.o bank1.o bank2.o bank3.o music.o levels/enemy_locations.o 6 | 7 | all: mario.gb check 8 | 9 | clean: 10 | rm -f mario.gb $(objects) 11 | 12 | # Quietly check the hash of the newly built ROM to make sure any disassembled 13 | # code matches the original 14 | check: mario.gb 15 | @sha1sum -c --quiet rom.sha1 16 | 17 | # Export everything for the moment, to make debugging easier 18 | %.o: %.asm 19 | @echo " ASM $@" 20 | @rgbasm -E -h -o $@ $< 21 | 22 | mario.gb: $(objects) 23 | @echo " LINK $@" 24 | @rgblink -d -n $*.sym -m $*.map -o $@ $^ 25 | @rgbfix -v $@ 26 | -------------------------------------------------------------------------------- /music_macros.asm: -------------------------------------------------------------------------------- 1 | Noise: MACRO 2 | REPT _NARG 3 | IF \1 == 0 4 | db 1 5 | ELIF \1 == 1 6 | db 6 7 | ELIF \1 == 2 8 | db 11 9 | ELIF \1 == 3 10 | db 16 11 | ELSE 12 | FAIL "Unknown noise note" 13 | ENDC 14 | SHIFT 15 | ENDR 16 | ENDM 17 | 18 | C_ EQU 1 19 | C_# EQU 2 20 | D_ EQU 3 21 | D_# EQU 4 22 | E_ EQU 5 23 | F_ EQU 6 24 | F_# EQU 7 25 | G_ EQU 8 26 | G_# EQU 9 27 | A_ EQU 10 28 | A_# EQU 11 29 | B_ EQU 12 30 | 31 | Notes: MACRO 32 | IF _NARG % 2 != 0 33 | FAIL "Note list needs an even number of arguments" 34 | ENDC 35 | REPT _NARG / 2 36 | db \1 + (\2 - 2) * 12 37 | ENDR 38 | ENDM 39 | 40 | EndSegment: MACRO 41 | db $00 42 | ENDM 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Super Mario Land Disassembly 2 | 3 | Disassembly of the first Game Boy game I ever played 4 | 5 | This repository builds Super Mario Land (World) (Rev A) with SHA1 checksum `418203621b887caa090215d97e3f509b79affd3e` 6 | 7 | As of now it requires a copy of the original ROM named "baserom.gb" to be placed in the repository, to fill in sections which have not been disassembled yet. The goal is to make this step obsolete. 8 | 9 | ## Requirements 10 | 11 | * RGBDS 0.3.5 12 | * pypng 13 | 14 | ## Coverage 15 | 16 | A quick and dirty Python script `coverage.py` is provided to estimate how much of the ROM has been disassembled 17 | 18 | * Bank 0: three quarters 19 | * Bank 1: a third 20 | * Bank 2: half 21 | * Bank 3: half 22 | * High RAM: 46 out of 127 bytes identified 23 | 24 | -------------------------------------------------------------------------------- /charmap.asm: -------------------------------------------------------------------------------- 1 | charmap "0", $00 2 | charmap "1", $01 3 | charmap "2", $02 4 | charmap "3", $03 5 | charmap "4", $04 6 | charmap "5", $05 7 | charmap "6", $06 8 | charmap "7", $07 9 | charmap "8", $08 10 | charmap "9", $09 11 | charmap "a", $0A 12 | charmap "b", $0B 13 | charmap "c", $0C 14 | charmap "d", $0D 15 | charmap "e", $0E 16 | charmap "f", $0F 17 | charmap "g", $10 18 | charmap "h", $11 19 | charmap "i", $12 20 | ; j is missing 21 | charmap "k", $14 22 | charmap "l", $15 23 | charmap "m", $16 24 | charmap "n", $17 25 | charmap "o", $18 26 | charmap "p", $19 27 | charmap "q", $1a 28 | charmap "r", $1b 29 | charmap "s", $1c 30 | charmap "t", $1d 31 | charmap "u", $1e 32 | charmap "v", $1f 33 | charmap "w", $20 34 | ; x is missing 35 | charmap "y", $22 36 | charmap ".", $23 37 | charmap ":", $25 38 | charmap ",", $26 39 | charmap "z", $27 40 | charmap "!", $28 41 | charmap "-", $29 42 | charmap "$", $2A 43 | charmap "*", $2B 44 | charmap " ", $2C 45 | charmap "♥", $84 46 | -------------------------------------------------------------------------------- /sound_constants.asm: -------------------------------------------------------------------------------- 1 | SFX_JUMP EQU $1 2 | SFX_SUPERBALL EQU $2 3 | SFX_STOMP EQU $3 4 | SFX_GROW EQU $4 5 | SFX_COIN EQU $5 6 | SFX_INJURY EQU $6 7 | SFX_BUMP EQU $7 8 | SFX_1UP EQU $8 9 | SFX_GIRA EQU $9 10 | SFX_TIMERTICK EQU $A 11 | SFX_FLOWER EQU $B 12 | 13 | SFX_EXPLOSION EQU $1 14 | SFX_BRICKSHATTER EQU $2 15 | SFX_DEATHCRY EQU $3 16 | SFX_FIREBREATH EQU $4 17 | 18 | MUS_LEVEL_COMPLETE EQU $1 19 | MUS_DEATH EQU $2 20 | MUS_EASTON EQU $3 21 | MUS_UNDERGROUND EQU $4 22 | MUS_AUTOSCROLL EQU $5 23 | MUS_CHAI EQU $6 24 | MUS_BIRABUTO EQU $7 25 | MUS_MUDA EQU $8 26 | MUS_BONUS_GAME EQU $9 27 | MUS_BONUS_GAME_WALK EQU $A 28 | MUS_BOSS EQU $B 29 | MUS_STAR EQU $C 30 | MUS_BONUS_GAME_WIN EQU $D 31 | MUS_BONUS_GAME_LOSE EQU $E 32 | MUS_OH_DAISY EQU $F 33 | MUS_GAME_OVER EQU $10 34 | MUS_VICTORY EQU $11 35 | MUS_FAKE_DAISY EQU $12 36 | MUS_FINAL_BOSS EQU $13 37 | -------------------------------------------------------------------------------- /dump_text.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | BANKS = 4 4 | BANK_SIZE = 1 << 16 >> 2 5 | ROM_SIZE = BANKS * BANK_SIZE 6 | 7 | with open("baserom.gb", "rb") as f: 8 | rom = f.read() 9 | 10 | assert len(rom) == ROM_SIZE 11 | 12 | charmap = { 13 | 0x00: "0", 14 | 0x01: "1", 15 | 0x02: "2", 16 | 0x03: "3", 17 | 0x04: "4", 18 | 0x05: "5", 19 | 0x06: "6", 20 | 0x07: "7", 21 | 0x08: "8", 22 | 0x09: "9", 23 | 0x0A: "a", 24 | 0x0B: "b", 25 | 0x0C: "c", 26 | 0x0D: "d", 27 | 0x0E: "e", 28 | 0x0F: "f", 29 | 0x10: "g", 30 | 0x11: "h", 31 | 0x12: "i", 32 | 0x14: "k", 33 | 0x15: "l", 34 | 0x16: "m", 35 | 0x17: "n", 36 | 0x18: "o", 37 | 0x19: "p", 38 | 0x1a: "q", 39 | 0x1b: "r", 40 | 0x1c: "s", 41 | 0x1d: "t", 42 | 0x1e: "u", 43 | 0x1f: "v", 44 | 0x20: "w", 45 | 0x22: "y", 46 | 0x23: ".", 47 | 0x25: ":", 48 | 0x26: ",", 49 | 0x27: "z", 50 | 0x28: "!", 51 | 0x29: "-", 52 | 0x2A: "$", 53 | 0x2B: "*", 54 | 0x2C: " ", 55 | 0x84: "♥" 56 | } 57 | 58 | text_ptr = 0x1557 59 | byte = 0x00 60 | 61 | while byte != 0xFF: 62 | byte = rom[text_ptr] 63 | text_ptr += 1 64 | if byte == 0xFF: 65 | break 66 | elif byte == 0xFE: 67 | print("\", $FE\ndb \"", end='') 68 | else: 69 | print(charmap[byte], end='') 70 | 71 | -------------------------------------------------------------------------------- /identifynote.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from math import log2 3 | 4 | NOTES = ['C ', 'C#', 'D ', 'D#', 'E ', 'F ', 'F#', 'G ', 'G#', 'A ', 'A#', 'B '] 5 | 6 | # Data from 3:6E74 7 | NOTE_DATA = binascii.unhexlify("000F2C009C0006016B01C90123027702C602120356039B03DA0316044E048304B504E50411053B0563058905AC05CE05ED050A06270642065B06720689069E06B206C406D606E706F7060607140721072D07390744074F07590762076B0773077B0783078A07900797079D07A207A707AC07B107B607BA07BE07C107C407C807CB07CE07D107D407D607D907DB07DD07DF07") 8 | 9 | def closest_note(hex_freq): 10 | freq = 0x20000 / (0x800 - hex_freq) 11 | note = 9 + round(12 * log2(freq/440)) # A is 9 semitones above C 12 | octave = 4 + note // 12 # A440 is the first A above C4 13 | scale_degree = note % 12 14 | cents_off = 1200 * (log2(freq/440) - (note - 9)/12) 15 | 16 | return freq, scale_degree, octave, cents_off 17 | 18 | for i in range(0, len(NOTE_DATA), 2): 19 | hex_freq = NOTE_DATA[i] + NOTE_DATA[i+1]*0x100 20 | 21 | try: 22 | freq, degree, octave, cents_off = closest_note(hex_freq) 23 | except ValueError: 24 | print("\tdw $%03X ; " % (hex_freq)) 25 | continue 26 | 27 | print("\tdw $%03X ; %6.1f Hz %s%d (%+.0f)" % (hex_freq, freq, NOTES[degree], octave, cents_off), end='') 28 | 29 | _, _, _, cents_up = closest_note(hex_freq + 1) 30 | _, _, _, cents_down = closest_note(hex_freq - 1) 31 | if abs(cents_up) < abs(cents_off): 32 | print(" Bug! %03X is closer" % (hex_freq + 1)) 33 | elif abs(cents_down) < abs(cents_off): 34 | print(" Bug! %03X is closer" % (hex_freq - 1)) 35 | else: 36 | print("") 37 | 38 | while True: 39 | try: 40 | num = input() 41 | except EOFError: 42 | break 43 | 44 | freq = 0x20000 / (0x800 - int(num, 16)) 45 | closest_note = 9 + round(12*log2(freq/440)) 46 | octave = 4 + closest_note // 12 47 | scale_degree = closest_note % 12 48 | cents_off = 1200 * (log2(freq/440) - (closest_note - 9)/12) 49 | 50 | print("%.2f Hz %s%d %.1f cents" % (freq, NOTES[scale_degree], octave, cents_off)) 51 | 52 | -------------------------------------------------------------------------------- /hram.asm: -------------------------------------------------------------------------------- 1 | SECTION "High RAM", HRAM 2 | 3 | hJoyHeld:: ; FF80 keys currently pressed 4 | ds 1 5 | 6 | hJoyPressed:: ; FF81 keys pressed since last time 7 | ds 1 8 | 9 | ds $85 - $82 10 | 11 | hVBlankOccurred:: ; FF85 12 | ds 1 13 | 14 | ds $99 - $86 15 | 16 | hSuperStatus:: ; FF99 TODO constants 17 | ds 1 18 | 19 | hWinCount:: ; FF9A TODO mirrored at C0E1? 20 | ds 1 21 | 22 | ds 1 ; FF9B unknown 23 | 24 | hStompChainTimer:: ; FF9C 25 | ds 1 26 | 27 | hStompChain:: ; FF9D 28 | ds 1 29 | 30 | ds $A4 - $9E 31 | 32 | hScrollX:: ; FFA4 33 | ds 1 34 | 35 | ds 1 ; FFA5 unknown 36 | 37 | hTimer:: ; FFA6 Generic frame based timer 38 | ds 1 39 | 40 | ds $AC - $A7 41 | 42 | hFrameCounter:: ; FFAC 43 | ds 1 44 | 45 | ds $B2 - $AD 46 | 47 | hGamePaused:: ; FFB2 48 | ds 1 49 | 50 | hGameState:: ; FFB3 51 | ds 1 52 | 53 | hWorldAndLevel::; FFB4 54 | ds 1 55 | 56 | hSuperballMario::; FFB5 57 | ds 1 58 | 59 | hDMARoutine:: ; FFB6 60 | ds $A 61 | 62 | ds $D0 - $C0 63 | 64 | hCurrentChannel:: ; FFD0 Used in music routine 65 | ds 1 66 | 67 | ds $D5 - $D1 68 | 69 | hPanTimer:: ; FFD5 70 | ds 1 71 | 72 | hPanInterval:: ; FFD6 73 | ds 1 74 | 75 | hPanCounter:: ; FFD7 76 | ds 1 77 | 78 | hMonoOrStereo:: ; FFD8 79 | ds 1 80 | 81 | hChannelEnableMask1:: ; FFD9 82 | ds 1 83 | 84 | hChannelEnableMask2:: ; FFDA 85 | ds 1 86 | 87 | ds $DE - $DB 88 | 89 | hPauseTuneTimer:: ; FFDE 90 | ds 1 91 | 92 | hPauseUnpauseMusic::; FFDF 93 | ds 1 94 | 95 | ds 1 ; FFE0 96 | 97 | hSavedRomBank:: ; FFE1 98 | ds 1 99 | 100 | hTextCursorHi:: ; FFE2 101 | ds 1 102 | 103 | hTextCursorLo:: ; FFE3 104 | ds 1 105 | 106 | hLevelIndex:: ; FFE4 107 | ds 1 108 | 109 | hScreenIndex:: ; FFE5 110 | ds 1 111 | 112 | hColumnIndex:: ; FFE6 113 | ds 1 114 | 115 | hColumnPointerHi:: ; FFE7 116 | ds 1 117 | 118 | hColumnPointerLo:: ; FFE8 119 | ds 1 120 | 121 | ds 2 ; FFE9 FFEA 122 | 123 | hFloatyX:: ; FFEB 124 | ds 1 125 | 126 | hFloatyY:: ; FFEC 127 | ds 1 128 | 129 | hFloatyControl:: ; FFED 130 | ds 1 131 | 132 | ds $FA - $EE 133 | 134 | hCoins:: ; FFFA 135 | ds 1 136 | 137 | ds 2 138 | 139 | hActiveRomBank:: ; FFFD 140 | ds 1 141 | -------------------------------------------------------------------------------- /wram.asm: -------------------------------------------------------------------------------- 1 | SECTION "wram", WRAM0 2 | 3 | wOAMBuffer:: 4 | ds $A0 5 | 6 | wScore:: ; C0A0 7 | ds 3 8 | 9 | wLivesEarnedLost:: 10 | ds 1 ; C0A3 11 | 12 | ds 1 ; C0A4 13 | 14 | wGameOverWindowEnabled :: ; C0A5 15 | db 16 | 17 | wNumContinues:: ; C0A6 18 | db 19 | 20 | db ; C0A7 21 | 22 | wContinueWorldAndLevel:: ; C0A8 23 | db 24 | 25 | wSuperballTTL:: ; C0A9 26 | db 27 | 28 | ds $AD - $AA 29 | 30 | wGameOverTimerExpired:: ; C0AD 31 | db 32 | 33 | ds $C0 - $AE 34 | 35 | wTopScore:: ; C0C0 36 | ds 3 37 | 38 | ds $C0D3 - $C0C3 39 | 40 | wInvincibilityTimer:: ; C0D3 41 | db 42 | 43 | ds $C0DF - $C0D4 44 | 45 | wScrollY:: ; C0DF 46 | db 47 | 48 | ds 1 ; C0F0 49 | 50 | wWinCount:: ; C0E1 51 | db 52 | 53 | ds $D002 - $C0E2 54 | 55 | wCurrentCommand:: ; D002 56 | db 57 | 58 | wCommandArgument:: ; D003 59 | db 60 | 61 | ds $D013 - $D004 62 | 63 | wObjectsDrawn:: ; D013 The upper 20 objects are used for enemies 64 | db 65 | 66 | wBackgroundAnimated:: ; D014 67 | db 68 | 69 | ; D100 - D190: enemies 70 | ds $DA00 - $D015 71 | 72 | wGameTimer:: ; DA00-DA02 73 | ds 3 74 | 75 | wFloaty0_TTL:: ; DA03-DA06 76 | db 77 | wFloaty1_TTL:: 78 | db 79 | wFloaty2_TTL:: 80 | db 81 | wFloaty3_TTL:: 82 | db 83 | 84 | wFloaty0_SpriteIfCoin:: ; DA07-DA0A 85 | db 86 | wFloaty1_SpriteIfCoin:: 87 | db 88 | wFloaty2_SpriteIfCoin:: 89 | db 90 | wFloaty3_SpriteIfCoin:: 91 | db 92 | 93 | wNextFloatyOAMIndex :: ; DA0B 94 | ds 1 95 | 96 | wFloaty0_IsCoin:: ; DA0C - DA0F 97 | db 98 | wFloaty1_IsCoin:: 99 | db 100 | wFloaty2_IsCoin:: 101 | db 102 | wFloaty3_IsCoin:: 103 | db 104 | 105 | ds $DA15 - $DA10 106 | 107 | wLives:: db ; $DA15 108 | 109 | ds 1 ; DA16 110 | ds 1 ; DA17 111 | 112 | wLadderLocationHi:: ; DA18 113 | db 114 | 115 | wLadderLocationLo:: ; DA19 116 | db 117 | 118 | ds 1 ; DA1A 119 | 120 | wBonusGameEndTimer:: ; DA1B 121 | db 122 | 123 | ds 1 ; DA1C 124 | 125 | wGameTimerExpiringFlag:: ; DA1D do i have a better name? 126 | db 127 | 128 | wBonusGameGrowAnimationFlag :: ; DA1E Long name... 129 | db 130 | 131 | wBonusGameAnimationTimer:: ; DA1F 132 | db 133 | 134 | ds $22 - $20 135 | 136 | wBonusGameFrameCounter:: ; DA22 137 | db 138 | 139 | wLadderTiles:: ; DA23 140 | ds 4 141 | -------------------------------------------------------------------------------- /dump_gfx.py: -------------------------------------------------------------------------------- 1 | import png 2 | 3 | def rom_offset(bank, address): 4 | return address + (bank - 1) * 0x4000 5 | 6 | def copy_tile(tile_2bpp, pxmap, x, y): 7 | pixels = tile_to_pixels(tile_2bpp) 8 | 9 | for py in range(8): 10 | for px in range(8): 11 | pxmap[y+py][x+px] = pixels[py*8 + px] 12 | 13 | 14 | # Decode a 2bpp encoded tile to a list of 8*8 pixels 15 | def tile_to_pixels(tile): 16 | assert len(tile) == 16 17 | 18 | pixels = [] 19 | 20 | for row in range(8): 21 | hi = tile[row*2 + 1] 22 | lo = tile[row*2] 23 | 24 | for col in reversed(range(8)): 25 | pixels += [3 - (((hi*2) >> col & 0b10) + (lo >> col & 0b01))] 26 | 27 | return pixels 28 | 29 | def convert_2bpp_to_png(name, image, tile_width): 30 | assert len(image) % 16 == 0 31 | 32 | tile_count = len(image) // 16 33 | # Round up 34 | tile_height = (tile_count + tile_width - 1) // tile_width 35 | 36 | width = tile_width * 8 37 | height = tile_height * 8 38 | 39 | pixelmap = [[0 for _ in range(width)] for _ in range(height)] 40 | 41 | for i in range(tile_count): 42 | x = (i % tile_width) * 8 43 | y = (i // tile_width) * 8 44 | copy_tile(image[i*16: i*16 + 16], pixelmap, x, y) 45 | 46 | 47 | f = open("gfx/%s.png" % name, "wb") 48 | w = png.Writer(tile_width * 8, tile_height * 8, greyscale=True, bitdepth=2) 49 | w.write(f, pixelmap) 50 | 51 | 52 | 53 | def dump_tiles(name, address, bank, num_tiles): 54 | off = rom_offset(bank, address) 55 | 56 | convert_2bpp_to_png(name, rom[off:off + num_tiles*16], 16) 57 | 58 | if __name__ == "__main__": 59 | with open("baserom.gb", "rb") as f: 60 | rom = f.read() 61 | 62 | # These addresses come from Call_5E7, GameState_08.enemyTileOffset, 63 | # .backdropTileOffset and GameState_OE 64 | dump_tiles("commonTiles1", 0x4032, 2, 160) 65 | dump_tiles("enemiesWorld1", 0x4A32, 2, 61) 66 | dump_tiles("commonTiles2", 0x4E02, 2, 84) 67 | dump_tiles("backgroundWorld1", 0x5342, 2, 63) 68 | dump_tiles("commonTiles3", 0x5732, 2, 16) 69 | dump_tiles("menuTiles1", 0x791A, 2, 80) 70 | dump_tiles("menuTiles2", 0x7E1A, 2, 23) 71 | 72 | dump_tiles("enemiesWorld2", 0x4032, 1, 61) 73 | dump_tiles("backgroundWorld2", 0x4402, 1, 63) 74 | dump_tiles("enemiesWorld4", 0x47F2, 1, 61) 75 | dump_tiles("backgroundWorld4", 0x4BC2, 1, 63) 76 | 77 | dump_tiles("enemiesWorld3", 0x4032, 3, 61) 78 | dump_tiles("backgroundWorld3", 0x4402, 3, 63) 79 | -------------------------------------------------------------------------------- /enemies.asm: -------------------------------------------------------------------------------- 1 | CHIBIBO EQU $00 2 | CHIBIBO_STOMPED EQU $01 3 | PAKKUN_FLOWER EQU $02 4 | GANCHAN_SPAWN EQU $03 5 | NOKOBON EQU $04 6 | NOKOBON_BOMB EQU $05 7 | GENKOTSU EQU $06 8 | ENEMY_07 EQU $07 ; spawns LIFT 9 | KING_TOTOMESU EQU $08 10 | POMPON_FLOWER EQU $09 11 | HORIZONTAL_PLATFORM EQU $0A 12 | VERTICAL_PLATFORM EQU $0B 13 | FALLING_SLAB EQU $0C 14 | ENEMY_0D EQU $0D ; upside down corpse? 15 | FLY EQU $0E ; TODO Also Kumo? 16 | FLY_STOMPED EQU $0F 17 | HONEN EQU $10 18 | ENEMY_11 EQU $11 ; chibibo upside down death animation? 19 | EMEMY_12 EQU $12 ; nokobon upside down death animation 20 | LIFT EQU $13 21 | LIFT_ASCENDING EQU $14 22 | ENEMY_15 EQU $15 ; fly death sfx and animation 23 | MEKABON EQU $16 24 | MEKABON_HEAD EQU $17 25 | MEKABON_BODY EQU $18 26 | ENEMY_19 EQU $19 ; same as 15? 27 | DRAGONZAMASU EQU $1A 28 | ENEMY_1B EQU $1B 29 | MEKABON_STOMPED EQU $1C ; also SUU_STOMPED 30 | YURARIN EQU $1D 31 | FIRE_BREATH EQU $1E 32 | ENEMY_1F EQU $1F 33 | GUNION EQU $20 34 | GUNION_EXPLOSION EQU $21 35 | GUNION_FIREBALL EQU $22 36 | FIREBALL EQU $23 37 | YURARIN_BOO EQU $24 38 | SUU EQU $25 39 | ENEMY_26 EQU $26 ; two byte script to immediately disable oneself 40 | EXPLOSION EQU $27 41 | MUSHROOM_IN_FLIGHT EQU $28 42 | MUSHROOM EQU $29 43 | HEART_IN_FLIGHT EQU $2A 44 | HEART EQU $2B 45 | ENEMY_2C EQU $2C 46 | FLOWER_GROWING EQU $2D 47 | FLOWER EQU $2E 48 | ENEMY_2F EQU $2F ; TORION_LAUNCHER? 49 | TORION EQU $30 50 | TOKOTOKO EQU $31 51 | HIYOIHOI EQU $32 52 | ENEMY_33 EQU $33 ; turns into a ganchan 53 | STAR EQU $34 54 | FALLING_SPIKE EQU $35 55 | DROP_BLOCK EQU $36 56 | DROP_BLOCK_FALLING EQU $37 57 | DIAGONAL_PLATFORM_NE EQU $38 58 | DIAGONAL_PLATFORM_NW EQU $39 59 | SMALL_VERTICAL_PLATFORM EQU $3A 60 | SMALL_HORIZONTAL_PLATFORM EQU $3B 61 | BATADON EQU $3C 62 | BATADON_STOMPED EQU $3D 63 | ENEMY_3E EQU $3E 64 | GAO EQU $3F ; and NYOLOLIN 65 | GAO_STOMPED EQU $40 ; and TOKOTOKO 66 | ENEMY_41 EQU $41 67 | BUNBUN EQU $42 68 | BUNBUN_STOMPED EQU $43 69 | ENEMY_44 EQU $44 70 | ARROW EQU $45 71 | ENEMY_46 EQU $46 72 | GANCHAN EQU $47 73 | TAMAO EQU $48 74 | PIPE_CANNON EQU $49 75 | ENEMY_4A EQU $4A ; orients GIRA? 76 | GIRA EQU $4B 77 | ENEMY_4C EQU $4C 78 | ENEMY_4D EQU $4D 79 | ENEMY_4E EQU $4E 80 | ENEMY_4F EQU $4F 81 | ENEMY_50 EQU $50 82 | POMPON_SPORE EQU $51 83 | ROKETON EQU $52 84 | CHICKEN EQU $53 85 | ROTO_DISC EQU $54 86 | REVERSE_PAKKUN_FLOWER EQU $55 87 | PIONPI EQU $56 88 | PIONPI_STOMED EQU $57 89 | ENEMY_58 EQU $58 ; DRAGONZAMASU_FIRE? 90 | CHIKAKO EQU $59 91 | DIAGONAL_GIRA EQU $5A 92 | ENEMY_5B EQU $5B ; tatanga rising up? 93 | CANNONBALL EQU $5C 94 | SMALL_CANNONBALL_TOP EQU $5D 95 | SMALL_CANNONBALL_MIDDLE EQU $5E 96 | SMALL_CANNONBALL_BOTTOM EQU $5F 97 | TATANGA EQU $60 98 | BIOKINTON EQU $61 99 | ENEMY_62 EQU $62 ; positions Tatanga? 100 | -------------------------------------------------------------------------------- /dump_levels_and_enemies.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | enemy_names = [] 4 | with open("enemies.asm", "r") as f: 5 | for line in f: 6 | enemy_names.append(line.split(" ")[0]) 7 | 8 | with open("baserom.gb", "rb") as f: 9 | rom = f.read() 10 | 11 | BANKS = 4 12 | BANK_SIZE = 1 << 16 >> 2 13 | ROM_SIZE = BANKS * BANK_SIZE 14 | 15 | assert len(rom) == ROM_SIZE 16 | 17 | # World "5" is the start menu and the hangar after a boss level 18 | world_banks = [2, 1, 3, 1, 2] 19 | 20 | def rom_offset(bank, address): 21 | assert address >= 0x4000 22 | return (bank - 1) * 0x4000 + address 23 | 24 | def read_byte(bank, address): 25 | return rom[rom_offset(bank, address)] 26 | 27 | def read_word(bank, address): 28 | offset = rom_offset(bank, address) 29 | return struct.unpack("> 2 6 | ROM_SIZE = BANK_SIZE * BANKS 7 | 8 | # Funny how the Game Boy had to make do with 8 kB of RAM, and I can now use a 9 | # array many times that size without thinking about it. It's not even worth it 10 | # at all to use a more efficient approach 11 | coverage_map = [1] * ROM_SIZE 12 | 13 | # Subtract what is included from the binary file 14 | coverage = [BANK_SIZE] * BANKS 15 | 16 | def tally_file(filepath): 17 | with open(filepath) as f: 18 | lines = f.readlines() 19 | 20 | for line in lines: 21 | s = line.rstrip("\n").split(", ") 22 | if not s[0].startswith('INCBIN') or not s[0].endswith('baserom.gb"'): 23 | continue 24 | 25 | assert len(s) == 3 26 | 27 | start = int(s[1][1:], 16) 28 | 29 | l = s[2].split(' - ') 30 | if len(l) == 1: 31 | length = int(l[0][1:], 16) 32 | elif len(l) == 2: 33 | length = int(l[0][1:], 16) - int(l[1][1:], 16) 34 | else: 35 | raise 36 | 37 | for i in range(length): 38 | coverage_map[start + i] = 0 39 | 40 | bank = start // BANK_SIZE 41 | coverage[bank] -= length 42 | 43 | for root, dirs, files in os.walk("."): 44 | for file in files: 45 | if file.endswith(".asm"): 46 | tally_file(os.path.join(root, file)) 47 | 48 | 49 | for bank in range(BANKS): 50 | print("Bank {}: {:5} bytes: {:.3g}%".format(bank, coverage[bank], 100 * coverage[bank] / BANK_SIZE)) 51 | 52 | total_coverage = sum([c for c in coverage]) 53 | print("Total : {:5} bytes: {:.3g}%".format(total_coverage, 100 * total_coverage / (BANKS * BANK_SIZE))) 54 | 55 | from PIL import Image 56 | 57 | WIDTH = 64 58 | HEIGHT = 64 59 | KB_HEIGHT = 0x1000 // (WIDTH * 16) 60 | BANK_HEIGHT = BANK_SIZE // (WIDTH * 16) 61 | KB_MARGIN = 1 62 | BANK_MARGIN = 2 63 | assert WIDTH * HEIGHT * 16 == ROM_SIZE # 16 bytes per tile 64 | SCALE = 4 65 | 66 | PX_WIDTH = WIDTH * 8 67 | PX_HEIGHT = HEIGHT * 8 + BANK_MARGIN * (BANKS - 1) + KB_MARGIN * (ROM_SIZE // 0x1000 - 1) 68 | 69 | PALETTE1 = [(255,255,255), (160,160,160), (85,85,85), (0,0,0)] 70 | PALETTE2 = [(155,188,15), (139,172,15), (48,98,48), (15,56,16)] 71 | 72 | img = Image.new("P", (PX_WIDTH, PX_HEIGHT), 8) 73 | #img.putpalette([ 74 | # 255,255,255, 180,180,180, 105,105,105, 0,0,0, # Monochrome 75 | # 0xE0,0xF8,0xD0, 0x88,0xC0,0x70, 0x34,0x68,0x56, 0x08,0x18,0x20, # Game Boy palette 76 | # 255,255,200 77 | #]) 78 | img.putpalette([ 79 | 255,255,255, 160,160,160, 85,85,85, 0,0,0, # Monochrome 80 | 155,188,15, 139,172,15, 48,98,48, 15,56,16, # Game Boy palette 81 | 255,255,200 82 | ]) 83 | 84 | with open("baserom.gb", "rb") as f: 85 | rom = f.read() 86 | 87 | def copy_tile(ix, iy, covered, bank, kb): 88 | tile = rom[(iy*WIDTH + ix) * 16:(iy*WIDTH + ix) * 16 + 16] 89 | 90 | for row in range(8): 91 | hi_byte = tile[row * 2 + 1] 92 | lo_byte = tile[row * 2] 93 | 94 | y = iy * 8 + row + bank * BANK_MARGIN + kb * KB_MARGIN 95 | 96 | x = ix * 8 + 0 97 | index = ((hi_byte & 0b10000000) >> 6) + ((lo_byte & 0b10000000) >> 7) 98 | img.putpixel((x,y), covered + index) 99 | x = ix * 8 + 1 100 | index = ((hi_byte & 0b01000000) >> 5) + ((lo_byte & 0b01000000) >> 6) 101 | img.putpixel((x,y), covered + index) 102 | x = ix * 8 + 2 103 | index = ((hi_byte & 0b00100000) >> 4) + ((lo_byte & 0b00100000) >> 5) 104 | img.putpixel((x,y), covered + index) 105 | x = ix * 8 + 3 106 | index = ((hi_byte & 0b00010000) >> 3) + ((lo_byte & 0b00010000) >> 4) 107 | img.putpixel((x,y), covered + index) 108 | x = ix * 8 + 4 109 | index = ((hi_byte & 0b00001000) >> 2) + ((lo_byte & 0b00001000) >> 3) 110 | img.putpixel((x,y), covered + index) 111 | x = ix * 8 + 5 112 | index = ((hi_byte & 0b00000100) >> 1) + ((lo_byte & 0b00000100) >> 2) 113 | img.putpixel((x,y), covered + index) 114 | x = ix * 8 + 6 115 | index = ((hi_byte & 0b00000010) >> 0) + ((lo_byte & 0b00000010) >> 1) 116 | img.putpixel((x,y), covered + index) 117 | x = ix * 8 + 7 118 | index = ((hi_byte & 0b00000001) << 1) + ((lo_byte & 0b00000001) >> 0) 119 | img.putpixel((x,y), covered + index) 120 | 121 | 122 | for ix in range(WIDTH): 123 | for iy in range(HEIGHT): 124 | covered = all(coverage_map[(iy * WIDTH + ix)*16: (iy * WIDTH + ix)*16 + 16]) # any or all? 125 | copy_tile(ix, iy, 4 if covered else 0, iy // BANK_HEIGHT, iy // KB_HEIGHT) 126 | 127 | img.save("coverage_map.png") 128 | -------------------------------------------------------------------------------- /bank1.asm: -------------------------------------------------------------------------------- 1 | INCLUDE "sound_constants.asm" 2 | 3 | SECTION "bank 1", ROMX, BANK[1] 4 | ; Unused slots are filled with repeats of other pointers 5 | ; todo should be named levelscreenpointers or so? 6 | LevelPointers:: ; 4000 Same every bank 7 | LevelPointersBank1:: ; 1:4000 8 | dw $55BB 9 | dw $55E2 10 | dw $5605 11 | dw $55BB ; 2-1 12 | dw $55E2 ; 2-2 13 | dw $5605 ; 2-3 14 | dw $55BB 15 | dw $55E2 16 | dw $5605 17 | dw $5630 ; 4-1 18 | dw $5665 ; 4-2 19 | dw $5694 ; 4-3 20 | dw $55BB 21 | 22 | LevelEnemyPointers:: ; 401A 23 | LevelEnemyPointersBank1:: ; 1:401A 24 | dw $5311 25 | dw $5405 26 | dw $54D5 27 | dw $5179 ; 2-1 28 | dw $5222 ; 2-2 29 | dw $529B ; 2-3 30 | dw $5311 31 | dw $5405 32 | dw $54D5 33 | dw $5311 ; 4-1 34 | dw $5405 ; 4-2 35 | dw $54D5 ; 4-3 36 | 37 | INCBIN "gfx/enemiesWorld2.2bpp" 38 | INCBIN "gfx/backgroundWorld2.2bpp" 39 | 40 | INCBIN "gfx/enemiesWorld4.2bpp" 41 | INCBIN "gfx/backgroundWorld4.2bpp" 42 | 43 | Call_4FB2:: ; 4FB2 44 | ldh a, [hFrameCounter] 45 | and a, $01 46 | ret nz 47 | ld a, [$C0D2] 48 | cp a, $07 49 | jr c, .jmp_4FCB 50 | ldh a, [hScrollX] 51 | and a, $0C 52 | jr nz, .jmp_4FCB 53 | ldh a, [hScrollX] 54 | and a, $FC 55 | ldh [hScrollX], a 56 | ret 57 | 58 | .jmp_4FCB 59 | ldh a, [hScrollX] 60 | inc a 61 | ldh [hScrollX], a 62 | ld b, $01 63 | call Call_1D26.call_1EA4 ; scroll sprites? 64 | call Call_2C9F ; scroll enemies? 65 | ld hl, $C202 ; X coord 66 | dec [hl] 67 | ld a, [hl] 68 | and a 69 | jr nz, .jmp_4FE2 70 | ld [hl], $F0 71 | .jmp_4FE2 72 | ld c, $08 73 | call Call_50CC 74 | ld hl, $C202 75 | inc [hl] 76 | ret 77 | 78 | Call_4FEC:: ; 4FEC 79 | ldh a, [hJoyHeld] 80 | bit 6, a ; up button 81 | jr nz, .jmp_5034 82 | bit 7, a ; down button 83 | jr nz, .jmp_5022 84 | .jmp_4FF6 85 | ldh a, [hJoyHeld] 86 | bit 4, a ; right button 87 | jr nz, .jmp_5014 88 | bit 5, a ; left button 89 | ret z 90 | ld c, $FA 91 | call Call_50CC 92 | ld hl, $C202 93 | ld a, [hl] 94 | cp a, $10 95 | ret c 96 | dec [hl] 97 | ld a, [$C0D2] 98 | cp a, $07 99 | ret nc 100 | dec [hl] 101 | ret 102 | 103 | .jmp_5014 104 | ld c, $08 105 | call Call_50CC 106 | ld hl, $C202 107 | ld a, [hl] 108 | cp a, $A0 109 | ret nc 110 | inc [hl] 111 | ret 112 | 113 | .jmp_5022 114 | call Call_5089 115 | cp a, $FF 116 | jr z, .jmp_4FF6 117 | ld hl, $C201 ; Y coord 118 | ld a, [hl] 119 | cp a, $94 ; ? 120 | jr nc, .jmp_4FF6 121 | inc [hl] 122 | jr .jmp_4FF6 123 | 124 | .jmp_5034 125 | call .call_5046 126 | cp a, $FF 127 | jr z, .jmp_4FF6 128 | ld hl, $C201 129 | ld a, [hl] 130 | cp a, $30 131 | jr c, .jmp_4FF6 132 | dec [hl] 133 | jr .jmp_4FF6 134 | 135 | .call_5046 136 | ld hl, $C201 137 | ldh a, [hSuperStatus] 138 | ld b, $FD 139 | and a 140 | jr z, .jmp_5052 141 | ld b, $FC 142 | .jmp_5052 143 | ldi a, [hl] 144 | add b 145 | ldh [$FFAD], a 146 | ldh a, [hScrollX] 147 | ld b, [hl] 148 | add b 149 | add a, $02 150 | ldh [$FFAE], a 151 | call LookupTile 152 | cp a, $60 153 | jr nc, .jmp_5071 154 | ldh a, [$FFAE] 155 | add a, $FA 156 | ldh [$FFAE], a 157 | call LookupTile 158 | cp a, $60 159 | ret c 160 | .jmp_5071 161 | cp a, $F4 162 | jr z, .jmp_5078 163 | ld a, $FF 164 | ret 165 | 166 | .jmp_5078 167 | push hl 168 | pop de 169 | ld hl, $FFEE 170 | ld [hl], $C0 171 | inc l 172 | ld [hl], d ; FFEF 173 | inc l 174 | ld [hl], e ; FFF0 175 | ld a, SFX_COIN 176 | ld [$DFE0], a 177 | ret 178 | 179 | Call_5089:: ; 5089 180 | ld hl, $C201 181 | ldi a, [hl] 182 | add a, $0A 183 | ldh [$FFAD], a 184 | ldh a, [hScrollX] 185 | ld b, a 186 | ld a, [hl] 187 | add b 188 | add a, $FE 189 | ldh [$FFAE], a 190 | call LookupTile 191 | cp a, $60 192 | jr nc, .jmp_50B4 193 | ldh a, [$FFAE] 194 | add a, $04 195 | ldh [$FFAE], a 196 | call LookupTile 197 | cp a, $E1 198 | jp z, Jmp_1B45 ; end of level? 199 | cp a, $60 200 | jr nc, .jmp_50B4 201 | ret 202 | 203 | .jmp_50B4 204 | cp a, $F4 205 | jr nz, .jmp_50C9 206 | push hl 207 | pop de 208 | ld hl, $FFEE 209 | ld [hl], $C0 210 | inc l 211 | ld [hl], d 212 | inc l 213 | ld [hl], e 214 | ld a, SFX_COIN 215 | ld [$DFE0], a 216 | ret 217 | 218 | .jmp_50C9 219 | ld a, $FF 220 | ret 221 | 222 | Call_50CC:: ; 50CC 223 | ld de, $0502 224 | ldh a, [hSuperStatus] 225 | cp a, $02 226 | jr z, .jmp_50D8 227 | ld de, $0501 228 | .jmp_50D8 229 | ld hl, $C201 230 | ldi a, [hl] 231 | add d 232 | ldh [$FFAD], a 233 | ld b, [hl] 234 | ld a, c 235 | add b 236 | ld b, a 237 | ldh a, [hScrollX] 238 | add b 239 | ldh [$FFAE], a 240 | push de 241 | call LookupTile 242 | pop de 243 | cp a, $60 244 | jr c, .jmp_5101 245 | cp a, $F4 ; coin? 246 | jr z, .jmp_5107 247 | cp a, $E1 ; boss switch 248 | jp z, Jmp_1B45 249 | cp a, $83 ; mushroom... 250 | jp z, Jmp_1B45 251 | pop hl 252 | ret 253 | 254 | .jmp_5101 255 | ld d, $FD 256 | dec e 257 | jr nz, .jmp_50D8 258 | ret 259 | 260 | .jmp_5107 261 | push hl 262 | pop de 263 | ld hl, $FFEE 264 | ld [hl], $C0 265 | inc l 266 | ld [hl], d 267 | inc l 268 | ld [hl], e 269 | ld a, SFX_COIN 270 | ld [$DFE0], a 271 | ret 272 | 273 | Call_5118:: ; 5118 274 | ld b, $03 ; 3 projectiles 275 | ld hl, $FFA9 ; projectile status 276 | ld de, wOAMBuffer + 1 277 | .loop 278 | ldi a, [hl] 279 | and a 280 | jr nz, .jmp_512C 281 | .jmp_5124 282 | inc e 283 | inc e 284 | inc e 285 | inc e 286 | dec b 287 | jr nz, .loop 288 | ret 289 | 290 | .jmp_512C 291 | push hl 292 | push de 293 | push bc 294 | dec l 295 | ld a, [de] 296 | inc a 297 | inc a 298 | ld [de], a 299 | ldh [$FFA1], a 300 | ldh [$FFC3], a ; isn't this for enemies? 301 | cp a, $A9 302 | jr c, .jmp_5143 303 | .jmp_513C 304 | xor a 305 | res 0, e 306 | ld [de], a 307 | ld [hl], a 308 | jr .jmp_5156 309 | 310 | .jmp_5143 311 | add a, $02 312 | push af 313 | dec e 314 | ld a, [de] 315 | ldh [$FFC2], a 316 | add a, $06 317 | ldh [$FFAD], a 318 | pop af 319 | call FindNeighboringTile 320 | jr c, .jmp_5156 321 | jr .jmp_513C 322 | 323 | .jmp_5156 324 | pop bc 325 | pop de 326 | pop hl 327 | call Call_200A ; collision with enemy 328 | jr .jmp_5124 329 | 330 | .jmp_515E 331 | ld a, [$C202] 332 | cp a, $01 333 | jr c, .jmp_5168 334 | cp a, $ED 335 | ret c 336 | .jmp_5168 337 | xor a 338 | ldh [hSuperStatus], a 339 | ldh [hSuperballMario], a 340 | inc a 341 | ldh [hGameState], a ; dead 342 | inc a 343 | ld [$DFE8], a ; MUS_DEATH 344 | ld a, $90 345 | ldh [hTimer], a 346 | ret 347 | 348 | SECTION "bank 1 levels", ROMX[$55BB], BANK[1] 349 | INCBIN "baserom.gb", $55BB, $8000 - $55BB 350 | -------------------------------------------------------------------------------- /dump_music.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | with open("baserom.gb", "rb") as f: 4 | rom = f.read() 5 | 6 | # TODO Put this crap in a module or so? 7 | def read_byte(address): 8 | return rom[address + 2*0x4000] 9 | 10 | def read_word(address): 11 | off = address + 2*0x4000 12 | 13 | return struct.unpack("