mirror of
https://github.com/pret/pokemon-reverse-engineering-tools.git
synced 2026-08-09 18:57:29 -05:00
Merge branch 'master' of github.com:kanzure/pokemon-reverse-engineering-tools
This commit is contained in:
commit
da7b863b8e
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -23,8 +23,8 @@
|
|||
# swap files for gedit
|
||||
*~
|
||||
|
||||
# no data from extras/
|
||||
*.json
|
||||
# labels.json is auto-generated by gbz80disasm
|
||||
labels.json
|
||||
|
||||
# for vim configuration
|
||||
# url: http://www.vim.org/scripts/script.php?script_id=441
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
include README.md
|
||||
recursive-include pokemontools *
|
||||
|
||||
global-exclude .*.swp
|
||||
global-exclude *.pyc
|
||||
global-exclude .gitignore
|
||||
global-exclude .DS_Store
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import config
|
||||
import configuration as config
|
||||
import crystal
|
||||
import preprocessor
|
||||
|
|
|
|||
14
pokemontools/addresses.py
Normal file
14
pokemontools/addresses.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
Common methods used against addresses.
|
||||
"""
|
||||
|
||||
def is_valid_address(address):
|
||||
"""is_valid_rom_address"""
|
||||
if address == None:
|
||||
return False
|
||||
if type(address) == str:
|
||||
address = int(address, 16)
|
||||
if 0 <= address <= 2097152:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
File diff suppressed because it is too large
Load Diff
0
pokemontools/crystalparts/__init__.py
Normal file
0
pokemontools/crystalparts/__init__.py
Normal file
8
pokemontools/crystalparts/asmline.py
Normal file
8
pokemontools/crystalparts/asmline.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
class AsmLine:
|
||||
# TODO: parse label lines
|
||||
def __init__(self, line, bank=None):
|
||||
self.line = line
|
||||
self.bank = bank
|
||||
|
||||
def to_asm(self):
|
||||
return self.line
|
||||
424
pokemontools/crystalparts/old_parsers.py
Normal file
424
pokemontools/crystalparts/old_parsers.py
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
"""
|
||||
Some old methods rescued from crystal.py
|
||||
"""
|
||||
|
||||
import pointers
|
||||
|
||||
map_header_byte_size = 9
|
||||
|
||||
all_map_headers = []
|
||||
|
||||
def old_parse_map_script_header_at(address, map_group=None, map_id=None, debug=True):
|
||||
logging.debug("starting to parse the map's script header..")
|
||||
#[[Number1 of pointers] Number1 * [2byte pointer to script][00][00]]
|
||||
ptr_line_size = 4 #[2byte pointer to script][00][00]
|
||||
trigger_ptr_cnt = ord(rom[address])
|
||||
trigger_pointers = helpers.grouper(rom_interval(address+1, trigger_ptr_cnt * ptr_line_size, strings=False), count=ptr_line_size)
|
||||
triggers = {}
|
||||
for index, trigger_pointer in enumerate(trigger_pointers):
|
||||
logging.debug("parsing a trigger header...")
|
||||
byte1 = trigger_pointer[0]
|
||||
byte2 = trigger_pointer[1]
|
||||
ptr = byte1 + (byte2 << 8)
|
||||
trigger_address = pointers.calculate_pointer(ptr, pointers.calculate_bank(address))
|
||||
trigger_script = parse_script_engine_script_at(trigger_address, map_group=map_group, map_id=map_id)
|
||||
triggers[index] = {
|
||||
"script": trigger_script,
|
||||
"address": trigger_address,
|
||||
"pointer": {"1": byte1, "2": byte2},
|
||||
}
|
||||
|
||||
# bump ahead in the byte stream
|
||||
address += trigger_ptr_cnt * ptr_line_size + 1
|
||||
|
||||
#[[Number2 of pointers] Number2 * [hook number][2byte pointer to script]]
|
||||
callback_ptr_line_size = 3
|
||||
callback_ptr_cnt = ord(rom[address])
|
||||
callback_ptrs = helpers.grouper(rom_interval(address+1, callback_ptr_cnt * callback_ptr_line_size, strings=False), count=callback_ptr_line_size)
|
||||
callback_pointers = {}
|
||||
callbacks = {}
|
||||
for index, callback_line in enumerate(callback_ptrs):
|
||||
logging.debug("parsing a callback header..")
|
||||
hook_byte = callback_line[0] # 1, 2, 3, 4, 5
|
||||
callback_byte1 = callback_line[1]
|
||||
callback_byte2 = callback_line[2]
|
||||
callback_ptr = callback_byte1 + (callback_byte2 << 8)
|
||||
callback_address = pointers.calculate_pointer(callback_ptr, pointers.calculate_bank(address))
|
||||
callback_script = parse_script_engine_script_at(callback_address)
|
||||
callback_pointers[len(callback_pointers.keys())] = [hook_byte, callback_ptr]
|
||||
callbacks[index] = {
|
||||
"script": callback_script,
|
||||
"address": callback_address,
|
||||
"pointer": {"1": callback_byte1, "2": callback_byte2},
|
||||
}
|
||||
|
||||
# XXX do these triggers/callbacks call asm or script engine scripts?
|
||||
return {
|
||||
#"trigger_ptr_cnt": trigger_ptr_cnt,
|
||||
"trigger_pointers": trigger_pointers,
|
||||
#"callback_ptr_cnt": callback_ptr_cnt,
|
||||
#"callback_ptr_scripts": callback_ptrs,
|
||||
"callback_pointers": callback_pointers,
|
||||
"trigger_scripts": triggers,
|
||||
"callback_scripts": callbacks,
|
||||
}
|
||||
|
||||
|
||||
def old_parse_map_header_at(address, map_group=None, map_id=None, debug=True):
|
||||
"""parses an arbitrary map header at some address"""
|
||||
logging.debug("parsing a map header at {0}".format(hex(address)))
|
||||
bytes = rom_interval(address, map_header_byte_size, strings=False, debug=debug)
|
||||
bank = bytes[0]
|
||||
tileset = bytes[1]
|
||||
permission = bytes[2]
|
||||
second_map_header_address = pointers.calculate_pointer(bytes[3] + (bytes[4] << 8), 0x25)
|
||||
location_on_world_map = bytes[5] # pokegear world map location
|
||||
music = bytes[6]
|
||||
time_of_day = bytes[7]
|
||||
fishing_group = bytes[8]
|
||||
|
||||
map_header = {
|
||||
"bank": bank,
|
||||
"tileset": tileset,
|
||||
"permission": permission, # map type?
|
||||
"second_map_header_pointer": {"1": bytes[3], "2": bytes[4]},
|
||||
"second_map_header_address": second_map_header_address,
|
||||
"location_on_world_map": location_on_world_map, # area
|
||||
"music": music,
|
||||
"time_of_day": time_of_day,
|
||||
"fishing": fishing_group,
|
||||
}
|
||||
logging.debug("second map header address is {0}".format(hex(second_map_header_address)))
|
||||
map_header["second_map_header"] = old_parse_second_map_header_at(second_map_header_address, debug=debug)
|
||||
event_header_address = map_header["second_map_header"]["event_address"]
|
||||
script_header_address = map_header["second_map_header"]["script_address"]
|
||||
# maybe event_header and script_header should be put under map_header["second_map_header"]
|
||||
map_header["event_header"] = old_parse_map_event_header_at(event_header_address, map_group=map_group, map_id=map_id, debug=debug)
|
||||
map_header["script_header"] = old_parse_map_script_header_at(script_header_address, map_group=map_group, map_id=map_id, debug=debug)
|
||||
return map_header
|
||||
|
||||
all_second_map_headers = []
|
||||
|
||||
def old_parse_second_map_header_at(address, map_group=None, map_id=None, debug=True):
|
||||
"""each map has a second map header"""
|
||||
bytes = rom_interval(address, second_map_header_byte_size, strings=False)
|
||||
border_block = bytes[0]
|
||||
height = bytes[1]
|
||||
width = bytes[2]
|
||||
blockdata_bank = bytes[3]
|
||||
blockdata_pointer = bytes[4] + (bytes[5] << 8)
|
||||
blockdata_address = pointers.calculate_pointer(blockdata_pointer, blockdata_bank)
|
||||
script_bank = bytes[6]
|
||||
script_pointer = bytes[7] + (bytes[8] << 8)
|
||||
script_address = pointers.calculate_pointer(script_pointer, script_bank)
|
||||
event_bank = script_bank
|
||||
event_pointer = bytes[9] + (bytes[10] << 8)
|
||||
event_address = pointers.calculate_pointer(event_pointer, event_bank)
|
||||
connections = bytes[11]
|
||||
return {
|
||||
"border_block": border_block,
|
||||
"height": height,
|
||||
"width": width,
|
||||
"blockdata_bank": blockdata_bank,
|
||||
"blockdata_pointer": {"1": bytes[4], "2": bytes[5]},
|
||||
"blockdata_address": blockdata_address,
|
||||
"script_bank": script_bank,
|
||||
"script_pointer": {"1": bytes[7], "2": bytes[8]},
|
||||
"script_address": script_address,
|
||||
"event_bank": event_bank,
|
||||
"event_pointer": {"1": bytes[9], "2": bytes[10]},
|
||||
"event_address": event_address,
|
||||
"connections": connections,
|
||||
}
|
||||
|
||||
def old_parse_warp_bytes(some_bytes, debug=True):
|
||||
"""parse some number of warps from the data"""
|
||||
assert len(some_bytes) % warp_byte_size == 0, "wrong number of bytes"
|
||||
warps = []
|
||||
for bytes in helpers.grouper(some_bytes, count=warp_byte_size):
|
||||
y = int(bytes[0], 16)
|
||||
x = int(bytes[1], 16)
|
||||
warp_to = int(bytes[2], 16)
|
||||
map_group = int(bytes[3], 16)
|
||||
map_id = int(bytes[4], 16)
|
||||
warps.append({
|
||||
"y": y,
|
||||
"x": x,
|
||||
"warp_to": warp_to,
|
||||
"map_group": map_group,
|
||||
"map_id": map_id,
|
||||
})
|
||||
return warps
|
||||
|
||||
def old_parse_signpost_bytes(some_bytes, bank=None, map_group=None, map_id=None, debug=True):
|
||||
assert len(some_bytes) % signpost_byte_size == 0, "wrong number of bytes"
|
||||
signposts = []
|
||||
for bytes in helpers.grouper(some_bytes, count=signpost_byte_size):
|
||||
y = int(bytes[0], 16)
|
||||
x = int(bytes[1], 16)
|
||||
func = int(bytes[2], 16)
|
||||
|
||||
additional = {}
|
||||
if func in [0, 1, 2, 3, 4]:
|
||||
logging.debug(
|
||||
"parsing signpost script.. signpost is at x={x} y={y}"
|
||||
.format(x=x, y=y)
|
||||
)
|
||||
script_ptr_byte1 = int(bytes[3], 16)
|
||||
script_ptr_byte2 = int(bytes[4], 16)
|
||||
script_pointer = script_ptr_byte1 + (script_ptr_byte2 << 8)
|
||||
|
||||
script_address = None
|
||||
script = None
|
||||
|
||||
script_address = pointers.calculate_pointer(script_pointer, bank)
|
||||
script = parse_script_engine_script_at(script_address, map_group=map_group, map_id=map_id)
|
||||
|
||||
additional = {
|
||||
"script_ptr": script_pointer,
|
||||
"script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2},
|
||||
"script_address": script_address,
|
||||
"script": script,
|
||||
}
|
||||
elif func in [5, 6]:
|
||||
logging.debug(
|
||||
"parsing signpost script.. signpost is at x={x} y={y}"
|
||||
.format(x=x, y=y)
|
||||
)
|
||||
|
||||
ptr_byte1 = int(bytes[3], 16)
|
||||
ptr_byte2 = int(bytes[4], 16)
|
||||
pointer = ptr_byte1 + (ptr_byte2 << 8)
|
||||
address = pointers.calculate_pointer(pointer, bank)
|
||||
bit_table_byte1 = ord(rom[address])
|
||||
bit_table_byte2 = ord(rom[address+1])
|
||||
script_ptr_byte1 = ord(rom[address+2])
|
||||
script_ptr_byte2 = ord(rom[address+3])
|
||||
script_address = calculate_pointer_from_bytes_at(address+2, bank=bank)
|
||||
script = parse_script_engine_script_at(script_address, map_group=map_group, map_id=map_id)
|
||||
|
||||
additional = {
|
||||
"bit_table_bytes": {"1": bit_table_byte1, "2": bit_table_byte2},
|
||||
"script_ptr": script_ptr_byte1 + (script_ptr_byte2 << 8),
|
||||
"script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2},
|
||||
"script_address": script_address,
|
||||
"script": script,
|
||||
}
|
||||
else:
|
||||
logging.debug(".. type 7 or 8 signpost not parsed yet.")
|
||||
|
||||
spost = {
|
||||
"y": y,
|
||||
"x": x,
|
||||
"func": func,
|
||||
}
|
||||
spost.update(additional)
|
||||
signposts.append(spost)
|
||||
return signposts
|
||||
|
||||
def old_parse_people_event_bytes(some_bytes, address=None, map_group=None, map_id=None, debug=True):
|
||||
"""parse some number of people-events from the data
|
||||
see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Scripthdr
|
||||
|
||||
For example, map 1.1 (group 1 map 1) has four person-events.
|
||||
|
||||
37 05 07 06 00 FF FF 00 00 02 40 FF FF
|
||||
3B 08 0C 05 01 FF FF 00 00 05 40 FF FF
|
||||
3A 07 06 06 00 FF FF A0 00 08 40 FF FF
|
||||
29 05 0B 06 00 FF FF 00 00 0B 40 FF FF
|
||||
"""
|
||||
assert len(some_bytes) % people_event_byte_size == 0, "wrong number of bytes"
|
||||
|
||||
# address is not actually required for this function to work...
|
||||
bank = None
|
||||
if address:
|
||||
bank = pointers.calculate_bank(address)
|
||||
|
||||
people_events = []
|
||||
for bytes in helpers.grouper(some_bytes, count=people_event_byte_size):
|
||||
pict = int(bytes[0], 16)
|
||||
y = int(bytes[1], 16) # y from top + 4
|
||||
x = int(bytes[2], 16) # x from left + 4
|
||||
face = int(bytes[3], 16) # 0-4 for regular, 6-9 for static facing
|
||||
move = int(bytes[4], 16)
|
||||
clock_time_byte1 = int(bytes[5], 16)
|
||||
clock_time_byte2 = int(bytes[6], 16)
|
||||
color_function_byte = int(bytes[7], 16) # Color|Function
|
||||
trainer_sight_range = int(bytes[8], 16)
|
||||
|
||||
lower_bits = color_function_byte & 0xF
|
||||
#lower_bits_high = lower_bits >> 2
|
||||
#lower_bits_low = lower_bits & 3
|
||||
higher_bits = color_function_byte >> 4
|
||||
#higher_bits_high = higher_bits >> 2
|
||||
#higher_bits_low = higher_bits & 3
|
||||
|
||||
is_regular_script = lower_bits == 00
|
||||
# pointer points to script
|
||||
is_give_item = lower_bits == 01
|
||||
# pointer points to [Item no.][Amount]
|
||||
is_trainer = lower_bits == 02
|
||||
# pointer points to trainer header
|
||||
|
||||
# goldmap called these next two bytes "text_block" and "text_bank"?
|
||||
script_pointer_byte1 = int(bytes[9], 16)
|
||||
script_pointer_byte2 = int(bytes[10], 16)
|
||||
script_pointer = script_pointer_byte1 + (script_pointer_byte2 << 8)
|
||||
# calculate the full address by assuming it's in the current bank
|
||||
# but what if it's not in the same bank?
|
||||
extra_portion = {}
|
||||
if bank:
|
||||
ptr_address = pointers.calculate_pointer(script_pointer, bank)
|
||||
if is_regular_script:
|
||||
logging.debug(
|
||||
"parsing a person-script at x={x} y={y} address={address}"
|
||||
.format(
|
||||
x=(x-4),
|
||||
y=(y-4),
|
||||
address=hex(ptr_address),
|
||||
)
|
||||
)
|
||||
script = parse_script_engine_script_at(ptr_address, map_group=map_group, map_id=map_id)
|
||||
extra_portion = {
|
||||
"script_address": ptr_address,
|
||||
"script": script,
|
||||
"event_type": "script",
|
||||
}
|
||||
if is_give_item:
|
||||
logging.debug("not parsing give item event.. [item id][quantity]")
|
||||
extra_portion = {
|
||||
"event_type": "give_item",
|
||||
"give_item_data_address": ptr_address,
|
||||
"item_id": ord(rom[ptr_address]),
|
||||
"item_qty": ord(rom[ptr_address+1]),
|
||||
}
|
||||
if is_trainer:
|
||||
logging.debug(
|
||||
"parsing a trainer (person-event) at x={x} y={y}"
|
||||
.format(x=x, y=y)
|
||||
)
|
||||
parsed_trainer = parse_trainer_header_at(ptr_address, map_group=map_group, map_id=map_id)
|
||||
extra_portion = {
|
||||
"event_type": "trainer",
|
||||
"trainer_data_address": ptr_address,
|
||||
"trainer_data": parsed_trainer,
|
||||
}
|
||||
|
||||
# XXX not sure what's going on here
|
||||
# bit no. of bit table 1 (hidden if set)
|
||||
# note: FFFF for none
|
||||
when_byte = int(bytes[11], 16)
|
||||
hide = int(bytes[12], 16)
|
||||
|
||||
bit_number_of_bit_table1_byte2 = int(bytes[11], 16)
|
||||
bit_number_of_bit_table1_byte1 = int(bytes[12], 16)
|
||||
bit_number_of_bit_table1 = bit_number_of_bit_table1_byte1 + (bit_number_of_bit_table1_byte2 << 8)
|
||||
|
||||
people_event = {
|
||||
"pict": pict,
|
||||
"y": y, # y from top + 4
|
||||
"x": x, # x from left + 4
|
||||
"face": face, # 0-4 for regular, 6-9 for static facing
|
||||
"move": move,
|
||||
"clock_time": {"1": clock_time_byte1,
|
||||
"2": clock_time_byte2}, # clock/time setting byte 1
|
||||
"color_function_byte": color_function_byte, # Color|Function
|
||||
"trainer_sight_range": trainer_sight_range, # trainer range of sight
|
||||
"script_pointer": {"1": script_pointer_byte1,
|
||||
"2": script_pointer_byte2},
|
||||
|
||||
#"text_block": text_block, # script pointer byte 1
|
||||
#"text_bank": text_bank, # script pointer byte 2
|
||||
"when_byte": when_byte, # bit no. of bit table 1 (hidden if set)
|
||||
"hide": hide, # note: FFFF for none
|
||||
|
||||
"is_trainer": is_trainer,
|
||||
"is_regular_script": is_regular_script,
|
||||
"is_give_item": is_give_item,
|
||||
}
|
||||
people_event.update(extra_portion)
|
||||
people_events.append(people_event)
|
||||
return people_events
|
||||
|
||||
def old_parse_trainer_header_at(address, map_group=None, map_id=None, debug=True):
|
||||
bank = pointers.calculate_bank(address)
|
||||
bytes = rom_interval(address, 12, strings=False)
|
||||
bit_number = bytes[0] + (bytes[1] << 8)
|
||||
trainer_group = bytes[2]
|
||||
trainer_id = bytes[3]
|
||||
text_when_seen_ptr = calculate_pointer_from_bytes_at(address+4, bank=bank)
|
||||
text_when_seen = parse_text_engine_script_at(text_when_seen_ptr, map_group=map_group, map_id=map_id, debug=debug)
|
||||
text_when_trainer_beaten_ptr = calculate_pointer_from_bytes_at(address+6, bank=bank)
|
||||
text_when_trainer_beaten = parse_text_engine_script_at(text_when_trainer_beaten_ptr, map_group=map_group, map_id=map_id, debug=debug)
|
||||
|
||||
if [ord(rom[address+8]), ord(rom[address+9])] == [0, 0]:
|
||||
script_when_lost_ptr = 0
|
||||
script_when_lost = None
|
||||
else:
|
||||
logging.debug("parsing script-when-lost")
|
||||
script_when_lost_ptr = calculate_pointer_from_bytes_at(address+8, bank=bank)
|
||||
script_when_lost = None
|
||||
silver_avoids = [0xfa53]
|
||||
if script_when_lost_ptr > 0x4000 and not script_when_lost_ptr in silver_avoids:
|
||||
script_when_lost = parse_script_engine_script_at(script_when_lost_ptr, map_group=map_group, map_id=map_id, debug=debug)
|
||||
|
||||
logging.debug("parsing script-talk-again") # or is this a text?
|
||||
script_talk_again_ptr = calculate_pointer_from_bytes_at(address+10, bank=bank)
|
||||
script_talk_again = None
|
||||
if script_talk_again_ptr > 0x4000:
|
||||
script_talk_again = parse_script_engine_script_at(script_talk_again_ptr, map_group=map_group, map_id=map_id, debug=debug)
|
||||
|
||||
return {
|
||||
"bit_number": bit_number,
|
||||
"trainer_group": trainer_group,
|
||||
"trainer_id": trainer_id,
|
||||
"text_when_seen_ptr": text_when_seen_ptr,
|
||||
"text_when_seen": text_when_seen,
|
||||
"text_when_trainer_beaten_ptr": text_when_trainer_beaten_ptr,
|
||||
"text_when_trainer_beaten": text_when_trainer_beaten,
|
||||
"script_when_lost_ptr": script_when_lost_ptr,
|
||||
"script_when_lost": script_when_lost,
|
||||
"script_talk_again_ptr": script_talk_again_ptr,
|
||||
"script_talk_again": script_talk_again,
|
||||
}
|
||||
|
||||
def old_parse_map_event_header_at(address, map_group=None, map_id=None, debug=True):
|
||||
"""parse crystal map event header byte structure thing"""
|
||||
returnable = {}
|
||||
|
||||
bank = pointers.calculate_bank(address)
|
||||
|
||||
logging.debug("event header address is {0}".format(hex(address)))
|
||||
filler1 = ord(rom[address])
|
||||
filler2 = ord(rom[address+1])
|
||||
returnable.update({"1": filler1, "2": filler2})
|
||||
|
||||
# warps
|
||||
warp_count = ord(rom[address+2])
|
||||
warp_byte_count = warp_byte_size * warp_count
|
||||
warps = rom_interval(address+3, warp_byte_count)
|
||||
after_warps = address + 3 + warp_byte_count
|
||||
returnable.update({"warp_count": warp_count, "warps": old_parse_warp_bytes(warps)})
|
||||
|
||||
# triggers (based on xy location)
|
||||
trigger_count = ord(rom[after_warps])
|
||||
trigger_byte_count = trigger_byte_size * trigger_count
|
||||
triggers = rom_interval(after_warps+1, trigger_byte_count)
|
||||
after_triggers = after_warps + 1 + trigger_byte_count
|
||||
returnable.update({"xy_trigger_count": trigger_count, "xy_triggers": old_parse_xy_trigger_bytes(triggers, bank=bank, map_group=map_group, map_id=map_id)})
|
||||
|
||||
# signposts
|
||||
signpost_count = ord(rom[after_triggers])
|
||||
signpost_byte_count = signpost_byte_size * signpost_count
|
||||
signposts = rom_interval(after_triggers+1, signpost_byte_count)
|
||||
after_signposts = after_triggers + 1 + signpost_byte_count
|
||||
returnable.update({"signpost_count": signpost_count, "signposts": old_parse_signpost_bytes(signposts, bank=bank, map_group=map_group, map_id=map_id)})
|
||||
|
||||
# people events
|
||||
people_event_count = ord(rom[after_signposts])
|
||||
people_event_byte_count = people_event_byte_size * people_event_count
|
||||
people_events_bytes = rom_interval(after_signposts+1, people_event_byte_count)
|
||||
people_events = old_parse_people_event_bytes(people_events_bytes, address=after_signposts+1, map_group=map_group, map_id=map_id)
|
||||
returnable.update({"people_event_count": people_event_count, "people_events": people_events})
|
||||
|
||||
return returnable
|
||||
|
|
@ -10,7 +10,7 @@ from ctypes import c_int8
|
|||
import random
|
||||
import json
|
||||
|
||||
import config
|
||||
import configuration
|
||||
import crystal
|
||||
import labels
|
||||
import wram
|
||||
|
|
@ -929,7 +929,7 @@ class Disassembler(object):
|
|||
return (output, offset, last_hl_address, last_a_address, used_3d97)
|
||||
|
||||
if __name__ == "__main__":
|
||||
conf = config.Config()
|
||||
conf = configuration.Config()
|
||||
disasm = Disassembler(conf)
|
||||
disasm.initialize()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import json
|
|||
import logging
|
||||
|
||||
import pointers
|
||||
import crystal
|
||||
|
||||
class Labels(object):
|
||||
"""
|
||||
|
|
@ -32,6 +31,7 @@ class Labels(object):
|
|||
"Running crystal.scan_for_predefined_labels to create \"{0}\". Trying.."
|
||||
.format(Labels.filename)
|
||||
)
|
||||
import crystal
|
||||
crystal.scan_for_predefined_labels()
|
||||
|
||||
self.labels = json.read(open(self.path, "r").read())
|
||||
|
|
@ -197,3 +197,13 @@ def get_label_from_line(line):
|
|||
#split up the line
|
||||
label = line.split(":")[0]
|
||||
return label
|
||||
|
||||
def find_labels_without_addresses(asm):
|
||||
"""scans the asm source and finds labels that are unmarked"""
|
||||
without_addresses = []
|
||||
for (line_number, line) in enumerate(asm):
|
||||
if line_has_label(line):
|
||||
label = get_label_from_line(line)
|
||||
if not line_has_comment_address(line):
|
||||
without_addresses.append({"line_number": line_number, "line": line, "label": label})
|
||||
return without_addresses
|
||||
|
|
|
|||
667
pokemontools/map_editor.py
Normal file
667
pokemontools/map_editor.py
Normal file
|
|
@ -0,0 +1,667 @@
|
|||
import os
|
||||
|
||||
from Tkinter import *
|
||||
import ttk
|
||||
from ttk import Frame, Style
|
||||
import PIL
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
import config
|
||||
conf = config.Config()
|
||||
|
||||
|
||||
#version = 'crystal'
|
||||
version = 'red'
|
||||
|
||||
if version == 'crystal':
|
||||
map_dir = os.path.join(conf.path, 'maps/')
|
||||
gfx_dir = os.path.join(conf.path, 'gfx/tilesets/')
|
||||
to_gfx_name = lambda x : '%.2d' % x
|
||||
block_dir = os.path.join(conf.path, 'tilesets/')
|
||||
block_ext = '_metatiles.bin'
|
||||
|
||||
palettes_on = True
|
||||
palmap_dir = os.path.join(conf.path, 'tilesets/')
|
||||
palette_dir = os.path.join(conf.path, 'tilesets/')
|
||||
|
||||
asm_dir = os.path.join(conf.path, 'maps/')
|
||||
|
||||
constants_dir = os.path.join(conf.path, 'constants/')
|
||||
constants_filename = os.path.join(constants_dir, 'map_constants.asm')
|
||||
|
||||
header_dir = os.path.join(conf.path, 'maps/')
|
||||
|
||||
elif version == 'red':
|
||||
map_dir = os.path.join(conf.path, 'maps/')
|
||||
gfx_dir = os.path.join(conf.path, 'gfx/tilesets/')
|
||||
to_gfx_name = lambda x : '%.2x' % x
|
||||
block_dir = os.path.join(conf.path, 'gfx/blocksets/')
|
||||
block_ext = '.bst'
|
||||
|
||||
palettes_on = False
|
||||
|
||||
asm_path = os.path.join(conf.path, 'main.asm')
|
||||
|
||||
constants_filename = os.path.join(conf.path, 'constants.asm')
|
||||
|
||||
header_path = os.path.join(conf.path, 'main.asm')
|
||||
|
||||
else:
|
||||
raise Exception, 'version must be "crystal" or "red"'
|
||||
|
||||
|
||||
def get_constants():
|
||||
lines = open(constants_filename, 'r').readlines()
|
||||
for line in lines:
|
||||
if ' EQU ' in line:
|
||||
name, value = [s.strip() for s in line.split(' EQU ')]
|
||||
globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b'))
|
||||
get_constants()
|
||||
|
||||
|
||||
class Application(Frame):
|
||||
def __init__(self, master=None):
|
||||
self.display_connections = False
|
||||
Frame.__init__(self, master)
|
||||
self.grid()
|
||||
Style().configure("TFrame", background="#444")
|
||||
self.paint_tile = 1
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
self.connections = {}
|
||||
self.button_frame = Frame(self)
|
||||
self.button_frame.grid(row=0, column=0, columnspan=2)
|
||||
self.map_frame = Frame(self)
|
||||
self.map_frame.grid(row=1, column=0, padx=5, pady=5)
|
||||
self.picker_frame = Frame(self)
|
||||
self.picker_frame.grid(row=1, column=1)
|
||||
|
||||
self.new = Button(self.button_frame)
|
||||
self.new["text"] = "New"
|
||||
self.new["command"] = self.new_map
|
||||
self.new.grid(row=0, column=0, padx=2)
|
||||
|
||||
self.open = Button(self.button_frame)
|
||||
self.open["text"] = "Open"
|
||||
self.open["command"] = self.open_map
|
||||
self.open.grid(row=0, column=1, padx=2)
|
||||
|
||||
self.save = Button(self.button_frame)
|
||||
self.save["text"] = "Save"
|
||||
self.save["command"] = self.save_map
|
||||
self.save.grid(row=0, column=2, padx=2)
|
||||
|
||||
self.get_map_list()
|
||||
self.map_list.grid(row=0, column=3, padx=2)
|
||||
|
||||
|
||||
def get_map_list(self):
|
||||
self.available_maps = sorted(m for m in get_available_maps())
|
||||
self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps)
|
||||
if len(self.available_maps):
|
||||
self.map_list.set(self.available_maps[0])
|
||||
|
||||
def new_map(self):
|
||||
self.map_name = None
|
||||
self.init_map()
|
||||
self.map.blockdata = [self.paint_tile] * 20 * 20
|
||||
self.map.width = 20
|
||||
self.map.height = 20
|
||||
self.draw_map()
|
||||
self.init_picker()
|
||||
|
||||
def open_map(self):
|
||||
self.map_name = self.map_list.get()
|
||||
self.init_map()
|
||||
self.draw_map()
|
||||
self.init_picker()
|
||||
|
||||
def save_map(self):
|
||||
if hasattr(self, 'map'):
|
||||
if self.map.blockdata_filename:
|
||||
with open(self.map.blockdata_filename, 'wb') as save:
|
||||
save.write(self.map.blockdata)
|
||||
print 'blockdata saved as %s' % self.map.blockdata_filename
|
||||
else:
|
||||
print 'dunno how to save this'
|
||||
else:
|
||||
print 'nothing to save'
|
||||
|
||||
def init_map(self):
|
||||
if hasattr(self, 'map'):
|
||||
self.map.kill_canvas()
|
||||
self.map = Map(self.map_frame, self.map_name)
|
||||
self.init_map_connections()
|
||||
|
||||
def draw_map(self):
|
||||
self.map.init_canvas(self.map_frame)
|
||||
self.map.canvas.pack() #.grid(row=1,column=1)
|
||||
self.map.draw()
|
||||
self.map.canvas.bind('<Button-1>', self.paint)
|
||||
self.map.canvas.bind('<B1-Motion>', self.paint)
|
||||
|
||||
def init_picker(self):
|
||||
|
||||
self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id)
|
||||
self.current_tile.blockdata = [self.paint_tile]
|
||||
self.current_tile.width = 1
|
||||
self.current_tile.height = 1
|
||||
self.current_tile.init_canvas()
|
||||
self.current_tile.draw()
|
||||
self.current_tile.canvas.grid(row=0, column=4, padx=4)
|
||||
|
||||
if hasattr(self, 'picker'):
|
||||
self.picker.kill_canvas()
|
||||
self.picker = Map(self, tileset_id=self.map.tileset_id)
|
||||
self.picker.blockdata = range(len(self.picker.tileset.blocks))
|
||||
self.picker.width = 4
|
||||
self.picker.height = len(self.picker.blockdata) / self.picker.width
|
||||
self.picker.init_canvas(self.picker_frame)
|
||||
|
||||
if hasattr(self.picker_frame, 'vbar'):
|
||||
self.picker_frame.vbar.destroy()
|
||||
self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL)
|
||||
self.picker_frame.vbar.pack(side=RIGHT, fill=Y)
|
||||
self.picker_frame.vbar.config(command=self.picker.canvas.yview)
|
||||
|
||||
|
||||
self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height))
|
||||
self.map_frame.update()
|
||||
self.picker.canvas.config(height=self.map_frame.winfo_height())
|
||||
self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set)
|
||||
self.picker.canvas.pack(side=LEFT, expand=True)
|
||||
|
||||
self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event))
|
||||
self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event))
|
||||
self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event))
|
||||
self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event))
|
||||
|
||||
self.picker.draw()
|
||||
self.picker.canvas.bind('<Button-1>', self.pick_block)
|
||||
|
||||
def scroll_picker(self, event):
|
||||
if event.num == 4:
|
||||
self.picker.canvas.yview('scroll', -1, 'units')
|
||||
elif event.num == 5:
|
||||
self.picker.canvas.yview('scroll', 1, 'units')
|
||||
|
||||
|
||||
def pick_block(self, event):
|
||||
block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width)
|
||||
block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height)
|
||||
i = block_y * self.picker.width + block_x
|
||||
self.paint_tile = self.picker.blockdata[i]
|
||||
|
||||
self.current_tile.blockdata = [self.paint_tile]
|
||||
self.current_tile.draw()
|
||||
|
||||
def paint(self, event):
|
||||
block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width)
|
||||
block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height)
|
||||
i = block_y * self.map.width + block_x
|
||||
if 0 <= i < len(self.map.blockdata):
|
||||
self.map.blockdata[i] = self.paint_tile
|
||||
self.map.draw_block(block_x, block_y)
|
||||
|
||||
def init_map_connections(self):
|
||||
if not display_connections:
|
||||
return
|
||||
for direction in self.map.connections.keys():
|
||||
if direction in self.connections.keys():
|
||||
if hasattr(self.connections[direction], 'canvas'):
|
||||
self.connections[direction].kill_canvas()
|
||||
if self.map.connections[direction] == {}:
|
||||
self.connections[direction] = {}
|
||||
continue
|
||||
self.connections[direction] = Map(self, self.map.connections[direction]['map_name'])
|
||||
|
||||
if direction in ['north', 'south']:
|
||||
x1 = 0
|
||||
y1 = 0
|
||||
x2 = x1 + eval(self.map.connections[direction]['strip_length'])
|
||||
y2 = y1 + 3
|
||||
else: # east, west
|
||||
x1 = 0
|
||||
y1 = 0
|
||||
x2 = x1 + 3
|
||||
y2 = y1 + eval(self.map.connections[direction]['strip_length'])
|
||||
|
||||
self.connections[direction].crop(x1, y1, x2, y2)
|
||||
self.connections[direction].init_canvas(self.map_frame)
|
||||
self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction])
|
||||
self.connections[direction].draw()
|
||||
|
||||
|
||||
class Map:
|
||||
def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None):
|
||||
self.parent = parent
|
||||
|
||||
self.name = name
|
||||
|
||||
self.blockdata_filename = blockdata_filename
|
||||
if not self.blockdata_filename and self.name:
|
||||
self.blockdata_filename = os.path.join(map_dir, self.name + '.blk')
|
||||
elif not self.blockdata_filename:
|
||||
self.blockdata_filename = ''
|
||||
|
||||
asm_filename = ''
|
||||
if self.name:
|
||||
if 'asm_dir' in globals().keys():
|
||||
asm_filename = os.path.join(asm_dir, self.name + '.asm')
|
||||
elif 'asm_path' in globals().keys():
|
||||
asm_filename = asm_path
|
||||
|
||||
if os.path.exists(asm_filename):
|
||||
for props in [map_header(self.name), second_map_header(self.name)]:
|
||||
self.__dict__.update(props)
|
||||
self.asm = open(asm_filename, 'r').read()
|
||||
self.events = event_header(self.asm, self.name)
|
||||
self.scripts = script_header(self.asm, self.name)
|
||||
|
||||
self.tileset_id = eval(self.tileset_id)
|
||||
|
||||
self.width = eval(self.width)
|
||||
self.height = eval(self.height)
|
||||
|
||||
else:
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.tileset_id = tileset_id
|
||||
|
||||
if self.blockdata_filename:
|
||||
self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read())
|
||||
else:
|
||||
self.blockdata = []
|
||||
|
||||
self.tileset = Tileset(self.tileset_id)
|
||||
|
||||
def init_canvas(self, parent=None):
|
||||
if parent == None:
|
||||
parent = self.parent
|
||||
if not hasattr(self, 'canvas'):
|
||||
self.canvas_width = self.width * 32
|
||||
self.canvas_height = self.height * 32
|
||||
self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height)
|
||||
self.canvas.xview_moveto(0)
|
||||
self.canvas.yview_moveto(0)
|
||||
|
||||
def kill_canvas(self):
|
||||
if hasattr(self, 'canvas'):
|
||||
self.canvas.destroy()
|
||||
|
||||
def crop(self, x1, y1, x2, y2):
|
||||
blockdata = self.blockdata
|
||||
start = y1 * self.width + x1
|
||||
width = x2 - x1
|
||||
height = y2 - y1
|
||||
self.blockdata = []
|
||||
for y in xrange(height):
|
||||
for x in xrange(width):
|
||||
self.blockdata += [blockdata[start + y * self.width + x]]
|
||||
self.blockdata = bytearray(self.blockdata)
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def draw(self):
|
||||
for i in xrange(len(self.blockdata)):
|
||||
block_x = i % self.width
|
||||
block_y = i / self.width
|
||||
self.draw_block(block_x, block_y)
|
||||
|
||||
def draw_block(self, block_x, block_y):
|
||||
# the canvas starts at 4, 4 for some reason
|
||||
# probably something to do with a border
|
||||
index, indey = 4, 4
|
||||
|
||||
# Draw one block (4x4 tiles)
|
||||
block = self.blockdata[block_y * self.width + block_x]
|
||||
for j, tile in enumerate(self.tileset.blocks[block]):
|
||||
try:
|
||||
# Tile gfx are split in half to make vram mapping easier
|
||||
if tile >= 0x80:
|
||||
tile -= 0x20
|
||||
tile_x = block_x * 32 + (j % 4) * 8
|
||||
tile_y = block_y * 32 + (j / 4) * 8
|
||||
self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile])
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class Tileset:
|
||||
def __init__(self, tileset_id=0):
|
||||
self.id = tileset_id
|
||||
|
||||
self.tile_width = 8
|
||||
self.tile_height = 8
|
||||
self.block_width = 4
|
||||
self.block_height = 4
|
||||
|
||||
self.alpha = 255
|
||||
|
||||
if palettes_on:
|
||||
self.get_palettes()
|
||||
self.get_palette_map()
|
||||
|
||||
self.get_blocks()
|
||||
self.get_tiles()
|
||||
|
||||
def get_tileset_gfx_filename(self):
|
||||
filename = None
|
||||
|
||||
if version == 'red':
|
||||
tileset_defs = open(os.path.join(conf.path, 'main.asm'), 'r').read()
|
||||
incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id)
|
||||
print incbin
|
||||
filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png')
|
||||
filename = os.path.join(conf.path, filename)
|
||||
print filename
|
||||
|
||||
if not filename:
|
||||
filename = os.path.join(
|
||||
gfx_dir,
|
||||
to_gfx_name(self.id) + '.png'
|
||||
)
|
||||
|
||||
return filename
|
||||
|
||||
def get_tiles(self):
|
||||
filename = self.get_tileset_gfx_filename()
|
||||
if not os.path.exists(filename):
|
||||
import gfx
|
||||
gfx.to_png(filename.replace('.png','.2bpp'), filename)
|
||||
self.img = Image.open(filename)
|
||||
self.img.width, self.img.height = self.img.size
|
||||
self.tiles = []
|
||||
cur_tile = 0
|
||||
for y in xrange(0, self.img.height, self.tile_height):
|
||||
for x in xrange(0, self.img.width, self.tile_width):
|
||||
tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height))
|
||||
|
||||
if hasattr(self, 'palette_map') and hasattr(self, 'palettes'):
|
||||
# Palette maps are padded to make vram mapping easier.
|
||||
pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7
|
||||
tile = self.colorize_tile(tile, self.palettes[pal])
|
||||
|
||||
self.tiles += [ImageTk.PhotoImage(tile)]
|
||||
cur_tile += 1
|
||||
|
||||
def colorize_tile(self, tile, palette):
|
||||
width, height = tile.size
|
||||
tile = tile.convert("RGB")
|
||||
px = tile.load()
|
||||
for y in xrange(height):
|
||||
for x in xrange(width):
|
||||
# assume greyscale
|
||||
which_color = 3 - (px[x, y][0] / 0x55)
|
||||
r, g, b = [v * 8 for v in palette[which_color]]
|
||||
px[x, y] = (r, g, b)
|
||||
return tile
|
||||
|
||||
def get_blocks(self):
|
||||
filename = os.path.join(
|
||||
block_dir,
|
||||
to_gfx_name(self.id) + block_ext
|
||||
)
|
||||
self.blocks = []
|
||||
block_length = self.block_width * self.block_height
|
||||
blocks = bytearray(open(filename, 'rb').read())
|
||||
for block in xrange(len(blocks) / (block_length)):
|
||||
i = block * block_length
|
||||
self.blocks += [blocks[i : i + block_length]]
|
||||
|
||||
def get_palette_map(self):
|
||||
filename = os.path.join(
|
||||
palmap_dir,
|
||||
str(self.id).zfill(2) + '_palette_map.bin'
|
||||
)
|
||||
self.palette_map = []
|
||||
palmap = bytearray(open(filename, 'rb').read())
|
||||
for i in xrange(len(palmap)):
|
||||
self.palette_map += [palmap[i] & 0xf]
|
||||
self.palette_map += [(palmap[i] >> 4) & 0xf]
|
||||
|
||||
def get_palettes(self):
|
||||
filename = os.path.join(
|
||||
palette_dir,
|
||||
['morn', 'day', 'nite'][time_of_day] + '.pal'
|
||||
)
|
||||
self.palettes = get_palettes(filename)
|
||||
|
||||
|
||||
time_of_day = 1
|
||||
|
||||
|
||||
def get_palettes(filename):
|
||||
pals = bytearray(open(filename, 'rb').read())
|
||||
|
||||
num_colors = 4
|
||||
color_length = 2
|
||||
|
||||
palette_length = num_colors * color_length
|
||||
|
||||
num_pals = len(pals) / palette_length
|
||||
|
||||
palettes = []
|
||||
for pal in xrange(num_pals):
|
||||
palettes += [[]]
|
||||
|
||||
for color in xrange(num_colors):
|
||||
i = pal * palette_length
|
||||
i += color * color_length
|
||||
word = pals[i] + pals[i+1] * 0x100
|
||||
palettes[pal] += [[
|
||||
c & 0x1f for c in [
|
||||
word >> 0,
|
||||
word >> 5,
|
||||
word >> 10,
|
||||
]
|
||||
]]
|
||||
return palettes
|
||||
|
||||
|
||||
|
||||
def get_available_maps():
|
||||
for root, dirs, files in os.walk(map_dir):
|
||||
for filename in files:
|
||||
base_name, ext = os.path.splitext(filename)
|
||||
if ext == '.blk':
|
||||
yield base_name
|
||||
|
||||
|
||||
def map_header(name):
|
||||
if version == 'crystal':
|
||||
headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read()
|
||||
label = name + '_MapHeader'
|
||||
header = asm_at_label(headers, label)
|
||||
macros = [ 'db', 'db', 'db', 'dw', 'db', 'db', 'db', 'db' ]
|
||||
attributes = [
|
||||
'bank',
|
||||
'tileset_id',
|
||||
'permission',
|
||||
'second_map_header',
|
||||
'world_map_location',
|
||||
'music',
|
||||
'time_of_day',
|
||||
'fishing_group',
|
||||
]
|
||||
values, l = read_header_macros(header, attributes, macros)
|
||||
attrs = dict(zip(attributes, values))
|
||||
return attrs
|
||||
|
||||
elif version == 'red':
|
||||
headers = open(header_path, 'r').read()
|
||||
|
||||
# there has to be a better way to do this
|
||||
lower_label = name + '_h'
|
||||
i = headers.lower().find(lower_label)
|
||||
if i == -1:
|
||||
return {}
|
||||
label = headers[i:i+len(lower_label)]
|
||||
|
||||
header = asm_at_label(headers, label)
|
||||
macros = [ 'db', 'db', 'db', 'dw', 'dw', 'dw', 'db' ]
|
||||
attributes = [
|
||||
'tileset_id',
|
||||
'height',
|
||||
'width',
|
||||
'blockdata_label',
|
||||
'text_label',
|
||||
'script_label',
|
||||
'which_connections',
|
||||
]
|
||||
values, l = read_header_macros(header, attributes, macros)
|
||||
|
||||
attrs = dict(zip(attributes, values))
|
||||
attrs['connections'], l = connections(attrs['which_connections'], header, l)
|
||||
|
||||
macros = [ 'dw' ]
|
||||
attributes = [
|
||||
'object_label',
|
||||
]
|
||||
values, l = read_header_macros(header[l:], attributes, macros)
|
||||
attrs.update(dict(zip(attributes, values)))
|
||||
|
||||
return attrs
|
||||
|
||||
return {}
|
||||
|
||||
def second_map_header(name):
|
||||
if version == 'crystal':
|
||||
headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read()
|
||||
label = name + '_SecondMapHeader'
|
||||
header = asm_at_label(headers, label)
|
||||
macros = [ 'db', 'db', 'db', 'db', 'dw', 'db', 'dw', 'dw', 'db' ]
|
||||
attributes = [
|
||||
'border_block',
|
||||
'height',
|
||||
'width',
|
||||
'blockdata_bank',
|
||||
'blockdata_label',
|
||||
'script_header_bank',
|
||||
'script_header_label',
|
||||
'map_event_header_label',
|
||||
'which_connections',
|
||||
]
|
||||
|
||||
values, l = read_header_macros(header, attributes, macros)
|
||||
attrs = dict(zip(attributes, values))
|
||||
attrs['connections'], l = connections(attrs['which_connections'], header, l)
|
||||
return attrs
|
||||
|
||||
return {}
|
||||
|
||||
def connections(which_connections, header, l=0):
|
||||
directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} }
|
||||
|
||||
if version == 'crystal':
|
||||
macros = [ 'db', 'db' ]
|
||||
attributes = [
|
||||
'map_group',
|
||||
'map_no',
|
||||
]
|
||||
|
||||
elif version == 'red':
|
||||
macros = [ 'db' ]
|
||||
attributes = [
|
||||
'map_id',
|
||||
]
|
||||
|
||||
macros += [ 'dw', 'dw', 'db', 'db', 'db', 'db', 'dw' ]
|
||||
attributes += [
|
||||
'strip_pointer',
|
||||
'strip_destination',
|
||||
'strip_length',
|
||||
'map_width',
|
||||
'y_offset',
|
||||
'x_offset',
|
||||
'window',
|
||||
]
|
||||
for d in directions.keys():
|
||||
if d.upper() in which_connections:
|
||||
values, l = read_header_macros(header, attributes, macros)
|
||||
header = header[l:]
|
||||
directions[d] = dict(zip(attributes, values))
|
||||
if version == 'crystal':
|
||||
directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','')
|
||||
elif version == 'red':
|
||||
directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','')
|
||||
return directions, l
|
||||
|
||||
def read_header_macros(header, attributes, macros):
|
||||
values = []
|
||||
i = 0
|
||||
l = 0
|
||||
for l, (asm, comment) in enumerate(header):
|
||||
if asm.strip() != '':
|
||||
mvalues = macro_values(asm, macros[i])
|
||||
values += mvalues
|
||||
i += len(mvalues)
|
||||
if len(values) >= len(attributes):
|
||||
l += 1
|
||||
break
|
||||
return values, l
|
||||
|
||||
|
||||
def event_header(asm, name):
|
||||
return {}
|
||||
|
||||
def script_header(asm, name):
|
||||
return {}
|
||||
|
||||
|
||||
def macro_values(line, macro):
|
||||
values = line[line.find(macro) + len(macro):].split(',')
|
||||
values = [v.replace('$','0x').strip() for v in values]
|
||||
if values[0] == 'w': # dbw
|
||||
values = values[1:]
|
||||
return values
|
||||
|
||||
def db_value(line):
|
||||
macro = 'db'
|
||||
return macro_values(line, macro)
|
||||
|
||||
def db_values(line):
|
||||
macro = 'db'
|
||||
return macro_values(line, macro)
|
||||
|
||||
|
||||
from preprocessor import separate_comment
|
||||
|
||||
def asm_at_label(asm, label):
|
||||
label_def = label + ':'
|
||||
lines = asm.split('\n')
|
||||
for line in lines:
|
||||
if line.startswith(label_def):
|
||||
lines = lines[lines.index(line):]
|
||||
lines[0] = lines[0][len(label_def):]
|
||||
break
|
||||
# go until the next label
|
||||
content = []
|
||||
for line in lines:
|
||||
l, comment = separate_comment(line + '\n')
|
||||
if ':' in l:
|
||||
break
|
||||
content += [[l, comment]]
|
||||
return content
|
||||
|
||||
def main():
|
||||
"""
|
||||
Launches the map editor.
|
||||
"""
|
||||
root = Tk()
|
||||
root.wm_title("MAP EDITOR")
|
||||
app = Application(master=root)
|
||||
|
||||
try:
|
||||
app.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
try:
|
||||
root.destroy()
|
||||
except TclError:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
528
pokemontools/old_text_script.py
Normal file
528
pokemontools/old_text_script.py
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
"""
|
||||
An old implementation of TextScript that may not be useful anymore.
|
||||
"""
|
||||
|
||||
import pointers
|
||||
|
||||
class OldTextScript:
|
||||
"a text is a sequence of commands different from a script-engine script"
|
||||
base_label = "UnknownText_"
|
||||
def __init__(self, address, map_group=None, map_id=None, debug=True, show=True, force=False, label=None):
|
||||
self.address = address
|
||||
self.map_group, self.map_id, self.debug, self.show, self.force = map_group, map_id, debug, show, force
|
||||
if not label:
|
||||
label = self.base_label + hex(address)
|
||||
self.label = Label(name=label, address=address, object=self)
|
||||
self.dependencies = []
|
||||
self.parse_text_at(address)
|
||||
|
||||
@staticmethod
|
||||
def find_addresses():
|
||||
"""returns a list of text pointers
|
||||
useful for testing parse_text_engine_script_at
|
||||
|
||||
Note that this list is not exhaustive. There are some texts that
|
||||
are only pointed to from some script that a current script just
|
||||
points to. So find_all_text_pointers_in_script_engine_script will
|
||||
have to recursively follow through each script to find those.
|
||||
.. it does this now :)
|
||||
"""
|
||||
addresses = set()
|
||||
# for each map group
|
||||
for map_group in map_names:
|
||||
# for each map id
|
||||
for map_id in map_names[map_group]:
|
||||
# skip the offset key
|
||||
if map_id == "offset": continue
|
||||
# dump this into smap
|
||||
smap = map_names[map_group][map_id]
|
||||
# signposts
|
||||
signposts = smap["signposts"]
|
||||
# for each signpost
|
||||
for signpost in signposts:
|
||||
if signpost["func"] in [0, 1, 2, 3, 4]:
|
||||
# dump this into script
|
||||
script = signpost["script"]
|
||||
elif signpost["func"] in [05, 06]:
|
||||
script = signpost["script"]
|
||||
else: continue
|
||||
# skip signposts with no bytes
|
||||
if len(script) == 0: continue
|
||||
# find all text pointers in script
|
||||
texts = find_all_text_pointers_in_script_engine_script(script, smap["event_bank"])
|
||||
# dump these addresses in
|
||||
addresses.update(texts)
|
||||
# xy triggers
|
||||
xy_triggers = smap["xy_triggers"]
|
||||
# for each xy trigger
|
||||
for xy_trigger in xy_triggers:
|
||||
# dump this into script
|
||||
script = xy_trigger["script"]
|
||||
# find all text pointers in script
|
||||
texts = find_all_text_pointers_in_script_engine_script(script, smap["event_bank"])
|
||||
# dump these addresses in
|
||||
addresses.update(texts)
|
||||
# trigger scripts
|
||||
triggers = smap["trigger_scripts"]
|
||||
# for each trigger
|
||||
for (i, trigger) in triggers.items():
|
||||
# dump this into script
|
||||
script = trigger["script"]
|
||||
# find all text pointers in script
|
||||
texts = find_all_text_pointers_in_script_engine_script(script, pointers.calculate_bank(trigger["address"]))
|
||||
# dump these addresses in
|
||||
addresses.update(texts)
|
||||
# callback scripts
|
||||
callbacks = smap["callback_scripts"]
|
||||
# for each callback
|
||||
for (k, callback) in callbacks.items():
|
||||
# dump this into script
|
||||
script = callback["script"]
|
||||
# find all text pointers in script
|
||||
texts = find_all_text_pointers_in_script_engine_script(script, pointers.calculate_bank(callback["address"]))
|
||||
# dump these addresses in
|
||||
addresses.update(texts)
|
||||
# people-events
|
||||
events = smap["people_events"]
|
||||
# for each event
|
||||
for event in events:
|
||||
if event["event_type"] == "script":
|
||||
# dump this into script
|
||||
script = event["script"]
|
||||
# find all text pointers in script
|
||||
texts = find_all_text_pointers_in_script_engine_script(script, smap["event_bank"])
|
||||
# dump these addresses in
|
||||
addresses.update(texts)
|
||||
if event["event_type"] == "trainer":
|
||||
trainer_data = event["trainer_data"]
|
||||
addresses.update([trainer_data["text_when_seen_ptr"]])
|
||||
addresses.update([trainer_data["text_when_trainer_beaten_ptr"]])
|
||||
trainer_bank = pointers.calculate_bank(event["trainer_data_address"])
|
||||
script1 = trainer_data["script_talk_again"]
|
||||
texts1 = find_all_text_pointers_in_script_engine_script(script1, trainer_bank)
|
||||
addresses.update(texts1)
|
||||
script2 = trainer_data["script_when_lost"]
|
||||
texts2 = find_all_text_pointers_in_script_engine_script(script2, trainer_bank)
|
||||
addresses.update(texts2)
|
||||
return addresses
|
||||
|
||||
def parse_text_at(self, address):
|
||||
"""parses a text-engine script ("in-text scripts")
|
||||
http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText
|
||||
|
||||
This is presently very broken.
|
||||
|
||||
see parse_text_at2, parse_text_at, and process_00_subcommands
|
||||
"""
|
||||
global rom, text_count, max_texts, texts, script_parse_table
|
||||
if rom == None:
|
||||
direct_load_rom()
|
||||
if address == None:
|
||||
return "not a script"
|
||||
map_group, map_id, debug, show, force = self.map_group, self.map_id, self.debug, self.show, self.force
|
||||
commands = {}
|
||||
|
||||
if is_script_already_parsed_at(address) and not force:
|
||||
logging.debug("text is already parsed at this location: {0}".format(hex(address)))
|
||||
raise Exception("text is already parsed, what's going on ?")
|
||||
return script_parse_table[address]
|
||||
|
||||
total_text_commands = 0
|
||||
command_counter = 0
|
||||
original_address = address
|
||||
offset = address
|
||||
end = False
|
||||
script_parse_table[original_address:original_address+1] = "incomplete text"
|
||||
while not end:
|
||||
address = offset
|
||||
command = {}
|
||||
command_byte = ord(rom[address])
|
||||
if debug:
|
||||
logging.debug(
|
||||
"TextScript.parse_script_at has encountered a command byte {0} at {1}"
|
||||
.format(hex(command_byte), hex(address))
|
||||
)
|
||||
end_address = address + 1
|
||||
if command_byte == 0:
|
||||
# read until $57, $50 or $58
|
||||
jump57 = how_many_until(chr(0x57), offset, rom)
|
||||
jump50 = how_many_until(chr(0x50), offset, rom)
|
||||
jump58 = how_many_until(chr(0x58), offset, rom)
|
||||
|
||||
# whichever command comes first
|
||||
jump = min([jump57, jump50, jump58])
|
||||
|
||||
end_address = offset + jump # we want the address before $57
|
||||
|
||||
lines = process_00_subcommands(offset+1, end_address, debug=debug)
|
||||
|
||||
if show and debug:
|
||||
text = parse_text_at2(offset+1, end_address-offset+1, debug=debug)
|
||||
logging.debug("output of parse_text_at2 is {0}".format(text))
|
||||
|
||||
command = {"type": command_byte,
|
||||
"start_address": offset,
|
||||
"end_address": end_address,
|
||||
"size": jump,
|
||||
"lines": lines,
|
||||
}
|
||||
|
||||
offset += jump
|
||||
elif command_byte == 0x17:
|
||||
# TX_FAR [pointer][bank]
|
||||
pointer_byte1 = ord(rom[offset+1])
|
||||
pointer_byte2 = ord(rom[offset+2])
|
||||
pointer_bank = ord(rom[offset+3])
|
||||
|
||||
pointer = (pointer_byte1 + (pointer_byte2 << 8))
|
||||
pointer = extract_maps.calculate_pointer(pointer, pointer_bank)
|
||||
|
||||
text = TextScript(pointer, map_group=self.map_group, map_id=self.amp_id, debug=self.debug, \
|
||||
show=self.debug, force=self.debug, label="Target"+self.label.name)
|
||||
if text.is_valid():
|
||||
self.dependencies.append(text)
|
||||
|
||||
command = {"type": command_byte,
|
||||
"start_address": offset,
|
||||
"end_address": offset + 3, # last byte belonging to this command
|
||||
"pointer": pointer, # parameter
|
||||
"text": text,
|
||||
}
|
||||
|
||||
offset += 3 + 1
|
||||
elif command_byte == 0x50 or command_byte == 0x57 or command_byte == 0x58: # end text
|
||||
command = {"type": command_byte,
|
||||
"start_address": offset,
|
||||
"end_address": offset,
|
||||
}
|
||||
|
||||
# this byte simply indicates to end the script
|
||||
end = True
|
||||
|
||||
# this byte simply indicates to end the script
|
||||
if command_byte == 0x50 and ord(rom[offset+1]) == 0x50: # $50$50 means end completely
|
||||
end = True
|
||||
commands[command_counter+1] = command
|
||||
|
||||
# also save the next byte, before we quit
|
||||
commands[command_counter+1]["start_address"] += 1
|
||||
commands[command_counter+1]["end_address"] += 1
|
||||
add_command_byte_to_totals(command_byte)
|
||||
elif command_byte == 0x50: # only end if we started with $0
|
||||
if len(commands.keys()) > 0:
|
||||
if commands[0]["type"] == 0x0: end = True
|
||||
elif command_byte == 0x57 or command_byte == 0x58: # end completely
|
||||
end = True
|
||||
offset += 1 # go past this 0x50
|
||||
elif command_byte == 0x1:
|
||||
# 01 = text from RAM. [01][2-byte pointer]
|
||||
size = 3 # total size, including the command byte
|
||||
pointer_byte1 = ord(rom[offset+1])
|
||||
pointer_byte2 = ord(rom[offset+2])
|
||||
|
||||
command = {"type": command_byte,
|
||||
"start_address": offset+1,
|
||||
"end_address": offset+2, # last byte belonging to this command
|
||||
"pointer": [pointer_byte1, pointer_byte2], # RAM pointer
|
||||
}
|
||||
|
||||
# view near these bytes
|
||||
# subsection = rom[offset:offset+size+1] #peak ahead
|
||||
#for x in subsection:
|
||||
# print hex(ord(x))
|
||||
#print "--"
|
||||
|
||||
offset += 2 + 1 # go to the next byte
|
||||
|
||||
# use this to look at the surrounding bytes
|
||||
if debug:
|
||||
logging.debug("next command is {0}".format(hex(ord(rom[offset]))))
|
||||
logging.debug(
|
||||
".. current command number is {counter} near {offset} on map_id={map_id}"
|
||||
.format(
|
||||
counter=command_counter,
|
||||
offset=hex(offset),
|
||||
map_id=map_id,
|
||||
)
|
||||
)
|
||||
elif command_byte == 0x7:
|
||||
# 07 = shift texts 1 row above (2nd line becomes 1st line); address for next text = 2nd line. [07]
|
||||
size = 1
|
||||
command = {"type": command_byte,
|
||||
"start_address": offset,
|
||||
"end_address": offset,
|
||||
}
|
||||
offset += 1
|
||||
elif command_byte == 0x3:
|
||||
# 03 = set new address in RAM for text. [03][2-byte RAM address]
|
||||
size = 3
|
||||
command = {"type": command_byte, "start_address": offset, "end_address": offset+2}
|
||||
offset += size
|
||||
elif command_byte == 0x4: # draw box
|
||||
# 04 = draw box. [04][2-Byte pointer][height Y][width X]
|
||||
size = 5 # including the command
|
||||
command = {
|
||||
"type": command_byte,
|
||||
"start_address": offset,
|
||||
"end_address": offset + size,
|
||||
"pointer_bytes": [ord(rom[offset+1]), ord(rom[offset+2])],
|
||||
"y": ord(rom[offset+3]),
|
||||
"x": ord(rom[offset+4]),
|
||||
}
|
||||
offset += size + 1
|
||||
elif command_byte == 0x5:
|
||||
# 05 = write text starting at 2nd line of text-box. [05][text][ending command]
|
||||
# read until $57, $50 or $58
|
||||
jump57 = how_many_until(chr(0x57), offset, rom)
|
||||
jump50 = how_many_until(chr(0x50), offset, rom)
|
||||
jump58 = how_many_until(chr(0x58), offset, rom)
|
||||
|
||||
# whichever command comes first
|
||||
jump = min([jump57, jump50, jump58])
|
||||
|
||||
end_address = offset + jump # we want the address before $57
|
||||
|
||||
lines = process_00_subcommands(offset+1, end_address, debug=debug)
|
||||
|
||||
if show and debug:
|
||||
text = parse_text_at2(offset+1, end_address-offset+1, debug=debug)
|
||||
logging.debug("parse_text_at2 text is {0}".format(text))
|
||||
|
||||
command = {"type": command_byte,
|
||||
"start_address": offset,
|
||||
"end_address": end_address,
|
||||
"size": jump,
|
||||
"lines": lines,
|
||||
}
|
||||
offset = end_address + 1
|
||||
elif command_byte == 0x6:
|
||||
# 06 = wait for keypress A or B (put blinking arrow in textbox). [06]
|
||||
command = {"type": command_byte, "start_address": offset, "end_address": offset}
|
||||
offset += 1
|
||||
elif command_byte == 0x7:
|
||||
# 07 = shift texts 1 row above (2nd line becomes 1st line); address for next text = 2nd line. [07]
|
||||
command = {"type": command_byte, "start_address": offset, "end_address": offset}
|
||||
offset += 1
|
||||
elif command_byte == 0x8:
|
||||
# 08 = asm until whenever
|
||||
command = {"type": command_byte, "start_address": offset, "end_address": offset}
|
||||
offset += 1
|
||||
end = True
|
||||
elif command_byte == 0x9:
|
||||
# 09 = write hex-to-dec number from RAM to textbox [09][2-byte RAM address][byte bbbbcccc]
|
||||
# bbbb = how many bytes to read (read number is big-endian)
|
||||
# cccc = how many digits display (decimal)
|
||||
#(note: max of decimal digits is 7,i.e. max number correctly displayable is 9999999)
|
||||
ram_address_byte1 = ord(rom[offset+1])
|
||||
ram_address_byte2 = ord(rom[offset+2])
|
||||
read_byte = ord(rom[offset+3])
|
||||
|
||||
command = {
|
||||
"type": command_byte,
|
||||
"address": [ram_address_byte1, ram_address_byte2],
|
||||
"read_byte": read_byte, # split this up when we make a macro for this
|
||||
}
|
||||
|
||||
offset += 4
|
||||
else:
|
||||
#if len(commands) > 0:
|
||||
# print "Unknown text command " + hex(command_byte) + " at " + hex(offset) + ", script began with " + hex(commands[0]["type"])
|
||||
if debug:
|
||||
logging.debug(
|
||||
"Unknown text command at {offset} - command: {command} on map_id={map_id}"
|
||||
.format(
|
||||
offset=hex(offset),
|
||||
command=hex(ord(rom[offset])),
|
||||
map_id=map_id,
|
||||
)
|
||||
)
|
||||
|
||||
# end at the first unknown command
|
||||
end = True
|
||||
commands[command_counter] = command
|
||||
command_counter += 1
|
||||
total_text_commands += len(commands)
|
||||
|
||||
text_count += 1
|
||||
#if text_count >= max_texts:
|
||||
# sys.exit()
|
||||
|
||||
self.commands = commands
|
||||
self.last_address = offset
|
||||
script_parse_table[original_address:offset] = self
|
||||
all_texts.append(self)
|
||||
self.size = self.byte_count = self.last_address - original_address
|
||||
return commands
|
||||
|
||||
def get_dependencies(self, recompute=False, global_dependencies=set()):
|
||||
global_dependencies.update(self.dependencies)
|
||||
return self.dependencies
|
||||
|
||||
def to_asm(self, label=None):
|
||||
address = self.address
|
||||
start_address = address
|
||||
if label == None: label = self.label.name
|
||||
# using deepcopy because otherwise additional @s get appended each time
|
||||
# like to the end of the text for TextScript(0x5cf3a)
|
||||
commands = deepcopy(self.commands)
|
||||
# apparently this isn't important anymore?
|
||||
needs_to_begin_with_0 = True
|
||||
# start with zero please
|
||||
byte_count = 0
|
||||
# where we store all output
|
||||
output = ""
|
||||
had_text_end_byte = False
|
||||
had_text_end_byte_57_58 = False
|
||||
had_db_last = False
|
||||
xspacing = ""
|
||||
# reset this pretty fast..
|
||||
first_line = True
|
||||
# for each command..
|
||||
for this_command in commands.keys():
|
||||
if not "lines" in commands[this_command].keys():
|
||||
command = commands[this_command]
|
||||
if not "type" in command.keys():
|
||||
logging.debug("ERROR in command: {0}".format(command))
|
||||
continue # dunno what to do here?
|
||||
|
||||
if command["type"] == 0x1: # TX_RAM
|
||||
p1 = command["pointer"][0]
|
||||
p2 = command["pointer"][1]
|
||||
|
||||
# remember to account for big endian -> little endian
|
||||
output += "\n" + xspacing + "TX_RAM $%.2x%.2x" %(p2, p1)
|
||||
byte_count += 3
|
||||
had_db_last = False
|
||||
elif command["type"] == 0x17: # TX_FAR
|
||||
#p1 = command["pointer"][0]
|
||||
#p2 = command["pointer"][1]
|
||||
output += "\n" + xspacing + "TX_FAR _" + label + " ; " + hex(command["pointer"])
|
||||
byte_count += 4 # $17, bank, address word
|
||||
had_db_last = False
|
||||
elif command["type"] == 0x9: # TX_RAM_HEX2DEC
|
||||
# address, read_byte
|
||||
output += "\n" + xspacing + "TX_NUM $%.2x%.2x, $%.2x" % (command["address"][1], command["address"][0], command["read_byte"])
|
||||
had_db_last = False
|
||||
byte_count += 4
|
||||
elif command["type"] == 0x50 and not had_text_end_byte:
|
||||
# had_text_end_byte helps us avoid repeating $50s
|
||||
if had_db_last:
|
||||
output += ", $50"
|
||||
else:
|
||||
output += "\n" + xspacing + "db $50"
|
||||
byte_count += 1
|
||||
had_db_last = True
|
||||
elif command["type"] in [0x57, 0x58] and not had_text_end_byte_57_58:
|
||||
if had_db_last:
|
||||
output += ", $%.2x" % (command["type"])
|
||||
else:
|
||||
output += "\n" + xspacing + "db $%.2x" % (command["type"])
|
||||
byte_count += 1
|
||||
had_db_last = True
|
||||
elif command["type"] in [0x57, 0x58] and had_text_end_byte_57_58:
|
||||
pass # this is ok
|
||||
elif command["type"] == 0x50 and had_text_end_byte:
|
||||
pass # this is also ok
|
||||
elif command["type"] == 0x0b:
|
||||
if had_db_last:
|
||||
output += ", $0b"
|
||||
else:
|
||||
output += "\n" + xspacing + "db $0B"
|
||||
byte_count += 1
|
||||
had_db_last = True
|
||||
elif command["type"] == 0x11:
|
||||
if had_db_last:
|
||||
output += ", $11"
|
||||
else:
|
||||
output += "\n" + xspacing + "db $11"
|
||||
byte_count += 1
|
||||
had_db_last = True
|
||||
elif command["type"] == 0x6: # wait for keypress
|
||||
if had_db_last:
|
||||
output += ", $6"
|
||||
else:
|
||||
output += "\n" + xspacing + "db $6"
|
||||
byte_count += 1
|
||||
had_db_last = True
|
||||
else:
|
||||
logging.debug("ERROR in command: {0}".format(hex(command["type"])))
|
||||
had_db_last = False
|
||||
|
||||
# everything else is for $0s, really
|
||||
continue
|
||||
lines = commands[this_command]["lines"]
|
||||
|
||||
# reset this in case we have non-$0s later
|
||||
had_db_last = False
|
||||
|
||||
# add the ending byte to the last line- always seems $57
|
||||
# this should already be in there, but it's not because of a bug in the text parser
|
||||
lines[len(lines.keys())-1].append(commands[len(commands.keys())-1]["type"])
|
||||
|
||||
first = True # first byte
|
||||
for line_id in lines:
|
||||
line = lines[line_id]
|
||||
output += xspacing + "db "
|
||||
if first and needs_to_begin_with_0:
|
||||
output += "$0, "
|
||||
first = False
|
||||
byte_count += 1
|
||||
|
||||
quotes_open = False
|
||||
first_byte = True
|
||||
was_byte = False
|
||||
for byte in line:
|
||||
if byte == 0x50:
|
||||
had_text_end_byte = True # don't repeat it
|
||||
if byte in [0x58, 0x57]:
|
||||
had_text_end_byte_57_58 = True
|
||||
|
||||
if byte in chars.chars:
|
||||
if not quotes_open and not first_byte: # start text
|
||||
output += ", \""
|
||||
quotes_open = True
|
||||
first_byte = False
|
||||
if not quotes_open and first_byte: # start text
|
||||
output += "\""
|
||||
quotes_open = True
|
||||
output += chars.chars[byte]
|
||||
elif byte in constant_abbreviation_bytes:
|
||||
if quotes_open:
|
||||
output += "\""
|
||||
quotes_open = False
|
||||
if not first_byte:
|
||||
output += ", "
|
||||
output += constant_abbreviation_bytes[byte]
|
||||
else:
|
||||
if quotes_open:
|
||||
output += "\""
|
||||
quotes_open = False
|
||||
|
||||
# if you want the ending byte on the last line
|
||||
#if not (byte == 0x57 or byte == 0x50 or byte == 0x58):
|
||||
if not first_byte:
|
||||
output += ", "
|
||||
|
||||
output += "$" + hex(byte)[2:]
|
||||
was_byte = True
|
||||
|
||||
# add a comma unless it's the end of the line
|
||||
#if byte_count+1 != len(line):
|
||||
# output += ", "
|
||||
|
||||
first_byte = False
|
||||
byte_count += 1
|
||||
# close final quotes
|
||||
if quotes_open:
|
||||
output += "\""
|
||||
quotes_open = False
|
||||
|
||||
output += "\n"
|
||||
#include_newline = "\n"
|
||||
#if len(output)!=0 and output[-1] == "\n":
|
||||
# include_newline = ""
|
||||
#output += include_newline + "; " + hex(start_address) + " + " + str(byte_count) + " bytes = " + hex(start_address + byte_count)
|
||||
if len(output) > 0 and output[-1] == "\n":
|
||||
output = output[:-1]
|
||||
self.size = self.byte_count = byte_count
|
||||
return output
|
||||
|
|
@ -489,12 +489,16 @@ class Preprocessor(object):
|
|||
"""
|
||||
Add any labels not already in globals.asm.
|
||||
"""
|
||||
globes = open(os.path.join(self.config.path, 'globals.asm'), 'r+')
|
||||
lines = globes.readlines()
|
||||
for globe in self.globes:
|
||||
line = 'GLOBAL ' + globe + '\n'
|
||||
if line not in lines:
|
||||
globes.write(line)
|
||||
# TODO: pokered needs to be fixed
|
||||
try:
|
||||
globes = open(os.path.join(self.config.path, 'globals.asm'), 'r+')
|
||||
lines = globes.readlines()
|
||||
for globe in self.globes:
|
||||
line = 'GLOBAL ' + globe + '\n'
|
||||
if line not in lines:
|
||||
globes.write(line)
|
||||
except Exception as exception:
|
||||
pass # don't care if it's not there...
|
||||
|
||||
def read_line(self, l):
|
||||
"""
|
||||
|
|
|
|||
313
pokemontools/redmusicdisasm.py
Executable file
313
pokemontools/redmusicdisasm.py
Executable file
|
|
@ -0,0 +1,313 @@
|
|||
import configuration
|
||||
config = configuration.Config()
|
||||
rom = bytearray(open(config.rom_path, "r").read())
|
||||
|
||||
songs = [
|
||||
"PalletTown",
|
||||
"Pokecenter",
|
||||
"Gym",
|
||||
"Cities1",
|
||||
"Cities2",
|
||||
"Celadon",
|
||||
"Cinnabar",
|
||||
"Vermilion",
|
||||
"Lavender",
|
||||
"SSAnne",
|
||||
"MeetProfOak",
|
||||
"MeetRival",
|
||||
"MuseumGuy",
|
||||
"SafariZone",
|
||||
"PkmnHealed",
|
||||
"Routes1",
|
||||
"Routes2",
|
||||
"Routes3",
|
||||
"Routes4",
|
||||
"IndigoPlateau",
|
||||
"GymLeaderBattle",
|
||||
"TrainerBattle",
|
||||
"WildBattle",
|
||||
"FinalBattle",
|
||||
"DefeatedTrainer",
|
||||
"DefeatedWildMon",
|
||||
"DefeatedGymLeader",
|
||||
"TitleScreen",
|
||||
"Credits",
|
||||
"HallOfFame",
|
||||
"OaksLab",
|
||||
"JigglypuffSong",
|
||||
"BikeRiding",
|
||||
"Surfing",
|
||||
"GameCorner",
|
||||
"IntroBattle",
|
||||
"Dungeon1",
|
||||
"Dungeon2",
|
||||
"Dungeon3",
|
||||
"CinnabarMansion",
|
||||
"PokemonTower",
|
||||
"SilphCo",
|
||||
"MeetEvilTrainer",
|
||||
"MeetFemaleTrainer",
|
||||
"MeetMaleTrainer",
|
||||
"UnusedSong",
|
||||
]
|
||||
"""
|
||||
songs = [
|
||||
"YellowIntro",
|
||||
"SurfingPikachu",
|
||||
"MeetJessieJames",
|
||||
"YellowUnusedSong",
|
||||
]
|
||||
"""
|
||||
music_commands = {
|
||||
0xd0: ["notetype", {"type": "nibble"}, 2],
|
||||
0xe0: ["octave", 1],
|
||||
0xe8: ["togglecall", 1],
|
||||
0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3],
|
||||
0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3],
|
||||
0xec: ["duty", {"type": "byte"}, 2],
|
||||
0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3],
|
||||
0xee: ["unknownmusic0xee", {"type": "byte"}, 2],
|
||||
0xf0: ["stereopanning", {"type": "byte"}, 2],
|
||||
0xf8: ["executemusic", 1],
|
||||
0xfc: ["dutycycle", {"type": "byte"}, 2],
|
||||
0xfd: ["callchannel", {"type": "label"}, 3],
|
||||
0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4],
|
||||
0xff: ["endchannel", 1],
|
||||
}
|
||||
|
||||
param_lengths = {
|
||||
"nibble": 1,
|
||||
"byte": 1,
|
||||
"label": 2,
|
||||
}
|
||||
|
||||
music_notes = {
|
||||
0x0: "C_",
|
||||
0x1: "C#",
|
||||
0x2: "D_",
|
||||
0x3: "D#",
|
||||
0x4: "E_",
|
||||
0x5: "F_",
|
||||
0x6: "F#",
|
||||
0x7: "G_",
|
||||
0x8: "G#",
|
||||
0x9: "A_",
|
||||
0xa: "A#",
|
||||
0xb: "B_",
|
||||
}
|
||||
|
||||
def printnoisechannel(songname, songfile, startingaddress, bank, output):
|
||||
noise_commands = {
|
||||
0xfd: ["callchannel", {"type": "label"}, 3],
|
||||
0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4],
|
||||
0xff: ["endchannel", 1],
|
||||
}
|
||||
|
||||
noise_instruments = {
|
||||
0x01: "snare1",
|
||||
0x02: "snare2",
|
||||
0x03: "snare3",
|
||||
0x04: "snare4",
|
||||
0x05: "snare5",
|
||||
0x06: "triangle1",
|
||||
0x07: "triangle2",
|
||||
0x08: "snare6",
|
||||
0x09: "snare7",
|
||||
0x0a: "snare8",
|
||||
0x0b: "snare9",
|
||||
0x0c: "cymbal1",
|
||||
0x0d: "cymbal2",
|
||||
0x0e: "cymbal3",
|
||||
0x0f: "mutedsnare1",
|
||||
0x10: "triangle3",
|
||||
0x11: "mutedsnare2",
|
||||
0x12: "mutedsnare3",
|
||||
0x13: "mutedsnare4",
|
||||
}
|
||||
|
||||
# pass 1, build a list of all addresses pointed to by calls and loops
|
||||
address = startingaddress
|
||||
labels = []
|
||||
labelsleft= []
|
||||
while 1:
|
||||
byte = rom[address]
|
||||
if byte < 0xc0:
|
||||
command_length = 2
|
||||
elif byte < 0xe0:
|
||||
command_length = 1
|
||||
else:
|
||||
command_length = noise_commands[byte][-1]
|
||||
if byte == 0xfd or byte == 0xfe:
|
||||
label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2]
|
||||
labels.append(label)
|
||||
if label > address % 0x4000 + 0x4000: labelsleft.append(label)
|
||||
address += command_length
|
||||
if len(labelsleft) == 0 and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break
|
||||
while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000)
|
||||
# once the loop ends, start over from first address
|
||||
if rom[address] == 0xff: address += 1
|
||||
end = address
|
||||
address = startingaddress
|
||||
byte = rom[address]
|
||||
output += "Music_{}_Ch4: ; {:02x} ({:0x}:{:02x})\n".format(songname, address, bank, address % 0x4000 + 0x4000)
|
||||
# pass 2, print commands and labels for addresses that are in labels
|
||||
while address != end:
|
||||
if address % 0x4000 + 0x4000 in labels and address != startingaddress:
|
||||
output += "\nMusic_{}_branch_{:02x}:\n".format(songname, address)
|
||||
if byte < 0xc0:
|
||||
output += "\tdnote {}, {}".format(byte % 0x10 + 1, noise_instruments[rom[address + 1]])
|
||||
command_length = 2
|
||||
elif byte < 0xd0:
|
||||
output += "\trest {}".format(byte % 0x10 + 1)
|
||||
command_length = 1
|
||||
elif byte < 0xe0:
|
||||
output += "\tdspeed {}".format(byte % 0x10)
|
||||
command_length = 1
|
||||
else:
|
||||
command = noise_commands[byte]
|
||||
output += "\t{}".format(command[0])
|
||||
command_length = 1
|
||||
params = 1
|
||||
# print all params for current command
|
||||
while params != len(noise_commands[byte]) - 1:
|
||||
param_type = noise_commands[byte][params]["type"]
|
||||
address += command_length
|
||||
command_length = param_lengths[param_type]
|
||||
param = rom[address]
|
||||
if param_type == "byte":
|
||||
output += " {}".format(param)
|
||||
else:
|
||||
param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000)
|
||||
if param == startingaddress: output += " Music_{}_Ch4".format(songname)
|
||||
else: output += " Music_{}_branch_{:02x}".format(songname, param)
|
||||
params += 1
|
||||
if params != len(noise_commands[byte]) - 1: output += ","
|
||||
output += "\n"
|
||||
address += command_length
|
||||
byte = rom[address]
|
||||
output += "; {}".format(hex(address))
|
||||
songfile.write(output)
|
||||
|
||||
for i, songname in enumerate(songs):
|
||||
songfile = open("music/" + songname.lower() + ".asm", 'a')
|
||||
if songname == "PalletTown": header = 0x822e
|
||||
if songname == "GymLeaderBattle": header = 0x202be
|
||||
if songname == "TitleScreen": header = 0x7c249
|
||||
if songname == "YellowIntro": header = 0x7c294
|
||||
if songname == "SurfingPikachu": header = 0x801cb
|
||||
bank = header / 0x4000
|
||||
startingaddress = rom[header + 2] * 0x100 + rom[header + 1] - 0x4000 + (0x4000 * bank)
|
||||
curchannel = 1
|
||||
lastchannel = (rom[header] >> 6) + 1
|
||||
exception = False
|
||||
if songname == "MeetRival" or songname == "Cities1":
|
||||
startingaddress -= 7
|
||||
exception = True
|
||||
if songname == "UnusedSong":
|
||||
bank = 2
|
||||
startingaddress = 0xa913
|
||||
lastchannel = 2
|
||||
output = ''
|
||||
while 1:
|
||||
# pass 1, build a list of all addresses pointed to by calls and loops
|
||||
address = startingaddress
|
||||
labels = []
|
||||
labelsleft = []
|
||||
if songname == "MeetRival":
|
||||
if curchannel == 1:
|
||||
labels.append(0x719b)
|
||||
labelsleft.append(0x719b)
|
||||
labels.append(0x71a2)
|
||||
labelsleft.append(0x71a2)
|
||||
if curchannel == 2:
|
||||
labels.append(0x721d)
|
||||
labelsleft.append(0x721d)
|
||||
if curchannel == 3:
|
||||
labels.append(0x72b5)
|
||||
labelsleft.append(0x72b5)
|
||||
while 1:
|
||||
byte = rom[address]
|
||||
if byte < 0xd0:
|
||||
command_length = 1
|
||||
elif byte < 0xe0:
|
||||
command_length = 2
|
||||
elif byte < 0xe8:
|
||||
command_length = 1
|
||||
else:
|
||||
command_length = music_commands[byte][-1]
|
||||
if byte == 0xfd or byte == 0xfe:
|
||||
label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2]
|
||||
labels.append(label)
|
||||
if label > address % 0x4000 + 0x4000: labelsleft.append(label)
|
||||
address += command_length
|
||||
if len(labelsleft) == 0 and (exception == False or address > startingaddress + 7) and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break
|
||||
while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000)
|
||||
# once the loop breaks, start over from first address
|
||||
if rom[address] == 0xff: address += 1
|
||||
end = address
|
||||
if curchannel != lastchannel and songname != "UnusedSong": end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1))
|
||||
address = startingaddress
|
||||
byte = rom[address]
|
||||
# if song has an alternate start to channel 1, print a label and set startingaddress to true channel start
|
||||
if exception:
|
||||
output += "Music_{}_branch_{:02x}:\n".format(songname, address)
|
||||
startingaddress += 7
|
||||
# pass 2, print commands and labels for addresses that are in labels
|
||||
while address != end:
|
||||
if address == startingaddress:
|
||||
if exception: output += "\n"
|
||||
output += "Music_{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(songname, curchannel, address, bank, address % 0x4000 + 0x4000)
|
||||
elif address % 0x4000 + 0x4000 in labels:
|
||||
output += "\nMusic_{}_branch_{:02x}:\n".format(songname, address)
|
||||
if byte < 0xc0:
|
||||
output += "\tnote {}, {}".format(music_notes[byte >> 4], byte % 0x10 + 1)
|
||||
command_length = 1
|
||||
elif byte < 0xd0:
|
||||
output += "\trest {}".format(byte % 0x10 + 1)
|
||||
command_length = 1
|
||||
else:
|
||||
if byte < 0xe0:
|
||||
command = music_commands[0xd0]
|
||||
output += "\t{} {},".format(command[0], byte % 0x10)
|
||||
byte = 0xd0
|
||||
elif byte < 0xe8:
|
||||
command = music_commands[0xe0]
|
||||
output += "\t{} {}".format(command[0], 0xe8 - byte)
|
||||
byte = 0xe0
|
||||
else:
|
||||
command = music_commands[byte]
|
||||
output += "\t{}".format(command[0])
|
||||
command_length = 1
|
||||
params = 1
|
||||
# print all params for current command
|
||||
while params != len(music_commands[byte]) - 1:
|
||||
param_type = music_commands[byte][params]["type"]
|
||||
address += command_length
|
||||
command_length = param_lengths[param_type]
|
||||
param = rom[address]
|
||||
if param_type == "nibble":
|
||||
output += " {}, {}".format(param >> 4, param % 0x10)
|
||||
elif param_type == "byte":
|
||||
output += " {}".format(param)
|
||||
else:
|
||||
param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000)
|
||||
if param == startingaddress: output += " Music_{}_Ch{}".format(songname, curchannel)
|
||||
else: output += " Music_{}_branch_{:02x}".format(songname, param)
|
||||
params += 1
|
||||
if params != len(music_commands[byte]) - 1: output += ","
|
||||
output += "\n"
|
||||
address += command_length
|
||||
byte = rom[address]
|
||||
header += 3
|
||||
if curchannel == lastchannel:
|
||||
output += "; {}".format(hex(address))
|
||||
songfile.write(output)
|
||||
break
|
||||
curchannel += 1
|
||||
output += "\n\n"
|
||||
startingaddress = end
|
||||
exception = False
|
||||
if curchannel == 4:
|
||||
printnoisechannel(songname, songfile, startingaddress, bank, output)
|
||||
header += 3
|
||||
break
|
||||
126
pokemontools/redsfxdisasm.py
Executable file
126
pokemontools/redsfxdisasm.py
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
import configuration
|
||||
config = configuration.Config()
|
||||
rom = bytearray(open(config.rom_path, "r").read())
|
||||
|
||||
banks = {
|
||||
0x02: 0x60,
|
||||
0x08: 0x78,
|
||||
0x1f: 0x68,
|
||||
}
|
||||
|
||||
music_commands = {
|
||||
0xd0: ["notetype", {"type": "nibble"}, 2],
|
||||
0xe0: ["octave", 1],
|
||||
0xe8: ["togglecall", 1],
|
||||
0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3],
|
||||
0xec: ["duty", {"type": "byte"}, 2],
|
||||
0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3],
|
||||
0xf0: ["stereopanning", {"type": "byte"}, 2],
|
||||
0xf8: ["executemusic", 1],
|
||||
0xfc: ["dutycycle", {"type": "byte"}, 2],
|
||||
0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4],
|
||||
0xff: ["endchannel", 1],
|
||||
}
|
||||
|
||||
param_lengths = {
|
||||
"nibble": 1,
|
||||
"byte": 1,
|
||||
"label": 2,
|
||||
}
|
||||
|
||||
music_notes = {
|
||||
0x0: "C_",
|
||||
0x1: "C#",
|
||||
0x2: "D_",
|
||||
0x3: "D#",
|
||||
0x4: "E_",
|
||||
0x5: "F_",
|
||||
0x6: "F#",
|
||||
0x7: "G_",
|
||||
0x8: "G#",
|
||||
0x9: "A_",
|
||||
0xa: "A#",
|
||||
0xb: "B_",
|
||||
}
|
||||
|
||||
for bank in banks:
|
||||
header = bank * 0x4000 + 3
|
||||
for sfx in range(1,banks[bank]):
|
||||
sfxname = "SFX_{:02x}_{:02x}".format(bank, sfx)
|
||||
sfxfile = open("music/sfx/" + sfxname.lower() + ".asm", 'w')
|
||||
startingaddress = rom[header + 2] * 0x100 + rom[header + 1] + (0x4000 * (bank - 1))
|
||||
end = 0
|
||||
curchannel = 1
|
||||
lastchannel = (rom[header] >> 6) + 1
|
||||
channelnumber = rom[header] % 0x10
|
||||
output = ''
|
||||
while 1:
|
||||
address = startingaddress
|
||||
if curchannel != lastchannel:
|
||||
end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1))
|
||||
byte = rom[address]
|
||||
if byte == 0xf8 or (bank == 2 and sfx == 0x5e): executemusic = True
|
||||
else: executemusic = False
|
||||
output += "{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(sfxname, curchannel, address, bank, address % 0x4000 + 0x4000)
|
||||
while 1:
|
||||
if address == 0x2062a or address == 0x2063d or address == 0x20930:
|
||||
output += "\n{}_branch_{:02x}:\n".format(sfxname, address)
|
||||
if byte == 0x10 and not executemusic:
|
||||
output += "\tunknownsfx0x{:02x} {}".format(byte, rom[address + 1])
|
||||
command_length = 2
|
||||
elif byte < 0x30 and not executemusic:
|
||||
if channelnumber == 7:
|
||||
output += "\tunknownnoise0x20 {}, {}, {}".format(byte % 0x10, rom[address + 1], rom[address + 2])
|
||||
command_length = 3
|
||||
else:
|
||||
output += "\tunknownsfx0x20 {}, {}, {}, {}".format(byte % 0x10, rom[address + 1], rom[address + 2], rom[address + 3])
|
||||
command_length = 4
|
||||
elif byte < 0xc0:
|
||||
output += "\tnote {}, {}".format(music_notes[byte >> 4], byte % 0x10 + 1)
|
||||
command_length = 1
|
||||
elif byte < 0xd0:
|
||||
output += "\trest {}".format(byte % 0x10 + 1)
|
||||
command_length = 1
|
||||
else:
|
||||
if byte < 0xe0:
|
||||
command = music_commands[0xd0]
|
||||
output += "\t{} {},".format(command[0], byte % 0x10)
|
||||
byte = 0xd0
|
||||
elif byte < 0xe8:
|
||||
command = music_commands[0xe0]
|
||||
output += "\t{} {}".format(command[0], 0xe8 - byte)
|
||||
byte = 0xe0
|
||||
else:
|
||||
command = music_commands[byte]
|
||||
output += "\t{}".format(command[0])
|
||||
command_length = 1
|
||||
params = 1
|
||||
# print all params for current command
|
||||
while params != len(music_commands[byte]) - 1:
|
||||
param_type = music_commands[byte][params]["type"]
|
||||
address += command_length
|
||||
command_length = param_lengths[param_type]
|
||||
param = rom[address]
|
||||
if param_type == "nibble":
|
||||
output += " {}, {}".format(param >> 4, param % 0x10)
|
||||
elif param_type == "byte":
|
||||
output += " {}".format(param)
|
||||
else:
|
||||
param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000)
|
||||
if param == startingaddress: output += " {}_Ch{}".format(sfxname, curchannel)
|
||||
else: output += " {}_branch_{:02x}".format(sfxname, param)
|
||||
params += 1
|
||||
if params != len(music_commands[byte]) - 1: output += ","
|
||||
output += "\n"
|
||||
address += command_length
|
||||
if byte == 0xff or address == end: break
|
||||
byte = rom[address]
|
||||
header += 3
|
||||
channelnumber = rom[header]
|
||||
if curchannel == lastchannel:
|
||||
output += "; {}".format(hex(address))
|
||||
sfxfile.write(output)
|
||||
break
|
||||
output += "\n\n"
|
||||
startingaddress = address
|
||||
curchannel += 1
|
||||
36
pokemontools/redsfxheaders.py
Executable file
36
pokemontools/redsfxheaders.py
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
import configuration
|
||||
config = configuration.Config()
|
||||
rom = bytearray(open(config.rom_path, "r").read())
|
||||
|
||||
headerlist = (
|
||||
["sfxheaders02.asm", 0x8003, 0x822e],
|
||||
["sfxheaders08.asm", 0x20003, 0x202be],
|
||||
["sfxheaders1f.asm", 0x7c003, 0x7c249],
|
||||
)
|
||||
|
||||
def printsfxheaders(filename, address, end):
|
||||
file = open(filename, 'w')
|
||||
bank = address / 0x4000
|
||||
byte = rom[address]
|
||||
sfx = 1
|
||||
channel = 1
|
||||
file.write("SFX_Headers_{:02x}:\n".format(bank))
|
||||
file.write("\tdb $ff, $ff, $ff ; padding\n")
|
||||
while address != end:
|
||||
left = (byte >> 6) + 1
|
||||
file.write("\nSFX_{:02x}_{:02x}: ; {:02x} ({:0x}:{:02x})\n".format(bank, sfx, address, bank, address % 0x4000 + 0x4000))
|
||||
while left != 0:
|
||||
pointer = rom[address + 2] * 0x100 + rom[address + 1]
|
||||
if byte >> 4 != 0: file.write(" db ( ${:0x}0 | CH{:0x} )\n".format(byte >> 4, byte % 0x10))
|
||||
else: file.write("\tdb CH{:0x}\n".format(byte))
|
||||
file.write("\tdw SFX_{:02x}_{:02x}_Ch{}\n".format(bank, sfx, channel))
|
||||
address += 3
|
||||
byte = rom[address]
|
||||
channel += 1
|
||||
left -= 1
|
||||
channel = 1
|
||||
sfx += 1
|
||||
file.write("\n; {}".format(hex(address)))
|
||||
|
||||
for header in headerlist:
|
||||
printsfxheaders(header[0], header[1], header[2])
|
||||
|
|
@ -104,5 +104,30 @@ def remove_parentheticals_from_trainer_group_names():
|
|||
i += 1
|
||||
return trainer_group_names
|
||||
|
||||
def pretty_print_trainer_id_constants(trainer_group_table, trainers):
|
||||
"""
|
||||
Prints out some constants for trainer ids, for "constants.asm".
|
||||
|
||||
make_trainer_group_name_trainer_ids must be called prior to this.
|
||||
"""
|
||||
assert trainer_group_table != None, "must make trainer_group_table first"
|
||||
assert trainers.trainer_group_names != None, "must have trainers.trainer_group_names available"
|
||||
assert "trainer_names" in trainers.trainer_group_names[1].keys(), "trainer_names must be set in trainers.trainer_group_names"
|
||||
|
||||
output = ""
|
||||
for (key, value) in trainers.trainer_group_names.items():
|
||||
if "uses_numeric_trainer_ids" in trainers.trainer_group_names[key].keys():
|
||||
continue
|
||||
id = key
|
||||
group = value
|
||||
header = group["header"]
|
||||
name = group["name"]
|
||||
trainer_names = group["trainer_names"]
|
||||
output += "; " + name + "\n"
|
||||
for (id, name) in enumerate(trainer_names):
|
||||
output += name.upper() + " EQU $%.2x"%(id+1) + "\n"
|
||||
output += "\n"
|
||||
return output
|
||||
|
||||
# remove [Blue] from each trainer group name
|
||||
remove_parentheticals_from_trainer_group_names()
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -24,7 +24,7 @@ requires = [
|
|||
|
||||
setup(
|
||||
name="pokemontools",
|
||||
version="1.3.0",
|
||||
version="1.4.1",
|
||||
description="Tools for compiling and disassembling Pokémon Red and Pokémon Crystal.",
|
||||
long_description=open("README.md", "r").read(),
|
||||
license="BSD",
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ from pokemontools.crystal import (
|
|||
process_incbins,
|
||||
get_labels_between,
|
||||
generate_diff_insert,
|
||||
find_labels_without_addresses,
|
||||
rom_text_at,
|
||||
get_label_for,
|
||||
split_incbin_line_into_three,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from pokemontools.labels import (
|
|||
line_has_comment_address,
|
||||
line_has_label,
|
||||
get_label_from_line,
|
||||
find_labels_without_addresses,
|
||||
)
|
||||
|
||||
from pokemontools.helpers import (
|
||||
|
|
@ -84,7 +85,6 @@ from pokemontools.crystal import (
|
|||
process_incbins,
|
||||
get_labels_between,
|
||||
generate_diff_insert,
|
||||
find_labels_without_addresses,
|
||||
rom_text_at,
|
||||
get_label_for,
|
||||
split_incbin_line_into_three,
|
||||
|
|
@ -389,10 +389,10 @@ class TestAsmList(unittest.TestCase):
|
|||
def test_find_labels_without_addresses(self):
|
||||
global asm
|
||||
asm = ["hello_world: ; 0x1", "hello_world2: ;"]
|
||||
labels = find_labels_without_addresses()
|
||||
labels = find_labels_without_addresses(asm)
|
||||
self.failUnless(labels[0]["label"] == "hello_world2")
|
||||
asm = ["hello world: ;1", "hello_world: ;2"]
|
||||
labels = find_labels_without_addresses()
|
||||
labels = find_labels_without_addresses(asm)
|
||||
self.failUnless(len(labels) == 0)
|
||||
asm = None
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user