Skip to content

Instantly share code, notes, and snippets.

@Miss-Inputs
Created February 8, 2018 04:37
Show Gist options
  • Save Miss-Inputs/b838b0114eee220fe5adffb1e8b9fd27 to your computer and use it in GitHub Desktop.
Save Miss-Inputs/b838b0114eee220fe5adffb1e8b9fd27 to your computer and use it in GitHub Desktop.
Quick and dirty and unfinished lister for Pokemon gen 1 .sav files
#!/usr/bin/python3
import sys
import math
TEXT_TABLE = {}
TERMINATOR_CHAR = 0x50
TEXT_TABLE[TERMINATOR_CHAR] = '<terminator>'
#4B and 4C are unknown control chars
TEXT_TABLE[0x00] = '<null>'
TEXT_TABLE[0x49] = '<button prompt>' #Prompts the player to press a button, only used in Pokedex entries
TEXT_TABLE[0x4A] = '<PKMN>'
TEXT_TABLE[0x4E] = '\n' #In Pokedex entries
TEXT_TABLE[0x4F] = '\n'
TEXT_TABLE[0x51] = '<button prompt>'
TEXT_TABLE[0x52] = '<player name>'
TEXT_TABLE[0x53] = '<rival name>'
TEXT_TABLE[0x54] = '<POKé>'
TEXT_TABLE[0x55] = '<button prompt>\n'
TEXT_TABLE[0x56] = '<……>'
TEXT_TABLE[0x57] = '<end of dialogue>'
TEXT_TABLE[0x58] = '<end of dialogue with visual prompt>'
TEXT_TABLE[0x59] = '<enemy Pokemon name>'
TEXT_TABLE[0x5A] = '<active Pokemon name>'
TEXT_TABLE[0x5B] = '<PC>'
TEXT_TABLE[0x5C] = '<TM>'
TEXT_TABLE[0x5D] = '<TRAINER>'
TEXT_TABLE[0x5E] = '<ROCKET>'
TEXT_TABLE[0x5F] = '<end of Pokedex entry>'
#These ones are leftover from the Japanese version, and print in bold
TEXT_TABLE[0x60] = '<A>'
TEXT_TABLE[0x61] = '<B>'
TEXT_TABLE[0x62] = '<C>'
TEXT_TABLE[0x63] = '<D>'
TEXT_TABLE[0x64] = '<E>'
TEXT_TABLE[0x65] = '<F>'
TEXT_TABLE[0x66] = '<G>'
TEXT_TABLE[0x67] = '<H>'
TEXT_TABLE[0x68] = '<I>'
TEXT_TABLE[0x69] = '<V>'
TEXT_TABLE[0x6A] = '<S>'
TEXT_TABLE[0x6B] = '<L>'
TEXT_TABLE[0x6C] = '<M>'
TEXT_TABLE[0x6D] = '<:>'
TEXT_TABLE[0x6E] = 'ぃ'
TEXT_TABLE[0x6F] = 'ぅ'
TEXT_TABLE[0x70] = "‘"
TEXT_TABLE[0x71] = '’'
TEXT_TABLE[0x72] = '“'
TEXT_TABLE[0x73] = '”'
TEXT_TABLE[0x74] = '・'
TEXT_TABLE[0x75] = '…'
TEXT_TABLE[0x76] = 'ぁ'
TEXT_TABLE[0x77] = 'ぇ'
TEXT_TABLE[0x78] = 'ぉ'
TEXT_TABLE[0x79] = '<pokeball border top left>'
TEXT_TABLE[0x7A] = '<pokeball border horizontal>'
TEXT_TABLE[0x7B] = '<pokeball border top right>'
TEXT_TABLE[0x7C] = '<pokeball border vertical'
TEXT_TABLE[0x7D] = '<pokeball border bottom left>'
TEXT_TABLE[0x7E] = '<pokeball border bottom right>'
TEXT_TABLE[0x7F] = ' '
TEXT_TABLE[0x80] = 'A'
TEXT_TABLE[0x81] = 'B'
TEXT_TABLE[0x82] = 'C'
TEXT_TABLE[0x83] = 'D'
TEXT_TABLE[0x84] = 'E'
TEXT_TABLE[0x85] = 'F'
TEXT_TABLE[0x86] = 'G'
TEXT_TABLE[0x87] = 'H'
TEXT_TABLE[0x88] = 'I'
TEXT_TABLE[0x89] = 'J'
TEXT_TABLE[0x8A] = 'K'
TEXT_TABLE[0x8B] = 'L'
TEXT_TABLE[0x8C] = 'M'
TEXT_TABLE[0x8D] = 'N'
TEXT_TABLE[0x8E] = 'O'
TEXT_TABLE[0x8F] = 'P'
TEXT_TABLE[0x90] = 'Q'
TEXT_TABLE[0x91] = 'R'
TEXT_TABLE[0x92] = 'S'
TEXT_TABLE[0x93] = 'T'
TEXT_TABLE[0x94] = 'U'
TEXT_TABLE[0x95] = 'V'
TEXT_TABLE[0x96] = 'W'
TEXT_TABLE[0x97] = 'X'
TEXT_TABLE[0x98] = 'Y'
TEXT_TABLE[0x99] = 'Z'
TEXT_TABLE[0x9A] = '('
TEXT_TABLE[0x9B] = ')'
TEXT_TABLE[0x9C] = ':'
TEXT_TABLE[0x9D] = ';'
TEXT_TABLE[0x9E] = '['
TEXT_TABLE[0x9F] = ']'
TEXT_TABLE[0xA0] = 'a'
TEXT_TABLE[0xA1] = 'b'
TEXT_TABLE[0xA2] = 'c'
TEXT_TABLE[0xA3] = 'd'
TEXT_TABLE[0xA4] = 'e'
TEXT_TABLE[0xA5] = 'f'
TEXT_TABLE[0xA6] = 'g'
TEXT_TABLE[0xA7] = 'h'
TEXT_TABLE[0xA8] = 'i'
TEXT_TABLE[0xA9] = 'j'
TEXT_TABLE[0xAA] = 'k'
TEXT_TABLE[0xAB] = 'l'
TEXT_TABLE[0xAC] = 'm'
TEXT_TABLE[0xAD] = 'n'
TEXT_TABLE[0xAE] = 'o'
TEXT_TABLE[0xAF] = 'p'
TEXT_TABLE[0xB0] = 'q'
TEXT_TABLE[0xB1] = 'r'
TEXT_TABLE[0xB2] = 's'
TEXT_TABLE[0xB3] = 't'
TEXT_TABLE[0xB4] = 'u'
TEXT_TABLE[0xB5] = 'v'
TEXT_TABLE[0xB6] = 'w'
TEXT_TABLE[0xB7] = 'x'
TEXT_TABLE[0xB8] = 'y'
TEXT_TABLE[0xB9] = 'z'
TEXT_TABLE[0xBA] = 'é'
TEXT_TABLE[0xBB] = "<'d>"
TEXT_TABLE[0xBC] = "<'l>"
TEXT_TABLE[0xBD] = "<'s>"
TEXT_TABLE[0xBE] = "<'t>"
TEXT_TABLE[0xBF] = "<'v>"
TEXT_TABLE[0xE0] = "'"
TEXT_TABLE[0xE1] = '<PK>'
TEXT_TABLE[0xE2] = '<MN>'
TEXT_TABLE[0xE3] = '-'
TEXT_TABLE[0xE4] = "<'r>"
TEXT_TABLE[0xE5] = "<'m>"
TEXT_TABLE[0xE6] = '?'
TEXT_TABLE[0xE7] = '!'
TEXT_TABLE[0xE8] = '.' #Unlike 0xF2, this is meant as punctuation and not a decimal point (distinguishable in the Japanese version), doesn't appear in user input table
TEXT_TABLE[0xE9] = 'ァ'
TEXT_TABLE[0xEA] = 'ゥ'
TEXT_TABLE[0xEB] = 'ェ'
TEXT_TABLE[0xEC] = '▷'
TEXT_TABLE[0xED] = '▶'
TEXT_TABLE[0xEE] = '▼'
TEXT_TABLE[0xEF] = '♂'
TEXT_TABLE[0xF0] = '<PokeDollar>'
TEXT_TABLE[0xF1] = '×'
TEXT_TABLE[0xF2] = '.'
TEXT_TABLE[0xF3] = '/'
TEXT_TABLE[0xF4] = ','
TEXT_TABLE[0xF5] = '♀'
TEXT_TABLE[0xF6] = '0'
TEXT_TABLE[0xF7] = '1'
TEXT_TABLE[0xF8] = '2'
TEXT_TABLE[0xF9] = '3'
TEXT_TABLE[0xFA] = '4'
TEXT_TABLE[0xFB] = '5'
TEXT_TABLE[0xFC] = '6'
TEXT_TABLE[0xFD] = '7'
TEXT_TABLE[0xFE] = '8'
TEXT_TABLE[0xFF] = '9'
BADGES = ['Boulder Badge', 'Cascade Badge', 'Thunder Badge', 'Rainbow Badge', 'Soul Badge', 'Marsh Badge', 'Volcano Badge', 'Earth Badge']
ITEMS = {
0: '<nothing>', #Glitch item, called !j in RB / x in Yellow
1: 'Master Ball',
2: 'Ultra Ball',
3: 'Great Ball',
4: 'Poké Ball',
5: 'Town Map',
6: 'Biycle',
7: '<Surfboard>', #Unobtainable
8: 'Safari Ball', #Unobtainable, acts as ball with 1.5x catch rate
9: 'Pokédex', #Unobtainable, opens Pokedex (corrupts graphics in battle)
10: 'Moon Stone',
11: 'Antidote',
12: 'Burn Heal',
13: 'Ice Heal',
14: 'Awakening',
15: 'Parlyz Heal',
16: 'Full Restore',
17: 'Max Potion',
18: 'Hyper Potion',
19: 'Super Potion',
20: 'Potion',
#These badges are all unobtainable
21: 'BoulderBadge', #throws bait
22: 'CascadeBadge', #throws rock
23: 'ThunderBadge',
24: 'RainbowBadge',
25: 'SoulBadge',
26: 'MarshBadge',
27: 'VolcanoBadge',
28: 'EarthBadge',
29: 'Escape Rope',
30: 'Repel',
31: 'Old Amber',
32: 'Fire Stone',
33: 'Thunderstone',
34: 'Water Stone',
35: 'HP Up',
36: 'Protein',
37: 'Iron',
38: 'Carbos',
39: 'Calcium',
40: 'Rare Candy',
41: 'Dome Fossil',
42: 'Helix Fossil',
43: 'Secret Key',
44: '????', #This one does nothing
45: 'Bike Voucher',
46: 'X Accuracy',
47: 'Leaf Stone',
48: 'Card Key',
49: 'Nugget',
50: '<Fake PP Up>', #Unobtainable, does nothing
51: 'Poké Doll',
52: 'Full Heal',
53: 'Revive',
54: 'Max Revive',
55: 'Guard Spec.',
56: 'Super Repel',
57: 'Max Repel',
58: 'Dire Hit',
59: 'Coin', #Unobtainable
60: 'Fresh Water',
61: 'Soda Pop',
62: 'Lemonade',
63: 'S.S. Ticket',
64: 'Gold Teeth',
65: 'X Attack',
66: 'X Defend',
67: 'X Speed',
68: 'X Special',
69: 'Coin Case',
70: 'Oak\'s Parcel',
71: 'Itemfinder',
72: 'Silph Scope',
73: 'Poké Flute',
74: 'Lift Key',
75: 'Exp. All',
76: 'Old Rod',
77: 'Good Rod',
78: 'Super Rod',
79: 'PP Up',
80: 'Ether',
81: 'Max Ether',
82: 'Elixir',
83: 'Max Elixir',
}
POKEMON_SPECIES = {
0: "'M / 3TrainerPoke",
1: 'Rhydon',
2: 'Kangaskhan',
3: 'Nidoran Male',
4: 'Clefairy',
5: 'Spearow',
6: 'Voltorb',
7: 'Nidoking',
8: 'Slowbro',
9: 'Ivysaur',
10: 'Exeggutor',
11: 'Lickitung',
12: 'Exeggcute',
13: 'Grimer',
14: 'Gengar',
15: 'Nidoran Female',
16: 'Nidoqueen',
17: 'Cubone',
18: 'Rhyhorn',
19: 'Lapras',
20: 'Arcanine',
21: 'Mew',
22: 'Gyarados',
23: 'Shellder',
24: 'Tentacool',
25: 'Gastly',
26: 'Scyther',
27: 'Staryu',
28: 'Blastoise',
29: 'Pinsir',
30: 'Tangela',
33: 'Growlithe',
34: 'Onix',
35: 'Fearow',
36: 'Pidgey',
37: 'Slowpoke',
38: 'Kadabra',
39: 'Graveler',
40: 'Chansey',
41: 'Machoke',
42: 'Mr. Mime',
43: 'Hitmonlee',
44: 'Hitmonchan',
45: 'Arbok',
46: 'Parasect',
47: 'Psyduck',
48: 'Drowzee',
49: 'Golem',
51: 'Magmar',
53: 'Electabuzz',
54: 'Magneton',
55: 'Koffing',
57: 'Mankey',
58: 'Seel',
59: 'Diglett',
60: 'Tauros',
64: 'Farfetch\'d',
65: 'Venonat',
66: 'Dragonite',
70: 'Doduo',
71: 'Poliwag',
72: 'Jynx',
73: 'Moltres',
74: 'Articuno',
75: 'Zapdos',
76: 'Ditto',
77: 'Meowth',
78: 'Krabby',
82: 'Vulpix',
83: 'Ninetales',
84: 'Pikachu',
85: 'Raichu',
88: 'Dratini',
89: 'Dragonair',
90: 'Kabuto',
91: 'Kabutops',
92: 'Horsea',
93: 'Seadra',
96: 'Sandshrew',
97: 'Sandslash',
98: 'Omanyte',
99: 'Omastar',
100: 'Jigglypuff',
101: 'Wigglytuff',
102: 'Eevee',
103: 'Flareon',
104: 'Jolteon',
105: 'Vaporeon',
106: 'Machop',
107: 'Zubat',
108: 'Ekans',
109: 'Paras',
110: 'Poliwhirl',
111: 'Poliwrath',
112: 'Weedle',
113: 'Kakuna',
114: 'Beedrill',
116: 'Dodrio',
117: 'Primeape',
118: 'Dugtrio',
119: 'Venomoth',
120: 'Dewgong',
123: 'Caterpie',
124: 'Metapod',
125: 'Butterfree',
126: 'Machamp',
128: 'Golduck',
129: 'Hypno',
130: 'Golbat',
131: 'Mewtwo',
132: 'Snorlax',
133: 'Magikarp',
136: 'Muk',
138: 'Kingler',
139: 'Cloyster',
141: 'Electrode',
142: 'Clefable',
143: 'Weezing',
144: 'Persian',
145: 'Marowak',
147: 'Haunter',
148: 'Abra',
149: 'Alakazam',
150: 'Pidgeotto',
151: 'Pidgeot',
152: 'Starmie',
153: 'Bulbasaur',
154: 'Venusaur',
155: 'Tentacruel',
157: 'Goldeen',
158: 'Seaking',
163: 'Ponyta',
164: 'Rapidash',
165: 'Rattata',
166: 'Raticate',
167: 'Nidorino',
168: 'Nidorina',
169: 'Geodude',
170: 'Porygon',
171: 'Aerodactyl',
173: 'Magnemite',
176: 'Charmander',
177: 'Squirtle',
178: 'Charmeleon',
179: 'Wartortle',
180: 'Charizard',
185: 'Oddish',
186: 'Gloom',
187: 'Vileplume',
188: 'Bellsprout',
189: 'Weepinbell',
190: 'Victreebel',
#here be dragons (glitches actually)
#Names are different across RB and Y, so I've formatted them as <RB name> / <Yellow name>
191: '> A / 4 4',
192: 'a / 4 4 Hy',
193: '<ゥ 0xc1> / <female symbol>',
194: 'ゥ.4 / pPKMNp\'\'',
195: 'h POKé / ゥ ( Z4',
196: 'PokéWTrainer / X ゥ- xゥ',
197: '<PkMn 0xc5> / 4. .',
198: 'ゥL ゥM 4 / ァ7g',
199: '<female symbol>Pゥ ゥゥT / u',
#These also hold trainer classes, so I've formatted them as <trainer class> / <RB name> / <Yellow name>
200: 'Jacred / ゥU? / g g',
201: 'Youngster / <triangle>ゥ 8 / ァ / g J 1',
202: 'Bug Catcher / PC4SH / <0xca>',
203: 'Lass / p / .pゥ',
204: 'Sailor / PKMN<triangle>n / .8',
205: 'Jr. Trainer Male / Trainer / ゥ.B',
206: 'Jr. Trainer Female / <down arrow> W G d / <PKMNpゥぁ ゥぇ 0xce>',
207: 'Pokémaniac / OPKMN4X / 4, ゥァ (0xcf)',
208: 'Super Nerd / PKMNPKMNT / ゥ\'',
209: 'Hiker / 4B 8 4 8 / B ァ h',
210: 'Biker / ゥ \' / PKMN ? A',
211: 'Burglar / M p\'u ゥ / ゥゥ)',
212: 'Engineer / Aゥ G / <ゥ 0xd4>',
213: 'Juggler / Pゥ ゥゥ / \'ゥ.',
214: 'Fisherman / 4 h / <PKMNpゥぁ ゥぇ 0xd6>',
215: 'Swimmer / <0xd7> / <B 0xd7>',
216: 'Cue Ball / PMNaPKMNゥ <male symbol>fPKMNk / <PKMN 0xd8>',
#TODO The species names for the rest of these
217: 'Gambler',
218: 'Beauty',
219: 'Psychic',
220: 'Rocker',
221: 'Juggler',
222: 'Tamer',
223: 'Bird Keeper',
224: 'Blackbelt',
225: 'Rival #1',
226: 'Professor Oak',
227: 'Chief',
228: 'Scientist',
229: 'Giovanni',
230: 'Rocket',
231: 'Cooltrainer Male',
232: 'Cooltrainer Female',
233: 'Bruno',
234: 'Brock',
235: 'Misty',
236: 'Lt. Surge',
237: 'Erika',
238: 'Koga',
239: 'Blaine',
240: 'Sabrina',
241: 'Gentleman',
242: 'Rival #2',
243: 'Rival at E4',
244: 'Lorelei',
245: 'Channeler',
246: 'Agatha',
247: 'Lance',
248: '<glitch trainer 0xf8>',
249: '<glitch trainer 0xf9>',
250: '<glitch trainer 0xfa>',
251: '<glitch trainer 0xfb>',
252: '<glitch trainer 0xfc>',
253: '<glitch trainer 0xfd>',
254: '<glitch trainer 0xfe>',
255: '<glitch trainer 0xff>',
}
TYPES = {
0: 'Normal',
1: 'Fighting',
2: 'Flying',
3: 'Poison',
4: 'Ground',
5: 'Rock',
7: 'Bug',
8: 'Ghost',
20: 'Fire',
21: 'Water',
22: 'Grass',
23: 'Electric',
24: 'Psychic',
25: 'Ice',
26: 'Dragon',
}
MOVES = {
0: ('<Cooltrainer>', 13),
1: ('Pound', 35),
2: ('Karate Chop', 25),
3: ('Double Slap', 10),
4: ('Comet Punch', 15),
5: ('Mega Punch', 20),
6: ('Pay Day', 20),
7: ('Fire Punch', 15),
8: ('Ice Punch', 15),
9: ('Thunderpunch', 15),
10: ('Scratch', 35),
11: ('Vicegrip', 30),
12: ('Guillotine', 5),
13: ('Razor Wind', 10),
14: ('Swords Dance', 30),
15: ('Cut', 30),
16: ('Gust', 35),
17: ('Wing Attack', 35),
18: ('Whirlwind', 20),
19: ('Fly', 15),
20: ('Bind', 20),
21: ('Slam', 20),
22: ('Vine Whip', 10),
23: ('Stomp', 20),
24: ('Double Kick', 30),
25: ('Mega Kick', 5),
26: ('Jump Kick', 25),
27: ('Rolling Kick', 15),
28: ('Sand Attack', 15),
29: ('Headbutt', 15),
30: ('Horn Attack', 25),
31: ('Fury Attack', 20),
32: ('Horn Drill', 5),
33: ('Tackle', 35),
34: ('Body Slam', 15),
35: ('Wrap', 20),
36: ('Take Down', 20),
37: ('Thrash', 20),
38: ('Double-Edge', 15),
39: ('Tail Whip', 30),
40: ('Poison Sting', 35),
41: ('Twineedle', 20),
42: ('Pin Missile', 20),
43: ('Leer', 30),
44: ('Bite', 25),
45: ('Growl', 40),
46: ('Roar', 20),
47: ('Sing', 15),
48: ('Supersonic', 20),
49: ('Sonicboom', 20),
50: ('Disable', 20),
51: ('Acid', 30),
52: ('Ember', 25),
53: ('Flamethrower', 15),
54: ('Mist', 30),
55: ('Water Gun', 25),
56: ('Hydro Pump', 5),
57: ('Surf', 15),
58: ('Ice Beam', 10),
59: ('Blizzard', 5),
60: ('Psybeam', 20),
61: ('Bubblebeam', 20),
62: ('Aurora Beam', 20),
63: ('Hyper Beam', 5),
64: ('Peck', 35),
65: ('Drill Peck', 20),
66: ('Submission', 25),
67: ('Low Kick', 20),
68: ('Counter', 20),
69: ('Seismic Toss', 20),
70: ('Strength', 15),
71: ('Absorb', 20),
72: ('Mega Drain', 10),
73: ('Leech Seed', 10),
74: ('Growth', 40),
75: ('Razor Leaf', 25),
76: ('Solar Beam', 10),
77: ('Poisonpowder', 35),
78: ('Stun Spore', 30),
79: ('Sleep Powder', 15),
80: ('Petal Dance', 20),
81: ('String Shot', 40),
82: ('Dragon Rage', 10),
83: ('Fire Spin', 15),
84: ('Thundershock', 30),
85: ('Thunderbolt', 15),
86: ('Thunder Wave', 20),
87: ('Thunder', 10),
88: ('Rock Throw', 15),
89: ('Earthquake', 10),
90: ('Fissure', 5),
91: ('Dig', 10),
92: ('Toxic', 10),
93: ('Confusion', 25),
94: ('Psychic', 10),
95: ('Hypnosis', 20),
96: ('Medidate', 40),
97: ('Agility', 30),
98: ('Quick Attack', 30),
99: ('Rage', 20),
100: ('Teleport', 20),
101: ('Night Shade', 15),
102: ('Mimic', 10),
103: ('Screech', 40),
104: ('Double Team', 15),
105: ('Recover', 20),
106: ('Harden', 30),
107: ('Minimize', 20),
108: ('Smokescreen', 20),
109: ('Confuse Ray', 10),
110: ('Withdraw', 40),
111: ('Defense Curl', 40),
112: ('Barrier', 30),
113: ('Light Screen', 30),
114: ('Haze', 30),
115: ('Reflect', 20),
116: ('Focus Energy', 30),
117: ('Bide', 10),
118: ('Metronome', 10),
119: ('Mirror Move', 20),
120: ('Selfdestruct', 5),
121: ('Egg Bomb', 10),
122: ('Lick', 30),
123: ('Smog', 20),
124: ('Sludge', 20),
125: ('Bone Club', 20),
126: ('Fire Blast', 5),
127: ('Waterfall', 15),
128: ('Clamp', 10),
129: ('Swift', 20),
130: ('Skull Bash', 15),
131: ('Spike Cannon', 15),
132: ('Constrict', 35),
133: ('Amnesia', 20),
134: ('Kinesis', 15),
135: ('Softboiled', 10),
136: ('Hi Jump Kick', 20),
137: ('Glare', 30),
138: ('Dream Eater', 15),
139: ('Poison Gas', 40),
140: ('Barrage', 20),
141: ('Leech Life', 15),
142: ('Lovely Kiss', 10),
143: ('Sky Attack', 5),
144: ('Transform', 10),
145: ('Bubble', 30),
146: ('Dizzy Punch', 10),
147: ('Spore', 15),
148: ('Flash', 20),
149: ('Psywave', 15),
150: ('Splash', 40),
151: ('Acid Armor', 40),
152: ('Crabhammer', 10),
153: ('Explosion', 5),
154: ('Fury Swipes', 15),
155: ('Boomerang', 10),
156: ('Rest', 10),
157: ('Rock Slide', 10),
158: ('Hyper Fang', 15),
159: ('Sharpen', 30),
160: ('Conversion', 30),
161: ('Tri Attack', 10),
162: ('Super Fang', 10),
163: ('Slash', 20),
164: ('Substitute', 10),
165: ('Struggle', 10),
}
def decode_text(the_bytes):
s = ''
for b in the_bytes:
if b == TERMINATOR_CHAR:
break
elif b in TEXT_TABLE:
s += TEXT_TABLE[b]
else:
s += ('<0x%02x>' % b)
return s
class Item:
def __init__(self, index, count):
self.index = index
self.count = count
def get_name(self):
if self.index in ITEMS:
return ITEMS[self.index]
elif self.index >= 196 and self.index <= 200:
return 'HM%d' % (self.index - 195)
elif self.index >= 201:
return 'TM%d' % (self.index - 200)
else:
return '<glitch item 0x{0:02x}>'.format(self.index)
def __repr__(self):
return '{0} * {1}'.format(self.get_name(), self.count)
class ItemList:
def __init__(self, data):
count = data[0]
self.entries = []
for x in range(count):
index = data[1 + (x * 2)]
count = data[1 + (x * 2 + 1)]
self.entries.append(Item(index, count))
class PokemonData:
def __init__(self, data):
species = data[0]
self.species = POKEMON_SPECIES.get(species, 'MissingNo. 0x{0:02X}'.format(species))
self.current_hp = (data[1] << 8) | data[2]
self.display_level = data[3] #Not actually used for anything since it's calculated when withdrawing from a box, seems to be 0 in party Pokemon anyway
status_byte = data[4]
#TODO This doesn't look right, also bits 0-2 are supposedly the sleep counter
self.asleep = (status_byte & 4) > 0
self.poisoned = (status_byte & 8) > 0
self.burned = (status_byte & 16) > 0
self.frozen = (status_byte & 32) > 0
self.paralyzed = (status_byte & 64) > 0
#TODO Something wrong is going on here
self.type_1 = TYPES.get(data[5], '<glitch 0x{0:02x}>'.format(data[5]))
self.type_2 = TYPES.get(data[6], '<glitch 0x{0:02x}>'.format(data[6]))
self.catch_rate = data[7] #Also held item
self.moves = data[8:0x0c]
self.ot_id = data[0x0c:0x0e]
self.experience = data[0x0e:0x11]
self.hp_ev = data[0x11:0x13]
self.attack_ev = data[0x13:0x15]
self.defense_ev = data[0x15:0x17]
self.speed_ev = data[0x17:0x19]
self.special_ev = data[0x19:0x1b]
#TODO Confirm this is right
self.attack_iv = (data[0x1b] & 240) >> 4
self.defense_iv = data[0x1b] & 15
self.speed_iv = (data[0x1c] & 240) >> 4
self.speed_iv = data[0x1c] & 15
self.pp_values = data[0x1d:0x21]
if len(data) > 33:
#Only party Pokemon have this data, in boxes it is calculated from base stats / experience / EVs / IVs etc
self.level = data[0x21]
self.hp = (data[0x22] << 8) | data[0x23]
self.attack = data[0x24:0x26]
self.defense = data[0x26:0x28]
self.speed = data[0x28:0x2a]
self.special = data[0x2a:0x2c]
class Pokemon:
def __init__(self, species, data, ot_name, nickname):
self.species = POKEMON_SPECIES.get(species, 'MissingNo. {0:02X}'.format(species))
self.data = PokemonData(data)
self.ot_name = decode_text(ot_name)
self.nickname = decode_text(nickname)
class PokemonList:
def __init__(self, data, is_party):
count = data[0]
species_list = data[1:8] if is_party else data[1:22]
pokemon_data_list = data[8:8+264] if is_party else data[16:16+660]
ot_name_list = data[0x110:0x110+66] if is_party else data[0x2aa:0x2aa+220]
nickname_list = data[0x152:0x152+66] if is_party else data[0x386:0x386+220]
self.entries = []
for x in range(count):
species = species_list[x]
pokemon_data = pokemon_data_list[44*x:44*x+45] if is_party else pokemon_data_list[33*x:33*x+33]
ot_name = ot_name_list[11*x:11*x+11]
nickname = nickname_list[11*x:11*x+11]
pokemon = Pokemon(species, pokemon_data, ot_name, nickname)
self.entries.append(pokemon)
def convert_bcd(data):
digits = []
for byte in data:
high = (byte & 240) >> 4
low = byte & 15
digits.append(high)
digits.append(low)
return int(''.join(map(str, digits)))
with open(sys.argv[1], 'rb') as f:
#Should be hall of fame data somewhere in here? Player name is in bank 1, HoF is in bank 0
f.seek(0x2598)
player_name = f.read(11)
print('Player name: %s' % decode_text(player_name))
pokedex_owned = f.read(19)
pokedex_seen = f.read(19)
#TODO Decode these (too lazy)
print('Pokedex owned: %s' % pokedex_owned)
print('Pokedex seen: %s' % pokedex_seen)
item_list = f.read(42)
print('Items: %s' % ItemList(item_list).entries)
money = f.read(3)
print('Money: %d' % convert_bcd(money))
rival_name = f.read(11)
print('Rival name: %s' % decode_text(rival_name))
options = f.read(1)[0]
print('Battle effects on: %s' % ((options & 128) > 0)) #Bit 7
print('Battle style on: %s' % ('Set' if (options & 64) > 0 else 'Switch')) #Bit 6
#Bit 5 and 4 are sound (options and (32 & 16) I guess)
sound_setting = (options & 48) >> 4
#The latter two donare only in Yellow, also Stereo is called "Earphone1"
print('Sound setting: %s' % {0: 'Mono', 1: 'Stereo', 2: 'Earphone2 (Left ear)', 3: 'Earphone3 (Right ear)'}.get(sound_setting, 'Unknown (%d)' % sound_setting))
#Bit 3 is unused, print options are stored in some unknown place
text_speed_setting = options & 7
print('Text speed setting: %s' % {1: 'Fast', 3: 'Normal', 6: 'Slow'}.get(text_speed_setting, 'Unknown (%d)' % text_speed_setting))
badges = f.read(1)[0]
badges_owned = []
for x in range(8):
if badges & (1 << x):
badges_owned.append(BADGES[x])
print('Badges: {0} ({1})'.format(len(badges_owned), ', '.join(badges_owned)))
#2 bytes go unused here
f.seek(0x2605)
trainer_id = f.read(2)
print('Trainer ID: %d' % ((trainer_id[0] << 8) | trainer_id[1]))
#A lot of unused bytes in between here (like 0x117)
f.seek(0x271c)
pikachu_friendship = f.read()[0]
#This should be 0 in Red/Blue/Green
print('Pikachu friendship: %d' % pikachu_friendship)
f.seek(0x27e6) #aaaaa
pc_item_list = f.read(102)
print('PC item list: %s' % ItemList(pc_item_list).entries)
#unknown what the high 4 bits do
current_box = (f.read()[0] & 15) + 1
print('Current box: %d' % current_box)
#2 bytes unused here
f.seek(0x2850)
casino_coins = f.read(2)
print('Casino coins: %d' % convert_bcd(casino_coins))
object_spawn_flags = f.read(240)
#print('Object spawn flags: %d' % object_spawn_flags)
for i in range(240):
offset = (object_spawn_flags[i] >> 3)
print('Object spawn flag {0}: {1}'.format(i, (offset >> i) & 1))
#https://github.com/kwsch/PKHeX/blob/master/PKHeX.Core/Saves/Substructures/G1OverworldSpawner.cs may have some more info
f.seek(0x29c3)
starter = f.read(1)[0]
print('Starter Pokemon: %s' % POKEMON_SPECIES.get(starter, 'MissingNo. {0:02X}'.format(starter)))
#Daycare data seems to be stored at 0x2cf4
f.seek(0x2ced)
hours_played_lo = f.read(1)[0]
hours_played_hi = f.read(1)[0]
hours_played = (hours_played_hi << 8) | hours_played_lo
minutes_played = f.read(1)[0]
seconds_played = f.read(1)[0]
print('Time played: {0}h {1}m {2}s'.format(hours_played, minutes_played, seconds_played))
f.seek(0x2f2c)
party_data = f.read(404)
#print('Party data: %s' % party_data)
print('Pokemon in party: ')
party = PokemonList(party_data, True)
for party_pokemon in party.entries:
print('\t%s' % party_pokemon.nickname)
print('\t\tSpecies: %s' % party_pokemon.species)
print('\t\tData:')
data = party_pokemon.data
print('\t\t\tSpecies (in data): %s' % data.species)
print('\t\t\tLevel: %d' % data.level)
print('\t\t\tHP: {0}/{1}'.format(party_pokemon.data.current_hp, data.hp))
print('\t\t\tAsleep: %s' % data.asleep)
print('\t\t\tPoisoned: %s' % data.poisoned)
print('\t\t\tBurned: %s' % data.burned)
print('\t\t\tFrozen: %s' % data.frozen)
print('\t\t\tParalyzed: %s' % data.paralyzed)
if data.type_1 == data.type_2:
print('\t\t\tType: %s' % data.type_1)
else:
print('\t\t\tType: {0}/{1}'.format(data.type_1, data.type_2))
print('\t\t\tCatch rate/held item GSC index: %s' % data.catch_rate)
print('\t\t\tMoves:')
for x in range(4):
if data.moves[x] == 0:
break
pp_ups_applied = (data.pp_values[x] & 192) >> 6
if data.moves[x] in MOVES:
max_pp = MOVES.get(data.moves[x], 0)[1]
max_pp += math.ceil(max_pp * 0.2 * pp_ups_applied)
else:
max_pp = '<max + %d PP Ups>' % pp_ups_applied
current_pp = data.pp_values[x] & 63
move_name = MOVES.get(data.moves[x], ['<move 0x{0:02x}>'.format(data.moves[x])])[0]
print('\t\t\t\t{0} ({1}/{2} PP)'.format(move_name, current_pp, max_pp))
print('\t\tOriginal Trainer: %s' % party_pokemon.ot_name)
currentBoxData = f.read(1122)
#TODO Not gonna bother printing all that
#Might verify that box current_box is equal to this though
f.seek(0x2f93)
event_flags = f.read(1)[0]
print('Event flags: %d' % event_flags)
#TODO Decode these (PKHex says EventFlagMax is 0xa00 (2560, 320 * 8))
# public bool[] EventFlags
# {
# get
# {
# if (EventFlagMax < 0)
# return null;#
#
# bool[] Flags = new bool[EventFlagMax];
# for (int i = 0; i < Flags.Length; i++)
# Flags[i] = GetEventFlag(i);
# return Flags;
# }
# set
# {
# if (EventFlagMax < 0)
# return;
# if (value.Length != EventFlagMax)
# return;
# for (int i = 0; i < value.Length; i++)
# SetEventFlag(i, value[i]);
# }
# }
#public bool GetEventFlag(int flagNumber)
# {
# if (flagNumber > EventFlagMax)
# throw new ArgumentException($"Event Flag to get ({flagNumber}) is greater than max ({EventFlagMax}).");
# return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7);
# }
#public bool GetFlag(int offset, int bitIndex)
# {
# bitIndex &= 7; // ensure bit access is 0-7
# return (Data[offset] >> bitIndex & 1) != 0;
# }
#1 byte goes unused here
f.seek(0x3523)
checksum = f.read(1)[0]
print('Checksum: %s' % checksum)
#TODO Add every byte from 0x2598 to 0x3522 inclusive with 8-bit overflow and invert result
f.seek(0x4000)
#For x = 1 to 12: read(1122) = box x data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment