1
0
mirror of https://github.com/zeldaret/oot.git synced 2024-09-21 21:04:43 +00:00
oot/tools/migrate-rodata.py
Lucas Shaw 045a92d7c3
Big actor cleanup (fixed) (#69)
* Started doing cleanup

* did more work

* did more migration

* migrated more rodata and worked on some structs

* did more work

* Removal of ROOM field from initvars, some rodata migration, some string decompilation

* General update

* Decompiled vt strings

* Tool work

* Tool improvements

* 270 overlay rodata files remaining

* better float handling

* floats

* Many more floats

* migrated boss_mo

* assorted fixes

* Migrated 10

* tool improvements

* migrated 10

* 10 more

* 1 more

* did a few more

* fixes

* 10 more

* more floats

* Did some more, updated migrate-rodata.py to 'modify' the C file after processing in order to make to compiler process it as if it was changed.

* removed changes made to script by accident

* migrated largest rodata - ovl_fishing

* Did some more

* 114 remaining

* 99 left !

* almost done migrating rodata

* did some more, done for tonight

* almost done, tried add support to the script for z_player

* All possible rodata migrated in actor overlays

* update

* removed static from all overlays, ran format.sh

* Removed unknown actor structs

* converted a few floats

* Added new lines to header files that were missing them. Removed unused asm files

* Removed unused asm files

* Formatting newlines

Further formatting

spacing

.float spacing

More space formatting

More spacing formatting

Removing .balign 4 after floats

Co-authored-by: Ethan Roseman <ethteck@gmail.com>
2020-04-14 14:16:34 -04:00

304 lines
11 KiB
Python

"""
Given a code file name (excluding the file extension, e.g. z_view) this script
will attempt to build rodata and late_rodata sections for all of its functions.
This supports overlays to an extent but before running it for any check to
ensure the rodata file is formatted correctly (see files in code for examples
of properly formatted rodata sections)
This supports automatically commenting or deleting the rodata file from the
spec and automatic deletion of the rodata file itself (only use if you are sure
it will build afterwards)
Place this in the root of your project (same folder as the makefile) and run.
Not sure if this works well with .incbin if that can even be found in rodata
"""
import re
import os
from os import system, listdir, remove, walk
from os.path import exists, isdir, sep
# FUNCTIONS -----------------------------------------------------------------
"""
Extracts rodata symbols from asm
"""
def asm_syms(asm):
split = re.split("jtbl_|D_",asm)
return [s.partition(")")[0] for s in split[1:len(split)]]
"""
Extracts rodata symbols from rodata
"""
def rodata_syms(rodata):
split = re.split("glabel jtbl_|glabel D_",rodata)
return [s.partition("\n")[0] for s in split[1:len(split)]]
"""
Extracts the symbol from a single rodata block
"""
def rodata_sym(rodata_block):
return (re.split("glabel jtbl_|glabel D_",rodata_block)[1]).split("\n")[0]
"""
Splits rodata into blocks
"""
def rodata_blocks(rodata):
split = rodata.split("glabel")
return ["glabel" + b for b in split[1:len(split)]]
"""
Gets the block size
"""
def rodata_block_size(rodata):
split = rodata.split(" .")
elements = split[1:len(split)]
size = 0
for e in elements:
element_type = e.split(" ")[0]
if element_type == "double":
size += 8
if element_type == "float":
size += 4
if element_type == "word":
size += 4
if element_type == "incbin":
size += int(e.rpartition(", ")[2].split("\n")[0],16)
if element_type == "ascii":
size += len(e.split("\"")[1].split("\"")[0])
if element_type == "asciz":
size += len(e.split("\"")[1].split("\"")[0])
if element_type == "balign":
size += size % int(e.split(" ")[1])
return size
"""
Gets the text size
"""
def text_size(asm):
instructions = [l for l in asm.split("\n") if l.startswith("/* ")]
return 4*(len(instructions)-1)
"""
Gets the rodata-to-text
"""
def late_rodata_ratio(asm,late_rodata_blocks):
total_rodata_size = 0
for b in late_rodata_blocks:
total_rodata_size += rodata_block_size(b)
return total_rodata_size/text_size(asm)
"""
Accepts a single block of rodata and extracts the type
"""
def rodata_type(rodata_block):
first_split = re.split("\s+\.", rodata_block)
return first_split[1].split(" ")[0]
"""
Checks if a block should go in .rdata or .late_rodata
"""
def is_rodata(r):
if rodata_type(r)=="asciz" or rodata_type=="ascii":
return True
if rodata_type(r)=="incbin":
return True
return False
"""
For given asm and rodata files, build a rodata section for the asm file
"""
def build_rodata(asm, rodata):
contained_syms = [s for s in asm_syms(asm) if s in rodata_syms(rodata)]
contained_blocks = [b for b in rodata_blocks(rodata) if rodata_sym(b) in contained_syms]
# TODO include arrays in rodata_list
rodata_list = [r for r in contained_blocks if is_rodata(r)]
return_str = ""
if (len(rodata_list)!=0):
return_str += ".rdata\n"
for r in rodata_list:
return_str += r
late_rodata_list = [r for r in contained_blocks if r not in rodata_list]
if (len(late_rodata_list)!=0):
if late_rodata_ratio(asm,late_rodata_list) > (1/3):
top_sym = rodata_sym(late_rodata_list[0])
if top_sym.endswith("0") or top_sym.endswith("8"):
return_str += ".late_rodata\n.late_rodata_alignment 8\n"
elif top_sym.endswith("4") or top_sym.endswith("C"):
return_str += ".late_rodata\n.late_rodata_alignment 4\n"
else:
return_str += ".late_rodata\n"
for r in late_rodata_list:
return_str += r
return None if return_str=="" else return_str + ("" if return_str.endswith("\n\n") else "\n") + ".text\n"
"""
Gets all file paths contained in a given folder, does not enter subfolders
"""
def get_file_paths(folder_path):
return next(walk(folder_path),(None,None,[]))[2]
"""
Given a path, reads the asm .s file into a single string
"""
def file_read(path):
with open(path, 'r', encoding="utf-8") as infile:
return infile.read()
"""
Writes the rodata section to the start of the file specified by path
"""
def rodata_write(path, section):
with open(path, 'r+', encoding="utf-8", newline="\n") as outfile:
original = outfile.read()
outfile.seek(0,0)
outfile.write(str(section) + original)
"""
Comments out the line in spec associated with the given filenames
"""
def modify_spec(filenames, identifier, delete):
lines = ""
with open("spec", 'r+', encoding="utf-8", newline="\n") as spec:
lines = spec.read().split("\n")
changed = False
files = filenames.split(",")
for filename in files:
if identifier == "code" and " include \"build/data/" + filename + ".rodata.o\"" in lines:
e = lines.index(" include \"build/data/" + filename + ".rodata.o\"")
if delete:
del lines[e]
else:
lines[e] = " //include \"build/data/" + filename + ".rodata.o\""
changed = True
elif " include \"build/data/overlays/" + identifier + "/z_" + filename.lower() + ".rodata.o\"" in lines:
e = lines.index(" include \"build/data/overlays/" + identifier + "/z_" + filename.lower() + ".rodata.o\"")
if delete:
del lines[e]
else:
lines[e] = " //include \"build/data/overlays/" + identifier + "/z_" + filename.lower() + ".rodata.o\""
changed = True
if changed:
modified = "\n".join(lines)
with open("spec", 'w', encoding="utf-8", newline="\n") as spec:
#spec.seek(0,0)
spec.write(modified)
"""
Processes each individual file
asm\non_matchings\overlays\<identifier>
data\overlays\<identifier>
asm\non_matchings\code\
data\code\
"""
def process_file(filename, identifier, delete_rodata):
folder_path = "asm" + sep + "non_matchings" + sep + ("code" + sep if identifier=="code" else "overlays" + sep + identifier.lower() + sep + "ovl_") + filename + sep
rodata_path = "data" + sep + (sep if identifier=="code" else "overlays" + sep + identifier.lower() + sep + "z_") + filename.lower() + ".rodata.s"
if filename == "player":
folder_path = "asm" + sep + "non_matchings" + sep + "overlays" + sep + "actors" + sep + "ovl_player_actor" + sep
print("ASM at: " + folder_path)
print("Data at: " + rodata_path)
if not exists(folder_path):
print("Aborting: ASM does not exist")
return
if not exists(rodata_path):
print("Aborting: Data does not exist")
return
files = [folder_path + f for f in get_file_paths(folder_path)]
for asm_file in files:
asm = file_read(asm_file)
print("Found asm file " + asm_file)
if ".rdata" in asm:
print("Skipping: it already has a rodata section")
continue
print("Processing asm file " + asm_file)
section = build_rodata(asm, file_read(rodata_path))
if section is not None:
print("Writing asm file " + asm_file)
rodata_write(asm_file, section)
else: print("Skipping: no rodata to write")
print("Built rodata sections for " + identifier + " file " + filename)
if delete_rodata:
remove(rodata_path)
print("Deleted rodata at " + rodata_path)
"""
Allows files to be provided as comma-separated filenames for batch migration
"""
def process_files(filenames, identifier, spechandle, delete_rodata):
if "," in filenames:
files = filenames.split(",")
for f in files:
process_file(f, identifier, delete_rodata)
else:
process_file(filenames, identifier, delete_rodata)
if spechandle.lower() == "delete":
modify_spec(filenames, identifier, True)
print("Deleted rodata for files in spec")
elif spechandle.lower() == "comment":
result = modify_spec(filenames, identifier, False)
if result:
print("Commented out rodata for files in spec")
"""
Asks what to do about spec and rodata, converts 'all' to comma-separated filenames
"""
def check_spec_rodata(filenames, identifier):
spechandle = input("Delete, Comment or Leave spec? ")
delete_rodata = input("Delete rodata file(s)? (Y/N) ")
if filenames == "all" or "all|" in filenames:
to_remove_list = []
if "all|" in filenames:
print("all| in filenames")
to_remove_list = filenames.split("|")[1]
basedir = "asm" + sep + "non_matchings" + sep + ("code" if identifier=="code" else "overlays" + sep + identifier.lower()) + sep
folder_names = [name.replace("ovl_","") for name in listdir(basedir) if isdir(basedir + name) and name not in to_remove_list]
filenames = ",".join(map(str, folder_names))
print(filenames)
process_files(filenames, identifier, spechandle, delete_rodata == "Y")
"""
Main execution
"""
def run(show_help):
if(show_help):
print("""Usage: Enter 'Code' or 'Overlay' and follow the instructions.
Batch migrate files by entering comma-separated filenames instead of a single filename.
Migrate all files by entering 'all' for filenames. Exclude files from all with all| followed by comma-separated filenames Use at your own risk.
Enter 'q' to the code or overlay question to quit.""")
code_or_overlay = input("Code or Overlay? ")
if(code_or_overlay == "q"):
return
if(code_or_overlay == "Code"):
filename = input("Enter the code file name(s) (excluding .c) or all: ")
check_spec_rodata(filename, "code")
elif(code_or_overlay == "Overlay"):
overlay_type = input("Actor, Effect or Gamestate? ")
if(overlay_type == "Actor"):
filename = input("Enter the actor name(s) (excluding ovl_ or z_, ex. obj_bombiwa) or all: ")
check_spec_rodata(filename, "actors")
elif(overlay_type == "Effect"):
filename = input("Enter the effect name(s) (excluding ovl_ or z_, ex. effect_ss_bomb) or all: ")
check_spec_rodata(filename, "effects")
elif(overlay_type == "Gamestate"):
filename = input("Enter the gamestate name(s) (excluding ovl_ or z_, ex. kaleido_scope) or all: ")
check_spec_rodata(filename, "gamestates")
run(True)
# PROGRAM -------------------------------------------------------------------
#run(False)
#bigs = ["Boss_Ganon", "Boss_Ganondrof","En_Wf", "Door_Warp1",]
ovls = ["En_Elf"]
for i, ovl in enumerate(ovls):
if i == 10:
break
process_files(ovl, "actors", "Delete", True)
command = "echo >> src/overlays/actors/ovl_" + ovls[i] + "/z_" + ovls[i].lower() + ".c"
if ovls[0] == "player":
command = "echo >> src/overlays/actors/ovl_player_actor/z_player.c"
os.system(command) # purpose of this is to "modify" each C file in order to prevent undefined symbol errors.
# the new line will be removed by format.sh