# 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'0', 0x00a3: u'1', 0x00a4: u'2', 0x00a5: u'3', 0x00a6: u'4', 0x00a7: u'5', 0x00a8: u'6', 0x00a9: u'7', 0x00aa: u'8', 0x00ab: u'9', 0x00ac: u'A', 0x00ad: u'B', 0x00ae: u'C', 0x00af: u'D', 0x00b0: u'E', 0x00b1: u'F', 0x00b2: u'G', 0x00b3: u'H', 0x00b4: u'I', 0x00b5: u'J', 0x00b6: u'K', 0x00b7: u'L', 0x00b8: u'M', 0x00b9: u'N', 0x00ba: u'O', 0x00bb: u'P', 0x00bc: u'Q', 0x00bd: u'R', 0x00be: u'S', 0x00bf: u'T', 0x00c0: u'U', 0x00c1: u'V', 0x00c2: u'W', 0x00c3: u'X', 0x00c4: u'Y', 0x00c5: u'Z', 0x00c6: u'a', 0x00c7: u'b', 0x00c8: u'c', 0x00c9: u'd', 0x00ca: u'e', 0x00cb: u'f', 0x00cc: u'g', 0x00cd: u'h', 0x00ce: u'i', 0x00cf: u'j', 0x00d0: u'k', 0x00d1: u'l', 0x00d2: u'm', 0x00d3: u'n', 0x00d4: u'o', 0x00d5: u'p', 0x00d6: u'q', 0x00d7: u'r', 0x00d8: u's', 0x00d9: u't', 0x00da: u'u', 0x00db: u'v', 0x00dc: u'w', 0x00dd: u'x', 0x00de: u'y', 0x00df: u'z', 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), )