mirror of
https://github.com/pret/pokered.git
synced 2024-10-23 15:08:23 +00:00
58a9aacc0b
This feature was removed misunderstanding its actual use: if filename passed to load_asm() is in defaults the correct current main asm file is loaded. This saves us from knowing which actually is the current name of the "main" asm file, because the correct one is chosen automatically (unless, of course, the passed filename is not in the defaults list.
493 lines
16 KiB
Python
493 lines
16 KiB
Python
#author: Bryan Bishop <kanzure@gmail.com>
|
|
#date: 2012-01-03
|
|
#purpose: map which addresses are left
|
|
#note: use python2.7 because of subprocess
|
|
import sys, os
|
|
from copy import copy, deepcopy
|
|
import subprocess
|
|
import json
|
|
from extract_maps import rom, assert_rom, load_rom, calculate_pointer, load_map_pointers, read_all_map_headers, map_headers
|
|
from pokered_dir import pokered_dir
|
|
|
|
try:
|
|
from pretty_map_headers import map_header_pretty_printer, map_name_cleaner
|
|
except Exception:
|
|
pass
|
|
|
|
#store each line of source code here
|
|
asm = None
|
|
|
|
#store each incbin line separately
|
|
incbin_lines = []
|
|
|
|
#storage for processed incbin lines
|
|
processed_incbins = {}
|
|
|
|
def offset_to_pointer(offset):
|
|
if type(offset) == str: offset = int(offset, base)
|
|
return int(offset) % 0x4000 + 0x4000
|
|
|
|
def load_asm(filename=os.path.join(pokered_dir, "main.asm")):
|
|
"""loads the asm source code into memory
|
|
this also detects if the revision of the repository
|
|
is using main.asm, common.asm or pokered.asm, which is
|
|
useful when generating images in romvisualizer.py"""
|
|
global asm
|
|
# chronological order is important
|
|
defaults = [os.path.join(pokered_dir, f) for f in ["main.asm", "common.asm", "pokered.asm"]]
|
|
if filename in defaults:
|
|
if not load_asm_if_one_exists_in(defaults):
|
|
raise Exception("This shouldn't happen")
|
|
elif os.path.exists(filename):
|
|
asm = get_all_lines_from_file(filename)
|
|
if asm is None:
|
|
raise Exception("file doesn't exists (did you mean one among: {0}?)".format(", ".join(defaults)))
|
|
return asm
|
|
|
|
def load_asm_if_one_exists_in(*args):
|
|
global asm
|
|
for f in args:
|
|
if os.path.exists(f):
|
|
asm = get_all_lines_from_file(f)
|
|
return True
|
|
return False
|
|
|
|
def get_all_lines_from_file(filename):
|
|
try:
|
|
return open(filename, "r").read().split("\n")
|
|
except IOError as e:
|
|
raise(e)
|
|
|
|
def isolate_incbins():
|
|
"find each incbin line"
|
|
global incbin_lines
|
|
incbin_lines = []
|
|
for line in asm:
|
|
if line == "": continue
|
|
if line.count(" ") == len(line): continue
|
|
|
|
#clean up whitespace at beginning of line
|
|
while line[0] == " ":
|
|
line = line[1:]
|
|
|
|
if line[0:6] == "INCBIN" and "baserom.gbc" in line:
|
|
incbin_lines.append(line)
|
|
return incbin_lines
|
|
|
|
def process_incbins():
|
|
"parse incbin lines into memory"
|
|
global incbins
|
|
incbins = {} #reset
|
|
for incbin in incbin_lines:
|
|
processed_incbin = {}
|
|
|
|
line_number = asm.index(incbin)
|
|
|
|
partial_start = incbin[21:]
|
|
start = partial_start.split(",")[0].replace("$", "0x")
|
|
start = eval(start)
|
|
start_hex = hex(start).replace("0x", "$")
|
|
|
|
partial_interval = incbin[21:].split(",")[1]
|
|
partial_interval = partial_interval.replace(";", "#")
|
|
partial_interval = partial_interval.replace("$", "0x").replace("0xx", "0x")
|
|
interval = eval(partial_interval)
|
|
interval_hex = hex(interval).replace("0x", "$").replace("x", "")
|
|
|
|
end = start + interval
|
|
end_hex = hex(end).replace("0x", "$")
|
|
|
|
processed_incbin = {
|
|
"line_number": line_number,
|
|
"line": incbin,
|
|
"start": start,
|
|
"interval": interval,
|
|
"end": end,
|
|
}
|
|
|
|
#don't add this incbin if the interval is 0
|
|
if interval != 0:
|
|
processed_incbins[line_number] = processed_incbin
|
|
|
|
def find_incbin_to_replace_for(address):
|
|
"""returns a line number for which incbin to edit
|
|
if you were to insert bytes into main.asm"""
|
|
if type(address) == str: address = int(address, 16)
|
|
|
|
for incbin_key in processed_incbins.keys():
|
|
incbin = processed_incbins[incbin_key]
|
|
|
|
start = incbin["start"]
|
|
end = incbin["end"]
|
|
|
|
#print "start is: " + str(start)
|
|
#print "end is: " + str(end)
|
|
#print "address is: " + str(type(address))
|
|
#print "checking.... " + hex(start) + " <= " + hex(address) + " <= " + hex(end)
|
|
|
|
if start <= address <= end:
|
|
return incbin_key
|
|
return None
|
|
|
|
def split_incbin_line_into_three(line, start_address, byte_count):
|
|
"""
|
|
splits an incbin line into three pieces.
|
|
you can replace the middle one with the new content of length bytecount
|
|
|
|
start_address: where you want to start inserting bytes
|
|
byte_count: how many bytes you will be inserting
|
|
"""
|
|
if type(start_address) == str: start_address = int(start_address, 16)
|
|
|
|
original_incbin = processed_incbins[line]
|
|
start = original_incbin["start"]
|
|
end = original_incbin["end"]
|
|
|
|
#start, end1, end2 (to be printed as start, end1 - end2)
|
|
if start_address - start > 0:
|
|
first = (start, start_address, start)
|
|
else:
|
|
first = (None) #skip this one because we're not including anything
|
|
|
|
#this is the one you will replace with whatever content
|
|
second = (start_address, byte_count)
|
|
|
|
third = (start_address + byte_count, end - (start_address + byte_count))
|
|
|
|
output = ""
|
|
|
|
if first:
|
|
output += "INCBIN \"baserom.gbc\",$" + hex(first[0])[2:] + ",$" + hex(first[1])[2:] + " - $" + hex(first[2])[2:] + "\n"
|
|
output += "INCBIN \"baserom.gbc\",$" + hex(second[0])[2:] + "," + str(byte_count) + "\n"
|
|
output += "INCBIN \"baserom.gbc\",$" + hex(third[0])[2:] + ",$" + hex(third[1])[2:] #no newline
|
|
return output
|
|
|
|
def generate_diff_insert(line_number, newline):
|
|
original = "\n".join(line for line in asm)
|
|
newfile = deepcopy(asm)
|
|
newfile[line_number] = newline #possibly inserting multiple lines
|
|
newfile = "\n".join(line for line in newfile)
|
|
|
|
original_filename = "ejroqjfoad.temp"
|
|
newfile_filename = "fjiqefo.temp"
|
|
|
|
original_fh = open(original_filename, "w")
|
|
original_fh.write(original)
|
|
original_fh.close()
|
|
|
|
newfile_fh = open(newfile_filename, "w")
|
|
newfile_fh.write(newfile)
|
|
newfile_fh.close()
|
|
|
|
try:
|
|
diffcontent = subprocess.check_output(
|
|
"diff -u {0} {1}".format(os.path.join(pokered_dir, "main.asm"), newfile_filename),
|
|
shell=True)
|
|
except AttributeError, exc:
|
|
raise exc
|
|
except Exception, exc:
|
|
diffcontent = exc.output
|
|
|
|
os.system("rm " + original_filename)
|
|
os.system("rm " + newfile_filename)
|
|
|
|
return diffcontent
|
|
|
|
def insert_map_header_asm(map_id):
|
|
map = map_headers[map_id]
|
|
line_number = find_incbin_to_replace_for(map["address"])
|
|
if line_number == None: # or map_name_cleaner(map["name"], 0) in "\n".join(line for line in asm):
|
|
print "i think map id=" + str(map_id) + " has previously been added."
|
|
return #this map has already been added i bet
|
|
newlines = split_incbin_line_into_three(line_number, map["address"], 12 + (11 * len(map["connections"])))
|
|
|
|
map_header_asm = map_header_pretty_printer(map_headers[map_id])
|
|
|
|
newlines = newlines.split("\n")
|
|
if len(newlines) == 2: index = 0
|
|
elif len(newlines) == 3:
|
|
index = 1
|
|
newlines[0] += "\n" #spacing is a nice thing to have
|
|
newlines[index] = map_header_asm
|
|
newlines = "\n".join(line for line in newlines)
|
|
|
|
diff = generate_diff_insert(line_number, newlines)
|
|
|
|
print diff
|
|
print "... Applying diff."
|
|
|
|
#write the diff to a file
|
|
fh = open("temp.patch", "w")
|
|
fh.write(diff)
|
|
fh.close()
|
|
|
|
#apply the patch
|
|
os.system("patch {0} temp.patch".format(os.path.join(pokered_dir, "main.asm")))
|
|
|
|
#remove the patch
|
|
os.system("rm temp.patch")
|
|
|
|
def wrapper_insert_map_header_asm(map_id):
|
|
"reload the asm because it has changed (probably)"
|
|
load_asm()
|
|
isolate_incbins()
|
|
process_incbins()
|
|
insert_map_header_asm(map_id)
|
|
|
|
def dump_all_remaining_maps():
|
|
for map_id in map_headers:
|
|
print "Inserting map id=" + str(map_id)
|
|
wrapper_insert_map_header_asm(map_id)
|
|
|
|
def reset_incbins():
|
|
"reset asm before inserting another diff"
|
|
asm = None
|
|
incbin_lines = []
|
|
processed_incbins = {}
|
|
load_asm()
|
|
isolate_incbins()
|
|
process_incbins()
|
|
|
|
def apply_diff(diff, try_fixing=True, do_compile=True):
|
|
print "... Applying diff."
|
|
|
|
#write the diff to a file
|
|
fh = open("temp.patch", "w")
|
|
fh.write(diff)
|
|
fh.close()
|
|
|
|
#apply the patch
|
|
os.system("cp {0} {1}".format(
|
|
os.path.join(pokered_dir, "main.asm"),
|
|
os.path.join(pokered_dir, "main1.asm")))
|
|
os.system("patch {0} {1}".format(
|
|
os.path.join(pokered_dir, "main.asm"),
|
|
"temp.patch"))
|
|
|
|
#remove the patch
|
|
os.system("rm temp.patch")
|
|
|
|
#confirm it's working
|
|
if do_compile:
|
|
try:
|
|
subprocess.check_call("cd {0}; make clean; LC_CTYPE=C make".format(pokered_dir), shell=True)
|
|
return True
|
|
except Exception, exc:
|
|
if try_fixing:
|
|
os.system("mv {0} {1}".format(
|
|
os.path.join(pokered_dir, "main1.asm"),
|
|
os.path.join(pokered_dir, "main.asm")))
|
|
return False
|
|
|
|
def index(seq, f):
|
|
"""return the index of the first item in seq
|
|
where f(item) == True."""
|
|
return next((i for i in xrange(len(seq)) if f(seq[i])), None)
|
|
|
|
def is_probably_pointer(input):
|
|
try:
|
|
blah = int(input, 16)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
label_errors = ""
|
|
def get_labels_between(start_line_id, end_line_id, bank_id):
|
|
labels = []
|
|
#label = {
|
|
# "line_number": 15,
|
|
# "bank_id": 32,
|
|
# "label": "PalletTownText1",
|
|
# "local_pointer": "$5315",
|
|
# "address": 0x75315,
|
|
#}
|
|
global label_errors
|
|
errors = ""
|
|
current_line_offset = 0
|
|
|
|
sublines = asm[start_line_id : end_line_id + 1]
|
|
for line in sublines:
|
|
label = {}
|
|
line_id = start_line_id + current_line_offset
|
|
address = None
|
|
local_pointer = None
|
|
|
|
if ": ; 0x" in line:
|
|
temp = line.split(": ; 0x")[1]
|
|
if not " " in temp:
|
|
address = int("0x" + temp, 16)
|
|
else:
|
|
temp2 = temp.split(" ")[0]
|
|
address = int("0x" + temp2, 16)
|
|
elif ": ; " in line:
|
|
partial = line.split(": ; ")[1]
|
|
if ": ; $" in line:
|
|
temp = line.split(": ; $")[1]
|
|
if " " in temp:
|
|
temp = temp.split(" ")[0]
|
|
local_pointer = "$" + temp
|
|
elif " " in partial:
|
|
if " to " in partial:
|
|
temp = partial.split(" to ")[0]
|
|
if "0x" in temp:
|
|
address = int(temp, 16)
|
|
elif len(temp) == 4:
|
|
local_pointer = "$" + temp
|
|
else:
|
|
errors += "found \" to \" in partial on line " + str(line_id) + ", but don't know what to do (debug14)" + "\n"
|
|
errors += "line is: " + line + "\n"
|
|
continue
|
|
elif len(partial[4]) == 4 or partial[4] == " ": #then it's probably a local pointer
|
|
temp = partial[0:4]
|
|
local_pointer = "$" + temp
|
|
else:
|
|
errors += "found \": ; \" and another \" \" in line " + str(line_id) + ", but don't know what to do (debug15)" + "\n"
|
|
errors += "line is: " + line + "\n"
|
|
continue
|
|
else:
|
|
if len(partial) > 3 and partial[2] == ":": #14:3BAC
|
|
temp = partial[2].split(":")[1]
|
|
if len(temp) == 3 or len(temp) == 4:
|
|
local_pointer = "$" + temp
|
|
else:
|
|
temp = temp.split(" ")[0]
|
|
local_pointer = "$" + temp
|
|
elif len(partial) == 4 or (len(partial) == 3 and is_probably_pointer(partial)):
|
|
local_pointer = "$" + partial
|
|
else:
|
|
errors += "found \": ; \" in line " + str(line_id) + ", but don't know what to do (debug16)" + "\n"
|
|
errors += "line is: " + line + "\n"
|
|
continue
|
|
else:
|
|
#this line doesn't have a label
|
|
continue
|
|
|
|
if local_pointer != None and not is_probably_pointer(local_pointer.replace("0x", "").replace("$", "")):
|
|
continue
|
|
|
|
line_label = line.split(": ;")[0]
|
|
|
|
if address == None and local_pointer != None:
|
|
temp = int(local_pointer.replace("$", "0x"), 16)
|
|
if temp < 0x4000 or bank_id == 0:
|
|
address = temp
|
|
else:
|
|
address = calculate_pointer(int(local_pointer.replace("$", "0x"), 16), bank_id)
|
|
elif local_pointer == None and address != None:
|
|
if address < 0x4000:
|
|
local_pointer = hex(address).replace("0x", "$")
|
|
else:
|
|
local_pointer = hex((address % 0x4000) + 0x4000).replace("0x", "$")
|
|
|
|
print line_label + " is at " + hex(address)
|
|
|
|
label = {
|
|
"line_number": line_id,
|
|
"bank_id": bank_id,
|
|
"label": line_label,
|
|
"local_pointer": local_pointer,
|
|
"address": address
|
|
}
|
|
labels.append(label)
|
|
|
|
current_line_offset += 1
|
|
label_errors += errors
|
|
return labels
|
|
|
|
def scan_for_predefined_labels():
|
|
"""looks through the asm file for labels at specific addresses,
|
|
this relies on the label having its address after. ex:
|
|
|
|
ViridianCity_h: ; 0x18357 to 0x18384 (45 bytes) (bank=6) (id=1)
|
|
PalletTownText1: ; 4F96 0x18f96
|
|
ViridianCityText1: ; 0x19102
|
|
|
|
It would be more productive to use rgbasm to spit out all label
|
|
addresses, but faster to write this script. rgbasm would be able
|
|
to grab all label addresses better than this script..
|
|
"""
|
|
bank_intervals = {}
|
|
all_labels = []
|
|
|
|
#figure out line numbers for each bank
|
|
for bank_id in range(0x2d):
|
|
abbreviation = ("%.x" % (bank_id)).upper()
|
|
abbreviation_next = ("%.x" % (bank_id+1)).upper()
|
|
if bank_id == 0:
|
|
abbreviation = "0"
|
|
abbreviation_next = "1"
|
|
|
|
start_line_id = index(asm, lambda line: "\"bank" + abbreviation + "\"" in line)
|
|
|
|
if bank_id != 0x2c:
|
|
end_line_id = index(asm, lambda line: "\"bank" + abbreviation_next + "\"" in line)
|
|
else:
|
|
end_line_id = len(asm) - 1
|
|
|
|
print "bank" + abbreviation + " starts at " + str(start_line_id) + " to " + str(end_line_id)
|
|
|
|
bank_intervals[bank_id] = {
|
|
"start": start_line_id,
|
|
"end": end_line_id,
|
|
}
|
|
|
|
for bank_id in bank_intervals.keys():
|
|
bank_data = bank_intervals[bank_id]
|
|
|
|
start_line_id = bank_data["start"]
|
|
end_line_id = bank_data["end"]
|
|
|
|
labels = get_labels_between(start_line_id, end_line_id, bank_id)
|
|
#bank_intervals[bank_id]["labels"] = labels
|
|
all_labels.extend(labels)
|
|
|
|
write_all_labels(all_labels)
|
|
return all_labels
|
|
|
|
def write_all_labels(all_labels):
|
|
fh = open("labels.json", "w")
|
|
fh.write(json.dumps(all_labels))
|
|
fh.close()
|
|
|
|
def analyze_intervals():
|
|
"""find the largest baserom.gbc intervals"""
|
|
global asm
|
|
global processed_incbins
|
|
if asm == None:
|
|
load_asm()
|
|
if processed_incbins == {}:
|
|
isolate_incbins()
|
|
process_incbins()
|
|
|
|
results = []
|
|
ordered_keys = sorted(processed_incbins, key=lambda entry: processed_incbins[entry]["interval"])
|
|
ordered_keys.reverse()
|
|
for key in ordered_keys:
|
|
results.append(processed_incbins[key])
|
|
|
|
return results
|
|
|
|
if __name__ == "__main__":
|
|
#load map headers
|
|
load_rom()
|
|
load_map_pointers()
|
|
read_all_map_headers()
|
|
|
|
#load incbins (mandatory)
|
|
load_asm()
|
|
#isolate_incbins()
|
|
#process_incbins()
|
|
#print processed_incbins
|
|
|
|
#line_number = find_incbin_to_replace_for(0x492c3)
|
|
#newlines = split_incbin_line_into_three(line_number, 0x492c3, 12)
|
|
#diff = generate_diff_insert(line_number, newlines)
|
|
#print diff
|
|
|
|
#insert_map_header_asm(86)
|
|
#dump_all_remaining_maps()
|
|
|
|
scan_for_predefined_labels()
|
|
print "Errors:"
|
|
print label_errors
|
|
|