# 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 * # 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), )