Created
February 8, 2018 04:37
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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