1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
# -*- coding: utf-8 -*-
# url: http://hax.iimarck.us/topic/8/
# for fixing trainer_group_names
import re
trainer_group_pointer_table_address = 0x39999
trianer_group_pointer_table_address_gs = 0x3993E
# TODO: check if "é", ".", "♂", "♀" are okay in the output
trainer_group_names = {
0x01: {"name": "Leader [Falkner]"},
0x02: {"name": "Leader [Whitney]"},
0x03: {"name": "Leader [Bugsy]"},
0x04: {"name": "Leader [Morty]"},
0x05: {"name": "Leader [Pryce]"},
0x06: {"name": "Leader [Jasmine]"},
0x07: {"name": "Leader [Chuck]"},
0x08: {"name": "Leader [Clair]"},
0x09: {"name": "Rival1"},
0x0A: {"name": "Pokémon Prof."},
0x0B: {"name": "Elite Four [Will]"},
0x0C: {"name": "PKMN Trainer [Cal]"},
0x0D: {"name": "Elite Four [Bruno]"},
0x0E: {"name": "Elite Four [Karen]"},
0x0F: {"name": "Elite Four [Koga]"},
0x10: {"name": "Champion"},
0x11: {"name": "Leader [Brock]"},
0x12: {"name": "Leader [Misty]"},
0x13: {"name": "Leader [Lt.Surge]"},
0x14: {"name": "Scientist"},
0x15: {"name": "Leader [Erika]"},
0x16: {"name": "Youngster"},
0x17: {"name": "Schoolboy"},
0x18: {"name": "Bird Keeper"},
0x19: {"name": "Lass"},
0x1A: {"name": "Leader [Janine]"},
0x1B: {"name": "CooltrainerM"},
0x1C: {"name": "CooltrainerF"},
0x1D: {"name": "Beauty"},
0x1E: {"name": "Pokémaniac"},
0x1F: {"name": "GruntM"},
0x20: {"name": "Gentleman"},
0x21: {"name": "Skier"},
0x22: {"name": "Teacher"},
0x23: {"name": "Leader [Sabrina]"},
0x24: {"name": "Bug Catcher"},
0x25: {"name": "Fisher"},
0x26: {"name": "SwimmerM"},
0x27: {"name": "SwimmerF"},
0x28: {"name": "Sailor"},
0x29: {"name": "Super Nerd"},
0x2A: {"name": "Rival2"},
0x2B: {"name": "Guitarist"},
0x2C: {"name": "Hiker"},
0x2D: {"name": "Biker"},
0x2E: {"name": "Leader [Blaine]"},
0x2F: {"name": "Burglar"},
0x30: {"name": "Firebreather"},
0x31: {"name": "Juggler"},
0x32: {"name": "Blackbelt"},
0x33: {"name": "ExecutiveM"},
0x34: {"name": "Psychic"},
0x35: {"name": "Picnicker"},
0x36: {"name": "Camper"},
0x37: {"name": "ExecutiveF"},
0x38: {"name": "Sage"},
0x39: {"name": "Medium"},
0x3A: {"name": "Boarder"},
0x3B: {"name": "PokéfanM"},
0x3C: {"name": "Kimono Girl"},
0x3D: {"name": "Twins"},
0x3E: {"name": "PokéfanF"},
0x3F: {"name": "PKMN Trainer [Red]"},
0x40: {"name": "Leader [Blue]"},
0x41: {"name": "Officer"},
0x42: {"name": "RocketF"},
0x43: {"name": "Mysticalman [Eusine]"}, # crystal only
}
def remove_parentheticals_from_trainer_group_names():
""" Clean up the trainer group names.
"""
i = 0
for (key, value) in trainer_group_names.items():
# remove the brackets and inner contents from each name
newvalue = re.sub(r'\[[^\)]*\]', '', value["name"]).strip()
# clean up some characters
newvalue = newvalue.replace("♀", "F")\
.replace("♂", "M")\
.replace(".", "")\
.replace(" ", "_")\
.replace("é", "e")
# and calculate the address of the first byte of this pointer
trainer_group_names[key] = {"name": newvalue,
"pointer_address": trainer_group_pointer_table_address + (i * 2),
}
i += 1
return trainer_group_names
# remove [Blue] from each trainer group name
remove_parentheticals_from_trainer_group_names()
class TrainerGroupTable:
""" A list of pointers.
"""
def __init__(self):
assert 0x43 in trainer_group_maximums.keys(), "TrainerGroupTable should onyl be created after all the trainers have been found"
self.address = trainer_group_pointer_table_address
self.bank = calculate_bank(trainer_group_pointer_table_address)
self.label = Label(name="TrainerGroupPointerTable", address=self.address, object=self)
self.size = None
self.last_address = None
self.dependencies = None
self.headers = []
self.parse()
# TODO: add this to script_parse_table
#script_parse_table[address : self.last_address] = self
def get_dependencies(self, recompute=False, global_dependencies=set()):
global_dependencies.update(self.headers)
if recompute == True and self.dependencies != None and self.dependencies != []:
return self.dependencies
dependencies = [self.headers]
for header in self.headers:
dependencies += header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
return dependencies
def parse(self):
size = 0
for (key, kvalue) in trainer_group_names.items():
# calculate the location of this trainer group header from its pointer
pointer_bytes_location = kvalue["pointer_address"]
parsed_address = calculate_pointer_from_bytes_at(pointer_bytes_location, bank=self.bank)
trainer_group_names[key]["parsed_address"] = parsed_address
# parse the trainer group header at this location
name = kvalue["name"]
trainer_group_header = TrainerGroupHeader(address=parsed_address, group_id=key, group_name=name)
trainer_group_names[key]["header"] = trainer_group_header
self.headers.append(trainer_group_header)
# keep track of the size of this pointer table
size += 2
self.size = size
self.last_address = self.address + self.size
def to_asm(self):
output = "".join([str("dw "+get_label_for(header.address)+"\n") for header in self.headers])
return output
class TrainerGroupHeader:
"""
A trainer group header is a repeating list of individual trainer headers.
<Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. Used by a few Cooltrainers.
"""
def __init__(self, address=None, group_id=None, group_name=None):
assert address!=None, "TrainerGroupHeader requires an address"
assert group_id!=None, "TrainerGroupHeader requires a group_id"
assert group_name!=None, "TrainerGroupHeader requires a group_name"
self.address = address
self.group_id = group_id
self.group_name = group_name
self.dependencies = None
self.individual_trainer_headers = []
self.label = Label(name=group_name+"TrainerGroupHeader", address=self.address, object=self)
self.parse()
# TODO: add this to script_parse_table
#script_parse_table[address : self.last_address] = self
def get_dependencies(self, recompute=False, global_dependencies=set()):
""" TrainerGroupHeader has no dependencies.
"""
# TODO: possibly include self.individual_trainer_headers
if recompute or self.dependencies == None:
self.dependencies = []
return self.dependencies
def parse(self):
"""
how do i know when there's no more data for this header?
do a global analysis of the rom and figure out the max ids
this wont work for rom hacks of course
see find_trainer_ids_from_scripts
"""
size = 0
current_address = self.address
# create an IndividualTrainerHeader for each id in range(min id, max id + 1)
min_id = min(trainer_group_maximums[self.group_id])
max_id = max(trainer_group_maximums[self.group_id])
for trainer_id in range(min_id, max_id+1):
trainer_header = TrainerHeader(address=current_address, trainer_group_id=self.group_id, trainer_id=trainer_id, parent=self)
current_address += trainer_header.size
size += trainer_header.size
self.last_address = current_address
self.size = size
def to_asm(self):
raise NotImplementedError
output += "
class TrainerHeader:
"""
<Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. Used by a few Cooltrainers.
"""
def __init__(self, address=None, trainer_group_id=None, trainer_id=None, parent=None):
self.parent = parent
self.address = address
self.trainer_group_id = trainer_group_id
self.trainer_id = trainer_id
self.dependencies = []
self.size = None
self.last_address = None
self.parse()
self.label = Label(name=self.make_name(), address=self.address, object=self)
# this shouldn't be added to script_parse_table because
# TrainerGroupHeader covers its address range
def make_name(self):
""" Must occur after parse() is called.
Constructs a name based on self.parent.group_name and self.name.
"""
return self.parent.group_name + "_" + self.name
def get_dependencies(self, recompute=False, global_dependencies=set()):
if recompute or self.dependencies == None:
self.dependencies = []
return self.dependencies
def parse(self):
address = self.address
# figure out how many bytes until 0x50 "@"
jump = how_many_until(chr(0x50), address)
# parse the "@" into the name
self.name = parse_text_at(address, jump+1)
# where is the next byte?
current_address = address + jump + 1
# figure out the pokemon data type
self.data_type = ord(rom[current_address])
current_address += 1
# figure out which partymon parser to use for this trainer header
party_mon_parser = None
for monparser in trainer_party_mon_parsers:
if monparser.id == self.data_type:
party_mon_parser = monparser
break
if party_mon_parser == None:
raise Exception, "no trainer party mon parser found to parse data type " + hex(self.data_type)
self.party_mons = party_mon_parser(address=current_address, group_id=self.trainer_group_id, trainer_id=self.trainer_id, parent=self)
# let's have everything in trainer_party_mon_parsers handle the last $FF
self.size = self.party_mons.size + 1 + len(self.name)
self.last_address = self.party_mons.last_address
def to_asm(self):
output = "db \""+self.name+"\"\n"
output += "; data type\n"
output += "db $%.2x\n"%(self.data_byte)
output += self.party_mons.to_asm()
return output
class TrainerPartyMonParser:
""" Just a generic trainer party mon parser.
Don't use this directly. Only use the child classes.
"""
id = None
dependencies = None
params = []
param_types = None
# could go either way on this one.. TrainerGroupHeader.parse would need to be changed
# so as to not increase current_address by one after reading "data_type"
override_byte_check = True
def __init__(self, address=None, group_id=None, trainer_id=None, parent=None):
self.address = address
self.group_id = group_id
self.trainer_id = trainer_id
self.parent = parent
self.parse()
# pick up the $FF at the end
self.size += 1
self.last_address += 1
parse = Command.parse
def to_asm(self):
output = "; " + ", ".join([param_type["name"] for param_type in self.param_types]) + "\n"
output += "db " + ", ".join([param.to_asm() for (name, param) in self.params.items()])
output += "\n"
output += "db $FF ; end trainer party mons"
return output
class TrainerPartyMonParser0(TrainerPartyMonParser):
""" Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers. """
id = 0
size = 2 + 1
param_types = {
0: {"name": "level", "class": DecimalParam},
1: {"name": "species", "class": PokemonParam},
}
class TrainerPartyMonParser1(TrainerPartyMonParser):
""" Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders."""
id = 1
size = 6 + 1
param_types = {
0: {"name": "level", "class": DecimalParam},
1: {"name": "species", "class": PokemonParam},
2: {"name": "move1", "class": MoveParam},
3: {"name": "move2", "class": MoveParam},
4: {"name": "move3", "class": MoveParam},
5: {"name": "move4", "class": MoveParam},
}
class TrainerPartyMonParser2(TrainerPartyMonParser):
""" Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans. """
id = 2
size = 3 + 1
param_types = {
0: {"name": "level", "class": DecimalParam},
1: {"name": "species", "class": PokemonParam},
2: {"name": "item", "class": ItemLabelByte},
}
class TrainerPartyMonParser3(TrainerPartyMonParser):
""" Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>.
Used by a few Cooltrainers. """
id = 3
size = 7 + 1
param_types = {
0: {"name": "level", "class": DecimalParam},
1: {"name": "species", "class": PokemonParam},
2: {"name": "item", "class": ItemLabelByte},
3: {"name": "move1", "class": MoveParam},
4: {"name": "move2", "class": MoveParam},
5: {"name": "move3", "class": MoveParam},
6: {"name": "move4", "class": MoveParam},
}
trainer_party_mon_parsers = [TrainerPartyMonParser0, TrainerPartyMonParser1, TrainerPartyMonParser2, TrainerPartyMonParser3]
|