tools: leave library code and weak copies out of the order check

The decomp links all libraries before the game code, whereas the
original interleaves them with it, so every library object counted as
misordered (156 of 157) and each newly bound library row added one.

A weak function's copy in the original comes from whichever object the
linker saw first, so it says nothing about where its decomp object
belongs: 172 of 248 strays were weak copies. Weak functions now only
count for order within their object's main block.

(AI assisted using Claude Opus 5.5)
This commit is contained in:
Léo Lam
2026-10-18 20:12:09 +03:00
parent 37bab93abc
commit c9cef4a09f
+24 -4
View File
@@ -30,6 +30,7 @@ class Function(NamedTuple):
decomp_addr: int
name: str
obj: str
is_weak: bool
@dataclass
@@ -38,6 +39,7 @@ class MatchedFunctions:
not_in_binary: int = 0
shared_inline: int = 0
ambiguous: int = 0
library: int = 0
@dataclass
@@ -118,7 +120,13 @@ def match_functions(decomp_elf: Path, build_dir: Path) -> MatchedFunctions:
matched.not_in_binary += 1
elif len(in_binary) == 1 and len(definitions) == 1:
obj = get_object_name(definitions[0].path, build_dir)
matched.functions.append(Function(info.addr, in_binary[0].address, info.decomp_name, obj))
# The decomp links all libraries before the game code, whereas the original
# interleaves them with it, so their objects would all count as misordered.
if obj.startswith("lib/"):
matched.library += 1
continue
matched.functions.append(
Function(info.addr, in_binary[0].address, info.decomp_name, obj, definitions[0].is_weak))
elif all(definition.is_weak for definition in definitions):
matched.shared_inline += 1
else:
@@ -229,10 +237,13 @@ def main() -> None:
decomp_elf = config.get_decomp_elf().resolve()
matched = match_functions(decomp_elf, decomp_elf.parent)
# A weak function's copy in the original came from whichever object the linker saw first, so
# it can't place its object or make it split. It still counts within its object's main block.
strong_functions = [function for function in matched.functions if not function.is_weak]
main_runs: dict[str, list[Function]] = {}
split_objects: list[str] = []
stray_functions: list[Function] = []
for obj, runs in split_into_blocks(matched.functions).items():
for obj, runs in split_into_blocks(strong_functions).items():
main_run = max(runs, key=len)
main_runs[obj] = main_run
if len(runs) > 1:
@@ -240,13 +251,21 @@ def main() -> None:
stray_functions += [function for run in runs if run is not main_run for function in run]
stray_functions.sort()
main_blocks = {obj: list(run) for obj, run in main_runs.items()}
for function in matched.functions:
run = main_runs.get(function.obj)
if function.is_weak and run and run[0].orig_addr < function.orig_addr < run[-1].orig_addr:
main_blocks[function.obj].append(function)
for block in main_blocks.values():
block.sort()
objects_in_original_order = sorted(main_runs, key=lambda obj: main_runs[obj][0].orig_addr)
misordered_objects = out_of_order(objects_in_original_order,
lambda obj: min(function.decomp_addr for function in main_runs[obj]))
misordered_functions: list[Function] = []
for obj in objects_in_original_order:
misordered_functions += out_of_order(main_runs[obj], lambda function: function.decomp_addr)
misordered_functions += out_of_order(main_blocks[obj], lambda function: function.decomp_addr)
report = Report(
misordered_objects=[f"0x{main_runs[obj][0].orig_addr + ORIGINAL_BASE:016x} {obj}" for obj in misordered_objects],
@@ -267,7 +286,8 @@ def main() -> None:
print(f"{len(matched.functions):>7d} compared functions")
print(f"{Style.DIM}{matched.not_in_binary:>7d} skipped, not in the decomp binary")
print(f"{matched.shared_inline:>7d} skipped, inline in several objects")
print(f"{matched.ambiguous:>7d} skipped, ambiguous name{Style.RESET_ALL}")
print(f"{matched.ambiguous:>7d} skipped, ambiguous name")
print(f"{matched.library:>7d} skipped, library code{Style.RESET_ALL}")
print(f"{len(main_runs):>7d} objects")
print(f"{len(misordered_objects):>7d} {Fore.RED}misordered objects{Fore.RESET}")
print(f"{len(split_objects):>7d} {Fore.YELLOW}objects split in the original{Fore.RESET} "