inital commit
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
# pyright: reportUnknownMemberType=false
|
||||
|
||||
import re
|
||||
import argparse
|
||||
|
||||
from typing import cast
|
||||
from pathlib import Path
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.sections import SymbolTableSection
|
||||
|
||||
|
||||
"""
|
||||
matches variable declaration:
|
||||
/* SECTION ADDRESS */ VAR_TYPE NAME[NUMEL];
|
||||
"""
|
||||
re_glob = re.compile(
|
||||
r"^/\* (?P<section>.*?) (?P<address>.*?) \*/ (?P<var_type>[^(]*) (?P<name>.*?)(?:\[(?P<numel>.*?)\])?;"
|
||||
)
|
||||
|
||||
"""
|
||||
matches structs (or unions) with no typedef:
|
||||
struct NAME { // SIZE
|
||||
...
|
||||
};
|
||||
"""
|
||||
re_struct = re.compile(
|
||||
r"^(?:struct|union) (?P<name>.*?) \{ // (?P<size>0x[0-9a-f]+)\n.*?^\};", flags=re.MULTILINE | re.DOTALL
|
||||
)
|
||||
|
||||
"""
|
||||
matches structs (or unions) with typedef:
|
||||
typedef struct { // SIZE
|
||||
...
|
||||
} NAME;
|
||||
"""
|
||||
re_typedef_struct = re.compile(
|
||||
r"^typedef (?:struct|union) \{ // (?P<size>0x[0-9a-f]+)\n.*?^\} (?P<name>.*?);", flags=re.MULTILINE | re.DOTALL
|
||||
)
|
||||
|
||||
sizes: dict[str, int] = {
|
||||
"char": 1,
|
||||
"u_char": 1,
|
||||
"short": 2,
|
||||
"short int": 2,
|
||||
"u_short": 2,
|
||||
"int": 4,
|
||||
"u_int": 4,
|
||||
"float": 4,
|
||||
"u_long128": 8,
|
||||
"sceVu0FMATRIX": 4 * 4 * 4,
|
||||
"sceVu0FVECTOR": 4 * 4,
|
||||
"sceSifClientData": 0x2C,
|
||||
}
|
||||
|
||||
command_script_keywords = (
|
||||
"VERSION",
|
||||
"SECTIONS",
|
||||
"ABSOLUTE",
|
||||
"LOADADDR",
|
||||
"ALIGN",
|
||||
"DEFINED",
|
||||
"NEXT",
|
||||
"SIZEOF",
|
||||
"SIZEOF_HEADERS",
|
||||
"MAX",
|
||||
"MIN",
|
||||
"PHDRS",
|
||||
"CREATE_OBJECT_SYMBOLS",
|
||||
"BYTE",
|
||||
"SHORT",
|
||||
"LONG",
|
||||
"SQUAD",
|
||||
"FILL",
|
||||
"BLOCK",
|
||||
"NOLOAD",
|
||||
"AT",
|
||||
"OVERLAY",
|
||||
"NOCROSSREFS",
|
||||
"PT_NULL",
|
||||
"PT_LOAD",
|
||||
"PT_DYNAMIC",
|
||||
"PT_INTERP",
|
||||
"PT_NOTE",
|
||||
"PT_SHLIB",
|
||||
"PT_PHDR",
|
||||
"ENTRY",
|
||||
"FLOAT",
|
||||
"NOFLOAT",
|
||||
"FORCE_COMMON_ALLOCATION",
|
||||
"INCLUDE",
|
||||
"INPUT",
|
||||
"GROUP",
|
||||
"OUTPUT",
|
||||
"OUTPUT_ARCH",
|
||||
"OUTPUT_FORMAT",
|
||||
"SEARCH_DIR",
|
||||
"STARTUP",
|
||||
"TARGET",
|
||||
"NOCROSSREFS",
|
||||
)
|
||||
|
||||
|
||||
class GlobalVarLineMatch:
|
||||
max_section_len: int = 0
|
||||
max_address_len: int = 0
|
||||
max_var_type_len: int = 0
|
||||
max_name_len: int = 0
|
||||
max_numel_len: int = 0
|
||||
|
||||
_name_cache: list[str] = []
|
||||
|
||||
@classmethod
|
||||
def _get_unique_name(cls, name: str):
|
||||
i = 1
|
||||
unique_name = f"{name}__local_{i}"
|
||||
while unique_name in cls._name_cache:
|
||||
i += 1
|
||||
unique_name = f"{name}__local_{i}"
|
||||
cls._name_cache.append(unique_name)
|
||||
return unique_name
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
symtab: SymbolTableSection,
|
||||
/,
|
||||
*,
|
||||
section: str,
|
||||
address: str | int,
|
||||
var_type: str,
|
||||
name: str,
|
||||
numel: str | int | None = None,
|
||||
):
|
||||
self.symtab: SymbolTableSection = symtab
|
||||
|
||||
self.section: str = section
|
||||
self.address: int = address if isinstance(address, int) else int(address, 16)
|
||||
self.var_type: str = var_type
|
||||
self.name: str = name
|
||||
self.numel: int
|
||||
|
||||
while self.name.startswith("*"):
|
||||
self.name = self.name[1:]
|
||||
self.var_type += "*"
|
||||
|
||||
# resolve function pointers (e.g., 'void (*SpecialEventInitTbl[0])...')
|
||||
if match := re.match(r"\(\*(.*?)\[.*?\]\).*", self.name):
|
||||
self.name = match.group(1)
|
||||
|
||||
self.name = self.name.replace("*", "")
|
||||
|
||||
if numel is not None:
|
||||
if isinstance(numel, int):
|
||||
self.numel = numel
|
||||
else:
|
||||
prod_numel = 1
|
||||
numels = numel.split("][")
|
||||
for n in numels:
|
||||
prod_numel *= int(n)
|
||||
self.numel = prod_numel
|
||||
|
||||
if numel is None:
|
||||
self.numel = 1
|
||||
|
||||
# get symbol
|
||||
symbol = self.symtab.get_symbol_by_name(self.name)
|
||||
|
||||
assert isinstance(symbol, list)
|
||||
|
||||
symbol = next(
|
||||
(sym for sym in symbol if sym.name == self.name and sym.entry["st_value"] == self.address),
|
||||
None,
|
||||
)
|
||||
|
||||
assert symbol
|
||||
|
||||
self.symbol = symbol
|
||||
|
||||
if not self.is_global:
|
||||
self.name = self._get_unique_name(self.name)
|
||||
|
||||
escaped_name_len = len(str(self.name))
|
||||
if self.name in command_script_keywords:
|
||||
escaped_name_len += 2
|
||||
|
||||
GlobalVarLineMatch.max_section_len = max(GlobalVarLineMatch.max_section_len, len(str(self.section)))
|
||||
GlobalVarLineMatch.max_address_len = max(GlobalVarLineMatch.max_address_len, len(str(self.address)))
|
||||
GlobalVarLineMatch.max_var_type_len = max(GlobalVarLineMatch.max_var_type_len, len(str(self.var_type)))
|
||||
GlobalVarLineMatch.max_name_len = max(GlobalVarLineMatch.max_name_len, escaped_name_len)
|
||||
GlobalVarLineMatch.max_numel_len = max(GlobalVarLineMatch.max_numel_len, len(str(self.numel)))
|
||||
|
||||
@property
|
||||
def is_global(self):
|
||||
return cast(str, self.symbol["st_info"]["bind"]) == "STB_GLOBAL"
|
||||
|
||||
@property
|
||||
def is_hidden(self):
|
||||
return cast(str, self.symbol["st_other"]["visibility"]) == "STV_HIDDEN"
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
size = cast(int, self.symbol["st_size"])
|
||||
if size == 0:
|
||||
if "*" in self.var_type:
|
||||
size = 4
|
||||
elif self.var_type in sizes:
|
||||
size = sizes[self.var_type] * self.numel
|
||||
return size
|
||||
|
||||
def to_string(self, as_linker_command_file: bool):
|
||||
name = self.name if self.name not in command_script_keywords else f'"{self.name}"'
|
||||
cls_str = f"{name:{GlobalVarLineMatch.max_name_len}s} = 0x{self.address:08x};"
|
||||
|
||||
if not as_linker_command_file:
|
||||
if self.size > 0:
|
||||
cls_str = f"{cls_str} // size:0x{self.size:x}"
|
||||
else:
|
||||
cls_str = f"{cls_str} //0 {self.var_type} * {self.numel}"
|
||||
|
||||
# cls_str += f" bind:{'global' if self.is_global else 'local'}"
|
||||
# cls_str += f" visibility:{'hidden' if self.is_hidden else 'visible'}"
|
||||
|
||||
return cls_str
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string(as_linker_command_file=False)
|
||||
|
||||
|
||||
def parse_globals(elf_path: Path, globals_path: Path, types_path: Path, as_linker_command_file: bool):
|
||||
with open(elf_path, mode="rb") as fh:
|
||||
elf = ELFFile(fh)
|
||||
|
||||
# Find the symbol table.
|
||||
symtab = elf.get_section_by_name(".symtab")
|
||||
assert isinstance(symtab, SymbolTableSection)
|
||||
|
||||
with open(types_path, mode="r") as f:
|
||||
types_data = f.read()
|
||||
|
||||
for struct_type, size_hex_str in re_struct.findall(types_data):
|
||||
struct_type = cast(str, struct_type)
|
||||
struct_size = int(cast(str, size_hex_str), 16)
|
||||
# assert not (struct_type in sizes and sizes[struct_type] != struct_size)
|
||||
sizes[struct_type] = struct_size
|
||||
|
||||
for size_hex_str, struct_type in re_typedef_struct.findall(types_data):
|
||||
struct_type = cast(str, struct_type)
|
||||
struct_size = int(cast(str, size_hex_str), 16)
|
||||
# assert not (struct_type in sizes and sizes[struct_type] != struct_size), (
|
||||
# struct_type,
|
||||
# sizes[struct_type],
|
||||
# struct_size,
|
||||
# )
|
||||
sizes[struct_type] = struct_size
|
||||
|
||||
if "tagSE_WRK" in sizes:
|
||||
sizes["SE_WRK"] = sizes["tagSE_WRK"]
|
||||
|
||||
with open(globals_path, mode="r") as f:
|
||||
lines = [line.strip() for line in f.readlines() if line.startswith("/*") and line.strip().endswith(";")]
|
||||
|
||||
matches = [GlobalVarLineMatch(symtab, **match.groupdict()) for line in lines if (match := re_glob.match(line))]
|
||||
matches.sort(key=lambda match: match.address)
|
||||
|
||||
for match in matches:
|
||||
print(match.to_string(as_linker_command_file=as_linker_command_file))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--language", required=True, choices=["us", "eu"])
|
||||
parser.add_argument("--as-linker-command-file", action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.language == "us":
|
||||
elf_path = Path("config/us/SLUS_203.88")
|
||||
globals_path = Path("ccc/SLUS_203.88/globals.h")
|
||||
types_path = Path("ccc/SLUS_203.88/types.h")
|
||||
else:
|
||||
elf_path = Path("config/eu/SLES_508.21")
|
||||
globals_path = Path("ccc/SLES_508.21/globals.h")
|
||||
types_path = Path("ccc/SLES_508.21/types.h")
|
||||
|
||||
parse_globals(elf_path, globals_path, types_path, args.as_linker_command_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user