veekun_pokedex/pokedex/struct/_pokemon_struct.py
2016-11-24 21:29:58 +00:00

817 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# encoding: utf8
u"""Defines a construct `pokemon_struct`, containing the structure of a single
Pokémon saved within a game -- often seen as a .pkm file. This is the same
format sent back and forth over the GTS.
"""
import datetime
from construct import (Adapter, BitField, BitStruct, Buffered,
EmbeddedBitStruct, Enum, Flag, Padding, String, Struct,
ULInt16, ULInt32, ULInt8)
# TODO:
# - strings should be validated, going both in and out
# - strings need to pad themselves when being re-encoded
# - strings sometimes need specific padding christ
# - date_met is not optional
# - some way to be more lenient with junk data, or at least
# - higher-level validation; see XXXes below
# - personality indirectly influences IVs due to PRNG use
# The entire gen 4 character table:
character_table = {
0x0002: u'',
0x0003: u'',
0x0004: u'',
0x0005: u'',
0x0006: u'',
0x0007: u'',
0x0008: u'',
0x0009: u'',
0x000a: u'',
0x000b: u'',
0x000c: u'',
0x000d: u'',
0x000e: u'',
0x000f: u'',
0x0010: u'',
0x0011: u'',
0x0012: u'',
0x0013: u'',
0x0014: u'',
0x0015: u'',
0x0016: u'',
0x0017: u'',
0x0018: u'',
0x0019: u'',
0x001a: u'',
0x001b: u'',
0x001c: u'',
0x001d: u'',
0x001e: u'',
0x001f: u'',
0x0020: u'',
0x0021: u'',
0x0022: u'',
0x0023: u'',
0x0024: u'',
0x0025: u'',
0x0026: u'',
0x0027: u'',
0x0028: u'',
0x0029: u'',
0x002a: u'',
0x002b: u'',
0x002c: u'',
0x002d: u'',
0x002e: u'',
0x002f: u'',
0x0030: u'',
0x0031: u'',
0x0032: u'',
0x0033: u'',
0x0034: u'',
0x0035: u'',
0x0036: u'',
0x0037: u'',
0x0038: u'',
0x0039: u'',
0x003a: u'',
0x003b: u'',
0x003c: u'',
0x003d: u'',
0x003e: u'',
0x003f: u'',
0x0040: u'',
0x0041: u'',
0x0042: u'',
0x0043: u'',
0x0044: u'',
0x0045: u'',
0x0046: u'',
0x0047: u'',
0x0048: u'',
0x0049: u'',
0x004a: u'',
0x004b: u'',
0x004c: u'',
0x004d: u'',
0x004e: u'',
0x004f: u'',
0x0050: u'',
0x0051: u'',
0x0052: u'',
0x0053: u'',
0x0054: u'',
0x0055: u'',
0x0056: u'',
0x0057: u'',
0x0058: u'',
0x0059: u'',
0x005a: u'',
0x005b: u'',
0x005c: u'',
0x005d: u'',
0x005e: u'',
0x005f: u'',
0x0060: u'',
0x0061: u'',
0x0062: u'',
0x0063: u'',
0x0064: u'',
0x0065: u'',
0x0066: u'',
0x0067: u'',
0x0068: u'',
0x0069: u'',
0x006a: u'',
0x006b: u'',
0x006c: u'',
0x006d: u'',
0x006e: u'',
0x006f: u'',
0x0070: u'',
0x0071: u'',
0x0072: u'',
0x0073: u'',
0x0074: u'',
0x0075: u'',
0x0076: u'',
0x0077: u'',
0x0078: u'',
0x0079: u'',
0x007a: u'',
0x007b: u'',
0x007c: u'',
0x007d: u'',
0x007e: u'',
0x007f: u'',
0x0080: u'',
0x0081: u'',
0x0082: u'',
0x0083: u'',
0x0084: u'',
0x0085: u'',
0x0086: u'',
0x0087: u'',
0x0088: u'',
0x0089: u'',
0x008a: u'',
0x008b: u'',
0x008c: u'',
0x008d: u'',
0x008e: u'',
0x008f: u'',
0x0090: u'',
0x0091: u'',
0x0092: u'',
0x0093: u'',
0x0094: u'',
0x0095: u'',
0x0096: u'',
0x0097: u'',
0x0098: u'',
0x0099: u'',
0x009a: u'',
0x009b: u'',
0x009c: u'',
0x009d: u'',
0x009e: u'',
0x009f: u'',
0x00a0: u'',
0x00a1: u'',
0x00a2: u'',
0x00a3: u'',
0x00a4: u'',
0x00a5: u'',
0x00a6: u'',
0x00a7: u'',
0x00a8: u'',
0x00a9: u'',
0x00aa: u'',
0x00ab: u'',
0x00ac: u'',
0x00ad: u'',
0x00ae: u'',
0x00af: u'',
0x00b0: u'',
0x00b1: u'',
0x00b2: u'',
0x00b3: u'',
0x00b4: u'',
0x00b5: u'',
0x00b6: u'',
0x00b7: u'',
0x00b8: u'',
0x00b9: u'',
0x00ba: u'',
0x00bb: u'',
0x00bc: u'',
0x00bd: u'',
0x00be: u'',
0x00bf: u'',
0x00c0: u'',
0x00c1: u'',
0x00c2: u'',
0x00c3: u'',
0x00c4: u'',
0x00c5: u'',
0x00c6: u'',
0x00c7: u'',
0x00c8: u'',
0x00c9: u'',
0x00ca: u'',
0x00cb: u'',
0x00cc: u'',
0x00cd: u'',
0x00ce: u'',
0x00cf: u'',
0x00d0: u'',
0x00d1: u'',
0x00d2: u'',
0x00d3: u'',
0x00d4: u'',
0x00d5: u'',
0x00d6: u'',
0x00d7: u'',
0x00d8: u'',
0x00d9: u'',
0x00da: u'',
0x00db: u'',
0x00dc: u'',
0x00dd: u'',
0x00de: u'',
0x00df: u'',
0x00e0: u'à',
0x00e1: u'',
0x00e2: u'',
0x00e3: u'',
0x00e4: u'',
0x00e5: u'',
0x00e6: u'',
0x00e7: u'',
0x00e8: u'',
0x00e9: u'',
0x00ea: u'',
0x00eb: u'',
0x00ec: u'',
0x00ed: u'',
0x00ee: u'',
0x00ef: u'',
0x00f0: u'',
0x00f1: u'',
0x00f2: u'×',
0x00f3: u'÷',
0x00f4: u'=',
0x00f5: u'~',
0x00f6: u'',
0x00f7: u'',
0x00f8: u'',
0x00f9: u'',
0x00fa: u'',
0x00fb: u'',
0x00fc: u'',
0x00fd: u'',
0x00fe: u'',
0x00ff: u'',
0x0100: u'',
0x0101: u'',
0x0102: u'',
0x0103: u'',
0x0104: u'',
0x0105: u'',
0x0106: u'%',
0x0107: u'',
0x0108: u'',
0x0109: u'',
0x010a: u'',
0x010f: u'',
0x0110: u'',
0x0112: u'',
0x0116: u'',
0x011b: u'',
0x011c: u'',
0x011d: u'',
0x011e: u'',
0x0120: u'&',
0x0121: u'0',
0x0122: u'1',
0x0123: u'2',
0x0124: u'3',
0x0125: u'4',
0x0126: u'5',
0x0127: u'6',
0x0128: u'7',
0x0129: u'8',
0x012a: u'9',
0x012b: u'A',
0x012c: u'B',
0x012d: u'C',
0x012e: u'D',
0x012f: u'E',
0x0130: u'F',
0x0131: u'G',
0x0132: u'H',
0x0133: u'I',
0x0134: u'J',
0x0135: u'K',
0x0136: u'L',
0x0137: u'M',
0x0138: u'N',
0x0139: u'O',
0x013a: u'P',
0x013b: u'Q',
0x013c: u'R',
0x013d: u'S',
0x013e: u'T',
0x013f: u'U',
0x0140: u'V',
0x0141: u'W',
0x0142: u'X',
0x0143: u'Y',
0x0144: u'Z',
0x0145: u'a',
0x0146: u'b',
0x0147: u'c',
0x0148: u'd',
0x0149: u'e',
0x014a: u'f',
0x014b: u'g',
0x014c: u'h',
0x014d: u'i',
0x014e: u'j',
0x014f: u'k',
0x0150: u'l',
0x0151: u'm',
0x0152: u'n',
0x0153: u'o',
0x0154: u'p',
0x0155: u'q',
0x0156: u'r',
0x0157: u's',
0x0158: u't',
0x0159: u'u',
0x015a: u'v',
0x015b: u'w',
0x015c: u'x',
0x015d: u'y',
0x015e: u'z',
0x015f: u'À',
0x0160: u'Á',
0x0161: u'Â',
0x0163: u'Ä',
0x0166: u'Ç',
0x0167: u'È',
0x0168: u'É',
0x0169: u'Ê',
0x016a: u'Ë',
0x016b: u'Ì',
0x016c: u'Í',
0x016d: u'Î',
0x016e: u'Ï',
0x0170: u'Ñ',
0x0171: u'Ò',
0x0172: u'Ó',
0x0173: u'Ô',
0x0175: u'Ö',
0x0176: u'×',
0x0178: u'Ù',
0x0179: u'Ú',
0x017a: u'Û',
0x017b: u'Ü',
0x017e: u'ß',
0x017f: u'à',
0x0180: u'á',
0x0181: u'â',
0x0183: u'ä',
0x0186: u'ç',
0x0187: u'è',
0x0188: u'é',
0x0189: u'ê',
0x018a: u'ë',
0x018b: u'ì',
0x018c: u'í',
0x018d: u'î',
0x018e: u'ï',
0x0190: u'ñ',
0x0191: u'ò',
0x0192: u'ó',
0x0193: u'ô',
0x0195: u'ö',
0x0196: u'÷',
0x0198: u'ù',
0x0199: u'ú',
0x019a: u'û',
0x019b: u'ü',
0x019f: u'Œ',
0x01a0: u'œ',
0x01a3: u'ª',
0x01a4: u'º',
0x01a5: u'þ',
0x01a6: u'Þ',
0x01a7: u'ʳ',
0x01a8: u'¥',
0x01a9: u'¡',
0x01aa: u'¿',
0x01ab: u'!',
0x01ac: u'?',
0x01ad: u',',
0x01ae: u'.',
0x01af: u'',
0x01b0: u'·',
0x01b1: u'/',
0x01b2: u'',
0x01b3: u'\'',
0x01b3: u'',
0x01b4: u'',
0x01b5: u'',
0x01b6: u'',
0x01b7: u'«',
0x01b8: u'»',
0x01b9: u'(',
0x01ba: u')',
0x01bb: u'',
0x01bc: u'',
0x01bd: u'+',
0x01be: u'-',
0x01bf: u'*',
0x01c0: u'#',
0x01c1: u'=',
0x01c2: u'&',
0x01c3: u'~',
0x01c4: u':',
0x01c5: u';',
0x01c6: u'',
0x01c7: u'',
0x01c8: u'',
0x01c9: u'',
0x01ca: u'',
0x01cb: u'',
0x01cc: u'',
0x01cd: u'',
0x01ce: u'',
0x01cf: u'',
0x01d0: u'@',
0x01d1: u'',
0x01d2: u'%',
0x01d3: u'',
0x01d4: u'',
0x01d5: u'',
0x01d6: u'',
0x01db: u'',
0x01dc: u'',
0x01de: u' ',
0xe000: u'\n',
0x25bc: u'\f',
0x25bd: u'\r',
}
# And the reverse dict, used with str.translate()
inverse_character_table = dict()
for in_, out in character_table.items():
inverse_character_table[ord(out)] = in_
def LittleEndianBitStruct(*args):
"""Construct's bit structs read a byte at a time in the order they appear,
reading each bit from most to least significant. Alas, this doesn't work
at all for a 32-bit bit field, because the bytes are 'backwards' in
little-endian files.
So this acts as a bit struct, but reverses the order of bytes before
reading/writing, so ALL the bits are read from most to least significant.
"""
return Buffered(
BitStruct(*args),
encoder=lambda s: s[::-1],
decoder=lambda s: s[::-1],
resizer=lambda _: _,
)
class PokemonStringAdapter(Adapter):
u"""Adapter that encodes/decodes Pokémon-formatted text stored in a regular
String struct.
"""
def _decode(self, obj, context):
decoded_text = obj.decode('utf16')
# Real string ends at the \uffff character
if u'\uffff' in decoded_text:
decoded_text = decoded_text[0:decoded_text.index(u'\uffff')]
# XXX save "trash bytes" somewhere..?
return decoded_text.translate(character_table)
def _encode(self, obj, context):
#padded_text = (obj + u'\xffff' + '\x00' * 12)
padded_text = obj
decoded_text = padded_text.translate(inverse_character_table)
return decoded_text.encode('utf16')
class DateAdapter(Adapter):
"""Converts between a three-byte string and a Python date.
Only dates in 2000 or later will work!
"""
def _decode(self, obj, context):
if obj == '\x00\x00\x00':
return None
y, m, d = (ord(byte) for byte in obj)
y += 2000
return datetime.date(y, m, d)
def _encode(self, obj, context):
if obj is None:
return '\x00\x00\x00'
y, m, d = obj.year - 2000, obj.month, obj.day
return ''.join(chr(n) for n in (y, m, d))
class PokemonFormAdapter(Adapter):
"""Converts form ids to form names, and vice versa."""
pokemon_forms = {
# Unown
201: 'abcdefghijklmnopqrstuvwxyz!?',
# Deoxys
386: ['normal', 'attack', 'defense', 'speed'],
# Burmy and Wormadam
412: ['plant', 'sandy', 'trash'],
413: ['plant', 'sandy', 'trash'],
# Shellos and Gastrodon
422: ['west', 'east'],
423: ['west', 'east'],
# Rotom
479: ['normal', 'heat', 'wash', 'frost', 'fan', 'cut'],
# Giratina
487: ['altered', 'origin'],
# Shaymin
492: ['land', 'sky'],
# Arceus
493: [
'normal', 'fighting', 'flying', 'poison', 'ground', 'rock',
'bug', 'ghost', 'steel', 'fire', 'water', 'grass',
'thunder', 'psychic', 'ice', 'dragon', 'dark', '???',
],
}
def _decode(self, obj, context):
try:
forms = self.pokemon_forms[ context['national_id'] ]
except KeyError:
return None
return forms[obj >> 3]
def _encode(self, obj, context):
try:
forms = self.pokemon_forms[ context['national_id'] ]
except KeyError:
return None
return forms.index(obj) << 3
# And here we go.
# Docs: http://projectpokemon.org/wiki/Pokemon_NDS_Structure
pokemon_struct = Struct('pokemon_struct',
# Header
ULInt32('personality'), # XXX aughgh http://bulbapedia.bulbagarden.net/wiki/Personality
Padding(2),
ULInt16('checksum'), # XXX should be checked or calculated
# Block A
ULInt16('national_id'),
ULInt16('held_item_id'),
ULInt16('original_trainer_id'),
ULInt16('original_trainer_secret_id'),
ULInt32('exp'),
ULInt8('happiness'),
ULInt8('ability_id'), # XXX needs to match personality + species
BitStruct('markings',
Padding(2),
Flag('diamond'),
Flag('star'),
Flag('heart'),
Flag('square'),
Flag('triangle'),
Flag('circle'),
),
Enum(
ULInt8('original_country'),
jp=1,
us=2,
fr=3,
it=4,
de=5,
es=7,
kr=8,
),
# XXX sum cannot surpass 510
ULInt8('effort_hp'),
ULInt8('effort_attack'),
ULInt8('effort_defense'),
ULInt8('effort_speed'),
ULInt8('effort_special_attack'),
ULInt8('effort_special_defense'),
ULInt8('contest_cool'),
ULInt8('contest_beauty'),
ULInt8('contest_cute'),
ULInt8('contest_smart'),
ULInt8('contest_tough'),
ULInt8('contest_sheen'),
LittleEndianBitStruct('sinnoh_ribbons',
Padding(4),
Flag('premier_ribbon'),
Flag('classic_ribbon'),
Flag('carnival_ribbon'),
Flag('festival_ribbon'),
Flag('blue_ribbon'),
Flag('green_ribbon'),
Flag('red_ribbon'),
Flag('legend_ribbon'),
Flag('history_ribbon'),
Flag('record_ribbon'),
Flag('footprint_ribbon'),
Flag('gorgeous_royal_ribbon'),
Flag('royal_ribbon'),
Flag('gorgeous_ribbon'),
Flag('smile_ribbon'),
Flag('snooze_ribbon'),
Flag('relax_ribbon'),
Flag('careless_ribbon'),
Flag('downcast_ribbon'),
Flag('shock_ribbon'),
Flag('alert_ribbon'),
Flag('world_ability_ribbon'),
Flag('pair_ability_ribbon'),
Flag('multi_ability_ribbon'),
Flag('double_ability_ribbon'),
Flag('great_ability_ribbon'),
Flag('ability_ribbon'),
Flag('sinnoh_champ_ribbon'),
),
# Block B
ULInt16('move1_id'),
ULInt16('move2_id'),
ULInt16('move3_id'),
ULInt16('move4_id'),
ULInt8('move1_pp'),
ULInt8('move2_pp'),
ULInt8('move3_pp'),
ULInt8('move4_pp'),
ULInt8('move1_pp_ups'),
ULInt8('move2_pp_ups'),
ULInt8('move3_pp_ups'),
ULInt8('move4_pp_ups'),
LittleEndianBitStruct('ivs',
Flag('is_nicknamed'),
Flag('is_egg'),
BitField('iv_special_defense', 5),
BitField('iv_special_attack', 5),
BitField('iv_speed', 5),
BitField('iv_defense', 5),
BitField('iv_attack', 5),
BitField('iv_hp', 5),
),
LittleEndianBitStruct('hoenn_ribbons',
Flag('world_ribbon'),
Flag('earth_ribbon'),
Flag('national_ribbon'),
Flag('country_ribbon'),
Flag('sky_ribbon'),
Flag('land_ribbon'),
Flag('marine_ribbon'),
Flag('effort_ribbon'),
Flag('artist_ribbon'),
Flag('victory_ribbon'),
Flag('winning_ribbon'),
Flag('champion_ribbon'),
Flag('tough_ribbon_master'),
Flag('tough_ribbon_hyper'),
Flag('tough_ribbon_super'),
Flag('tough_ribbon'),
Flag('smart_ribbon_master'),
Flag('smart_ribbon_hyper'),
Flag('smart_ribbon_super'),
Flag('smart_ribbon'),
Flag('cute_ribbon_master'),
Flag('cute_ribbon_hyper'),
Flag('cute_ribbon_super'),
Flag('cute_ribbon'),
Flag('beauty_ribbon_master'),
Flag('beauty_ribbon_hyper'),
Flag('beauty_ribbon_super'),
Flag('beauty_ribbon'),
Flag('cool_ribbon_master'),
Flag('cool_ribbon_hyper'),
Flag('cool_ribbon_super'),
Flag('cool_ribbon'),
),
EmbeddedBitStruct(
PokemonFormAdapter(BitField('alternate_form', 5)),
Enum(BitField('gender', 2),
genderless = 2,
male = 0,
female = 1,
),
Flag('fateful_encounter'),
),
BitStruct('shining_leaves',
Padding(2),
Flag('crown'),
Flag('leaf5'),
Flag('leaf4'),
Flag('leaf3'),
Flag('leaf2'),
Flag('leaf1'),
),
Padding(2),
ULInt16('pt_egg_location_id'),
ULInt16('pt_met_location_id'),
# Block C
PokemonStringAdapter(String('nickname', 22)),
Padding(1),
Enum(ULInt8('original_version'),
sapphire = 1,
ruby = 2,
emerald = 3,
firered = 4,
leafgreen = 5,
heartgold = 7,
soulsilver = 8,
diamond = 10,
pearl = 11,
platinum = 12,
orre = 15,
),
LittleEndianBitStruct('sinnoh_contest_ribbons',
Padding(12),
Flag('tough_ribbon_master'),
Flag('tough_ribbon_ultra'),
Flag('tough_ribbon_great'),
Flag('tough_ribbon'),
Flag('smart_ribbon_master'),
Flag('smart_ribbon_ultra'),
Flag('smart_ribbon_great'),
Flag('smart_ribbon'),
Flag('cute_ribbon_master'),
Flag('cute_ribbon_ultra'),
Flag('cute_ribbon_great'),
Flag('cute_ribbon'),
Flag('beauty_ribbon_master'),
Flag('beauty_ribbon_ultra'),
Flag('beauty_ribbon_great'),
Flag('beauty_ribbon'),
Flag('cool_ribbon_master'),
Flag('cool_ribbon_ultra'),
Flag('cool_ribbon_great'),
Flag('cool_ribbon'),
),
Padding(4),
# Block D
PokemonStringAdapter(String('original_trainer_name', 16)),
DateAdapter(String('date_egg_received', 3)),
DateAdapter(String('date_met', 3)),
ULInt16('dp_egg_location_id'),
ULInt16('dp_met_location_id'),
ULInt8('pokerus'),
ULInt8('dppt_pokeball'),
EmbeddedBitStruct(
Enum(Flag('original_trainer_gender'),
male = False,
female = True,
),
BitField('met_at_level', 7),
),
Enum(ULInt8('encounter_type'),
special = 0, # egg; pal park; event; honey tree; shaymin
grass = 2, # or darkrai
dialga_palkia = 4,
cave = 5, # or giratina or hall of origin
water = 7,
building = 9,
safari_zone = 10, # includes great marsh
gift = 12, # starter; fossil; ingame trade?
# distortion_world = ???,
hgss_gift = 24, # starter; fossil; bebe's eevee (pt only??)
),
ULInt8('hgss_pokeball'),
Padding(1),
)