mirror of
https://github.com/veekun/pokedex.git
synced 2024-08-20 18:16:34 +00:00
Port RBY dumping code to Construct 2.8
This commit is contained in:
parent
508b5cd88f
commit
b8a5f634d5
1 changed files with 76 additions and 93 deletions
|
@ -19,7 +19,14 @@ import sys
|
||||||
|
|
||||||
from camel import Camel
|
from camel import Camel
|
||||||
from classtools import reify
|
from classtools import reify
|
||||||
from construct import *
|
from construct import (
|
||||||
|
# Simple fields
|
||||||
|
BitsInteger, Byte, Bytes, CString, Const, Int8ul, Int16ub, Int16ul,
|
||||||
|
Padding, String,
|
||||||
|
# Structures and meta stuff
|
||||||
|
Adapter, Array, Bitwise, Construct, Embedded, Enum, Peek, Pointer, Struct,
|
||||||
|
Subconstruct, Switch,
|
||||||
|
)
|
||||||
|
|
||||||
from pokedex.extract.lib.gbz80 import find_code
|
from pokedex.extract.lib.gbz80 import find_code
|
||||||
import pokedex.schema as schema
|
import pokedex.schema as schema
|
||||||
|
@ -742,13 +749,13 @@ class PokemonString:
|
||||||
|
|
||||||
class PokemonCString(Adapter):
|
class PokemonCString(Adapter):
|
||||||
"""Construct thing for `PokemonString`."""
|
"""Construct thing for `PokemonString`."""
|
||||||
def __init__(self, name, length=None):
|
def __init__(self, length=None):
|
||||||
# No matter which charmap, the "end of string" character is always
|
# No matter which charmap, the "end of string" character is always
|
||||||
# encoded as P
|
# encoded as P
|
||||||
if length is None:
|
if length is None:
|
||||||
subcon = CString(name, terminators=b'P')
|
subcon = CString(terminators=b'P')
|
||||||
else:
|
else:
|
||||||
subcon = String(name, length, padchar=b'P')
|
subcon = String(length, padchar=b'P')
|
||||||
super().__init__(subcon)
|
super().__init__(subcon)
|
||||||
|
|
||||||
def _encode(self, obj, context):
|
def _encode(self, obj, context):
|
||||||
|
@ -762,7 +769,7 @@ class MacroPokemonCString(Construct):
|
||||||
"""Similar to the above, but for strings that may contain the 0x17 "far
|
"""Similar to the above, but for strings that may contain the 0x17 "far
|
||||||
load" macro, whose parameters may in turn contain the NUL byte 0x50 without
|
load" macro, whose parameters may in turn contain the NUL byte 0x50 without
|
||||||
marking the end of the string. Yikes."""
|
marking the end of the string. Yikes."""
|
||||||
def _parse(self, stream, context):
|
def _parse(self, stream, context, path):
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
while True:
|
while True:
|
||||||
byte, = stream.read(1)
|
byte, = stream.read(1)
|
||||||
|
@ -773,7 +780,7 @@ class MacroPokemonCString(Construct):
|
||||||
pos = stream.tell()
|
pos = stream.tell()
|
||||||
try:
|
try:
|
||||||
stream.seek(offset)
|
stream.seek(offset)
|
||||||
buf.extend(self._parse(stream, context).raw)
|
buf.extend(self._parse(stream, context, path).raw)
|
||||||
finally:
|
finally:
|
||||||
stream.seek(pos)
|
stream.seek(pos)
|
||||||
elif byte == 0x50:
|
elif byte == 0x50:
|
||||||
|
@ -789,28 +796,19 @@ class MacroPokemonCString(Construct):
|
||||||
|
|
||||||
|
|
||||||
class NullTerminatedArray(Subconstruct):
|
class NullTerminatedArray(Subconstruct):
|
||||||
_peeker = Peek(ULInt8('___'))
|
_peeker = Peek(Int8ul)
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __init__(self, subcon):
|
def _parse(self, stream, context, path):
|
||||||
super().__init__(subcon)
|
|
||||||
self._clear_flag(self.FLAG_COPY_CONTEXT)
|
|
||||||
self._set_flag(self.FLAG_DYNAMIC)
|
|
||||||
|
|
||||||
def _parse(self, stream, context):
|
|
||||||
from construct.lib import ListContainer
|
from construct.lib import ListContainer
|
||||||
obj = ListContainer()
|
obj = ListContainer()
|
||||||
orig_context = context
|
|
||||||
while True:
|
while True:
|
||||||
nextbyte = self._peeker.parse_stream(stream)
|
nextbyte = self._peeker.parse_stream(stream)
|
||||||
if nextbyte == 0:
|
if nextbyte == 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.subcon.conflags & self.FLAG_COPY_CONTEXT:
|
|
||||||
context = orig_context.__copy__()
|
|
||||||
|
|
||||||
# TODO what if we hit the end of the stream
|
# TODO what if we hit the end of the stream
|
||||||
obj.append(self.subcon._parse(stream, context))
|
obj.append(self.subcon._parse(stream, context, path))
|
||||||
|
|
||||||
# Consume the trailing zero
|
# Consume the trailing zero
|
||||||
stream.read(1)
|
stream.read(1)
|
||||||
|
@ -832,108 +830,95 @@ def IdentEnum(subcon, mapping):
|
||||||
# http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header
|
# http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header
|
||||||
# TODO hey! i wish i had a little cli entry point that would spit this out for a game. and do other stuff like scan for likely pokemon text or graphics. that would be really cool in fact. maybe put this in a gb module and make that exist sometime.
|
# TODO hey! i wish i had a little cli entry point that would spit this out for a game. and do other stuff like scan for likely pokemon text or graphics. that would be really cool in fact. maybe put this in a gb module and make that exist sometime.
|
||||||
game_boy_header_struct = Struct(
|
game_boy_header_struct = Struct(
|
||||||
'game_boy_header',
|
|
||||||
# Entry point for the game; generally contains a jump to 0x0150
|
# Entry point for the game; generally contains a jump to 0x0150
|
||||||
String('entry_point', 4),
|
'entry_point' / String(4),
|
||||||
# Nintendo logo; must be exactly this or booting will not continue
|
# Nintendo logo; must be exactly this or booting will not continue
|
||||||
Const(
|
'nintendo_logo' / Const(
|
||||||
String('nintendo_logo', 48),
|
|
||||||
bytes.fromhex("""
|
bytes.fromhex("""
|
||||||
CE ED 66 66 CC 0D 00 0B 03 73 00 83 00 0C 00 0D
|
CE ED 66 66 CC 0D 00 0B 03 73 00 83 00 0C 00 0D
|
||||||
00 08 11 1F 88 89 00 0E DC CC 6E E6 DD DD D9 99
|
00 08 11 1F 88 89 00 0E DC CC 6E E6 DD DD D9 99
|
||||||
BB BB 67 63 6E 0E EC CC DD DC 99 9F BB B9 33 3E
|
BB BB 67 63 6E 0E EC CC DD DC 99 9F BB B9 33 3E
|
||||||
""".replace('\n', '')),
|
""".replace('\n', '')),
|
||||||
),
|
),
|
||||||
String('title', 11, padchar=b'\x00'),
|
'title' / Bytes(11),
|
||||||
String('manufacturer_code', 4),
|
'manufacturer_code' / Bytes(4),
|
||||||
ULInt8('cgb_flag'),
|
'cgb_flag' / Int8ul,
|
||||||
String('new_licensee_code', 2),
|
'new_licensee_code' / Bytes(2),
|
||||||
ULInt8('sgb_flag'), # 3 for super game boy support
|
'sgb_flag' / Int8ul, # 3 for super game boy support
|
||||||
ULInt8('cartridge_type'),
|
'cartridge_type' / Int8ul,
|
||||||
ULInt8('rom_size'),
|
'rom_size' / Int8ul,
|
||||||
ULInt8('ram_size'),
|
'ram_size' / Int8ul,
|
||||||
ULInt8('region_code'), # 0 for japan, 1 for not japan
|
'region_code' / Int8ul, # 0 for japan, 1 for not japan
|
||||||
ULInt8('old_licensee_code'), # 0x33 means to use licensee_code
|
'old_licensee_code' / Int8ul, # 0x33 means to use licensee_code
|
||||||
ULInt8('game_version'),
|
'game_version' / Int8ul,
|
||||||
ULInt8('header_checksum'),
|
'header_checksum' / Int8ul,
|
||||||
UBInt16('cart_checksum'),
|
'cart_checksum' / Int16ub,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# The mother lode — Pokémon base stats
|
# The mother lode — Pokémon base stats
|
||||||
pokemon_struct = Struct(
|
pokemon_struct = Struct(
|
||||||
'pokemon',
|
'pokedex_number' / Byte,
|
||||||
ULInt8('pokedex_number'),
|
'base_hp' / Byte,
|
||||||
ULInt8('base_hp'),
|
'base_attack' / Byte,
|
||||||
ULInt8('base_attack'),
|
'base_defense' / Byte,
|
||||||
ULInt8('base_defense'),
|
'base_speed' / Byte,
|
||||||
ULInt8('base_speed'),
|
'base_special' / Byte,
|
||||||
ULInt8('base_special'),
|
'type1' / IdentEnum(Byte, TYPE_IDENTIFIERS),
|
||||||
IdentEnum(ULInt8('type1'), TYPE_IDENTIFIERS),
|
'type2' / IdentEnum(Byte, TYPE_IDENTIFIERS),
|
||||||
IdentEnum(ULInt8('type2'), TYPE_IDENTIFIERS),
|
'catch_rate' / Byte,
|
||||||
ULInt8('catch_rate'),
|
'base_experience' / Byte,
|
||||||
ULInt8('base_experience'),
|
|
||||||
# TODO ???? "sprite dimensions"
|
# TODO ???? "sprite dimensions"
|
||||||
ULInt8('_sprite_dimensions'),
|
'_sprite_dimensions' / Byte,
|
||||||
ULInt16('front_sprite_pointer'),
|
'front_sprite_pointer' / Int16ul,
|
||||||
ULInt16('back_sprite_pointer'),
|
'back_sprite_pointer' / Int16ul,
|
||||||
# TODO somehow rig this to discard trailing zeroes; there's a paddedstring that does it
|
# TODO somehow rig this to discard trailing zeroes; there's a paddedstring that does it
|
||||||
Array(4, IdentEnum(ULInt8('initial_moveset'), MOVE_IDENTIFIERS)),
|
'initial_moveset' / Array(4, IdentEnum(Byte, MOVE_IDENTIFIERS)),
|
||||||
IdentEnum(ULInt8('growth_rate'), GROWTH_RATES),
|
'growth_rate' / IdentEnum(Byte, GROWTH_RATES),
|
||||||
# TODO argh, this is a single huge integer; i want an array, but then i lose the byteswapping!
|
# TODO argh, this is a single huge integer; i want an array, but then i lose the byteswapping!
|
||||||
Bitwise(
|
'machines' / Bitwise(BitsInteger(7 * 8, swapped=True)),
|
||||||
BitField('machines', 7 * 8, swapped=True),
|
|
||||||
),
|
|
||||||
Padding(1),
|
Padding(1),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
evos_moves_struct = Struct(
|
evos_moves_struct = Struct(
|
||||||
'evos_moves',
|
'evolutions' / NullTerminatedArray(
|
||||||
NullTerminatedArray(
|
|
||||||
Struct(
|
Struct(
|
||||||
'evolutions',
|
'evo_trigger' / IdentEnum(Byte, EVOLUTION_TRIGGERS),
|
||||||
IdentEnum(ULInt8('evo_trigger'), EVOLUTION_TRIGGERS),
|
'evo_arguments' / Embedded(Switch(
|
||||||
Embedded(Switch(
|
|
||||||
'evo_arguments',
|
|
||||||
lambda ctx: ctx.evo_trigger, {
|
lambda ctx: ctx.evo_trigger, {
|
||||||
'evolution-trigger.level-up': Struct(
|
'evolution-trigger.level-up': Struct(
|
||||||
'---',
|
'evo_level' / Byte,
|
||||||
ULInt8('evo_level'),
|
|
||||||
),
|
),
|
||||||
'evolution-trigger.use-item': Struct(
|
'evolution-trigger.use-item': Struct(
|
||||||
'---',
|
|
||||||
# TODO item enum too wow!
|
# TODO item enum too wow!
|
||||||
ULInt8('evo_item'),
|
'evo_item' / Byte,
|
||||||
# TODO ??? always seems to be 1
|
# TODO ??? always seems to be 1
|
||||||
ULInt8('evo_level'),
|
'evo_level' / Byte,
|
||||||
),
|
),
|
||||||
# TODO ??? always seems to be 1 here too
|
# TODO ??? always seems to be 1 here too
|
||||||
'evolution-trigger.trade': Struct(
|
'evolution-trigger.trade': Struct(
|
||||||
'---',
|
'evo_level' / Byte,
|
||||||
ULInt8('evo_level'),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
# TODO alas, the species here is a number, because it's an internal
|
# TODO alas, the species here is a number, because it's an internal
|
||||||
# id and we switch those back using data from the game...
|
# id and we switch those back using data from the game...
|
||||||
ULInt8('evo_species'),
|
'evo_species' / Byte,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
NullTerminatedArray(
|
'level_up_moves' / NullTerminatedArray(
|
||||||
Struct(
|
Struct(
|
||||||
'level_up_moves',
|
'level' / Byte,
|
||||||
ULInt8('level'),
|
'move' / IdentEnum(Byte, MOVE_IDENTIFIERS),
|
||||||
IdentEnum(ULInt8('move'), MOVE_IDENTIFIERS),
|
'_end' / Peek(Byte), # TODO what, what is this
|
||||||
Peek(ULInt8('_end')),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
evos_moves_pointer = Struct(
|
evos_moves_pointer = Struct(
|
||||||
'xxx',
|
'offset' / Int16ul,
|
||||||
ULInt16('offset'),
|
|
||||||
# TODO hardcoded as the same bank, ugh
|
# TODO hardcoded as the same bank, ugh
|
||||||
Pointer(lambda ctx: ctx.offset + (0xE - 1) * 0x4000, evos_moves_struct),
|
'evos_moves' / Pointer(lambda ctx: ctx.offset + (0xE - 1) * 0x4000, evos_moves_struct),
|
||||||
)
|
)
|
||||||
|
|
||||||
# There are actually two versions of this struct... an imperial one, used by
|
# There are actually two versions of this struct... an imperial one, used by
|
||||||
|
@ -941,19 +926,17 @@ evos_moves_pointer = Struct(
|
||||||
# one has separate bytes for feet and inches, so it's a different size, making
|
# one has separate bytes for feet and inches, so it's a different size, making
|
||||||
# it completely incompatible.
|
# it completely incompatible.
|
||||||
pokedex_flavor_struct_metric = Struct(
|
pokedex_flavor_struct_metric = Struct(
|
||||||
'pokedex_flavor_metric',
|
'genus' / PokemonCString(),
|
||||||
PokemonCString('genus'),
|
'height_decimeters' / Int8ul,
|
||||||
ULInt8('height_decimeters'),
|
'weight_hectograms' / Int16ul,
|
||||||
ULInt16('weight_hectograms'),
|
'flavor_text' / MacroPokemonCString(),
|
||||||
MacroPokemonCString('flavor_text'),
|
|
||||||
)
|
)
|
||||||
pokedex_flavor_struct_imperial = Struct(
|
pokedex_flavor_struct_imperial = Struct(
|
||||||
'pokedex_flavor_imperial',
|
'genus' / PokemonCString(),
|
||||||
PokemonCString('genus'),
|
'height_feet' / Int8ul,
|
||||||
ULInt8('height_feet'),
|
'height_inches' / Int8ul,
|
||||||
ULInt8('height_inches'),
|
'weight_decipounds' / Int16ul,
|
||||||
ULInt16('weight_decipounds'),
|
'flavor_text' / MacroPokemonCString(),
|
||||||
MacroPokemonCString('flavor_text'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1107,7 +1090,7 @@ class RBYCart:
|
||||||
raise CartDetectionError("Can't find name array")
|
raise CartDetectionError("Can't find name array")
|
||||||
rem, inputs = match
|
rem, inputs = match
|
||||||
start = inputs['NamePointers']
|
start = inputs['NamePointers']
|
||||||
name_pointers = Array(7, ULInt16('dummy')).parse(
|
name_pointers = Array(7, Int16ul).parse(
|
||||||
self.data[start:start + 14])
|
self.data[start:start + 14])
|
||||||
# One downside to the Game Boy memory structure is that banks are
|
# One downside to the Game Boy memory structure is that banks are
|
||||||
# not stored anywhere near their corresponding addresses. Most
|
# not stored anywhere near their corresponding addresses. Most
|
||||||
|
@ -1437,7 +1420,7 @@ class RBYCart:
|
||||||
self.stream.seek(self.addrs['ItemNames'])
|
self.stream.seek(self.addrs['ItemNames'])
|
||||||
# Item 0 is MASTER BALL. The first item with a different name in every
|
# Item 0 is MASTER BALL. The first item with a different name in every
|
||||||
# single language is item 4, TOWN MAP, so chew through five names.
|
# single language is item 4, TOWN MAP, so chew through five names.
|
||||||
single_string_struct = PokemonCString('dummy')
|
single_string_struct = PokemonCString()
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
name = single_string_struct.parse_stream(self.stream)
|
name = single_string_struct.parse_stream(self.stream)
|
||||||
|
|
||||||
|
@ -1515,7 +1498,7 @@ class RBYCart:
|
||||||
name_length = 5
|
name_length = 5
|
||||||
else:
|
else:
|
||||||
name_length = 10
|
name_length = 10
|
||||||
for index, pokemon_name in enumerate(Array(self.max_pokemon_index, PokemonCString('...', name_length)).parse_stream(self.stream), start=1):
|
for index, pokemon_name in enumerate(Array(self.max_pokemon_index, PokemonCString(name_length)).parse_stream(self.stream), start=1):
|
||||||
try:
|
try:
|
||||||
id = self.pokedex_order[index]
|
id = self.pokedex_order[index]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1528,7 +1511,7 @@ class RBYCart:
|
||||||
def machine_moves(self):
|
def machine_moves(self):
|
||||||
"""List of move identifiers corresponding to TMs/HMs."""
|
"""List of move identifiers corresponding to TMs/HMs."""
|
||||||
self.stream.seek(self.addrs['TechnicalMachines'])
|
self.stream.seek(self.addrs['TechnicalMachines'])
|
||||||
return Array(self.NUM_MACHINES, IdentEnum(ULInt8('move'), MOVE_IDENTIFIERS)).parse_stream(self.stream)
|
return Array(self.NUM_MACHINES, IdentEnum(Byte, MOVE_IDENTIFIERS)).parse_stream(self.stream)
|
||||||
|
|
||||||
@reify
|
@reify
|
||||||
def pokemon_records(self):
|
def pokemon_records(self):
|
||||||
|
@ -1572,7 +1555,7 @@ class RBYCart:
|
||||||
|
|
||||||
self.stream.seek(self.addrs['PokedexEntryPointers'])
|
self.stream.seek(self.addrs['PokedexEntryPointers'])
|
||||||
# This address is just an array of pointers
|
# This address is just an array of pointers
|
||||||
for index, address in enumerate(Array(self.max_pokemon_index, ULInt16('pointer')).parse_stream(self.stream), start=1):
|
for index, address in enumerate(Array(self.max_pokemon_index, Int16ul).parse_stream(self.stream), start=1):
|
||||||
try:
|
try:
|
||||||
id = self.pokedex_order[index]
|
id = self.pokedex_order[index]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1586,7 +1569,7 @@ class RBYCart:
|
||||||
@reify
|
@reify
|
||||||
def move_names(self):
|
def move_names(self):
|
||||||
self.stream.seek(self.addrs['MoveNames'])
|
self.stream.seek(self.addrs['MoveNames'])
|
||||||
return Array(NUM_MOVES, PokemonCString('move_name')).parse_stream(self.stream)
|
return Array(self.NUM_MOVES, PokemonCString()).parse_stream(self.stream)
|
||||||
|
|
||||||
|
|
||||||
# TODO would be slick to convert this to a construct... construct
|
# TODO would be slick to convert this to a construct... construct
|
||||||
|
|
Loading…
Reference in a new issue