mirror of https://github.com/pmret/papermario.git
393 lines
13 KiB
Python
393 lines
13 KiB
Python
from typing import Dict, List, Optional, Union, Literal
|
|
from pathlib import Path
|
|
from util import log
|
|
from util import compiler
|
|
from util.compiler import Compiler
|
|
|
|
opts = {}
|
|
|
|
|
|
def initialize(config: Dict, config_path, base_path=None, target_path=None):
|
|
global opts
|
|
opts = dict(config.get("options", {}))
|
|
|
|
if base_path:
|
|
opts["base_path"] = Path(base_path)
|
|
else:
|
|
if "base_path" not in opts:
|
|
log.error(
|
|
"Error: Base output dir not specified as a command line arg or via the config yaml (base_path)"
|
|
)
|
|
|
|
opts["base_path"] = Path(config_path[0]).parent / opts["base_path"]
|
|
|
|
if not target_path:
|
|
if "target_path" not in opts:
|
|
log.error(
|
|
"Error: Target binary path not specified as a command line arg or via the config yaml (target_path)"
|
|
)
|
|
|
|
|
|
def set(opt, val):
|
|
opts[opt] = val
|
|
|
|
|
|
def get(opt, default=None):
|
|
return opts.get(opt, default)
|
|
|
|
|
|
# Returns whether the given mode is currently enabled
|
|
def mode_active(mode) -> bool:
|
|
return mode in opts["modes"] or "all" in opts["modes"]
|
|
|
|
|
|
# TODO standardize names of options for categories & decide whether to stick to get_ or drop it
|
|
################################################################################
|
|
# Debug / logging options
|
|
################################################################################
|
|
|
|
# Determines whether to log more verbose output
|
|
def verbose() -> bool:
|
|
return opts.get("verbose", False)
|
|
|
|
|
|
def dump_symbols() -> bool:
|
|
return opts.get("dump_symbols", False)
|
|
|
|
|
|
################################################################################
|
|
# Global options
|
|
#
|
|
# These can be set in the config yaml at the top level
|
|
################################################################################
|
|
|
|
# Determines the platform of the target binary
|
|
# Currently supported: n64, psx
|
|
def get_platform() -> str:
|
|
return opts.get("platform", "n64")
|
|
|
|
|
|
# Determines the compiler used to compile the target binary
|
|
def get_compiler() -> Compiler:
|
|
return compiler.for_name(opts.get("compiler", "IDO"))
|
|
|
|
|
|
# Determines the endianness of the target binary
|
|
def get_endianess() -> Union[Literal["little"], Literal["big"]]:
|
|
return opts.get("endianess", "little" if get_platform().upper() == "PSX" else "big")
|
|
|
|
|
|
# Determines the default section order of the target binary
|
|
# this can be overridden per-segment
|
|
def get_section_order() -> List[str]:
|
|
return opts.get("section_order", [".text", ".data", ".rodata", ".bss"])
|
|
|
|
|
|
# Determines the code that is inserted by default in generated .c files
|
|
def get_generated_c_premble() -> str:
|
|
return opts.get("generated_c_preamble", '#include "common.h"')
|
|
|
|
|
|
# Determines the code that is inserted by default in generated .s files
|
|
def get_generated_s_preamble() -> str:
|
|
return opts.get("generated_s_preamble", "")
|
|
|
|
|
|
# Determines the base path of the project. Everything is relative to this path
|
|
def get_base_path() -> Path:
|
|
return Path(opts["base_path"])
|
|
|
|
|
|
# Determines the asset path of the project. Assets such as raw binary files are stored here
|
|
def get_asset_path() -> Path:
|
|
return get_base_path() / opts.get("asset_path", "assets")
|
|
|
|
|
|
# Determines the path to the target binary
|
|
def get_target_path() -> Path:
|
|
return get_base_path() / opts["target_path"]
|
|
|
|
|
|
# Determines the path to the symbol addresses file(s)
|
|
# A symbol_addrs file is to be updated/curated manually and contains addresses of symbols
|
|
# as well as optional metadata such as rom address, type, and more
|
|
#
|
|
# It's possible to use more than one file by supplying a list instead of a string
|
|
def get_symbol_addrs_paths() -> List[Path]:
|
|
paths: Union[List[str], str] = opts.get("symbol_addrs_path", "symbol_addrs.txt")
|
|
|
|
if isinstance(paths, str):
|
|
return [get_base_path() / paths]
|
|
else:
|
|
return [get_base_path() / path for path in paths]
|
|
|
|
|
|
# Determines the path to the project build directory
|
|
def get_build_path() -> Path:
|
|
return get_base_path() / opts.get("build_path", "build")
|
|
|
|
|
|
# Determines the path to the source code directory
|
|
def get_src_path() -> Path:
|
|
return get_base_path() / opts.get("src_path", "src")
|
|
|
|
|
|
# Determines the path to the asm code directory
|
|
def get_asm_path() -> Path:
|
|
return get_base_path() / opts.get("asm_path", "asm")
|
|
|
|
|
|
# Determines the path to the asm data directory
|
|
def get_data_path() -> Path:
|
|
if "data_path" in opts:
|
|
return get_base_path() / opts["data_path"]
|
|
return get_asm_path() / "data"
|
|
|
|
|
|
# Determines the path to the asm nonmatchings directory
|
|
def get_nonmatchings_path() -> Path:
|
|
if "nonmatchings_path" in opts:
|
|
return get_base_path() / opts["nonmatchings_path"]
|
|
return get_asm_path() / "nonmatchings"
|
|
|
|
|
|
# Determines the path to the cache file (used when supplied --use-cache via the CLI)
|
|
def get_cache_path() -> Path:
|
|
return get_base_path() / opts.get("cache_path", ".splat_cache")
|
|
|
|
|
|
# Determines whether to create an automatically-generated undefined functions file
|
|
# this file stores all functions that are referenced in the code but are not defined as seen by splat
|
|
def get_create_undefined_funcs_auto() -> bool:
|
|
return opts.get("create_undefined_funcs_auto", True)
|
|
|
|
|
|
# Determines the path to the undefined_funcs_auto file
|
|
def get_undefined_funcs_auto_path() -> Path:
|
|
return get_base_path() / opts.get(
|
|
"undefined_funcs_auto_path", "undefined_funcs_auto.txt"
|
|
)
|
|
|
|
|
|
# Determines whether to create an automatically-generated undefined symbols file
|
|
# this file stores all symbols that are referenced in the code but are not defined as seen by splat
|
|
def get_create_undefined_syms_auto() -> bool:
|
|
return opts.get("create_undefined_syms_auto", True)
|
|
|
|
|
|
# Determines the path to the undefined_symbols_auto file
|
|
def get_undefined_syms_auto_path() -> Path:
|
|
return get_base_path() / opts.get(
|
|
"undefined_syms_auto_path", "undefined_syms_auto.txt"
|
|
)
|
|
|
|
|
|
# TODO document
|
|
def get_create_elf_section_list_auto() -> bool:
|
|
return opts.get("create_elf_section_list_auto", False)
|
|
|
|
|
|
# TODO document
|
|
def get_elf_section_list_path() -> Path:
|
|
return get_base_path() / opts.get("elf_section_list_path", "elf_sections.txt")
|
|
|
|
|
|
# Determines the path in which to search for custom splat extensions
|
|
def get_extensions_path() -> Optional[Path]:
|
|
ext_opt = opts.get("extensions_path")
|
|
if not ext_opt:
|
|
return None
|
|
|
|
return get_base_path() / ext_opt
|
|
|
|
|
|
# Determines the path to library files that are to be linked into the target binary
|
|
def get_lib_path() -> Path:
|
|
return get_base_path() / opts.get("lib_path", "lib")
|
|
|
|
|
|
# Determines whether to use .o as the suffix for all binary files?... TODO document
|
|
def use_o_as_suffix() -> bool:
|
|
return opts.get("o_as_suffix", False)
|
|
|
|
|
|
# the value of the $gp register to correctly calculate offset to %gp_rel relocs
|
|
def get_gp() -> Optional[int]:
|
|
return opts.get("gp_value", None)
|
|
|
|
|
|
################################################################################
|
|
# Linker script options
|
|
################################################################################
|
|
|
|
# Determines the desired path to the linker script that splat will generate
|
|
def get_ld_script_path() -> Path:
|
|
return get_base_path() / opts.get("ld_script_path", f"{opts.get('basename')}.ld")
|
|
|
|
|
|
# Determines the default subalign value to be specified in the generated linker script
|
|
def get_subalign() -> int:
|
|
return opts.get("subalign", 16)
|
|
|
|
|
|
# The following option determines whether to automatically configure the linker script to link against
|
|
# specified sections for all "base" (asm/c) files when the yaml doesn't have manual configurations
|
|
# for these sections.
|
|
def auto_all_sections() -> List[str]:
|
|
val = opts.get("auto_all_sections", [".data", ".rodata", ".bss"])
|
|
if not isinstance(val, list):
|
|
raise RuntimeError(
|
|
'auto_all_sections must be a list (for example, [".data", ".rodata", ".bss"])'
|
|
)
|
|
return val
|
|
|
|
|
|
# Determines the desired path to the linker symbol header, which exposes externed definitions for all segment ram/rom start/end locations
|
|
def get_linker_symbol_header_path() -> Optional[Path]:
|
|
if "linker_symbol_header_path" in opts:
|
|
return get_base_path() / str(opts["linker_symbol_header_path"])
|
|
else:
|
|
return None
|
|
|
|
|
|
# Determines whether to create a shiftable linker script (EXPERIMENTAL)
|
|
def get_shiftable() -> bool:
|
|
return opts.get("shiftable", False)
|
|
|
|
|
|
# Determines whether to add a discard section to the linker script
|
|
def linker_discard_section() -> bool:
|
|
return opts.get("linker_discard_section", True)
|
|
|
|
|
|
# Determines the list of section labels that are to be added to the linker script
|
|
def ld_section_labels() -> List[str]:
|
|
return opts.get("ld_section_labels", [".text", ".data", ".rodata", ".bss"])
|
|
|
|
|
|
################################################################################
|
|
# C file options
|
|
################################################################################
|
|
|
|
# Determines whether to create new c files if they don't exist
|
|
def get_create_c_files() -> bool:
|
|
return opts.get("create_c_files", True)
|
|
|
|
|
|
# Determines whether to "auto-decompile" empty functions
|
|
def get_auto_decompile_empty_functions() -> bool:
|
|
return opts.get("auto_decompile_empty_functions", True)
|
|
|
|
|
|
# Determines whether to detect matched/unmatched functions in existing c files
|
|
# so we can avoid creating .s files for already-decompiled functions
|
|
def do_c_func_detection() -> bool:
|
|
return opts.get("do_c_func_detection", True)
|
|
|
|
|
|
# Determines the newline char(s) to be used in c files
|
|
def c_newline() -> str:
|
|
return opts.get("c_newline", get_compiler().c_newline)
|
|
|
|
|
|
################################################################################
|
|
# (Dis)assembly-related options
|
|
################################################################################
|
|
|
|
# The following options determine the format that symbols should be named by default
|
|
def get_symbol_name_format() -> str:
|
|
return opts.get("symbol_name_format", "$VRAM")
|
|
|
|
|
|
# Same as above but for symbols with no rom address
|
|
def get_symbol_name_format_no_rom() -> str:
|
|
return opts.get("symbol_name_format_no_rom", "$VRAM_$SEG")
|
|
|
|
|
|
# Determines whether to detect and hint to the user about likely file splits when disassembling
|
|
def find_file_boundaries() -> bool:
|
|
return opts.get("find_file_boundaries", True)
|
|
|
|
|
|
# Determines whether to attempt to automatically migrate rodata into functions (only works in certain circumstances)
|
|
def get_migrate_rodata_to_functions() -> bool:
|
|
return opts.get("migrate_rodata_to_functions", True)
|
|
|
|
|
|
# Determines the header to be used in every asm file that's included from c files
|
|
def asm_inc_header() -> str:
|
|
return opts.get("asm_inc_header", get_compiler().asm_inc_header)
|
|
|
|
|
|
# Determines the macro used to declare functions in asm files
|
|
def get_asm_function_macro() -> str:
|
|
return opts.get("asm_function_macro", get_compiler().asm_function_macro)
|
|
|
|
|
|
# Determines the macro used to declare data symbols in asm files
|
|
def get_asm_data_macro() -> str:
|
|
return opts.get("asm_data_macro", get_compiler().asm_data_macro)
|
|
|
|
|
|
# Determines the macro used at the end of a function, such as endlabel or .end
|
|
def get_asm_end_label() -> str:
|
|
return opts.get("asm_endlabels", get_compiler().asm_end_label)
|
|
|
|
|
|
# Determines the number of characters to left align before the TODO finish documenting
|
|
def mnemonic_ljust() -> int:
|
|
return opts.get("mnemonic_ljust", 11)
|
|
|
|
|
|
# Determines whether to pad the rom address
|
|
def rom_address_padding() -> bool:
|
|
return opts.get("rom_address_padding", False)
|
|
|
|
|
|
# Determines which ABI names to use for general purpose registers
|
|
# Valid values: 'numeric', 'o32', 'n32', 'n64'
|
|
def get_mips_abi_gpr() -> str:
|
|
return opts.get("mips_abi_gpr", "o32")
|
|
|
|
|
|
# Determines which ABI names to use for floating point registers
|
|
# Valid values: 'numeric', 'o32', 'n32', 'n64'
|
|
# o32 is highly recommended, as it provides logically named registers for floating point instructions
|
|
# For more info, see https://gist.github.com/EllipticEllipsis/27eef11205c7a59d8ea85632bc49224d
|
|
def get_mips_abi_float_regs() -> str:
|
|
return opts.get("mips_abi_float_regs", "numeric")
|
|
|
|
|
|
# Determines whether to ad ".set gp=64 to asm/hasm files"
|
|
def get_add_set_gp_64() -> bool:
|
|
return opts.get("add_set_gp_64", True)
|
|
|
|
|
|
################################################################################
|
|
# N64-specific options
|
|
################################################################################
|
|
|
|
# Determines the encoding of the header
|
|
def get_header_encoding() -> str:
|
|
return opts.get("header_encoding", "ASCII")
|
|
|
|
|
|
# Determines the type gfx ucode (used by gfx segments)
|
|
# Valid options are ['f3d', 'f3db', 'f3dex', 'f3dexb', 'f3dex2']
|
|
def get_gfx_ucode() -> str:
|
|
valid_options = ["f3d", "f3db", "f3dex", "f3dexb", "f3dex2"]
|
|
ret = opts.get("gfx_ucode", "f3dex2")
|
|
if ret not in valid_options:
|
|
log.error(f"Invalid gfx_ucode: {ret}. Valid options are: {valid_options}")
|
|
return ret
|
|
|
|
|
|
################################################################################
|
|
# Compiler-specific options
|
|
################################################################################
|
|
|
|
# Determines whether to use a legacy INCLUDE_ASM macro format in c files
|
|
# only applies to GCC/SN64
|
|
def get_use_legacy_include_asm() -> bool:
|
|
return opts.get("use_legacy_include_asm", True)
|