· 6 years ago · Mar 26, 2019, 08:04 PM
1# -*- coding: utf-8 -*-
2"""
3utilities to help disassemble pokémon crystal
4"""
5from __future__ import print_function
6from __future__ import absolute_import
7
8import os
9import sys
10import inspect
11import json
12from copy import copy, deepcopy
13import subprocess
14import logging
15
16# for capwords
17import string
18
19# for python2.6
20if not hasattr(json, "dumps"):
21 json.dumps = json.write
22
23spacing = "\t"
24
25lousy_dragon_shrine_hack = [0x18d079, 0x18d0a9, 0x18d061, 0x18d091]
26
27# table of pointers to map groups
28# each map group contains some number of map headers
29map_group_pointer_table = 0x94000
30map_group_count = 26
31map_group_offsets = []
32map_header_byte_size = 9
33second_map_header_byte_size = 12
34
35# event segment sizes
36warp_byte_size = 5
37trigger_byte_size = 8
38signpost_byte_size = 5
39people_event_byte_size = 13
40
41max_texts = 3
42text_count = 0
43texts = []
44
45# these appear outside of quotes (see pokered/extras/pretty_map_headers.py)
46# this doesn't do anything but is still used in TextScript
47constant_abbreviation_bytes = {}
48import pokemontools
49from pokemontools import helpers
50from pokemontools import chars
51from pokemontools import labels
52from pokemontools import pksv
53from pokemontools import romstr
54from pokemontools import pointers
55from pokemontools import interval_map
56from pokemontools import trainers
57from pokemontools import move_constants
58from pokemontools import pokemon_constants
59from pokemontools import item_constants
60from pokemontools import wram
61from pokemontools import exceptions
62
63from pokemontools import addresses
64is_valid_address = addresses.is_valid_address
65
66from pokemontools import old_text_script
67OldTextScript = old_text_script
68
69from pokemontools import configuration
70conf = configuration.Config()
71
72data_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data/pokecrystal/")
73conf.wram = os.path.join(data_path, "wram.asm")
74conf.gbhw = os.path.join(data_path, "gbhw.asm")
75conf.hram = os.path.join(data_path, "hram.asm")
76
77from pokemontools import map_names
78from pokemontools import song_names
79from pokemontools import map_names
80# ---- script_parse_table explanation ----
81# This is an IntervalMap that keeps track of previously parsed scripts, texts
82# and other objects. Anything that has a location in the ROM should be mapped
83# to an interval (a range of addresses) in this structure. Each object that is
84# assigned to an interval should implement attributes or methods like:
85# ATTRIBUTE/METHOD EXPLANATION
86# label what the heck to call the object
87# address where it begins
88# to_asm() spit out asm (not including label)
89# keys are intervals "500..555" of byte addresses for each script
90# last byte is not inclusive(?) really? according to who??
91# this is how to make sure scripts are not recalculated
92script_parse_table = interval_map.IntervalMap()
93
94def is_script_already_parsed_at(address):
95 """looks up whether or not a script is parsed at a certain address"""
96 if script_parse_table[address] == None:
97 return False
98 return True
99
100def script_parse_table_pretty_printer():
101 """helpful debugging output"""
102 for each in script_parse_table.items():
103 logging.info("{0}".format(each))
104
105def map_name_cleaner(input):
106 """generate a valid asm label for a given map name"""
107 return input.replace(":", "").\
108 replace("(", "").\
109 replace(")", "").\
110 replace("'", "").\
111 replace("/", "").\
112 replace(",", "").\
113 replace(".", "").\
114 replace("Pokémon Center", "PokeCenter").\
115 replace("é", "e").\
116 replace("-", "").\
117 replace("Hooh", "HoOh").\
118 replace("hooh", "HoOh").\
119 replace(" ", "")
120
121from pokemontools.romstr import (
122 RomStr,
123 AsmList,
124)
125
126rom = RomStr(None)
127
128def direct_load_rom(filename=None):
129 """loads bytes into memory"""
130 if filename == None:
131 filename = os.path.join(conf.path, "baserom.gbc")
132 global rom
133 file_handler = open(filename, "rb")
134 rom = RomStr(file_handler.read())
135 file_handler.close()
136 return rom
137
138def load_rom(filename=None):
139 """checks that the loaded rom matches the path
140 and then loads the rom if necessary."""
141 global rom
142 if filename == None:
143 filename = os.path.join(conf.path, "baserom.gbc")
144 return direct_load_rom(filename)
145
146def direct_load_asm(filename=None):
147 if filename == None:
148 filename = os.path.join(conf.path, "main.asm")
149 """returns asm source code (AsmList) from a file"""
150 global asm
151 asm = open(filename, "r").read().split('\n')
152 asm = pokemontools.romstr.AsmList(asm)
153 return asm
154
155def load_asm(filename=None):
156 """returns asm source code (AsmList) from a file (uses a global)"""
157 global asm
158 if filename == None:
159 filename = os.path.join(conf.path, "main.asm")
160 asm = direct_load_asm(filename=filename)
161 return asm
162
163
164def rom_interval(offset, length, strings=True, debug=True):
165 """returns hex values for the rom starting at offset until offset+length"""
166 global rom
167 return rom.interval(offset, length, strings=strings, debug=debug)
168
169def rom_until(offset, byte, strings=True, debug=True):
170 """returns hex values from rom starting at offset until the given byte"""
171 global rom
172 return rom.until(offset, byte, strings=strings, debug=debug)
173
174def how_many_until(byte, starting, rom):
175 index = rom.find(byte, starting)
176 return index - starting
177
178def load_map_group_offsets(map_group_pointer_table, map_group_count):
179 """reads the map group table for the list of pointers"""
180 global rom
181 map_group_offsets = [] # otherwise this method can only be used once
182 data = rom.interval(map_group_pointer_table, map_group_count*2, strings=False)
183 data = pokemontools.helpers.grouper(data)
184 for pointer_parts in data:
185 pointer = pointer_parts[0] + (pointer_parts[1] << 8)
186 offset = pointer - 0x4000 + map_group_pointer_table
187 map_group_offsets.append(offset)
188 return map_group_offsets
189
190from pokemontools.pointers import (
191 calculate_bank,
192 calculate_pointer,
193)
194
195def calculate_pointer_from_bytes_at(address, bank=False):
196 """calculates a pointer from 2 bytes at a location
197 or 3-byte pointer [bank][2-byte pointer] if bank=True"""
198 if bank == True:
199 bank = ord(rom[address])
200 address += 1
201 elif bank == False or bank == None:
202 bank = pokemontools.pointers.calculate_bank(address)
203 elif bank == "reverse" or bank == "reversed":
204 bank = ord(rom[address+2])
205 elif type(bank) == int:
206 pass
207 else:
208 raise Exception("bad bank given to calculate_pointer_from_bytes_at")
209 byte1 = ord(rom[address])
210 byte2 = ord(rom[address+1])
211 temp = byte1 + (byte2 << 8)
212 if temp == 0:
213 return None
214 return pokemontools.pointers.calculate_pointer(temp, bank)
215
216def clean_up_long_info(long_info):
217 """cleans up some data from parse_script_engine_script_at formatting issues"""
218 long_info = str(long_info)
219 # get rid of the first newline
220 if long_info[0] == "\n":
221 long_info = long_info[1:]
222 # get rid of the last newline and any leftover space
223 if long_info.count("\n") > 0:
224 if long_info[long_info.rindex("\n")+1:].isspace():
225 long_info = long_info[:long_info.rindex("\n")]
226 # remove spaces+hash from the front of each line
227 new_lines = []
228 for line in long_info.split("\n"):
229 line = line.strip()
230 if line[0] == "#":
231 line = line[1:]
232 new_lines.append(line)
233 long_info = "\n".join(new_lines)
234 return long_info
235
236def get_pokemon_constant_by_id(id):
237 if id == 0:
238 return None
239 else:
240 return pokemon_constants.pokemon_constants[id]
241
242def command_debug_information(command_byte=None, map_group=None, map_id=None, address=0, info=None, long_info=None, pksv_name=None):
243 "used to help debug in parse_script_engine_script_at"
244 info1 = "parsing command byte " + hex(command_byte) + " for map " + \
245 str(map_group) + "." + str(map_id) + " at " + hex(address)
246 info1 += " pksv: " + str(pksv_name)
247 #info1 += " info: " + str(info)
248 #info1 += " long_info: " + long_info
249 return info1
250
251all_texts = []
252class TextScript(object):
253 """
254 A text is a sequence of bytes (and sometimes commands). It's not the same
255 thing as a Script. The bytes are translated into characters based on the
256 lookup table (see chars.py). The in-text commands are for including values
257 from RAM, playing sound, etc.
258
259 see: http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText
260 """
261 base_label = "UnknownText_"
262 def __init__(self, address, map_group=None, map_id=None, debug=False, label=None, force=False, show=None, script_parse_table=None, text_command_classes=None):
263 self.text_command_classes = text_command_classes
264 self.script_parse_table = script_parse_table
265
266 self.address = address
267 # $91, $84, $82, $54, $8c
268 # 0x19768c is a a weird problem?
269 if address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]:
270 return None
271 self.map_group, self.map_id, self.debug = map_group, map_id, debug
272 self.dependencies = None
273 self.commands = None
274 self.force = force
275
276 if is_script_already_parsed_at(address) and not force:
277 raise Exception("TextScript already parsed at "+hex(address))
278
279 if not label:
280 label = self.base_label + hex(address)
281 self.label = Label(name=label, address=address, object=self)
282
283 self.parse()
284
285 def is_valid(self):
286 return not (self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c])
287
288 # hmm this looks exactly like Script.get_dependencies (which makes sense..)
289 def get_dependencies(self, recompute=False, global_dependencies=set()):
290 if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]:
291 return []
292
293 if self.dependencies != None and not recompute:
294 global_dependencies.update(self.dependencies)
295 return self.dependencies
296
297 dependencies = []
298
299 for command in self.commands:
300 deps = command.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
301 dependencies.extend(deps)
302
303 self.dependencies = dependencies
304 return self.dependencies
305
306 # this is almost an exact copy of Script.parse
307 # with the exception of using text_command_classes instead of command_classes
308 def parse(self):
309 if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]:
310 return None
311
312 text_command_classes = self.text_command_classes
313 script_parse_table = self.script_parse_table
314 current_address = copy(self.address)
315 start_address = copy(current_address)
316
317 # don't clutter up my screen
318 if self.debug:
319 logging.debug(
320 "NewTextScript.parse address={address} map_group={map_group} map_id={map_id}"
321 .format(
322 address=hex(self.address),
323 map_group=str(self.map_group),
324 map_id=self.map_id,
325 )
326 )
327
328 # load up the rom if it hasn't been loaded already
329 rom = load_rom()
330
331 # in the event that the script parsing fails.. it would be nice to leave evidence
332 script_parse_table[start_address:start_address+1] = "incomplete NewTextScript.parse"
333
334 # start with a blank script
335 commands = []
336
337 # use this to control the while loop
338 end = False
339
340 # for each command found...
341 while not end:
342 # get the current scripting byte
343 cur_byte = ord(rom[current_address])
344
345 # reset the command class (last command was probably different)
346 scripting_command_class = None
347
348 # match the command id byte to a scripting command class like MainText
349 for class_ in text_command_classes:
350 if class_[1].id == cur_byte:
351 scripting_command_class = class_[1]
352
353 if self.address == 0x9c00e and self.debug:
354 if current_address > 0x9c087:
355 logging.debug("self.commands is: {commands}".format(commands=commands))
356 for num in [0, 1]:
357 logging.debug(
358 "command {id} address={address} last_address={last}"
359 .format(
360 id=str(num),
361 address=hex(commands[num].address),
362 last=hex(commands[num].last_address),
363 )
364 )
365 raise Exception("going beyond the bounds for this text script")
366
367 # no matching command found
368 if scripting_command_class == None:
369 raise Exception("unable to parse text command $%.2x in the text script at %s at %s" % (cur_byte, hex(start_address), hex(current_address)))
370
371 # create an instance of the command class and let it parse its parameter bytes
372 cls = scripting_command_class(address=current_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=self.force)
373
374 if self.debug:
375 logging.debug(cls.to_asm())
376
377 # store it in this script object
378 commands.append(cls)
379
380 # certain commands will end the scripting engine
381 end = cls.end
382
383 # skip past the command's parameter bytes to go to the next command
384 #current_address += cls.size
385 current_address = cls.last_address
386
387 # last byte belonging to script is last byte of last command,
388 # or the last byte of the last command's last parameter
389 # (actually i think this might be the next byte after??)
390 self.last_address = current_address
391
392 if self.debug:
393 logging.debug("cls.address is {0}".format(hex(cls.address)))
394 logging.debug("cls.size is {0}".format(hex(cls.size)))
395 logging.debug("cls.last_address is {0}".format(hex(cls.last_address)))
396 logging.debug("self.last_address is {0}".format(hex(self.last_address)))
397
398 if self.last_address != (cls.address + cls.size):
399 raise exceptions.TextScriptException(
400 "the last address should equal the last command's (address + size)"
401 )
402
403 if self.last_address != cls.last_address:
404 raise exceptions.TextScriptException(
405 "the last address of the TextScript should be the last_address of its last command"
406 )
407
408 # just some debugging..
409 if self.debug:
410 last_address = self.last_address
411 logging.debug("TextScript last_address == {0}".format(hex(last_address)))
412 #assert last_address != 0x5db06, "TextScript.parse somehow has a text with a last_address of 0x5db06 instead of 0x5db07"
413
414 # store the script in the global table/map thing
415 script_parse_table[start_address:current_address] = self
416 all_texts.append(self)
417
418 if self.debug:
419 asm_output = "\n".join([command.to_asm() for command in commands])
420 logging.debug("asm_output is:\n{0}".format(asm_output))
421
422 # store the script
423 self.commands = commands
424
425 return commands
426
427 def to_asm(self):
428 if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]:
429 return None
430
431 asm_output = "\n".join([command.to_asm() for command in self.commands])
432 return asm_output
433
434def parse_text_engine_script_at(address, map_group=None, map_id=None, debug=True, show=True, force=False):
435 """parses a text-engine script ("in-text scripts")
436 http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText
437 see parse_text_at2, parse_text_at, and process_00_subcommands
438 """
439 if is_script_already_parsed_at(address) and not force:
440 return script_parse_table[address]
441 return TextScript(address, map_group=map_group, map_id=map_id, debug=debug, show=show, force=force, script_parse_table=script_parse_table, text_command_classes=text_command_classes)
442
443def find_text_addresses():
444 """returns a list of text pointers
445 useful for testing parse_text_engine_script_at"""
446 return TextScript.find_addresses()
447
448class EncodedText(object):
449 """a sequence of bytes that, when decoded, represent readable text
450 based on the chars table from preprocessor.py and other places"""
451 base_label = "UnknownRawText_"
452
453 def __init__(self, address, bank=None, map_group=None, map_id=None, debug=True, label=None):
454 self.address = address
455 if bank:
456 self.bank = bank
457 else:
458 self.bank = pokemontools.pointers.calculate_bank(address)
459 self.map_group, self.map_id, self.debug = map_group, map_id, debug
460 if not label:
461 label = self.base_label + hex(address)
462 self.label = Label(name=label, address=address, object=self)
463 self.dependencies = None
464 self.parse()
465 script_parse_table[self.address : self.last_address] = self
466
467 def get_dependencies(self, recompute=False, global_dependencies=set()):
468 return []
469
470 def parse(self):
471 offset = self.address
472
473 # read until $57, $50 or $58
474 jump57 = how_many_until(chr(0x57), offset, rom)
475 jump50 = how_many_until(chr(0x50), offset, rom)
476 jump58 = how_many_until(chr(0x58), offset, rom)
477
478 # whichever command comes first
479 jump = min([jump57, jump50, jump58])
480
481 end_address = offset + jump # we want the address before $57
482
483 text = parse_text_at2(offset, end_address-offset, debug=self.debug)
484
485 if jump == jump50:
486 text += "@"
487
488 self.text = text
489
490 self.last_address = self.end_address = end_address
491
492 def to_asm(self):
493 return "\""+self.text+"\""
494
495 @staticmethod
496 def process_00_subcommands(start_address, end_address, debug=True):
497 """split this text up into multiple lines
498 based on subcommands ending each line"""
499 if debug:
500 logging.debug(
501 "process_00_subcommands({start}, {end})"
502 .format(
503 start=hex(start_address),
504 end=hex(end_address),
505 )
506 )
507 lines = {}
508 subsection = rom[start_address:end_address]
509
510 line_count = 0
511 current_line = []
512 for pbyte in subsection:
513 byte = ord(pbyte)
514 current_line.append(byte)
515 if byte == 0x4f or byte == 0x51 or byte == 0x55:
516 lines[line_count] = current_line
517 current_line = []
518 line_count += 1
519
520 # don't forget the last line
521 lines[line_count] = current_line
522 line_count += 1
523 return lines
524
525 @staticmethod
526 def from_bytes(bytes, debug=True, japanese=False):
527 """assembles a string based on bytes looked up in the chars table"""
528 line = ""
529 if japanese: charset = chars.jap_chars
530 else: charset = chars.chars
531 for byte in bytes:
532 if type(byte) != int:
533 byte = ord(byte)
534 if byte in charset.keys():
535 line += charset[byte]
536 elif debug:
537 logging.debug("byte not known: {0}".format(hex(byte)))
538 return line
539
540 @staticmethod
541 def parse_text_at(address, count=10, debug=True, japanese=False):
542 """returns a string of text from an address
543 this does not handle text commands"""
544 output = ""
545 commands = process_00_subcommands(address, address+count, debug=debug)
546 for (line_id, line) in commands.items():
547 output += parse_text_from_bytes(line, debug=debug, japanese=japanese)
548 texts.append([address, output])
549 return output
550
551
552def process_00_subcommands(start_address, end_address, debug=True):
553 """split this text up into multiple lines
554 based on subcommands ending each line"""
555 return EncodedText.process_00_subcommands(start_address, end_address, debug=debug)
556
557def parse_text_from_bytes(bytes, debug=True, japanese=False):
558 """assembles a string based on bytes looked up in the chars table"""
559 return EncodedText.from_bytes(bytes, debug=debug, japanese=japanese)
560
561def parse_text_at(address, count=10, debug=True):
562 """returns a list of bytes from an address
563 see parse_text_at2 for pretty printing"""
564 return parse_text_from_bytes(rom.interval(address, count, strings=False), debug=debug)
565
566def parse_text_at2(address, count=10, debug=True, japanese=False):
567 """returns a string of text from an address
568 this does not handle text commands"""
569 return EncodedText.parse_text_at(address, count, debug=debug, japanese=japanese)
570
571def parse_text_at3(address, map_group=None, map_id=None, debug=False):
572 deh = script_parse_table[address]
573 if deh:
574 return deh
575 else:
576 text = TextScript(address, map_group=map_group, map_id=map_id, debug=debug, script_parse_table=script_parse_table, text_command_classes=text_command_classes)
577 if text.is_valid():
578 return text
579 else:
580 return None
581
582def rom_text_at(address, count=10):
583 """prints out raw text from the ROM
584 like for 0x112110"""
585 return "".join([chr(x) for x in rom.interval(address, count, strings=False)])
586
587def get_map_constant_label(map_group=None, map_id=None, map_internal_ids=None):
588 """returns PALLET_TOWN for some map group/id pair"""
589 if map_group == None:
590 raise Exception("need map_group")
591 if map_id == None:
592 raise Exception("need map_id")
593
594 for (id, each) in map_internal_ids.items():
595 if each["map_group"] == map_group and each["map_id"] == map_id:
596 return each["label"]
597 return None
598
599def get_map_constant_label_by_id(global_id, map_internal_ids):
600 """returns a map constant label for a particular map id"""
601 return map_internal_ids[global_id]["label"]
602
603def get_id_for_map_constant_label(label):
604 """returns some global id for a given map constant label
605 PALLET_TOWN = 1, for instance."""
606 global map_internal_ids
607 for (id, each) in map_internal_ids.items():
608 if each["label"] == label:
609 return id
610 return None
611
612def generate_map_constant_labels():
613 """generates the global for this script
614 mapping ids to map groups/ids/labels"""
615 global map_internal_ids
616 map_internal_ids = {}
617 i = 0
618 for map_group in map_names.keys():
619 for map_id in map_names[map_group].keys():
620 if map_id == "offset": continue
621 cmap = map_names[map_group][map_id]
622 name = cmap["name"]
623 name = name.replace("Pokémon Center", "PokeCenter").\
624 replace(" ", "_").\
625 replace("-", "_").\
626 replace("é", "e")
627 constant_label = map_name_cleaner(name).upper()
628 map_internal_ids[i] = {"label": constant_label,
629 "map_id": map_id,
630 "map_group": map_group}
631 i += 1
632 return map_internal_ids
633
634# see generate_map_constant_labels() later
635def generate_map_constants():
636 """generates content for constants.asm
637 this will generate two macros: GROUP and MAP"""
638 global map_internal_ids
639 if map_internal_ids == None or map_internal_ids == {}:
640 generate_map_constant_labels()
641 globals, groups, maps = "", "", ""
642 for (id, each) in map_internal_ids.items():
643 label = each["label"].replace("-", "_").replace("é", "e").upper()
644
645 groups += "GROUP_"+ label + " EQU $%.2x" % (each["map_group"])
646 groups += "\n"
647 maps += "MAP_"+ label + " EQU $%.2x" % (each["map_id"])
648 maps += "\n"
649 globals += label + " EQU $%.2x" % (id)
650 globals += "\n"
651 #for multi-byte constants:
652 #print each["label"] + " EQUS \"$%.2x,$%.2x\"" % (each["map_group"], each["map_id"])
653 logging.debug("globals: {0}".format(globals))
654 logging.debug("groups: {0}".format(groups))
655 logging.debug("maps: {0}".format(maps))
656
657def generate_map_constants_dimensions():
658 """
659 Generate _WIDTH and _HEIGHT properties.
660 """
661 global map_internal_ids
662 output = ""
663 if map_internal_ids == None or map_internal_ids == {}:
664 generate_map_constant_labels()
665 for (id, each) in map_internal_ids.items():
666 map_group = each["map_group"]
667 map_id = each["map_id"]
668 label = each["label"].replace("-", "_").replace("é", "e").upper()
669 output += label + "_HEIGHT EQU %d\n" % (map_names[map_group][map_id]["header_new"].second_map_header.height.byte)
670 output += label + "_WIDTH EQU %d\n" % (map_names[map_group][map_id]["header_new"].second_map_header.width.byte)
671 return output
672
673def transform_wildmons(asm, map_internal_ids):
674 """
675 Converts a wildmons section to use map constants.
676 input: wildmons text.
677 """
678 asmlines = asm.split("\n")
679 returnlines = []
680 for line in asmlines:
681 if "; " in line and not ("day" in line or "morn" in line or "nite" in line or "0x" in line or "encounter" in line) \
682 and line != "" and line.split("; ")[0] != "":
683 map_group = int(line.split("\tdb ")[1].split(",")[0].replace("$", "0x"), base=16)
684 map_id = int(line.split("\tdb ")[1].split(",")[1].replace("$", "0x").split("; ")[0], base=16)
685 label = get_map_constant_label(map_group=map_group, map_id=map_id, map_internal_ids=map_internal_ids)
686 returnlines.append("\tdb GROUP_"+label+", MAP_"+label) #+" ; " + line.split(";")[1])
687 else:
688 returnlines.append(line)
689 return "\n".join(returnlines)
690
691def parse_script_asm_at(*args, **kwargs):
692 # XXX TODO
693 logging.debug("TODO: parse_script_asm_at")
694 return None
695
696def find_all_text_pointers_in_script_engine_script(script, bank=None, debug=False):
697 """returns a list of text pointers
698 based on each script-engine script command"""
699 # TODO: recursively follow any jumps in the script
700 if script == None:
701 return []
702 addresses = set()
703 for (k, command) in enumerate(script.commands):
704 if debug:
705 logging.debug("command is: {0}".format(command))
706 if command.id == 0x4B:
707 addresses.add(command.params[0].parsed_address)
708 elif command.id == 0x4C:
709 addresses.add(command.params[0].parsed_address)
710 elif command.id == 0x51:
711 addresses.add(command.params[0].parsed_address)
712 elif command.id == 0x53:
713 addresses.add(command.params[0].parsed_address)
714 elif command.id == 0x64:
715 addresses.add(command.params[0].parsed_address)
716 addresses.add(command.params[1].parsed_address)
717 return addresses
718
719def translate_command_byte(crystal=None, gold=None):
720 """takes a command byte from either crystal or gold
721 returns the command byte in the other (non-given) game
722
723 The new commands are values 0x52 and 0x9F. This means:
724 Crystal's 0x00–0x51 correspond to Gold's 0x00–0x51
725 Crystal's 0x53–0x9E correspond to Gold's 0x52–0x9D
726 Crystal's 0xA0–0xA5 correspond to Gold's 0x9E–0xA3
727
728 see: http://www.pokecommunity.com/showpost.php?p=4347261
729 """
730 if crystal != None: # convert to gold
731 if crystal <= 0x51: return crystal
732 if crystal == 0x52: return None
733 if 0x53 <= crystal <= 0x9E: return crystal-1
734 if crystal == 0x9F: return None
735 if 0xA0 <= crystal <= 0xA5: return crystal-2
736 if crystal > 0xA5:
737 raise Exception("dunno yet if crystal has new insertions after crystal:0xA5 (gold:0xA3)")
738 elif gold != None: # convert to crystal
739 if gold <= 0x51: return gold
740 if 0x52 <= gold <= 0x9D: return gold+1
741 if 0x9E <= gold <= 0xA3: return gold+2
742 if gold > 0xA3:
743 raise Exception("dunno yet if crystal has new insertions after gold:0xA3 (crystal:0xA5)")
744 else:
745 raise Exception("translate_command_byte needs either a crystal or gold command")
746
747class SingleByteParam():
748 """or SingleByte(CommandParam)"""
749 size = 1
750 should_be_decimal = False
751 byte_type = "db"
752
753 def __init__(self, *args, **kwargs):
754 for (key, value) in kwargs.items():
755 setattr(self, key, value)
756 # check address
757 if not hasattr(self, "address"):
758 raise Exception("an address is a requirement")
759 elif self.address == None:
760 raise Exception("address must not be None")
761 elif not is_valid_address(self.address):
762 raise Exception("address must be valid")
763 # check size
764 if not hasattr(self, "size") or self.size == None:
765 raise Exception("size is probably 1?")
766 # parse bytes from ROM
767 self.parse()
768
769 def parse(self): self.byte = ord(rom[self.address])
770
771 def get_dependencies(self, recompute=False, global_dependencies=set()):
772 return []
773
774 def to_asm(self):
775 if not self.should_be_decimal:
776 return hex(self.byte).replace("0x", "$")
777 else:
778 return str(self.byte)
779
780 @staticmethod
781 def from_asm(value):
782 return value
783
784class DollarSignByte(SingleByteParam):
785 def to_asm(self):
786 return hex(self.byte).replace("0x", "$")
787HexByte=DollarSignByte
788
789class ItemLabelByte(DollarSignByte):
790 def to_asm(self):
791 label = item_constants.find_item_label_by_id(self.byte)
792 if label:
793 return label
794 elif not label:
795 return DollarSignByte.to_asm(self)
796
797
798class DecimalParam(SingleByteParam):
799 should_be_decimal = True
800
801
802class MultiByteParam():
803 """or MultiByte(CommandParam)"""
804 size = 2
805 should_be_decimal = False
806 byte_type = "dw"
807
808 def __init__(self, *args, **kwargs):
809 self.prefix = "$" # default.. feel free to set 0x in kwargs
810 for (key, value) in kwargs.items():
811 setattr(self, key, value)
812 # check address
813 if not hasattr(self, "address") or self.address == None:
814 raise Exception("an address is a requirement")
815 elif not is_valid_address(self.address):
816 raise Exception("address must be valid")
817 # check size
818 if not hasattr(self, "size") or self.size == None:
819 raise Exception("don't know how many bytes to read (size)")
820 self.parse()
821
822 def parse(self):
823 self.bytes = rom.interval(self.address, self.size, strings=False)
824 self.parsed_number = self.bytes[0] + (self.bytes[1] << 8)
825 if hasattr(self, "bank"):
826 self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
827 else:
828 self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=None)
829
830 def get_dependencies(self, recompute=False, global_dependencies=set()):
831 return []
832
833 # you won't actually use this to_asm because it's too generic
834 #def to_asm(self): return ", ".join([(self.prefix+"%.2x")%x for x in self.bytes])
835 def to_asm(self):
836 if not self.should_be_decimal:
837 return self.prefix+"".join([("%.2x")%x for x in reversed(self.bytes)])
838 elif self.should_be_decimal:
839 decimal = int("0x"+"".join([("%.2x")%x for x in reversed(self.bytes)]), 16)
840 return str(decimal)
841
842 @staticmethod
843 def from_asm(value):
844 return value
845
846
847class PointerLabelParam(MultiByteParam):
848 # default size is 2 bytes
849 default_size = 2
850 size = 2
851 # default is to not parse out a bank
852 bank = False
853 force = False
854 debug = False
855
856 def __init__(self, *args, **kwargs):
857 self.dependencies = None
858 # bank can be overriden
859 if "bank" in kwargs.keys():
860 if kwargs["bank"] != False and kwargs["bank"] != None and kwargs["bank"] in [True, "reverse"]:
861 self.given_bank = kwargs["bank"]
862 #if kwargs["bank"] not in [None, False, True, "reverse"]:
863 # raise Exception("bank cannot be: " + str(kwargs["bank"]))
864 if self.size > 3:
865 raise Exception("param size is too large")
866 # continue instantiation.. self.bank will be set down the road
867 MultiByteParam.__init__(self, *args, **kwargs)
868
869 def parse(self):
870 self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
871 MultiByteParam.parse(self)
872
873 def get_dependencies(self, recompute=False, global_dependencies=set()):
874 dependencies = []
875 if self.parsed_address == self.address:
876 return dependencies
877 if self.dependencies != None and not recompute:
878 global_dependencies.update(self.dependencies)
879 return self.dependencies
880 thing = script_parse_table[self.parsed_address]
881 if thing and thing.address == self.parsed_address and not (thing is self):
882 #if self.debug:
883 # print "parsed address is: " + hex(self.parsed_address) + " with label: " + thing.label.name + " of type: " + str(thing.__class__)
884 dependencies.append(thing)
885 if not thing in global_dependencies:
886 global_dependencies.add(thing)
887 more = thing.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
888 dependencies.extend(more)
889 self.dependencies = dependencies
890 return dependencies
891
892 def to_asm(self):
893 bank = self.bank
894 # we pass bank= for whether or not to include a bank byte when reading
895 #.. it's not related to caddress
896 caddress = None
897 if not (hasattr(self, "parsed_address") and self.parsed_address != None):
898 caddress = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
899 else:
900 caddress = self.parsed_address
901 label = get_label_for(caddress)
902 pointer_part = label # use the label, if it is found
903
904 # check that the label actually points to the right place
905 result = script_parse_table[caddress]
906 if result != None and hasattr(result, "label"):
907 if result.label.name != label:
908 label = None
909 elif result.address != caddress:
910 label = None
911 elif hasattr(result, "keys") and "label" in result.keys():
912 label = result["label"]
913 elif result != None:
914 label = None
915
916 # setup output bytes if the label was not found
917 if not label:
918 if bank == True:
919 lo, hi = self.bytes[1:3]
920 else:
921 lo, hi = self.bytes[0:2]
922 pointer_part = "{0}{1:02x}{2:02x}".format(self.prefix, hi, lo)
923
924 # bank positioning matters!
925 if bank == True or bank == "reverse": # bank, pointer
926 # possibly use BANK(LABEL) if we know the bank
927 if label:
928 bank_part = "BANK({})".format(label)
929 else:
930 if "$" in pointer_part:
931 if 0x4000 <= caddress <= 0x7FFF:
932 #bank_part = "$%.2x" % (pointers.calculate_bank(self.parent.parent.address))
933 bank_part = "1"
934 else:
935 bank_part = "$%.2x" % (pointers.calculate_bank(caddress))
936 else:
937 bank_part = ((self.prefix+"%.2x")%bank)
938 # for labels, expand bank_part at build time
939 if bank in ["reverse", True] and label:
940 return label
941 # return the asm based on the order the bytes were specified to be in
942 elif bank == "reverse": # pointer, bank
943 return pointer_part+", "+bank_part
944 elif bank == True: # bank, pointer
945 return bank_part+", "+pointer_part
946 else:
947 raise Exception("this should never happen")
948 raise Exception("this should never happen")
949 # this next one will either return the label or the raw bytes
950 elif bank == False or bank == None: # pointer
951 return pointer_part # this could be the same as label
952 else:
953 #raise Exception("this should never happen")
954 return pointer_part # probably in the same bank ?
955 raise Exception("this should never happen")
956
957class PointerLabelBeforeBank(PointerLabelParam):
958 size = 3
959 bank = True # bank appears first, see calculate_pointer_from_bytes_at
960 byte_type = 'db'
961
962 @staticmethod
963 def from_asm(value):
964 return 'BANK({0})\n\tdw {0}'.format(value)
965
966class PointerLabelAfterBank(PointerLabelParam):
967 size = 3
968 bank = "reverse" # bank appears last, see calculate_pointer_from_bytes_at
969 byte_type = 'dw'
970
971 @staticmethod
972 def from_asm(value):
973 return '{0}\n\tdb BANK({0})'.format(value)
974
975
976class ScriptPointerLabelParam(PointerLabelParam): pass
977
978
979class ScriptPointerLabelBeforeBank(PointerLabelBeforeBank): pass
980
981
982class ScriptPointerLabelAfterBank(PointerLabelAfterBank): pass
983
984
985def _parse_script_pointer_bytes(self, debug = False):
986 PointerLabelParam.parse(self)
987 if debug:
988 logging.debug(
989 "_parse_script_pointer_bytes - calculating the pointer located at {0}"
990 .format(hex(self.address))
991 )
992 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
993 if address != None and address > 0x4000:
994 if debug:
995 logging.debug(
996 "_parse_script_pointer_bytes - the pointer is: {0}"
997 .format(hex(address))
998 )
999 self.script = parse_script_engine_script_at(address, debug=self.debug, force=self.force, map_group=self.map_group, map_id=self.map_id)
1000ScriptPointerLabelParam.parse = _parse_script_pointer_bytes
1001ScriptPointerLabelBeforeBank.parse = _parse_script_pointer_bytes
1002ScriptPointerLabelAfterBank.parse = _parse_script_pointer_bytes
1003
1004class PointerLabelToScriptPointer(PointerLabelParam):
1005 def parse(self):
1006 PointerLabelParam.parse(self)
1007 address = calculate_pointer_from_bytes_at(self.parsed_address, bank=self.bank)
1008 address2 = calculate_pointer_from_bytes_at(address, bank=True)
1009 self.script = parse_script_engine_script_at(address2, origin=False, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug)
1010
1011
1012class AsmPointerParam(PointerLabelBeforeBank):
1013 def parse(self):
1014 PointerLabelBeforeBank.parse(self)
1015 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) # 3-byte pointer
1016 self.asm = parse_script_asm_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) # might end in some specific way?
1017
1018
1019class PointerToAsmPointerParam(PointerLabelParam):
1020 def parse(self):
1021 PointerLabelParam.parse(self)
1022 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) # 2-byte pointer
1023 address2 = calculate_pointer_from_bytes_at(address, bank="reverse") # maybe not "reverse"?
1024 self.asm = parse_script_asm_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) # might end in some specific way?
1025
1026
1027class RAMAddressParam(MultiByteParam):
1028 def to_asm(self):
1029 address = calculate_pointer_from_bytes_at(self.address, bank=False)
1030 label = get_ram_label(address)
1031 if label:
1032 return label
1033 else:
1034 return "$"+"".join(["%.2x"%x for x in reversed(self.bytes)])+""
1035
1036
1037class MoneyByteParam(MultiByteParam):
1038 size = 3
1039 byte_type = "db"
1040 max_value = 0x0F423F
1041 should_be_decimal = True
1042 def parse(self):
1043 MultiByteParam.parse(self)
1044 # in the rom as xxyyzz
1045 self.x = self.bytes[2]
1046 self.y = self.bytes[1]
1047 self.z = self.bytes[0]
1048 def to_asm(self):
1049 return str(self.x + (self.y << 8) + (self.z << 16))
1050
1051 # this is used by the preprocessor
1052 @staticmethod
1053 def from_asm(value):
1054 # max is 0F423F
1055 # z = 0x0F ; y = 0x42 ; x = 0x3F
1056 # 999999 = x + (y << 8) + (z << 16)
1057
1058 value = int(value)
1059
1060 x = (value & 0x0000FF)
1061 y = (value & 0x00FF00) >> 8
1062 z = (value & 0xFF0000) >> 16
1063
1064 return str(z) + "\ndb "+str(y)+"\ndb "+str(x)
1065
1066def read_money(address, dohex=False):
1067 z = ord(rom[address])
1068 y = ord(rom[address+1])
1069 x = ord(rom[address+2])
1070 answer = x + (y << 8) + (z << 16)
1071 if not dohex:
1072 return answer
1073 else:
1074 return hex(answer)
1075
1076def write_money(money):
1077 value = money
1078 x = (value & 0x0000FF)
1079 y = (value & 0x00FF00) >> 8
1080 z = (value & 0xFF0000) >> 16
1081 return "db "+str(z)+"\ndb "+str(y)+"\ndb "+str(x)
1082
1083class CoinByteParam(MultiByteParam):
1084 size = 2
1085 max_value = 0x270F
1086 should_be_decimal = True
1087
1088
1089class MapGroupParam(SingleByteParam):
1090 def to_asm(self):
1091 map_id = ord(rom[self.address+1])
1092 map_constant_label = get_map_constant_label(map_id=map_id, map_group=self.byte, map_internal_ids=self.map_internal_ids) # like PALLET_TOWN
1093 if map_constant_label == None:
1094 return str(self.byte)
1095 #else: return "GROUP("+map_constant_label+")"
1096 else:
1097 return "GROUP_"+map_constant_label
1098
1099
1100class MapIdParam(SingleByteParam):
1101 def parse(self):
1102 SingleByteParam.parse(self)
1103 self.map_group = ord(rom[self.address-1])
1104
1105 def to_asm(self):
1106 map_group = ord(rom[self.address-1])
1107 map_constant_label = get_map_constant_label(map_id=self.byte, map_group=map_group, map_internal_ids=self.map_internal_ids)
1108 if map_constant_label == None:
1109 return str(self.byte)
1110 #else: return "MAP("+map_constant_label+")"
1111 else:
1112 return "MAP_"+map_constant_label
1113
1114
1115class MapGroupIdParam(MultiByteParam):
1116 def parse(self):
1117 MultiByteParam.parse(self)
1118 self.map_group = self.bytes[0]
1119 self.map_id = self.bytes[1]
1120
1121 def to_asm(self):
1122 map_group = self.map_group
1123 map_id = self.map_id
1124 label = get_map_constant_label(map_group=map_group, map_id=map_id, map_internal_ids=self.map_internal_ids)
1125 return label
1126
1127
1128class PokemonParam(SingleByteParam):
1129 def to_asm(self):
1130 pokemon_constant = get_pokemon_constant_by_id(self.byte)
1131 if pokemon_constant:
1132 return pokemon_constant
1133 else:
1134 return str(self.byte)
1135
1136class PokemonWordParam(MultiByteParam):
1137 def to_asm(self):
1138 pokemon_constant = get_pokemon_constant_by_id(self.parsed_number)
1139 if pokemon_constant:
1140 return pokemon_constant
1141 else:
1142 return str(self.parsed_number)
1143
1144
1145class PointerParamToItemAndLetter(MultiByteParam):
1146 # [2F][2byte pointer to item no + 0x20 bytes letter text]
1147 pass
1148
1149
1150class TrainerIdParam(SingleByteParam):
1151 def to_asm(self):
1152 # find the group id by first finding the param type id
1153 i = 0
1154 foundit = None
1155 for (k, v) in self.parent.param_types.items():
1156 if v["class"] == TrainerGroupParam:
1157 foundit = i
1158 break
1159 i += 1
1160
1161 if foundit == None:
1162 raise Exception("didn't find a TrainerGroupParam in this command??")
1163
1164 # now get the trainer group id
1165 trainer_group_id = self.parent.params[foundit].byte
1166
1167 # check the rule to see whether to use an id or not
1168 if ("uses_numeric_trainer_ids" in trainers.trainer_group_names[trainer_group_id].keys()) or \
1169 (not "trainer_names" in trainers.trainer_group_names[trainer_group_id].keys()):
1170 return str(self.byte)
1171 else:
1172 return trainers.trainer_group_names[trainer_group_id]["trainer_names"][self.byte-1]
1173
1174class TrainerGroupParam(SingleByteParam):
1175 def to_asm(self):
1176 trainer_group_id = self.byte
1177 return trainers.trainer_group_names[trainer_group_id]["constant"]
1178
1179class MoveParam(SingleByteParam):
1180 def to_asm(self):
1181 if self.byte in move_constants.moves.keys():
1182 return move_constants.moves[self.byte]
1183 else:
1184 # this happens for move=0 (no move) in trainer headers
1185 return str(self.byte)
1186
1187class MenuDataPointerParam(PointerLabelParam):
1188 """read menu data at the target site"""
1189
1190
1191string_to_text_texts = []
1192class RawTextPointerLabelParam(PointerLabelParam):
1193 # not sure if these are always to a text script or raw text?
1194 def parse(self):
1195 PointerLabelParam.parse(self)
1196 #bank = pokemontools.pointers.calculate_bank(self.address)
1197 address = calculate_pointer_from_bytes_at(self.address, bank=False)
1198 self.calculated_address = address
1199 #self.text = parse_text_at3(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
1200 #self.text = TextScript(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
1201 self.text = parse_text_engine_script_at(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
1202
1203 def get_dependencies(self, recompute=False, global_dependencies=set()):
1204 global_dependencies.add(self.text)
1205 return [self.text]
1206
1207class EncodedTextLabelParam(PointerLabelParam):
1208 def parse(self):
1209 PointerLabelParam.parse(self)
1210
1211 address = calculate_pointer_from_bytes_at(self.address, bank=False)
1212 self.parsed_address = address
1213 self.text = EncodedText(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
1214
1215 if isinstance(self.text, EncodedText):
1216 string_to_text_texts.append(self.text)
1217
1218 def get_dependencies(self, recompute=False, global_dependencies=set()):
1219 global_dependencies.add(self.text)
1220 return [self.text]
1221
1222class TextPointerLabelParam(PointerLabelParam):
1223 """this is a pointer to a text script"""
1224 bank = False
1225 text = None
1226 def parse(self):
1227 PointerLabelParam.parse(self)
1228 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
1229 if address != None and address != 0:
1230 self.text = parse_text_engine_script_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug)
1231 if not self.text:
1232 self.text = script_parse_table[address]
1233
1234 def get_dependencies(self, recompute=False, global_dependencies=set()):
1235 if self.text:
1236 global_dependencies.add(self.text)
1237 return [self.text]
1238 else:
1239 return []
1240
1241class TextPointerLabelAfterBankParam(PointerLabelAfterBank):
1242 text = None
1243 def parse(self):
1244 PointerLabelAfterBank.parse(self)
1245 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
1246 if address != None and address != 0:
1247 self.text = parse_text_engine_script_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug)
1248 if not self.text:
1249 self.text = script_parse_table[address]
1250
1251 def get_dependencies(self, recompute=False, global_dependencies=set()):
1252 if self.text:
1253 global_dependencies.add(self.text)
1254 return [self.text]
1255 else:
1256 return []
1257
1258class MovementPointerLabelParam(PointerLabelParam):
1259 def parse(self):
1260 PointerLabelParam.parse(self)
1261 if is_script_already_parsed_at(self.parsed_address):
1262 self.movement = script_parse_table[self.parsed_address]
1263 else:
1264 self.movement = ApplyMovementData(self.parsed_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
1265
1266 def get_dependencies(self, recompute=False, global_dependencies=set()):
1267 if hasattr(self, "movement") and self.movement:
1268 global_dependencies.add(self.movement)
1269 return [self.movement] + self.movement.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
1270 else:
1271 raise Exception("MovementPointerLabelParam hasn't been parsed yet")
1272
1273class MapDataPointerParam(PointerLabelParam):
1274 pass
1275
1276class Command(object):
1277 """
1278 Note: when dumping to asm, anything in script_parse_table that directly
1279 inherits Command should not be .to_asm()'d.
1280 """
1281 # use this when the "byte id" doesn't matter
1282 # .. for example, a non-script command doesn't use the "byte id"
1283 override_byte_check = False
1284 is_rgbasm_macro = False
1285 base_label = "UnseenLabel_"
1286
1287 def __init__(self, address=None, *pargs, **kwargs):
1288 """params:
1289 address - where the command starts
1290 force - whether or not to force the script to be parsed (default False)
1291 debug - are we in debug mode? default False
1292 map_group
1293 map_id
1294 """
1295 defaults = {"force": False, "debug": False, "map_group": None, "map_id": None}
1296 if not is_valid_address(address):
1297 raise Exception("address is invalid")
1298 # set up some variables
1299 self.address = address
1300 self.last_address = None
1301 # setup the label based on base_label if available
1302 label = self.base_label + hex(self.address)
1303 self.label = Label(name=label, address=address, object=self)
1304 # params are where this command's byte parameters are stored
1305 self.params = {}
1306 self.dependencies = None
1307 # override default settings
1308 defaults.update(kwargs)
1309 # set everything
1310 for (key, value) in defaults.items():
1311 setattr(self, key, value)
1312 # but also store these kwargs
1313 self.args = defaults
1314 # start parsing this command's parameter bytes
1315 self.parse()
1316
1317 def get_dependencies(self, recompute=False, global_dependencies=set()):
1318 dependencies = []
1319 #if self.dependencies != None and not recompute:
1320 # global_dependencies.update(self.dependencies)
1321 # return self.dependencies
1322 for (key, param) in self.params.items():
1323 if hasattr(param, "get_dependencies") and param != self:
1324 deps = param.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
1325 if deps != None and not self in deps:
1326 dependencies.extend(deps)
1327 self.dependencies = dependencies
1328 return dependencies
1329
1330 def to_asm(self):
1331 # start with the rgbasm macro name for this command
1332 output = ""
1333 #if len(self.macro_name) > 0 and self.macro_name[0].isdigit():
1334 # output += "_"
1335 output += self.macro_name
1336 # return if there are no params
1337 if len(self.param_types.keys()) == 0:
1338 return output
1339 # first one will have no prefixing comma
1340 first = True
1341 # start reading the bytes after the command byte
1342 if not self.override_byte_check:
1343 current_address = self.address+1
1344 else:
1345 current_address = self.address
1346 #output = self.macro_name + ", ".join([param.to_asm() for (key, param) in self.params.items()])
1347 # add each param
1348 for (key, param) in self.params.items():
1349 name = param.name
1350 # the first param shouldn't have ", " prefixed
1351 if first:
1352 output += " "
1353 first = False
1354 # but all other params should
1355 else: output += ", "
1356 # now add the asm-compatible param string
1357 output += param.to_asm()
1358 current_address += param.size
1359 #for param_type in self.param_types:
1360 # name = param_type["name"]
1361 # klass = param_type["klass"]
1362 # # create an instance of this type
1363 # # tell it to begin parsing at this latest byte
1364 # obj = klass(address=current_address)
1365 # # the first param shouldn't have ", " prefixed
1366 # if first: first = False
1367 # # but all other params should
1368 # else: output += ", "
1369 # # now add the asm-compatible param string
1370 # output += obj.to_asm()
1371 # current_address += obj.size
1372 return output
1373
1374 def parse(self):
1375 # id, size (inclusive), param_types
1376 #param_type = {"name": each[1], "class": each[0]}
1377 if not self.override_byte_check:
1378 current_address = self.address+1
1379 else:
1380 current_address = self.address
1381 byte = ord(rom[self.address])
1382 if not self.override_byte_check and (not byte == self.id):
1383 raise Exception("byte ("+hex(byte)+") != self.id ("+hex(self.id)+")")
1384 i = 0
1385 for (key, param_type) in self.param_types.items():
1386 name = param_type["name"]
1387 klass = param_type["class"]
1388 # make an instance of this class, like SingleByteParam()
1389 # or ItemLabelByte.. by making an instance, obj.parse() is called
1390 obj = klass(address=current_address, name=name, parent=self, **dict([(k,v) for (k, v) in self.args.items() if k not in ["parent"]]))
1391 # save this for later
1392 self.params[i] = obj
1393 # increment our counters
1394 current_address += obj.size
1395 i += 1
1396 self.last_address = current_address
1397 return True
1398
1399
1400class GivePoke(Command):
1401 id = 0x2D
1402 macro_name = "givepoke"
1403 size = 4 # minimum
1404 end = False
1405 param_types = {
1406 0: {"name": "pokemon", "class": PokemonParam},
1407 1: {"name": "level", "class": DecimalParam},
1408 2: {"name": "item", "class": ItemLabelByte},
1409 3: {"name": "trainer", "class": DecimalParam},
1410 4: {"name": "trainer_name_pointer", "class": MultiByteParam}, # should probably use TextLabelParam
1411 5: {"name": "pkmn_nickname", "class": MultiByteParam}, # XXX TextLabelParam ?
1412 }
1413 allowed_lengths = [4, 6]
1414
1415 def parse(self):
1416 self.params = {}
1417 byte = ord(rom[self.address])
1418 if not byte == self.id:
1419 raise Exception("this should never happen")
1420 current_address = self.address+1
1421 i = 0
1422 self.size = 1
1423 for (key, param_type) in self.param_types.items():
1424 # stop executing after the 4th byte unless it == 0x1
1425 if i == 4:
1426 logging.debug("self.params[3].byte is: {0}".format(str(self.params[3].byte)))
1427 if i == 4 and self.params[3].byte != 1: break
1428 name = param_type["name"]
1429 klass = param_type["class"]
1430 # make an instance of this class, like SingleByteParam()
1431 # or ItemLabelByte.. by making an instance, obj.parse() is called
1432 obj = klass(address=current_address, name=name)
1433 # save this for later
1434 self.params[i] = obj
1435 # increment our counters
1436 current_address += obj.size
1437 self.size += obj.size
1438 i += 1
1439 self.last_address = current_address
1440 return True
1441
1442class DataByteWordMacro(Command):
1443 """
1444 Only used by the preprocessor.
1445 """
1446
1447 id = None
1448 macro_name = "dbw"
1449 size = 3
1450 override_byte_check = True
1451
1452 param_types = {
1453 0: {"name": "db value", "class": DecimalParam},
1454 1: {"name": "dw value", "class": PointerLabelParam},
1455 }
1456
1457 def __init__(self): pass
1458 def parse(self): pass
1459 def to_asm(self): pass
1460
1461
1462event_flags = None
1463def read_event_flags():
1464 global event_flags
1465 constants = pokemontools.wram.read_constants(os.path.join(conf.path, 'constants.asm'))
1466 event_flags = dict(filter(lambda key_value: key_value[1].startswith('EVENT_'), constants.items()))
1467 return event_flags
1468
1469engine_flags = None
1470def read_engine_flags():
1471 global engine_flags
1472 constants = pokemontools.wram.read_constants(os.path.join(conf.path, 'constants.asm'))
1473 engine_flags = dict(filter(lambda key_value1: key_value1[1].startswith('ENGINE_'), constants.items()))
1474 return engine_flags
1475
1476class EventFlagParam(MultiByteParam):
1477 def to_asm(self):
1478 global event_flags
1479 if event_flags is None:
1480 event_flags = read_event_flags()
1481 return event_flags.get(self.parsed_number) or MultiByteParam.to_asm(self)
1482
1483class EngineFlagParam(MultiByteParam):
1484 def to_asm(self):
1485 global engine_flags
1486 if engine_flags is None:
1487 engine_flags = read_engine_flags()
1488 return engine_flags.get(self.parsed_number) or MultiByteParam.to_asm(self)
1489
1490
1491class MovementCommand(Command):
1492 # the vast majority of movement commands do not end the movement script
1493 end = False
1494
1495 # this is only used for e.g. macros that don't appear as a byte in the ROM
1496 # don't use the override because all movements are specified with a byte
1497 override_byte_check = False
1498
1499 # most commands have size=1 but one or two have a single parameter (gasp)
1500 size = 1
1501
1502 param_types = {}
1503 params = []
1504
1505 # most movement commands won't have any dependencies
1506 # get_dependencies on Command will look at the values of params
1507 # so this doesn't need to be specified by MovementCommand as long as it extends Command
1508 #def get_dependencies(self, recompute=False, global_dependencies=set()):
1509 # return []
1510
1511 def parse(self):
1512 if ord(rom[self.address]) < 0x45:
1513 # this is mostly handled in to_asm
1514 pass
1515 else:
1516 Command.parse(self)
1517
1518 def to_asm(self):
1519 # return "db $%.2x"%(self.byte)
1520 return Command.to_asm(self)
1521
1522class MovementDBCommand(Command):
1523 end = False
1524 macro_name = "db"
1525 override_byte_check = True
1526 id = None
1527 byte = None
1528 size = 1
1529 param_types = {
1530 0: {"name": "db value", "class": SingleByteParam},
1531 }
1532 params = []
1533
1534 def to_asm(self):
1535 asm = Command.to_asm(self)
1536 return asm + " ; movement"
1537
1538# down, up, left, right
1539movement_command_bases = {
1540 0x00: "turn_head",
1541 0x04: "half_step",
1542 0x08: "slow_step", # small_step?
1543 0x0C: "step",
1544 0x10: "big_step", # fast_step?
1545 0x14: "slow_slide_step",
1546 0x18: "slide_step",
1547 0x1C: "fast_slide_step",
1548 0x20: "turn_away",
1549 0x24: "turn_in", # towards?
1550 0x28: "turn_waterfall", # what??
1551 0x2C: "slow_jump_step",
1552 0x30: "jump_step",
1553 0x34: "fast_jump_step",
1554
1555 # tauwasser says the pattern stops at $45 but $38 looks more realistic?
1556 0x3A: "remove_fixed_facing",
1557 0x3B: "fix_facing",
1558 0x3D: "hide_person",
1559 0x3E: "show_person",
1560 0x45: "accelerate_last",
1561 0x46: ["step_sleep", ["duration", DecimalParam]],
1562 0x47: "step_end",
1563 0x49: "remove_person",
1564
1565 # do these next two have any params ??
1566 0x4C: "teleport_from",
1567 0x4D: "teleport_to",
1568
1569 0x4E: "skyfall",
1570 0x4F: "step_wait5",
1571 0x53: "hide_emote",
1572 0x54: "show_emote",
1573 0x55: ["step_shake", ["displacement", DecimalParam]],
1574}
1575
1576# create MovementCommands from movement_command_bases
1577def create_movement_commands(debug=False):
1578 """
1579 Creates MovementCommands from movement_command_bases. This is just a cheap
1580 trick instead of manually defining all of those classes.
1581 """
1582 #movement_command_classes = inspect.getmembers(sys.modules[__name__], \
1583 # lambda obj: inspect.isclass(obj) and \
1584 # issubclass(obj, MovementCommand) and \
1585 # not (obj is MovementCommand))
1586 movement_command_classes2 = []
1587 for (byte, cmd) in movement_command_bases.items():
1588 if type(cmd) == str:
1589 cmd = [cmd]
1590 cmd_name = cmd[0].replace(" ", "_")
1591 params = {"id": byte, "size": 1, "end": byte is 0x47, "macro_name": cmd_name}
1592 params["param_types"] = {}
1593 if len(cmd) > 1:
1594 param_types = cmd[1:]
1595 for (i, each) in enumerate(param_types):
1596 thing = {"name": each[0], "class": each[1]}
1597 params["param_types"][i] = thing
1598 if debug:
1599 logging.debug(
1600 "each is {0} and thing[class] is {1}"
1601 .format(each, str(thing["class"]))
1602 )
1603 params["size"] += thing["class"].size
1604
1605 if byte <= 0x34:
1606 for x in range(0, 4):
1607
1608 direction = None
1609 if x == 0:
1610 direction = "down"
1611 elif x == 1:
1612 direction = "up"
1613 elif x == 2:
1614 direction = "left"
1615 elif x == 3:
1616 direction = "right"
1617 else:
1618 raise Exception("this should never happen")
1619
1620 cmd_name = cmd[0].replace(" ", "_") + "_" + direction
1621 klass_name = cmd_name+"Command"
1622 params["id"] = copy(byte)
1623 params["macro_name"] = cmd_name
1624 klass = type(copy(klass_name), (MovementCommand,), deepcopy(params))
1625 globals()[klass_name] = klass
1626 movement_command_classes2.append(klass)
1627
1628 byte += 1
1629 del cmd_name
1630 del params
1631 del klass_name
1632 else:
1633 klass_name = cmd_name+"Command"
1634 klass = type(klass_name, (MovementCommand,), params)
1635 globals()[klass_name] = klass
1636 movement_command_classes2.append(klass)
1637 # later an individual klass will be instantiated to handle something
1638 return movement_command_classes2
1639
1640movement_command_classes = create_movement_commands()
1641
1642all_movements = []
1643class ApplyMovementData(object):
1644 base_label = "MovementData_"
1645
1646 def __init__(self, address, map_group=None, map_id=None, debug=False, label=None, force=False):
1647 self.address = address
1648 self.map_group = map_group
1649 self.map_id = map_id
1650 self.debug = debug
1651 self.force = force
1652
1653 if not label:
1654 label = self.base_label + hex(address)
1655 self.label = Label(name=label, address=address, object=self)
1656
1657 self.dependencies = []
1658 self.commands = []
1659
1660 self.parse()
1661
1662 # this is almost an exact copy of Script.parse
1663 # with the exception of using text_command_classes instead of command_classes
1664 def parse(self):
1665 global movement_command_classes, script_parse_table
1666 address = self.address
1667
1668 # i feel like checking myself
1669 if not is_valid_address(address):
1670 raise exceptions.AddressException(
1671 "ApplyMovementData.parse must be given a valid address (but got {0})."
1672 .format(hex(address))
1673 )
1674
1675 current_address = copy(self.address)
1676 start_address = copy(current_address)
1677
1678 # don't clutter up my screen
1679 if self.debug:
1680 logging.debug(
1681 "ApplyMovementData.parse address={address} map_group={map_group} map_id={map_id}"
1682 .format(
1683 address=hex(self.address),
1684 map_group=str(self.map_group),
1685 map_id=str(self.map_id),
1686 )
1687 )
1688
1689 # load up the rom if it hasn't been loaded already
1690 rom = load_rom()
1691
1692 # in the event that the script parsing fails.. it would be nice to leave evidence
1693 script_parse_table[start_address:start_address+1] = "incomplete ApplyMovementData.parse"
1694
1695 # start with a blank script
1696 commands = []
1697
1698 # use this to control the while loop
1699 end = False
1700
1701 # for each command found...
1702 while not end:
1703 # get the current scripting byte
1704 cur_byte = ord(rom[current_address])
1705
1706 # reset the command class (last command was probably different)
1707 scripting_command_class = None
1708
1709 # match the command id byte to a scripting command class like "step half"
1710 for class_ in movement_command_classes:
1711 # allow lists of ids
1712 if (type(class_.id) == list and cur_byte in class_.id) \
1713 or class_.id == cur_byte:
1714 scripting_command_class = class_
1715
1716 # temporary fix for applymovement scripts
1717 if ord(rom[current_address]) == 0x47:
1718 end = True
1719
1720 # no matching command found
1721 xyz = None
1722 if scripting_command_class == None:
1723 scripting_command_class = MovementDBCommand
1724 #scripting_command_class = deepcopy(MovementCommand)
1725 #scripting_command_class.id = scripting_command_class.byte = ord(rom[current_address])
1726 #scripting_command_class.macro_name = "db"
1727 #scripting_command_class.size = 1
1728 #scripting_command_class.override_byte_check = True
1729 #scripting_command_class.id = None
1730 #scripting_command_class.param_types = {0: {"name": "db value", "class": DecimalParam}}
1731
1732 xyz = True
1733
1734 # create an instance of the command class and let it parse its parameter bytes
1735 cls = scripting_command_class(address=current_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=self.force)
1736
1737 if self.debug:
1738 logging.debug("cls.to_asm() is: {0}".format(cls.to_asm()))
1739
1740 # store it in this script object
1741 commands.append(cls)
1742
1743 # certain commands will end the movement engine
1744 end = cls.end
1745
1746 # skip past the command's parameter bytes to go to the next command
1747 current_address += cls.size
1748
1749 # last byte belonging to script is last byte of last command,
1750 # or the last byte of the last command's last parameter
1751 # (actually i think this might be the next byte after??)
1752 self.last_address = current_address
1753
1754 # store the script in the global table/map thing
1755 all_movements.append(self)
1756 script_parse_table[start_address:current_address] = self
1757
1758 if self.debug:
1759 asm_output = "\n".join([command.to_asm() for command in commands])
1760 logging.debug("asm_output: {0}".format(asm_output))
1761
1762 # store the script
1763 self.commands = commands
1764 return commands
1765
1766 def to_asm(self):
1767 asm_output = "\n".join([command.to_asm() for command in self.commands])
1768 return asm_output
1769
1770 # TODO: get_dependencies doesn't work if ApplyMovementData uses labels in the future
1771 def get_dependencies(self, recompute=False, global_dependencies=set()):
1772 return []
1773
1774def print_all_movements():
1775 for each in all_movements:
1776 print(each.to_asm())
1777 print("------------------")
1778 print("done")
1779
1780class TextCommand(Command):
1781 # an individual text command will not end it
1782 end = False
1783
1784 # this is only used for e.g. macros that don't appear as a byte in the ROM
1785 # don't use the override because all text commands are specified with a byte
1786 override_byte_check = False
1787
1788 # in the case of text/asm commands, size is unknown until after parsing
1789 # some text commands can specify this upfront but not $0
1790 size = None
1791
1792 param_types = {}
1793 params = []
1794
1795 # most text commands won't have any dependencies
1796 # .. except for that one that points to another location for text
1797 # get_dependencies on Command will look at the values of params
1798 # so this doesn't need to be specified by TextCommand as long as it extends Command
1799 #def get_dependencies(self, recompute=False, global_dependencies=set()):
1800 # return []
1801
1802
1803# this is a regular command in a TextScript for writing text
1804# but unlike other macros that preprocessor.py handles,
1805# the preprocessor-parser is custom and MainText is not
1806# used as a macro by main.asm - however, MainText is
1807# treated as a macro for the sake of parsing the ROM because
1808# it is called with $0. This is very similar to how Script
1809# is parsed and handled. But again, script command macros
1810# are quite different.. preprocessor.py allows some of them
1811# to handle how they should be parsed from main.asm, in
1812# addition to their regular "parse()" method.
1813class MainText(TextCommand):
1814 "Write text. Structure: [00][Text][0x50 (ends code)]"
1815 id = 0x0
1816 macro_name = "do_text"
1817 use_zero = True
1818
1819 def parse(self):
1820 offset = self.address
1821
1822 # the code below assumes we're jumping past a $0 byte
1823 if self.use_zero == False:
1824 offset = offset
1825 else:
1826 offset = offset + 1
1827
1828 # read until $50, $57 or $58 (not sure about $58...)
1829 jump57 = how_many_until(chr(0x57), offset, rom)
1830 jump50 = how_many_until(chr(0x50), offset, rom)
1831 jump58 = how_many_until(chr(0x58), offset, rom)
1832
1833 # pick whichever one comes first
1834 jump = min([jump57, jump50, jump58])
1835
1836 # if $57 appears first then this command is the last in this text script
1837 if jump == jump57 or jump == jump58:
1838 self.end = True
1839
1840 jump += 1
1841
1842 # we want the address after the $57
1843 # ("last_address" is misnamed everywhere)
1844 end_address = offset + jump
1845 self.last_address = self.end_address = end_address
1846
1847 # read the text bytes into a structure
1848 # skip the first offset byte because that's the command byte
1849 self.bytes = rom.interval(offset, jump, strings=False)
1850
1851 # include the original command in the size calculation
1852 self.size = jump
1853
1854 if self.use_zero:
1855 self.last_address = self.address + jump + 1
1856 self.size = self.last_address - self.address
1857
1858 if self.address == 0x9c00e and self.debug:
1859 if self.last_address != 0x9c086:
1860 argparams = {
1861 "self.address": hex(self.address),
1862 "jump": str(jump),
1863 "bytes": str(self.bytes),
1864 "self.size": str(self.size),
1865 "self.last_address": hex(self.last_address),
1866 }
1867
1868 logging.debug(str(argparams))
1869
1870 raise Exception("last_address is wrong for 0x9c00e")
1871
1872 def to_asm(self):
1873 if self.size < 2 or len(self.bytes) < 1:
1874 raise Exception("$0 text command can't end itself with no follow-on bytes")
1875
1876 if self.use_zero:
1877 output = "db $0"
1878 else:
1879 output = ""
1880
1881 # db $0, $57 or db $0, $50 or w/e
1882 if self.size == 2 and len(self.bytes) == 1:
1883 output += ", $%.2x" % (self.bytes[0])
1884 return output
1885
1886 # whether or not quotes are open
1887 in_quotes = False
1888
1889 # whether or not to print "db " next
1890 new_line = False
1891
1892 # whether or not there was a ", " last..
1893 # this is useful outside of quotes
1894 was_comma = False
1895
1896 # has a $50 or $57 been passed yet?
1897 end = False
1898
1899 if not self.use_zero:
1900 new_line = True
1901 was_comma = False
1902
1903 for byte in self.bytes:
1904 if end:
1905 raise Exception("the text ended due to a $50 or $57 but there are more bytes?")
1906
1907 if new_line:
1908 if in_quotes:
1909 raise Exception("can't be in_quotes on a newline")
1910 elif was_comma:
1911 raise Exception("last line's last character can't be a comma")
1912
1913 output += "db "
1914
1915 # $4f, $51 and $55 can end a line
1916 if byte in [0x4f, 0x51, 0x55]:
1917 if new_line:
1918 raise exceptions.TextScriptException("can't have $4f, $51, $55 as the first character on a newline")
1919
1920 if in_quotes:
1921 output += "\", $%.2x\n" % (byte)
1922 elif not in_quotes:
1923 if not was_comma:
1924 output += ", "
1925 output += "$%.2x\n" % (byte)
1926
1927 # reset everything
1928 in_quotes = False
1929 new_line = True
1930 was_comma = False
1931 elif byte == 0x50:
1932 # technically you could have this i guess... db "@"
1933 # but in most situations it will be added to the end of the previous line
1934 #assert not new_line, "can't have $50 or '@' as the first character on a newline in the text at "+hex(self.address)
1935
1936 if in_quotes:
1937 output += "@\"\n"
1938 new_line = True
1939 elif not in_quotes:
1940 if not was_comma and not new_line:
1941 output += ", "
1942 output += "\"@\"\n"
1943
1944 # reset everything
1945 in_quotes = False
1946 new_line = True
1947 was_comma = False
1948 end = True
1949
1950 # self.end should be set in parse or constructor
1951 # so this is very useless here.. but it's a truism i guess
1952 self.end = True
1953 elif byte == 0x57 or byte == 0x58:
1954 # close any quotes
1955 if in_quotes:
1956 output += "\""
1957 was_comma = False
1958
1959 if not was_comma and not new_line:
1960 output += ", "
1961
1962 output += "$%.2x\n" % (byte)
1963
1964 in_quotes = False
1965 new_line = True
1966 was_comma = False
1967 end = True
1968
1969 # dunno if $58 should end a text script or not
1970 # also! self.end should be set in parse not in to_asm
1971 # so this is pretty useless overall...
1972 if byte == 0x58:
1973 self.end = True
1974 elif byte in chars.chars.keys():
1975 # figure out what the character actually is
1976 char = chars.chars[byte]
1977
1978 # oh wait.. quotes isn't a valid character in the first place :(
1979 if char == "\"":
1980 if in_quotes:
1981 output += "\""
1982 in_quotes = False
1983 elif not in_quotes:
1984 if new_line:
1985 output += "\""
1986 elif not new_line:
1987 if not was_comma:
1988 output += ", "
1989 output += "\""
1990 in_quotes = True
1991
1992 # the above if statement is probably never called
1993 else:
1994 if not in_quotes:
1995 if not new_line and not was_comma:
1996 output += ", "
1997 output += "\""
1998 in_quotes = True
1999
2000 output += char
2001
2002 new_line = False
2003 was_comma = False
2004 end = False
2005 else:
2006 # raise Exception("unknown byte in text script ($%.2x)" % (byte))
2007 # just add an unknown byte directly to the text.. what's the worse that can happen?
2008
2009 if in_quotes:
2010 output += "\", $%.2x" % (byte)
2011
2012 in_quotes = False
2013 was_comma = False
2014 new_line = False
2015 elif not in_quotes:
2016 if not was_comma and not new_line:
2017 output += ", "
2018 output += "$%.2x" % (byte)
2019
2020 # reset things
2021 in_quotes = False
2022 new_line = False
2023 was_comma = False
2024
2025 # this shouldn't happen because of the rom_until calls in the parse method
2026 if not end:
2027 raise Exception("ran out of bytes without the script ending? starts at "+hex(self.address))
2028
2029 # last character may or may not be allowed to be a newline?
2030 # Script.to_asm() has command.to_asm()+"\n"
2031 if output[-1] == "\n":
2032 output = output[:-1]
2033
2034 return output
2035
2036class PokedexText(MainText):
2037 use_zero = False
2038
2039class WriteTextFromRAM(TextCommand):
2040 """
2041 Write text from ram. Structure: [01][Ram address (2byte)]
2042 For valid ram addresses see Glossary. This enables use of variable text strings.
2043 """
2044 id = 0x1
2045 macro_name = "text_from_ram"
2046 size = 3
2047 param_types = {
2048 0: {"name": "pointer", "class": MultiByteParam},
2049 }
2050class WriteNumberFromRAM(TextCommand):
2051 """
2052 02 = Write number from ram. Structure: [02][Ram address (2byte)][Byte]
2053
2054 Byte:
2055
2056 Bit5:Bit6:Bit7
2057 1: 1: 1 = PokéDollar| Don’t write zeros
2058 0: 1: 1 = Don’t write zeros
2059 0: 0: 1 = Spaces instead of zeros
2060 0: 0: 0 = Write zeros
2061 0: 1: 0 = Write zeros
2062 1: 0: 0 = PokéDollar
2063 1: 1: 0 = PokéDollar
2064 1: 0: 1 = Spaces instead of zeros| PokéDollar
2065
2066 Number of figures = Byte AND 0x1F *2
2067 No Hex --> Dec Conversio
2068 """
2069 id = 0x2
2070 macro_name = "number_from_ram"
2071 size = 4
2072 param_types = {
2073 0: {"name": "pointer", "class": PointerLabelParam},
2074 1: {"name": "config", "class": HexByte},
2075 }
2076class SetWriteRAMLocation(TextCommand):
2077 "Define new ram address to write to. Structure: [03][Ram address (2byte)]"
2078 id = 0x3
2079 macro_name = "store_at"
2080 size = 3
2081 param_types = {
2082 0: {"name": "ram address", "class": PointerLabelParam},
2083 }
2084class ShowBoxWithValueAt(TextCommand):
2085 "04 = Write a box. Structure: [04][Ram address (2byte)][Y][X]"
2086 id = 0x4
2087 macro_name = "text_box"
2088 size = 5
2089 param_types = {
2090 0: {"name": "ram address", "class": PointerLabelParam},
2091 1: {"name": "y", "class": DecimalParam},
2092 2: {"name": "x", "class": DecimalParam},
2093 }
2094class Populate2ndLineOfTextBoxWithRAMContents(TextCommand):
2095 "05 = New ram address to write to becomes 2nd line of a text box. Structure: [05]"
2096 id = 0x5
2097 macro_name = "text_dunno1"
2098 size = 1
2099class ShowArrowsAndButtonWait(TextCommand):
2100 "06 = Wait for key down + show arrows. Structure: [06]"
2101 id = 0x6
2102 macro_name = "text_waitbutton"
2103 size = 1
2104class Populate2ndLine(TextCommand):
2105 """
2106 07 = New ram address to write to becomes 2nd line of a text box
2107 Textbox + show arrows. Structure: [07]
2108 """
2109 id = 0x7
2110 macro_name = "text_dunno2"
2111 size = 1
2112class TextInlineAsm(TextCommand):
2113 "08 = After the code an ASM script starts. Structure: [08][Script]"
2114 id = 0x8
2115 macro_name = "start_asm"
2116 end = True
2117 size = 1
2118 # TODO: parse the following asm with gbz80disasm
2119class WriteDecimalNumberFromRAM(TextCommand):
2120 """
2121 09 = Write number from rom/ram in decimal. Structure: [09][Ram address/Pointer (2byte)][Byte]
2122 Byte:
2123
2124 Is split: 1. 4 bits = Number of bytes to load. 0 = 3, 1 = 1, 2 = 2
2125 2. 4 bits = Number of figures of displayed number
2126 0 = Don’t care
2127 1 = Don’t care
2128 >=2 = Number
2129 """
2130 id = 0x9
2131 macro_name = "deciram"
2132 size = 4
2133 param_types = {
2134 0: {"name": "pointer?", "class": PointerLabelParam},
2135 1: {"name": "config", "class": HexByte},
2136 }
2137class InterpretDataStream(TextCommand):
2138 """
2139 0A = Interpret Data stream. Structure: [0A]
2140 see: http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke88
2141 """
2142 id = 0xA
2143 macro_name = "interpret_data"
2144 size = 1
2145class Play0thSound(TextCommand):
2146 "0B = Play sound 0x0000. Structure: [0B]"
2147 id = 0xB
2148 sound_num = 0
2149 macro_name = "sound0"
2150 size = 1
2151class LimitedIntrepretDataStream(TextCommand):
2152 """
2153 0C = Interpret Data stream. Structure: [0C][Number of codes to interpret]
2154 For every interpretation there is a“…“ written
2155 """
2156 id = 0xC
2157 macro_name = "limited_interpret_data"
2158 size = 2
2159 param_types = {
2160 0: {"name": "number of codes to interpret", "class": DecimalParam},
2161 }
2162class WaitForKeyDownDisplayArrow(ShowArrowsAndButtonWait):
2163 """
2164 0D = Wait for key down display arrow. Structure: [0D]
2165 """
2166 id = 0xD
2167 macro_name = "waitbutton2"
2168 size = 1
2169class Play9thSound(Play0thSound):
2170 id = 0xE
2171 sound_num = 9
2172 macro_name = "sound0x09"
2173 size = 1
2174class Play1stSound(Play0thSound):
2175 id = 0xF
2176 sound_num = 1
2177 macro_name = "sound0x0F"
2178 size = 1
2179class Play2ndSound(Play0thSound):
2180 id = 0x10
2181 sound_num = 2
2182 macro_name = "sound0x02"
2183 size = 1
2184class Play10thSound(Play0thSound):
2185 id = 0x11
2186 sound_num = 10
2187 macro_name = "sound0x0A"
2188 size = 1
2189class Play45thSound(Play0thSound):
2190 id = 0x12
2191 sound_num = 0x2D
2192 macro_name = "sound0x2D"
2193 size = 1
2194class Play44thSound(Play0thSound):
2195 id = 0x13
2196 sound_num = 0x2C
2197 macro_name = "sound0x2C"
2198 size = 1
2199class DisplayByteFromRAMAt(TextCommand):
2200 """
2201 14 = Display MEMORY. Structure: [14][Byte]
2202
2203 Byte:
2204
2205 00 = MEMORY1
2206 01 = MEMORY2
2207 02 = MEMORY
2208 04 = TEMPMEMORY2
2209 05 = TEMPMEMORY1
2210 """
2211 id = 0x14
2212 macro_name = "show_byte_at"
2213 size = 2
2214 param_types = {
2215 1: {"name": "memory byte id", "class": DecimalParam},
2216 }
2217class WriteCurrentDay(TextCommand):
2218 "15 = Write current day. Structure: [15]"
2219 id = 0x15
2220 macro_name = "current_day"
2221 size = 1
2222class TextJump(TextCommand):
2223 "16 = 3byte pointer to new text follows. Structure: [16][2byte pointer][bank]"
2224 id = 0x16
2225 macro_name = "text_jump"
2226 size = 4
2227 param_types = {
2228 0: {"name": "text", "class": TextPointerLabelAfterBankParam},
2229 }
2230# this is needed because sometimes a script ends with $50 $50
2231class TextEndingCommand(TextCommand):
2232 id = 0x50
2233 macro_name = "db"
2234 override_byte_check = False
2235 size = 1
2236 end = True
2237 def to_asm(self):
2238 return "db $50"
2239
2240text_command_classes = inspect.getmembers(sys.modules[__name__], \
2241 lambda obj: inspect.isclass(obj) and \
2242 issubclass(obj, TextCommand) and \
2243 obj != TextCommand and obj != PokedexText)
2244
2245# byte: [name, [param1 name, param1 type], [param2 name, param2 type], ...]
2246# 0x9E: ["verbosegiveitem", ["item", ItemLabelByte], ["quantity", SingleByteParam]],
2247pksv_crystal_more = {
2248 0x00: ["2call", ["pointer", ScriptPointerLabelParam]],
2249 0x01: ["3call", ["pointer", ScriptPointerLabelBeforeBank]],
2250 0x02: ["2ptcall", ["pointer", RAMAddressParam]],
2251 0x03: ["2jump", ["pointer", ScriptPointerLabelParam]],
2252 0x04: ["3jump", ["pointer", ScriptPointerLabelBeforeBank]],
2253 0x05: ["2ptjump", ["pointer", RAMAddressParam]],
2254 0x06: ["if equal", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]],
2255 0x07: ["if not equal", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]],
2256 0x08: ["iffalse", ["pointer", ScriptPointerLabelParam]],
2257 0x09: ["iftrue", ["pointer", ScriptPointerLabelParam]],
2258 0x0A: ["if greater than", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]],
2259 0x0B: ["if less than", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]],
2260 0x0C: ["jumpstd", ["predefined_script", MultiByteParam]],
2261 0x0D: ["callstd", ["predefined_script", MultiByteParam]],
2262 0x0E: ["3callasm", ["asm", AsmPointerParam]],
2263 0x0F: ["special", ["predefined_script", MultiByteParam]],
2264 0x10: ["2ptcallasm", ["asm", RAMAddressParam]],
2265 # should map_group/map_id be dealt with in some special way in the asm?
2266 0x11: ["checkmaptriggers", ["map_group", SingleByteParam], ["map_id", SingleByteParam]],
2267 0x12: ["domaptrigger", ["map_group", MapGroupParam], ["map_id", MapIdParam], ["trigger_id", SingleByteParam]],
2268 0x13: ["checktriggers"],
2269 0x14: ["dotrigger", ["trigger_id", SingleByteParam]],
2270 0x15: ["writebyte", ["value", SingleByteParam]],
2271 0x16: ["addvar", ["value", SingleByteParam]],
2272 0x17: ["random", ["input", SingleByteParam]],
2273 0x18: ["checkver"],
2274 0x19: ["copybytetovar", ["address", RAMAddressParam]],
2275 0x1A: ["copyvartobyte", ["address", RAMAddressParam]],
2276 0x1B: ["loadvar", ["address", RAMAddressParam], ["value", SingleByteParam]],
2277 0x1C: ["checkcode", ["variable_id", SingleByteParam]],
2278 0x1D: ["writevarcode", ["variable_id", SingleByteParam]],
2279 0x1E: ["writecode", ["variable_id", SingleByteParam], ["value", SingleByteParam]],
2280 0x1F: ["giveitem", ["item", ItemLabelByte], ["quantity", SingleByteParam]],
2281 0x20: ["takeitem", ["item", ItemLabelByte], ["quantity", DecimalParam]],
2282 0x21: ["checkitem", ["item", ItemLabelByte]],
2283 0x22: ["givemoney", ["account", SingleByteParam], ["money", MoneyByteParam]],
2284 0x23: ["takemoney", ["account", SingleByteParam], ["money", MoneyByteParam]],
2285 0x24: ["checkmoney", ["account", SingleByteParam], ["money", MoneyByteParam]],
2286 0x25: ["givecoins", ["coins", CoinByteParam]],
2287 0x26: ["takecoins", ["coins", CoinByteParam]],
2288 0x27: ["checkcoins", ["coins", CoinByteParam]],
2289 # 0x28-0x2A not from pksv
2290 0x28: ["addcellnum", ["person", SingleByteParam]],
2291 0x29: ["delcellnum", ["person", SingleByteParam]],
2292 0x2A: ["checkcellnum", ["person", SingleByteParam]],
2293 # back on track...
2294 0x2B: ["checktime", ["time", SingleByteParam]],
2295 0x2C: ["checkpoke", ["pkmn", PokemonParam]],
2296#0x2D: ["givepoke", ], .... see GivePoke class
2297 0x2E: ["giveegg", ["pkmn", PokemonParam], ["level", DecimalParam]],
2298 0x2F: ["givepokeitem", ["pointer", PointerParamToItemAndLetter]],
2299 0x30: ["checkpokeitem", ["pointer", PointerParamToItemAndLetter]], # not pksv
2300 0x31: ["checkevent", ["event_flag", EventFlagParam]],
2301 0x32: ["clearevent", ["event_flag", EventFlagParam]],
2302 0x33: ["setevent", ["event_flag", EventFlagParam]],
2303 0x34: ["checkflag", ["engine_flag", EngineFlagParam]],
2304 0x35: ["clearflag", ["engine_flag", EngineFlagParam]],
2305 0x36: ["setflag", ["engine_flag", EngineFlagParam]],
2306 0x37: ["wildon"],
2307 0x38: ["wildoff"],
2308 0x39: ["xycompare", ["pointer", MultiByteParam]],
2309 0x3A: ["warpmod", ["warp_id", SingleByteParam], ["map_group", MapGroupParam], ["map_id", MapIdParam]],
2310 0x3B: ["blackoutmod", ["map_group", MapGroupParam], ["map_id", MapIdParam]],
2311 0x3C: ["warp", ["map_group", MapGroupParam], ["map_id", MapIdParam], ["x", SingleByteParam], ["y", SingleByteParam]],
2312 0x3D: ["readmoney", ["account", SingleByteParam], ["memory", SingleByteParam]], # not pksv
2313 0x3E: ["readcoins", ["memory", SingleByteParam]], # not pksv
2314 0x3F: ["RAM2MEM", ["memory", SingleByteParam]], # not pksv
2315 0x40: ["pokenamemem", ["pokemon", PokemonParam], ["memory", SingleByteParam]], # not pksv
2316 0x41: ["itemtotext", ["item", ItemLabelByte], ["memory", SingleByteParam]],
2317 0x42: ["mapnametotext", ["memory", SingleByteParam]], # not pksv
2318 0x43: ["trainertotext", ["trainer_id", TrainerGroupParam], ["trainer_group", TrainerIdParam], ["memory", SingleByteParam]],
2319 0x44: ["stringtotext", ["text_pointer", EncodedTextLabelParam], ["memory", SingleByteParam]],
2320 0x45: ["itemnotify"],
2321 0x46: ["pocketisfull"],
2322 0x47: ["loadfont"],
2323 0x48: ["refreshscreen", ["dummy", SingleByteParam]],
2324 0x49: ["loadmovesprites"],
2325 0x4A: ["loadbytec1ce", ["byte", SingleByteParam]], # not pksv
2326 0x4B: ["3writetext", ["text_pointer", PointerLabelBeforeBank]],
2327 0x4C: ["2writetext", ["text_pointer", RawTextPointerLabelParam]],
2328 0x4D: ["repeattext", ["byte", SingleByteParam], ["byte", SingleByteParam]], # not pksv
2329 0x4E: ["yesorno"],
2330 0x4F: ["loadmenudata", ["data", MenuDataPointerParam]],
2331 0x50: ["writebackup"],
2332 0x51: ["jumptextfaceplayer", ["text_pointer", RawTextPointerLabelParam]],
2333 0x52: ["3jumptext", ["text_pointer", PointerLabelBeforeBank]],
2334 0x53: ["jumptext", ["text_pointer", RawTextPointerLabelParam]],
2335 0x54: ["closetext"],
2336 0x55: ["keeptextopen"],
2337 0x56: ["pokepic", ["pokemon", PokemonParam]],
2338 0x57: ["pokepicyesorno"],
2339 0x58: ["interpretmenu"],
2340 0x59: ["interpretmenu2"],
2341# not pksv
2342 0x5A: ["loadpikachudata"],
2343 0x5B: ["battlecheck"],
2344 0x5C: ["loadtrainerdata"],
2345# back to pksv..
2346 0x5D: ["loadpokedata", ["pokemon", PokemonParam], ["level", DecimalParam]],
2347 0x5E: ["loadtrainer", ["trainer_group", TrainerGroupParam], ["trainer_id", TrainerIdParam]],
2348 0x5F: ["startbattle"],
2349 0x60: ["returnafterbattle"],
2350 0x61: ["catchtutorial", ["byte", SingleByteParam]],
2351# not pksv
2352 0x62: ["trainertext", ["which_text", SingleByteParam]],
2353 0x63: ["trainerstatus", ["action", SingleByteParam]],
2354# back to pksv..
2355 0x64: ["winlosstext", ["win_text_pointer", TextPointerLabelParam], ["loss_text_pointer", TextPointerLabelParam]],
2356 0x65: ["scripttalkafter"], # not pksv
2357 0x66: ["talkaftercancel"],
2358 0x67: ["talkaftercheck"],
2359 0x68: ["setlasttalked", ["person", SingleByteParam]],
2360 0x69: ["applymovement", ["person", SingleByteParam], ["data", MovementPointerLabelParam]],
2361 0x6A: ["applymovement2", ["data", MovementPointerLabelParam]], # not pksv
2362 0x6B: ["faceplayer"],
2363 0x6C: ["faceperson", ["person1", SingleByteParam], ["person2", SingleByteParam]],
2364 0x6D: ["variablesprite", ["byte", SingleByteParam], ["sprite", SingleByteParam]],
2365 0x6E: ["disappear", ["person", SingleByteParam]], # hideperson
2366 0x6F: ["appear", ["person", SingleByteParam]], # showperson
2367 0x70: ["follow", ["person2", SingleByteParam], ["person1", SingleByteParam]],
2368 0x71: ["stopfollow"],
2369 0x72: ["moveperson", ["person", SingleByteParam], ["x", SingleByteParam], ["y", SingleByteParam]],
2370 0x73: ["writepersonxy", ["person", SingleByteParam]], # not pksv
2371 0x74: ["loademote", ["bubble", SingleByteParam]],
2372 0x75: ["showemote", ["bubble", SingleByteParam], ["person", SingleByteParam], ["time", DecimalParam]],
2373 0x76: ["spriteface", ["person", SingleByteParam], ["facing", SingleByteParam]],
2374 0x77: ["follownotexact", ["person2", SingleByteParam], ["person1", SingleByteParam]],
2375 0x78: ["earthquake", ["param", DecimalParam]],
2376 0x79: ["changemap", ["map_data_pointer", MapDataPointerParam]],
2377 0x7A: ["changeblock", ["x", SingleByteParam], ["y", SingleByteParam], ["block", SingleByteParam]],
2378 0x7B: ["reloadmap"],
2379 0x7C: ["reloadmappart"],
2380 0x7D: ["writecmdqueue", ["queue_pointer", MultiByteParam]],
2381 0x7E: ["delcmdqueue", ["byte", SingleByteParam]],
2382 0x7F: ["playmusic", ["music_pointer", MultiByteParam]],
2383 0x80: ["playrammusic"],
2384 0x81: ["musicfadeout", ["music", MultiByteParam], ["fadetime", SingleByteParam]],
2385 0x82: ["playmapmusic"],
2386 0x83: ["reloadmapmusic"],
2387 0x84: ["cry", ["cry_id", PokemonWordParam]],
2388 0x85: ["playsound", ["sound_pointer", MultiByteParam]],
2389 0x86: ["waitbutton"],
2390 0x87: ["warpsound"],
2391 0x88: ["specialsound"],
2392 0x89: ["passtoengine", ["data_pointer", PointerLabelBeforeBank]],
2393 0x8A: ["newloadmap", ["which_method", SingleByteParam]],
2394 0x8B: ["pause", ["length", DecimalParam]],
2395 0x8C: ["deactivatefacing", ["time", SingleByteParam]],
2396 0x8D: ["priorityjump", ["pointer", ScriptPointerLabelParam]],
2397 0x8E: ["warpcheck"],
2398 0x8F: ["ptpriorityjump", ["pointer", ScriptPointerLabelParam]],
2399 0x90: ["return"],
2400 0x91: ["end"],
2401 0x92: ["reloadandreturn", ["which_method", SingleByteParam]],
2402 0x93: ["resetfuncs"],
2403 0x94: ["pokemart", ["dialog_id", SingleByteParam], ["mart_id", MultiByteParam]], # maybe it should be a pokemark constant id/label?
2404 0x95: ["elevator", ["floor_list_pointer", PointerLabelParam]],
2405 0x96: ["trade", ["trade_id", SingleByteParam]],
2406 0x97: ["askforphonenumber", ["number", SingleByteParam]],
2407 0x98: ["phonecall", ["caller_name", RawTextPointerLabelParam]],
2408 0x99: ["hangup"],
2409 0x9A: ["describedecoration", ["byte", SingleByteParam]],
2410 0x9B: ["fruittree", ["tree_id", SingleByteParam]],
2411 0x9C: ["specialphonecall", ["call_id", MultiByteParam]],
2412 0x9D: ["checkphonecall"],
2413 0x9E: ["verbosegiveitem", ["item", ItemLabelByte], ["quantity", DecimalParam]],
2414 0x9F: ["verbosegiveitem2", ["item", ItemLabelByte], ["var", SingleByteParam]],
2415 0xA0: ["loadwilddata", ["flag", SingleByteParam], ["map_group", MapGroupParam], ["map_id", MapIdParam]],
2416 0xA1: ["halloffame"],
2417 0xA2: ["credits"],
2418 0xA3: ["warpfacing", ["facing", SingleByteParam], ["map_group", MapGroupParam], ["map_id", MapIdParam], ["x", SingleByteParam], ["y", SingleByteParam]],
2419 0xA4: ["storetext", ["memory", SingleByteParam]],
2420 0xA5: ["displaylocation", ["id", SingleByteParam], ["memory", SingleByteParam]],
2421 0xA6: ["trainerclassname", ["id", SingleByteParam], ["memory", SingleByteParam]],
2422 0xA7: ["name", ["type", SingleByteParam], ["id", SingleByteParam], ["mempry", SingleByteParam]],
2423 0xA8: ["wait", ["duration", DecimalParam]],
2424 0xA9: ["unknown0xa9"],
2425}
2426def create_command_classes(debug=False):
2427 """creates some classes for each command byte"""
2428 # don't forget to add any manually created script command classes
2429 # .. except for Warp, Signpost and some others that aren't found in scripts
2430 klasses = [GivePoke]
2431 for (byte, cmd) in pksv_crystal_more.items():
2432 cmd_name = cmd[0].replace(" ", "_")
2433 params = {"id": byte, "size": 1, "end": byte in pksv.pksv_crystal_more_enders, "macro_name": cmd_name}
2434 params["param_types"] = {}
2435 if len(cmd) > 1:
2436 param_types = cmd[1:]
2437 for (i, each) in enumerate(param_types):
2438 thing = {"name": each[0], "class": each[1]}
2439 params["param_types"][i] = thing
2440 if debug:
2441 logging.debug("each is {0} and thing[class] is {1}".format(each, thing["class"]))
2442 params["size"] += thing["class"].size
2443 klass_name = cmd_name+"Command"
2444 klass = type(klass_name, (Command,), params)
2445 globals()[klass_name] = klass
2446 klasses.append(klass)
2447 # later an individual klass will be instantiated to handle something
2448 return klasses
2449command_classes = create_command_classes()
2450
2451
2452class BigEndianParam(object):
2453 """big-endian word"""
2454 size = 2
2455 should_be_decimal = False
2456 byte_type = "bigdw"
2457
2458 def __init__(self, *args, **kwargs):
2459 self.prefix = '$'
2460 for (key, value) in kwargs.items():
2461 setattr(self, key, value)
2462 self.parse()
2463
2464 def parse(self):
2465 self.bytes = rom.interval(self.address, 2, strings=False)
2466 self.parsed_number = self.bytes[0] * 0x100 + self.bytes[1]
2467
2468 def to_asm(self):
2469 if not self.should_be_decimal:
2470 return self.prefix+"".join([("%.2x")%x for x in self.bytes])
2471 elif self.should_be_decimal:
2472 decimal = int("0x"+"".join([("%.2x")%x for x in self.bytes]), 16)
2473 return str(decimal)
2474
2475 @staticmethod
2476 def from_asm(value):
2477 return value
2478
2479class DecimalBigEndianParam(BigEndianParam):
2480 should_be_decimal = True
2481
2482music_commands = {
2483 0xD0: ["octave 8"],
2484 0xD1: ["octave 7"],
2485 0xD2: ["octave 6"],
2486 0xD3: ["octave 5"],
2487 0xD4: ["octave 4"],
2488 0xD5: ["octave 3"],
2489 0xD6: ["octave 2"],
2490 0xD7: ["octave 1"],
2491 0xD8: ["notetype", ["note_length", SingleByteParam], ["intensity", SingleByteParam]], # no intensity on channel 4/8
2492 0xD9: ["forceoctave", ["octave", SingleByteParam]],
2493 0xDA: ["tempo", ["tempo", DecimalBigEndianParam]],
2494 0xDB: ["dutycycle", ["duty_cycle", SingleByteParam]],
2495 0xDC: ["intensity", ["intensity", SingleByteParam]],
2496 0xDD: ["soundinput", ["input", SingleByteParam]],
2497 0xDE: ["unknownmusic0xde", ["unknown", SingleByteParam]],
2498 0xDF: ["togglesfx"],
2499 0xE0: ["unknownmusic0xe0", ["unknown", SingleByteParam], ["unknown", SingleByteParam]],
2500 0xE1: ["vibrato", ["delay", SingleByteParam], ["extent", SingleByteParam]],
2501 0xE2: ["unknownmusic0xe2", ["unknown", SingleByteParam]],
2502 0xE3: ["togglenoise", ["id", SingleByteParam]], # no parameters on toggle off
2503 0xE4: ["panning", ["tracks", SingleByteParam]],
2504 0xE5: ["volume", ["volume", SingleByteParam]],
2505 0xE6: ["tone", ["tone", BigEndianParam]],
2506 0xE7: ["unknownmusic0xe7", ["unknown", SingleByteParam]],
2507 0xE8: ["unknownmusic0xe8", ["unknown", SingleByteParam]],
2508 0xE9: ["globaltempo", ["value", DecimalBigEndianParam]],
2509 0xEA: ["restartchannel", ["address", PointerLabelParam]],
2510 0xEB: ["newsong", ["id", DecimalBigEndianParam]],
2511 0xEC: ["sfxpriorityon"],
2512 0xED: ["sfxpriorityoff"],
2513 0xEE: ["unknownmusic0xee", ["address", PointerLabelParam]],
2514 0xEF: ["stereopanning", ["tracks", SingleByteParam]],
2515 0xF0: ["sfxtogglenoise", ["id", SingleByteParam]], # no parameters on toggle off
2516 0xF1: ["music0xf1"], # nothing
2517 0xF2: ["music0xf2"], # nothing
2518 0xF3: ["music0xf3"], # nothing
2519 0xF4: ["music0xf4"], # nothing
2520 0xF5: ["music0xf5"], # nothing
2521 0xF6: ["music0xf6"], # nothing
2522 0xF7: ["music0xf7"], # nothing
2523 0xF8: ["music0xf8"], # nothing
2524 0xF9: ["unknownmusic0xf9"],
2525 0xFA: ["setcondition", ["condition", SingleByteParam]],
2526 0xFB: ["jumpif", ["condition", SingleByteParam], ["address", PointerLabelParam]],
2527 0xFC: ["jumpchannel", ["address", PointerLabelParam]],
2528 0xFD: ["loopchannel", ["count", DecimalParam], ["address", PointerLabelParam]],
2529 0xFE: ["callchannel", ["address", PointerLabelParam]],
2530 0xFF: ["endchannel"],
2531}
2532
2533music_command_enders = [
2534 "restartchannel",
2535 "newsong",
2536 "unknownmusic0xee",
2537 "jumpchannel",
2538 "endchannel",
2539]
2540
2541def create_music_command_classes(debug=False):
2542 klasses = []
2543 for (byte, cmd) in music_commands.items():
2544 cmd_name = cmd[0].replace(" ", "_")
2545 params = {
2546 "id": byte,
2547 "size": 1,
2548 "end": cmd[0] in music_command_enders,
2549 "macro_name": cmd[0]
2550 }
2551 params["param_types"] = {}
2552 if len(cmd) > 1:
2553 param_types = cmd[1:]
2554 for (i, each) in enumerate(param_types):
2555 thing = {"name": each[0], "class": each[1]}
2556 params["param_types"][i] = thing
2557 if debug:
2558 logging.debug("each is {0} and thing[class] is {1}".format(each, thing["class"]))
2559 params["size"] += thing["class"].size
2560 klass_name = cmd_name+"Command"
2561 klass = type(klass_name, (Command,), params)
2562 globals()[klass_name] = klass
2563 if klass.macro_name == "notetype":
2564 klass.allowed_lengths = [1, 2]
2565 elif klass.macro_name in ["togglenoise", "sfxtogglenoise"]:
2566 klass.allowed_lengths = [0, 1]
2567 klasses.append(klass)
2568 # later an individual klass will be instantiated to handle something
2569 return klasses
2570
2571music_classes = create_music_command_classes()
2572
2573class OctaveParam(DecimalParam):
2574 @staticmethod
2575 def from_asm(value):
2576 value = int(value)
2577 return hex(0xd8 - value).replace("0x", "$")
2578
2579class OctaveCommand(Command):
2580 macro_name = "octave"
2581 size = 0
2582 end = False
2583 param_types = {
2584 0: {"name": "octave", "class": OctaveParam},
2585 }
2586 allowed_lengths = [1]
2587 override_byte_check = True
2588
2589class ChannelCommand(Command):
2590 macro_name = "channel"
2591 size = 3
2592 override_byte_check = True
2593 param_types = {
2594 0: {"name": "id", "class": DecimalParam},
2595 1: {"name": "address", "class": PointerLabelParam},
2596 }
2597
2598
2599# pokered
2600
2601class callchannel(Command):
2602 id = 0xFD
2603 macro_name = "callchannel"
2604 size = 3
2605 param_types = {
2606 0: {"name": "address", "class": PointerLabelParam},
2607 }
2608
2609class loopchannel(Command):
2610 id = 0xFE
2611 macro_name = "loopchannel"
2612 size = 4
2613 param_types = {
2614 0: {"name": "count", "class": SingleByteParam},
2615 1: {"name": "address", "class": PointerLabelParam},
2616 }
2617
2618
2619effect_commands = {
2620 0x1: ['checkturn'],
2621 0x2: ['checkobedience'],
2622 0x3: ['usedmovetext'],
2623 0x4: ['doturn'],
2624 0x5: ['critical'],
2625 0x6: ['damagestats'],
2626 0x7: ['stab'],
2627 0x8: ['damagevariation'],
2628 0x9: ['checkhit'],
2629 0xa: ['effect0x0a'],
2630 0xb: ['effect0x0b'],
2631 0xc: ['effect0x0c'],
2632 0xd: ['resulttext'],
2633 0xe: ['checkfaint'],
2634 0xf: ['criticaltext'],
2635 0x10: ['supereffectivetext'],
2636 0x11: ['checkdestinybond'],
2637 0x12: ['buildopponentrage'],
2638 0x13: ['poisontarget'],
2639 0x14: ['sleeptarget'],
2640 0x15: ['draintarget'],
2641 0x16: ['eatdream'],
2642 0x17: ['burntarget'],
2643 0x18: ['freezetarget'],
2644 0x19: ['paralyzetarget'],
2645 0x1a: ['selfdestruct'],
2646 0x1b: ['mirrormove'],
2647 0x1c: ['statup'],
2648 0x1d: ['statdown'],
2649 0x1e: ['payday'],
2650 0x1f: ['conversion'],
2651 0x20: ['resetstats'],
2652 0x21: ['storeenergy'],
2653 0x22: ['unleashenergy'],
2654 0x23: ['forceswitch'],
2655 0x24: ['endloop'],
2656 0x25: ['flinchtarget'],
2657 0x26: ['ohko'],
2658 0x27: ['recoil'],
2659 0x28: ['mist'],
2660 0x29: ['focusenergy'],
2661 0x2a: ['confuse'],
2662 0x2b: ['confusetarget'],
2663 0x2c: ['heal'],
2664 0x2d: ['transform'],
2665 0x2e: ['screen'],
2666 0x2f: ['poison'],
2667 0x30: ['paralyze'],
2668 0x31: ['substitute'],
2669 0x32: ['rechargenextturn'],
2670 0x33: ['mimic'],
2671 0x34: ['metronome'],
2672 0x35: ['leechseed'],
2673 0x36: ['splash'],
2674 0x37: ['disable'],
2675 0x38: ['cleartext'],
2676 0x39: ['charge'],
2677 0x3a: ['checkcharge'],
2678 0x3b: ['traptarget'],
2679 0x3c: ['effect0x3c'],
2680 0x3d: ['rampage'],
2681 0x3e: ['checkrampage'],
2682 0x3f: ['constantdamage'],
2683 0x40: ['counter'],
2684 0x41: ['encore'],
2685 0x42: ['painsplit'],
2686 0x43: ['snore'],
2687 0x44: ['conversion2'],
2688 0x45: ['lockon'],
2689 0x46: ['sketch'],
2690 0x47: ['defrostopponent'],
2691 0x48: ['sleeptalk'],
2692 0x49: ['destinybond'],
2693 0x4a: ['spite'],
2694 0x4b: ['falseswipe'],
2695 0x4c: ['healbell'],
2696 0x4d: ['kingsrock'],
2697 0x4e: ['triplekick'],
2698 0x4f: ['kickcounter'],
2699 0x50: ['thief'],
2700 0x51: ['arenatrap'],
2701 0x52: ['nightmare'],
2702 0x53: ['defrost'],
2703 0x54: ['curse'],
2704 0x55: ['protect'],
2705 0x56: ['spikes'],
2706 0x57: ['foresight'],
2707 0x58: ['perishsong'],
2708 0x59: ['startsandstorm'],
2709 0x5a: ['endure'],
2710 0x5b: ['checkcurl'],
2711 0x5c: ['rolloutpower'],
2712 0x5d: ['effect0x5d'],
2713 0x5e: ['furycutter'],
2714 0x5f: ['attract'],
2715 0x60: ['happinesspower'],
2716 0x61: ['present'],
2717 0x62: ['damagecalc'],
2718 0x63: ['frustrationpower'],
2719 0x64: ['safeguard'],
2720 0x65: ['checksafeguard'],
2721 0x66: ['getmagnitude'],
2722 0x67: ['batonpass'],
2723 0x68: ['pursuit'],
2724 0x69: ['clearhazards'],
2725 0x6a: ['healmorn'],
2726 0x6b: ['healday'],
2727 0x6c: ['healnite'],
2728 0x6d: ['hiddenpower'],
2729 0x6e: ['startrain'],
2730 0x6f: ['startsun'],
2731 0x70: ['attackup'],
2732 0x71: ['defenseup'],
2733 0x72: ['speedup'],
2734 0x73: ['specialattackup'],
2735 0x74: ['specialdefenseup'],
2736 0x75: ['accuracyup'],
2737 0x76: ['evasionup'],
2738 0x77: ['attackup2'],
2739 0x78: ['defenseup2'],
2740 0x79: ['speedup2'],
2741 0x7a: ['specialattackup2'],
2742 0x7b: ['specialdefenseup2'],
2743 0x7c: ['accuracyup2'],
2744 0x7d: ['evasionup2'],
2745 0x7e: ['attackdown'],
2746 0x7f: ['defensedown'],
2747 0x80: ['speeddown'],
2748 0x81: ['specialattackdown'],
2749 0x82: ['specialdefensedown'],
2750 0x83: ['accuracydown'],
2751 0x84: ['evasiondown'],
2752 0x85: ['attackdown2'],
2753 0x86: ['defensedown2'],
2754 0x87: ['speeddown2'],
2755 0x88: ['specialattackdown2'],
2756 0x89: ['specialdefensedown2'],
2757 0x8a: ['accuracydown2'],
2758 0x8b: ['evasiondown2'],
2759 0x8c: ['statmessageuser'],
2760 0x8d: ['statmessagetarget'],
2761 0x8e: ['statupfailtext'],
2762 0x8f: ['statdownfailtext'],
2763 0x90: ['effectchance'],
2764 0x91: ['effect0x91'],
2765 0x92: ['effect0x92'],
2766 0x93: ['switchturn'],
2767 0x94: ['fakeout'],
2768 0x95: ['bellydrum'],
2769 0x96: ['psychup'],
2770 0x97: ['rage'],
2771 0x98: ['doubleflyingdamage'],
2772 0x99: ['doubleundergrounddamage'],
2773 0x9a: ['mirrorcoat'],
2774 0x9b: ['checkfuturesight'],
2775 0x9c: ['futuresight'],
2776 0x9d: ['doubleminimizedamage'],
2777 0x9e: ['skipsuncharge'],
2778 0x9f: ['thunderaccuracy'],
2779 0xa0: ['teleport'],
2780 0xa1: ['beatup'],
2781 0xa2: ['ragedamage'],
2782 0xa3: ['effect0xa3'],
2783 0xa4: ['allstatsup'],
2784 0xa5: ['effect0xa5'],
2785 0xa6: ['effect0xa6'],
2786 0xa7: ['effect0xa7'],
2787 0xa8: ['effect0xa8'],
2788 0xa9: ['clearmissdamage'],
2789 0xaa: ['movedelay'],
2790 0xab: ['hittarget'],
2791 0xac: ['tristatuschance'],
2792 0xad: ['supereffectivelooptext'],
2793 0xae: ['startloop'],
2794 0xaf: ['curl'],
2795 0xfe: ['endturn'],
2796 0xff: ['endmove'],
2797}
2798
2799effect_command_enders = [0xFF,]
2800
2801def create_effect_command_classes(debug=False):
2802 klasses = []
2803 for (byte, cmd) in effect_commands.items():
2804 cmd_name = cmd[0].replace(" ", "_")
2805 params = {
2806 "id": byte,
2807 "size": 1,
2808 "end": byte in effect_command_enders,
2809 "macro_name": cmd_name
2810 }
2811 params["param_types"] = {}
2812 if len(cmd) > 1:
2813 param_types = cmd[1:]
2814 for (i, each) in enumerate(param_types):
2815 thing = {"name": each[0], "class": each[1]}
2816 params["param_types"][i] = thing
2817 if debug:
2818 logging.debug("each is {0} and thing[class] is {1}".format(each, thing["class"]))
2819 params["size"] += thing["class"].size
2820 klass_name = cmd_name+"Command"
2821 klass = type(klass_name, (Command,), params)
2822 globals()[klass_name] = klass
2823 klasses.append(klass)
2824 # later an individual klass will be instantiated to handle something
2825 return klasses
2826
2827effect_classes = create_effect_command_classes()
2828
2829
2830
2831def generate_macros(filename=None):
2832 if filename == None:
2833 filename = os.path.join(conf.path, "script_macros.asm")
2834 """generates all macros based on commands
2835 this is dumped into script_macros.asm"""
2836 output = "; This file is generated by generate_macros.\n"
2837 for command in command_classes:
2838 output += "\n"
2839 #if command.macro_name[0].isdigit():
2840 # output += "_"
2841 output += command.macro_name + ": MACRO\n"
2842 output += spacing + "db $%.2x\n"%(command.id)
2843 current_param = 1
2844 for (index, each) in command.param_types.items():
2845 if issubclass(each["class"], SingleByteParam):
2846 output += spacing + "db \\" + str(current_param) + "\n"
2847 elif issubclass(each["class"], MultiByteParam):
2848 output += spacing + "dw \\" + str(current_param) + "\n"
2849 current_param += 1
2850 output += spacing + "ENDM\n"
2851
2852 fh = open(filename, "w")
2853 fh.write(output)
2854 fh.close()
2855
2856 return output
2857
2858# use this to keep track of commands without pksv names
2859pksv_no_names = {}
2860def pretty_print_pksv_no_names():
2861 """just some nice debugging output
2862 use this to keep track of commands without pksv names
2863 pksv_no_names is created in parse_script_engine_script_at"""
2864 for (command_byte, addresses) in pksv_no_names.items():
2865 if command_byte in pksv.pksv_crystal_unknowns:
2866 continue
2867 print(hex(command_byte) + " appearing in these scripts: ")
2868 for address in addresses:
2869 print(" " + hex(address))
2870
2871recursive_scripts = set([])
2872def rec_parse_script_engine_script_at(address, origin=None, debug=True):
2873 """this is called in parse_script_engine_script_at for recursion
2874 when this works it should be flipped back to using the regular
2875 parser."""
2876 recursive_scripts.add((address, origin))
2877 return parse_script_engine_script_at(address, origin=origin, debug=debug)
2878
2879def find_broken_recursive_scripts(output=False, debug=True):
2880 """well.. these at least have a chance of maybe being broken?"""
2881 for r in list(recursive_scripts):
2882 script = {}
2883 length = "not counted here"
2884 if is_script_already_parsed_at(r[0]):
2885 script = script_parse_table[r[0]]
2886 length = str(len(script))
2887 if len(script) > 20 or script == {}:
2888 logging.debug(
2889 "script at {address} from main script {scr} with length {length}"
2890 .format(
2891 address=hex(r[0]),
2892 scr=hex(r[1]),
2893 length=length,
2894 )
2895 )
2896 if output:
2897 parse_script_engine_script_at(r[0], force=True, debug=True)
2898
2899
2900stop_points = [0x1aafa2,
2901 0x9f58f, # battle tower
2902 0x9f62f, # battle tower
2903 ]
2904class Script(object):
2905 base_label = "UnknownScript_"
2906 def __init__(self, *args, **kwargs):
2907 self.address = None
2908 self.commands = None
2909 if len(kwargs) == 0 and len(args) == 0:
2910 raise Exception("Script.__init__ must be given some arguments")
2911 # first positional argument is address
2912 if len(args) == 1:
2913 address = args[0]
2914 if type(address) == str:
2915 address = int(address, 16)
2916 elif type(address) != int:
2917 raise Exception("address must be an integer or string")
2918 self.address = address
2919 elif len(args) > 1:
2920 raise Exception("don't know what to do with second (or later) positional arguments")
2921 self.dependencies = None
2922 if "label" in kwargs.keys():
2923 label = kwargs["label"]
2924 else:
2925 label = None
2926 if not label:
2927 label = self.base_label + hex(self.address)
2928 self.label = Label(name=label, address=address, object=self)
2929 if "map_group" in kwargs.keys():
2930 self.map_group = kwargs["map_group"]
2931 if "map_id" in kwargs.keys():
2932 self.map_id = kwargs["map_id"]
2933 if "parent" in kwargs.keys():
2934 self.parent = kwargs["parent"]
2935 # parse the script at the address
2936 if "use_old_parse" in kwargs.keys() and kwargs["use_old_parse"] == True:
2937 self.old_parse(**kwargs)
2938 else:
2939 self.parse(self.address, **kwargs)
2940
2941 def pksv_list(self):
2942 """shows a list of pksv names for each command in the script"""
2943 items = []
2944 if type(self.commands) == dict:
2945 for (id, command) in self.commands.items():
2946 if command["type"] in pksv.pksv_crystal:
2947 items.append(pksv.pksv_crystal[command["type"]])
2948 else:
2949 items.append(hex(command["type"]))
2950 else:
2951 for command in self.commands:
2952 items.append(command.macro_name)
2953 return items
2954
2955
2956 def to_pksv(self):
2957 """returns a string of pksv command names"""
2958 pksv = self.pksv_list()
2959 output = "script starting at: "+hex(self.address)+" .. "
2960 first = True
2961 for item in pksv:
2962 item = str(item)
2963 if first:
2964 output += item
2965 first = False
2966 else:
2967 output += ", "+item
2968 return output
2969
2970 def show_pksv(self):
2971 """prints a list of pksv command names in this script"""
2972 logging.debug("to_pksv(): {0}".format(self.to_pksv()))
2973
2974 def parse(self, start_address, force=False, map_group=None, map_id=None, force_top=True, origin=True, debug=False):
2975 """parses a script using the Command classes
2976 as an alternative to the old method using hard-coded commands
2977
2978 force_top just means 'force the main script to get parsed, but not any subscripts'
2979 """
2980 global command_classes, rom, script_parse_table
2981 current_address = start_address
2982 if debug:
2983 logging.debug(
2984 "Script.parse address={address} map_group={map_group} map_id={map_id}"
2985 .format(
2986 address=hex(self.address),
2987 map_group=str(map_group),
2988 map_id=str(map_id),
2989 )
2990 )
2991 if start_address in stop_points and force == False:
2992 if debug:
2993 logging.debug(
2994 "script parsing is stopping at stop_point={stop_point} at map_group={map_group} map_id={map_id}"
2995 .format(
2996 stop_point=hex(start_address),
2997 map_group=str(map_group),
2998 map_id=str(map_id),
2999 )
3000 )
3001 return None
3002 if start_address < 0x4000 and start_address not in [0x26ef, 0x114, 0x1108]:
3003 if debug:
3004 logging.debug("address is less than 0x4000.. address is {0}".format(hex(start_address)))
3005 sys.exit(1)
3006 if is_script_already_parsed_at(start_address) and not force and not force_top:
3007 raise Exception("this script has already been parsed before, please use that instance ("+hex(start_address)+")")
3008
3009 # load up the rom if it hasn't been loaded already
3010 rom = load_rom()
3011
3012 # in the event that the script parsing fails.. it would be nice to leave evidence
3013 script_parse_table[start_address:start_address+1] = "incomplete parse_script_with_command_classes"
3014
3015 # start with a blank script
3016 commands = []
3017
3018 # use this to control the while loop
3019 end = False
3020
3021 # for each command found..
3022 while not end:
3023 # get the current scripting byte
3024 cur_byte = ord(rom[current_address])
3025
3026 # reset the command class (last command was probably different)
3027 scripting_command_class = None
3028
3029 # match the command id byte to a scripting command class like GivePoke
3030 for class_ in command_classes:
3031 if class_.id == cur_byte:
3032 scripting_command_class = class_
3033
3034 # no matching command found (not implemented yet)- just end this script
3035 # NOTE: might be better to raise an exception and end the program?
3036 if scripting_command_class == None:
3037 if debug:
3038 logging.debug("parsing script; current_address is: {0}".format(hex(current_address)))
3039 current_address += 1
3040 asm_output = "\n".join([command.to_asm() for command in commands])
3041 end = True
3042 continue
3043 # maybe the program should exit with failure instead?
3044 #raise Exception("no command found? id: " + hex(cur_byte) + " at " + hex(current_address) + " asm is:\n" + asm_output)
3045
3046 # create an instance of the command class and let it parse its parameter bytes
3047 #print "about to parse command(script@"+hex(start_address)+"): " + str(scripting_command_class.macro_name)
3048 cls = scripting_command_class(address=current_address, force=force, map_group=map_group, map_id=map_id, parent=self)
3049
3050 #if self.debug:
3051 # print cls.to_asm()
3052
3053 # store it in this script object
3054 commands.append(cls)
3055
3056 # certain commands will end the scripting engine
3057 end = cls.end
3058
3059 # skip past the command's parameter bytes to go to the next command
3060 #current_address = cls.last_address + 1
3061 current_address += cls.size
3062
3063 # last byte belonging to script is last byte of last command,
3064 # or the last byte of the last command's last parameter
3065 self.last_address = current_address
3066
3067 # store the script in the global table/map thing
3068 script_parse_table[start_address:current_address] = self
3069
3070 asm_output = "\n".join([command.to_asm() for command in commands])
3071 if debug:
3072 logging.debug("asm_output is: {0}".format(asm_output))
3073
3074 # store the script
3075 self.commands = commands
3076
3077 return commands
3078
3079 def get_dependencies(self, recompute=False, global_dependencies=set()):
3080 if self.dependencies != None and not recompute:
3081 global_dependencies.update(self.dependencies)
3082 return self.dependencies
3083 dependencies = []
3084 for command in self.commands:
3085 deps = command.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
3086 dependencies.extend(deps)
3087 self.dependencies = dependencies
3088 return dependencies
3089
3090 def to_asm(self):
3091 asm_output = "".join([command.to_asm()+"\n" for command in self.commands])
3092 if asm_output[-1] == "\n":
3093 asm_output = asm_output[:-1]
3094 return asm_output
3095
3096 def old_parse(self, *args, **kwargs):
3097 """included from old_parse_scripts"""
3098
3099from pokemontools import old_parse_scripts
3100Script.old_parse = old_parse_scripts.old_parse
3101
3102def parse_script_engine_script_at(address, map_group=None, map_id=None, force=False, debug=True, origin=True):
3103 if is_script_already_parsed_at(address) and not force:
3104 return script_parse_table[address]
3105 return Script(address, map_group=map_group, map_id=map_id, force=force, debug=debug, origin=origin)
3106
3107def compare_script_parsing_methods(address):
3108 """
3109 compares the parsed scripts using the new method and the old method
3110 The new method is Script.parse, the old method is Script.old_parse.
3111
3112 There are likely to be problems with the new script parser, the one
3113 that uses the command classes to parse bytes. To look for these
3114 problems, you can compare the output of one parsing method to the
3115 output of the other. When there's a difference, there is something
3116 worth correcting. Probably by each command's "macro_name" attribute.
3117 """
3118 rom = load_rom()
3119 separator = "################ compare_script_parsing_methods"
3120 # first do it the old way
3121 logging.debug(separator)
3122 logging.debug("parsing the script at {address} using the old method".format(address=hex(address)))
3123 oldscript = Script(address, debug=True, force=True, origin=True, use_old_parse=True)
3124 # and now the old way
3125 logging.debug(separator)
3126 logging.debug("parsing the script at {address} using the new method".format(address=hex(address)))
3127 newscript = Script(address, debug=True, force=True, origin=True)
3128 # let the comparison begin..
3129 errors = 0
3130 logging.debug("{0} COMPARISON RESULTS".format(separator))
3131 if not len(oldscript.commands.keys()) == len(newscript.commands):
3132 logging.debug("the two scripts don't have the same number of commands")
3133 errors += 1
3134 for (id, oldcommand) in oldscript.commands.items():
3135 newcommand = newscript.commands[id]
3136 oldcommand_pksv_name = pksv.pksv_crystal[oldcommand["type"]].replace(" ", "_")
3137 if oldcommand["start_address"] != newcommand.address:
3138 logging.debug(
3139 "The two address (command id={id}) do not match old={old} new={new}"
3140 .format(
3141 id=id,
3142 old=hex(oldcommand["start_address"]),
3143 new=hex(newcommand.address),
3144 )
3145 )
3146 errors += 1
3147 if oldcommand_pksv_name != newcommand.macro_name:
3148 logging.debug(
3149 "The two commands (id={id}) do not have the same name old={old} new={new}"
3150 .format(
3151 id=id,
3152 old=oldcommand_pksv_name,
3153 new=newcommand.macro_name,
3154 )
3155 )
3156 errors += 1
3157 logging.info("total comparison errors: {0}".format(errors))
3158 return oldscript, newscript
3159
3160
3161class Warp(Command):
3162 """only used outside of scripts"""
3163 size = warp_byte_size
3164 macro_name = "warp_def"
3165 param_types = {
3166 0: {"name": "y", "class": HexByte},
3167 1: {"name": "x", "class": HexByte},
3168 2: {"name": "warp_to", "class": DecimalParam},
3169 3: {"name": "map_bank", "class": MapGroupParam},
3170 4: {"name": "map_id", "class": MapIdParam},
3171 }
3172 override_byte_check = True
3173
3174 def __init__(self, *args, **kwargs):
3175 self.id = kwargs["id"]
3176 script_parse_table[kwargs["address"] : kwargs["address"] + self.size] = self
3177 Command.__init__(self, *args, **kwargs)
3178
3179 def get_dependencies(self, recompute=False, global_dependencies=set()):
3180 return []
3181
3182all_warps = []
3183def parse_warps(address, warp_count, bank=None, map_group=None, map_id=None, debug=True):
3184 warps = []
3185 current_address = address
3186 for each in range(warp_count):
3187 warp = Warp(address=current_address, id=each, bank=bank, map_group=map_group, map_id=map_id, debug=debug)
3188 current_address += warp_byte_size
3189 warps.append(warp)
3190 all_warps.extend(warps)
3191 return warps
3192
3193class XYTrigger(Command):
3194 size = trigger_byte_size
3195 macro_name = "xy_trigger"
3196 param_types = {
3197 0: {"name": "number", "class": DecimalParam},
3198 1: {"name": "y", "class": HexByte},
3199 2: {"name": "x", "class": HexByte},
3200 3: {"name": "unknown1", "class": SingleByteParam},
3201 4: {"name": "script", "class": ScriptPointerLabelParam},
3202 5: {"name": "unknown2", "class": SingleByteParam},
3203 6: {"name": "unknown3", "class": SingleByteParam},
3204 }
3205 override_byte_check = True
3206
3207 def __init__(self, *args, **kwargs):
3208 self.id = kwargs["id"]
3209 self.dependencies = None
3210 Command.__init__(self, *args, **kwargs)
3211
3212 def get_dependencies(self, recompute=False, global_dependencies=set()):
3213 dependencies = []
3214 if self.dependencies != None and not recompute:
3215 global_dependencies.update(self.dependencies)
3216 return self.dependencies
3217 thing = script_parse_table[self.params[4].parsed_address]
3218 if thing and thing != self.params[4]:
3219 dependencies.append(thing)
3220 global_dependencies.add(thing)
3221 self.dependencies = dependencies
3222 return dependencies
3223
3224all_xy_triggers = []
3225def parse_xy_triggers(address, trigger_count, bank=None, map_group=None, map_id=None, debug=True):
3226 xy_triggers = []
3227 current_address = address
3228 for each in range(trigger_count):
3229 xy_trigger = XYTrigger(address=current_address, id=each, bank=bank, map_group=map_group, map_id=map_id, debug=debug)
3230 current_address += trigger_byte_size
3231 xy_triggers.append(xy_trigger)
3232 all_xy_triggers.extend(xy_triggers)
3233 return xy_triggers
3234
3235def old_parse_xy_trigger_bytes(some_bytes, bank=None, map_group=None, map_id=None, debug=True):
3236 """parse some number of triggers from the data"""
3237 assert len(some_bytes) % trigger_byte_size == 0, "wrong number of bytes"
3238 triggers = []
3239 for bytes in pokemontools.helpers.grouper(some_bytes, count=trigger_byte_size):
3240 trigger_number = int(bytes[0], 16)
3241 y = int(bytes[1], 16)
3242 x = int(bytes[2], 16)
3243 unknown1 = int(bytes[3], 16) # XXX probably 00?
3244 script_ptr_byte1 = int(bytes[4], 16)
3245 script_ptr_byte2 = int(bytes[5], 16)
3246 script_ptr = script_ptr_byte1 + (script_ptr_byte2 << 8)
3247 script_address = None
3248 script = None
3249 if bank:
3250 script_address = pointers.calculate_pointer(script_ptr, bank)
3251 logging.debug(
3252 "parsing xy trigger byte scripts.. x={x} y={y}"
3253 .format(x=x, y=y)
3254 )
3255 script = parse_script_engine_script_at(script_address, map_group=map_group, map_id=map_id)
3256
3257 triggers.append({
3258 "trigger_number": trigger_number,
3259 "y": y,
3260 "x": x,
3261 "unknown1": unknown1, # probably 00
3262 "script_ptr": script_ptr,
3263 "script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2},
3264 "script_address": script_address,
3265 "script": script,
3266 })
3267 return triggers
3268
3269
3270class ItemFragment(Command):
3271 """used by ItemFragmentParam and PeopleEvent
3272 (for items placed on a map)"""
3273 size = 2
3274 macro_name = "db"
3275 base_label = "ItemFragment_"
3276 override_byte_check = True
3277 param_types = {
3278 0: {"name": "item", "class": ItemLabelByte},
3279 1: {"name": "quantity", "class": DecimalParam},
3280 }
3281
3282 def __init__(self, address=None, bank=None, map_group=None, map_id=None, debug=False, label=None):
3283 if not is_valid_address(address):
3284 raise exceptions.AddressException(
3285 "ItemFragment must be given a valid address (but got {0})."
3286 .format(hex(address))
3287 )
3288 self.address = address
3289 self.last_address = address + self.size
3290 self.bank = bank
3291 if not label:
3292 label = self.base_label + hex(address)
3293 self.label = Label(name=label, address=address, object=self)
3294 self.map_group = map_group
3295 self.map_id = map_id
3296 self.debug = debug
3297 self.params = {}
3298 self.dependencies = None
3299 self.args = {"debug": debug, "map_group": map_group, "map_id": map_id, "bank": bank}
3300 script_parse_table[self.address : self.last_address] = self
3301 self.parse()
3302
3303
3304class ItemFragmentParam(PointerLabelParam):
3305 """used by PeopleEvent"""
3306
3307 def parse(self):
3308 PointerLabelParam.parse(self)
3309
3310 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
3311 self.calculated_address = address
3312
3313 itemfrag = ItemFragment(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
3314 self.itemfrag = itemfrag
3315
3316 def get_dependencies(self, recompute=False, global_dependencies=set()):
3317 if self.dependencies != None and not recompute:
3318 global_dependencies.update(self.dependencies)
3319 return self.dependencies
3320 self.dependencies = [self.itemfrag].extend(self.itemfrag.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3321 global_dependencies.add(self.itemfrag)
3322 return self.dependencies
3323
3324class TrainerFragment(Command):
3325 """used by TrainerFragmentParam and PeopleEvent for trainer data
3326
3327 Maybe this shouldn't be a Command. The output might sprawl
3328 over multiple lines, and maybe it should be commented in to_asm?
3329
3330 [Event flag (2byte)][Trainer group][Trainer]
3331 [2byte pointer to Text when seen]
3332 [2byte pointer to text when trainer beaten]
3333 [2byte pointer to script when lost (0000=Blackout)]
3334 [2byte pointer to script if won/talked to again]
3335
3336 The event flag tells the game later on if the trainer has been
3337 beaten already (set) or not (reset).
3338
3339 03 = Nothing
3340 04 = Nothing
3341 05 = Nothing
3342 06 = Nothing
3343 """
3344 size = 12
3345 macro_name = "trainer_def"
3346 base_label = "Trainer_"
3347 override_byte_check = True
3348 param_types = {
3349 0: {"name": "event_flag", "class": EventFlagParam},
3350 1: {"name": "trainer_group", "class": TrainerGroupParam},
3351 2: {"name": "trainer_id", "class": TrainerIdParam},
3352 3: {"name": "text_when_seen", "class": TextPointerLabelParam},
3353 4: {"name": "text_when_trainer_beaten", "class": TextPointerLabelParam},
3354 5: {"name": "script_when_lost", "class": ScriptPointerLabelParam},
3355 6: {"name": "script_talk_again", "class": ScriptPointerLabelParam},
3356 }
3357
3358 def __init__(self, *args, **kwargs):
3359 address = kwargs["address"]
3360 logging.debug("TrainerFragment address={0}".format(hex(address)))
3361 self.address = address
3362 self.last_address = self.address + self.size
3363 if not is_valid_address(address) or address in [0x26ef]:
3364 self.include_in_asm = False
3365 return
3366 script_parse_table[self.address : self.last_address] = self
3367 self.dependencies = None
3368 Command.__init__(self, *args, **kwargs)
3369
3370 def get_dependencies(self, recompute=False, global_dependencies=set()):
3371 deps = []
3372 if not is_valid_address(self.address):
3373 return deps
3374 if self.dependencies != None and not recompute:
3375 global_dependencies.update(self.dependencies)
3376 return self.dependencies
3377 #deps.append(self.params[3])
3378 deps.extend(self.params[3].get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3379 #deps.append(self.params[4])
3380 deps.extend(self.params[4].get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3381 #deps.append(self.params[5])
3382 deps.extend(self.params[5].get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3383 #deps.append(self.params[6])
3384 deps.extend(self.params[6].get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3385 self.dependencies = deps
3386 return deps
3387
3388 def parse(self):
3389 Command.parse(self)
3390
3391 # get the trainer group id
3392 trainer_group = self.params[1].byte
3393
3394 # get the trainer id
3395 trainer_id = self.params[2].byte
3396
3397 if not trainer_group in self.trainer_group_maximums.keys():
3398 self.trainer_group_maximums[trainer_group] = set([trainer_id])
3399 else:
3400 self.trainer_group_maximums[trainer_group].add(trainer_id)
3401
3402 # give this object a possibly better label
3403 label = "Trainer"
3404 if ("uses_numeric_trainer_ids" in trainers.trainer_group_names[trainer_group].keys()) \
3405 or ("trainer_names" not in trainers.trainer_group_names[trainer_group].keys()):
3406 label += string.capwords(trainers.trainer_group_names[trainer_group]["constant"])
3407 if "trainer_names" in trainers.trainer_group_names[trainer_group].keys() \
3408 and len(trainers.trainer_group_names[trainer_group]["trainer_names"]) > 1:
3409 label += str(trainer_id)
3410 else:
3411 label += string.capwords(trainers.trainer_group_names[trainer_group]["constant"]) + \
3412 string.capwords(trainers.trainer_group_names[trainer_group]["trainer_names"][trainer_id-1])
3413
3414 label = label.replace("Gruntm", "GruntM").replace("Gruntf", "GruntF").replace("Lt_surge", "LtSurge")
3415
3416 self.label = Label(name=label, address=self.address, object=self)
3417
3418 # ---- give better labels to the objects created by TrainerFragment ----
3419
3420 text_when_seen_text = script_parse_table[self.params[3].parsed_address]
3421 if text_when_seen_text != None:
3422 text_when_seen_label = Label(name=label + "WhenSeenText", address=text_when_seen_text.address, object=text_when_seen_text)
3423 text_when_seen_text.label = text_when_seen_label
3424
3425 text_when_beaten_text = script_parse_table[self.params[4].parsed_address]
3426 if text_when_beaten_text != None:
3427 text_when_beaten_label = Label(name=label + "WhenBeatenText", address=text_when_beaten_text.address, object=text_when_beaten_text)
3428 text_when_beaten_text.label = text_when_beaten_label
3429
3430 script_when_lost = script_parse_table[self.params[5].parsed_address]
3431 if script_when_lost != None:
3432 script_when_lost_label = Label(name=label + "WhenLostScript", address=script_when_lost.address, object=script_when_lost)
3433 script_when_lost.label = script_when_lost_label
3434
3435 script_talk_again = script_parse_table[self.params[6].parsed_address]
3436 if script_talk_again != None:
3437 script_talk_again_label = Label(name=label + "WhenTalkScript", address=script_talk_again.address, object=script_talk_again)
3438 script_talk_again.label = script_talk_again_label
3439
3440 def to_asm(self):
3441 xspacing = ""
3442 output = ""
3443 output += xspacing + "; event flag\n"
3444 output += xspacing + "dw $%.2x"%(self.params[0].parsed_number)
3445 output += "\n\n"+xspacing+"; trainer group && trainer id\n"
3446 output += xspacing + "db %s, %s" % (self.params[1].to_asm(), self.params[2].to_asm())
3447 output += "\n\n"+xspacing+"; text when seen\n"
3448 output += xspacing + "dw " + self.params[3].to_asm()
3449 output += "\n\n"+xspacing+"; text when trainer beaten\n"
3450 output += xspacing + "dw " + self.params[4].to_asm()
3451 output += "\n\n"+xspacing+"; script when lost\n"
3452 output += xspacing + "dw " + self.params[5].to_asm()
3453 output += "\n\n"+xspacing+"; script when talk again\n"
3454 output += xspacing + "dw " + self.params[6].to_asm()
3455 return output
3456
3457class TrainerFragmentParam(PointerLabelParam):
3458 """used by PeopleEvent to point to trainer data"""
3459 def parse(self):
3460 address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
3461 self.calculated_address = address
3462 if address == 0x26ef:
3463 self.trainerfrag = None
3464 else:
3465 trainerfrag = TrainerFragment(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
3466 self.trainerfrag = trainerfrag
3467 PointerLabelParam.parse(self)
3468
3469 def get_dependencies(self, recompute=False, global_dependencies=set()):
3470 deps = []
3471 if self.dependencies != None and not recompute:
3472 global_dependencies.update(self.dependencies)
3473 return self.dependencies
3474 if self.trainerfrag:
3475 global_dependencies.add(self.trainerfrag)
3476 deps.append(self.trainerfrag)
3477 deps.extend(self.trainerfrag.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3478 self.dependencies = deps
3479 return deps
3480
3481trainer_group_table = None
3482class TrainerGroupTable(object):
3483 """
3484 A list of pointers.
3485
3486 This should probably be called TrainerGroupPointerTable.
3487 """
3488
3489 def __init__(self, trainer_group_maximums=None, trainers=None, script_parse_table=None):
3490 self.trainer_group_maximums = trainer_group_maximums
3491 self.trainers = trainers
3492 self.script_parse_table = script_parse_table
3493 assert 0x43 in self.trainer_group_maximums.keys(), "TrainerGroupTable should only be created after all the trainers have been found"
3494 self.address = self.trainers.trainer_group_pointer_table_address
3495 self.bank = pokemontools.pointers.calculate_bank(self.trainers.trainer_group_pointer_table_address)
3496 self.label = Label(name="TrainerGroupPointerTable", address=self.address, object=self)
3497 self.size = None
3498 self.last_address = None
3499 self.dependencies = None
3500 self.headers = []
3501 self.parse()
3502
3503 self.script_parse_table[self.address : self.last_address] = self
3504
3505 def get_dependencies(self, recompute=False, global_dependencies=set()):
3506 global_dependencies.update(self.headers)
3507 if recompute == True and self.dependencies != None and self.dependencies != []:
3508 return self.dependencies
3509 dependencies = copy(self.headers)
3510 for header in self.headers:
3511 dependencies.extend(header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
3512 return dependencies
3513
3514 def parse(self):
3515 size = 0
3516 for (key, kvalue) in self.trainers.trainer_group_names.items():
3517 # calculate the location of this trainer group header from its pointer
3518 pointer_bytes_location = kvalue["pointer_address"]
3519 parsed_address = calculate_pointer_from_bytes_at(pointer_bytes_location, bank=self.bank)
3520 self.trainers.trainer_group_names[key]["parsed_address"] = parsed_address
3521
3522 # parse the trainer group header at this location
3523 name = kvalue["name"]
3524 trainer_group_header = TrainerGroupHeader(address=parsed_address, group_id=key, group_name=name, trainer_group_maximums=self.trainer_group_maximums)
3525 self.trainers.trainer_group_names[key]["header"] = trainer_group_header
3526 self.headers.append(trainer_group_header)
3527
3528 # keep track of the size of this pointer table
3529 size += 2
3530 self.size = size
3531 self.last_address = self.address + self.size
3532
3533 def to_asm(self):
3534 output = "".join([str("dw "+get_label_for(header.address)+"\n") for header in self.headers])
3535 return output
3536
3537class TrainerGroupHeader(object):
3538 """
3539 A trainer group header is a repeating list of individual trainer headers.
3540
3541 <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
3542
3543 Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
3544 Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
3545 Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
3546 Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. Used by a few Cooltrainers.
3547 """
3548
3549 def __init__(self, address=None, group_id=None, group_name=None, trainer_group_maximums=None):
3550 assert address!=None, "TrainerGroupHeader requires an address"
3551 assert group_id!=None, "TrainerGroupHeader requires a group_id"
3552 assert group_name!=None, "TrainerGroupHeader requires a group_name"
3553
3554 self.trainer_group_maximums = trainer_group_maximums
3555
3556 self.address = address
3557 self.group_id = group_id
3558 self.group_name = group_name
3559 self.dependencies = None
3560 self.individual_trainer_headers = []
3561 self.label = Label(name=group_name+"TrainerGroupHeader", address=self.address, object=self)
3562 self.parse()
3563
3564 script_parse_table[address : self.last_address] = self
3565
3566 def get_dependencies(self, recompute=False, global_dependencies=set()):
3567 """
3568 TrainerGroupHeader has no dependencies.
3569 """
3570 # TODO: possibly include self.individual_trainer_headers
3571 if recompute or self.dependencies == None:
3572 self.dependencies = []
3573 return self.dependencies
3574
3575 def parse(self):
3576 """
3577 how do i know when there's no more data for this header?
3578 do a global analysis of the rom and figure out the max ids
3579 this wont work for rom hacks of course
3580 see find_trainer_ids_from_scripts
3581 """
3582 size = 0
3583 current_address = self.address
3584
3585 if self.group_id not in self.trainer_group_maximums.keys():
3586 self.size = 0
3587 self.last_address = current_address
3588 return
3589
3590 # create an IndividualTrainerHeader for each id in range(min id, max id + 1)
3591 min_id = min(self.trainer_group_maximums[self.group_id])
3592 max_id = max(self.trainer_group_maximums[self.group_id])
3593
3594 if self.group_id == 0x0C:
3595 # CAL appears a third time with third-stage evos (meganium, typhlosion, feraligatr)
3596 max_id += 1
3597 elif self.group_id == 0x29:
3598 # there's a missing supernerd :(
3599 max_id += 1
3600 elif self.group_id == 0x2D:
3601 # missing bikers
3602 max_id += 2
3603 elif self.group_id == 0x31:
3604 # missing jugglers
3605 max_id += 3
3606 elif self.group_id == 0x32:
3607 # blackbelt wai
3608 max_id += 1
3609 elif self.group_id == 0x3C:
3610 # kimono girl miki
3611 max_id += 1
3612 elif self.group_id == 0x3D:
3613 # twins lea & pia
3614 max_id += 1
3615
3616 for trainer_id in range(min_id, max_id+1):
3617 trainer_header = TrainerHeader(address=current_address, trainer_group_id=self.group_id, trainer_id=trainer_id, parent=self)
3618 self.individual_trainer_headers.append(trainer_header)
3619 # current_address += trainer_header.size
3620 current_address = trainer_header.last_address
3621 size += trainer_header.size
3622
3623 self.last_address = current_address
3624 self.size = size
3625
3626 def to_asm(self):
3627 output = "\n\n".join(["; "+header.make_constant_name()+" ("+str(header.trainer_id)+") at "+hex(header.address)+"\n"+header.to_asm() for header in self.individual_trainer_headers])
3628 return output
3629
3630class TrainerHeader(object):
3631 """
3632 <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
3633
3634 Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
3635 Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
3636 Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
3637 Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. Used by a few Cooltrainers.
3638 """
3639
3640 def __init__(self, address=None, trainer_group_id=None, trainer_id=None, parent=None):
3641 self.parent = parent
3642 self.address = address
3643 self.trainer_group_id = trainer_group_id
3644 self.trainer_id = trainer_id
3645 self.dependencies = []
3646 self.size = None
3647 self.last_address = None
3648 self.parse()
3649 self.label = Label(name=self.make_name(), address=self.address, object=self)
3650 # this shouldn't be added to script_parse_table because
3651 # TrainerGroupHeader covers its address range
3652
3653 def make_name(self):
3654 """
3655 Must occur after parse() is called.
3656 Constructs a name based on self.parent.group_name and self.name.
3657 """
3658 if self.trainer_group_id in [0x14, 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2B, 0x2C, 0x2D, 0x2F, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x41]:
3659 return self.parent.group_name.upper() + "_" + self.name[:-1]
3660 else:
3661 return self.parent.group_name + "_" + str(self.trainer_id)
3662
3663 def make_constant_name(self):
3664 if hasattr(self, "seed_constant_name"):
3665 seed = self.seed_constant_name
3666 else:
3667 seed = self.name
3668
3669 if "?" in seed:
3670 if seed[-2].isdigit():
3671 x = 2
3672 else:
3673 x = 1
3674 seed = trainers.trainer_group_names[self.trainer_group_id]["name"]+"_"+seed[-x:]
3675 elif self.trainer_group_id == 0x1f and "EXECUTIVE" in seed:
3676 seed = "GRUNT_"+seed
3677 elif self.trainer_group_id == 0x2d and "BENNY" in seed.upper():
3678 seed = "BIKER_BENNY"
3679 elif self.trainer_group_id == 0x24 and "BENNY" in seed.upper():
3680 seed = "BUG_CATCHER_BENNY"
3681
3682 return string.capwords(seed).\
3683 replace("@", "").\
3684 replace(" & ", "AND").\
3685 replace(" ", "").\
3686 replace(".", "_").\
3687 upper()
3688
3689 def get_dependencies(self, recompute=False, global_dependencies=set()):
3690 if recompute or self.dependencies == None:
3691 self.dependencies = []
3692 return self.dependencies
3693
3694 def parse(self):
3695 address = self.address
3696
3697 # figure out how many bytes until 0x50 "@"
3698 jump = how_many_until(chr(0x50), address, rom)
3699
3700 # parse the "@" into the name
3701 self.name = parse_text_at(address, jump+1)
3702
3703 # where is the next byte?
3704 current_address = address + jump + 1
3705
3706 # figure out the pokemon data type
3707 self.data_type = ord(rom[current_address])
3708
3709 current_address += 1
3710
3711 # figure out which partymon parser to use for this trainer header
3712 party_mon_parser = None
3713 for monparser in trainer_party_mon_parsers:
3714 if monparser.id == self.data_type:
3715 party_mon_parser = monparser
3716 break
3717
3718 if party_mon_parser == None:
3719 raise Exception("no trainer party mon parser found to parse data type " + hex(self.data_type))
3720
3721 self.party_mons = party_mon_parser(address=current_address, group_id=self.trainer_group_id, trainer_id=self.trainer_id, parent=self)
3722
3723 # let's have everything in trainer_party_mon_parsers handle the last $FF
3724 #self.size = self.party_mons.size + 1 + len(self.name)
3725 self.size = self.party_mons.last_address - self.address
3726 self.last_address = self.party_mons.last_address
3727
3728 def to_asm(self):
3729 output = "db \""+self.name+"\"\n"
3730 output += "db $%.2x ; data type\n" % (self.data_type)
3731 output += self.party_mons.to_asm()
3732 output += "\n; last_address="+hex(self.last_address)+" size="+str(self.size)
3733 return output
3734
3735class TrainerPartyMonParser(object):
3736 """
3737 Just a generic trainer party mon parser.
3738 Don't use this directly. Only use the child classes.
3739 """
3740 id = None
3741 dependencies = None
3742 param_types = None
3743
3744 # could go either way on this one.. TrainerGroupHeader.parse would need to be changed
3745 # so as to not increase current_address by one after reading "data_type"
3746 override_byte_check = True
3747
3748 def __init__(self, address=None, group_id=None, trainer_id=None, parent=None):
3749 self.address = address
3750 self.group_id = group_id
3751 self.trainer_id = trainer_id
3752 self.parent = parent
3753 self.args = {}
3754 self.mons = {}
3755 self.parse()
3756
3757 # pick up the $FF at the end
3758 self.last_address += 1
3759
3760 def parse(self):
3761 current_address = self.address
3762 pkmn = 0
3763 continuer = True
3764 while continuer:
3765 self.mons[pkmn] = {}
3766 i = 0
3767 for (key, param_type) in self.param_types.items():
3768 name = param_type["name"]
3769 klass = param_type["class"]
3770 # make an instance of this class, like SingleByteParam()
3771 # or ItemLabelByte.. by making an instance, obj.parse() is called
3772 obj = klass(address=current_address, name=name, parent=self, **dict([(k,v) for (k, v) in self.args.items() if k not in ["parent"]]))
3773 # save this for later
3774 self.mons[pkmn][i] = obj
3775 # increment our counters
3776 current_address += obj.size
3777 i += 1
3778 pkmn += 1
3779 if ord(rom[current_address]) == 0xFF:
3780 break
3781 self.last_address = current_address
3782 return True
3783
3784 def to_asm(self):
3785 output = ""
3786 #output = "; " + ", ".join([param_type["name"] for (key, param_type) in self.param_types.items()]) + "\n"
3787 for mon in self.mons:
3788 output += "db " + ", ".join([param.to_asm() for (name, param) in self.mons[mon].items()])
3789 output += "\n"
3790 output += "db $ff ; end trainer party mons"
3791 return output
3792
3793class TrainerPartyMonParser0(TrainerPartyMonParser):
3794 """
3795 Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
3796 """
3797 id = 0
3798 size = 2 + 1
3799 param_types = {
3800 0: {"name": "level", "class": DecimalParam},
3801 1: {"name": "species", "class": PokemonParam},
3802 }
3803class TrainerPartyMonParser1(TrainerPartyMonParser):
3804 """
3805 Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3>
3806 <Move4>. Used often for Gym Leaders.
3807 """
3808 id = 1
3809 size = 6 + 1
3810 param_types = {
3811 0: {"name": "level", "class": DecimalParam},
3812 1: {"name": "species", "class": PokemonParam},
3813 2: {"name": "move1", "class": MoveParam},
3814 3: {"name": "move2", "class": MoveParam},
3815 4: {"name": "move3", "class": MoveParam},
3816 5: {"name": "move4", "class": MoveParam},
3817 }
3818class TrainerPartyMonParser2(TrainerPartyMonParser):
3819 """
3820 Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
3821 """
3822 id = 2
3823 size = 3 + 1
3824 param_types = {
3825 0: {"name": "level", "class": DecimalParam},
3826 1: {"name": "species", "class": PokemonParam},
3827 2: {"name": "item", "class": ItemLabelByte},
3828 }
3829class TrainerPartyMonParser3(TrainerPartyMonParser):
3830 """
3831 Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1>
3832 <Move2> <Move3> <Move4>.
3833 Used by a few Cooltrainers.
3834 """
3835 id = 3
3836 size = 7 + 1
3837 param_types = {
3838 0: {"name": "level", "class": DecimalParam},
3839 1: {"name": "species", "class": PokemonParam},
3840 2: {"name": "item", "class": ItemLabelByte},
3841 3: {"name": "move1", "class": MoveParam},
3842 4: {"name": "move2", "class": MoveParam},
3843 5: {"name": "move3", "class": MoveParam},
3844 6: {"name": "move4", "class": MoveParam},
3845 }
3846
3847trainer_party_mon_parsers = [TrainerPartyMonParser0, TrainerPartyMonParser1, TrainerPartyMonParser2, TrainerPartyMonParser3]
3848
3849def find_trainer_ids_from_scripts(script_parse_table=None, trainer_group_maximums=None):
3850 """
3851 Looks through all scripts to find trainer group numbers and trainer numbers.
3852
3853 This can be used with trainer_group_maximums to figure out the current number of
3854 trainers in each of the originating trainer groups.
3855 """
3856 total_unreferenced_trainers = 0
3857
3858 # look at each possibly relevant script
3859 for item in script_parse_table.items():
3860 object = item[1]
3861 if isinstance(object, Script):
3862 check_script_has_trainer_data(object, trainer_group_maximums=trainer_group_maximums)
3863
3864 # make a set of each list of trainer ids to avoid dupes
3865 # this will be used later in TrainerGroupTable
3866 for item in trainer_group_maximums.items():
3867 key = item[0]
3868 value = set(item[1])
3869 trainer_group_maximums[key] = value
3870
3871def report_unreferenced_trainer_ids(trainer_group_maximums):
3872 """
3873 Reports on the number of unreferenced trainer ids in each group.
3874
3875 This should be called after find_trainer_ids_from_scripts.
3876
3877 These are trainer groups with "unused" trainer ids. The
3878 "find_trainer_ids_from_scripts" function analyzes each script in the game,
3879 and each map header in the game (because of code in TrainerFragment), and
3880 finds all references to trainers. But, if there are any trainers that are
3881 referenced in raw ASM, this method does not detect them. Each instance of a
3882 trainer reference is added to a global table called
3883 "trainer_group_maximums". Next, "find_trainer_ids_from_scripts" looks at
3884 the trainer IDs referenced for each group and takes the minimum number and
3885 the maximum number. To find whether or not there are any unused trainers,
3886 it takes the minimum and maximum ids and then sees which intermediate
3887 numbers are missing from the list of "referenced" trainer ids.
3888 """
3889 for item in trainer_group_maximums.items():
3890 key = item[0]
3891 value = item[1]
3892
3893 # i'm curious: are there any missing trainer ids in this group?
3894 min_id = min(value)
3895 max_id = max(value)
3896 expectables = range(min_id, max_id+1)
3897
3898 unreferenced = set()
3899
3900 for expectable in expectables:
3901 if not expectable in value:
3902 unreferenced.add(expectable)
3903
3904 if len(unreferenced) > 0:
3905 total_unreferenced_trainers += len(unreferenced)
3906 output = "trainer group "+hex(key)+" (\""+trainers.trainer_group_names[key]["name"]+"\")"
3907 output += " (min="+str(min_id)+", max="+str(max_id)+")"
3908 output += " has "+str(len(unreferenced))+" unreferenced trainer ids"
3909 output += ": " + str(unreferenced)
3910 logging.info(output)
3911 logging.info("total unreferenced trainers: {0}".format(total_unreferenced_trainers))
3912
3913def trainer_group_report(trainer_group_maximums):
3914 """
3915 Reports how many trainer ids are used in each trainer group.
3916 """
3917 output = ""
3918 total = 0
3919 for trainer_group_id in trainer_group_maximums.keys():
3920 group_name = trainers.trainer_group_names[trainer_group_id]["name"]
3921 first_name = trainer_name_from_group(trainer_group_id).replace("\n", "")
3922 trainers = len(trainer_group_maximums[trainer_group_id])
3923 total += trainers
3924 output += "group "+hex(trainer_group_id)+":\n"
3925 output += "\tname: "+group_name+"\n"
3926 output += "\tfirst: "+first_name+"\n"
3927 output += "\ttrainer count:\t"+str(trainers)+"\n\n"
3928 output += "total trainers: " + str(total)
3929 return output
3930
3931def check_script_has_trainer_data(script, trainer_group_maximums=None):
3932 """
3933 see find_trainer_ids_from_scripts
3934 """
3935 for command in script.commands:
3936 trainer_group = None
3937 trainer_id = None
3938
3939 if command.id == 0x43:
3940 trainer_group = command.params[0].byte
3941 trainer_id = command.params[1].byte
3942 elif command.id == 0x5E:
3943 trainer_group = command.params[0].byte
3944 trainer_id = command.params[1].byte
3945
3946 if trainer_group != None and trainer_id != None:
3947 if trainer_group in trainer_group_maximums.keys():
3948 trainer_group_maximums[trainer_group].add(trainer_id)
3949 else:
3950 trainer_group_maximums[trainer_group] = set([trainer_id])
3951
3952def trainer_name_from_group(group_id, trainer_id=0):
3953 """This doesn't actually work for trainer_id > 0."""
3954 bank = pokemontools.pointers.calculate_bank(0x39999)
3955 ptr_address = 0x39999 + ((group_id - 1)*2)
3956 address = calculate_pointer_from_bytes_at(ptr_address, bank=bank)
3957 text = parse_text_at2(address, how_many_until(chr(0x50), address, rom))
3958 return text
3959
3960def make_trainer_group_name_trainer_ids(trainer_group_table, debug=True):
3961 """
3962 Edits trainers.trainer_group_names and sets the trainer names.
3963 For instance, "AMY & MAY" becomes "AMY_AND_MAY1" and "AMY_AND_MAY2"
3964
3965 This should only be used after TrainerGroupTable.parse has been called.
3966 """
3967 assert trainer_group_table != None, "TrainerGroupTable must be called before setting the trainer names"
3968
3969 if debug:
3970 logging.info("starting to make trainer names and give ids to repeated trainer names")
3971
3972 i = 1
3973 for header in trainer_group_table.headers:
3974 trainer_names = [] # (name, trainer_header)
3975 dupes = set()
3976 group_id = i
3977 group_name = header.group_name
3978 for trainer_header in header.individual_trainer_headers:
3979 if trainer_header.name in [x[0] for x in trainer_names]:
3980 dupes.add(trainer_header.name)
3981 trainer_names.append([trainer_header.name, trainer_header])
3982
3983 # now fix trainers with duplicate names by appending an id
3984 if len(dupes) > 0:
3985 for dupe in dupes:
3986 culprits = [trainer_header for trainer_header in header.individual_trainer_headers if trainer_header.name == dupe]
3987 for (id, culprit) in enumerate(culprits):
3988 culprit.seed_constant_name = culprit.name.replace("@", "") + str(id+1)
3989 culprit.constant_name = culprit.make_constant_name()
3990
3991 # now add the trainer names to trainers.trainer_group_names
3992 trainers.trainer_group_names[i]["trainer_names"] = [theader.make_constant_name() for theader in header.individual_trainer_headers]
3993
3994 i += 1
3995
3996 if debug:
3997 logging.info("done improving trainer names")
3998
3999
4000class SpriteParam(SingleByteParam):
4001 sprites = {
4002 0x1: 'SPRITE_CHRIS',
4003 0x2: 'SPRITE_CHRIS_BIKE',
4004 0x3: 'SPRITE_GAMEBOY_KID',
4005 0x4: 'SPRITE_SILVER',
4006 0x5: 'SPRITE_OAK',
4007 0x6: 'SPRITE_RED',
4008 0x7: 'SPRITE_BLUE',
4009 0x8: 'SPRITE_BILL',
4010 0x9: 'SPRITE_ELDER',
4011 0xa: 'SPRITE_JANINE',
4012 0xb: 'SPRITE_KURT',
4013 0xc: 'SPRITE_MOM',
4014 0xd: 'SPRITE_BLAINE',
4015 0xe: 'SPRITE_REDS_MOM',
4016 0xf: 'SPRITE_DAISY',
4017 0x10: 'SPRITE_ELM',
4018 0x11: 'SPRITE_WILL',
4019 0x12: 'SPRITE_FALKNER',
4020 0x13: 'SPRITE_WHITNEY',
4021 0x14: 'SPRITE_BUGSY',
4022 0x15: 'SPRITE_MORTY',
4023 0x16: 'SPRITE_CHUCK',
4024 0x17: 'SPRITE_JASMINE',
4025 0x18: 'SPRITE_PRYCE',
4026 0x19: 'SPRITE_CLAIR',
4027 0x1a: 'SPRITE_BROCK',
4028 0x1b: 'SPRITE_KAREN',
4029 0x1c: 'SPRITE_BRUNO',
4030 0x1d: 'SPRITE_MISTY',
4031 0x1e: 'SPRITE_LANCE',
4032 0x1f: 'SPRITE_SURGE',
4033 0x20: 'SPRITE_ERIKA',
4034 0x21: 'SPRITE_KOGA',
4035 0x22: 'SPRITE_SABRINA',
4036 0x23: 'SPRITE_COOLTRAINER_M',
4037 0x24: 'SPRITE_COOLTRAINER_F',
4038 0x25: 'SPRITE_BUG_CATCHER',
4039 0x26: 'SPRITE_TWIN',
4040 0x27: 'SPRITE_YOUNGSTER',
4041 0x28: 'SPRITE_LASS',
4042 0x29: 'SPRITE_TEACHER',
4043 0x2a: 'SPRITE_BUENA',
4044 0x2b: 'SPRITE_SUPER_NERD',
4045 0x2c: 'SPRITE_ROCKER',
4046 0x2d: 'SPRITE_POKEFAN_M',
4047 0x2e: 'SPRITE_POKEFAN_F',
4048 0x2f: 'SPRITE_GRAMPS',
4049 0x30: 'SPRITE_GRANNY',
4050 0x31: 'SPRITE_SWIMMER_GUY',
4051 0x32: 'SPRITE_SWIMMER_GIRL',
4052 0x33: 'SPRITE_BIG_SNORLAX',
4053 0x34: 'SPRITE_SURFING_PIKACHU',
4054 0x35: 'SPRITE_ROCKET',
4055 0x36: 'SPRITE_ROCKET_GIRL',
4056 0x37: 'SPRITE_NURSE',
4057 0x38: 'SPRITE_LINK_RECEPTIONIST',
4058 0x39: 'SPRITE_CLERK',
4059 0x3a: 'SPRITE_FISHER',
4060 0x3b: 'SPRITE_FISHING_GURU',
4061 0x3c: 'SPRITE_SCIENTIST',
4062 0x3d: 'SPRITE_KIMONO_GIRL',
4063 0x3e: 'SPRITE_SAGE',
4064 0x3f: 'SPRITE_UNUSED_GUY',
4065 0x40: 'SPRITE_GENTLEMAN',
4066 0x41: 'SPRITE_BLACK_BELT',
4067 0x42: 'SPRITE_RECEPTIONIST',
4068 0x43: 'SPRITE_OFFICER',
4069 0x44: 'SPRITE_CAL',
4070 0x45: 'SPRITE_SLOWPOKE',
4071 0x46: 'SPRITE_CAPTAIN',
4072 0x47: 'SPRITE_BIG_LAPRAS',
4073 0x48: 'SPRITE_GYM_GUY',
4074 0x49: 'SPRITE_SAILOR',
4075 0x4a: 'SPRITE_BIKER',
4076 0x4b: 'SPRITE_PHARMACIST',
4077 0x4c: 'SPRITE_MONSTER',
4078 0x4d: 'SPRITE_FAIRY',
4079 0x4e: 'SPRITE_BIRD',
4080 0x4f: 'SPRITE_DRAGON',
4081 0x50: 'SPRITE_BIG_ONIX',
4082 0x51: 'SPRITE_N64',
4083 0x52: 'SPRITE_SUDOWOODO',
4084 0x53: 'SPRITE_SURF',
4085 0x54: 'SPRITE_POKE_BALL',
4086 0x55: 'SPRITE_POKEDEX',
4087 0x56: 'SPRITE_PAPER',
4088 0x57: 'SPRITE_VIRTUAL_BOY',
4089 0x58: 'SPRITE_OLD_LINK_RECEPTIONIST',
4090 0x59: 'SPRITE_ROCK',
4091 0x5a: 'SPRITE_BOULDER',
4092 0x5b: 'SPRITE_SNES',
4093 0x5c: 'SPRITE_FAMICOM',
4094 0x5d: 'SPRITE_FRUIT_TREE',
4095 0x5e: 'SPRITE_GOLD_TROPHY',
4096 0x5f: 'SPRITE_SILVER_TROPHY',
4097 0x60: 'SPRITE_KRIS',
4098 0x61: 'SPRITE_KRIS_BIKE',
4099 0x62: 'SPRITE_KURT_OUTSIDE',
4100 0x63: 'SPRITE_SUICUNE',
4101 0x64: 'SPRITE_ENTEI',
4102 0x65: 'SPRITE_RAIKOU',
4103 0x66: 'SPRITE_STANDING_YOUNGSTER',
4104 }
4105
4106 pokemon_sprites = {
4107 0x80: 'SPRITE_UNOWN',
4108 0x81: 'SPRITE_GEODUDE',
4109 0x82: 'SPRITE_GROWLITHE',
4110 0x83: 'SPRITE_WEEDLE',
4111 0x84: 'SPRITE_SHELLDER',
4112 0x85: 'SPRITE_ODDISH',
4113 0x86: 'SPRITE_GENGAR',
4114 0x87: 'SPRITE_ZUBAT',
4115 0x88: 'SPRITE_MAGIKARP',
4116 0x89: 'SPRITE_SQUIRTLE',
4117 0x8a: 'SPRITE_TOGEPI',
4118 0x8b: 'SPRITE_BUTTERFREE',
4119 0x8c: 'SPRITE_DIGLETT',
4120 0x8d: 'SPRITE_POLIWAG',
4121 0x8e: 'SPRITE_PIKACHU',
4122 0x8f: 'SPRITE_CLEFAIRY',
4123 0x90: 'SPRITE_CHARMANDER',
4124 0x91: 'SPRITE_JYNX',
4125 0x92: 'SPRITE_STARMIE',
4126 0x93: 'SPRITE_BULBASAUR',
4127 0x94: 'SPRITE_JIGGLYPUFF',
4128 0x95: 'SPRITE_GRIMER',
4129 0x96: 'SPRITE_EKANS',
4130 0x97: 'SPRITE_PARAS',
4131 0x98: 'SPRITE_TENTACOOL',
4132 0x99: 'SPRITE_TAUROS',
4133 0x9a: 'SPRITE_MACHOP',
4134 0x9b: 'SPRITE_VOLTORB',
4135 0x9c: 'SPRITE_LAPRAS',
4136 0x9d: 'SPRITE_RHYDON',
4137 0x9e: 'SPRITE_MOLTRES',
4138 0x9f: 'SPRITE_SNORLAX',
4139 0xa0: 'SPRITE_GYARADOS',
4140 0xa1: 'SPRITE_LUGIA',
4141 0xa2: 'SPRITE_HO_OH',
4142 }
4143
4144 variable_sprites = {
4145 0xe0: 'SPRITE_DAYCARE_MON_1',
4146 0xe1: 'SPRITE_DAYCARE_MON_2',
4147 0xf0: 'SPRITE_VARS',
4148 0xf0: 'SPRITE_CONSOLE',
4149 0xf1: 'SPRITE_DOLL_1',
4150 0xf2: 'SPRITE_DOLL_2',
4151 0xf3: 'SPRITE_BIG_DOLL',
4152 0xf4: 'SPRITE_WEIRD_TREE',
4153 0xf5: 'SPRITE_OLIVINE_RIVAL',
4154 0xf6: 'SPRITE_AZALEA_ROCKET',
4155 0xf7: 'SPRITE_FUSCHIA_GYM_1',
4156 0xf8: 'SPRITE_FUSCHIA_GYM_2',
4157 0xf9: 'SPRITE_FUSCHIA_GYM_3',
4158 0xfa: 'SPRITE_FUSCHIA_GYM_4',
4159 0xfb: 'SPRITE_COPYCAT',
4160 0xfc: 'SPRITE_JANINE_IMPERSONATOR',
4161 }
4162
4163 def to_asm(self):
4164 if self.byte in self.sprites.keys():
4165 return self.sprites[self.byte]
4166 if self.byte in self.pokemon_sprites.keys():
4167 return self.sprites[self.byte]
4168 if self.byte in self.variable_sprites.keys():
4169 return self.sprites[self.byte]
4170 return SingleByteParam.to_asm(self)
4171
4172
4173
4174class PeopleEvent(Command):
4175 size = people_event_byte_size
4176 macro_name = "person_event"
4177 base_label = "PeopleEvent_"
4178 override_byte_check = True
4179 param_types = {
4180 0: {"name": "sprite", "class": SpriteParam},
4181 1: {"name": "y from top+4", "class": DecimalParam},
4182 2: {"name": "x from top+4", "class": DecimalParam},
4183 3: {"name": "facing", "class": HexByte},
4184 4: {"name": "movement", "class": HexByte},
4185 5: {"name": "clock_hour", "class": DecimalParam},
4186 6: {"name": "clock_daytime", "class": DecimalParam},
4187 7: {"name": "color_function", "class": HexByte},
4188 8: {"name": "sight_range", "class": DecimalParam},
4189 9: {"name": "pointer", "class": PointerLabelParam}, # or ScriptPointerLabelParam or ItemLabelParam
4190 10: {"name": "event flag", "class": EventFlagParam},
4191 }
4192
4193 def xto_asm(self):
4194 output = "\n; person-event\n; picture, y, x, facing, movement, clock_hour, clock_daytime, color_function, sight_range\n"
4195 output += "db $%.2x, %d, %d, $%.2x, $%.2x, %d, %d, $%.2x, %d\n" % (self.params[0].byte, self.params[1].byte, self.params[2].byte, self.params[3].byte, self.params[4].byte, self.params[5].byte, self.params[6].byte, self.params[7].byte, self.params[8].byte)
4196 output += "; pointer\ndw %s\n" % (self.params[9].to_asm())
4197 output += "; event flag\ndw %s" % (self.params[10].to_asm())
4198 return output
4199
4200 def __init__(self, address, id, bank=None, map_group=None, map_id=None, debug=False, label=None, force=False):
4201 if not is_valid_address(address):
4202 raise exceptions.AddressException(
4203 "PeopleEvent must be given a valid address (but got {0})."
4204 .format(hex(address))
4205 )
4206 self.address = address
4207 self.last_address = address + people_event_byte_size
4208 self.id = id
4209 self.bank = bank
4210 if not label:
4211 label = self.base_label + hex(address)
4212 self.label = Label(name=label, address=address, object=self)
4213 self.map_group = map_group
4214 self.map_id = map_id
4215 self.debug = debug
4216 self.force = force
4217 self.params = {}
4218 self.dependencies = None
4219 # PeopleEvent should probably not be in the global script_parse_table
4220 #script_parse_table[self.address : self.last_address] = self
4221 self.parse()
4222
4223 def parse(self):
4224 address = self.address
4225 bank = self.bank
4226
4227 color_function_byte = None
4228 lower_bits = None
4229 higher_bits = None
4230 is_regular_script = None
4231 is_give_item = None
4232 is_trainer = None
4233
4234 self.params = {}
4235 current_address = self.address
4236 i = 0
4237 self.size = 1
4238 color_function_byte = None
4239 for (key, param_type) in self.param_types.items():
4240 if i == 9:
4241 if is_give_item:
4242 name = "item_fragment_pointer"
4243 klass = ItemFragmentParam
4244 elif is_regular_script:
4245 name = "script_pointer"
4246 klass = ScriptPointerLabelParam
4247 elif is_trainer:
4248 name = "trainer"
4249 #klass = MultiByteParam
4250 klass = TrainerFragmentParam
4251 else:
4252 name = "unknown"
4253 klass = MultiByteParam
4254 else:
4255 name = param_type["name"]
4256 klass = param_type["class"]
4257 obj = klass(address=current_address, name=name, debug=self.debug, force=self.force, map_group=self.map_group, map_id=self.map_id, bank=self.bank)
4258 self.params[i] = obj
4259 if i == 7:
4260 color_function_byte = ord(rom[current_address])
4261 lower_bits = color_function_byte & 0xF
4262 higher_bits = color_function_byte >> 4
4263 is_regular_script = lower_bits == 00
4264 is_give_item = lower_bits == 0o1
4265 is_trainer = lower_bits == 0o2
4266 current_address += obj.size
4267 self.size += obj.size
4268 i += 1
4269 self.last_address = current_address
4270 self.is_trainer = is_trainer
4271 self.is_give_item = is_give_item
4272 self.is_regular_script = is_regular_script
4273 self.y = self.params[1].byte
4274 self.x = self.params[2].byte
4275 self.facing = self.params[3].byte
4276 self.movement = self.params[4].byte
4277 self.clock_hour = self.params[5].byte
4278 self.clock_daytime = self.params[6].byte
4279 self.color_function = self.params[7].byte
4280 self.sight_range = self.params[8].byte
4281 self.pointer = self.params[9].bytes
4282 self.event_flag = self.params[10].bytes
4283 return True
4284
4285
4286all_people_events = []
4287def parse_people_events(address, people_event_count, bank=None, map_group=None, map_id=None, debug=False, force=False):
4288 # people_event_byte_size
4289 people_events = []
4290 current_address = address
4291 id = 0
4292 for each in range(people_event_count):
4293 pevent = PeopleEvent(address=current_address, id=id, bank=bank, map_group=map_group, map_id=map_id, debug=debug, force=force)
4294 current_address += people_event_byte_size
4295 people_events.append(pevent)
4296 id += 1
4297 all_people_events.extend(people_events)
4298 return people_events
4299
4300def old_parse_people_event_bytes(some_bytes, address=None, map_group=None, map_id=None, debug=True):
4301 """parse some number of people-events from the data
4302 see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Scripthdr
4303
4304 For example, map 1.1 (group 1 map 1) has four person-events.
4305
4306 37 05 07 06 00 FF FF 00 00 02 40 FF FF
4307 3B 08 0C 05 01 FF FF 00 00 05 40 FF FF
4308 3A 07 06 06 00 FF FF A0 00 08 40 FF FF
4309 29 05 0B 06 00 FF FF 00 00 0B 40 FF FF
4310 """
4311 assert len(some_bytes) % people_event_byte_size == 0, "wrong number of bytes"
4312
4313 # address is not actually required for this function to work...
4314 bank = None
4315 if address:
4316 bank = pokemontools.pointers.calculate_bank(address)
4317
4318 people_events = []
4319 for bytes in pokemontools.helpers.grouper(some_bytes, count=people_event_byte_size):
4320 pict = int(bytes[0], 16)
4321 y = int(bytes[1], 16) # y from top + 4
4322 x = int(bytes[2], 16) # x from left + 4
4323 face = int(bytes[3], 16) # 0-4 for regular, 6-9 for static facing
4324 move = int(bytes[4], 16)
4325 clock_time_byte1 = int(bytes[5], 16)
4326 clock_time_byte2 = int(bytes[6], 16)
4327 color_function_byte = int(bytes[7], 16) # Color|Function
4328 trainer_sight_range = int(bytes[8], 16)
4329
4330 lower_bits = color_function_byte & 0xF
4331 #lower_bits_high = lower_bits >> 2
4332 #lower_bits_low = lower_bits & 3
4333 higher_bits = color_function_byte >> 4
4334 #higher_bits_high = higher_bits >> 2
4335 #higher_bits_low = higher_bits & 3
4336
4337 is_regular_script = lower_bits == 00
4338 # pointer points to script
4339 is_give_item = lower_bits == 0o1
4340 # pointer points to [Item no.][Amount]
4341 is_trainer = lower_bits == 0o2
4342 # pointer points to trainer header
4343
4344 # goldmap called these next two bytes "text_block" and "text_bank"?
4345 script_pointer_byte1 = int(bytes[9], 16)
4346 script_pointer_byte2 = int(bytes[10], 16)
4347 script_pointer = script_pointer_byte1 + (script_pointer_byte2 << 8)
4348 # calculate the full address by assuming it's in the current bank
4349 # but what if it's not in the same bank?
4350 extra_portion = {}
4351 if bank:
4352 ptr_address = pokemontools.pointers.calculate_pointer(script_pointer, bank)
4353 if is_regular_script:
4354 logging.debug(
4355 "parsing a person-script at x={x} y={y} address={address}"
4356 .format(
4357 x=(x-4),
4358 y=(y-4),
4359 address=hex(ptr_address),
4360 )
4361 )
4362 script = parse_script_engine_script_at(ptr_address, map_group=map_group, map_id=map_id)
4363 extra_portion = {
4364 "script_address": ptr_address,
4365 "script": script,
4366 "event_type": "script",
4367 }
4368 if is_give_item:
4369 logging.debug("not parsing give item event.. [item id][quantity]")
4370 extra_portion = {
4371 "event_type": "give_item",
4372 "give_item_data_address": ptr_address,
4373 "item_id": ord(rom[ptr_address]),
4374 "item_qty": ord(rom[ptr_address+1]),
4375 }
4376 if is_trainer:
4377 logging.debug(
4378 "parsing a trainer (person-event) at x={x} y={y}"
4379 .format(x=x, y=y)
4380 )
4381 parsed_trainer = parse_trainer_header_at(ptr_address, map_group=map_group, map_id=map_id)
4382 extra_portion = {
4383 "event_type": "trainer",
4384 "trainer_data_address": ptr_address,
4385 "trainer_data": parsed_trainer,
4386 }
4387
4388 # XXX not sure what's going on here
4389 # event flag (hidden if set)
4390 # note: FFFF for none
4391 when_byte = int(bytes[11], 16)
4392 hide = int(bytes[12], 16)
4393
4394 event_flag_byte2 = int(bytes[11], 16)
4395 event_flag_byte1 = int(bytes[12], 16)
4396 event_flag = event_flag_byte1 + (event_flag_byte2 << 8)
4397
4398 people_event = {
4399 "pict": pict,
4400 "y": y, # y from top + 4
4401 "x": x, # x from left + 4
4402 "face": face, # 0-4 for regular, 6-9 for static facing
4403 "move": move,
4404 "clock_time": {"1": clock_time_byte1,
4405 "2": clock_time_byte2}, # clock/time setting byte 1
4406 "color_function_byte": color_function_byte, # Color|Function
4407 "trainer_sight_range": trainer_sight_range, # trainer range of sight
4408 "script_pointer": {"1": script_pointer_byte1,
4409 "2": script_pointer_byte2},
4410
4411 #"text_block": text_block, # script pointer byte 1
4412 #"text_bank": text_bank, # script pointer byte 2
4413 "when_byte": when_byte, # event flag (hidden if set)
4414 "hide": hide, # note: FFFF for none
4415
4416 "is_trainer": is_trainer,
4417 "is_regular_script": is_regular_script,
4418 "is_give_item": is_give_item,
4419 }
4420 people_event.update(extra_portion)
4421 people_events.append(people_event)
4422 return people_events
4423
4424
4425class SignpostRemoteBase(object):
4426 def __init__(self, address, bank=None, map_group=None, map_id=None, signpost=None, debug=False, label=None):
4427 self.address = address
4428 self.last_address = address + self.size
4429 script_parse_table[self.address : self.last_address] = self
4430 self.bank = bank
4431 self.map_group = map_group
4432 self.map_id = map_id
4433 self.signpost = signpost
4434 self.debug = debug
4435 self.params = []
4436 if not label:
4437 label = self.base_label + hex(address)
4438 self.label = Label(name=label, address=address, object=self)
4439 self.dependencies = None
4440 self.parse()
4441
4442 def get_dependencies(self, recompute=False, global_dependencies=set()):
4443 dependencies = []
4444 if self.dependencies != None and not recompute:
4445 global_dependencies.update(self.dependencies)
4446 return self.dependencies
4447 for p in self.params:
4448 deps = p.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
4449 dependencies.extend(deps)
4450 self.dependencies = dependencies
4451 return dependencies
4452
4453 def to_asm(self):
4454 """very similar to Command.to_asm"""
4455 if len(self.params) == 0:
4456 return ""
4457 #output = ", ".join([p.to_asm() for p in self.params])
4458 output = ""
4459 for param in self.params:
4460 if issubclass(param.__class__, SingleByteParam):
4461 output += "db "
4462 else:
4463 output += "dw "
4464 output += param.to_asm() + "\n"
4465 return output
4466
4467
4468class SignpostRemoteScriptChunk(SignpostRemoteBase):
4469 """
4470 a signpost might point to [Event flag (2byte)][2byte pointer to script]
4471 """
4472 base_label = "SignpostRemoteScript_"
4473 size = 4
4474
4475 def parse(self):
4476 address = self.address
4477 bank = self.bank
4478
4479 #event_flag_byte1 = ord(rom[address])
4480 #event_flag_byte2 = ord(rom[address+1])
4481 event_flag = MultiByteParam(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4482 self.params.append(event_flag)
4483
4484 #script_address = calculate_pointer_from_bytes_at(address+2, bank=bank)
4485 #script = parse_script_engine_script_at(script_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4486 script_param = ScriptPointerLabelParam(address=address+2, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=False)
4487 self.params.append(script_param)
4488 self.script = script_param.script
4489 self.signpost.remote_script = self.script
4490
4491 #self.event_flag_bytes = [event_flag_byte1, event_flag_byte2]
4492 #self.script_address = script_address
4493 #self.script = script
4494
4495
4496class SignpostRemoteItemChunk(SignpostRemoteBase):
4497 """
4498 a signpost might point to [Event flag (2byte)][Item no.]
4499 """
4500 base_label = "SignpostRemoteItem_"
4501 size = 3
4502
4503 def parse(self):
4504 address = self.address
4505 bank = self.bank
4506
4507 event_flag = MultiByteParam(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4508 self.params.append(event_flag)
4509
4510 item = ItemLabelByte(address=address+2)
4511 self.params.append(item)
4512 self.item = item
4513
4514
4515class SignpostRemoteUnknownChunk(SignpostRemoteBase):
4516 """
4517 a signpost might point to [Event flag (2byte)][??]
4518 """
4519 base_label = "SignpostRemoteUnknown_"
4520 size = 3
4521
4522 def parse(self):
4523 address = self.address
4524 bank = self.bank
4525
4526 event_flag = MultiByteParam(address=address, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4527 self.params.append(event_flag)
4528
4529 byte = SingleByteParam(address=address+2)
4530 self.params.append(byte)
4531
4532
4533# this could potentially extend Command
4534# see how class Warp does this
4535class Signpost(Command):
4536 """parse some number of signposts from the data
4537
4538 [Y position][X position][Function][Script pointer (2byte)]
4539
4540 functions:
4541 00 Sign can be read from all directions
4542 script pointer to: script
4543 01 Sign can only be read from below
4544 script pointer to: script
4545 02 Sign can only be read from above
4546 script pointer to: script
4547 03 Sign can only be read from right
4548 script pointer to: script
4549 04 Sign can only be read from left
4550 script pointer to: script
4551 05 If event flag is set then pointer is interpreted
4552 script pointer to: [event flag (2byte)][2byte pointer to script]
4553 06 If event flag is not set then pointer is interpreted
4554 script pointer to: [event flag (2byte)][2byte pointer to script]
4555 07 If event flag is set then item is given
4556 script pointer to: [event flag (2byte)][Item no.]
4557 08 No Action
4558 script pointer to: [event flag (2byte)][??]
4559 """
4560 size = 5
4561 macro_name = "signpost"
4562 override_byte_check = True
4563
4564 # preprocessor uses this
4565 param_types = {
4566 0: {"name": "y", "class": DecimalParam},
4567 1: {"name": "x", "class": DecimalParam},
4568 2: {"name": "function", "class": HexByte},
4569 3: {"name": "pointer", "class": PointerLabelParam},
4570 }
4571
4572 def __init__(self, address, id, bank=None, map_group=None, map_id=None, debug=True, label=None):
4573 self.address = address
4574 self.id = id
4575 if label == None:
4576 label = "UnknownSignpost_"+str(map_group)+"Map"+str(map_id)+"_"+hex(address)
4577 self.label = Label(name=label, address=address, object=self)
4578 self.map_group = map_group
4579 self.map_id = map_id
4580 self.debug = debug
4581 self.bank = bank
4582 self.last_address = self.address + self.size
4583 self.y, self.x, self.func = None, None, None
4584 # Signpost should probably not be in the globals
4585 #script_parse_table[self.address : self.last_address] = self
4586 self.remotes = []
4587 self.params = []
4588 self.dependencies = None
4589 self.parse()
4590
4591 def parse(self):
4592 """parse just one signpost"""
4593 address = self.address
4594 bank = self.bank
4595 self.last_address = self.address + self.size
4596 bytes = rom.interval(self.address, self.size) #, signpost_byte_size)
4597
4598 self.y = int(bytes[0], 16)
4599 self.x = int(bytes[1], 16)
4600 self.func = int(bytes[2], 16)
4601 y, x, func = self.y, self.x, self.func
4602
4603 # y
4604 self.params.append(DecimalParam(address=address, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug))
4605 # x
4606 self.params.append(DecimalParam(address=address+1, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug))
4607 # func
4608 self.params.append(HexByte(address=address+2, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug))
4609
4610 output = "******* parsing signpost "+str(self.id)+" at: "
4611 output += "x="+str(x)+" y="+str(y)+" on map_group="
4612 output += str(self.map_group)+" map_id="+str(self.map_id)
4613
4614 if func in [0, 1, 2, 3, 4]:
4615 # signpost's script pointer points to a script
4616 script_ptr_byte1 = int(bytes[3], 16)
4617 script_ptr_byte2 = int(bytes[4], 16)
4618 script_pointer = script_ptr_byte1 + (script_ptr_byte2 << 8)
4619
4620 script_address = pokemontools.pointers.calculate_pointer(script_pointer, bank)
4621 output += " script@"+hex(script_address)
4622 logging.debug(output)
4623
4624 param = ScriptPointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=False)
4625 self.params.append(param)
4626 param = script_parse_table[param.parsed_address]
4627 param.label = Label(address=param.address, object=param, name="Map"+map_names[self.map_group][self.map_id]["label"]+"Signpost"+str(self.id)+"Script")
4628
4629 #self.script_address = script_address
4630 #self.script = script
4631 elif func in [5, 6]:
4632 # signpost's script pointer points to [event flag (2byte)][2byte pointer to script]
4633 ptr_byte1 = int(bytes[3], 16)
4634 ptr_byte2 = int(bytes[4], 16)
4635 pointer = ptr_byte1 + (ptr_byte2 << 8)
4636 address = pokemontools.pointers.calculate_pointer(pointer, bank)
4637
4638 event_flag_byte1 = ord(rom[address])
4639 event_flag_byte2 = ord(rom[address+1])
4640 script_ptr_byte1 = ord(rom[address+2])
4641 script_ptr_byte2 = ord(rom[address+3])
4642 script_address = calculate_pointer_from_bytes_at(address+2, bank=bank)
4643
4644 output += " remote_chunk@"+hex(address)+" remote_script@"+hex(script_address)
4645 logging.debug(output)
4646
4647 r1 = SignpostRemoteScriptChunk(address, signpost=self, \
4648 bank=self.bank, map_group=self.map_group, map_id=self.map_id, \
4649 debug=self.debug)
4650 self.remotes.append(r1)
4651
4652 # give a better label to the SignpostRemoteScriptChunk
4653 r1.label = Label(address=r1.address, object=r1, name="Map"+map_names[self.map_group][self.map_id]["label"]+"SignpostPtr"+str(self.id))
4654
4655 mb = PointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4656 self.params.append(mb)
4657
4658 # update the remote script address
4659 param = script_parse_table[script_address]
4660 param.label = Label(address=param.address, object=param, name="Map"+map_names[self.map_group][self.map_id]["label"]+"Signpost"+str(self.id)+"Script")
4661
4662 elif func == 7:
4663 # signpost's script pointer points to [event flag (2byte)][Item no.]
4664 ptr_byte1 = int(bytes[3], 16)
4665 ptr_byte2 = int(bytes[4], 16)
4666 pointer = ptr_byte1 + (ptr_byte2 << 8)
4667 address = pokemontools.pointers.calculate_pointer(pointer, bank)
4668
4669 item_id = ord(rom[address+2])
4670 output += " item_id="+str(item_id)
4671 logging.debug(output)
4672
4673 r1 = SignpostRemoteItemChunk(address, signpost=self, \
4674 bank=self.bank, map_group=self.map_group, map_id=self.map_id, \
4675 debug=self.debug)
4676 self.remotes.append(r1)
4677 r1.label = Label(address=r1.address, object=r1, name="Map"+map_names[self.map_group][self.map_id]["label"]+"SignpostItem"+str(self.id))
4678
4679 mb = PointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4680 self.params.append(mb)
4681
4682 #event_flag_byte1 = ord(rom[address])
4683 #event_flag_byte2 = ord(rom[address+1])
4684 #self.event_flag_bytes = [event_flag_byte1, event_flag_byte2]
4685 #self.item_id = item_id
4686 elif func == 8:
4687 # signpost's script pointer points to [event flag (2byte)][??]
4688 ptr_byte1 = int(bytes[3], 16)
4689 ptr_byte2 = int(bytes[4], 16)
4690 pointer = ptr_byte1 + (ptr_byte2 << 8)
4691 address = pokemontools.pointers.calculate_pointer(pointer, bank)
4692
4693 output += " remote unknown chunk at="+hex(address)
4694 logging.debug(output)
4695
4696 r1 = SignpostRemoteUnknownChunk(address, signpost=self, \
4697 bank=self.bank, map_group=self.map_group, map_id=self.map_id, \
4698 debug=self.debug)
4699 self.remotes.append(r1)
4700
4701 mb = PointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4702 self.params.append(mb)
4703 else:
4704 raise Exception("unknown signpost type byte="+hex(func) + " signpost@"+hex(self.address))
4705
4706 def get_dependencies(self, recompute=False, global_dependencies=set()):
4707 dependencies = []
4708 if self.dependencies != None and not recompute:
4709 global_dependencies.update(self.dependencies)
4710 return self.dependencies
4711 for p in self.params:
4712 dependencies.extend(p.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
4713 self.dependencies = dependencies
4714 return dependencies
4715
4716 def to_asm(self):
4717 output = self.macro_name + " "
4718 if self.params == []:
4719 raise Exception("signpost has no params?")
4720 output += ", ".join([p.to_asm() for p in self.params])
4721 return output
4722
4723all_signposts = []
4724def parse_signposts(address, signpost_count, bank=None, map_group=None, map_id=None, debug=True):
4725 if bank == None:
4726 raise Exception("signposts need to know their bank")
4727 signposts = []
4728 current_address = address
4729 id = 0
4730 for each in range(signpost_count):
4731 signpost = Signpost(current_address, id, bank=bank, map_group=map_group, map_id=map_id, debug=debug)
4732 current_address += signpost_byte_size # i think ??
4733 signposts.append(signpost)
4734 id += 1
4735 all_signposts.extend(signposts)
4736 return signposts
4737
4738
4739class LandmarkParam(SingleByteParam):
4740 landmarks = [
4741 'SPECIAL_MAP',
4742
4743 # johto
4744 'NEW_BARK_TOWN',
4745 'ROUTE_29',
4746 'CHERRYGROVE_CITY',
4747 'ROUTE_30',
4748 'ROUTE_31',
4749 'VIOLET_CITY',
4750 'SPROUT_TOWER',
4751 'ROUTE_32',
4752 'RUINS_OF_ALPH',
4753 'UNION_CAVE',
4754 'ROUTE_33',
4755 'AZALEA_TOWN',
4756 'SLOWPOKE_WELL',
4757 'ILEX_FOREST',
4758 'ROUTE_34',
4759 'GOLDENROD_CITY',
4760 'RADIO_TOWER',
4761 'ROUTE_35',
4762 'NATIONAL_PARK',
4763 'ROUTE_36',
4764 'ROUTE_37',
4765 'ECRUTEAK_CITY',
4766 'TIN_TOWER',
4767 'BURNED_TOWER',
4768 'ROUTE_38',
4769 'ROUTE_39',
4770 'OLIVINE_CITY',
4771 'LIGHTHOUSE',
4772 'BATTLE_TOWER',
4773 'ROUTE_40',
4774 'WHIRL_ISLANDS',
4775 'ROUTE_41',
4776 'CIANWOOD_CITY',
4777 'ROUTE_42',
4778 'MT_MORTAR',
4779 'MAHOGANY_TOWN',
4780 'ROUTE_43',
4781 'LAKE_OF_RAGE',
4782 'ROUTE_44',
4783 'ICE_PATH',
4784 'BLACKTHORN_CITY',
4785 'DRAGONS_DEN',
4786 'ROUTE_45',
4787 'DARK_CAVE',
4788 'ROUTE_46',
4789 'SILVER_CAVE',
4790
4791 # kanto
4792 'PALLET_TOWN',
4793 'ROUTE_1',
4794 'VIRIDIAN_CITY',
4795 'ROUTE_2',
4796 'PEWTER_CITY',
4797 'ROUTE_3',
4798 'MT_MOON',
4799 'ROUTE_4',
4800 'CERULEAN_CITY',
4801 'ROUTE_24',
4802 'ROUTE_25',
4803 'ROUTE_5',
4804 'UNDERGROUND',
4805 'ROUTE_6',
4806 'VERMILION_CITY',
4807 'DIGLETTS_CAVE',
4808 'ROUTE_7',
4809 'ROUTE_8',
4810 'ROUTE_9',
4811 'ROCK_TUNNEL',
4812 'ROUTE_10',
4813 'POWER_PLANT',
4814 'LAVENDER_TOWN',
4815 'LAV_RADIO_TOWER',
4816 'CELADON_CITY',
4817 'SAFFRON_CITY',
4818 'ROUTE_11',
4819 'ROUTE_12',
4820 'ROUTE_13',
4821 'ROUTE_14',
4822 'ROUTE_15',
4823 'ROUTE_16',
4824 'ROUTE_17',
4825 'ROUTE_18',
4826 'FUCHSIA_CITY',
4827 'ROUTE_19',
4828 'ROUTE_20',
4829 'SEAFOAM_ISLANDS',
4830 'CINNABAR_ISLAND',
4831 'ROUTE_21',
4832 'ROUTE_22',
4833 'VICTORY_ROAD',
4834 'ROUTE_23',
4835 'INDIGO_PLATEAU',
4836 'ROUTE_26',
4837 'ROUTE_27',
4838 'TOHJO_FALLS',
4839 'ROUTE_28',
4840 'FAST_SHIP',
4841 ]
4842
4843 def to_asm(self):
4844 if self.byte < len(self.landmarks):
4845 return self.landmarks[self.byte]
4846 return SingleByteParam.to_asm(self)
4847
4848
4849class SongParam(SingleByteParam):
4850 def to_asm(self):
4851 if self.byte < len(song_names):
4852 return 'MUSIC_' + song_names[self.byte].upper().replace(' ','_')
4853 return SingleByteParam.to_asm(self)
4854
4855
4856class TimeOfDayParam(DecimalParam):
4857 times = ['MORN', 'DAY', 'NITE', 'DARKNESS']
4858 def to_asm(self):
4859 if self.byte < len(self.times):
4860 return self.times[self.byte]
4861 return DecimalParam.to_asm(self)
4862
4863
4864class MapHeader(object):
4865 base_label = "MapHeader_"
4866
4867 def __init__(self, address, map_group=None, map_id=None, debug=True, label=None, bank=0x25):
4868 logging.debug(
4869 "creating a MapHeader at {address} map_group={map_group} map_id={map_id}"
4870 .format(address=hex(address), map_group=map_group, map_id=map_id)
4871 )
4872 self.address = address
4873 self.map_group = map_group
4874 self.map_id = map_id
4875 self.bank = bank
4876 self.debug = debug
4877 self.dependencies = None
4878 label = self.make_label()
4879 self.label = Label(name=label, address=address, object=self)
4880 self.last_address = address + 9
4881 script_parse_table[address : self.last_address] = self
4882 self.parse()
4883
4884 def make_label(self):
4885 return map_names[self.map_group][self.map_id]["label"] + "_MapHeader"
4886
4887 def parse(self):
4888 address = self.address
4889 logging.debug("parsing a MapHeader at {0}".format(hex(address)))
4890 self.bank = HexByte(address=address)
4891 self.tileset = HexByte(address=address+1)
4892 self.permission = DecimalParam(address=address+2)
4893 self.second_map_header_address = pokemontools.pointers.calculate_pointer(ord(rom[address+3])+(ord(rom[address+4])<<8), self.bank.byte)
4894 # TODO: is the bank really supposed to be 0x25 all the time ??
4895 self.second_map_header = SecondMapHeader(self.second_map_header_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4896 all_second_map_headers.append(self.second_map_header)
4897 self.location_on_world_map = LandmarkParam(address=address+5)
4898 self.music = SongParam(address=address+6)
4899 self.time_of_day = TimeOfDayParam(address=address+7)
4900 self.fishing_group = DecimalParam(address=address+8)
4901
4902 def get_dependencies(self, recompute=False, global_dependencies=set()):
4903 if self.dependencies != None and not recompute:
4904 global_dependencies.update(self.dependencies)
4905 return self.dependencies
4906 dependencies = [self.second_map_header]
4907 global_dependencies.add(self.second_map_header)
4908 dependencies.append(self.second_map_header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
4909 self.dependencies = dependencies
4910 return dependencies
4911
4912 def to_asm(self):
4913 output = "; bank, tileset, permission\n"
4914 output += "db " + ", ".join(["BANK(" + self.second_map_header.label.name + ")", self.tileset.to_asm(), self.permission.to_asm()])
4915 output += "\n\n; second map header\n"
4916 output += "dw " + PointerLabelParam(address=self.address+3).to_asm() # TODO: should we include bank=self.bank.byte ??
4917 output += "\n\n; location on world map, music, time of day, fishing group\n"
4918 output += "db " + ", ".join([self.location_on_world_map.to_asm(), self.music.to_asm(), self.time_of_day.to_asm(), self.fishing_group.to_asm()])
4919 return output
4920
4921def parse_map_header_at(address, map_group=None, map_id=None, all_map_headers=None, rom=None, debug=True):
4922 """parses an arbitrary map header at some address"""
4923 logging.debug("parsing a map header at {0}".format(hex(address)))
4924 map_header = MapHeader(address, map_group=map_group, map_id=map_id, debug=debug)
4925 all_map_headers.append(map_header)
4926 return map_header
4927
4928def get_direction(connection_byte, connection_id):
4929 """
4930 Given a connection byte and a connection id, which direction is this
4931 connection?
4932
4933 example:
4934 The 0th connection of $5 is SOUTH and the 1st connection is
4935 EAST.
4936 """
4937 connection_options = [0b1000, 0b0100, 0b0010, 0b0001]
4938 results = ["NORTH", "SOUTH", "WEST", "EAST"]
4939
4940 for option in connection_options:
4941 if (option & connection_byte) == 0:
4942 results[connection_options.index(option)] = ""
4943
4944 # prune results
4945 while "" in results:
4946 results.remove("")
4947
4948 return results[connection_id]
4949
4950class SecondMapHeader(object):
4951 base_label = "SecondMapHeader_"
4952
4953 def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None):
4954 logging.debug("creating a SecondMapHeader at {0}".format(hex(address)))
4955 self.address = address
4956 self.map_group = map_group
4957 self.map_id = map_id
4958 self.debug = debug
4959 self.bank = bank
4960 self.dependencies = None
4961 label = self.make_label()
4962 self.label = Label(name=label, address=address, object=self)
4963
4964 # the minimum number of bytes is 12
4965 self.last_address = address+12
4966 self.size = 12
4967
4968 script_parse_table[address : self.last_address] = self
4969 self.parse()
4970
4971 def make_label(self):
4972 return map_names[self.map_group][self.map_id]["label"] + "_SecondMapHeader"
4973
4974 def parse(self):
4975 address = self.address
4976 bytes = rom.interval(address, second_map_header_byte_size, strings=False)
4977 size = second_map_header_byte_size
4978
4979 # for later
4980 self.connections = []
4981
4982 self.border_block = HexByte(address=address)
4983 self.height = DecimalParam(address=address+1)
4984 self.width = DecimalParam(address=address+2)
4985
4986 # bank appears first
4987 ###self.blockdata_address = PointerLabelBeforeBank(address+3)
4988 self.blockdata_address = calculate_pointer_from_bytes_at(address+3, bank=True)
4989 xyz = script_parse_table[self.blockdata_address]
4990 if xyz == None:
4991 self.blockdata = MapBlockData(self.blockdata_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug, width=self.width, height=self.height)
4992 else:
4993 self.blockdata = xyz
4994
4995 # bank appears first
4996 ###self.script_address = PointerLabelBeforeBank(address+6)
4997 self.script_header_address = calculate_pointer_from_bytes_at(address+6, bank=True)
4998 self.script_header = MapScriptHeader(self.script_header_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
4999 all_map_script_headers.append(self.script_header)
5000
5001 self.event_bank = ord(rom[address+6])
5002 self.event_header_address = calculate_pointer_from_bytes_at(address+9, bank=ord(rom[address+6]))
5003 self.event_header = MapEventHeader(self.event_header_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug)
5004 self.connection_byte = DecimalParam(address=address+11)
5005 all_map_event_headers.append(self.event_header)
5006
5007 self.size = size
5008
5009 if self.connection_byte == 0:
5010 return True
5011
5012 current_address = address+12
5013
5014 # short alias
5015 cb = self.connection_byte.byte
5016
5017 # east = 1, west = 2, south = 4, north = 8 (or'd together)
5018 east = ((cb & 0x1) != 0)
5019 west = ((cb & 0x2) != 0)
5020 south = ((cb & 0x4) != 0)
5021 north = ((cb & 0x8) != 0)
5022 directions = [east, west, south, north]
5023 connection_count = directions.count(True)
5024
5025 for connection in range(0, connection_count):
5026 direction = get_direction(self.connection_byte.byte, connection)
5027 connection = Connection(current_address, direction=direction, map_group=self.map_group, map_id=self.map_id, debug=self.debug, smh=self)
5028 self.connections.append(connection)
5029
5030 # 12 bytes each?
5031 current_address += connection.size
5032
5033 self.last_address = current_address
5034
5035 return True
5036
5037 def get_dependencies(self, recompute=False, global_dependencies=set()):
5038 if self.dependencies != None and not recompute:
5039 global_dependencies.update(self.dependencies)
5040 return self.dependencies
5041 dependencies = [self.script_header, self.event_header, self.blockdata]
5042 global_dependencies.update(dependencies)
5043 dependencies.append(self.script_header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
5044 dependencies.append(self.event_header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
5045 self.dependencies = dependencies
5046 return dependencies
5047
5048 def to_asm(self):
5049 self_constant_label = get_map_constant_label(map_group=self.map_group, map_id=self.map_id, map_internal_ids=self.map_internal_ids)
5050 output = "; border block\n"
5051 output += "db " + self.border_block.to_asm() + "\n\n"
5052 output += "; height, width\n"
5053 output += "db " + self_constant_label + "_HEIGHT, " + self_constant_label + "_WIDTH\n\n"
5054 output += "; blockdata (bank-then-pointer)\n"
5055 thing = ScriptPointerLabelBeforeBank(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug).to_asm()
5056 output += "dbw " + thing.split(", ")[0] + ", "+thing.split(", ")[1] + "\n\n"
5057 output += "; script header (bank-then-pointer)\n"
5058 thing = ScriptPointerLabelBeforeBank(address=self.address+6, map_group=self.map_group, map_id=self.map_id, debug=self.debug).to_asm()
5059 output += "dbw " + thing.split(", ")[0] + ", " + thing.split(", ")[1] + "\n\n"
5060 output += "; map event header (bank-then-pointer)\n"
5061 output += "dw " + PointerLabelParam(address=self.address+9, bank=self.event_bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug).to_asm() + "\n\n"
5062
5063 output += "; connections\n"
5064 dir_results = []
5065 connection_options = [0b1000, 0b0100, 0b0010, 0b0001]
5066 dirs = ["NORTH", "SOUTH", "WEST", "EAST"]
5067 for (id, each) in enumerate(dirs):
5068 if ((connection_options[id] & self.connection_byte.byte) != 0):
5069 dir_results.append(each)
5070 output += "db " + " | ".join(dir_results)
5071 if len(dir_results) == 0:
5072 output += "0"
5073
5074 if self.connection_byte.byte == 0 or len(dir_results) == 0:
5075 return output
5076 else:
5077 output += "\n\n"
5078
5079 connections = "\n\n".join([connection.to_asm() for connection in self.connections])
5080 output += connections
5081
5082 return output
5083
5084strip_pointer_data = []
5085strip_destination_data = []
5086connections = []
5087wrong_norths = []
5088wrong_easts = []
5089wrong_souths = []
5090wrong_wests = []
5091
5092class Connection(object):
5093 size = 12
5094
5095 def __init__(self, address, direction=None, map_group=None, map_id=None, debug=True, smh=None):
5096 self.address = address
5097 self.direction = direction.lower()
5098 self.map_group = map_group
5099 self.map_id = map_id
5100 self.debug = debug
5101 self.smh = smh
5102 self.last_address = address + self.size
5103 connections.append(self)
5104
5105 self.parse()
5106
5107 def parse(self):
5108 current_address = self.address
5109
5110 is_vertical = ((self.direction == "north") or (self.direction == "south"))
5111 is_horizontal = ((self.direction == "east") or (self.direction == "west"))
5112
5113 connected_map_group_id = ord(rom[current_address])
5114 self.connected_map_group_id = connected_map_group_id
5115 current_address += 1
5116
5117 connected_map_id = ord(rom[current_address])
5118 self.connected_map_id = connected_map_id
5119 current_address += 1
5120
5121 # window (use JohtoMap's calculation, not this)
5122 # up: C701h + height_of_connected_map * (width_of_connected_map + 6)
5123 # left: C706h + 2 * width_of_connected_map
5124 # down/right: C707h + width_of_connected_map
5125 #
5126 # 2 bytes (flipped) - X position of starting point for intermediate
5127 # tiles (scrolls through connected map line-by-line. this way you can
5128 # change Y position also)
5129 #
5130 # According to JohtoMap, the calculation for tile data pointer is:
5131 # int p = otherMap.tileDataLocation;
5132 # int h = (otherMap.width - otherMap.height)
5133 # if (h > 0)
5134 # p += (h * otherMap.height) + (otherMap.height * 3) + (otherMap.height + 3)
5135 # else
5136 # p += (otherMap.height * otherMap.width) - (otherMap.width * 3);
5137 # c.tileDataPointer = gb.Get2BytePointer(p);
5138 #
5139 # tauwasser calls this "connection strip pointer"
5140 tile_data_pointer = ord(rom[current_address]) + (ord(rom[current_address+1]) << 8)
5141 strip_pointer = tile_data_pointer
5142 self.strip_pointer = tile_data_pointer
5143 current_address += 2
5144
5145 # 10:19 <comet> memoryotherpointer is <comet> what johtomap calls OMDL (in ram where the tiles start getting pulled from the other map)
5146 memory_other_pointer = ord(rom[current_address]) + (ord(rom[current_address+1]) << 8)
5147 # 10:42 <comet> it would be a good idea to rename otherpointer strippointer or striploc
5148 # 10:42 <comet> since thats more accurate
5149 # 11:05 <comet> Above: C803h + xoffset
5150 # 11:05 <comet> Below: C803h + (m.height + 3) * (m.width + 6) + xoffset
5151 # 11:05 <comet> Left: C800h + (m.width + 6) * (yoffset + 3)
5152 # 11:05 <comet> Right: C7FDh + (m.width + 6) * (yoffset + 4)
5153 #
5154 # tauwasser calls this "connection strip destination" and lin calls this "memoryOtherPointer"
5155 # Points to the upper left block of the connection strip
5156 # (The bank the Blockdata is in, is loaded out of the Mapheader of the connected Map.)
5157 # The connection strip is always 3 Blocks high resp. wide
5158 # (depending on the connection's direction)
5159 strip_destination = memory_other_pointer
5160 self.strip_destination = memory_other_pointer
5161 current_address += 2
5162
5163 # length of the connection strip in blocks
5164 connection_strip_length = ord(rom[current_address])
5165 current_address += 1
5166 connected_map_width = ord(rom[current_address])
5167 current_address += 1
5168
5169 self.connection_strip_length = connection_strip_length
5170 self.connected_map_width = connected_map_width
5171
5172 y_position_after_map_change = ord(rom[current_address])
5173 yoffset = y_position_after_map_change
5174 current_address += 1
5175
5176 x_position_after_map_change = ord(rom[current_address])
5177 xoffset = x_position_after_map_change
5178 current_address += 1
5179
5180 # in pokered these were called alignments? same thing?
5181 self.yoffset = y_position_after_map_change
5182 self.xoffset = x_position_after_map_change
5183
5184 # tauwasser calls this "window" and lin calls this "memoryCurrentPointer"
5185 # Position of the upper left block after entering the Map
5186 #
5187 # tauwasser's formula for windows:
5188 # Above: C701h + Height_of_connected_map * (Width_of_connected_map + 6)
5189 # Left: C706h + 2 * Width_of_connected_map
5190 # Below/Right: C707h + Width_of_connected_map
5191 window = ord(rom[current_address]) + (ord(rom[current_address+1]) << 8)
5192 current_address += 2
5193
5194 self.window = window
5195
5196 current_map_height = self.smh.height.byte
5197 current_map_width = self.smh.width.byte
5198
5199 if "header_new" in map_names[connected_map_group_id][connected_map_id].keys():
5200 # the below code ensures that there's an equation to handle strip_pointer
5201
5202 ldirection = self.direction.lower()
5203 connected_map_header = map_names[connected_map_group_id][connected_map_id]["header_new"]
5204 connected_second_map_header = connected_map_header.second_map_header
5205 connected_map_height = connected_second_map_header.height.byte
5206 connected_map_width = connected_second_map_header.width.byte
5207 p = connected_second_map_header.blockdata.address
5208 h = None
5209 method = "default"
5210
5211 if ldirection == "north":
5212 h = connected_map_width - self.smh.width.byte
5213 if ((p + ((connected_map_height * connected_map_width) - (connected_map_width * 3)))%0x4000)+0x4000 == strip_pointer:
5214 # lin's equation:
5215 # p += (otherMap.height * otherMap.width) - (otherMap.width * 3)
5216 p += (connected_map_height * connected_map_width) - (connected_map_width * 3)
5217 method = "north1"
5218 elif ((p + connected_map_width + xoffset + (16 * connected_map_height) - 16)%0x4000)+0x4000 == strip_pointer:
5219 p += connected_map_width + xoffset + (16 * connected_map_height) - 16
5220 method = "north2"
5221 elif p != strip_pointer:
5222 # worst case scenario: we don't know how to calculate p, so we'll just set it as a constant
5223 # example: Route10North north to Route9 (strip_pointer=0x7eae, connected map's blockdata=0x7de9)
5224 p = strip_pointer
5225 method = "north3"
5226 else:
5227 # this doesn't seem to ever happen
5228 # or just do nothing (value is already ok)
5229 method = "north4"
5230 elif ldirection == "west":
5231 h = connected_map_height - self.smh.height.byte
5232 if ((p + (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2)%0x4000)+0x4000 == strip_pointer:
5233 # lin's method:
5234 # p += (h * otherMap.width) - (otherMap.width * 3) + (otherMap.width - 3)
5235 p += (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2
5236 method = "west1"
5237 elif ((p + connected_map_width - 3)%0x4000)+0x4000 == strip_pointer:
5238 logging.debug("west h <= 0")
5239 # lin's method:
5240 # p += otherMap.width - 3
5241 p += connected_map_width - 3
5242 method = "west2"
5243 elif ((p + xoffset + (current_map_height * 2))%0x4000 + 0x4000) == strip_pointer:
5244 method = "west3"
5245 p += xoffset + (current_map_height * 2)
5246 elif (p%0x4000)+0x4000 != strip_pointer:
5247 # worst case scenario: dunno what to do
5248 method = "west4"
5249 p = strip_pointer
5250 else:
5251 # this doesn't seem to ever happen
5252 # do nothing
5253 method = "west5"
5254 elif ldirection == "south":
5255 logging.debug("south.. dunno what to do?")
5256
5257 if (p%0x4000)+0x4000 == strip_pointer:
5258 # do nothing
5259 method = "south1"
5260 elif ((p + (xoffset - connection_strip_length + self.smh.width.byte) / 2)%0x4000)+0x4000 == strip_pointer:
5261 # comet's method
5262 method = "south2"
5263 p += (xoffset - connection_strip_length + self.smh.width.byte) / 2
5264 elif ((p + ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1)%0x4000)+0x4000 == strip_pointer:
5265 method = "south3"
5266 p += ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1
5267 elif ldirection == "east":
5268 if (p%0x4000)+0x4000 == strip_pointer:
5269 # do nothing
5270 method = "east1"
5271 elif ((p + (connected_map_height - connection_strip_length) * connected_map_width)%0x4000)+0x4000 == strip_pointer:
5272 p += (connected_map_height - connection_strip_length) * connected_map_width
5273 method = "east2"
5274 elif ((p + 100 - 4 * connected_map_width)%0x4000) + 0x4000 == strip_pointer:
5275 method = "east3"
5276 p += 100 - 4 * connected_map_width
5277 elif ((p + 2 * (100 - 4 * connected_map_width))%0x4000) + 0x4000 == strip_pointer:
5278 method = "east4"
5279 # the "2" is possibly ( connected_map_height / current_map_height )
5280 # or current_map_width/yoffset or connected_map_width/yoffset
5281 p += 2 * (100 - 4 * connected_map_width)
5282
5283 # convert the address to a 2-byte pointer
5284 intermediate_p = p
5285 p = (p % 0x4000) + 0x4000
5286
5287 data = {
5288 "strip_pointer": strip_pointer,
5289 "strip_length": connection_strip_length,
5290 "other_blockdata_address": connected_second_map_header.blockdata.address,
5291 "other_blockdata_pointer": (connected_second_map_header.blockdata.address%0x4000)+0x4000,
5292
5293 "xoffset": xoffset,
5294 "yoffset": yoffset,
5295
5296 "connected_map_height": connected_map_height,
5297 "connected_map_width": connected_map_width,
5298 "connected_map_group_id": connected_map_group_id,
5299 "connected_map_id": connected_map_id,
5300 "connected_map_label": map_names[connected_map_group_id][connected_map_id]["label"],
5301
5302 "current_map_width": self.smh.width.byte,
5303 "current_map_height": self.smh.height.byte,
5304 "current_map_label": map_names[self.smh.map_group][self.smh.map_id]["label"],
5305 "current_map_group_id": self.smh.map_group,
5306 "current_map_id": self.smh.map_id,
5307
5308 "difference": strip_pointer - ((connected_second_map_header.blockdata.address%0x4000)+0x4000),
5309 "direction": ldirection,
5310 "method": method,
5311 }
5312 strip_pointer_data.append(data)
5313
5314 if p != strip_pointer:
5315 wowparams = {
5316 "method": method,
5317 "direction": ldirection,
5318 "other map blockdata address": hex(connected_second_map_header.blockdata.address),
5319 "h": h,
5320 "initial p": hex(connected_second_map_header.blockdata.address),
5321 "intermediate p": hex(intermediate_p),
5322 "final p": hex(p),
5323 "connection length": connection_strip_length,
5324 "strip_pointer": hex(strip_pointer),
5325 "other map height": connected_map_height,
5326 "other map width": connected_map_width,
5327 }
5328
5329 logging.debug(wowparams)
5330
5331 whatparams = {
5332 "other map group_id": hex(connected_map_group_id),
5333 "other map map_id": hex(connected_map_id),
5334 "other map name": +map_names[connected_map_group_id][connected_map_id]["label"],
5335 "smh": hex(connected_second_map_header.address),
5336 "width": connected_second_map_header.width.byte,
5337 "height": connected_second_map_header.height.byte,
5338 }
5339
5340 logging.debug(whatparams)
5341
5342 curparams = {
5343 "current map group_id": hex(self.map_group),
5344 "current map map_id": hex(self.map_id),
5345 "current map name": map_names[self.map_group][self.map_id]["label"],
5346 "smh": hex(self.smh.address),
5347 "width": self.smh.width.byte,
5348 "height": self.smh.height.byte,
5349 }
5350
5351 logging.debug(curparams)
5352
5353 if ldirection == "east":
5354 wrong_easts.append(data)
5355 elif ldirection == "west":
5356 wrong_wests.append(data)
5357 elif ldirection == "south":
5358 wrong_souths.append(data)
5359 elif ldirection == "north":
5360 wrong_norths.append(data)
5361
5362 # this will only happen if there's a bad formula
5363 raise Exception("tauwasser strip_pointer calculation was wrong? strip_pointer="+hex(strip_pointer) + " p="+hex(p))
5364
5365 calculated_destination = None
5366 method = "strip_destination_default"
5367 x_movement_of_the_connection_strip_in_blocks = None
5368 y_movement_of_the_connection_strip_in_blocks = None
5369
5370 # the below code makes sure there's an equation to calculate strip_destination
5371 # 11:05 <comet> Above: C803h + xoffset
5372 # 11:05 <comet> Below: C803h + (m.height + 3) * (m.width + 6) + xoffset
5373 # 11:05 <comet> Left: C800h + (m.width + 6) * (yoffset + 3)
5374 # 11:05 <comet> Right: C7FDh + (m.width + 6) * (yoffset + 4)
5375 #
5376 # tauwasser calls this "connection strip destination" and lin calls this "memoryOtherPointer"
5377 # Points to the upper left block of the connection strip
5378 # (The bank the Blockdata is in, is loaded out of the Mapheader of the connected Map.)
5379 # The connection strip is always 3 Blocks high resp. wide
5380 # (depending on the connection's direction)
5381 if ldirection == "north":
5382 x_movement_of_the_connection_strip_in_blocks = strip_destination - 0xC703
5383 logging.debug(
5384 "(north) x_movement_of_the_connection_strip_in_blocks is: {0}"
5385 .format(x_movement_of_the_connection_strip_in_blocks)
5386 )
5387 if x_movement_of_the_connection_strip_in_blocks < 0:
5388 raise Exception("x_movement_of_the_connection_strip_in_blocks is wrong? " + str(x_movement_of_the_connection_strip_in_blocks))
5389 elif ldirection == "south":
5390 # strip_destination =
5391 # 0xc703 + (current_map_height + 3) * (current_map_width + 6) + x_movement_of_the_connection_strip_in_blocks
5392 x_movement_of_the_connection_strip_in_blocks = strip_destination - (0xc703 + (current_map_height + 3) * (current_map_width + 6))
5393 logging.debug(
5394 "(south) x_movement_of_the_connection_strip_in_blocks is: {0}"
5395 .format(x_movement_of_the_connection_strip_in_blocks)
5396 )
5397 elif ldirection == "east":
5398 # strip_destination =
5399 # 0xc700 + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 3)
5400 y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc700) / (current_map_width + 6) - 3
5401 logging.debug(
5402 "(east) y_movement_of_the_connection_strip_in_blocks is {0}"
5403 .format(y_movement_of_the_connection_strip_in_blocks)
5404 )
5405 elif ldirection == "west":
5406 # strip_destination =
5407 # 0xc6fd + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 4)
5408 y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc6fd) / (current_map_width + 6) - 4
5409 logging.debug(
5410 "(west) y_movement_of_the_connection_strip_in_blocks is {0}"
5411 .format(y_movement_of_the_connection_strip_in_blocks)
5412 )
5413
5414 # let's also check the window equations
5415 # tauwasser calls this "window" and lin calls this "memoryCurrentPointer"
5416 # Position of the upper left block after entering the Map
5417 #
5418 # tauwasser's formula for windows:
5419 # Above: C701h + Height_of_connected_map * (Width_of_connected_map + 6)
5420 # Left: C706h + 2 * Width_of_connected_map
5421 # Below/Right: C707h + Width_of_connected_map
5422 window_worked = False
5423 if ldirection == "north":
5424 # tauwasser's formula: 0xc701 + connected_map_height * (connected_map_width + 6)
5425 window_start = 0xc801
5426 if window == window_start + (connected_map_height * 6) + (connected_map_height * connected_map_width):
5427 window_worked = True
5428 elif ldirection == "east":
5429 window_start = 0xc807
5430 if window == (window_start + connected_map_width):
5431 window_worked = True
5432 elif ldirection == "south":
5433 window_start = 0xc807
5434 if window == (window_start + connected_map_width):
5435 window_worked = True
5436 elif ldirection == "west":
5437 window_start = 0xc807
5438 if window == (window_start + xoffset):
5439 window_worked = True
5440
5441 data = {
5442 "window": window,
5443 "window_start": window_start,
5444 "window_diff": window - window_start,
5445 "window_worked": window_worked,
5446 "strip_destination": strip_destination,
5447 "strip_length": connection_strip_length,
5448 "other_blockdata_address": connected_second_map_header.blockdata.address,
5449 "other_blockdata_pointer": (connected_second_map_header.blockdata.address%0x4000)+0x4000,
5450
5451 "xoffset": xoffset,
5452 "yoffset": yoffset,
5453
5454 "connected_map_height": connected_map_height,
5455 "connected_map_width": connected_map_width,
5456 "connected_map_group_id": connected_map_group_id,
5457 "connected_map_id": connected_map_id,
5458 "connected_map_label": map_names[connected_map_group_id][connected_map_id]["label"],
5459
5460 "current_map_width": self.smh.width.byte,
5461 "current_map_height": self.smh.height.byte,
5462 "current_map_label": map_names[self.smh.map_group][self.smh.map_id]["label"],
5463 "current_map_group_id": self.smh.map_group,
5464 "current_map_id": self.smh.map_id,
5465
5466 "y_movement_of_the_connection_strip_in_blocks": y_movement_of_the_connection_strip_in_blocks,
5467 "x_movement_of_the_connection_strip_in_blocks": x_movement_of_the_connection_strip_in_blocks,
5468
5469 "direction": ldirection,
5470 "method": method,
5471 }
5472 strip_destination_data.append(data)
5473
5474 def to_asm(self):
5475 output = ""
5476 ldirection = self.direction.lower()
5477
5478 connected_map_group_id = self.connected_map_group_id
5479 connected_map_id = self.connected_map_id
5480
5481 connected_map_header = map_names[connected_map_group_id][connected_map_id]["header_new"]
5482 connected_second_map_header = connected_map_header.second_map_header
5483 connected_map_height = connected_second_map_header.height.byte
5484 connected_map_width = connected_second_map_header.width.byte
5485
5486 connection_strip_length = self.connection_strip_length
5487 connected_map_width = self.connected_map_width
5488
5489 current_map_height = self.smh.height.byte
5490 current_map_width = self.smh.width.byte
5491
5492 map_constant_label = get_map_constant_label(map_group=connected_map_group_id, map_id=connected_map_id, map_internal_ids=self.map_internal_ids)
5493 self_constant_label = get_map_constant_label(map_group=self.smh.map_group, map_id=self.smh.map_id, map_internal_ids=self.map_internal_ids)
5494 if map_constant_label != None:
5495 map_group_label = "GROUP_" + map_constant_label
5496 map_label = "MAP_" + map_constant_label
5497 else:
5498 map_group_label = str(connected_map_group_id)
5499 map_label = str(connected_map_id)
5500
5501 output += "; " + self.direction.upper() + " to " \
5502 + map_names[connected_map_group_id][connected_map_id]["name"] \
5503 + "\n"
5504
5505 output += "db %s, %s ; connected map (group, id)\n" % (map_group_label, map_label)
5506
5507 yoffset = self.yoffset
5508 xoffset = self.xoffset
5509
5510 # According to JohtoMap, the calculation for tile data pointer is:
5511 # int p = otherMap.tileDataLocation;
5512 # int h = (otherMap.width - otherMap.height)
5513 # if (h > 0)
5514 # p += (h * otherMap.height) + (otherMap.height * 3) + (otherMap.height + 3)
5515 # else
5516 # p += (otherMap.height * otherMap.width) - (otherMap.width * 3);
5517 # c.tileDataPointer = gb.Get2BytePointer(p);
5518 strip_pointer = self.strip_pointer
5519
5520 p = connected_second_map_header.blockdata.address
5521
5522 output += "dw "
5523
5524 if ldirection == "north":
5525 h = connected_map_width - self.smh.width.byte
5526 if ((p + ((connected_map_height * connected_map_width) - (connected_map_width * 3)))%0x4000)+0x4000 == strip_pointer:
5527 # lin's equation:
5528 # p += (otherMap.height * otherMap.width) - (otherMap.width * 3)
5529 p += (connected_map_height * connected_map_width) - (connected_map_width * 3)
5530 method = "north1"
5531 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + (" + map_constant_label + "_HEIGHT * " + map_constant_label + "_WIDTH) - (" + map_constant_label + "_WIDTH * 3))"
5532 elif ((p + connected_map_width + xoffset + (16 * connected_map_height) - 16)%0x4000)+0x4000 == strip_pointer:
5533 p += connected_map_width + xoffset + (16 * connected_map_height) - 16
5534 method = "north2"
5535 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + map_constant_label + "_WIDTH + " + str(xoffset) + " + (16 * " + map_constant_label + "_HEIGHT) - 16)"
5536 elif p != strip_pointer:
5537 # worst case scenario: we don't know how to calculate p, so we'll just set it as a constant
5538 # example: Route10North north to Route9 (strip_pointer=0x7eae, connected map's blockdata=0x7de9)
5539 p = strip_pointer
5540 method = "north3"
5541 output += "$%.2x" % (p)
5542 else:
5543 # this doesn't seem to ever happen
5544 # or just do nothing (value is already ok)
5545 method = "north4"
5546 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")"
5547 elif ldirection == "west":
5548 h = connected_map_height - self.smh.height.byte
5549 h_out = "(" + map_constant_label +"_HEIGHT - " + self_constant_label +"_HEIGHT)"
5550 if ((p + (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2)%0x4000)+0x4000 == strip_pointer:
5551 # lin's method:
5552 # p += (h * otherMap.width) - (otherMap.width * 3) + (otherMap.width - 3)
5553 p += (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2
5554 method = "west1"
5555 this_part = "((" + h_out + " * " + map_constant_label + "_WIDTH) - (" + map_constant_label + "_WIDTH * 3) + (" + map_constant_label + "_WIDTH - 1) - 2)"
5556 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")"
5557 elif ((p + connected_map_width - 3)%0x4000)+0x4000 == strip_pointer:
5558 logging.debug("west h <= 0")
5559 # lin's method:
5560 # p += otherMap.width - 3
5561 p += connected_map_width - 3
5562 method = "west2"
5563 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + map_constant_label + "_WIDTH - 3)"
5564 elif ((p + xoffset + (current_map_height * 2))%0x4000 + 0x4000) == strip_pointer:
5565 method = "west3"
5566 p += xoffset + (current_map_height * 2)
5567 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + str(xoffset) + " + (" + map_constant_label + "_HEIGHT * 2))"
5568 elif (p%0x4000)+0x4000 != strip_pointer:
5569 # worst case scenario: dunno what to do
5570 method = "west4"
5571 p = strip_pointer
5572 output += "$%.2x" % ((p%0x4000)+0x4000)
5573 else:
5574 # this doesn't seem to ever happen
5575 # do nothing
5576 method = "west5"
5577 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")"
5578 elif ldirection == "south":
5579 if (p%0x4000)+0x4000 == strip_pointer:
5580 # do nothing
5581 method = "south1"
5582 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")"
5583 elif ((p + (xoffset - connection_strip_length + self.smh.width.byte) / 2)%0x4000)+0x4000 == strip_pointer:
5584 # comet's method
5585 method = "south2"
5586 p += (xoffset - connection_strip_length + self.smh.width.byte) / 2
5587 this_part = "((" + str(xoffset) + " - " + str(connection_strip_length) + " + " + self_constant_label + "_WIDTH) / 2)"
5588 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")"
5589 elif ((p + ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1)%0x4000)+0x4000 == strip_pointer:
5590 method = "south3"
5591 p += ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1
5592 this_part = "(((" + str(xoffset) + " - " + str(connection_strip_length) + " + " + self_constant_label + "_WIDTH) / 2) - 1)"
5593 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")"
5594 elif ldirection == "east":
5595 if (p%0x4000)+0x4000 == strip_pointer:
5596 # do nothing
5597 method = "east1"
5598 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")"
5599 elif ((p + (connected_map_height - connection_strip_length) * connected_map_width)%0x4000)+0x4000 == strip_pointer:
5600 p += (connected_map_height - connection_strip_length) * connected_map_width
5601 method = "east2"
5602 this_part = "((" + map_constant_label + "_HEIGHT - " + str(connection_strip_length) + ") * " + map_constant_label + "_WIDTH)"
5603 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")"
5604 elif ((p + 100 - 4 * connected_map_width)%0x4000) + 0x4000 == strip_pointer:
5605 method = "east3"
5606 p += 100 - 4 * connected_map_width
5607 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + 100 - (" + map_constant_label + "_WIDTH * 4))"
5608 elif ((p + 2 * (100 - 4 * connected_map_width))%0x4000) + 0x4000 == strip_pointer:
5609 method = "east4"
5610 # the "2" is possibly ( connected_map_height / current_map_height )
5611 # or current_map_width/yoffset or connected_map_width/yoffset
5612 p += 2 * (100 - 4 * connected_map_width)
5613 output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + ((100 - (" + map_constant_label + "_WIDTH * 4)) * 2))"
5614
5615 output += " ; strip pointer\n"
5616
5617 # tauwasser calls this "connection strip destination" and lin calls this "memoryOtherPointer"
5618 # Points to the upper left block of the connection strip
5619 # (The bank the Blockdata is in, is loaded out of the Mapheader of the connected Map.)
5620 # The connection strip is always 3 Blocks high resp. wide
5621 # (depending on the connection's direction)
5622 strip_destination = self.strip_destination
5623
5624 output += "dw "
5625
5626 # i am not convinced about these calculations
5627 if ldirection == "north":
5628 x_movement_of_the_connection_strip_in_blocks = strip_destination - 0xC703
5629 xmov = x_movement_of_the_connection_strip_in_blocks
5630 output += "($C703 + " + str(xmov) + ")"
5631 elif ldirection == "south":
5632 # strip_destination =
5633 # 0xc703 + (current_map_height + 3) * (current_map_width + 6) + x_movement_of_the_connection_strip_in_blocks
5634 x_movement_of_the_connection_strip_in_blocks = strip_destination - (0xc703 + (current_map_height + 3) * (current_map_width + 6))
5635 xmov = x_movement_of_the_connection_strip_in_blocks
5636 #output += "($C703 + (((" + self_constant_label + "_HEIGHT + 3) * (" + self_constant_label + "_WIDTH + 6)) + " + str(xmov) + "))"
5637
5638 # xmov = strip_destination - (0xc703 + (current_map_height + 3) * (current_map_width + 6))
5639 #difference = 0xC715 + xmov + 6*current_map_height + 3*current_map_width + current_map_width*current_map_height
5640 #difference = 50965 + ymov + 6*current_map_height + 3*current_map_width + current_map_width*current_map_height
5641
5642 output += "($C703 + " + str(xmov) + " + ((" + self_constant_label + "_HEIGHT + 3) * (" + self_constant_label + "_WIDTH + 6)))"
5643 elif ldirection == "east":
5644 # strip_destination =
5645 # 0xc700 + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 3)
5646 y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc700) / (current_map_width + 6) - 3
5647 ymov = y_movement_of_the_connection_strip_in_blocks
5648 #output += "($C700 + ((" + self_constant_label + "_WIDTH + 6) * (" + str(ymov) + " + 3)) + "+str(ymov)+")"
5649 output += "$%.2x" % (strip_destination)
5650 elif ldirection == "west":
5651 # strip_destination =
5652 # 0xc6fd + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 4)
5653 y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc700) / (current_map_width + 6) - 3
5654 ymov = y_movement_of_the_connection_strip_in_blocks
5655 #output += "($C700 + ((" + self_constant_label + "_WIDTH + 6) * (" + str(ymov) + " + 4)) - 4)"
5656 output += "$%.2x" % (strip_destination)
5657 output += " ; strip destination\n"
5658
5659 output += "db " + str(connection_strip_length,) + ", " + map_constant_label + "_WIDTH ; (connection strip length, connected map width)\n"
5660
5661 #if ldirection in ["east", "west"]:
5662 # Y_movement_of_connection_strip_in_blocks =
5663 #elif direction in ["north", "south"]:
5664 # X_movement_of_connection_strip_in_blocks =
5665
5666 # Above: (Height_of_connected_map * 2) - 1
5667 # Below: 0
5668 # Left/Right: (Y_movement_of_connection_strip_in_blocks * -2)
5669 yoffset = self.yoffset # y_position_after_map_change
5670
5671 if ldirection == "south" and yoffset != 0:
5672 raise Exception("tauwasser was wrong about yoffset=0 for south? it's: " + str(yoffset))
5673 elif ldirection == "north" and yoffset != ((connected_map_height * 2) - 1):
5674 raise Exception("tauwasser was wrong about yoffset for north? it's: " + str(yoffset))
5675 #elif not ((yoffset % -2) == 0):
5676 # raise Exception("tauwasser was wrong about yoffset for west/east? it's not divisible by -2: " + str(yoffset))
5677
5678 # Left: (Width_of_connected_map * 2) - 1
5679 # Right: 0
5680 # Above/Below: (X_movement_of_connection_strip_in_blocks * -2)
5681 xoffset = self.xoffset # x_position_after_map_change
5682
5683 if ldirection == "east" and xoffset != 0:
5684 raise Exception("tauwasser was wrong about xoffset=0 for east? it's: " + str(xoffset))
5685 elif ldirection == "west" and xoffset != ((connected_map_width * 2) - 1):
5686 raise Exception("tauwasser was wrong about xoffset for west? it's: " + str(xoffset))
5687 #elif not ((xoffset % -2) == 0):
5688 # raise Exception("tauwasser was wrong about xoffset for north/south? it's not divisible by -2: " + str(xoffset))
5689
5690 output += "db "
5691
5692 if ldirection == "south":
5693 output += "0"
5694 elif ldirection == "north":
5695 output += "((" + map_constant_label + "_HEIGHT * 2) - 1)"
5696 else:
5697 output += str(yoffset)
5698
5699 output += ", "
5700
5701 if ldirection == "east":
5702 output += "0"
5703 elif ldirection == "west":
5704 output += "((" + map_constant_label + "_WIDTH * 2) - 1)"
5705 else:
5706 output += str(xoffset)
5707
5708 output += " ; yoffset, xoffset\n"
5709
5710 window = self.window
5711
5712 output += "dw "
5713
5714 # let's also check the window equations
5715 # tauwasser calls this "window" and lin calls this "memoryCurrentPointer"
5716 # Position of the upper left block after entering the Map
5717 #
5718 # tauwasser's formula for windows:
5719 # Above: C701h + Height_of_connected_map * (Width_of_connected_map + 6)
5720 # Left: C706h + 2 * Width_of_connected_map
5721 # Below/Right: C707h + Width_of_connected_map
5722 window_worked = False
5723 if ldirection == "north":
5724 # tauwasser's formula: 0xc701 + connected_map_height * (connected_map_width + 6)
5725 window_start = 0xc801
5726 if window == window_start + (connected_map_height * 6) + (connected_map_height * connected_map_width):
5727 window_worked = True
5728 output += "($C801 + ((" + map_constant_label + "_HEIGHT * 6) + (" + map_constant_label + "_HEIGHT * " + map_constant_label + "_WIDTH)))"
5729 elif ldirection == "east":
5730 window_start = 0xc807
5731 if window == (window_start + connected_map_width):
5732 window_worked = True
5733 output += "($C807 + " + map_constant_label + "_WIDTH)"
5734 elif ldirection == "south":
5735 window_start = 0xc807
5736 if window == (window_start + connected_map_width):
5737 window_worked = True
5738 output += "($C807 + " + map_constant_label + "_WIDTH)"
5739 elif ldirection == "west":
5740 window_start = 0xc807
5741 if window == (window_start + xoffset):
5742 window_worked = True
5743 output += "($C807 + " + str(xoffset) + ")"
5744
5745 output += " ; window"
5746
5747 return output
5748
5749all_second_map_headers = []
5750def parse_second_map_header_at(address, map_group=None, map_id=None, debug=True):
5751 """each map has a second map header"""
5752 smh = SecondMapHeader(address, map_group=map_group, map_id=map_id, debug=debug)
5753 all_second_map_headers.append(smh)
5754 return smh
5755
5756class MapBlockData(object):
5757 base_label = "MapBlockData_"
5758 maps_path = os.path.realpath(os.path.join(conf.path, "maps"))
5759
5760 def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None, width=None, height=None):
5761 self.address = address
5762 self.map_group = map_group
5763 self.map_id = map_id
5764 self.map_name = map_names[map_group][map_id]["label"]
5765 self.map_path = os.path.join(self.maps_path, self.map_name + ".blk")
5766 self.debug = debug
5767 self.bank = bank
5768 if width and height:
5769 self.width = width
5770 self.height = height
5771 else:
5772 raise Exception("MapBlockData needs to know the width/height of its map")
5773 label = self.make_label()
5774 self.label = Label(name=label, address=address, object=self)
5775 self.last_address = self.address + (self.width.byte * self.height.byte)
5776 script_parse_table[address : self.last_address] = self
5777 self.parse()
5778
5779 def make_label(self):
5780 return map_names[self.map_group][self.map_id]["label"] + "_BlockData"
5781
5782 def save_to_file(self):
5783 # check if the file exists already
5784 map_path = self.map_path
5785 if not os.path.exists(self.maps_path):
5786 os.mkdir(self.maps_path)
5787 if not os.path.exists(map_path):
5788 # dump to file
5789 #bytes = rom.interval(self.address, self.width.byte*self.height.byte, strings=True)
5790 bytes = rom[self.address : self.address + self.width.byte*self.height.byte]
5791 file_handler = open(map_path, "w")
5792 file_handler.write(bytes)
5793 file_handler.close()
5794
5795 def parse(self):
5796 self.save_to_file()
5797
5798 def to_asm(self):
5799 return "INCBIN \"maps/"+self.map_name+".blk\""
5800
5801
5802class MapEventHeader(object):
5803 base_label = "MapEventHeader_"
5804
5805 def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None):
5806 logging.debug(
5807 "making a MapEventHeader at {address} map_group={map_group} map_id={map_id}"
5808 .format(
5809 address=hex(address),
5810 map_group=map_group,
5811 map_id=map_id,
5812 )
5813 )
5814 self.address = address
5815 self.map_group = map_group
5816 self.map_id = map_id
5817 self.debug = debug
5818 self.bank = bank
5819 self.dependencies = None
5820 label = self.make_label()
5821 self.label = Label(name=label, address=address, object=self)
5822 self.parse()
5823 script_parse_table[address : self.last_address] = self
5824
5825 def make_label(self):
5826 return map_names[self.map_group][self.map_id]["label"] + "_MapEventHeader"
5827
5828 def parse(self):
5829 map_group, map_id, debug = self.map_group, self.map_id, self.debug
5830 address = self.address
5831 bank = pokemontools.pointers.calculate_bank(self.address) # or use self.bank
5832 logging.debug("event header address is {0}".format(hex(address)))
5833
5834 filler1 = ord(rom[address])
5835 filler2 = ord(rom[address+1])
5836 self.fillers = [filler1, filler2]
5837
5838 # warps
5839 warp_count = ord(rom[address+2])
5840 warp_byte_count = warp_byte_size * warp_count
5841 after_warps = address + 3 + warp_byte_count
5842 warps = parse_warps(address+3, warp_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug)
5843 self.warp_count = warp_count
5844 self.warps = warps
5845
5846 # triggers (based on xy location)
5847 xy_trigger_count = ord(rom[after_warps])
5848 trigger_byte_count = trigger_byte_size * xy_trigger_count
5849 xy_triggers = parse_xy_triggers(after_warps+1, xy_trigger_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug)
5850 after_triggers = after_warps + 1 + trigger_byte_count
5851 self.xy_trigger_count = xy_trigger_count
5852 self.xy_triggers = xy_triggers
5853
5854 # signposts
5855 signpost_count = ord(rom[after_triggers])
5856 signpost_byte_count = signpost_byte_size * signpost_count
5857 # signposts = rom.interval(after_triggers+1, signpost_byte_count)
5858 signposts = parse_signposts(after_triggers+1, signpost_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug)
5859 after_signposts = after_triggers + 1 + signpost_byte_count
5860 self.signpost_count = signpost_count
5861 self.signposts = signposts
5862
5863 # people events
5864 people_event_count = ord(rom[after_signposts])
5865 people_event_byte_count = people_event_byte_size * people_event_count
5866 # people_events_bytes = rom.interval(after_signposts+1, people_event_byte_count)
5867 # people_events = parse_people_event_bytes(people_events_bytes, address=after_signposts+1, map_group=map_group, map_id=map_id)
5868 people_events = parse_people_events(after_signposts+1, people_event_count, bank=pokemontools.pointers.calculate_bank(after_signposts+2), map_group=map_group, map_id=map_id, debug=debug)
5869 self.people_event_count = people_event_count
5870 self.people_events = people_events
5871
5872 if people_event_count > 0:
5873 self.last_address = people_events[-1].last_address
5874 else:
5875 self.last_address = after_signposts+1
5876 return True
5877
5878 def get_dependencies(self, recompute=False, global_dependencies=set()):
5879 if self.dependencies != None and not recompute:
5880 global_dependencies.update(self.dependencies)
5881 return self.dependencies
5882 bases = []
5883 bases += self.people_events
5884 bases += self.signposts
5885 bases += self.xy_triggers
5886 bases += self.warps
5887
5888 dependencies = []
5889 for p in bases:
5890 dependencies.extend(p.get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
5891 self.dependencies = dependencies
5892 return dependencies
5893
5894 def to_asm(self):
5895 xspacing = "" # was =spacing
5896 output = "; filler\n"
5897 output += "db %d, %d\n\n" % (self.fillers[0], self.fillers[1])
5898
5899 output += xspacing + "; warps\n"
5900 output += xspacing + "db %d"%(self.warp_count)
5901 if len(self.warps) > 0:
5902 output += "\n"
5903 output += "\n".join([xspacing+warp.to_asm() for warp in self.warps])
5904
5905 output += "\n\n"
5906 output += xspacing + "; xy triggers\n"
5907 output += xspacing + "db %d"%(self.xy_trigger_count)
5908 if len(self.xy_triggers) > 0:
5909 output += "\n"
5910 output += "\n".join([xspacing+xy_trigger.to_asm() for xy_trigger in self.xy_triggers])
5911
5912 output += "\n\n"
5913 output += xspacing + "; signposts\n"
5914 output += xspacing + "db %d"%(self.signpost_count)
5915 if len(self.signposts) > 0:
5916 output += "\n"
5917 output += "\n".join([xspacing+signpost.to_asm() for signpost in self.signposts])
5918
5919 output += "\n\n"
5920 output += xspacing + "; people-events\n"
5921 output += xspacing + "db %d"%(self.people_event_count)
5922 if len(self.people_events) > 0:
5923 output += "\n"
5924
5925 for people_event in self.people_events:
5926 output += xspacing
5927 output += people_event.to_asm()
5928 output += "\n"
5929
5930 if output[-1] == "\n":
5931 output = output[:-1]
5932 return output
5933
5934all_map_event_headers = []
5935def parse_map_event_header_at(address, map_group=None, map_id=None, debug=True, bank=None):
5936 """parse crystal map event header byte structure thing"""
5937 ev = MapEventHeader(address, map_group=map_group, map_id=map_id, debug=debug, bank=bank)
5938 all_map_event_headers.append(ev)
5939 return ev
5940
5941class MapScriptHeader(object):
5942 """parses a script header
5943
5944 This structure allows the game to have e.g. one-time only events on a map
5945 or first enter events or permanent changes to the map or permanent script
5946 calls.
5947
5948 This header a combination of a trigger script section and a callback script
5949 section. I don't know if these 'trigger scripts' are the same as the others
5950 referenced in the map event header, so this might need to be renamed very
5951 soon. The scripts in MapEventHeader are called XYTrigger.
5952
5953 trigger scripts:
5954 [[Number1 of pointers] Number1 * [2byte pointer to script][00][00]]
5955
5956 callback scripts:
5957 [[Number2 of pointers] Number2 * [hook number][2byte pointer to script]]
5958
5959 hook byte choices:
5960 01 - map data has already been loaded to ram, tileset and sprites still missing
5961 map change (3rd step)
5962 loading (2nd step)
5963 map connection (3rd step)
5964 after battle (1st step)
5965 02 - map data, tileset and sprites are all loaded
5966 map change (5th step)
5967 03 - neither map data not tilesets nor sprites are loaded
5968 map change (2nd step)
5969 loading (1st step)
5970 map connection (2nd step)
5971 04 - map data and tileset loaded, sprites still missing
5972 map change (4th step)
5973 loading (3rd step)
5974 sprite reload (1st step)
5975 map connection (4th step)
5976 after battle (2nd step)
5977 05 - neither map data not tilesets nor sprites are loaded
5978 map change (1st step)
5979 map connection (1st step)
5980
5981 When certain events occur, the call backs will be called in this order (same info as above):
5982 map change:
5983 05, 03, 01, 04, 02
5984 loading:
5985 03, 01, 04
5986 sprite reload:
5987 04
5988 map connection:
5989 05, 03, 01, 04 note that #2 is not called (unlike "map change")
5990 after battle:
5991 01, 04
5992 """
5993 base_label = "MapScriptHeader_"
5994
5995 def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None):
5996 logging.debug(
5997 "creating a MapScriptHeader at {address} map_group={map_group} map_id={map_id}"
5998 .format(
5999 address=hex(address),
6000 map_group=map_group,
6001 map_id=map_id,
6002 )
6003 )
6004 self.address = address
6005 self.map_group = map_group
6006 self.map_id = map_id
6007 self.debug = debug
6008 self.bank = bank
6009 self.dependencies = None
6010 label = self.make_label()
6011 self.label = Label(name=label, address=address, object=self)
6012 self.parse()
6013 script_parse_table[address : self.last_address] = self
6014
6015 def make_label(self):
6016 return map_names[self.map_group][self.map_id]["label"] + "_MapScriptHeader"
6017
6018 def parse(self):
6019 address = self.address
6020 map_group = self.map_group
6021 map_id = self.map_id
6022 debug = self.debug
6023 #[[Number1 of pointers] Number1 * [2byte pointer to script][00][00]]
6024 self.trigger_count = ord(rom[address])
6025 self.triggers = []
6026 ptr_line_size = 4
6027 groups = pokemontools.helpers.grouper(rom.interval(address+1, self.trigger_count * ptr_line_size, strings=False), count=ptr_line_size)
6028 current_address = address+1
6029 for (index, trigger_bytes) in enumerate(groups):
6030 logging.debug(
6031 "parsing a map trigger script at {address} map_group={map_group} map_id={map_id}"
6032 .format(
6033 address=hex(current_address),
6034 map_group=map_group,
6035 map_id=map_id,
6036 )
6037 )
6038 script = ScriptPointerLabelParam(address=current_address, map_group=map_group, map_id=map_id, debug=debug)
6039 extra_bytes = MultiByteParam(address=current_address+2, map_group=map_group, map_id=map_id, debug=debug)
6040 self.triggers.append([script, extra_bytes])
6041 current_address += ptr_line_size
6042 current_address = address + (self.trigger_count * ptr_line_size) + 1
6043 #[[Number2 of pointers] Number2 * [hook number][2byte pointer to script]]
6044 callback_ptr_line_size = 3
6045 self.callback_count = DecimalParam(address=current_address)
6046 self.callback_count = self.callback_count.byte
6047 current_address += 1
6048 self.callbacks = []
6049 for index in range(self.callback_count):
6050 logging.debug(
6051 "parsing a callback script at {address} map_group={map_group} map_id={map_id}"
6052 .format(
6053 address=hex(current_address),
6054 map_group=map_group,
6055 map_id=map_id,
6056 )
6057 )
6058 hook_byte = HexByte(address=current_address)
6059 callback = ScriptPointerLabelParam(address=current_address+1, map_group=map_group, map_id=map_id, debug=debug)
6060 self.callbacks.append({"hook": hook_byte, "callback": callback})
6061 current_address += 3 # i think?
6062 self.last_address = current_address
6063 logging.debug(
6064 "done parsing a MapScriptHeader map_group={map_group} map_id={map_id}"
6065 .format(map_group=map_group, map_id=map_id)
6066 )
6067 return True
6068
6069 def get_dependencies(self, recompute=False, global_dependencies=set()):
6070 if self.dependencies != None and not recompute:
6071 global_dependencies.update(self.dependencies)
6072 return self.dependencies
6073 dependencies = []
6074 for p in list(self.triggers):
6075 # dependencies.append(p[0])
6076 dependencies.extend(p[0].get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
6077 for callback in self.callbacks:
6078 dependencies.append(callback["callback"])
6079 global_dependencies.add(callback["callback"])
6080 dependencies.extend(callback["callback"].get_dependencies(recompute=recompute, global_dependencies=global_dependencies))
6081 self.dependencies = dependencies
6082 return dependencies
6083
6084 def to_asm(self):
6085 output = ""
6086 output += "; trigger count\n"
6087 output += "db %d\n"%self.trigger_count
6088 if len(self.triggers) > 0:
6089 output += "\n; triggers\n"
6090 output += "\n".join([str("dw "+p[0].to_asm()+", "+p[1].to_asm()) for p in self.triggers])
6091 output += "\n"
6092 output += "\n; callback count\n"
6093 output += "db %d"%self.callback_count
6094 if len(self.callbacks) > 0:
6095 output += "\n\n; callbacks\n\n"
6096 output += "\n\n".join(["dbw "+str(p["hook"].byte)+", "+p["callback"].to_asm() for p in self.callbacks])
6097 return output
6098
6099all_map_script_headers = []
6100def parse_map_script_header_at(address, map_group=None, map_id=None, debug=True):
6101 evv = MapScriptHeader(address, map_group=map_group, map_id=map_id, debug=debug)
6102 all_map_script_headers.append(evv)
6103 return evv
6104
6105def parse_map_header_by_id(*args, **kwargs):
6106 """convenience function to parse a specific map"""
6107 map_group, map_id = None, None
6108 all_map_headers = kwargs["all_map_headers"]
6109 if "map_group" in kwargs.keys():
6110 map_group = kwargs["map_group"]
6111 if "map_id" in kwargs.keys():
6112 map_id = kwargs["map_id"]
6113 if (map_group == None and map_id != None) or \
6114 (map_group != None and map_id == None):
6115 raise Exception("map_group and map_id must both be provided")
6116 elif map_group == None and map_id == None and len(args) == 0:
6117 raise Exception("must be given an argument")
6118 elif len(args) == 1 and type(args[0]) == str:
6119 map_group = int(args[0].split(".")[0])
6120 map_id = int(args[0].split(".")[1])
6121 elif map_group == None and map_id == None:
6122 raise Exception("dunno what to do with input")
6123 offset = map_names[map_group]["offset"]
6124 map_header_offset = offset + ((map_id - 1) * map_header_byte_size)
6125 return parse_map_header_at(map_header_offset, all_map_headers=all_map_headers, map_group=map_group, map_id=map_id)
6126
6127def parse_all_map_headers(map_names, all_map_headers=None, _parse_map_header_at=None, rom=None, debug=True):
6128 """
6129 Calls parse_map_header_at for each map in each map group. Updates the
6130 map_names structure.
6131 """
6132 if _parse_map_header_at == None:
6133 _parse_map_header_at = parse_map_header_at
6134 if "offset" not in map_names[1]:
6135 raise Exception("dunno what to do - map_names should have groups with pre-calculated offsets by now")
6136 for (group_id, group_data) in map_names.items():
6137 offset = group_data["offset"]
6138 # we only care about the maps
6139 #del group_data["offset"]
6140 for (map_id, map_data) in group_data.items():
6141 if map_id == "offset": continue # skip the "offset" address for this map group
6142 if debug:
6143 logging.debug(
6144 "map_group={group_id} map_id={map_id}"
6145 .format(group_id=group_id, map_id=map_id)
6146 )
6147 map_header_offset = offset + ((map_id - 1) * map_header_byte_size)
6148 map_names[group_id][map_id]["header_offset"] = map_header_offset
6149
6150 new_parsed_map = _parse_map_header_at(map_header_offset, map_group=group_id, map_id=map_id, all_map_headers=all_map_headers, rom=rom, debug=debug)
6151 map_names[group_id][map_id]["header_new"] = new_parsed_map
6152
6153class PokedexEntryPointerTable(object):
6154 """
6155 A list of pointers.
6156 """
6157
6158 def __init__(self):
6159 self.address = 0x44378
6160 self.target_bank = pokemontools.pointers.calculate_bank(0x181695)
6161 self.label = Label(name="PokedexDataPointerTable", address=self.address, object=self)
6162 self.size = None
6163 self.last_address = None
6164 self.dependencies = None
6165 self.entries = []
6166 self.parse()
6167
6168 script_parse_table[self.address : self.last_address] = self
6169
6170 def get_dependencies(self, recompute=False, global_dependencies=set()):
6171 global_dependencies.update(self.entries)
6172 dependencies = []
6173 [dependencies.extend(entry.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) for entry in self.entries]
6174 return dependencies
6175
6176 def parse(self):
6177 size = 0
6178 lastpointer = 0
6179 for i in range(251):
6180 # Those are consecutive in GS!
6181 if i == 0x40:
6182 self.target_bank = 0x6e
6183 elif i == 0x80:
6184 self.target_bank = 0x73
6185 elif i == 0xc0:
6186 self.target_bank = 0x74
6187 loc = self.address+(i*2)
6188 pointer = calculate_pointer_from_bytes_at(loc, bank=self.target_bank)
6189 #print(hex(pointer))
6190 #if pointer < lastpointer:
6191 # self.target_bank += 1
6192 # pointer += 0x4000
6193 self.entries.append(PokedexEntry(pointer, i+1))
6194
6195 size += 2
6196 self.size = size
6197 self.last_address = self.address + self.size
6198
6199 def to_asm(self):
6200 output = "".join([str("dw "+get_label_for(entry.address)+"\n") for entry in self.entries])
6201 return output
6202
6203class PokedexEntry(object):
6204 def __init__(self, address, pokemon_id):
6205 self.address = address
6206 self.dependencies = None
6207 #label = self.make_label()
6208 if pokemon_id in pokemon_constants.pokemon_constants:
6209 pokename = string.capwords(pokemon_constants.pokemon_constants[pokemon_id].replace("__", " ").replace("_", " ")).replace(" ", "")
6210 else:
6211 pokename = "Pokemon{0}".format(pokemon_id)
6212 self.label = Label(name=pokename+"PokedexEntry", address=self.address, object=self)
6213 self.parse()
6214 script_parse_table[address : self.last_address] = self
6215
6216 def get_dependencies(self, recompute=False, global_dependencies=set()):
6217 return []
6218
6219 def parse(self):
6220 # eww.
6221 address = self.address
6222 jump = how_many_until(chr(0x50), address, rom)
6223 self.species = parse_text_at(address, jump+1)
6224 address = address + jump + 1
6225
6226 self.weight = ord(rom[address ]) + (ord(rom[address+1]) << 8)
6227 self.height = ord(rom[address+2]) + (ord(rom[address+3]) << 8)
6228 address += 4
6229
6230 jump = how_many_until(chr(0x50), address, rom)
6231 self.page1 = PokedexText(address)
6232 address = address + jump + 1
6233 jump = how_many_until(chr(0x50), address, rom)
6234 self.page2 = PokedexText(address)
6235
6236 self.last_address = address + jump + 1
6237 #print(self.to_asm())
6238 return True
6239
6240 def to_asm(self):
6241 output = """\
6242 db "{0}" ; species name
6243 dw {1}, {2} ; height, weight
6244
6245 {3}
6246 {4}""".format(self.species, self.weight, self.height, self.page1.to_asm(), self.page2.to_asm())
6247 return output
6248
6249# map names with no labels will be generated
6250# generate labels for each map name
6251from pokemontools import map_names
6252
6253for map_group_id in map_names kwargs.keys():
6254 map_group = map_names[map_group_id]
6255 for map_id in map_group.keys():
6256 # skip if we maybe already have the 'offset' label set in this map group
6257 if map_id == "offset": continue
6258 # skip if we provided a pre-set value for the map's label
6259 if "label" in map_group[map_id]: continue
6260 # convience alias
6261 map_data = map_group[map_id]
6262 # clean up the map name to be an asm label
6263 cleaned_name = map_name_cleaner(map_data["name"])
6264 # set the value in the original dictionary
6265 map_names[map_group_id][map_id]["label"] = cleaned_name
6266# generate map constants (like 1=PALLET_TOWN)
6267generate_map_constant_labels()
6268
6269#### asm utilities ####
6270# these are pulled in from pokered/extras/analyze_incbins.py
6271
6272# store each line of source code here
6273asm = None
6274
6275# store each incbin line separately
6276incbin_lines = []
6277
6278# storage for processed incbin lines
6279processed_incbins = {}
6280
6281def to_asm(some_object, use_asm_rules=False):
6282 """shows an object's asm with a label and an ending comment
6283 showing the next byte address"""
6284 if isinstance(some_object, int):
6285 some_object = script_parse_table[some_object]
6286 # add one to the last_address to show where the next byte is in the file
6287 last_address = some_object.last_address
6288 # create a line like "label: ; 0x10101"
6289 asm = some_object.label.name + ": ; " + hex(some_object.address) + "\n"
6290 # now add the inner/actual asm
6291 #asm += spacing + some_object.to_asm().replace("\n", "\n"+spacing).replace("\n"+spacing+"\n"+spacing, "\n\n"+spacing)
6292 asmr = some_object.to_asm()
6293 asmr = asmr.replace("\n", "\n"+spacing)
6294 asmr = asmr.replace("\n"+spacing+"\n", "\n\n"+spacing)
6295 asmr = asmr.replace("\n\n"+spacing+spacing, "\n\n"+spacing)
6296 asm += spacing + asmr
6297 if use_asm_rules:
6298 asm = asm.replace("\n" + spacing + "; ", "\n; ")
6299 asm = asm.replace("\n" + spacing + ".asm_", "\n.asm_")
6300 # show the address of the next byte below this
6301 asm += "\n; " + hex(last_address)
6302 return asm
6303
6304def get_dependencies_for(some_object, recompute=False, global_dependencies=set()):
6305 """
6306 calculates which labels need to be satisfied for an object
6307 to be inserted into the asm and compile successfully.
6308
6309 You could also choose to not insert labels into the asm, but
6310 then you're losing out on the main value of having asm in the
6311 first place.
6312 """
6313 try:
6314 if isinstance(some_object, int):
6315 some_object = script_parse_table[some_object]
6316 if some_object.dependencies != None and not recompute:
6317 global_dependencies.update(some_object.dependencies)
6318 else:
6319 some_object.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)
6320 return global_dependencies
6321 except RuntimeError as e:
6322 # 1552, 1291, 2075, 1552, 1291...
6323
6324 errorargs = {
6325 "some_object": some_object,
6326 "class type": some_object.__class__,
6327 "label name": some_object.label.name,
6328 "address": some_object.address,
6329 "asm": to_asm(some_object),
6330 }
6331
6332 logging.debug(str(errorargs))
6333
6334 raise e
6335
6336def isolate_incbins(asm=None):
6337 "find each incbin line"
6338 global incbin_lines
6339 if asm == None:
6340 asm = globals()["asm"]
6341 incbin_lines = []
6342 for line in asm:
6343 if line == "": continue
6344 if line.count(" ") == len(line): continue
6345
6346 # clean up whitespace at beginning of line
6347 while line[0] == " ":
6348 line = line[1:]
6349
6350 if line[0:6] == "INCBIN" and "baserom.gbc" in line:
6351 incbin_lines.append(line)
6352 return incbin_lines
6353
6354def process_incbins():
6355 "parse incbin lines into memory"
6356 global asm, incbin_lines, processed_incbins
6357 # load asm if it isn't ready yet
6358 if asm == [] or asm == None:
6359 load_asm()
6360 # get a list of incbins if that hasn't happened yet
6361 if incbin_lines == [] or incbin_lines == None:
6362 isolate_incbins(asm=asm)
6363 # reset the global that this function creates
6364 processed_incbins = {}
6365 # for each incbin..
6366 for incbin in incbin_lines:
6367 # reset this entry
6368 processed_incbin = {}
6369 # get the line number from the global asm line list
6370 line_number = asm.index(incbin)
6371 # forget about all the leading characters
6372 partial_start = incbin[21:]
6373 start = partial_start.split(",")[0].replace("$", "0x")
6374 start = eval(start)
6375 start_hex = hex(start).replace("0x", "$")
6376
6377 partial_interval = incbin[21:].split(",")[1]
6378 partial_interval = partial_interval.replace(";", "#")
6379 partial_interval = partial_interval.replace("$", "0x").replace("0xx", "0x")
6380 interval = eval(partial_interval)
6381 interval_hex = hex(interval).replace("0x", "$").replace("x", "")
6382
6383 end = start + interval
6384 end_hex = hex(end).replace("0x", "$")
6385
6386 processed_incbin = {"line_number": line_number,
6387 "line": incbin,
6388 "start": start,
6389 "interval": interval,
6390 "end": end, }
6391 # don't add this incbin if the interval is 0
6392 if interval != 0:
6393 processed_incbins[line_number] = processed_incbin
6394 return processed_incbins
6395
6396def reset_incbins():
6397 "reset asm before inserting another diff"
6398 global asm, incbin_lines, processed_incbins
6399 asm = None
6400 incbin_lines = []
6401 processed_incbins = {}
6402 load_asm()
6403 isolate_incbins(asm=asm)
6404 process_incbins()
6405
6406def find_incbin_to_replace_for(address, debug=False, rom_file=None):
6407 """returns a line number for which incbin to edit
6408 if you were to insert bytes into main.asm"""
6409 if rom_file == None:
6410 rom_file = os.path.join(conf.path, "baserom.gbc")
6411 if type(address) == str: address = int(address, 16)
6412 if not (0 <= address <= os.lstat(rom_file).st_size):
6413 raise IndexError("address is out of bounds")
6414 for incbin_key in processed_incbins.keys():
6415 incbin = processed_incbins[incbin_key]
6416 start = incbin["start"]
6417 end = incbin["end"]
6418 if debug:
6419 argstuff = {
6420 "start": start,
6421 "end": end,
6422 "address": str(type(address)),
6423 }
6424
6425 logging.debug(str(argstuff))
6426 logging.debug(
6427 "checking... {start} <= {address} <= {end}"
6428 .format(
6429 start=hex(start),
6430 address=hex(address),
6431 end=hex(end),
6432 )
6433 )
6434 if start <= address <= end:
6435 return incbin_key
6436 return None
6437
6438def split_incbin_line_into_three(line, start_address, byte_count, rom_file=None):
6439 """
6440 splits an incbin line into three pieces.
6441 you can replace the middle one with the new content of length bytecount
6442
6443 start_address: where you want to start inserting bytes
6444 byte_count: how many bytes you will be inserting
6445 """
6446 if rom_file == None:
6447 rom_file = os.path.join(conf.path, "baserom.gbc")
6448 if type(start_address) == str: start_address = int(start_address, 16)
6449 if not (0 <= start_address <= os.lstat(rom_file).st_size):
6450 raise IndexError("start_address is out of bounds")
6451 if len(processed_incbins) == 0:
6452 raise Exception("processed_incbins must be populated")
6453
6454 original_incbin = processed_incbins[line]
6455 start = original_incbin["start"]
6456 end = original_incbin["end"]
6457
6458 # start, end1, end2 (to be printed as start, end1 - end2)
6459 if start_address - start > 0:
6460 first = (start, start_address, start)
6461 else:
6462 first = (None) # skip this one because we're not including anything
6463
6464 # this is the one you will replace with whatever content
6465 second = (start_address, byte_count)
6466
6467 third = (start_address + byte_count, end - (start_address + byte_count))
6468
6469 output = ""
6470
6471 if first:
6472 output += "INCBIN \"baserom.gbc\",$" + hex(first[0])[2:] + ",$" + hex(first[1])[2:] + " - $" + hex(first[2])[2:] + "\n"
6473 output += "INCBIN \"baserom.gbc\",$" + hex(second[0])[2:] + "," + str(byte_count) + "\n"
6474 output += "INCBIN \"baserom.gbc\",$" + hex(third[0])[2:] + ",$" + hex(third[1])[2:] # no newline
6475 return output
6476
6477
6478def generate_diff_insert(line_number, newline, debug=False):
6479 """generates a diff between the old main.asm and the new main.asm
6480 note: requires python2.7 i think? b/c of subprocess.check_output"""
6481 global asm
6482 original = "\n".join(line for line in asm)
6483 newfile = deepcopy(asm)
6484 newfile[line_number] = newline # possibly inserting multiple lines
6485 newfile = "\n".join(line for line in newfile)
6486
6487 # make sure there's a newline at the end of the file
6488 if newfile[-1] != "\n":
6489 newfile += "\n"
6490
6491 original_filename = "ejroqjfoad.temp"
6492 newfile_filename = "fjiqefo.temp"
6493
6494 main_path = os.path.join(conf.path, "main.asm")
6495 if os.path.exists(main_path):
6496 original_filename = main_path
6497
6498 original_fh = open(original_filename, "w")
6499 original_fh.write(original)
6500 original_fh.close()
6501
6502 newfile_fh = open(newfile_filename, "w")
6503 newfile_fh.write(newfile)
6504 newfile_fh.close()
6505
6506 try:
6507 from subprocess import CalledProcessError
6508 except ImportError:
6509 CalledProcessError = None
6510
6511 try:
6512 diffcontent = subprocess.check_output("diff -u " + os.path.join(conf.path, "main.asm") + " " + newfile_filename, shell=True)
6513 except (AttributeError, CalledProcessError):
6514 p = subprocess.Popen(["diff", "-u", os.path.join(conf.path, "main.asm"), newfile_filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
6515 out, err = p.communicate()
6516 diffcontent = out
6517
6518 os.system("rm " + original_filename)
6519 os.system("rm " + newfile_filename)
6520
6521 if debug:
6522 logging.debug("diffcontent is {0}".format(diffcontent))
6523 return diffcontent
6524
6525def apply_diff(diff, try_fixing=True, do_compile=True):
6526 logging.info("Applying diff.")
6527
6528 # write the diff to a file
6529 fh = open("temp.patch", "w")
6530 fh.write(diff)
6531 fh.close()
6532
6533 # apply the patch
6534 os.system("cp " + os.path.join(conf.path, "main.asm") + " " + os.path.join(conf.path, "main1.asm"))
6535 os.system("patch " + os.path.join(conf.path, "main.asm") + " " + "temp.patch")
6536
6537 # remove the patch
6538 os.system("rm temp.patch")
6539
6540 # confirm it's working
6541 if do_compile:
6542 try:
6543 subprocess.check_call("cd " + conf.path + "; make clean; make", shell=True)
6544 return True
6545 except Exception as exc:
6546 if try_fixing:
6547 os.system("mv " + os.path.join(conf.path, "main1.asm") + " " + os.path.join(conf.path, "main.asm"))
6548 return False
6549
6550from pokemontools.crystalparts.asmline import AsmLine
6551
6552class Incbin(object):
6553 def __init__(self, line, bank=None, debug=False):
6554 self.line = line
6555 self.bank = bank
6556 self.replace_me = False
6557 self.debug = debug
6558 self.parse()
6559
6560 def parse(self):
6561 incbin = self.line
6562 partial_start = incbin[21:]
6563 start = partial_start.split(",")[0].replace("$", "0x")
6564
6565 if self.debug:
6566 logging.debug("Incbin.parse line is {0}".format(self.line))
6567 logging.debug("Incbin.parse partial_start is {0}".format(partial_start))
6568 logging.debug("Incbin.parse start is {0}".format(start))
6569 try:
6570 start = eval(start)
6571 except Exception as e:
6572 logging.debug("start is {0}".format(start))
6573 raise Exception("problem with evaluating interval range: " + str(e))
6574
6575 start_hex = hex(start).replace("0x", "$")
6576
6577 partial_interval = incbin[21:].split(",")[1]
6578 partial_interval = partial_interval.replace(";", "#")
6579 partial_interval = partial_interval.replace("$", "0x").replace("0xx", "0x")
6580 interval = eval(partial_interval)
6581 interval_hex = hex(interval).replace("0x", "$").replace("x", "")
6582
6583 end = start + interval
6584 end_hex = hex(end).replace("0x", "$")
6585
6586 self.address = start
6587 self.start_address = start
6588 self.end_address = end
6589 self.last_address = end
6590 self.interval = interval
6591
6592 def to_asm(self):
6593 if self.interval > 0:
6594 return self.line
6595 else:
6596 return ""
6597
6598 def split(self, start_address, byte_count):
6599 """splits this incbin into three separate incbins"""
6600 if start_address < self.start_address or start_address > self.end_address:
6601 raise Exception("this incbin doesn't handle this address")
6602 incbins = []
6603
6604 if self.debug:
6605 logging.debug(
6606 "splitting an incbin (\"{line}\") into three at {start} for {count} bytes"
6607 .format(
6608 line=self.line,
6609 start=hex(start_address),
6610 count=byte_count,
6611 )
6612 )
6613
6614 # start, end1, end2 (to be printed as start, end1 - end2)
6615 if (start_address - self.start_address) > 0:
6616 first = (self.start_address, start_address, self.start_address)
6617 incbins.append(Incbin("INCBIN \"baserom.gbc\",$%.2x,$%.2x - $%.2x" % (first[0], first[1], first[2])))
6618 if self.debug:
6619 logging.debug(incbins[0].line)
6620 else:
6621 # skip this one because we're not including anything
6622 first = None
6623
6624 # this is the one you will replace with whatever content
6625 second = (start_address, byte_count)
6626 incbins.append(Incbin("INCBIN \"baserom.gbc\",$%.2x,$%.2x" % (start_address, byte_count)))
6627 incbins[-1].replace_me = True
6628 if self.debug:
6629 logging.debug(incbins[-1].line)
6630
6631 if (self.last_address - (start_address + byte_count)) > 0:
6632 third = (start_address + byte_count, self.last_address - (start_address + byte_count))
6633 incbins.append(Incbin("INCBIN \"baserom.gbc\",$%.2x,$%.2x" % (third[0], third[1])))
6634 if self.debug:
6635 logging.debug(incbins[-1].line)
6636
6637 return incbins
6638
6639class AsmSection(object):
6640 def __init__(self, line):
6641 self.bank_id = None
6642 self.line = line
6643 self.parse()
6644
6645 def parse(self):
6646 line = self.line
6647
6648 if not "bank" in line:
6649 self.bank_id = -1
6650 self.address = -1
6651 self.last_address = None
6652 self.end_address = None
6653 return
6654
6655 bank_id = int(line.split("\"")[1].split("bank")[1], 16)
6656 self.bank_id = bank_id
6657 start_address = bank_id * 0x4000
6658 end_address = (bank_id * 0x4000) + 0x4000 - 1
6659
6660 self.address = self.start_address = start_address
6661 self.last_address = None
6662 self.end_address = None
6663 # this entity doesn't actually take up this space..
6664 # although it could be argued that lines should exist under this object
6665 #self.address = self.start_address = start_address
6666 #self.last_address = self.end_address = end_address
6667
6668 def to_asm(self):
6669 return self.line
6670
6671new_asm = None
6672def load_asm2(filename=None, force=False):
6673 """loads the asm source code into memory"""
6674 if filename == None:
6675 filename = os.path.join(conf.path, "main.asm")
6676 global new_asm
6677 if new_asm == None or force:
6678 new_asm = Asm(filename=filename)
6679 return new_asm
6680
6681class Asm(object):
6682 """controls the overall asm output"""
6683 def __init__(self, filename=None, debug=True):
6684 if filename == None:
6685 filename = os.path.join(conf.path, "main.asm")
6686 self.parts = []
6687 self.labels = []
6688 self.filename = filename
6689 self.debug = debug
6690 self.load_and_parse()
6691
6692 def load_and_parse(self):
6693 self.parts = []
6694 asm = open(self.filename, "r").read().split("\n")
6695 asm_list = romstr.AsmList(asm)
6696 bank = 0
6697 for line in asm_list:
6698 if (line[0:6] == "INCBIN" or line[1:6] == "INCBIN") and not any([contaminant+"\"" in line for contaminant in [".2bpp", ".1bpp", ".asm", ".lz"]]):
6699 thing = Incbin(line, bank=bank)
6700 elif line[0:7] == "SECTION":
6701 thing = AsmSection(line)
6702 bank = thing.bank_id
6703 else:
6704 thing = AsmLine(line, bank=bank)
6705 label = labels.get_label_from_line(line)
6706 if label:
6707 laddress = labels.get_address_from_line_comment(line)
6708 thing.label = Label(name=label, address=laddress, object=thing, add_to_globals=False)
6709 self.labels.append(thing.label)
6710 self.parts.append(thing)
6711
6712 def is_label_name_in_file(self, label_name):
6713 for llabel in self.labels:
6714 if llabel.name == label_name:
6715 return llabel
6716 return False
6717
6718 def does_address_have_label(self, address):
6719 """
6720 Checks if an address has a label.
6721 """
6722 # either something will directly have the address
6723 # or- it's possibel that no label was given
6724 # or there will be an Incbin that covers the range
6725 for part in self.parts:
6726 if isinstance(part, Incbin) and part.start_address <= address <= part.end_address:
6727 return False
6728 elif hasattr(part, "address") and part.address == address and hasattr(part, "label"):
6729 return part.label
6730
6731 return None
6732
6733 def insert(self, new_object):
6734 if isinstance(new_object, ScriptPointerLabelParam):
6735 # its' probably being injected in some get_dependencies() somewhere
6736 logging.debug("don't know why ScriptPointerLabelParam is getting to this point?")
6737 return
6738
6739 # first some validation
6740 if not hasattr(new_object, "address"):
6741 logging.debug("object needs to have an address property: {0}".format(new_object))
6742 return
6743
6744 start_address = new_object.address
6745
6746 # skip this dragon shrine script calling itself
6747 # what about other scripts that call themselves ?
6748 if start_address in lousy_dragon_shrine_hack:
6749 logging.debug("skipping 0x18d079 in dragon shrine for a lousy hack")
6750 return
6751
6752 if not hasattr(new_object, "label") and hasattr(new_object, "is_valid") and not new_object.is_valid():
6753 return
6754
6755 debugmsg = "object is " + new_object.label.name + " type="+str(new_object.__class__)+" new_object="+str(new_object)
6756 debugmsg += " label = " + new_object.label.name
6757 debugmsg += " start_address="+hex(start_address)#+" end_address="+hex(end_address)
6758
6759 if not hasattr(new_object, "last_address"):
6760 logging.debug(debugmsg)
6761 raise Exception("object needs to have a last_address property")
6762 end_address = new_object.last_address
6763 debugmsg += " last_address="+hex(end_address)
6764
6765 # check if the object is already inserted
6766 if new_object in self.parts:
6767 logging.debug(
6768 "object was previously inserted ({new_object}; {address})"
6769 .format(
6770 new_object=new_object,
6771 address=hex(new_object.address),
6772 )
6773 )
6774 return
6775 # check by label
6776 other_obj = self.is_label_name_in_file(new_object.label.name)
6777 if other_obj:
6778 other_obj = other_obj.object
6779 logging.debug(
6780 "object was previously inserted ({name} at {address}) by {othername} at {otheraddress}"
6781 .format(
6782 name=new_object.label.name,
6783 address=hex(new_object.address),
6784 othername=other_obj.label.name,
6785 otheraddress=other_obj.address,
6786 )
6787 )
6788 return
6789 # check by address
6790 #if self.does_address_have_label(new_object.address):
6791 # print "object's address is already used ("+str(new_object)+") at "+hex(new_object.address)+" label="+new_object.label.name
6792 # return
6793
6794 if self.debug:
6795 logging.debug(debugmsg)
6796 del debugmsg
6797 if (end_address < start_address) or ((end_address - start_address) < 0):
6798 if not self.debug:
6799 logging.debug("object is new_object={0}".format(new_object))
6800 logging.deubg(
6801 "start_address={start} end_address={end}"
6802 .format(start=hex(start_address), end=hex(end_address))
6803 )
6804 if hasattr(new_object, "to_asm"):
6805 logging.debug(to_asm(new_object))
6806 raise Exception("Asm.insert was given an object with a bad address range")
6807
6808 # 1) find which object needs to be replaced
6809 # or
6810 # 2) find which object goes after it
6811 found = False
6812 for object in list(self.parts):
6813 # skip objects without a defined interval (like a comment line)
6814 if not hasattr(object, "address") or not hasattr(object, "last_address"):
6815 continue
6816 # skip an AsmSection
6817 if isinstance(object, AsmSection):
6818 continue
6819 # replace an incbin with three incbins, replace middle incbin with whatever
6820 elif isinstance(object, Incbin) and (object.address <= start_address < object.last_address):
6821 # split up the incbin into three segments
6822 incbins = object.split(start_address, end_address - start_address)
6823 # figure out which incbin to replace with the new object
6824 if incbins[0].replace_me:
6825 index = 0
6826 else: # assume incbins[1].replace_me (the middle one)
6827 index = 1
6828 # replace that index with the new_object
6829 incbins[index] = new_object
6830 # insert these incbins into self.parts
6831 gindex = self.parts.index(object)
6832 self.parts = self.parts[:gindex] + incbins + self.parts[gindex:]
6833 self.parts.remove(object)
6834 found = True
6835 break
6836 elif object.address <= start_address < object.last_address:
6837 logging.debug("this is probably a script that is looping back on itself?")
6838 found = True
6839 break
6840 # insert before the current object
6841 elif object.address > end_address:
6842 #insert_before = index of object
6843 index = self.parts.index(object)
6844 self.parts.insert(index, new_object)
6845 found = True
6846 break
6847 if not found:
6848 raise Exception("unable to insert object into Asm")
6849 self.labels.append(new_object.label)
6850 return True
6851
6852 def insert_with_dependencies(self, input):
6853 if type(input) == list:
6854 input_objects = input
6855 else:
6856 input_objects = [input]
6857
6858 for object0 in input_objects:
6859 global_dependencies = set([object0])
6860 poopbutt = get_dependencies_for(object0, global_dependencies=global_dependencies, recompute=False)
6861 objects = global_dependencies
6862 objects.update(poopbutt)
6863 new_objects = copy(objects)
6864 for object in objects:
6865 if hasattr(object, "dependencies") and object.dependencies == None:
6866 new_objects.update(object.get_dependencies())
6867 for object in new_objects:
6868 if isinstance(object, ScriptPointerLabelParam):
6869 continue
6870 #if object in self.parts:
6871 # if self.debug:
6872 # print "already inserted -- object.__class__="+str(object.__class__)+" object is: "+str(object)+\
6873 # " for object.__class__="+str(object0.__class__)+" object="+str(object0)
6874 # continue
6875 if self.debug:
6876 logging.debug("object is: {0}".format(object))
6877 self.insert(object)
6878
6879 # just some old debugging
6880 #if object.label.name == "UnknownText_0x60128":
6881 # raise Exception("debugging...")
6882 #elif object.label.name == "UnknownScript_0x60011":
6883 # raise Exception("debugging.. dependencies are: " + str(object.dependencies) + " versus: " + str(object.get_dependencies()))
6884
6885 def insert_single_with_dependencies(self, object):
6886 self.insert_with_dependencies(object)
6887
6888 def insert_multiple_with_dependencies(self, objects):
6889 self.insert_with_dependencies(objects)
6890
6891 def insert_all(self, limit=100):
6892 count = 0
6893 for each in script_parse_table.items():
6894 if count == limit: break
6895 object = each[1]
6896 if type(object) == str: continue
6897 self.insert_single_with_dependencies(object)
6898 count += 1
6899
6900 def insert_and_dump(self, limit=100, filename="output.txt"):
6901 self.insert_all(limit=limit)
6902 self.dump(filename=filename)
6903
6904 def dump(self, filename="output.txt"):
6905 fh = open(filename, "w")
6906 newlines_before_next_obj_requested = 0
6907 newlines_before_next_obj_given = 0
6908
6909 current_requested_newlines_before = 0
6910 current_requested_newlines_after = 0
6911 previous_requested_newlines_before = 0
6912 previous_requested_newlines_after = 0
6913
6914 written_newlines = 0
6915 write_something = False
6916 first = True
6917 last = None
6918 for each in self.parts:
6919 asm = ""
6920 previous_requested_newlines_after = current_requested_newlines_after
6921 current_requested_newlines_before = current_requested_newlines_after
6922
6923 write_something = True
6924 if (isinstance(each, str) and each == "") or (isinstance(each, AsmLine) and each.line == ""):
6925 current_requested_newlines_before = 0
6926 if current_requested_newlines_after < 2:
6927 current_requested_newlines_after += 1
6928 write_something = False
6929 elif (isinstance(each, str) and each != "") or (isinstance(each, AsmLine) and each.line != ""):
6930 if isinstance(each, AsmLine):
6931 asm = each.to_asm()
6932 elif isinstance(each, str):
6933 asm = each
6934 current_requested_newlines_before = 0
6935 current_requested_newlines_after = 1
6936 elif isinstance(each, AsmSection) or isinstance(each, Incbin) or hasattr(each, "to_asm"):
6937 if isinstance(each, AsmSection) or isinstance(each, Incbin):
6938 asm = each.to_asm()
6939 else:
6940 asm = to_asm(each)
6941 current_requested_newlines_before = 2
6942 current_requested_newlines_after = 2
6943 else:
6944 raise Exception("dunno what to do with("+str(each)+") in Asm.parts")
6945
6946 if write_something:
6947 if not first:
6948 newlines_before = max([current_requested_newlines_before, previous_requested_newlines_after])
6949 while written_newlines < newlines_before:
6950 fh.write("\n")
6951 written_newlines += 1
6952 else:
6953 first = False
6954 fh.write(asm)
6955 written_newlines = 0
6956 last = each
6957
6958 # make sure the file ends with a newline
6959 fh.write("\n")
6960
6961def list_things_in_bank(bank):
6962 objects = []
6963 for blah in script_parse_table.items():
6964 object = blah[1]
6965 if hasattr(object, "address") and pokemontools.pointers.calculate_bank(object.address) == bank:
6966 objects.append(object)
6967 return objects
6968
6969def list_texts_in_bank(bank):
6970 """
6971 Narrows down the list of objects that you will be inserting into Asm.
6972 """
6973 if len(all_texts) == 0:
6974 raise Exception("all_texts is blank.. parse_rom() will populate it")
6975
6976 assert bank != None, "list_texts_in_banks must be given a particular bank"
6977
6978 assert 0 <= bank < 0x80, "bank doesn't exist in the ROM"
6979
6980 texts = []
6981 for text in all_texts:
6982 if pokemontools.pointers.calculate_bank(text.address) == bank:
6983 texts.append(text)
6984
6985 return texts
6986
6987def list_movements_in_bank(bank, all_movements):
6988 """
6989 Narrows down the list of objects to speed up Asm insertion.
6990 """
6991 if len(all_movements) == 0:
6992 raise Exception("all_movements is blank.. parse_rom() will populate it")
6993
6994 assert bank != None, "list_movements_in_bank must be given a particular bank"
6995 assert 0 <= bank < 0x80, "bank doesn't exist in the ROM (out of bounds)"
6996
6997 movements = []
6998 for movement in all_movements:
6999 if pokemontools.pointers.calculate_bank(movement.address) == bank:
7000 movements.append(movement)
7001 return movements
7002
7003def dump_asm_for_texts_in_bank(bank, start=50, end=100, rom=None):
7004 """
7005 Simple utility to help with dumping texts into a particular bank. This is
7006 helpful for figuring out which text is breaking that bank.
7007 """
7008 # load and parse the ROM if necessary
7009 if rom == None or len(rom) <= 4:
7010 rom = load_rom()
7011 parse_rom()
7012
7013 # get all texts
7014 # first 100 look okay?
7015 texts = list_texts_in_bank(bank)[start:end]
7016
7017 # create a new dump
7018 asm = Asm()
7019
7020 # start the insertion process
7021 asm.insert_multiple_with_dependencies(texts)
7022
7023 # start dumping
7024 asm.dump()
7025
7026 logging.info("done dumping texts for bank {banked}".format(banked="$%.2x" % bank))
7027
7028def dump_asm_for_movements_in_bank(bank, start=0, end=100, all_movements=None):
7029 if rom == None or len(rom) <= 4:
7030 rom = load_rom()
7031 parse_rom()
7032
7033 movements = list_movements_in_bank(bank, all_movements)[start:end]
7034
7035 asm = Asm()
7036 asm.insert_with_dependencies(movements)
7037 asm.dump()
7038 logging.info("done dumping movements for bank {banked}".format(banked="$%.2x" % bank))
7039
7040def dump_things_in_bank(bank, start=50, end=100):
7041 """
7042 is helpful for figuring out which object is breaking that bank.
7043 """
7044 # load and parse the ROM if necessary
7045 if rom == None or len(rom) <= 4:
7046 rom = load_rom()
7047 parse_rom()
7048
7049 things = list_things_in_bank(bank)[start:end]
7050
7051 # create a new dump
7052 asm = Asm()
7053
7054 # start the insertion process
7055 asm.insert_with_dependencies(things)
7056
7057 # start dumping
7058 asm.dump()
7059
7060 logging.info("done dumping things for bank {banked}".format(banked="$%.2x" % bank))
7061
7062def analyze_intervals():
7063 """find the largest baserom.gbc intervals"""
7064 global asm, processed_incbins
7065 if asm == None:
7066 load_asm()
7067 if processed_incbins == {}:
7068 isolate_incbins(asm=asm)
7069 process_incbins()
7070 results = []
7071 ordered_keys = sorted(processed_incbins, key=lambda entry: processed_incbins[entry]["interval"])
7072 ordered_keys.reverse()
7073 for key in ordered_keys:
7074 results.append(processed_incbins[key])
7075 return results
7076
7077all_labels = []
7078def write_all_labels(all_labels, filename="labels.json"):
7079 fh = open(filename, "w")
7080 fh.write(json.dumps(all_labels))
7081 fh.close()
7082 return True
7083
7084def setup_wram_labels(config=conf):
7085 """
7086 Get all wram labels and store it on the module.
7087 """
7088 wramproc = wram.WRAMProcessor(config=config)
7089 wramproc.initialize()
7090 wram.wram_labels = wramproc.wram_labels
7091
7092def get_ram_label(address):
7093 """
7094 returns a label assigned to a particular ram address
7095 """
7096 if not hasattr(wram, "wram_labels"):
7097 setup_wram_labels()
7098 if address in wram.wram_labels.keys():
7099 return wram.wram_labels[address][-1]
7100 return None
7101
7102def get_label_for(address, _all_labels=None, _script_parse_table=None):
7103 """
7104 returns a label assigned to a particular address
7105 """
7106 global all_labels
7107 global script_parse_table
7108
7109 if _all_labels == None:
7110 _all_labels = all_labels
7111
7112 if _script_parse_table == None:
7113 _script_parse_table = script_parse_table
7114
7115 if address == None:
7116 return None
7117 if type(address) != int:
7118 raise Exception("get_label_for requires an integer address, got: " + str(type(address)))
7119
7120 # lousy hack to get around recursive scripts in dragon shrine
7121 if address in lousy_dragon_shrine_hack:
7122 return None
7123
7124 # the old way
7125 for thing in _all_labels:
7126 if thing["address"] == address:
7127 return thing["label"]
7128
7129 # the new way
7130 obj = _script_parse_table[address]
7131 if obj:
7132 if hasattr(obj, "label"):
7133 return obj.label.name
7134 elif hasattr(obj, "keys") and "label" in obj.keys():
7135 return obj["label"]
7136 else:
7137 return "AlreadyParsedNoDefaultUnknownLabel_" + hex(address)
7138
7139 return None
7140
7141# all_new_labels is a temporary replacement for all_labels,
7142# at least until the two approaches are merged in the code base.
7143all_new_labels = []
7144
7145class Label(object):
7146 """
7147 Every object in script_parse_table is given a label.
7148
7149 This label is simply a way to keep track of what objects have
7150 been previously written to file.
7151 """
7152 def __init__(self, name=None, address=None, line_number=None, object=None, is_in_file=None, address_is_in_file=None, add_to_globals=True):
7153 assert address != None, "need an address"
7154 assert is_valid_address(address), "address must be valid"
7155 assert object != None, "need an object to relate with"
7156
7157 self.address = address
7158 self.object = object
7159
7160 # label might not be in the file yet
7161 self.line_number = line_number
7162
7163 # -- These were some old attempts to check whether the label
7164 # -- was already in use. They work, but the other method is
7165 # -- better.
7166 #
7167 # check if the label is in the file already
7168 # check if the address of this label is already in use
7169
7170 self.is_in_file = is_in_file
7171
7172 self.address_is_in_file = address_is_in_file
7173
7174 if name == None:
7175 name = object.base_label + "_" + hex(object.address)
7176
7177 self.name = name
7178
7179 if add_to_globals:
7180 all_new_labels.append(self)
7181
7182 def check_is_in_file(self):
7183 """
7184 This method checks if the label appears in the file based on the
7185 entries to the Asm.parts list.
7186 """
7187 # assert new_asm != None, "new_asm should be an instance of Asm"
7188 new_asm = load_asm2()
7189 is_in_file = new_asm.is_label_name_in_file(self.name)
7190 self.is_in_file = is_in_file
7191 return is_in_file
7192
7193 def check_address_is_in_file(self):
7194 """
7195 Checks if the address is in use by another label.
7196 """
7197 new_asm = load_asm2()
7198 self.address_is_in_file = new_asm.does_address_have_label(self.address)
7199 return self.address_is_in_file
7200
7201 def old_check_address_is_in_file(self):
7202 """
7203 Checks whether or not the address of the object is already in the file.
7204 This might happen if the label name is different but the address is the
7205 same. Another scenario is that the label is already used, but at a
7206 different address.
7207
7208 This method works by looking at the INCBINs. When there is
7209 an INCBIN that covers this address in the file, then there
7210 is no label at this address yet (or there is, but we can
7211 easily add another label in front of the incbin or something),
7212 and when there is no INCBIN that has this address, then we
7213 know that something is already using this address.
7214 """
7215 if processed_incbins == {}:
7216 process_incbins()
7217
7218 incbin = find_incbin_to_replace_for(self.address)
7219
7220 if incbin == None:
7221 return True
7222 else:
7223 return False
7224
7225 def make_label(self):
7226 """
7227 Generates a label name based on parents and self.object.
7228 """
7229 obj = self.object
7230 name = obj.make_label()
7231 return name
7232
7233label_errors = ""
7234def get_labels_between(start_line_id, end_line_id, bank):
7235 foundlabels = []
7236 #label = {
7237 # "line_number": 15,
7238 # "bank": 32,
7239 # "label": "PalletTownText1",
7240 # "offset": 0x5315,
7241 # "address": 0x75315,
7242 #}
7243 if asm == None:
7244 load_asm()
7245 sublines = asm[start_line_id : end_line_id + 1]
7246 for (current_line_offset, line) in enumerate(sublines):
7247 # skip lines without labels
7248 if not labels.line_has_label(line): continue
7249 # reset some variables
7250 line_id = start_line_id + current_line_offset
7251 line_label = labels.get_label_from_line(line)
7252 address = None
7253 offset = None
7254 # setup a place to store return values from line_has_comment_address
7255 returnable = {}
7256 # get the address from the comment
7257 has_comment = labels.line_has_comment_address(line, returnable=returnable, bank=bank)
7258 # skip this line if it has no address in the comment
7259 if not has_comment: continue
7260 # parse data from line_has_comment_address
7261 address = returnable["address"]
7262 bank = returnable["bank"]
7263 offset = returnable["offset"]
7264 # dump all this info into a single structure
7265 label = {
7266 "line_number": line_id,
7267 "bank": bank,
7268 "label": line_label,
7269 "offset": offset,
7270 "address": address,
7271 }
7272 # store this structure
7273 foundlabels.append(label)
7274 return foundlabels
7275
7276def scan_for_predefined_labels(debug=False):
7277 """looks through the asm file for labels at specific addresses,
7278 this relies on the label having its address after. ex:
7279
7280 ViridianCity_h: ; 0x18357 to 0x18384 (45 bytes) (bank=6) (id=1)
7281 PalletTownText1: ; 4F96 0x18f96
7282 ViridianCityText1: ; 0x19102
7283
7284 It would be more productive to use rgbasm to spit out all label
7285 addresses, but faster to write this script. rgbasm would be able
7286 to grab all label addresses better than this script..
7287 """
7288 global all_labels
7289 all_labels = []
7290 bank_intervals = {}
7291
7292 if asm == None:
7293 load_asm()
7294
7295 # figure out line numbers for each bank
7296 for bank_id in range(0x7F+1):
7297 abbreviation = ("%.x" % (bank_id)).upper()
7298 abbreviation_next = ("%.x" % (bank_id+1)).upper()
7299 if bank_id == 0:
7300 abbreviation = "0"
7301 abbreviation_next = "1"
7302
7303 # calculate the start/stop line numbers for this bank
7304 start_line_id = pokemontools.helpers.index(asm, lambda line: "\"bank" + abbreviation.lower() + "\"" in line.lower())
7305 if bank_id != 0x7F:
7306 end_line_id = pokemontools.helpers.index(asm, lambda line: "\"bank" + abbreviation_next.lower() + "\"" in line.lower())
7307 end_line_id += 1
7308 else:
7309 end_line_id = len(asm) - 1
7310
7311 if debug:
7312 output = "bank" + abbreviation + " starts at "
7313 output += str(start_line_id)
7314 output += " to "
7315 output += str(end_line_id)
7316 logging.debug(output)
7317
7318 # store the start/stop line number for this bank
7319 bank_intervals[bank_id] = {"start": start_line_id,
7320 "end": end_line_id,}
7321 # for each bank..
7322 for bank_id in bank_intervals.keys():
7323 # get the start/stop line number
7324 bank_data = bank_intervals[bank_id]
7325 start_line_id = bank_data["start"]
7326 end_line_id = bank_data["end"]
7327 # get all labels between these two lines
7328 labels = get_labels_between(start_line_id, end_line_id, bank_id)
7329 # bank_intervals[bank_id]["labels"] = labels
7330 all_labels.extend(labels)
7331 write_all_labels(all_labels)
7332 return all_labels
7333
7334all_map_headers = []
7335
7336trainer_group_maximums = {}
7337
7338# Some of the commands need a reference to this data. This is a hacky way to
7339# get around having a global, and it should be fixed eventually.
7340Command.trainer_group_maximums = trainer_group_maximums
7341
7342SingleByteParam.map_internal_ids = map_internal_ids
7343MultiByteParam.map_internal_ids = map_internal_ids
7344
7345def add_map_offsets_into_map_names(map_group_offsets, map_names=None):
7346 """
7347 Add the offsets for each map into the map_names variable.
7348 """
7349 # add the offsets into our map structure, why not (johto maps only)
7350 return [map_names[map_group_id+1].update({"offset": offset}) for map_group_id, offset in enumerate(map_group_offsets)]
7351
7352rom_parsed = False
7353
7354def parse_rom(rom=None, _skip_wram_labels=False, _parse_map_header_at=None, debug=False):
7355 if not rom:
7356 # read the rom and figure out the offsets for maps
7357 rom = direct_load_rom()
7358
7359 # make wram.wram_labels available
7360 if not _skip_wram_labels:
7361 setup_wram_labels()
7362
7363 # figure out the map offsets
7364 map_group_offsets = load_map_group_offsets(map_group_pointer_table=map_group_pointer_table, map_group_count=map_group_count, rom=rom)
7365
7366 # populate the map_names structure with the offsets
7367 add_map_offsets_into_map_names(map_group_offsets, map_names=map_names)
7368
7369 # parse map header bytes for each map
7370 parse_all_map_headers(map_names, all_map_headers=all_map_headers, _parse_map_header_at=_parse_map_header_at, rom=rom, debug=debug)
7371
7372 # find trainers based on scripts and map headers
7373 # this can only happen after parsing the entire map and map scripts
7374 find_trainer_ids_from_scripts(script_parse_table=script_parse_table, trainer_group_maximums=trainer_group_maximums)
7375
7376 # and parse the main TrainerGroupTable once we know the max number of trainers
7377 #global trainer_group_table
7378 trainer_group_table = TrainerGroupTable(trainer_group_maximums=trainer_group_maximums, trainers=trainers, script_parse_table=script_parse_table)
7379
7380 # improve duplicate trainer names
7381 make_trainer_group_name_trainer_ids(trainer_group_table)
7382
7383 global rom_parsed
7384 rom_parsed = True
7385
7386 return map_names
7387
7388def cachably_parse_rom(rom=None):
7389 """
7390 Calls parse_rom if it hasn't been called and completed yet.
7391 """
7392 global rom_parsed
7393 if not rom_parsed:
7394 return parse_rom(rom=rom)
7395 else:
7396 return map_names
7397
7398if __name__ == "crystal":
7399 pass