def setRomInfo(rom, seed, options): try: version = subprocess.run(['git', 'describe', '--tags', '--dirty=-D'], stdout=subprocess.PIPE).stdout.strip().decode( "ascii", "replace") except: version = "" if options.race: seed = "Race" if isinstance(options.race, str): seed += " " + options.race rom.patch(0x00, 0x07, "00", "01") else: rom.patch(0x00, 0x07, "00", "52") line_1_hex = _encode(seed[:16]) line_2_hex = _encode(seed[16:]) try: seednr = int(seed, 16) except: import hashlib seednr = int( hashlib.md5(seed.encode('ascii', 'replace')).hexdigest(), 16) for n in (3, 4): be = BackgroundEditor(rom, n) ba = BackgroundEditor(rom, n, attributes=True) for n, v in enumerate(_encode(version)): be.tiles[0x98a0 + 0x13 - len(version) + n] = v ba.tiles[0x98a0 + 0x13 - len(version) + n] = 0x00 for n, v in enumerate(line_1_hex): be.tiles[0x9a01 + n] = v ba.tiles[0x9a01 + n] = 0x00 for n, v in enumerate(line_2_hex): be.tiles[0x9a21 + n] = v ba.tiles[0x9a21 + n] = 0x00 for n in range(0x09, 0x14): be.tiles[0x9820 + n] = 0x7F be.tiles[0x9840 + n] = 0xA0 + (n % 2) be.tiles[0x9860 + n] = 0xA2 sn = seednr for n in range(0x0A, 0x14): tilenr = sn % 30 sn //= 30 if tilenr > 12: tilenr += 2 if tilenr > 16: tilenr += 1 if tilenr > 19: tilenr += 3 if tilenr > 27: tilenr += 1 if tilenr > 29: tilenr += 2 if tilenr > 35: tilenr += 1 be.tiles[0x9800 + n] = tilenr * 2 be.tiles[0x9820 + n] = tilenr * 2 + 1 pal = sn % 8 sn //= 8 ba.tiles[0x9800 + n] = 0x08 | pal ba.tiles[0x9820 + n] = 0x08 | pal be.store(rom) ba.store(rom)
def singleSaveSlot(rom): # Do not validate/erase slots 2 and 3 at rom start rom.patch(0x01, 0x06B3, ASM("call $4794"), "", fill_nop=True) rom.patch(0x01, 0x06B9, ASM("call $4794"), "", fill_nop=True) # Patch the code that checks if files have proper filenames to skip file 2/3 rom.patch(0x01, 0x1DD9, ASM("ld b, $02"), ASM("ret"), fill_nop=True) # Remove the part that writes death counters for save2/3 on the file select screen rom.patch(0x01, 0x0821, 0x084B, "", fill_nop=True) # Remove the call that updates the hearts for save2 rom.patch(0x01, 0x0800, ASM("call $4DBE"), "", fill_nop=True) # Remove the call that updates the hearts for save3 rom.patch(0x01, 0x0806, ASM("call $4DD6"), "", fill_nop=True) # Remove the call that updates the names for save2 and save3 rom.patch(0x01, 0x0D70, ASM("call $4D94\ncall $4D9D"), "", fill_nop=True) # Remove the 2/3 slots from the screen and remove the copy text be = BackgroundEditor(rom, 0x03) del be.tiles[0x9924] del be.tiles[0x9984] be.store(rom) be = BackgroundEditor(rom, 0x04) del be.tiles[0x9924] del be.tiles[0x9984] for n in range(0x99ED, 0x99F1): del be.tiles[n] be.store(rom) # Do not do left/right for erase/copy selection. rom.patch(0x01, 0x092B, ASM("jr z, $0B"), ASM("jr $0B")) # Only switch between players rom.patch(0x01, 0x08FA, 0x091D, ASM(""" ld a, [$DBA7] and a ld a, [$DBA6] jr z, skip xor $03 skip: """), fill_nop=True) # On the erase screen, only switch between save 1 and return rom.patch(0x01, 0x0E12, ASM("inc a\nand $03"), ASM("xor $03"), fill_nop=True) rom.patch(0x01, 0x0E21, ASM("dec a\ncp $ff\njr nz, $02\nld a,$03"), ASM("xor $03"), fill_nop=True) be = BackgroundEditor(rom, 0x06) del be.tiles[0x9924] del be.tiles[0x9984] be.store(rom)
def advancedInventorySubscreen(rom): # Instrument positions rom.patch(0x01, 0x2BCF, "0F51B1EFECAA4A0C", "090C0F12494C4F52") be = BackgroundEditor(rom, 2) be.tiles[0x9DA9] = 0x4A be.tiles[0x9DC9] = 0x4B for x in range(1, 10): be.tiles[0x9DE9 + x] = 0xB0 + (x % 9) be.tiles[0x9DE9] = 0xBA be.store(rom) be = BackgroundEditor(rom, 2, attributes=True) # Remove all attributes out of range. for y in range(0x9C00, 0x9E40, 0x20): for x in range(0x14, 0x20): del be.tiles[x + y] for n in range(0x9E40, 0xA020): del be.tiles[n] # Remove palette of instruments for y in range(0x9D00, 0x9E20, 0x20): for x in range(0x00, 0x14): be.tiles[x + y] = 0x01 # And place it at the proper location for y in range(0x9D00, 0x9D80, 0x20): for x in range(0x09, 0x14): be.tiles[x + y] = 0x07 # Key from 2nd vram bank be.tiles[0x9DA9] = 0x09 be.tiles[0x9DC9] = 0x09 # Nightmare heads from 2nd vram bank with proper palette for n in range(1, 10): be.tiles[0x9DA9 + n] = 0x0E be.store(rom) rom.patch(0x20, 0x19D3, ASM("ld bc, $5994\nld e, $33"), ASM("ld bc, $7E08\nld e, $%02x" % (0x33 + 24))) rom.banks[0x20][0x3E08:0x3E08 + 0x33] = rom.banks[0x20][0x1994:0x1994 + 0x33] rom.patch(0x20, 0x3E08 + 0x32, "00" * 25, "9DAA08464646464646464646" "9DCA08B0B0B0B0B0B0B0B0B0" "00") # instead of doing an GBC specific check, jump to our custom handling rom.patch(0x20, 0x19DE, ASM("ldh a, [$FE]\nand a\njr z, $40"), ASM("call $7F00"), fill_nop=True) rom.patch(0x20, 0x3F00, "00" * 0x100, ASM( """ ld a, [$DBA5] ; isIndoor and a jr z, RenderKeysCounts ldh a, [$F7] ; mapNr cp $FF jr z, RenderDungeonFix cp $06 jr z, D7RenderDungeonFix cp $08 jr c, RenderDungeonFix RenderKeysCounts: ; Check if we have each nightmare key, and else null out the rendered tile ld hl, $D636 ld de, $DB19 ld c, $08 NKeyLoop: ld a, [de] and a jr nz, .hasNKey ld a, $7F ld [hl], a .hasNKey: inc hl inc de inc de inc de inc de inc de dec c jr nz, NKeyLoop ld a, [$DDDD] and a jr nz, .hasCNKey ld a, $7F ld [hl], a .hasCNKey: ; Check the small key count for each dungeon and increase the tile to match the number ld hl, $D642 ld de, $DB1A ld c, $08 KeyLoop: ld a, [de] add a, $B0 ld [hl], a inc hl inc de inc de inc de inc de inc de dec c jr nz, KeyLoop ld a, [$DDDE] add a, $B0 ld [hl], a ret D7RenderDungeonFix: ld de, D7DungeonFix ld c, $11 jr RenderDungeonFixGo RenderDungeonFix: ld de, DungeonFix ld c, $0D RenderDungeonFixGo: ld hl, $D633 .copyLoop: ld a, [de] inc de ldi [hl], a dec c jr nz, .copyLoop ret DungeonFix: db $9D, $09, $C7, $7F db $9D, $0A, $C7, $7F db $9D, $13, $C3, $7F db $00 D7DungeonFix: db $9D, $09, $C7, $7F db $9D, $0A, $C7, $7F db $9D, $6B, $48, $7F db $9D, $0F, $C7, $7F db $00 """, 0x7F00), 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] rom.patch(0x01, 0x3E20, 0x4000, 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. xor a ld [$D401], a 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 """ % (warp.room, warp.target_x, warp.target_y)), 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]