from ..assembler import ASM from ..entranceInfo import ENTRANCE_INFO from ..roomEditor import RoomEditor, ObjectWarp, ObjectHorizontal from ..backgroundEditor import BackgroundEditor from .. import utils def bugfixWrittingWrongRoomStatus(rom): # The normal rom contains a pretty nasty bug where door closing triggers in D7/D8 can effect doors in # dungeons D1-D6. This fix should prevent this. rom.patch(0x02, 0x1D21, 0x1D3C, ASM("call $5B9F"), fill_nop=True) def fixEggDeathClearingItems(rom): rom.patch(0x01, 0x1E79, ASM("cp $0A"), ASM("cp $08")) def fixWrongWarp(rom): rom.patch(0x00, 0x18CE, ASM("cp $04"), ASM("cp $03")) re = RoomEditor(rom, 0x2b) for x in range(10): re.removeObject(x, 7) re.objects.append(ObjectHorizontal(0, 7, 0x2C, 10)) while len(re.getWarps()) < 4: re.objects.append(ObjectWarp(1, 3, 0x7a, 80, 124)) re.store(rom) def bugfixBossroomTopPush(rom): rom.patch(0x14, 0x14D9, ASM(""" ldh a, [$99] dec a ldh [$99], a """), ASM(""" jp $7F80 """), fill_nop=True) rom.patch(0x14, 0x3F80, "00" * 0x80, ASM(""" ldh a, [$99] cp $50 jr nc, up down: inc a ldh [$99], a jp $54DE up: dec a ldh [$99], a jp $54DE """), fill_nop=True) def bugfixPowderBagSprite(rom): rom.patch(0x03, 0x2055, "8E16", "0E1E") def easyColorDungeonAccess(rom): re = RoomEditor(rom, 0x312) re.entities = [(3, 1, 246), (6, 1, 247)] re.store(rom) def removeGhost(rom): ## Ghost patch # Do not have the ghost follow you after dungeon 4 rom.patch(0x03, 0x1E1B, ASM("LD [$DB79], A"), "", fill_nop=True) def alwaysAllowSecretBook(rom): rom.patch(0x15, 0x3F23, ASM("ld a, [$DB0E]\ncp $0E"), ASM("xor a\ncp $00"), fill_nop=True) rom.patch(0x15, 0x3F2A, 0x3F30, "", fill_nop=True) def cleanup(rom): # Remove unused rooms to make some space in the rom re = RoomEditor(rom, 0x2C4) re.objects = [] re.entities = [] re.store(rom, 0x2C4) re.store(rom, 0x2D4) re.store(rom, 0x277) re.store(rom, 0x278) re.store(rom, 0x279) re.store(rom, 0x1ED) re.store(rom, 0x1FC) # Beta room rom.texts[0x02B] = b'' # unused text def disablePhotoPrint(rom): rom.patch(0x28, 0x07CC, ASM("ldh [$01], a\nldh [$02], a"), "", fill_nop=True) # do not reset the serial link rom.patch(0x28, 0x0483, ASM("ld a, $13"), ASM("jr $EA", 0x4483)) # Do not print on A press, but jump to cancel rom.patch(0x28, 0x0492, ASM("ld hl, $4439"), ASM("ret"), fill_nop=True) # Do not show the print/cancel overlay def fixMarinFollower(rom): # Allow opening of D0 with marin rom.patch(0x02, 0x3402, ASM("ld a, [$DB73]"), ASM("xor a"), fill_nop=True) # Instead of uselessly checking for sidescroller rooms for follower spawns, check for color dungeon instead rom.patch(0x01, 0x1FCB, 0x1FD3, ASM("cp $FF\nret z"), fill_nop=True) # Do not load marin graphics in color dungeon rom.patch(0x00, 0x2EA6, 0x2EB0, ASM("cp $FF\njp $2ED3"), fill_nop=True) # Fix marin on taltal bridge causing a lockup if you have marin with you # This changes the location where the index to the marin entity is stored from it's normal location # To the memory normal reserved for progress on the egg maze (which is reset to 0 on a warp) rom.patch(0x18, 0x1EF7, ASM("ld [$C50F], a"), ASM("ld [$C5AA], a")) rom.patch(0x18, 0x2126, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]")) rom.patch(0x18, 0x2139, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]")) rom.patch(0x18, 0x214F, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]")) rom.patch(0x18, 0x2166, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]")) def quickswap(rom, button): rom.patch(0x00, 0x1094, ASM("jr c, $49"), ASM("jr nz, $49")) # prevent agressive key repeat rom.patch(0x00, 0x10BC, # Patch the open minimap code to swap the your items instead ASM("xor a\nld [$C16B], a\nld [$C16C], a\nld [$DB96], a\nld a, $07\nld [$DB95], a"), ASM(""" ld a, [$DB%02X] ld e, a ld a, [$DB%02X] ld [$DB%02X], a ld a, e ld [$DB%02X], a ret """ % (button, button + 2, button, button + 2))) def injectMainLoop(rom): rom.patch(0x00, 0x0346, ASM(""" ldh a, [$FE] and a jr z, $08 """), ASM(""" ; Call the mainloop handler xor a rst 8 """), fill_nop=True) def warpHome(rom): # Patch the S&Q menu to allow 3 options rom.patch(0x01, 0x012A, 0x0150, ASM(""" ld hl, $C13F call $6BA8 ; make sound on keypress ldh a, [$CC] ; load joystick status and $04 ; if up jr z, noUp dec [hl] noUp: ldh a, [$CC] ; load joystick status and $08 ; if down jr z, noDown inc [hl] noDown: ld a, [hl] cp $ff jr nz, noWrapUp ld a, $02 noWrapUp: cp $03 jr nz, noWrapDown xor a noWrapDown: ld [hl], a jp $7E02 """), fill_nop=True) rom.patch(0x01, 0x3E02, 0x3E20, ASM(""" swap a add a, $48 ld hl, $C018 ldi [hl], a ld a, $24 ldi [hl], a ld a, $BE ldi [hl], a ld [hl], $00 ret """), fill_nop=True) rom.patch(0x01, 0x00B7, ASM(""" ld a, [$C13F] cp $01 jr z, $3B """), ASM(""" ld a, [$C13F] jp $7E20 """), fill_nop=True) re = RoomEditor(rom, 0x2a3) warp = re.getWarps()[0] type = 0x00 map = 0x00 room = warp.room x = warp.target_x y = warp.target_y one_way = [ 'd0', 'd1', 'd3', 'd4', 'd6', 'd8', 'animal_cave', 'right_fairy', 'rooster_grave', 'prairie_left_cave2', 'prairie_left_fairy', 'armos_fairy', 'boomerang_cave', 'madbatter_taltal', 'forest_madbatter', ] one_way = {ENTRANCE_INFO[x].room for x in one_way} if warp.room in one_way: # we're starting at a one way exit room # warp indoors to avoid soft locks type = 0x01 map = 0x10 room = 0xa3 x = 0x50 y = 0x7f rom.patch(0x01, 0x3E20, 0x3E6B, ASM(""" ; First, handle save & quit cp $01 jp z, $40F9 and a jp z, $40BE ; return to normal "return to game" handling ld a, [$C509] ; Check if we have an item in the shop and a jp nz, $40BE ; return to normal "return to game" handling ld a, $0B ld [$DB95], a call $0C7D ; Replace warp0 tile data, and put link on that tile. ld a, $%02x ; Type ld [$D401], a ld a, $%02x ; Map ld [$D402], a ld a, $%02x ; Room ld [$D403], a ld a, $%02x ; X ld [$D404], a ld a, $%02x ; Y ld [$D405], a ldh a, [$98] swap a and $0F ld e, a ldh a, [$99] sub $08 and $F0 or e ld [$D416], a ld a, $07 ld [$DB96], a ret jp $40BE ; return to normal "return to game" handling """ % (type, map, room, x, y)), fill_nop=True) # Patch the RAM clear not to delete our custom dialog when we screen transition # This is kind of horrible as it relies on bank 1 being loaded, lol rom.patch(0x01, 0x042C, "C629", "6B7E") rom.patch(0x01, 0x3E6B, 0x3E7B, ASM(""" ld bc, $A0 call $29DC ld bc, $1200 ld hl, $C100 call $29DF ret """), fill_nop=True) # Patch the S&Q screen to have 3 options. be = BackgroundEditor(rom, 0x0D) for n in range(2, 18): be.tiles[0x99C0 + n] = be.tiles[0x9980 + n] be.tiles[0x99A0 + n] = be.tiles[0x9960 + n] be.tiles[0x9980 + n] = be.tiles[0x9940 + n] be.tiles[0x9960 + n] = be.tiles[0x98e0 + n] be.tiles[0x9960 + 10] = 0xCE be.tiles[0x9960 + 11] = 0xCF be.tiles[0x9960 + 12] = 0xC4 be.tiles[0x9960 + 13] = 0x7F be.tiles[0x9960 + 14] = 0x7F be.store(rom) sprite_data = [ 0b00000000, 0b01000100, 0b01000101, 0b01000101, 0b01111101, 0b01000101, 0b01000101, 0b01000100, 0b00000000, 0b11100100, 0b00010110, 0b00010101, 0b00010100, 0b00010100, 0b00010100, 0b11100100, ] for n in range(32): rom.banks[0x0F][0x08E0 + n] = sprite_data[n // 2] def addFrameCounter(rom, check_count): # Patch marin giving the start the game to jump to a custom handler rom.patch(0x05, 0x1299, ASM("ld a, $01\ncall $2385"), ASM("push hl\nld a, $0D\nrst 8\npop hl"), fill_nop=True) # Add code that needs to be called every frame to tick our ingame time counter. rom.patch(0x00, 0x0091, "00" * (0x100 - 0x91), ASM(""" ld a, [$DB95] ;Get the gameplay type dec a ; and if it was 1 ret z ; we are at the credits and the counter should stop. ; Check if the timer expired ld hl, $FF0F bit 2, [hl] ret z res 2, [hl] ; Increase the "subsecond" counter, and continue if it "overflows" call $27D0 ; Enable SRAM ld hl, $B000 ld a, [hl] inc a cp $20 ld [hl], a ret nz xor a ldi [hl], a ; Increase the seconds counter/minutes/hours counter increaseSecMinHours: ld a, [hl] inc a daa ld [hl], a cp $60 ret nz xor a ldi [hl], a jr increaseSecMinHours """), fill_nop=True) # Replace a cgb check with the call to our counter code. rom.patch(0x00, 0x0367, ASM("ld a, $0C\ncall $0B0B"), ASM("call $0091\nld a, $2C")) # Do not switch to 8x8 sprite mode rom.patch(0x17, 0x2E9E, ASM("res 2, [hl]"), "", fill_nop=True) # We need to completely reorder link sitting on the raft to work with 16x8 sprites. sprites = rom.banks[0x38][0x1600:0x1800] sprites[0x1F0:0x200] = b'\x00' * 16 for index, position in enumerate( (0, 0x1F, 1, 0x1F, 2, 0x1F, 7, 8, 3, 9, 4, 10, 5, 11, 6, 12, 3, 13, 4, 14, 5, 15, 6, 16, 3, 17, 4, 18, 5, 19, 6, 20, )): rom.banks[0x38][0x1600+index*0x10:0x1610+index*0x10] = sprites[position*0x10:0x10+position*0x10] rom.patch(0x27, 0x376E, 0x3776, "00046601", fill_nop=True) rom.patch(0x27, 0x384E, ASM("ld c, $08"), ASM("ld c, $04")) rom.patch(0x27, 0x3776, 0x3826, "FA046002" "0208640402006204" "0A106E030A086C030A006A030AF86803" "FA046002" "0208640402006204" "0A1076030A0874030A0072030AF87003" "FA046002" "0208640402006204" "0A107E030A087C030A007A030AF87803" , fill_nop=True) rom.patch(0x27, 0x382E, ASM("ld a, $6C"), ASM("ld a, $80")) # OAM start position rom.patch(0x27, 0x384E, ASM("ld c, $08"), ASM("ld c, $04")) # Amount of overlay OAM data rom.patch(0x27, 0x3826, 0x382E, ASM("dw $7776, $7792, $77AE, $7792")) # pointers to animation rom.patch(0x27, 0x3846, ASM("ld c, $2C"), ASM("ld c, $1C")) # Amount of OAM data # TODO: fix flying windfish # Upper line of credits roll into "TIME" rom.patch(0x17, 0x069D, 0x0713, ASM(""" ld hl, OAMData ld de, $C000 ; OAM Buffer ld bc, $0048 call $2914 ret OAMData: db $20, $18, $34, $00 ;T db $20, $20, $20, $00 ;I db $20, $28, $28, $00 ;M db $20, $30, $18, $00 ;E db $20, $70, $16, $00 ;D db $20, $78, $18, $00 ;E db $20, $80, $10, $00 ;A db $20, $88, $34, $00 ;T db $20, $90, $1E, $00 ;H db $50, $18, $14, $00 ;C db $50, $20, $1E, $00 ;H db $50, $28, $18, $00 ;E db $50, $30, $14, $00 ;C db $50, $38, $24, $00 ;K db $50, $40, $32, $00 ;S db $68, $38, $%02x, $00 ;0 db $68, $40, $%02x, $00 ;0 db $68, $48, $%02x, $00 ;0 """ % ((((check_count // 100) % 10) * 2) | 0x40, (((check_count // 10) % 10) * 2) | 0x40, ((check_count % 10) * 2) | 0x40), 0x469D), fill_nop=True) # Lower line of credits roll into XX XX XX rom.patch(0x17, 0x0784, 0x082D, ASM(""" ld hl, OAMData ld de, $C048 ; OAM Buffer ld bc, $0038 call $2914 call $27D0 ; Enable SRAM ld hl, $C04A ld a, [$B003] ; hours call updateOAM ld a, [$B002] ; minutes call updateOAM ld a, [$B001] ; seconds call updateOAM ld a, [$DB58] ; death count high call updateOAM ld a, [$DB57] ; death count low call updateOAM ld a, [$B011] ; check count high call updateOAM ld a, [$B010] ; check count low call updateOAM ret updateOAM: ld de, $0004 ld b, a swap a and $0F add a, a or $40 ld [hl], a add hl, de ld a, b and $0F add a, a or $40 ld [hl], a add hl, de ret OAMData: db $38, $18, $40, $00 ;0 (10 hours) db $38, $20, $40, $00 ;0 (1 hours) db $38, $30, $40, $00 ;0 (10 minutes) db $38, $38, $40, $00 ;0 (1 minutes) db $38, $48, $40, $00 ;0 (10 seconds) db $38, $50, $40, $00 ;0 (1 seconds) db $00, $00, $40, $00 ;0 (1000 death) db $38, $80, $40, $00 ;0 (100 death) db $38, $88, $40, $00 ;0 (10 death) db $38, $90, $40, $00 ;0 (1 death) ; checks db $00, $00, $40, $00 ;0 db $68, $18, $40, $00 ;0 db $68, $20, $40, $00 ;0 db $68, $28, $40, $00 ;0 """, 0x4784), fill_nop=True) # Grab the "mostly" complete A-Z font sprites = rom.banks[0x38][0x1100:0x1400] for index, position in enumerate(( 0x10, 0x20, # A 0x11, 0x21, # B 0x12, 0x12 | 0x100, # C 0x13, 0x23, # D 0x14, 0x24, # E 0x14, 0x25, # F 0x12, 0x22, # G 0x20 | 0x100, 0x26, # H 0x17, 0x17 | 0x100, # I 0x28, 0x28, # J 0x19, 0x29, # K 0x06, 0x07, # L 0x1A, 0x2A, # M 0x1B, 0x2B, # N 0x00, 0x00, # O? 0x00, 0x00, # P? #0x00, 0x00, # Q? 0x11, 0x18, # R 0x1C, 0x2C, # S 0x1D, 0x2D, # T 0x26, 0x10, # U 0x00, 0x00, # V? 0x1E, 0x2E, # W #0x00, 0x00, # X? #0x00, 0x00, # Y? 0x27, 0x27, # Z )): sprite = sprites[(position&0xFF)*0x10:0x10+(position&0xFF)*0x10] if position & 0x100: for n in range(4): sprite[n * 2], sprite[14 - n * 2] = sprite[14 - n * 2], sprite[n * 2] sprite[n * 2 + 1], sprite[15 - n * 2] = sprite[15 - n * 2], sprite[n * 2 + 1] rom.banks[0x38][0x1100+index*0x10:0x1110+index*0x10] = sprite # Number graphics change for the end tile_graphics = """ ........ ........ ........ ........ ........ ........ ........ ........ ........ ........ .111111. ..1111.. .111111. .111111. ..11111. 11111111 .111111. 11111111 .111111. .111111. 11333311 .11331.. 11333311 11333311 .113331. 13333331 11333311 13333331 11333311 11333311 13311331 113331.. 13311331 13311331 1133331. 13311111 13311331 11111331 13311331 13311331 13311331 133331.. 13311331 11111331 1331331. 1331.... 13311331 ...11331 13311331 13311331 13311331 133331.. 11111331 ....1331 1331331. 1331.... 13311111 ...13311 13311331 13311331 13311331 111331.. ...13311 .1111331 1331331. 1331111. 1331.... ..11331. 13311331 13311331 13311331 ..1331.. ..11331. .1333331 13313311 13333311 1331111. ..13311. 11333311 11333331 13311331 ..1331.. ..13311. .1111331 13333331 13311331 13333311 .11331.. 13311331 .1111331 13311331 ..1331.. .11331.. ....1331 11113311 11111331 13311331 .13311.. 13311331 ....1331 13311331 ..1331.. .13311.. ....1331 ...1331. ....1331 13311331 11331... 13311331 ....1331 13311331 ..1331.. 11331... 11111331 ...1331. 11111331 13311331 13311... 13311331 11111331 13311331 ..1331.. 13311111 13311331 ...1331. 13311331 13311331 1331.... 13311331 13311331 11333311 ..1331.. 13333331 11333311 ...1331. 11333311 11333311 1331.... 11333311 11333311 .111111. ..1111.. 11111111 .111111. ...1111. .111111. .111111. 1111.... .111111. .111111. ........ ........ ........ ........ ........ ........ ........ ........ ........ ........ """.strip() for n in range(10): gfx_high = "\n".join([line.split(" ")[n] for line in tile_graphics.split("\n")[:8]]) gfx_low = "\n".join([line.split(" ")[n] for line in tile_graphics.split("\n")[8:]]) rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high) rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low) def addWarpImprovements(rom, extra_warps): # Patch in a warp icon tile = utils.createTileData( \ """11111111 10000000 10200320 10323200 10033300 10023230 10230020 10000000""", key="0231") MINIMAP_BASE = 0x3800 # This is replacing a junk tile never used on the minimap rom.banks[0x2C][MINIMAP_BASE + len(tile) * 0x65 : MINIMAP_BASE + len(tile) * 0x66] = tile # Allow using ENTITY_WARP for finding which map sections are warps # Interesting - 3CA0 should be free, but something has pushed all the code forward a byte rom.patch(0x02, 0x3CA1, None, ASM(""" ld e, $0F ld d, $00 warp_search_loop: ; Warp search loop ld hl, $C3A0 add hl, de ; $5FE1: $19 ld a, [hl] ; $5FE2: $7E cp $61 ; ENTITY_WARP jr nz, search_continue ; if it's not a warp, check the next one ld hl, $C280 add hl, de ld a, [hl] and a jr z, search_continue ; if this is despawned, check the next one found: jp $511B ; found search_continue: dec e ld a, e cp $FF jr nz, warp_search_loop not_found: jp $512B """)) # Insert redirect to above code rom.patch(0x02, 0x1109, ASM(""" ldh a, [$F6] cp 1 """), ASM(""" jp $7CA1 nop """)) # Leaves some extra bytes behind, if we need more space in 0x02 # On warp hole, open map instead rom.patch(0x19, 0x1DB9, None, ASM(""" ld a, 7 ; Set GAMEPLAY_MAP ld [$DB95], a ld a, 0 ; reset subtype ld [$DB96], a ld a, 1 ; Set flag for using teleport ld [$FFDD], a ret """), fill_nop=True) # Patch over some instructions that decided if we are in debug mode holding some # buttons with instead checking for FFDD (why FFDD? It appears to be never used anywhere, so we repurpose it for "is in teleport mode") rom.banks[0x01][0x17B8] = 0xDD rom.banks[0x01][0x17B9] = 0xFF rom.banks[0x01][0x17FD] = 0xDD rom.banks[0x01][0x17FE] = 0xFF # If in warp mode, don't allow manual exit rom.patch(0x01, 0x1800, "20021E60", ASM("jp nz, $5818"), fill_nop=True) # Allow warp with just B rom.banks[0x01][0x17C0] = 0x20 # Allow cursor to move over black squares # This allows warping to undiscovered areas - a fine cheat, but needs a check for wOverworldRoomStatus in the warp code CHEAT_WARP_ANYWHERE = False if CHEAT_WARP_ANYWHERE: rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5")) # This disables the arrows around the selection bubble #rom.patch(0x01, 0x1B6F, None, ASM("ret"), fill_nop=True) # Fix lag when moving the cursor # One option - just disable the delay code #rom.patch(0x01, 0x1A76, 0x1A76+3, ASM("xor a"), fill_nop=True) #rom.banks[0x01][0x1A7C] = 0 # Another option - just remove the animation rom.banks[0x01][0x1B20] = 0 rom.banks[0x01][0x1B3B] = 0 # Patch the icon for all teleports all_warps = [0x01, 0x95, 0x2C, 0xEC] if extra_warps: # mamu all_warps.append(0x45) # Tweak the flute location rom.banks[0x14][0x0E95] += 0x10 rom.banks[0x14][0x0EA3] += 0x01 mamu_pond = RoomEditor(rom, 0x45) # Remove some tall grass so we can add a warp instead mamu_pond.changeObject(1, 6, 0xE8) mamu_pond.moveObject(1, 6, 3, 5) mamu_pond.addEntity(3, 5, 0x61) mamu_pond.store(rom) # eagle all_warps.append(0x0F) room = RoomEditor(rom, 0x0F) # Move one cliff edge and change it into a pit room.changeObject(7, 6, 0xE8) room.moveObject(7, 6, 6, 4) # Add the warp room.addEntity(6, 4, 0x61) # move the two corners room.moveObject(6, 7, 7, 7) room.moveObject(6, 6, 7, 6) for object in room.objects: # Extend the lower wall if ((object.x == 0 and object.y == 7) # Extend the lower floor or (object.x == 0 and object.y == 6)): room.overlay[object.x + object.count + object.y * 10] = object.type_id object.count += 1 room.store(rom) for warp in all_warps: # Set icon rom.banks[0x20][0x168B + warp] = 0x55 # Set text if not rom.banks[0x01][0x1959 + warp]: rom.banks[0x01][0x1959 + warp] = 0x42 # Set palette # rom.banks[0x20][0x178B + 0x95] = 0x1 # Setup [?!] icon on map and associated text rom.banks[0x01][0x1909 + 0x42] = 0x2B rom.texts[0x02B] = utils.formatText('Warp') # call warp function (why not just jmp?!) rom.patch(0x01, 0x17C3, None, ASM(""" call $7E7B ret """)) # Build a switch statement by hand warp_jump = "".join(f"cp ${hex(warp)[2:]}\njr z, success\n" for warp in all_warps) rom.patch(0x01, 0x3E7B, None, ASM(f""" TeleportHandler: ld a, [$DBB4] ; Load the current selected tile ; TODO: check if actually revealed so we can have free movement ; Check cursor against different tiles to see if we are selecting a warp {warp_jump} jr exit success: ld a, $0B ld [$DB95], a ; Gameplay type xor a ld [$D401], a ; wWarp0MapCategory ldh [$DD], a ; unset teleport flag(!!!) ld [$D402], a ; wWarp0Map ld a, [$DBB4] ; wDBB4 ld [$D403], a ; wWarp0Room ld a, $68 ld [$D404], a ; wWarp0DestinationX ldh [$98], a ; LinkPositionY ld [$D475], a ld a, $70 ld [$D405], a ; wWarp0DestinationY ldh [$99], a ; LinkPositionX ld a, $66 ld [$D416], a ; wWarp0PositionTileIndex ld a, $07 ld [$DB96], a ; wGameplaySubtype ldh a, [$A2] ld [$DBC8], a call $0C83 ; ApplyMapFadeOutTransition xor a ; $5DF3: $AF ld [$C167], a ; $5DF4: $EA $67 $C1 exit: ret """))