Replace most of the address-hunting with code matches

This commit is contained in:
Eevee (Lexy Munroe) 2016-09-10 18:05:00 -07:00
parent ffa0bf65a0
commit 44fdaee4d9
2 changed files with 171 additions and 79 deletions

View file

@ -186,6 +186,7 @@ gbz80_instructions = {
0x7e: 'ld a, [hl]', 0x7e: 'ld a, [hl]',
0x7f: 'ld a, a', 0x7f: 'ld a, a',
# TODO understand these as both "add a, b" and "add a"
0x80: 'add a, b', 0x80: 'add a, b',
0x81: 'add a, c', 0x81: 'add a, c',
0x82: 'add a, d', 0x82: 'add a, d',
@ -305,6 +306,7 @@ gbz80_instructions = {
0xee: 'xor #d8', 0xee: 'xor #d8',
0xef: 'rst $28', 0xef: 'rst $28',
# TODO really want better support for this
0xf0: 'ldh a, [#a8]', 0xf0: 'ldh a, [#a8]',
0xf1: 'pop af', 0xf1: 'pop af',
0xf2: 'ld a, [$ff00+c]', # XXX table says 1 but this looks like 1 to me 0xf2: 'ld a, [$ff00+c]', # XXX table says 1 but this looks like 1 to me
@ -599,7 +601,9 @@ def find_code(haystack, needle, **kwargs):
if inputs is not None: if inputs is not None:
break break
else: else:
raise SyntaxError raise SyntaxError(
"Can't figure out what instruction corresponds to: "
+ instruction)
instr = candidate instr = candidate
pattern_chunks.append(re.escape(instr.prefix)) pattern_chunks.append(re.escape(instr.prefix))

View file

@ -1300,14 +1300,70 @@ class RBYCart:
raise CartDetectionError("Can't find flavor text pointers") raise CartDetectionError("Can't find flavor text pointers")
addresses['PokedexEntryPointers'] = idx + len(asm_DrawTileLine) addresses['PokedexEntryPointers'] = idx + len(asm_DrawTileLine)
# This is a helper function for figuring out moves, followed by another
# 5-byte function, then the table of evolutions and moves. match = find_code(self.data, '''
asm_WriteMonMoves_ShiftMoveData = bytes.fromhex('0e03 131a 220d 20fa c9') ;EvolutionAfterBattle:
try: ldh a, [#hTilesetType]
idx = self.data.index(asm_WriteMonMoves_ShiftMoveData) push af
except ValueError: xor a
ld [#wEvolutionOccurred], a
dec a
ld [#wWhichPokemon], a
push hl
push bc
push de
ld hl, #wPartyCount
push hl
;Evolution_PartyMonLoop: ; loop over party mons
ld hl, #wWhichPokemon
inc [hl]
pop hl
inc hl
ld a, [hl]
cp $ff ; have we reached the end of the party?
jp z, #_done
ld [#wEvoOldSpecies], a
push hl
ld a, [#wWhichPokemon]
ld c, a
ld hl, #wCanEvolveFlags
ld b, #FLAG_TEST
call #Evolution_FlagAction
ld a, c
and a ; is the mon's bit set?
jp z, #Evolution_PartyMonLoop ; if not, go to the next mon
ld a, [#wEvoOldSpecies]
dec a
ld b, 0
ld hl, #EvosMovesPointerTable
add a, a
rl b
ld c, a
add hl, bc
ld a, [hl+]
ld h, [hl]
ld l, a
push hl
ld a, [#wcf91]
push af
xor a ; PLAYER_PARTY_DATA
ld [#wMonDataLocation], a
call #LoadMonData
pop af
ld [#wcf91], a
pop hl
''',
hTilesetType=0xD7, # FFD7
)
if not match:
raise CartDetectionError("Can't find evolution and moveset table") raise CartDetectionError("Can't find evolution and moveset table")
addresses['EvosMovesPointerTable'] = idx + len(asm_WriteMonMoves_ShiftMoveData) + 5 rem, inputs = match
# As usual, there's no bank given... but this code has to be in the
# same bank as the data it loads!
codebank, _ = bank(rem.start())
addresses['EvosMovesPointerTable'] = unbank(
codebank, inputs['EvosMovesPointerTable'])
# Several lists of names are accessed by a single function, which looks # Several lists of names are accessed by a single function, which looks
# through a list of pointers to find the right set of names to use. # through a list of pointers to find the right set of names to use.
@ -1352,11 +1408,6 @@ class RBYCart:
ldh a,[#H_LOADEDROMBANK] ldh a,[#H_LOADEDROMBANK]
push af push af
ld a,#BANK_MonsterNames ld a,#BANK_MonsterNames
ldh [#H_LOADEDROMBANK],a
ld [#MBC1RomBank],a
ld a,[#wd11e]
dec a
ld hl,#MonsterNames
''', ''',
H_LOADEDROMBANK=0xB8, # full address is $FFB8; ldh adds the $FF H_LOADEDROMBANK=0xB8, # full address is $FFB8; ldh adds the $FF
MBC1RomBank=0x2000, MBC1RomBank=0x2000,
@ -1451,13 +1502,44 @@ class RBYCart:
# Here's plan B: look for the function that /loads/ base stats, and # Here's plan B: look for the function that /loads/ base stats, and
# scrape the address out of it. This function is a bit hairy; I've had # scrape the address out of it. This function is a bit hairy; I've had
# to expand some of pokered's macros and rewrite the jumps to something # to expand some of pokered's macros and rewrite the jumps to something
# that the rudimentary code matcher can understand. # that the rudimentary code matcher can understand. Also, there were
match = find_code(self.data, ''' # two edits made in Yellow: bank switching is done with a function
# rather inlined, and Mew is no longer separate.
# TODO i guess it would be nice if find_code could deal with this,
# seeing as it IS just a regex, but i don't know how i'd cram the
# syntax in without a real parser™
rgb_bits = dict(
bankswitch="""
ldh [#H_LOADEDROMBANK], a
ld [#MBC1RomBank], a
""",
mewjump="""
cp #MEW
jr z,#mew
""",
mewblock="""
jr #done2
;.mew
ld hl, #MewBaseStats
ld de, #wMonHeader
ld bc, #MonBaseStatsLength
ld a, #BANK_MewBaseStats
call #FarCopyData
""",
)
yellow_bits = dict(
bankswitch="""
call #BankswitchCommon
""",
mewjump="",
mewblock="",
)
for bits in (rgb_bits, yellow_bits):
code = '''
ldh a, [#H_LOADEDROMBANK] ldh a, [#H_LOADEDROMBANK]
push af push af
ld a, #BANK_BaseStats ld a, #BANK_BaseStats
ldh [#H_LOADEDROMBANK], a {bankswitch}
ld [#MBC1RomBank], a
push bc push bc
push de push de
push hl push hl
@ -1476,8 +1558,7 @@ class RBYCart:
ld b,$77 ; size of Aerodactyl fossil sprite ld b,$77 ; size of Aerodactyl fossil sprite
cp #FOSSIL_AERODACTYL ; Aerodactyl fossil cp #FOSSIL_AERODACTYL ; Aerodactyl fossil
jr z,#specialID3 jr z,#specialID3
cp #MEW {mewjump}
jr z,#mew
ld a, #IndexToPokedexPredef ld a, #IndexToPokedexPredef
call #IndexToPokedex ; convert pokemon ID in [wd11e] to pokedex number call #IndexToPokedex ; convert pokemon ID in [wd11e] to pokedex number
ld a,[#wd11e] ld a,[#wd11e]
@ -1496,14 +1577,12 @@ class RBYCart:
ld [hl], e ; write front sprite pointer ld [hl], e ; write front sprite pointer
inc hl inc hl
ld [hl], d ld [hl], d
jr #done2 {mewblock}
;.mew '''.format(**bits)
ld hl, #MewBaseStats
ld de, #wMonHeader match = find_code(
ld bc, #MonBaseStatsLength self.data,
ld a, #BANK_MewBaseStats code,
call #FarCopyData
''',
# These are constants; I left them in the above code for clarity # These are constants; I left them in the above code for clarity
H_LOADEDROMBANK=0xB8, # full address is $FFB8; ldh adds the $FF H_LOADEDROMBANK=0xB8, # full address is $FFB8; ldh adds the $FF
MBC1RomBank=0x2000, MBC1RomBank=0x2000,
@ -1512,8 +1591,14 @@ class RBYCart:
) )
if match: if match:
rem, inputs = match rem, inputs = match
addresses['BaseStats'] = unbank(inputs['BANK_BaseStats'], inputs['BaseStats']) addresses['BaseStats'] = unbank(
addresses['MewBaseStats'] = unbank(inputs['BANK_MewBaseStats'], inputs['MewBaseStats']) inputs['BANK_BaseStats'], inputs['BaseStats'])
if 'MewBaseStats' in inputs:
addresses['MewBaseStats'] = unbank(
inputs['BANK_MewBaseStats'], inputs['MewBaseStats'])
else:
addresses['MewBaseStats'] = None
break
else: else:
raise CartDetectionError("Can't find base stats") raise CartDetectionError("Can't find base stats")
@ -1738,10 +1823,13 @@ class RBYCart:
def pokemon_records(self): def pokemon_records(self):
"""List of pokemon_structs.""" """List of pokemon_structs."""
self.stream.seek(self.addrs['BaseStats']) self.stream.seek(self.addrs['BaseStats'])
# Mew's data is, awkwardly, stored separately pre-Yellow
if self.addrs['MewBaseStats']:
records = Array(self.NUM_POKEMON - 1, pokemon_struct).parse_stream(self.stream) records = Array(self.NUM_POKEMON - 1, pokemon_struct).parse_stream(self.stream)
# Mew's data is, awkwardly, stored separately
self.stream.seek(self.addrs['MewBaseStats']) self.stream.seek(self.addrs['MewBaseStats'])
records.append(pokemon_struct.parse_stream(self.stream)) records.append(pokemon_struct.parse_stream(self.stream))
else:
records = Array(self.NUM_POKEMON, pokemon_struct).parse_stream(self.stream)
return records return records