diff --git a/diff.py b/diff.py index d177daace4..646b6f6585 100755 --- a/diff.py +++ b/diff.py @@ -4,17 +4,18 @@ import argparse import sys from typing import ( Any, + Callable, Dict, + Iterator, List, Match, - NamedTuple, NoReturn, Optional, + Pattern, Set, Tuple, + Type, Union, - Callable, - Pattern, ) @@ -23,333 +24,892 @@ def fail(msg: str) -> NoReturn: sys.exit(1) -# Prefer to use diff_settings.py from the current working directory -sys.path.insert(0, ".") -try: - import diff_settings -except ModuleNotFoundError: - fail("Unable to find diff_settings.py in the same directory.") -sys.path.pop(0) +def static_assert_unreachable(x: NoReturn) -> NoReturn: + raise Exception("Unreachable! " + repr(x)) + # ==== COMMAND-LINE ==== -try: - import argcomplete # type: ignore -except ModuleNotFoundError: - argcomplete = None +if __name__ == "__main__": + # Prefer to use diff_settings.py from the current working directory + sys.path.insert(0, ".") + try: + import diff_settings + except ModuleNotFoundError: + fail("Unable to find diff_settings.py in the same directory.") + sys.path.pop(0) -parser = argparse.ArgumentParser(description="Diff MIPS or AArch64 assembly.") + try: + import argcomplete + except ModuleNotFoundError: + argcomplete = None -start_argument = parser.add_argument( - "start", - help="Function name or address to start diffing from.", -) + parser = argparse.ArgumentParser( + description="Diff MIPS, PPC, AArch64, or ARM32 assembly." + ) -if argcomplete: + start_argument = parser.add_argument( + "start", + help="Function name or address to start diffing from.", + ) - def complete_symbol( - prefix: str, parsed_args: argparse.Namespace, **kwargs: object - ) -> List[str]: - if not prefix or prefix.startswith("-"): - # skip reading the map file, which would - # result in a lot of useless completions - return [] - config: Dict[str, Any] = {} - diff_settings.apply(config, parsed_args) # type: ignore - mapfile = config.get("mapfile") - if not mapfile: - return [] - completes = [] - with open(mapfile) as f: - data = f.read() - # assume symbols are prefixed by a space character - search = f" {prefix}" - pos = data.find(search) - while pos != -1: - # skip the space character in the search string - pos += 1 - # assume symbols are suffixed by either a space - # character or a (unix-style) line return - spacePos = data.find(" ", pos) - lineReturnPos = data.find("\n", pos) - if lineReturnPos == -1: - endPos = spacePos - elif spacePos == -1: - endPos = lineReturnPos - else: - endPos = min(spacePos, lineReturnPos) - if endPos == -1: - match = data[pos:] - pos = -1 - else: - match = data[pos:endPos] - pos = data.find(search, endPos) - completes.append(match) - return completes + if argcomplete: - setattr(start_argument, "completer", complete_symbol) + def complete_symbol( + prefix: str, parsed_args: argparse.Namespace, **kwargs: object + ) -> List[str]: + if not prefix or prefix.startswith("-"): + # skip reading the map file, which would + # result in a lot of useless completions + return [] + config: Dict[str, Any] = {} + diff_settings.apply(config, parsed_args) # type: ignore + mapfile = config.get("mapfile") + if not mapfile: + return [] + completes = [] + with open(mapfile) as f: + data = f.read() + # assume symbols are prefixed by a space character + search = f" {prefix}" + pos = data.find(search) + while pos != -1: + # skip the space character in the search string + pos += 1 + # assume symbols are suffixed by either a space + # character or a (unix-style) line return + spacePos = data.find(" ", pos) + lineReturnPos = data.find("\n", pos) + if lineReturnPos == -1: + endPos = spacePos + elif spacePos == -1: + endPos = lineReturnPos + else: + endPos = min(spacePos, lineReturnPos) + if endPos == -1: + match = data[pos:] + pos = -1 + else: + match = data[pos:endPos] + pos = data.find(search, endPos) + completes.append(match) + return completes -parser.add_argument( - "end", - nargs="?", - help="Address to end diff at.", -) -parser.add_argument( - "-o", - dest="diff_obj", - action="store_true", - help="Diff .o files rather than a whole binary. This makes it possible to " - "see symbol names. (Recommended)", -) -parser.add_argument( - "-e", - "--elf", - dest="diff_elf_symbol", - metavar="SYMBOL", - help="Diff a given function in two ELFs, one being stripped and the other " - "one non-stripped. Requires objdump from binutils 2.33+.", -) -parser.add_argument( - "--source", - action="store_true", - help="Show source code (if possible). Only works with -o and -e.", -) -parser.add_argument( - "--inlines", - action="store_true", - help="Show inline function calls (if possible). Only works with -o and -e.", -) -parser.add_argument( - "--base-asm", - dest="base_asm", - metavar="FILE", - help="Read assembly from given file instead of configured base img.", -) -parser.add_argument( - "--write-asm", - dest="write_asm", - metavar="FILE", - help="Write the current assembly output to file, e.g. for use with --base-asm.", -) -parser.add_argument( - "-m", - "--make", - dest="make", - action="store_true", - help="Automatically run 'make' on the .o file or binary before diffing.", -) -parser.add_argument( - "-l", - "--skip-lines", - dest="skip_lines", - type=int, - default=0, - metavar="LINES", - help="Skip the first N lines of output.", -) -parser.add_argument( - "-s", - "--stop-jr-ra", - dest="stop_jrra", - action="store_true", - help="Stop disassembling at the first 'jr ra'. Some functions have multiple return points, so use with care!", -) -parser.add_argument( - "-i", - "--ignore-large-imms", - dest="ignore_large_imms", - action="store_true", - help="Pretend all large enough immediates are the same.", -) -parser.add_argument( - "-I", - "--ignore-addr-diffs", - action="store_true", - help="Ignore address differences. Currently only affects AArch64.", -) -parser.add_argument( - "-B", - "--no-show-branches", - dest="show_branches", - action="store_false", - help="Don't visualize branches/branch targets.", -) -parser.add_argument( - "-S", - "--base-shift", - dest="base_shift", - type=str, - default="0", - help="Diff position X in our img against position X + shift in the base img. " - 'Arithmetic is allowed, so e.g. |-S "0x1234 - 0x4321"| is a reasonable ' - "flag to pass if it is known that position 0x1234 in the base img syncs " - "up with position 0x4321 in our img. Not supported together with -o.", -) -parser.add_argument( - "-w", - "--watch", - dest="watch", - action="store_true", - help="Automatically update when source/object files change. " - "Recommended in combination with -m.", -) -parser.add_argument( - "-3", - "--threeway=prev", - dest="threeway", - action="store_const", - const="prev", - help="Show a three-way diff between target asm, current asm, and asm " - "prior to -w rebuild. Requires -w.", -) -parser.add_argument( - "-b", - "--threeway=base", - dest="threeway", - action="store_const", - const="base", - help="Show a three-way diff between target asm, current asm, and asm " - "when diff.py was started. Requires -w.", -) -parser.add_argument( - "--width", - dest="column_width", - type=int, - default=50, - help="Sets the width of the left and right view column.", -) -parser.add_argument( - "--algorithm", - dest="algorithm", - default="levenshtein", - choices=["levenshtein", "difflib"], - help="Diff algorithm to use. Levenshtein gives the minimum diff, while difflib " - "aims for long sections of equal opcodes. Defaults to %(default)s.", -) -parser.add_argument( - "--max-size", - "--max-lines", - dest="max_lines", - type=int, - default=1024, - help="The maximum length of the diff, in lines.", -) + setattr(start_argument, "completer", complete_symbol) -# Project-specific flags, e.g. different versions/make arguments. -add_custom_arguments_fn = getattr(diff_settings, "add_custom_arguments", None) -if add_custom_arguments_fn: - add_custom_arguments_fn(parser) + parser.add_argument( + "end", + nargs="?", + help="Address to end diff at.", + ) + parser.add_argument( + "-o", + dest="diff_obj", + action="store_true", + help="""Diff .o files rather than a whole binary. This makes it possible to + see symbol names. (Recommended)""", + ) + parser.add_argument( + "-e", + "--elf", + dest="diff_elf_symbol", + metavar="SYMBOL", + help="""Diff a given function in two ELFs, one being stripped and the other + one non-stripped. Requires objdump from binutils 2.33+.""", + ) + parser.add_argument( + "-c", + "--source", + dest="show_source", + action="store_true", + help="Show source code (if possible). Only works with -o or -e.", + ) + parser.add_argument( + "-C", + "--source-old-binutils", + dest="source_old_binutils", + action="store_true", + help="""Tweak --source handling to make it work with binutils < 2.33. + Implies --source.""", + ) + parser.add_argument( + "-L", + "--line-numbers", + dest="show_line_numbers", + action="store_const", + const=True, + help="""Show source line numbers in output, when available. May be enabled by + default depending on diff_settings.py.""", + ) + parser.add_argument( + "--no-line-numbers", + dest="show_line_numbers", + action="store_const", + const=False, + help="Hide source line numbers in output.", + ) + parser.add_argument( + "--inlines", + dest="inlines", + action="store_true", + help="Show inline function calls (if possible). Only works with -o or -e.", + ) + parser.add_argument( + "--base-asm", + dest="base_asm", + metavar="FILE", + help="Read assembly from given file instead of configured base img.", + ) + parser.add_argument( + "--write-asm", + dest="write_asm", + metavar="FILE", + help="Write the current assembly output to file, e.g. for use with --base-asm.", + ) + parser.add_argument( + "-m", + "--make", + dest="make", + action="store_true", + help="Automatically run 'make' on the .o file or binary before diffing.", + ) + parser.add_argument( + "-l", + "--skip-lines", + dest="skip_lines", + metavar="LINES", + type=int, + default=0, + help="Skip the first LINES lines of output.", + ) + parser.add_argument( + "-s", + "--stop-jr-ra", + dest="stop_jrra", + action="store_true", + help="""Stop disassembling at the first 'jr ra'. Some functions have + multiple return points, so use with care!""", + ) + parser.add_argument( + "-i", + "--ignore-large-imms", + dest="ignore_large_imms", + action="store_true", + help="Pretend all large enough immediates are the same.", + ) + parser.add_argument( + "-I", + "--ignore-addr-diffs", + dest="ignore_addr_diffs", + action="store_true", + help="Ignore address differences. Currently only affects AArch64 and ARM32.", + ) + parser.add_argument( + "-B", + "--no-show-branches", + dest="show_branches", + action="store_false", + help="Don't visualize branches/branch targets.", + ) + parser.add_argument( + "-S", + "--base-shift", + dest="base_shift", + metavar="N", + type=str, + default="0", + help="""Diff position N in our img against position N + shift in the base img. + Arithmetic is allowed, so e.g. |-S "0x1234 - 0x4321"| is a reasonable + flag to pass if it is known that position 0x1234 in the base img syncs + up with position 0x4321 in our img. Not supported together with -o.""", + ) + parser.add_argument( + "-w", + "--watch", + dest="watch", + action="store_true", + help="""Automatically update when source/object files change. + Recommended in combination with -m.""", + ) + parser.add_argument( + "-3", + "--threeway=prev", + dest="threeway", + action="store_const", + const="prev", + help="""Show a three-way diff between target asm, current asm, and asm + prior to -w rebuild. Requires -w.""", + ) + parser.add_argument( + "-b", + "--threeway=base", + dest="threeway", + action="store_const", + const="base", + help="""Show a three-way diff between target asm, current asm, and asm + when diff.py was started. Requires -w.""", + ) + parser.add_argument( + "--width", + dest="column_width", + metavar="COLS", + type=int, + default=50, + help="Sets the width of the left and right view column.", + ) + parser.add_argument( + "--algorithm", + dest="algorithm", + default="levenshtein", + choices=["levenshtein", "difflib"], + help="""Diff algorithm to use. Levenshtein gives the minimum diff, while difflib + aims for long sections of equal opcodes. Defaults to %(default)s.""", + ) + parser.add_argument( + "--max-size", + "--max-lines", + metavar="LINES", + dest="max_lines", + type=int, + default=1024, + help="The maximum length of the diff, in lines.", + ) + parser.add_argument( + "--no-pager", + dest="no_pager", + action="store_true", + help="""Disable the pager; write output directly to stdout, then exit. + Incompatible with --watch.""", + ) + parser.add_argument( + "--format", + choices=("color", "plain", "html", "json"), + default="color", + help="Output format, default is color. --format=html or json implies --no-pager.", + ) + parser.add_argument( + "-U", + "--compress-matching", + metavar="N", + dest="compress_matching", + type=int, + help="""Compress streaks of matching lines, leaving N lines of context + around non-matching parts.""", + ) + parser.add_argument( + "-V", + "--compress-sameinstr", + metavar="N", + dest="compress_sameinstr", + type=int, + help="""Compress streaks of lines with same instructions (but possibly + different regalloc), leaving N lines of context around other parts.""", + ) -if argcomplete: - argcomplete.autocomplete(parser) + # Project-specific flags, e.g. different versions/make arguments. + add_custom_arguments_fn = getattr(diff_settings, "add_custom_arguments", None) + if add_custom_arguments_fn: + add_custom_arguments_fn(parser) + + if argcomplete: + argcomplete.autocomplete(parser) # ==== IMPORTS ==== # (We do imports late to optimize auto-complete performance.) -import re -import os +import abc import ast -import subprocess +from collections import Counter, defaultdict +from dataclasses import asdict, dataclass, field, replace import difflib -import string +import enum +import html import itertools -import threading +import json +import os import queue +import re +import string +import struct +import subprocess +import threading import time +import traceback MISSING_PREREQUISITES = ( "Missing prerequisite python module {}. " - "Run `python3 -m pip install --user colorama ansiwrap watchdog python-Levenshtein cxxfilt` to install prerequisites (cxxfilt only needed with --source)." + "Run `python3 -m pip install --user colorama watchdog python-Levenshtein cxxfilt` to install prerequisites (cxxfilt only needed with --source)." ) try: - from colorama import Fore, Style, Back # type: ignore - import ansiwrap # type: ignore - import watchdog # type: ignore + from colorama import Back, Fore, Style + import watchdog except ModuleNotFoundError as e: fail(MISSING_PREREQUISITES.format(e.name)) # ==== CONFIG ==== -args = parser.parse_args() -# Set imgs, map file and make flags in a project-specific manner. -config: Dict[str, Any] = {} -diff_settings.apply(config, args) # type: ignore +@dataclass +class ProjectSettings: + arch_str: str + objdump_executable: str + build_command: List[str] + map_format: str + mw_build_dir: str + baseimg: Optional[str] + myimg: Optional[str] + mapfile: Optional[str] + source_directories: Optional[List[str]] + source_extensions: List[str] + show_line_numbers_default: bool -arch: str = config.get("arch", "mips") -baseimg: Optional[str] = config.get("baseimg") -myimg: Optional[str] = config.get("myimg") -mapfile: Optional[str] = config.get("mapfile") -build_command: List[str] = config.get("make_command", ["make", *config.get("makeflags", [])]) -source_directories: Optional[List[str]] = config.get("source_directories") -objdump_executable: Optional[str] = config.get("objdump_executable") -map_format: str = config.get("map_format", "gnu") -mw_build_dir: str = config.get("mw_build_dir", "build/") -MAX_FUNCTION_SIZE_LINES: int = args.max_lines -MAX_FUNCTION_SIZE_BYTES: int = MAX_FUNCTION_SIZE_LINES * 4 +@dataclass +class Compress: + context: int + same_instr: bool -COLOR_ROTATION: List[str] = [ - Fore.MAGENTA, - Fore.CYAN, - Fore.GREEN, - Fore.RED, - Fore.LIGHTYELLOW_EX, - Fore.LIGHTMAGENTA_EX, - Fore.LIGHTCYAN_EX, - Fore.LIGHTGREEN_EX, - Fore.LIGHTBLACK_EX, -] -BUFFER_CMD: List[str] = ["tail", "-c", str(10 ** 9)] -LESS_CMD: List[str] = ["less", "-SRic", "-#6"] +@dataclass +class Config: + arch: "ArchSettings" -DEBOUNCE_DELAY: float = 0.1 -FS_WATCH_EXTENSIONS: List[str] = [".c", ".h"] + # Build/objdump options + diff_obj: bool + make: bool + source_old_binutils: bool + inlines: bool + max_function_size_lines: int + max_function_size_bytes: int -# ==== LOGIC ==== + # Display options + formatter: "Formatter" + threeway: Optional[str] + base_shift: int + skip_lines: int + compress: Optional[Compress] + show_branches: bool + show_line_numbers: bool + show_source: bool + stop_jrra: bool + ignore_large_imms: bool + ignore_addr_diffs: bool + algorithm: str -ObjdumpCommand = Tuple[List[str], str, Optional[str]] + # Score options + score_stack_differences = True + penalty_stackdiff = 1 + penalty_regalloc = 5 + penalty_reordering = 60 + penalty_insertion = 100 + penalty_deletion = 100 -if args.algorithm == "levenshtein": - try: - import Levenshtein # type: ignore - except ModuleNotFoundError as e: - fail(MISSING_PREREQUISITES.format(e.name)) -if args.source: - try: - import cxxfilt # type: ignore - except ModuleNotFoundError as e: - fail(MISSING_PREREQUISITES.format(e.name)) +def create_project_settings(settings: Dict[str, Any]) -> ProjectSettings: + return ProjectSettings( + arch_str=settings.get("arch", "mips"), + baseimg=settings.get("baseimg"), + myimg=settings.get("myimg"), + mapfile=settings.get("mapfile"), + build_command=settings.get( + "make_command", ["make", *settings.get("makeflags", [])] + ), + source_directories=settings.get("source_directories"), + source_extensions=settings.get( + "source_extensions", [".c", ".h", ".cpp", ".hpp", ".s"] + ), + objdump_executable=get_objdump_executable(settings.get("objdump_executable")), + map_format=settings.get("map_format", "gnu"), + mw_build_dir=settings.get("mw_build_dir", "build/"), + show_line_numbers_default=settings.get("show_line_numbers_default", True), + ) -if args.threeway and not args.watch: - fail("Threeway diffing requires -w.") -if objdump_executable is None: - for objdump_cand in ["mips-linux-gnu-objdump", "mips64-elf-objdump"]: +def create_config(args: argparse.Namespace, project: ProjectSettings) -> Config: + arch = get_arch(project.arch_str) + + formatter: Formatter + if args.format == "plain": + formatter = PlainFormatter(column_width=args.column_width) + elif args.format == "color": + formatter = AnsiFormatter(column_width=args.column_width) + elif args.format == "html": + formatter = HtmlFormatter() + elif args.format == "json": + formatter = JsonFormatter(arch_str=arch.name) + else: + raise ValueError(f"Unsupported --format: {args.format}") + + compress = None + if args.compress_matching is not None: + compress = Compress(args.compress_matching, False) + if args.compress_sameinstr is not None: + if compress is not None: + raise ValueError( + "Cannot pass both --compress-matching and --compress-sameinstr" + ) + compress = Compress(args.compress_sameinstr, True) + + show_line_numbers = args.show_line_numbers + if show_line_numbers is None: + show_line_numbers = project.show_line_numbers_default + + return Config( + arch=arch, + # Build/objdump options + diff_obj=args.diff_obj, + make=args.make, + source_old_binutils=args.source_old_binutils, + inlines=args.inlines, + max_function_size_lines=args.max_lines, + max_function_size_bytes=args.max_lines * 4, + # Display options + formatter=formatter, + threeway=args.threeway, + base_shift=eval_int( + args.base_shift, "Failed to parse --base-shift (-S) argument as an integer." + ), + skip_lines=args.skip_lines, + compress=compress, + show_branches=args.show_branches, + show_line_numbers=show_line_numbers, + show_source=args.show_source or args.source_old_binutils, + stop_jrra=args.stop_jrra, + ignore_large_imms=args.ignore_large_imms, + ignore_addr_diffs=args.ignore_addr_diffs, + algorithm=args.algorithm, + ) + + +def get_objdump_executable(objdump_executable: Optional[str]) -> str: + if objdump_executable is not None: + return objdump_executable + + objdump_candidates = ["mips-linux-gnu-objdump", "mips64-elf-objdump", "mips-elf-objdump"] + for objdump_cand in objdump_candidates: try: subprocess.check_call( [objdump_cand, "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) - objdump_executable = objdump_cand - break + return objdump_cand except subprocess.CalledProcessError: pass except FileNotFoundError: pass -if not objdump_executable: - fail( - "Missing binutils; please ensure mips-linux-gnu-objdump or mips64-elf-objdump exist, or configure objdump_executable." + return fail( + f"Missing binutils; please ensure {' or '.join(objdump_candidates)} exists, or configure objdump_executable." ) +def get_arch(arch_str: str) -> "ArchSettings": + for settings in ARCH_SETTINGS: + if arch_str == settings.name: + return settings + raise ValueError(f"Unknown architecture: {arch_str}") + + +BUFFER_CMD: List[str] = ["tail", "-c", str(10 ** 9)] + +# -S truncates long lines instead of wrapping them +# -R interprets color escape sequences +# -i ignores case when searching +# -c something about how the screen gets redrawn; I don't remember the purpose +# -#6 makes left/right arrow keys scroll by 6 characters +LESS_CMD: List[str] = ["less", "-SRic", "-#6"] + +DEBOUNCE_DELAY: float = 0.1 + +# ==== FORMATTING ==== + + +@enum.unique +class BasicFormat(enum.Enum): + NONE = enum.auto() + IMMEDIATE = enum.auto() + STACK = enum.auto() + REGISTER = enum.auto() + DELAY_SLOT = enum.auto() + DIFF_CHANGE = enum.auto() + DIFF_ADD = enum.auto() + DIFF_REMOVE = enum.auto() + SOURCE_FILENAME = enum.auto() + SOURCE_FUNCTION = enum.auto() + SOURCE_LINE_NUM = enum.auto() + SOURCE_OTHER = enum.auto() + + +@dataclass(frozen=True) +class RotationFormat: + group: str + index: int + key: str + + +Format = Union[BasicFormat, RotationFormat] +FormatFunction = Callable[[str], Format] + + +class Text: + segments: List[Tuple[str, Format]] + + def __init__(self, line: str = "", f: Format = BasicFormat.NONE) -> None: + self.segments = [(line, f)] if line else [] + + def reformat(self, f: Format) -> "Text": + return Text(self.plain(), f) + + def plain(self) -> str: + return "".join(s for s, f in self.segments) + + def __repr__(self) -> str: + return f"" + + def __bool__(self) -> bool: + return any(s for s, f in self.segments) + + def __str__(self) -> str: + # Use Formatter.apply(...) instead + return NotImplemented + + def __eq__(self, other: object) -> bool: + return NotImplemented + + def __add__(self, other: Union["Text", str]) -> "Text": + if isinstance(other, str): + other = Text(other) + result = Text() + # If two adjacent segments have the same format, merge their lines + if ( + self.segments + and other.segments + and self.segments[-1][1] == other.segments[0][1] + ): + result.segments = ( + self.segments[:-1] + + [(self.segments[-1][0] + other.segments[0][0], self.segments[-1][1])] + + other.segments[1:] + ) + else: + result.segments = self.segments + other.segments + return result + + def __radd__(self, other: Union["Text", str]) -> "Text": + if isinstance(other, str): + other = Text(other) + return other + self + + def finditer(self, pat: Pattern[str]) -> Iterator[Match[str]]: + """Replacement for `pat.finditer(text)` that operates on the inner text, + and returns the exact same matches as `Text.sub(pat, ...)`.""" + for chunk, f in self.segments: + for match in pat.finditer(chunk): + yield match + + def sub(self, pat: Pattern[str], sub_fn: Callable[[Match[str]], "Text"]) -> "Text": + result = Text() + for chunk, f in self.segments: + i = 0 + for match in pat.finditer(chunk): + start, end = match.start(), match.end() + assert i <= start <= end <= len(chunk) + sub = sub_fn(match) + if i != start: + result.segments.append((chunk[i:start], f)) + result.segments.extend(sub.segments) + i = end + if chunk[i:]: + result.segments.append((chunk[i:], f)) + return result + + def ljust(self, column_width: int) -> "Text": + length = sum(len(x) for x, _ in self.segments) + return self + " " * max(column_width - length, 0) + + +@dataclass +class TableMetadata: + headers: Tuple[Text, ...] + current_score: int + previous_score: Optional[int] + + +class Formatter(abc.ABC): + @abc.abstractmethod + def apply_format(self, chunk: str, f: Format) -> str: + """Apply the formatting `f` to `chunk` and escape the contents.""" + ... + + @abc.abstractmethod + def table(self, meta: TableMetadata, lines: List[Tuple["OutputLine", ...]]) -> str: + """Format a multi-column table with metadata""" + ... + + def apply(self, text: Text) -> str: + return "".join(self.apply_format(chunk, f) for chunk, f in text.segments) + + @staticmethod + def outputline_texts(lines: Tuple["OutputLine", ...]) -> Tuple[Text, ...]: + return tuple([lines[0].base or Text()] + [line.fmt2 for line in lines[1:]]) + + +@dataclass +class PlainFormatter(Formatter): + column_width: int + + def apply_format(self, chunk: str, f: Format) -> str: + return chunk + + def table(self, meta: TableMetadata, lines: List[Tuple["OutputLine", ...]]) -> str: + rows = [meta.headers] + [self.outputline_texts(ls) for ls in lines] + return "\n".join( + "".join(self.apply(x.ljust(self.column_width)) for x in row) for row in rows + ) + + +@dataclass +class AnsiFormatter(Formatter): + # Additional ansi escape codes not in colorama. See: + # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters + STYLE_UNDERLINE = "\x1b[4m" + STYLE_NO_UNDERLINE = "\x1b[24m" + STYLE_INVERT = "\x1b[7m" + + BASIC_ANSI_CODES = { + BasicFormat.NONE: "", + BasicFormat.IMMEDIATE: Fore.LIGHTBLUE_EX, + BasicFormat.STACK: Fore.YELLOW, + BasicFormat.REGISTER: Fore.YELLOW, + BasicFormat.DELAY_SLOT: Fore.LIGHTBLACK_EX, + BasicFormat.DIFF_CHANGE: Fore.LIGHTBLUE_EX, + BasicFormat.DIFF_ADD: Fore.GREEN, + BasicFormat.DIFF_REMOVE: Fore.RED, + BasicFormat.SOURCE_FILENAME: Style.DIM + Style.BRIGHT, + BasicFormat.SOURCE_FUNCTION: Style.DIM + Style.BRIGHT + STYLE_UNDERLINE, + BasicFormat.SOURCE_LINE_NUM: Fore.LIGHTBLACK_EX, + BasicFormat.SOURCE_OTHER: Style.DIM, + } + + BASIC_ANSI_CODES_UNDO = { + BasicFormat.NONE: "", + BasicFormat.SOURCE_FILENAME: Style.NORMAL, + BasicFormat.SOURCE_FUNCTION: Style.NORMAL + STYLE_NO_UNDERLINE, + BasicFormat.SOURCE_OTHER: Style.NORMAL, + } + + ROTATION_ANSI_COLORS = [ + Fore.MAGENTA, + Fore.CYAN, + Fore.GREEN, + Fore.RED, + Fore.LIGHTYELLOW_EX, + Fore.LIGHTMAGENTA_EX, + Fore.LIGHTCYAN_EX, + Fore.LIGHTGREEN_EX, + Fore.LIGHTBLACK_EX, + ] + + column_width: int + + def apply_format(self, chunk: str, f: Format) -> str: + if f == BasicFormat.NONE: + return chunk + undo_ansi_code = Fore.RESET + if isinstance(f, BasicFormat): + ansi_code = self.BASIC_ANSI_CODES[f] + undo_ansi_code = self.BASIC_ANSI_CODES_UNDO.get(f, undo_ansi_code) + elif isinstance(f, RotationFormat): + ansi_code = self.ROTATION_ANSI_COLORS[ + f.index % len(self.ROTATION_ANSI_COLORS) + ] + else: + static_assert_unreachable(f) + return f"{ansi_code}{chunk}{undo_ansi_code}" + + def table(self, meta: TableMetadata, lines: List[Tuple["OutputLine", ...]]) -> str: + rows = [(meta.headers, False)] + [ + (self.outputline_texts(line), line[1].is_data_ref) for line in lines + ] + return "\n".join( + "".join( + (self.STYLE_INVERT if is_data_ref else "") + + self.apply(x.ljust(self.column_width)) + for x in row + ) + for (row, is_data_ref) in rows + ) + + +@dataclass +class HtmlFormatter(Formatter): + rotation_formats: int = 9 + + def apply_format(self, chunk: str, f: Format) -> str: + chunk = html.escape(chunk) + if f == BasicFormat.NONE: + return chunk + if isinstance(f, BasicFormat): + class_name = f.name.lower().replace("_", "-") + data_attr = "" + elif isinstance(f, RotationFormat): + class_name = f"rotation-{f.index % self.rotation_formats}" + rotation_key = html.escape(f"{f.group};{f.key}", quote=True) + data_attr = f'data-rotation="{rotation_key}"' + else: + static_assert_unreachable(f) + return f"{chunk}" + + def table(self, meta: TableMetadata, lines: List[Tuple["OutputLine", ...]]) -> str: + def table_row(line: Tuple[Text, ...], is_data_ref: bool, cell_el: str) -> str: + tr_attrs = " class='data-ref'" if is_data_ref else "" + output_row = f" " + for cell in line: + cell_html = self.apply(cell) + output_row += f"<{cell_el}>{cell_html}" + output_row += "\n" + return output_row + + output = "\n" + output += " \n" + output += table_row(meta.headers, False, "th") + output += " \n" + output += " \n" + output += "".join( + table_row(self.outputline_texts(line), line[1].is_data_ref, "td") + for line in lines + ) + output += " \n" + output += "
\n" + return output + + +@dataclass +class JsonFormatter(Formatter): + arch_str: str + + def apply_format(self, chunk: str, f: Format) -> str: + # This method is unused by this formatter + return NotImplemented + + def table(self, meta: TableMetadata, rows: List[Tuple["OutputLine", ...]]) -> str: + def serialize_format(s: str, f: Format) -> Dict[str, Any]: + if f == BasicFormat.NONE: + return {"text": s} + elif isinstance(f, BasicFormat): + return {"text": s, "format": f.name.lower()} + elif isinstance(f, RotationFormat): + attrs = asdict(f) + attrs.update( + { + "text": s, + "format": "rotation", + } + ) + return attrs + else: + static_assert_unreachable(f) + + def serialize(text: Optional[Text]) -> List[Dict[str, Any]]: + if text is None: + return [] + return [serialize_format(s, f) for s, f in text.segments] + + is_threeway = len(meta.headers) == 3 + + output: Dict[str, Any] = {} + output["arch_str"] = self.arch_str + output["header"] = { + name: serialize(h) + for h, name in zip(meta.headers, ("base", "current", "previous")) + } + output["current_score"] = meta.current_score + if meta.previous_score is not None: + output["previous_score"] = meta.previous_score + output_rows: List[Dict[str, Any]] = [] + for row in rows: + output_row: Dict[str, Any] = {} + output_row["key"] = row[0].key2 + output_row["is_data_ref"] = row[1].is_data_ref + iters = [ + ("base", row[0].base, row[0].line1), + ("current", row[1].fmt2, row[1].line2), + ] + if is_threeway: + iters.append(("previous", row[2].fmt2, row[2].line2)) + if all(line is None for _, _, line in iters): + # Skip rows that were only for displaying source code + continue + for column_name, text, line in iters: + column: Dict[str, Any] = {} + column["text"] = serialize(text) + if line: + if line.line_num is not None: + column["line"] = line.line_num + if line.branch_target is not None: + column["branch"] = line.branch_target + if line.source_lines: + column["src"] = line.source_lines + if line.comment is not None: + column["src_comment"] = line.comment + if line.source_line_num is not None: + column["src_line"] = line.source_line_num + if line or column["text"]: + output_row[column_name] = column + output_rows.append(output_row) + output["rows"] = output_rows + return json.dumps(output) + + +def format_fields( + pat: Pattern[str], + out1: Text, + out2: Text, + color1: FormatFunction, + color2: Optional[FormatFunction] = None, +) -> Tuple[Text, Text]: + diffs = [ + of.group() != nf.group() + for (of, nf) in zip(out1.finditer(pat), out2.finditer(pat)) + ] + + it = iter(diffs) + + def maybe_color(color: FormatFunction, s: str) -> Text: + return Text(s, color(s)) if next(it, False) else Text(s) + + out1 = out1.sub(pat, lambda m: maybe_color(color1, m.group())) + it = iter(diffs) + out2 = out2.sub(pat, lambda m: maybe_color(color2 or color1, m.group())) + + return out1, out2 + + +def symbol_formatter(group: str, base_index: int) -> FormatFunction: + symbol_formats: Dict[str, Format] = {} + + def symbol_format(s: str) -> Format: + # TODO: it would be nice to use a unique Format for each symbol, so we could + # add extra UI elements in the HTML version + f = symbol_formats.get(s) + if f is None: + index = len(symbol_formats) + base_index + f = RotationFormat(key=s, index=index, group=group) + symbol_formats[s] = f + return f + + return symbol_format + + +# ==== LOGIC ==== + +ObjdumpCommand = Tuple[List[str], str, Optional[str]] + + def maybe_eval_int(expr: str) -> Optional[int]: try: ret = ast.literal_eval(expr) @@ -367,79 +927,124 @@ def eval_int(expr: str, emsg: str) -> int: return ret -def eval_line_num(expr: str) -> int: - return int(expr.strip().replace(":", ""), 16) +def eval_line_num(expr: str) -> Optional[int]: + expr = expr.strip().replace(":", "") + if expr == "": + return None + return int(expr, 16) -def run_make(target: str) -> None: - subprocess.check_call(build_command + [target]) +def run_make(target: str, project: ProjectSettings) -> None: + subprocess.check_call(project.build_command + [target]) -def run_make_capture_output(target: str) -> "subprocess.CompletedProcess[bytes]": +def run_make_capture_output( + target: str, project: ProjectSettings +) -> "subprocess.CompletedProcess[bytes]": return subprocess.run( - build_command + [target], + project.build_command + [target], stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) def restrict_to_function(dump: str, fn_name: str) -> str: - out: List[str] = [] - search = f"<{fn_name}>:" - found = False - for line in dump.split("\n"): - if found: - if len(out) >= MAX_FUNCTION_SIZE_LINES: - break - out.append(line) - elif search in line: - found = True - return "\n".join(out) + try: + ind = dump.index("\n", dump.index(f"<{fn_name}>:")) + return dump[ind + 1 :] + except ValueError: + return "" -def maybe_get_objdump_source_flags() -> List[str]: - if not args.source: - return [] +def serialize_data_references(references: List[Tuple[int, int, str]]) -> str: + return "".join( + f"DATAREF {text_offset} {from_offset} {from_section}\n" + for (text_offset, from_offset, from_section) in references + ) - flags = [ - "--source", - "--source-comment=│ ", - "-l", - ] - if args.inlines: - flags.append("--inlines") +def maybe_get_objdump_source_flags(config: Config) -> List[str]: + flags = [] + + if config.show_line_numbers or config.show_source: + flags.append("--line-numbers") + + if config.show_source: + flags.append("--source") + + if not config.source_old_binutils: + flags.append("--source-comment=│ ") + + if config.inlines: + flags.append("--inlines") return flags -def run_objdump(cmd: ObjdumpCommand) -> str: +def run_objdump(cmd: ObjdumpCommand, config: Config, project: ProjectSettings) -> str: flags, target, restrict = cmd - assert objdump_executable, "checked previously" - out = subprocess.check_output( - [objdump_executable] + arch_flags + flags + [target], universal_newlines=True - ) + try: + out = subprocess.run( + [project.objdump_executable] + config.arch.arch_flags + flags + [target], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ).stdout + except subprocess.CalledProcessError as e: + print(e.stdout) + print(e.stderr) + if "unrecognized option '--source-comment" in e.stderr: + fail("** Try using --source-old-binutils instead of --source **") + raise e + + obj_data: Optional[bytes] = None + if config.diff_obj: + with open(target, "rb") as f: + obj_data = f.read() + + return preprocess_objdump_out(restrict, obj_data, out) + + +def preprocess_objdump_out( + restrict: Optional[str], obj_data: Optional[bytes], objdump_out: str +) -> str: + """ + Preprocess the output of objdump into a format that `process()` expects. + This format is suitable for saving to disk with `--write-asm`. + + - Optionally filter the output to a single function (`restrict`) + - Otherwise, strip objdump header (7 lines) + - Prepend .data references ("DATAREF" lines) when working with object files + """ + out = objdump_out + if restrict is not None: - return restrict_to_function(out, restrict) + out = restrict_to_function(out, restrict) + else: + for i in range(7): + out = out[out.find("\n") + 1 :] + out = out.rstrip("\n") + + if obj_data: + out = serialize_data_references(parse_elf_data_references(obj_data)) + out + return out -base_shift: int = eval_int( - args.base_shift, "Failed to parse --base-shift (-S) argument as an integer." -) - - -def search_map_file(fn_name: str) -> Tuple[Optional[str], Optional[int]]: - if not mapfile: +def search_map_file( + fn_name: str, project: ProjectSettings +) -> Tuple[Optional[str], Optional[int]]: + if not project.mapfile: fail(f"No map file configured; cannot find function {fn_name}.") try: - with open(mapfile) as f: + with open(project.mapfile) as f: contents = f.read() except Exception: - fail(f"Failed to open map file {mapfile} for reading.") + fail(f"Failed to open map file {project.mapfile} for reading.") - if map_format == 'gnu': + if project.map_format == "gnu": lines = contents.split("\n") try: @@ -461,8 +1066,6 @@ def search_map_file(fn_name: str) -> Tuple[Optional[str], Optional[int]]: cands.append((cur_objfile, ram + ram_to_rom)) last_line = line except Exception as e: - import traceback - traceback.print_exc() fail(f"Internal error while parsing map file") @@ -470,41 +1073,174 @@ def search_map_file(fn_name: str) -> Tuple[Optional[str], Optional[int]]: fail(f"Found multiple occurrences of function {fn_name} in map file.") if len(cands) == 1: return cands[0] - elif map_format == 'mw': - # ram elf rom object name - find = re.findall(re.compile(r' \S+ \S+ (\S+) (\S+) . ' + fn_name + r'(?: \(entry of \.(?:init|text)\))? \t(\S+)'), contents) + elif project.map_format == "mw": + find = re.findall( + re.compile( + # ram elf rom + r" \S+ \S+ (\S+) (\S+) . " + + fn_name + # object name + + r"(?: \(entry of \.(?:init|text)\))? \t(\S+)" + ), + contents, + ) if len(find) > 1: fail(f"Found multiple occurrences of function {fn_name} in map file.") if len(find) == 1: - rom = int(find[0][1],16) + rom = int(find[0][1], 16) objname = find[0][2] - # The metrowerks linker map format does not contain the full object path, so we must complete it manually. - objfiles = [os.path.join(dirpath, f) for dirpath, _, filenames in os.walk(mw_build_dir) for f in filenames if f == objname] + # The metrowerks linker map format does not contain the full object path, + # so we must complete it manually. + objfiles = [ + os.path.join(dirpath, f) + for dirpath, _, filenames in os.walk(project.mw_build_dir) + for f in filenames + if f == objname + ] if len(objfiles) > 1: all_objects = "\n".join(objfiles) - fail(f"Found multiple objects of the same name {objname} in {mw_build_dir}, cannot determine which to diff against: \n{all_objects}") + fail( + f"Found multiple objects of the same name {objname} in {project.mw_build_dir}, " + f"cannot determine which to diff against: \n{all_objects}" + ) if len(objfiles) == 1: objfile = objfiles[0] - # TODO Currently the ram-rom conversion only works for diffing ELF executables, but it would likely be more convenient to diff DOLs. - # At this time it is recommended to always use -o when running the diff script as this mode does not make use of the ram-rom conversion + # TODO Currently the ram-rom conversion only works for diffing ELF + # executables, but it would likely be more convenient to diff DOLs. + # At this time it is recommended to always use -o when running the diff + # script as this mode does not make use of the ram-rom conversion. return objfile, rom else: - fail(f"Linker map format {map_format} unrecognised.") + fail(f"Linker map format {project.map_format} unrecognised.") return None, None -def dump_elf() -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: - if not baseimg or not myimg: +def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: + e_ident = data[:16] + if e_ident[:4] != b"\x7FELF": + return [] + + SHT_SYMTAB = 2 + SHT_REL = 9 + SHT_RELA = 4 + + is_32bit = e_ident[4] == 1 + is_little_endian = e_ident[5] == 1 + str_end = "<" if is_little_endian else ">" + str_off = "I" if is_32bit else "Q" + sym_size = {"B": 1, "H": 2, "I": 4, "Q": 8} + + def read(spec: str, offset: int) -> Tuple[int, ...]: + spec = spec.replace("P", str_off) + size = struct.calcsize(spec) + return struct.unpack(str_end + spec, data[offset : offset + size]) + + ( + e_type, + e_machine, + e_version, + e_entry, + e_phoff, + e_shoff, + e_flags, + e_ehsize, + e_phentsize, + e_phnum, + e_shentsize, + e_shnum, + e_shstrndx, + ) = read("HHIPPPIHHHHHH", 16) + if e_type != 1: # relocatable + return [] + assert e_shoff != 0 + assert e_shnum != 0 # don't support > 0xFF00 sections + assert e_shstrndx != 0 + + @dataclass + class Section: + sh_name: int + sh_type: int + sh_flags: int + sh_addr: int + sh_offset: int + sh_size: int + sh_link: int + sh_info: int + sh_addralign: int + sh_entsize: int + + sections = [ + Section(*read("IIPPPPIIPP", e_shoff + i * e_shentsize)) for i in range(e_shnum) + ] + shstr = sections[e_shstrndx] + sec_name_offs = [shstr.sh_offset + s.sh_name for s in sections] + sec_names = [data[offset : data.index(b"\0", offset)] for offset in sec_name_offs] + + symtab_sections = [i for i in range(e_shnum) if sections[i].sh_type == SHT_SYMTAB] + assert len(symtab_sections) == 1 + symtab = sections[symtab_sections[0]] + + text_sections = [i for i in range(e_shnum) if sec_names[i] == b".text"] + assert len(text_sections) == 1 + text_section = text_sections[0] + + ret: List[Tuple[int, int, str]] = [] + for s in sections: + if s.sh_type == SHT_REL or s.sh_type == SHT_RELA: + if s.sh_info == text_section: + # Skip .text -> .text references + continue + sec_name = sec_names[s.sh_info].decode("latin1") + sec_base = sections[s.sh_info].sh_offset + for i in range(0, s.sh_size, s.sh_entsize): + if s.sh_type == SHT_REL: + r_offset, r_info = read("PP", s.sh_offset + i) + else: + r_offset, r_info, r_addend = read("PPP", s.sh_offset + i) + + if is_32bit: + r_sym = r_info >> 8 + r_type = r_info & 0xFF + sym_offset = symtab.sh_offset + symtab.sh_entsize * r_sym + st_name, st_value, st_size, st_info, st_other, st_shndx = read( + "IIIBBH", sym_offset + ) + else: + r_sym = r_info >> 32 + r_type = r_info & 0xFFFFFFFF + sym_offset = symtab.sh_offset + symtab.sh_entsize * r_sym + st_name, st_info, st_other, st_shndx, st_value, st_size = read( + "IBBHQQ", sym_offset + ) + if st_shndx == text_section: + if s.sh_type == SHT_REL: + if e_machine == 8 and r_type == 2: # R_MIPS_32 + (r_addend,) = read("I", sec_base + r_offset) + else: + continue + text_offset = (st_value + r_addend) & 0xFFFFFFFF + ret.append((text_offset, r_offset, sec_name)) + return ret + + +def dump_elf( + start: str, + end: Optional[str], + diff_elf_symbol: str, + config: Config, + project: ProjectSettings, +) -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: + if not project.baseimg or not project.myimg: fail("Missing myimg/baseimg in config.") - if base_shift: + if config.base_shift: fail("--base-shift not compatible with -e") - start_addr = eval_int(args.start, "Start address must be an integer expression.") + start_addr = eval_int(start, "Start address must be an integer expression.") - if args.end is not None: - end_addr = eval_int(args.end, "End address must be an integer expression.") + if end is not None: + end_addr = eval_int(end, "End address must be an integer expression.") else: - end_addr = start_addr + MAX_FUNCTION_SIZE_BYTES + end_addr = start_addr + config.max_function_size_bytes flags1 = [ f"--start-address={start_addr}", @@ -512,219 +1248,382 @@ def dump_elf() -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: ] flags2 = [ - f"--disassemble={args.diff_elf_symbol}", + f"--disassemble={diff_elf_symbol}", ] objdump_flags = ["-drz", "-j", ".text"] return ( - myimg, - (objdump_flags + flags1, baseimg, None), - (objdump_flags + flags2 + maybe_get_objdump_source_flags(), myimg, None), + project.myimg, + (objdump_flags + flags1, project.baseimg, None), + ( + objdump_flags + flags2 + maybe_get_objdump_source_flags(config), + project.myimg, + None, + ), ) -def dump_objfile() -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: - if base_shift: +def dump_objfile( + start: str, end: Optional[str], config: Config, project: ProjectSettings +) -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: + if config.base_shift: fail("--base-shift not compatible with -o") - if args.end is not None: + if end is not None: fail("end address not supported together with -o") - if args.start.startswith("0"): + if start.startswith("0"): fail("numerical start address not supported with -o; pass a function name") - objfile, _ = search_map_file(args.start) + objfile, _ = search_map_file(start, project) if not objfile: fail("Not able to find .o file for function.") - if args.make: - run_make(objfile) + if config.make: + run_make(objfile, project) if not os.path.isfile(objfile): fail(f"Not able to find .o file for function: {objfile} is not a file.") - refobjfile = "ver/us/expected/" + os.path.relpath(objfile) + refobjfile = "ver/us/expected/" + objfile if not os.path.isfile(refobjfile): fail(f'Please ensure an OK .o file exists at "{refobjfile}".') - objdump_flags = ["-Drz"] + objdump_flags = ["-drz", "-j", ".text"] return ( objfile, - (objdump_flags, refobjfile, args.start), - (objdump_flags + maybe_get_objdump_source_flags(), objfile, args.start), + (objdump_flags, refobjfile, start), + (objdump_flags + maybe_get_objdump_source_flags(config), objfile, start), ) -def dump_binary() -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: - if not baseimg or not myimg: +def dump_binary( + start: str, end: Optional[str], config: Config, project: ProjectSettings +) -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: + if not project.baseimg or not project.myimg: fail("Missing myimg/baseimg in config.") - if args.make: - run_make(myimg) - start_addr = maybe_eval_int(args.start) + if config.make: + run_make(project.myimg, project) + start_addr = maybe_eval_int(start) if start_addr is None: - _, start_addr = search_map_file(args.start) + _, start_addr = search_map_file(start, project) if start_addr is None: fail("Not able to find function in map file.") - if args.end is not None: - end_addr = eval_int(args.end, "End address must be an integer expression.") + if end is not None: + end_addr = eval_int(end, "End address must be an integer expression.") else: - end_addr = start_addr + MAX_FUNCTION_SIZE_BYTES - objdump_flags = ["-Dz", "-bbinary", "-EB"] + end_addr = start_addr + config.max_function_size_bytes + objdump_flags = ["-Dz", "-bbinary"] + ["-EB" if config.arch.big_endian else "-EL"] flags1 = [ - f"--start-address={start_addr + base_shift}", - f"--stop-address={end_addr + base_shift}", + f"--start-address={start_addr + config.base_shift}", + f"--stop-address={end_addr + config.base_shift}", ] flags2 = [f"--start-address={start_addr}", f"--stop-address={end_addr}"] return ( - myimg, - (objdump_flags + flags1, baseimg, None), - (objdump_flags + flags2, myimg, None), + project.myimg, + (objdump_flags + flags1, project.baseimg, None), + (objdump_flags + flags2, project.myimg, None), ) -def ansi_ljust(s: str, width: int) -> str: - """Like s.ljust(width), but accounting for ANSI colors.""" - needed: int = width - ansiwrap.ansilen(s) - if needed > 0: - return s + " " * needed - else: - return s +class DifferenceNormalizer: + def __init__(self, config: Config) -> None: + self.config = config + + def normalize(self, mnemonic: str, row: str) -> str: + """This should be called exactly once for each line.""" + arch = self.config.arch + row = self._normalize_arch_specific(mnemonic, row) + if self.config.ignore_large_imms and mnemonic not in arch.branch_instructions: + row = re.sub(self.config.arch.re_large_imm, "", row) + return row + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + return row -if arch == "mips": - re_int = re.compile(r"[0-9]+") - re_comment = re.compile(r"<.*?>") - re_reg = re.compile( - r"\$?\b(a[0-3]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|k[01]|fp|ra|zero)\b" - ) - re_sprel = re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)") - re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") - re_imm = re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)") - forbidden = set(string.ascii_letters + "_") - arch_flags = ["-m", "mips:4300"] - branch_likely_instructions = { - "beql", - "bnel", - "beqzl", - "bnezl", - "bgezl", - "bgtzl", - "blezl", - "bltzl", - "bc1tl", - "bc1fl", - } - branch_instructions = branch_likely_instructions.union( - { - "b", - "beq", - "bne", - "beqz", - "bnez", - "bgez", - "bgtz", - "blez", - "bltz", - "bc1t", - "bc1f", - } - ) - instructions_with_address_immediates = branch_instructions.union({"jal", "j"}) -elif arch == "aarch64": - re_int = re.compile(r"[0-9]+") - re_comment = re.compile(r"(<.*?>|//.*$)") - # GPRs and FP registers: X0-X30, W0-W30, [DSHQ]0..31 - # The zero registers and SP should not be in this list. - re_reg = re.compile(r"\$?\b([dshq][12]?[0-9]|[dshq]3[01]|[xw][12]?[0-9]|[xw]30)\b") - re_sprel = re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b") - re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") - re_imm = re.compile(r"(?|//.*$)") - re_reg = re.compile(r"\$?\b([rf][0-9]+)\b") - re_sprel = re.compile(r"(?<=,)(-?[0-9]+|-?0x[0-9a-f]+)\(r1\)") - re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") - re_imm = re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(r1)|[^@]*@(ha|h|lo)") - arch_flags = [] - forbidden = set(string.ascii_letters + "_") - branch_likely_instructions = set() - branch_instructions = { +class DifferenceNormalizerAArch64(DifferenceNormalizer): + def __init__(self, config: Config) -> None: + super().__init__(config) + self._adrp_pair_registers: Set[str] = set() + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + if self.config.ignore_addr_diffs: + row = self._normalize_adrp_differences(mnemonic, row) + row = self._normalize_bl(mnemonic, row) + return row + + def _normalize_bl(self, mnemonic: str, row: str) -> str: + if mnemonic != "bl": + return row + + row, _ = split_off_address(row) + return row + "" + + def _normalize_adrp_differences(self, mnemonic: str, row: str) -> str: + """Identifies ADRP + LDR/ADD pairs that are used to access the GOT and + suppresses any immediate differences. + + Whenever an ADRP is seen, the destination register is added to the set of registers + that are part of an ADRP + LDR/ADD pair. Registers are removed from the set as soon + as they are used for an LDR or ADD instruction which completes the pair. + + This method is somewhat crude but should manage to detect most such pairs. + """ + row_parts = row.split("\t", 1) + if mnemonic == "adrp": + self._adrp_pair_registers.add(row_parts[1].strip().split(",")[0]) + row, _ = split_off_address(row) + return row + "" + elif mnemonic == "ldr": + for reg in self._adrp_pair_registers: + # ldr xxx, [reg] + # ldr xxx, [reg, ] + if f", [{reg}" in row_parts[1]: + self._adrp_pair_registers.remove(reg) + return normalize_imms(row, AARCH64_SETTINGS) + elif mnemonic == "add": + for reg in self._adrp_pair_registers: + # add reg, reg, + if row_parts[1].startswith(f"{reg}, {reg}, "): + self._adrp_pair_registers.remove(reg) + return normalize_imms(row, AARCH64_SETTINGS) + + return row + + +class DifferenceNormalizerARM32(DifferenceNormalizer): + def __init__(self, config: Config) -> None: + super().__init__(config) + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + if self.config.ignore_addr_diffs: + row = self._normalize_bl(mnemonic, row) + return row + + def _normalize_bl(self, mnemonic: str, row: str) -> str: + if mnemonic != "bl": + return row + + row, _ = split_off_address(row) + return row + "" + + +@dataclass +class ArchSettings: + name: str + re_int: Pattern[str] + re_comment: Pattern[str] + re_reg: Pattern[str] + re_sprel: Pattern[str] + re_large_imm: Pattern[str] + re_imm: Pattern[str] + branch_instructions: Set[str] + instructions_with_address_immediates: Set[str] + forbidden: Set[str] = field(default_factory=lambda: set(string.ascii_letters + "_")) + arch_flags: List[str] = field(default_factory=list) + branch_likely_instructions: Set[str] = field(default_factory=set) + difference_normalizer: Type[DifferenceNormalizer] = DifferenceNormalizer + big_endian: Optional[bool] = True + + +MIPS_BRANCH_LIKELY_INSTRUCTIONS = { + "beql", + "bnel", + "beqzl", + "bnezl", + "bgezl", + "bgtzl", + "blezl", + "bltzl", + "bc1tl", + "bc1fl", +} +MIPS_BRANCH_INSTRUCTIONS = MIPS_BRANCH_LIKELY_INSTRUCTIONS.union( + { "b", "beq", - "beq+", - "beq-", "bne", - "bne+", - "bne-", - "blt", - "blt+", - "blt-", - "ble", - "ble+", - "ble-", - "bdnz", - "bdnz+", - "bdnz-", - "bge", - "bge+", - "bge-", - "bgt", - "bgt+", - "bgt-", + "beqz", + "bnez", + "bgez", + "bgtz", + "blez", + "bltz", + "bc1t", + "bc1f", } - instructions_with_address_immediates = branch_instructions.union({"bl"}) -else: - fail(f"Unknown architecture: {arch}") +) + +ARM32_PREFIXES = {"b", "bl"} +ARM32_CONDS = { + "", + "eq", + "ne", + "cs", + "cc", + "mi", + "pl", + "vs", + "vc", + "hi", + "ls", + "ge", + "lt", + "gt", + "le", + "al", +} +ARM32_SUFFIXES = {"", ".n", ".w"} +ARM32_BRANCH_INSTRUCTIONS = { + f"{prefix}{cond}{suffix}" + for prefix in ARM32_PREFIXES + for cond in ARM32_CONDS + for suffix in ARM32_SUFFIXES +} + +AARCH64_BRANCH_INSTRUCTIONS = { + "bl", + "b", + "b.eq", + "b.ne", + "b.cs", + "b.hs", + "b.cc", + "b.lo", + "b.mi", + "b.pl", + "b.vs", + "b.vc", + "b.hi", + "b.ls", + "b.ge", + "b.lt", + "b.gt", + "b.le", + "cbz", + "cbnz", + "tbz", + "tbnz", +} + +PPC_BRANCH_INSTRUCTIONS = { + "b", + "beq", + "beq+", + "beq-", + "bne", + "bne+", + "bne-", + "blt", + "blt+", + "blt-", + "ble", + "ble+", + "ble-", + "bdnz", + "bdnz+", + "bdnz-", + "bge", + "bge+", + "bge-", + "bgt", + "bgt+", + "bgt-", +} + +MIPS_SETTINGS = ArchSettings( + name="mips", + re_int=re.compile(r"[0-9]+"), + re_comment=re.compile(r"<.*?>"), + re_reg=re.compile( + r"\$?\b(a[0-3]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|k[01]|fp|ra|zero)\b" + ), + re_sprel=re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)"), + arch_flags=["-m", "mips:4300"], + branch_likely_instructions=MIPS_BRANCH_LIKELY_INSTRUCTIONS, + branch_instructions=MIPS_BRANCH_INSTRUCTIONS, + instructions_with_address_immediates=MIPS_BRANCH_INSTRUCTIONS.union({"jal", "j"}), +) + +MIPSEL_SETTINGS = replace(MIPS_SETTINGS, name="mipsel", big_endian=False) + +ARM32_SETTINGS = ArchSettings( + name="arm32", + re_int=re.compile(r"[0-9]+"), + re_comment=re.compile(r"(<.*?>|//.*$)"), + # Includes: + # - General purpose registers: r0..13 + # - Frame pointer registers: lr (r14), pc (r15) + # - VFP/NEON registers: s0..31, d0..31, q0..15, fpscr, fpexc, fpsid + # SP should not be in this list. + re_reg=re.compile( + r"\$?\b([rq][0-9]|[rq]1[0-5]|pc|lr|[ds][12]?[0-9]|[ds]3[01]|fp(scr|exc|sid))\b" + ), + re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(?|//.*$)"), + # GPRs and FP registers: X0-X30, W0-W30, [DSHQ]0..31 + # The zero registers and SP should not be in this list. + re_reg=re.compile(r"\$?\b([dshq][12]?[0-9]|[dshq]3[01]|[xw][12]?[0-9]|[xw]30)\b"), + re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(?|//.*$)"), + re_reg=re.compile(r"\$?\b([rf][0-9]+)\b"), + re_sprel=re.compile(r"(?<=,)(-?[0-9]+|-?0x[0-9a-f]+)\(r1\)"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(r1)|[^@]*@(ha|h|lo)"), + branch_instructions=PPC_BRANCH_INSTRUCTIONS, + instructions_with_address_immediates=PPC_BRANCH_INSTRUCTIONS.union({"bl"}), +) + +ARCH_SETTINGS = [ + MIPS_SETTINGS, + MIPSEL_SETTINGS, + ARM32_SETTINGS, + AARCH64_SETTINGS, + PPC_SETTINGS, +] -def hexify_int(row: str, pat: Match[str]) -> str: +def hexify_int(row: str, pat: Match[str], arch: ArchSettings) -> str: full = pat.group(0) if len(full) <= 1: # leave one-digit ints alone return full start, end = pat.span() - if start and row[start - 1] in forbidden: + if start and row[start - 1] in arch.forbidden: return full - if end < len(row) and row[end] in forbidden: + if end < len(row) and row[end] in arch.forbidden: return full return hex(int(full)) def parse_relocated_line(line: str) -> Tuple[str, str, str]: - try: - ind2 = line.rindex(",") - except ValueError: - try: - ind2 = line.rindex("\t") - except ValueError: - ind2 = line.rindex(" ") + for c in ",\t ": + if c in line: + ind2 = line.rindex(c) + break + else: + raise Exception(f"failed to parse relocated line: {line}") before = line[: ind2 + 1] after = line[ind2 + 1 :] ind2 = after.find("(") @@ -737,7 +1636,7 @@ def parse_relocated_line(line: str) -> Tuple[str, str, str]: return before, imm, after -def process_mips_reloc(row: str, prev: str) -> str: +def process_mips_reloc(row: str, prev: str, arch: ArchSettings) -> str: before, imm, after = parse_relocated_line(prev) repl = row.split()[-1] if imm != "0": @@ -748,7 +1647,10 @@ def process_mips_reloc(row: str, prev: str) -> str: # TODO: handle unambiguous cases where all addends for a symbol are the # same, or show "+???". mnemonic = prev.split()[0] - if mnemonic in instructions_with_address_immediates and not imm.startswith("0x"): + if ( + mnemonic in arch.instructions_with_address_immediates + and not imm.startswith("0x") + ): imm = "0x" + imm repl += "+" + imm if int(imm, 0) > 0 else imm if "R_MIPS_LO16" in row: @@ -771,7 +1673,9 @@ def process_mips_reloc(row: str, prev: str) -> str: def process_ppc_reloc(row: str, prev: str) -> str: - assert any(r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"]), f"unknown relocation type '{row}' for line '{prev}'" + assert any( + r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] + ), f"unknown relocation type '{row}' for line '{prev}'" before, imm, after = parse_relocated_line(prev) repl = row.split()[-1] if "R_PPC_REL24" in row: @@ -791,7 +1695,7 @@ def process_ppc_reloc(row: str, prev: str) -> str: if "+0x7" in repl: # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) # computations and are unimportant in a diff setting. - if int(repl.split("+")[1],16) > 0x70000000: + if int(repl.split("+")[1], 16) > 0x70000000: repl = repl.split("+")[0] elif "R_PPC_EMB_SDA21" in row: # small data area @@ -799,6 +1703,12 @@ def process_ppc_reloc(row: str, prev: str) -> str: return before + repl + after +def process_arm_reloc(row: str, prev: str, arch: ArchSettings) -> str: + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + return before + repl + after + + def pad_mnemonic(line: str) -> str: if "\t" not in line: return line @@ -806,127 +1716,99 @@ def pad_mnemonic(line: str) -> str: return f"{mn:<7s} {args}" -class Line(NamedTuple): +@dataclass +class Line: mnemonic: str diff_row: str original: str normalized_original: str - line_num: str - branch_target: Optional[str] - source_lines: List[str] - comment: Optional[str] + scorable_line: str + line_num: Optional[int] = None + branch_target: Optional[int] = None + source_filename: Optional[str] = None + source_line_num: Optional[int] = None + source_lines: List[str] = field(default_factory=list) + comment: Optional[str] = None -class DifferenceNormalizer: - def normalize(self, mnemonic: str, row: str) -> str: - """This should be called exactly once for each line.""" - row = self._normalize_arch_specific(mnemonic, row) - if args.ignore_large_imms: - row = re.sub(re_large_imm, "", row) - return row - - def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: - return row - - -class DifferenceNormalizerAArch64(DifferenceNormalizer): - def __init__(self) -> None: - super().__init__() - self._adrp_pair_registers: Set[str] = set() - - def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: - if args.ignore_addr_diffs: - row = self._normalize_adrp_differences(mnemonic, row) - row = self._normalize_bl(mnemonic, row) - return row - - def _normalize_bl(self, mnemonic: str, row: str) -> str: - if mnemonic != "bl": - return row - - row, _ = split_off_branch(row) - return row - - def _normalize_adrp_differences(self, mnemonic: str, row: str) -> str: - """Identifies ADRP + LDR/ADD pairs that are used to access the GOT and - suppresses any immediate differences. - - Whenever an ADRP is seen, the destination register is added to the set of registers - that are part of an ADRP + LDR/ADD pair. Registers are removed from the set as soon - as they are used for an LDR or ADD instruction which completes the pair. - - This method is somewhat crude but should manage to detect most such pairs. - """ - row_parts = row.split("\t", 1) - if mnemonic == "adrp": - self._adrp_pair_registers.add(row_parts[1].strip().split(",")[0]) - row, _ = split_off_branch(row) - elif mnemonic == "ldr": - for reg in self._adrp_pair_registers: - # ldr xxx, [reg] - # ldr xxx, [reg, ] - if f", [{reg}" in row_parts[1]: - self._adrp_pair_registers.remove(reg) - return normalize_imms(row) - elif mnemonic == "add": - for reg in self._adrp_pair_registers: - # add reg, reg, - if row_parts[1].startswith(f"{reg}, {reg}, "): - self._adrp_pair_registers.remove(reg) - return normalize_imms(row) - - return row - - -def make_difference_normalizer() -> DifferenceNormalizer: - if arch == "aarch64": - return DifferenceNormalizerAArch64() - return DifferenceNormalizer() - - -def process(lines: List[str]) -> List[Line]: - normalizer = make_difference_normalizer() +def process(dump: str, config: Config) -> List[Line]: + arch = config.arch + normalizer = arch.difference_normalizer(config) skip_next = False source_lines = [] - if not args.diff_obj: - lines = lines[7:] - if lines and not lines[-1]: - lines.pop() + source_filename = None + source_line_num = None + i = 0 + num_instr = 0 + data_refs: Dict[int, Dict[str, List[int]]] = defaultdict(lambda: defaultdict(list)) output: List[Line] = [] stop_after_delay_slot = False - for row in lines: - if args.diff_obj and (">:" in row or not row): + lines = dump.split("\n") + while i < len(lines): + row = lines[i] + i += 1 + + if not row: continue - if args.source and (row and row[0] != " "): + if re.match(r"^[0-9a-f]+ <.*>:$", row): + continue + + if row.startswith("DATAREF"): + parts = row.split(" ", 3) + text_offset = int(parts[1]) + from_offset = int(parts[2]) + from_section = parts[3] + data_refs[text_offset][from_section].append(from_offset) + continue + + if config.diff_obj and num_instr >= config.max_function_size_lines: + output.append( + Line( + mnemonic="...", + diff_row="...", + original="...", + normalized_original="...", + scorable_line="...", + ) + ) + break + + if not re.match(r"^ +[0-9a-f]+:\t", row): + # This regex is conservative, and assumes the file path does not contain "weird" + # characters like colons, tabs, or angle brackets. + if re.match( + r"^[^ \t<>:][^\t<>:]*:[0-9]+( \(discriminator [0-9]+\))?$", row + ): + source_filename, _, tail = row.rpartition(":") + source_line_num = int(tail.partition(" ")[0]) source_lines.append(row) continue - if "R_AARCH64_" in row: - # TODO: handle relocation - continue - - if "R_MIPS_" in row: - # N.B. Don't transform the diff rows, they already ignore immediates - # if output[-1].diff_row != "": - # output[-1] = output[-1].replace(diff_row=process_mips_reloc(row, output[-1].row_with_imm)) - new_original = process_mips_reloc(row, output[-1].original) - output[-1] = output[-1]._replace(original=new_original) - continue - - if "R_PPC_" in row: - new_original = process_ppc_reloc(row, output[-1].original) - output[-1] = output[-1]._replace(original=new_original) - continue - - m_comment = re.search(re_comment, row) + m_comment = re.search(arch.re_comment, row) comment = m_comment[0] if m_comment else None - row = re.sub(re_comment, "", row) + row = re.sub(arch.re_comment, "", row) row = row.rstrip() tabs = row.split("\t") row = "\t".join(tabs[2:]) - line_num = tabs[0].strip() + line_num = eval_line_num(tabs[0].strip()) + + if line_num in data_refs: + refs = data_refs[line_num] + ref_str = "; ".join( + section_name + "+" + ",".join(hex(off) for off in offs) + for section_name, offs in refs.items() + ) + output.append( + Line( + mnemonic="", + diff_row="", + original=ref_str, + normalized_original=ref_str, + scorable_line="", + ) + ) if "\t" in row: row_parts = row.split("\t", 1) @@ -935,32 +1817,61 @@ def process(lines: List[str]) -> List[Line]: row_parts = [part.lstrip() for part in row.split(" ", 1)] mnemonic = row_parts[0].strip() - if mnemonic not in instructions_with_address_immediates: - row = re.sub(re_int, lambda m: hexify_int(row, m), row) + if mnemonic not in arch.instructions_with_address_immediates: + row = re.sub(arch.re_int, lambda m: hexify_int(row, m, arch), row) + + # Let 'original' be 'row' with relocations applied, while we continue + # transforming 'row' into a coarser version that ignores registers and + # immediates. original = row + + while i < len(lines): + reloc_row = lines[i] + if "R_AARCH64_" in reloc_row: + # TODO: handle relocation + pass + elif "R_MIPS_" in reloc_row: + original = process_mips_reloc(reloc_row, original, arch) + elif "R_PPC_" in reloc_row: + original = process_ppc_reloc(reloc_row, original) + elif "R_ARM_" in reloc_row: + original = process_arm_reloc(reloc_row, original, arch) + else: + break + i += 1 + normalized_original = normalizer.normalize(mnemonic, original) + + scorable_line = normalized_original + if not config.score_stack_differences: + scorable_line = re.sub(arch.re_sprel, "addr(sp)", scorable_line) + if mnemonic in arch.branch_instructions: + # Replace the final argument with "" + scorable_line = re.sub(r"[^, \t]+$", "", scorable_line) + if skip_next: skip_next = False row = "" mnemonic = "" - if mnemonic in branch_likely_instructions: + scorable_line = "" + if mnemonic in arch.branch_likely_instructions: skip_next = True - row = re.sub(re_reg, "", row) - row = re.sub(re_sprel, "addr(sp)", row) + + row = re.sub(arch.re_reg, "", row) + row = re.sub(arch.re_sprel, "addr(sp)", row) row_with_imm = row - if mnemonic in instructions_with_address_immediates: + if mnemonic in arch.instructions_with_address_immediates: row = row.strip() - row, _ = split_off_branch(row) + row, _ = split_off_address(row) row += "" else: - row = normalize_imms(row) + row = normalize_imms(row, arch) branch_target = None - if mnemonic in branch_instructions: - target = row_parts[1].strip().split(",")[-1] - if mnemonic in branch_likely_instructions: - target = hex(int(target, 16) - 4)[2:] - branch_target = target.strip() + if mnemonic in arch.branch_instructions: + branch_target = int(row_parts[1].strip().split(",")[-1], 16) + if mnemonic in arch.branch_likely_instructions: + branch_target -= 4 output.append( Line( @@ -968,15 +1879,19 @@ def process(lines: List[str]) -> List[Line]: diff_row=row, original=original, normalized_original=normalized_original, + scorable_line=scorable_line, line_num=line_num, branch_target=branch_target, + source_filename=source_filename, + source_line_num=source_line_num, source_lines=source_lines, comment=comment, ) ) + num_instr += 1 source_lines = [] - if args.stop_jrra and mnemonic == "jr" and row_parts[1].strip() == "ra": + if config.stop_jrra and mnemonic == "jr" and row_parts[1].strip() == "ra": stop_after_delay_slot = True elif stop_after_delay_slot: break @@ -984,65 +1899,27 @@ def process(lines: List[str]) -> List[Line]: return output -def format_single_line_diff(line1: str, line2: str, column_width: int) -> str: - return ansi_ljust(line1, column_width) + line2 +def normalize_imms(row: str, arch: ArchSettings) -> str: + return re.sub(arch.re_imm, "", row) -class SymbolColorer: - symbol_colors: Dict[str, str] - - def __init__(self, base_index: int) -> None: - self.color_index = base_index - self.symbol_colors = {} - - def color_symbol(self, s: str, t: Optional[str] = None) -> str: - try: - color = self.symbol_colors[s] - except: - color = COLOR_ROTATION[self.color_index % len(COLOR_ROTATION)] - self.color_index += 1 - self.symbol_colors[s] = color - t = t or s - return f"{color}{t}{Fore.RESET}" +def normalize_stack(row: str, arch: ArchSettings) -> str: + return re.sub(arch.re_sprel, "addr(sp)", row) -def normalize_imms(row: str) -> str: - return re.sub(re_imm, "", row) +def imm_matches_everything(row: str, arch: ArchSettings) -> bool: + # (this should probably be arch-specific) + return "(." in row -def normalize_stack(row: str) -> str: - return re.sub(re_sprel, "addr(sp)", row) - - -def split_off_branch(line: str) -> Tuple[str, str]: +def split_off_address(line: str) -> Tuple[str, str]: + """Split e.g. 'beqz $r0,1f0' into 'beqz $r0,' and '1f0'.""" parts = line.split(",") if len(parts) < 2: parts = line.split(None, 1) off = len(line) - len(parts[-1]) return line[:off], line[off:] -ColorFunction = Callable[[str], str] - -def color_fields(pat: Pattern[str], out1: str, out2: str, color1: ColorFunction, color2: Optional[ColorFunction]=None) -> Tuple[str, str]: - diffs = [of.group() != nf.group() for (of, nf) in zip(pat.finditer(out1), pat.finditer(out2))] - - it = iter(diffs) - def maybe_color(color: ColorFunction, s: str) -> str: - return color(s) if next(it, False) else f"{Style.RESET_ALL}{s}" - - out1 = pat.sub(lambda m: maybe_color(color1, m.group()), out1) - it = iter(diffs) - out2 = pat.sub(lambda m: maybe_color(color2 or color1, m.group()), out2) - - return out1, out2 - - -def color_branch_imms(br1: str, br2: str) -> Tuple[str, str]: - if br1 != br2: - br1 = f"{Fore.LIGHTBLUE_EX}{br1}{Style.RESET_ALL}" - br2 = f"{Fore.LIGHTBLUE_EX}{br2}{Style.RESET_ALL}" - return br1, br2 - def diff_sequences_difflib( seq1: List[str], seq2: List[str] @@ -1052,10 +1929,10 @@ def diff_sequences_difflib( def diff_sequences( - seq1: List[str], seq2: List[str] + seq1: List[str], seq2: List[str], algorithm: str ) -> List[Tuple[str, int, int, int, int]]: if ( - args.algorithm != "levenshtein" + algorithm != "levenshtein" or len(seq1) * len(seq2) > 4 * 10 ** 8 or len(seq1) + len(seq2) >= 0x110000 ): @@ -1077,17 +1954,22 @@ def diff_sequences( rem1 = remap(seq1) rem2 = remap(seq2) - return Levenshtein.opcodes(rem1, rem2) # type: ignore + import Levenshtein + + ret: List[Tuple[str, int, int, int, int]] = Levenshtein.opcodes(rem1, rem2) + return ret def diff_lines( lines1: List[Line], lines2: List[Line], + algorithm: str, ) -> List[Tuple[Optional[Line], Optional[Line]]]: ret = [] for (tag, i1, i2, j1, j2) in diff_sequences( [line.mnemonic for line in lines1], [line.mnemonic for line in lines2], + algorithm, ): for line1, line2 in itertools.zip_longest(lines1[i1:i2], lines2[j1:j2]): if tag == "replace": @@ -1104,41 +1986,154 @@ def diff_lines( return ret +def score_diff_lines( + lines: List[Tuple[Optional[Line], Optional[Line]]], config: Config +) -> int: + # This logic is copied from `scorer.py` from the decomp permuter project + # https://github.com/simonlindholm/decomp-permuter/blob/main/src/scorer.py + score = 0 + deletions = [] + insertions = [] + + def lo_hi_match(old: str, new: str) -> bool: + # TODO: Make this arch-independent, like `imm_matches_everything()` + old_lo = old.find("%lo") + old_hi = old.find("%hi") + new_lo = new.find("%lo") + new_hi = new.find("%hi") + + if old_lo != -1 and new_lo != -1: + old_idx = old_lo + new_idx = new_lo + elif old_hi != -1 and new_hi != -1: + old_idx = old_hi + new_idx = new_hi + else: + return False + + if old[:old_idx] != new[:new_idx]: + return False + + old_inner = old[old_idx + 4 : -1] + new_inner = new[new_idx + 4 : -1] + return old_inner.startswith(".") or new_inner.startswith(".") + + def diff_sameline(old: str, new: str) -> None: + nonlocal score + if old == new: + return + + if lo_hi_match(old, new): + return + + ignore_last_field = False + if config.score_stack_differences: + oldsp = re.search(config.arch.re_sprel, old) + newsp = re.search(config.arch.re_sprel, new) + if oldsp and newsp: + oldrel = int(oldsp.group(1) or "0", 0) + newrel = int(newsp.group(1) or "0", 0) + score += abs(oldrel - newrel) * config.penalty_stackdiff + ignore_last_field = True + + # Probably regalloc difference, or signed vs unsigned + + # Compare each field in order + newfields, oldfields = new.split(","), old.split(",") + if ignore_last_field: + newfields = newfields[:-1] + oldfields = oldfields[:-1] + for nf, of in zip(newfields, oldfields): + if nf != of: + score += config.penalty_regalloc + # Penalize any extra fields + score += abs(len(newfields) - len(oldfields)) * config.penalty_regalloc + + def diff_insert(line: str) -> None: + # Reordering or totally different codegen. + # Defer this until later when we can tell. + insertions.append(line) + + def diff_delete(line: str) -> None: + deletions.append(line) + + # Find the end of the last long streak of matching mnemonics, if it looks + # like the objdump output was truncated. This is used to skip scoring + # misaligned lines at the end of the diff. + last_mismatch = -1 + max_index = None + lines_were_truncated = False + for index, (line1, line2) in enumerate(lines): + if (line1 and line1.original == "...") or (line2 and line2.original == "..."): + lines_were_truncated = True + if line1 and line2 and line1.mnemonic == line2.mnemonic: + if index - last_mismatch >= 50: + max_index = index + else: + last_mismatch = index + if not lines_were_truncated: + max_index = None + + for index, (line1, line2) in enumerate(lines): + if max_index is not None and index > max_index: + break + if line1 and line2 and line1.mnemonic == line2.mnemonic: + diff_sameline(line1.scorable_line, line2.scorable_line) + else: + if line1: + diff_delete(line1.scorable_line) + if line2: + diff_insert(line2.scorable_line) + + insertions_co = Counter(insertions) + deletions_co = Counter(deletions) + for item in insertions_co + deletions_co: + ins = insertions_co[item] + dels = deletions_co[item] + common = min(ins, dels) + score += ( + (ins - common) * config.penalty_insertion + + (dels - common) * config.penalty_deletion + + config.penalty_reordering * common + ) + + return score + + +@dataclass(frozen=True) class OutputLine: - base: Optional[str] - fmt2: str + base: Optional[Text] = field(compare=False) + fmt2: Text = field(compare=False) key2: Optional[str] - - def __init__(self, base: Optional[str], fmt2: str, key2: Optional[str]) -> None: - self.base = base - self.fmt2 = fmt2 - self.key2 = key2 - - def __eq__(self, other: object) -> bool: - if not isinstance(other, OutputLine): - return NotImplemented - return self.key2 == other.key2 - - def __hash__(self) -> int: - return hash(self.key2) + boring: bool = field(compare=False) + is_data_ref: bool = field(compare=False) + line1: Optional[Line] = field(compare=False) + line2: Optional[Line] = field(compare=False) -def do_diff(basedump: str, mydump: str) -> List[OutputLine]: +@dataclass(frozen=True) +class Diff: + lines: List[OutputLine] + score: int + + +def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: + if config.show_source: + import cxxfilt + arch = config.arch + fmt = config.formatter output: List[OutputLine] = [] - lines1 = process(basedump.split("\n")) - lines2 = process(mydump.split("\n")) + sc1 = symbol_formatter("base-reg", 0) + sc2 = symbol_formatter("my-reg", 0) + sc3 = symbol_formatter("base-stack", 4) + sc4 = symbol_formatter("my-stack", 4) + sc5 = symbol_formatter("base-branch", 0) + sc6 = symbol_formatter("my-branch", 0) + bts1: Set[int] = set() + bts2: Set[int] = set() - sc1 = SymbolColorer(0) - sc2 = SymbolColorer(0) - sc3 = SymbolColorer(4) - sc4 = SymbolColorer(4) - sc5 = SymbolColorer(0) - sc6 = SymbolColorer(0) - bts1: Set[str] = set() - bts2: Set[str] = set() - - if args.show_branches: + if config.show_branches: for (lines, btset, sc) in [ (lines1, bts1, sc5), (lines2, bts2, sc6), @@ -1146,136 +2141,250 @@ def do_diff(basedump: str, mydump: str) -> List[OutputLine]: for line in lines: bt = line.branch_target if bt is not None: - btset.add(bt + ":") - sc.color_symbol(bt + ":") + btset.add(bt) + sc(str(bt)) - for (line1, line2) in diff_lines(lines1, lines2): - line_color1 = line_color2 = sym_color = Fore.RESET + diffed_lines = diff_lines(lines1, lines2, config.algorithm) + score = score_diff_lines(diffed_lines, config) + + line_num_base = -1 + line_num_offset = 0 + line_num_2to1 = {} + for (line1, line2) in diffed_lines: + if line1 is not None and line1.line_num is not None: + line_num_base = line1.line_num + line_num_offset = 0 + else: + line_num_offset += 1 + if line2 is not None and line2.line_num is not None: + line_num_2to1[line2.line_num] = (line_num_base, line_num_offset) + + for (line1, line2) in diffed_lines: + line_color1 = line_color2 = sym_color = BasicFormat.NONE line_prefix = " " + is_data_ref = False + out1 = Text() if not line1 else Text(pad_mnemonic(line1.original)) + out2 = Text() if not line2 else Text(pad_mnemonic(line2.original)) if line1 and line2 and line1.diff_row == line2.diff_row: - if line1.normalized_original == line2.normalized_original: - out1 = line1.original - out2 = line2.original + if line1.diff_row == "": + if line1.normalized_original != line2.normalized_original: + line_prefix = "i" + sym_color = BasicFormat.DIFF_CHANGE + out1 = out1.reformat(sym_color) + out2 = out2.reformat(sym_color) + is_data_ref = True + elif ( + line1.normalized_original == line2.normalized_original + and line2.branch_target is None + ): + # Fast path: no coloring needed. We don't include branch instructions + # in this case because we need to check that their targets line up in + # the diff, and don't just happen to have the are the same address + # by accident. + pass elif line1.diff_row == "": - out1 = f"{Style.BRIGHT}{Fore.LIGHTBLACK_EX}{line1.original}" - out2 = f"{Style.BRIGHT}{Fore.LIGHTBLACK_EX}{line2.original}" + # Don't draw attention to differing branch-likely delay slots: they + # typically mirror the branch destination - 1 so the real difference + # is elsewhere. Still, do mark them as different to avoid confusion. + # No need to consider branches because delay slots can't branch. + out1 = out1.reformat(BasicFormat.DELAY_SLOT) + out2 = out2.reformat(BasicFormat.DELAY_SLOT) else: mnemonic = line1.original.split()[0] - out1, out2 = line1.original, line2.original - branch1 = branch2 = "" - if mnemonic in instructions_with_address_immediates: - out1, branch1 = split_off_branch(line1.original) - out2, branch2 = split_off_branch(line2.original) - branchless1 = out1 - branchless2 = out2 - out1, out2 = color_fields(re_imm, out1, out2, lambda s: f"{Fore.LIGHTBLUE_EX}{s}{Style.RESET_ALL}") + branchless1, address1 = out1.plain(), "" + branchless2, address2 = out2.plain(), "" + if mnemonic in arch.instructions_with_address_immediates: + branchless1, address1 = split_off_address(branchless1) + branchless2, address2 = split_off_address(branchless2) - same_relative_target = False - if line1.branch_target is not None and line2.branch_target is not None: - relative_target1 = eval_line_num(line1.branch_target) - eval_line_num(line1.line_num) - relative_target2 = eval_line_num(line2.branch_target) - eval_line_num(line2.line_num) - same_relative_target = relative_target1 == relative_target2 + out1 = Text(branchless1) + out2 = Text(branchless2) + out1, out2 = format_fields( + arch.re_imm, out1, out2, lambda _: BasicFormat.IMMEDIATE + ) - if not same_relative_target: - branch1, branch2 = color_branch_imms(branch1, branch2) + if line2.branch_target is not None: + target = line2.branch_target + line2_target = line_num_2to1.get(line2.branch_target) + if line2_target is None: + # If the target is outside the disassembly, extrapolate. + # This only matters near the bottom. + assert line2.line_num is not None + line2_line = line_num_2to1[line2.line_num] + line2_target = (line2_line[0] + (target - line2.line_num), 0) - out1 += branch1 - out2 += branch2 - if normalize_imms(branchless1) == normalize_imms(branchless2): - if not same_relative_target: - # only imms differences - sym_color = Fore.LIGHTBLUE_EX + # Set the key for three-way diffing to a normalized version. + norm2, norm_branch2 = split_off_address(line2.normalized_original) + if norm_branch2 != "": + line2.normalized_original = norm2 + str(line2_target) + same_target = line2_target == (line1.branch_target, 0) + else: + # Do a naive comparison for non-branches (e.g. function calls). + same_target = address1 == address2 + + if normalize_imms(branchless1, arch) == normalize_imms( + branchless2, arch + ): + if imm_matches_everything(branchless2, arch): + # ignore differences due to %lo(.rodata + ...) vs symbol + out1 = out1.reformat(BasicFormat.NONE) + out2 = out2.reformat(BasicFormat.NONE) + elif line2.branch_target is not None and same_target: + # same-target branch, don't color + pass + else: + # must have an imm difference (or else we would have hit the + # fast path) + sym_color = BasicFormat.IMMEDIATE line_prefix = "i" else: - out1, out2 = color_fields(re_sprel, out1, out2, sc3.color_symbol, sc4.color_symbol) - if normalize_stack(branchless1) == normalize_stack(branchless2): + out1, out2 = format_fields(arch.re_sprel, out1, out2, sc3, sc4) + if normalize_stack(branchless1, arch) == normalize_stack( + branchless2, arch + ): # only stack differences (luckily stack and imm # differences can't be combined in MIPS, so we # don't have to think about that case) - sym_color = Fore.YELLOW + sym_color = BasicFormat.STACK line_prefix = "s" else: - # regs differences and maybe imms as well - out1, out2 = color_fields(re_reg, out1, out2, sc1.color_symbol, sc2.color_symbol) - line_color1 = line_color2 = sym_color = Fore.YELLOW + # reg differences and maybe imm as well + out1, out2 = format_fields(arch.re_reg, out1, out2, sc1, sc2) + line_color1 = line_color2 = sym_color = BasicFormat.REGISTER line_prefix = "r" + + if same_target: + address_imm_fmt = BasicFormat.NONE + else: + address_imm_fmt = BasicFormat.IMMEDIATE + out1 += Text(address1, address_imm_fmt) + out2 += Text(address2, address_imm_fmt) elif line1 and line2: line_prefix = "|" - line_color1 = Fore.LIGHTBLUE_EX - line_color2 = Fore.LIGHTBLUE_EX - sym_color = Fore.LIGHTBLUE_EX - out1 = line1.original - out2 = line2.original + line_color1 = line_color2 = sym_color = BasicFormat.DIFF_CHANGE + out1 = out1.reformat(line_color1) + out2 = out2.reformat(line_color2) elif line1: line_prefix = "<" - line_color1 = sym_color = Fore.RED - out1 = line1.original - out2 = "" + line_color1 = sym_color = BasicFormat.DIFF_REMOVE + out1 = out1.reformat(line_color1) + out2 = Text() elif line2: line_prefix = ">" - line_color2 = sym_color = Fore.GREEN - out1 = "" - out2 = line2.original + line_color2 = sym_color = BasicFormat.DIFF_ADD + out1 = Text() + out2 = out2.reformat(line_color2) - if args.source and line2 and line2.comment: + if config.show_source and line2 and line2.comment: out2 += f" {line2.comment}" def format_part( - out: str, + out: Text, line: Optional[Line], - line_color: str, - btset: Set[str], - sc: SymbolColorer, - ) -> Optional[str]: + line_color: Format, + btset: Set[int], + sc: FormatFunction, + ) -> Optional[Text]: if line is None: return None - in_arrow = " " - out_arrow = "" - if args.show_branches: + if line.line_num is None: + return out + in_arrow = Text(" ") + out_arrow = Text() + if config.show_branches: if line.line_num in btset: - in_arrow = sc.color_symbol(line.line_num, "~>") + line_color + in_arrow = Text("~>", sc(str(line.line_num))) if line.branch_target is not None: - out_arrow = " " + sc.color_symbol(line.branch_target + ":", "~>") - out = pad_mnemonic(out) - return f"{line_color}{line.line_num} {in_arrow} {out}{Style.RESET_ALL}{out_arrow}" + out_arrow = " " + Text("~>", sc(str(line.branch_target))) + formatted_line_num = Text(hex(line.line_num)[2:] + ":", line_color) + return formatted_line_num + " " + in_arrow + " " + out + out_arrow part1 = format_part(out1, line1, line_color1, bts1, sc5) part2 = format_part(out2, line2, line_color2, bts2, sc6) - key2 = line2.original if line2 else None - mid = f"{sym_color}{line_prefix}" - - if line2: + if config.show_source and line2: for source_line in line2.source_lines: - color = Style.DIM - # File names and function names - if source_line and source_line[0] != "│": - color += Style.BRIGHT - # Function names - if source_line.endswith("():"): - # Underline. Colorama does not provide this feature, unfortunately. - color += "\u001b[4m" + line_format = BasicFormat.SOURCE_OTHER + if config.source_old_binutils: + if source_line and re.fullmatch(".*\.c(?:pp)?:\d+", source_line): + line_format = BasicFormat.SOURCE_FILENAME + elif source_line and source_line.endswith("():"): + line_format = BasicFormat.SOURCE_FUNCTION try: source_line = cxxfilt.demangle( source_line[:-3], external_only=False ) except: pass + else: + # File names and function names + if source_line and source_line[0] != "│": + line_format = BasicFormat.SOURCE_FILENAME + # Function names + if source_line.endswith("():"): + line_format = BasicFormat.SOURCE_FUNCTION + try: + source_line = cxxfilt.demangle( + source_line[:-3], external_only=False + ) + except: + pass + padding = " " * 7 if config.show_line_numbers else " " * 2 output.append( OutputLine( - None, - f" {color}{source_line}{Style.RESET_ALL}", - source_line, + base=None, + fmt2=padding + Text(source_line, line_format), + key2=source_line, + boring=True, + is_data_ref=False, + line1=None, + line2=None, ) ) - fmt2 = mid + " " + (part2 or "") - output.append(OutputLine(part1, fmt2, key2)) + key2 = line2.normalized_original if line2 else None + boring = False + if line_prefix == " ": + boring = True + elif config.compress and config.compress.same_instr and line_prefix in "irs": + boring = True - return output + if config.show_line_numbers: + if line2 and line2.source_line_num is not None: + num_color = ( + BasicFormat.SOURCE_LINE_NUM + if sym_color == BasicFormat.NONE + else sym_color + ) + num2 = Text(f"{line2.source_line_num:5}", num_color) + else: + num2 = Text(" " * 5) + else: + num2 = Text() + + fmt2 = Text(line_prefix, sym_color) + num2 + " " + (part2 or Text()) + + output.append( + OutputLine( + base=part1, + fmt2=fmt2, + key2=key2, + boring=boring, + is_data_ref=is_data_ref, + line1=line1, + line2=line2, + ) + ) + + return Diff(lines=output, score=score) -def chunk_diff(diff: List[OutputLine]) -> List[Union[List[OutputLine], OutputLine]]: +def chunk_diff_lines( + diff: List[OutputLine], +) -> List[Union[List[OutputLine], OutputLine]]: + """Chunk a diff into an alternating list like A B A B ... A, where: + * A is a List[OutputLine] of insertions, + * B is a single non-insertion OutputLine, with .base != None.""" cur_right: List[OutputLine] = [] chunks: List[Union[List[OutputLine], OutputLine]] = [] for output_line in diff: @@ -1289,69 +2398,121 @@ def chunk_diff(diff: List[OutputLine]) -> List[Union[List[OutputLine], OutputLin return chunks -def format_diff( - old_diff: List[OutputLine], new_diff: List[OutputLine] -) -> Tuple[str, List[str]]: - old_chunks = chunk_diff(old_diff) - new_chunks = chunk_diff(new_diff) - output: List[Tuple[str, OutputLine, OutputLine]] = [] - assert len(old_chunks) == len(new_chunks), "same target" - empty = OutputLine("", "", None) - for old_chunk, new_chunk in zip(old_chunks, new_chunks): - if isinstance(old_chunk, list): - assert isinstance(new_chunk, list) - if not old_chunk and not new_chunk: - # Most of the time lines sync up without insertions/deletions, - # and there's no interdiffing to be done. - continue - differ = difflib.SequenceMatcher(a=old_chunk, b=new_chunk, autojunk=False) - for (tag, i1, i2, j1, j2) in differ.get_opcodes(): - if tag in ["equal", "replace"]: - for i, j in zip(range(i1, i2), range(j1, j2)): - output.append(("", old_chunk[i], new_chunk[j])) - if tag in ["insert", "replace"]: - for j in range(j1 + i2 - i1, j2): - output.append(("", empty, new_chunk[j])) - if tag in ["delete", "replace"]: - for i in range(i1 + j2 - j1, i2): - output.append(("", old_chunk[i], empty)) - else: - assert isinstance(new_chunk, OutputLine) - assert new_chunk.base - # old_chunk.base and new_chunk.base have the same text since - # both diffs are based on the same target, but they might - # differ in color. Use the new version. - output.append((new_chunk.base, old_chunk, new_chunk)) +def compress_matching( + li: List[Tuple[OutputLine, ...]], context: int +) -> List[Tuple[OutputLine, ...]]: + ret: List[Tuple[OutputLine, ...]] = [] + matching_streak: List[Tuple[OutputLine, ...]] = [] + context = max(context, 0) - # TODO: status line, with e.g. approximate permuter score? - width = args.column_width - if args.threeway: - header_line = "TARGET".ljust(width) + " CURRENT".ljust(width) + " PREVIOUS" + def flush_matching() -> None: + if len(matching_streak) <= 2 * context + 1: + ret.extend(matching_streak) + else: + ret.extend(matching_streak[:context]) + skipped = len(matching_streak) - 2 * context + filler = OutputLine( + base=Text(f"<{skipped} lines>", BasicFormat.SOURCE_OTHER), + fmt2=Text(), + key2=None, + boring=False, + is_data_ref=False, + line1=None, + line2=None, + ) + columns = len(matching_streak[0]) + ret.append(tuple([filler] * columns)) + if context > 0: + ret.extend(matching_streak[-context:]) + matching_streak.clear() + + for line in li: + if line[0].boring: + matching_streak.append(line) + else: + flush_matching() + ret.append(line) + + flush_matching() + return ret + + +def align_diffs( + old_diff: Diff, new_diff: Diff, config: Config +) -> Tuple[TableMetadata, List[Tuple[OutputLine, ...]]]: + meta: TableMetadata + diff_lines: List[Tuple[OutputLine, ...]] + padding = " " * 7 if config.show_line_numbers else " " * 2 + + if config.threeway: + meta = TableMetadata( + headers=( + Text("TARGET"), + Text(f"{padding}CURRENT ({new_diff.score})"), + Text(f"{padding}PREVIOUS ({old_diff.score})"), + ), + current_score=new_diff.score, + previous_score=old_diff.score, + ) + old_chunks = chunk_diff_lines(old_diff.lines) + new_chunks = chunk_diff_lines(new_diff.lines) + diff_lines = [] + empty = OutputLine(Text(), Text(), None, True, False, None, None) + assert len(old_chunks) == len(new_chunks), "same target" + for old_chunk, new_chunk in zip(old_chunks, new_chunks): + if isinstance(old_chunk, list): + assert isinstance(new_chunk, list) + if not old_chunk and not new_chunk: + # Most of the time lines sync up without insertions/deletions, + # and there's no interdiffing to be done. + continue + differ = difflib.SequenceMatcher( + a=old_chunk, b=new_chunk, autojunk=False + ) + for (tag, i1, i2, j1, j2) in differ.get_opcodes(): + if tag in ["equal", "replace"]: + for i, j in zip(range(i1, i2), range(j1, j2)): + diff_lines.append((empty, new_chunk[j], old_chunk[i])) + if tag in ["insert", "replace"]: + for j in range(j1 + i2 - i1, j2): + diff_lines.append((empty, new_chunk[j], empty)) + if tag in ["delete", "replace"]: + for i in range(i1 + j2 - j1, i2): + diff_lines.append((empty, empty, old_chunk[i])) + else: + assert isinstance(new_chunk, OutputLine) + # old_chunk.base and new_chunk.base have the same text since + # both diffs are based on the same target, but they might + # differ in color. Use the new version. + diff_lines.append((new_chunk, new_chunk, old_chunk)) diff_lines = [ - ansi_ljust(base, width) - + ansi_ljust(new.fmt2, width) - + (old.fmt2 or "-" if old != new else "") - for (base, old, new) in output + (base, new, old if old != new else empty) for base, new, old in diff_lines ] else: - header_line = "" - diff_lines = [ - ansi_ljust(base, width) + new.fmt2 - for (base, old, new) in output - if base or new.key2 is not None - ] - return header_line, diff_lines + meta = TableMetadata( + headers=( + Text("TARGET"), + Text(f"{padding}CURRENT ({new_diff.score})"), + ), + current_score=new_diff.score, + previous_score=None, + ) + diff_lines = [(line, line) for line in new_diff.lines] + if config.compress: + diff_lines = compress_matching(diff_lines, config.compress.context) + return meta, diff_lines def debounced_fs_watch( targets: List[str], outq: "queue.Queue[Optional[float]]", - debounce_delay: float, + config: Config, + project: ProjectSettings, ) -> None: - import watchdog.events # type: ignore - import watchdog.observers # type: ignore + import watchdog.events + import watchdog.observers - class WatchEventHandler(watchdog.events.FileSystemEventHandler): # type: ignore + class WatchEventHandler(watchdog.events.FileSystemEventHandler): def __init__( self, queue: "queue.Queue[float]", file_targets: List[str] ) -> None: @@ -1368,10 +2529,10 @@ def debounced_fs_watch( def should_notify(self, path: str) -> bool: for target in self.file_targets: - if path == target: + if os.path.normpath(path) == target: return True - if args.make and any( - path.endswith(suffix) for suffix in FS_WATCH_EXTENSIONS + if config.make and any( + path.endswith(suffix) for suffix in project.source_extensions ): return True return False @@ -1390,7 +2551,7 @@ def debounced_fs_watch( if os.path.isdir(target): observer.schedule(event_handler, target, recursive=True) else: - file_targets.append(target) + file_targets.append(os.path.normpath(target)) target = os.path.dirname(target) or "." if target not in observed: observed.add(target) @@ -1400,7 +2561,7 @@ def debounced_fs_watch( t = listenq.get() more = True while more: - delay = t + debounce_delay - time.time() + delay = t + DEBOUNCE_DELAY - time.time() if delay > 0: time.sleep(delay) # consume entire queue @@ -1420,38 +2581,45 @@ def debounced_fs_watch( class Display: basedump: str mydump: str + last_refresh_key: object + config: Config emsg: Optional[str] - last_diff_output: Optional[List[OutputLine]] - pending_update: Optional[Tuple[str, bool]] + last_diff_output: Optional[Diff] + pending_update: Optional[str] ready_queue: "queue.Queue[None]" watch_queue: "queue.Queue[Optional[float]]" less_proc: "Optional[subprocess.Popen[bytes]]" - def __init__(self, basedump: str, mydump: str) -> None: - self.basedump = basedump + def __init__(self, basedump: str, mydump: str, config: Config) -> None: + self.config = config + self.base_lines = process(basedump, config) self.mydump = mydump self.emsg = None + self.last_refresh_key = None self.last_diff_output = None - def run_less(self) -> "Tuple[subprocess.Popen[bytes], subprocess.Popen[bytes]]": + def run_diff(self) -> Tuple[str, object]: if self.emsg is not None: - output = self.emsg - else: - diff_output = do_diff(self.basedump, self.mydump) - last_diff_output = self.last_diff_output or diff_output - if args.threeway != "base" or not self.last_diff_output: - self.last_diff_output = diff_output - header, diff_lines = format_diff(last_diff_output, diff_output) - header_lines = [header] if header else [] - output = "\n".join(header_lines + diff_lines[args.skip_lines :]) + return (self.emsg, self.emsg) - # Windows Terminal does not handle buffers properly - # Janky hack to clear its scrollback buffer until it's fixed - clear_proc = subprocess.Popen( - ["echo", "-en", "\"\e[3J\""] + my_lines = process(self.mydump, self.config) + diff_output = do_diff(self.base_lines, my_lines, self.config) + last_diff_output = self.last_diff_output or diff_output + if self.config.threeway != "base" or not self.last_diff_output: + self.last_diff_output = diff_output + + meta, diff_lines = align_diffs(last_diff_output, diff_output, self.config) + diff_lines = diff_lines[self.config.skip_lines :] + output = self.config.formatter.table(meta, diff_lines) + refresh_key = ( + [[col.key2 for col in x[1:]] for x in diff_lines], + diff_output.score, ) - clear_proc.wait() + return (output, refresh_key) + def run_less( + self, output: str + ) -> "Tuple[subprocess.Popen[bytes], subprocess.Popen[bytes]]": # Pipe the output through 'tail' and only then to less, to ensure the # write call doesn't block. ('tail' has to buffer all its input before # it starts writing.) This also means we don't have to deal with pipe @@ -1468,7 +2636,8 @@ class Display: return (buffer_proc, less_proc) def run_sync(self) -> None: - proca, procb = self.run_less() + output, _ = self.run_diff() + proca, procb = self.run_less(output) procb.wait() proca.wait() @@ -1476,12 +2645,14 @@ class Display: self.watch_queue = watch_queue self.ready_queue = queue.Queue() self.pending_update = None - dthread = threading.Thread(target=self.display_thread) + output, refresh_key = self.run_diff() + self.last_refresh_key = refresh_key + dthread = threading.Thread(target=self.display_thread, args=(output,)) dthread.start() self.ready_queue.get() - def display_thread(self) -> None: - proca, procb = self.run_less() + def display_thread(self, initial_output: str) -> None: + proca, procb = self.run_less(initial_output) self.less_proc = procb self.ready_queue.put(None) while True: @@ -1493,14 +2664,9 @@ class Display: os.system("tput reset") if ret != 0 and self.pending_update is not None: # killed by program with the intent to refresh - msg, error = self.pending_update + output = self.pending_update self.pending_update = None - if not error: - self.mydump = msg - self.emsg = None - else: - self.emsg = msg - proca, procb = self.run_less() + proca, procb = self.run_less(output) self.less_proc = procb self.ready_queue.put(None) else: @@ -1518,7 +2684,17 @@ class Display: if not error and not self.emsg and text == self.mydump: self.progress("Unchanged. ") return - self.pending_update = (text, error) + if not error: + self.mydump = text + self.emsg = None + else: + self.emsg = text + output, refresh_key = self.run_diff() + if refresh_key == self.last_refresh_key: + self.progress("Unchanged. ") + return + self.last_refresh_key = refresh_key + self.pending_update = output if not self.less_proc: return self.less_proc.kill() @@ -1532,15 +2708,50 @@ class Display: def main() -> None: + args = parser.parse_args() + + # Apply project-specific configuration. + settings: Dict[str, Any] = {} + diff_settings.apply(settings, args) # type: ignore + project = create_project_settings(settings) + + try: + config = create_config(args, project) + except ValueError as e: + fail(str(e)) + + if config.algorithm == "levenshtein": + try: + import Levenshtein + except ModuleNotFoundError as e: + fail(MISSING_PREREQUISITES.format(e.name)) + + if config.show_source: + try: + import cxxfilt + except ModuleNotFoundError as e: + fail(MISSING_PREREQUISITES.format(e.name)) + + if config.threeway and not args.watch: + fail("Threeway diffing requires -w.") + if args.diff_elf_symbol: - make_target, basecmd, mycmd = dump_elf() - elif args.diff_obj: - make_target, basecmd, mycmd = dump_objfile() + make_target, basecmd, mycmd = dump_elf( + args.start, args.end, args.diff_elf_symbol, config, project + ) + elif config.diff_obj: + make_target, basecmd, mycmd = dump_objfile( + args.start, args.end, config, project + ) else: - make_target, basecmd, mycmd = dump_binary() + make_target, basecmd, mycmd = dump_binary(args.start, args.end, config, project) + + map_build_target_fn = getattr(diff_settings, "map_build_target", None) + if map_build_target_fn: + make_target = map_build_target_fn(make_target=make_target) if args.write_asm is not None: - mydump = run_objdump(mycmd) + mydump = run_objdump(mycmd, config, project) with open(args.write_asm, "w") as f: f.write(mydump) print(f"Wrote assembly to {args.write_asm}.") @@ -1550,13 +2761,15 @@ def main() -> None: with open(args.base_asm) as f: basedump = f.read() else: - basedump = run_objdump(basecmd) + basedump = run_objdump(basecmd, config, project) - mydump = run_objdump(mycmd) + mydump = run_objdump(mycmd, config, project) - display = Display(basedump, mydump) + display = Display(basedump, mydump, config) - if not args.watch: + if args.no_pager or args.format in ("html", "json"): + print(display.run_diff()[0]) + elif not args.watch: display.run_sync() else: if not args.make: @@ -1573,13 +2786,13 @@ def main() -> None: ) if watch_sources_for_target_fn: watch_sources = watch_sources_for_target_fn(make_target) - watch_sources = watch_sources or source_directories + watch_sources = watch_sources or project.source_directories if not watch_sources: fail("Missing source_directories config, don't know what to watch.") else: watch_sources = [make_target] q: "queue.Queue[Optional[float]]" = queue.Queue() - debounced_fs_watch(watch_sources, q, DEBOUNCE_DELAY) + debounced_fs_watch(watch_sources, q, config, project) display.run_async(q) last_build = 0.0 try: @@ -1592,7 +2805,7 @@ def main() -> None: last_build = time.time() if args.make: display.progress("Building...") - ret = run_make_capture_output(make_target) + ret = run_make_capture_output(make_target, project) if ret.returncode != 0: display.update( ret.stderr.decode("utf-8-sig", "replace") @@ -1600,10 +2813,11 @@ def main() -> None: error=True, ) continue - mydump = run_objdump(mycmd) + mydump = run_objdump(mycmd, config, project) display.update(mydump, error=False) except KeyboardInterrupt: display.terminate() -main() +if __name__ == "__main__": + main() diff --git a/src/effects.c b/src/effects.c index e55c13e488..3da62a7686 100644 --- a/src/effects.c +++ b/src/effects.c @@ -322,16 +322,12 @@ void clear_effect_data(void) { void func_80059D48(void) { } -// The third loop is doing some negative reference bs -#ifdef NON_MATCHING void update_effects(void) { - EffectGraphics* effectGraphics; - s32 i; - if (!(gOverrideFlags & 0xC00)) { - for (i = 0; i < ARRAY_COUNT(gEffectGraphicsData); i++) { - effectGraphics = &gEffectGraphicsData[i]; + EffectGraphics* effectGraphics; + s32 i; + for (i = 0, effectGraphics = gEffectGraphicsData; i < ARRAY_COUNT(gEffectGraphicsData); i++, effectGraphics++) { if (effectGraphics->flags & FX_GRAPHICS_ENABLED) { if (!(effectGraphics->flags & FX_GRAPHICS_2)) { effectGraphics->flags |= FX_GRAPHICS_2; @@ -360,17 +356,15 @@ void update_effects(void) { } } - for (i = 0; i < ARRAY_COUNT(gEffectGraphicsData); i++) { - effectGraphics = &gEffectGraphicsData[i]; - + for (i = 0, effectGraphics = gEffectGraphicsData; i < ARRAY_COUNT(gEffectGraphicsData); i++, effectGraphics++) { if (effectGraphics->flags & FX_GRAPHICS_ENABLED) { if (effectGraphics->flags & FX_GRAPHICS_2) { if (effectGraphics->freeDelay != 0) { effectGraphics->freeDelay--; } else { - if (effectGraphics->data[0] != NULL) { + if (effectGraphics->data != NULL) { general_heap_free(effectGraphics->data); - effectGraphics->data[0] = NULL; + effectGraphics->data = NULL; } effectGraphics->flags = FX_GRAPHICS_DISABLED; osUnmapTLB(i); @@ -380,9 +374,6 @@ void update_effects(void) { } } } -#else -INCLUDE_ASM(s32, "341d0", update_effects); -#endif s32 render_effects_world(void) { s32 i; diff --git a/src/effects/effect_6.c b/src/effects/effect_6.c index ca06606ac4..c3479fe38f 100644 --- a/src/effects/effect_6.c +++ b/src/effects/effect_6.c @@ -27,9 +27,8 @@ void fx_6_update(EffectInstance* effect); void fx_6_render(EffectInstance* effect); void fx_6_appendGfx(EffectInstance* effect); -static s32 D_E000CC10[] = { 0x09002B20, 0x09002B40, 0x09002B60, 0x09002B60, 0x09002B60 }; - -static s32 D_E000CC24[] = { 0x09002780, 0x09002868, 0x09002950, 0x09002A38, 0x09002A38 }; +static s32 sDlists[] = { 0x09002B20, 0x09002B40, 0x09002B60, 0x09002B60, 0x09002B60 }; +static s32 sDlists2[] = { 0x09002780, 0x09002868, 0x09002950, 0x09002A38, 0x09002A38 }; static s8 D_E000CC38[] = { 0x00, 0x01, 0x02, 0x1A, 0x03, 0x1B, 0x04, 0x1C, 0x05, 0x15, 0x35, 0x46, 0x46, 0x46, 0xFF, 0x00 }; @@ -38,14 +37,14 @@ static s8 D_E000CC48[] = { 0x00, 0x01, 0x02, 0x1A, 0x03, 0x1B, 0x04, 0x1C, 0x05, 0x67, 0xFF, 0x00, 0x00, 0x00 }; static s8 D_E000CC5C[] = { 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x01, 0x09, 0x11, 0x19, 0x21, 0x29, 0x31, - 0x39, 0x02, 0x02, 0x0A, 0x0A, 0x12, 0x12, 0x1A, 0x1A, 0x22, 0x22, 0x2A, 0x2A, 0x32, 0x32, 0x3A, - 0x3A, 0x03, 0x03, 0x0B, 0x0B, 0x13, 0x13, 0x1B, 0x1B, 0x23, 0x23, 0x2B, 0x2B, 0x33, 0x33, 0x3B, - 0x3B, 0x04, 0x04, 0x0C, 0x0C, 0x14, 0x14, 0x1C, 0x1C, 0x24, 0x24, 0x2C, 0x2C, 0x34, 0x34, 0x3C, - 0x3C, 0x05, 0x05, 0x0D, 0x0D, 0x15, 0x15, 0x1D, 0x1D, 0x25, 0x25, 0x2D, 0x2D, 0x35, 0x35, 0x3D, - 0x3D, 0x06, 0x06, 0x06, 0x0E, 0x0E, 0x0E, 0x16, 0x16, 0x16, 0x1E, 0x1E, 0x1E, 0x26, 0x26, 0x26, - 0x2E, 0x2E, 0x2E, 0x36, 0x36, 0x36, 0x3E, 0x3E, 0x3E, 0x47, 0x47, 0x47, 0x4F, 0x4F, 0x4F, 0x57, - 0x57, 0x57, 0x5F, 0x5F, 0x5F, 0x67, 0x67, 0x67, 0x6F, 0x6F, 0x6F, 0x77, 0x77, 0x77, 0x7F, 0x7F, - 0x7F, 0xFF, 0x00, 0x00, 0x00 }; + 0x39, 0x02, 0x02, 0x0A, 0x0A, 0x12, 0x12, 0x1A, 0x1A, 0x22, 0x22, 0x2A, 0x2A, 0x32, 0x32, + 0x3A, 0x3A, 0x03, 0x03, 0x0B, 0x0B, 0x13, 0x13, 0x1B, 0x1B, 0x23, 0x23, 0x2B, 0x2B, 0x33, + 0x33, 0x3B, 0x3B, 0x04, 0x04, 0x0C, 0x0C, 0x14, 0x14, 0x1C, 0x1C, 0x24, 0x24, 0x2C, 0x2C, + 0x34, 0x34, 0x3C, 0x3C, 0x05, 0x05, 0x0D, 0x0D, 0x15, 0x15, 0x1D, 0x1D, 0x25, 0x25, 0x2D, + 0x2D, 0x35, 0x35, 0x3D, 0x3D, 0x06, 0x06, 0x06, 0x0E, 0x0E, 0x0E, 0x16, 0x16, 0x16, 0x1E, + 0x1E, 0x1E, 0x26, 0x26, 0x26, 0x2E, 0x2E, 0x2E, 0x36, 0x36, 0x36, 0x3E, 0x3E, 0x3E, 0x47, + 0x47, 0x47, 0x4F, 0x4F, 0x4F, 0x57, 0x57, 0x57, 0x5F, 0x5F, 0x5F, 0x67, 0x67, 0x67, 0x6F, + 0x6F, 0x6F, 0x77, 0x77, 0x77, 0x7F, 0x7F, 0x7F, 0xFF, 0x00, 0x00, 0x00 }; static s8 D_E000CCE0[] = { 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31, 0x02, 0x0A, 0x12, 0x1A, 0x22, 0x2A, 0x32, 0x3A, 0x03, 0x0B, 0x13, 0x1B, 0x23, 0x2B, 0x33, 0x3B, 0x04, 0x0C, 0x14, 0x1C, 0x24, 0x2C, @@ -55,8 +54,17 @@ static s8 D_E000CCE0[] = { 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31, 0x02, static s8* D_E000CD24[4] = { &D_E000CC38, &D_E000CC48, &D_E000CC5C, &D_E000CCE0 }; -void func_E000C000(Effect6* part); -INCLUDE_ASM(s32, "effects/effect_6", func_E000C000); +void func_E000C000(Effect6* part) { + part->unk_18 += part->unk_1C; + part->unk_14 += part->unk_18; + part->unk_10 += part->unk_14; + part->x += part->unk_10 * part->unk_30; + part->z += part->unk_10 * part->unk_34; + part->unk_28 += part->unk_2C; + part->unk_24 += part->unk_28; + part->unk_20 += part->unk_24; + part->y += part->unk_20; +} void func_E000C094(Effect6* part) { part->y += part->unk_20; @@ -262,4 +270,89 @@ void fx_6_render(EffectInstance* effect) { retTask->renderMode |= RENDER_MODE_2; } -INCLUDE_ASM(s32, "effects/effect_6", fx_6_appendGfx); +void fx_6_appendGfx(EffectInstance* effect) { + Effect6* part = effect->data; + s32 type = part->type; + s32 temp_t0 = part->unk_40; + Matrix4f mtx1; + Matrix4f mtx2; + Matrix4f mtx3; + s32 spD8; + s32 spDC; + s32 temp_lo; + s32 envAlpha; + s32 dlist1; + s32 dlist2; + s32 phi_a0; + s32 temp; + s32 i; + + envAlpha = (temp_t0 & 0x38) * 4; + + dlist1 = sDlists[type]; + dlist2 = sDlists2[type]; + + gDPPipeSync(gMasterGfxPos++); + gSPSegment(gMasterGfxPos++, 0x09, VIRTUAL_TO_PHYSICAL(effect->effect->data)); + gSPDisplayList(gMasterGfxPos++, dlist2); + + spD8 = temp_t0 & 7; + spDC = temp_t0 & 0x40; + + shim_guTranslateF(mtx1, part->x, part->y, part->z); + shim_guRotateF(mtx2, -gCameras[gCurrentCameraID].currentYaw, 0.0f, 1.0f, 0.0f); + shim_guMtxCatF(mtx2, mtx1, mtx3); + shim_guMtxF2L(mtx3, &gDisplayContext->matrixStack[gMatrixListPos]); + + gSPMatrix(gMasterGfxPos++, + &gDisplayContext->matrixStack[gMatrixListPos++], G_MTX_PUSH | G_MTX_MUL | G_MTX_MODELVIEW); + + switch (type) { + case 0: + case 1: + gDPSetPrimColor(gMasterGfxPos++, 0, 0, 230, 222, 222, 110); + gDPSetEnvColor(gMasterGfxPos++, 0, 0, 0, envAlpha); + break; + case 2: + case 3: + case 4: + gDPSetPrimColor(gMasterGfxPos++, 0, 0, 230, 222, 222, 130); + gDPSetEnvColor(gMasterGfxPos++, 0, 0, 0, envAlpha); + break; + } + + if (type == 0) { + phi_a0 = 24; + } else { + phi_a0 = 32; + } + + temp_lo = spD8 * phi_a0; + temp = temp_lo + phi_a0; + + gDPSetTileSize(gMasterGfxPos++, G_TX_RENDERTILE, temp_lo * 4, 0, temp * 4, phi_a0 * 4); + + if (spDC) { + gDPSetTileSize(gMasterGfxPos++, 1, temp * 4, phi_a0 * 4, (temp_lo + (phi_a0 * 2)) * 4, phi_a0 * 8); + } else { + gDPSetTileSize(gMasterGfxPos++, 1, temp * 4, 0, (temp_lo + (phi_a0 * 2)) * 4, phi_a0 * 4); + } + + if (type == 2) { + gSPDisplayList(gMasterGfxPos++, dlist1); + } + + part++; + + for (i = 1; i < effect->numParts; i++, part++) { + shim_guTranslateF(mtx1, part->x, part->y, part->z); + shim_guMtxF2L(mtx1, &gDisplayContext->matrixStack[gMatrixListPos]); + gSPMatrix(gMasterGfxPos++, &gDisplayContext->matrixStack[gMatrixListPos++], + G_MTX_PUSH | G_MTX_MUL | G_MTX_MODELVIEW); + gSPDisplayList(gMasterGfxPos++, dlist1); + gSPPopMatrix(gMasterGfxPos++, G_MTX_MODELVIEW); + } + + gSPPopMatrix(gMasterGfxPos++, G_MTX_MODELVIEW); + gDPPipeSync(gMasterGfxPos++); +} diff --git a/src/heap.c b/src/heap.c index e71cecd72c..8aac065c75 100644 --- a/src/heap.c +++ b/src/heap.c @@ -40,7 +40,7 @@ void* heap_malloc(s32 size) { } s32 heap_free(void* ptr) { - if (gGameStatusPtr->isBattle != 0) { + if (gGameStatusPtr->isBattle) { return _heap_free(&D_803DA800, ptr); } else { return general_heap_free(ptr); diff --git a/src/os/nusys/nuContRmbMgr.c b/src/os/nusys/nuContRmbMgr.c index 632b20fe83..5a71834c0c 100644 --- a/src/os/nusys/nuContRmbMgr.c +++ b/src/os/nusys/nuContRmbMgr.c @@ -1,7 +1,7 @@ #include "common.h" #include "nu/nusys.h" -s32 contRmbControl(NUSiCommonMesg* mesg); +s32 contRmbRetrace(NUSiCommonMesg* mesg); s32 contRmbCheckMesg(NUSiCommonMesg* mesg); s32 contRmbStartMesg(NUSiCommonMesg* mesg); s32 contRmbStopMesg(NUSiCommonMesg* mesg); @@ -10,18 +10,17 @@ s32 contRmbForceStopEndMesg(NUSiCommonMesg* mesg); u32 nuContRmbSearchTime = 300; -s32 D_80093CE4[] = { contRmbControl, contRmbCheckMesg, contRmbStartMesg, contRmbStopMesg, contRmbForceStopMesg, contRmbForceStopEndMesg, NULL}; +s32 D_80093CE4[] = { contRmbRetrace, contRmbCheckMesg, contRmbStartMesg, contRmbStopMesg, contRmbForceStopMesg, contRmbForceStopEndMesg, NULL}; NUCallBackList nuContRmbCallBack = {.next = NULL, .func = D_80093CE4, .majorNo = 0x300, .funcNum = 0}; -s32 _osMotorStop(NUContRmbCtl* rmbCtl, u32 contNo) { +s32 contRmbControl(NUContRmbCtl* rmbCtl, u32 contNo) { s32 ret = 0; u32 cnt; switch (rmbCtl->state) { case NU_CONT_RMB_STATE_STOPPED: break; - case NU_CONT_RMB_STATE_STOPPING: if (rmbCtl->counter != 0) { ret = osMotorStop(&nuContPfs[contNo]); @@ -30,7 +29,6 @@ s32 _osMotorStop(NUContRmbCtl* rmbCtl, u32 contNo) { } rmbCtl->counter--; break; - case NU_CONT_RMB_STATE_RUN: if (rmbCtl->frame > 0) { rmbCtl->counter += rmbCtl->freq; @@ -48,7 +46,6 @@ s32 _osMotorStop(NUContRmbCtl* rmbCtl, u32 contNo) { } rmbCtl->frame--; break; - case NU_CONT_RMB_STATE_FORCESTOP: ret = osMotorInit(&nuSiMesgQ, &nuContPfs[contNo], contNo); if (ret == 0) { @@ -61,7 +58,7 @@ s32 _osMotorStop(NUContRmbCtl* rmbCtl, u32 contNo) { return ret; } -s32 contRmbControl(NUSiCommonMesg* mesg) { +s32 contRmbRetrace(NUSiCommonMesg* mesg) { u32 i; NUContRmbCtl* cont; @@ -70,13 +67,11 @@ s32 contRmbControl(NUSiCommonMesg* mesg) { switch (cont->mode) { case NU_CONT_RMB_MODE_DISABLE: break; - case NU_CONT_RMB_MODE_ENABLE: - if (_osMotorStop(cont, i)) { + if (contRmbControl(cont, i)) { cont->mode = NU_CONT_RMB_MODE_DISABLE; } break; - case NU_CONT_RMB_MODE_AUTORUN: if (cont->autorun == NU_CONT_RMB_AUTO_SEARCH) { if ((cont->counter % nuContRmbSearchTime) == 0) { @@ -87,18 +82,17 @@ s32 contRmbControl(NUSiCommonMesg* mesg) { } cont->counter++; } else { - if (_osMotorStop(cont, i)) { + if (contRmbControl(cont, i)) { cont->counter = i; cont->autorun = NU_CONT_RMB_AUTO_SEARCH; cont->type = NU_CONT_PAK_TYPE_NONE; } } break; - case (NU_CONT_RMB_MODE_ENABLE | NU_CONT_RMB_MODE_PAUSE): case (NU_CONT_RMB_MODE_AUTORUN | NU_CONT_RMB_MODE_PAUSE): if (cont->type == NU_CONT_PAK_TYPE_RUMBLE) { - _osMotorStop(cont, i); + contRmbControl(cont, i); } } } diff --git a/tools/build/configure.py b/tools/build/configure.py index 04a7f58d4f..3533e18dd1 100755 --- a/tools/build/configure.py +++ b/tools/build/configure.py @@ -284,7 +284,7 @@ class Configure: def write_ninja(self, ninja: ninja_syntax.Writer, skip_outputs: Set[str]): import segtypes - import segtypes.n64.data + import segtypes.common.data import segtypes.n64.Yay0 assert self.linker_entries is not None @@ -335,9 +335,9 @@ class Configure: if isinstance(seg, segtypes.n64.header.N64SegHeader): build(entry.object_path, entry.src_paths, "as") - elif isinstance(seg, segtypes.n64.asm.N64SegAsm) or (isinstance(seg, segtypes.n64.data.N64SegData) and not seg.type[0] == "."): + elif isinstance(seg, segtypes.common.asm.CommonSegAsm) or (isinstance(seg, segtypes.common.data.CommonSegData) and not seg.type[0] == "."): build(entry.object_path, entry.src_paths, "as") - elif isinstance(seg, segtypes.n64.c.N64SegC) or (isinstance(seg, segtypes.n64.data.N64SegData) and seg.type[0] == "."): + elif isinstance(seg, segtypes.common.c.CommonSegC) or (isinstance(seg, segtypes.common.data.CommonSegData) and seg.type[0] == "."): cflags = None if isinstance(seg.yaml, dict): cflags = seg.yaml.get("cflags") @@ -367,7 +367,7 @@ class Configure: build(entry.object_path, entry.src_paths, task, variables={"cflags": cflags}) # images embedded inside data aren't linked, but they do need to be built into .inc.c files - if isinstance(seg, segtypes.n64.group.N64SegGroup): + if isinstance(seg, segtypes.common.group.CommonSegGroup): for seg in seg.subsegments: if isinstance(seg, segtypes.n64.img.N64SegImg): flags = "" @@ -397,7 +397,7 @@ class Configure: "img_flags": "", }) build(inc_dir / (seg.name + ".pal.inc.c"), [bin_path], "bin_inc_c") - elif isinstance(seg, segtypes.n64.bin.N64SegBin): + elif isinstance(seg, segtypes.common.bin.CommonSegBin): build(entry.object_path, entry.src_paths, "bin") elif isinstance(seg, segtypes.n64.Yay0.N64SegYay0): compressed_path = entry.object_path.with_suffix("") # remove .o diff --git a/tools/splat/.gitrepo b/tools/splat/.gitrepo index c5e8b472c7..87bc0e0cd5 100644 --- a/tools/splat/.gitrepo +++ b/tools/splat/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/ethteck/splat.git branch = master - commit = 7eb5744b2a3a5e2a4253996f587b7666db597c57 - parent = 31629ad772cbb4cda8df2a96ce9d91b74883f752 + commit = 0efa552c5d3cf866b6325486868714787261673d + parent = 35fa67cd8de20bf6dacdb92f1ac8411d3e5f09cf method = merge cmdver = 0.4.3 diff --git a/tools/splat/CHANGELOG.md b/tools/splat/CHANGELOG.md index 17c7a2562c..f79131e372 100644 --- a/tools/splat/CHANGELOG.md +++ b/tools/splat/CHANGELOG.md @@ -1,5 +1,11 @@ # splat Release Notes +### 0.7.10: WIP PSX support +* WIP PSX support has been added, thanks to @mkst! (https://github.com/ethteck/splat/pull/99) + * Many segments have moved to a "common" package + * Endianness of the input binary is now a configurable option +* Linker hack restored but is now optional and off by default + ### 0.7.9 * Finally removed the dumb linker section alignment hack * Added version number to output on execution diff --git a/tools/splat/segtypes/common/__init__.py b/tools/splat/segtypes/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/splat/segtypes/common/asm.py b/tools/splat/segtypes/common/asm.py new file mode 100644 index 0000000000..fbfcac1576 --- /dev/null +++ b/tools/splat/segtypes/common/asm.py @@ -0,0 +1,34 @@ +from segtypes.common.codesubsegment import CommonSegCodeSubsegment +from typing import Optional +from pathlib import Path + +from util import options + + +class CommonSegAsm(CommonSegCodeSubsegment): + def out_path(self) -> Optional[Path]: + return options.get_asm_path() / self.dir / f"{self.name}.s" + + def scan(self, rom_bytes: bytes): + if self.rom_start != "auto" and self.rom_end != "auto" and self.rom_start != self.rom_end: + self.funcs_text = self.disassemble_code(rom_bytes, options.get("asm_endlabels", False)) + + def get_file_header(self): + return [] + + def split(self, rom_bytes: bytes): + if not self.rom_start == self.rom_end: + out_path = self.out_path() + if out_path: + out_path.parent.mkdir(parents=True, exist_ok=True) + + out_lines = self.get_file_header() + for func in self.funcs_text: + out_lines.extend(self.funcs_text[func][0]) + out_lines.append("") + + self.split_write(out_path, out_lines) + + def split_write(self, out_path, out_lines): + with open(out_path, "w", newline="\n") as f: + f.write("\n".join(out_lines)) diff --git a/tools/splat/segtypes/n64/bin.py b/tools/splat/segtypes/common/bin.py similarity index 88% rename from tools/splat/segtypes/n64/bin.py rename to tools/splat/segtypes/common/bin.py index ba27a0d91b..028310ecc3 100644 --- a/tools/splat/segtypes/n64/bin.py +++ b/tools/splat/segtypes/common/bin.py @@ -1,13 +1,13 @@ from pathlib import Path from typing import Optional -from segtypes.n64.segment import N64Segment +from segtypes.common.segment import CommonSegment from util import options from util import log -class N64SegBin(N64Segment): +class CommonSegBin(CommonSegment): def out_path(self) -> Optional[Path]: return options.get_asset_path() / self.dir / f"{self.name}.bin" - + def split(self, rom_bytes): path = self.out_path() path.parent.mkdir(parents=True, exist_ok=True) diff --git a/tools/splat/segtypes/n64/bss.py b/tools/splat/segtypes/common/bss.py similarity index 86% rename from tools/splat/segtypes/n64/bss.py rename to tools/splat/segtypes/common/bss.py index 2606ce7812..fbd1f89f75 100644 --- a/tools/splat/segtypes/n64/bss.py +++ b/tools/splat/segtypes/common/bss.py @@ -1,7 +1,7 @@ -from segtypes.n64.data import N64SegData +from segtypes.common.data import CommonSegData from util import log -class N64SegBss(N64SegData): +class CommonSegBss(CommonSegData): def get_linker_section(self) -> str: return ".bss" diff --git a/tools/splat/segtypes/n64/c.py b/tools/splat/segtypes/common/c.py similarity index 91% rename from tools/splat/segtypes/n64/c.py rename to tools/splat/segtypes/common/c.py index 0a1f63525f..c2cd6573c9 100644 --- a/tools/splat/segtypes/n64/c.py +++ b/tools/splat/segtypes/common/c.py @@ -1,5 +1,5 @@ -from segtypes.n64.codesubsegment import N64SegCodeSubsegment -from segtypes.n64.group import N64SegGroup +from segtypes.common.codesubsegment import CommonSegCodeSubsegment +from segtypes.common.group import CommonSegGroup from typing import Optional, Set import os import re @@ -8,7 +8,7 @@ from pathlib import Path from util import log, options -class N64SegC(N64SegCodeSubsegment): +class CommonSegC(CommonSegCodeSubsegment): defined_funcs: Set[str] = set() global_asm_funcs: Set[str] = set() @@ -35,20 +35,20 @@ class N64SegC(N64SegCodeSubsegment): return " " else: return s - return re.sub(N64SegC.STRIP_C_COMMENTS_RE, replacer, text) + return re.sub(CommonSegC.STRIP_C_COMMENTS_RE, replacer, text) @staticmethod def get_funcs_defined_in_c(c_file): with open(c_file, "r") as f: - text = N64SegC.strip_c_comments(f.read()) + text = CommonSegC.strip_c_comments(f.read()) - return set(m.group(2) for m in N64SegC.C_FUNC_RE.finditer(text)) + return set(m.group(2) for m in CommonSegC.C_FUNC_RE.finditer(text)) @staticmethod def get_global_asm_funcs(c_file): with open(c_file, "r") as f: - text = N64SegC.strip_c_comments(f.read()) - return set(m.group(3) for m in N64SegC.C_GLOBAL_ASM_RE.finditer(text)) + text = CommonSegC.strip_c_comments(f.read()) + return set(m.group(3) for m in CommonSegC.C_GLOBAL_ASM_RE.finditer(text)) def out_path(self) -> Optional[Path]: return options.get_src_path() / self.dir / f"{self.name}.c" @@ -124,7 +124,7 @@ class N64SegC(N64SegCodeSubsegment): else: out_lines = [] - if self.parent and isinstance(self.parent, N64SegGroup): + if self.parent and isinstance(self.parent, CommonSegGroup): if func in self.parent.rodata_syms: func_rodata = list({s for s in self.parent.rodata_syms[func] if s.disasm_str}) func_rodata.sort(key=lambda s:s.vram_start) diff --git a/tools/splat/segtypes/n64/code.py b/tools/splat/segtypes/common/code.py similarity index 97% rename from tools/splat/segtypes/n64/code.py rename to tools/splat/segtypes/common/code.py index eaf1cf7b03..9c678c3e33 100644 --- a/tools/splat/segtypes/n64/code.py +++ b/tools/splat/segtypes/common/code.py @@ -1,11 +1,11 @@ -from segtypes.n64.group import N64SegGroup +from segtypes.common.group import CommonSegGroup from typing import Optional from segtypes.segment import Segment from util import symbols from util.symbols import Symbol # code group -class N64SegCode(N64SegGroup): +class CommonSegCode(CommonSegGroup): def __init__(self, rom_start, rom_end, type, name, vram_start, extract, given_subalign, given_is_overlay, given_dir, args, yaml): super().__init__(rom_start, rom_end, type, name, vram_start, extract, given_subalign, given_is_overlay, given_dir, args, yaml) @@ -91,4 +91,3 @@ class N64SegCode(N64SegGroup): ret.referenced = True return ret - diff --git a/tools/splat/segtypes/n64/codesubsegment.py b/tools/splat/segtypes/common/codesubsegment.py similarity index 93% rename from tools/splat/segtypes/n64/codesubsegment.py rename to tools/splat/segtypes/common/codesubsegment.py index 5b02129b3a..e0ab03af95 100644 --- a/tools/splat/segtypes/n64/codesubsegment.py +++ b/tools/splat/segtypes/common/codesubsegment.py @@ -1,23 +1,28 @@ from typing import Optional from util import options -from segtypes.n64.code import N64SegCode +from segtypes.common.code import CommonSegCode from collections import OrderedDict import re -from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS64, CS_MODE_BIG_ENDIAN +from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS64, CS_MODE_BIG_ENDIAN, CS_MODE_MIPS32, CS_MODE_LITTLE_ENDIAN from capstone.mips import * from segtypes.segment import Segment # abstract class for c, asm, data, etc -class N64SegCodeSubsegment(Segment): +class CommonSegCodeSubsegment(Segment): double_mnemonics = ["ldc1", "sdc1"] word_mnemonics = ["addiu", "sw", "lw", "jtbl"] float_mnemonics = ["lwc1", "swc1"] short_mnemonics = ["addiu", "lh", "sh", "lhu"] byte_mnemonics = ["lb", "sb", "lbu"] - md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + CS_MODE_BIG_ENDIAN) + if options.get_endianess() == "big": + capstone_mode = CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN + else: + capstone_mode = CS_MODE_MIPS32 | CS_MODE_LITTLE_ENDIAN + + md = Cs(CS_ARCH_MIPS, capstone_mode) md.detail = True md.skipdata = True @@ -40,7 +45,7 @@ class N64SegCodeSubsegment(Segment): return (mnemonic.startswith("b") and not mnemonic.startswith("binsl") and not mnemonic == "break") or mnemonic == "j" def disassemble_code(self, rom_bytes, addsuffix=False): - insns = [insn for insn in N64SegCodeSubsegment.md.disasm(rom_bytes[self.rom_start : self.rom_end], self.vram_start)] + insns = [insn for insn in CommonSegCodeSubsegment.md.disasm(rom_bytes[self.rom_start : self.rom_end], self.vram_start)] funcs = self.process_insns(insns, self.rom_start) @@ -53,8 +58,8 @@ class N64SegCodeSubsegment(Segment): return self.add_labels(funcs, addsuffix) def process_insns(self, insns, rom_addr): - assert(isinstance(self.parent, N64SegCode)) - self.parent: N64SegCode = self.parent + assert(isinstance(self.parent, CommonSegCode)) + self.parent: CommonSegCode = self.parent ret = OrderedDict() @@ -63,6 +68,8 @@ class N64SegCodeSubsegment(Segment): end_func = False labels = [] + big_endian = options.get_endianess() == "big" + # Collect labels for insn in insns: if self.is_branch_insn(insn.mnemonic): @@ -85,7 +92,9 @@ class N64SegCodeSubsegment(Segment): if mnemonic == "move": # Let's get the actual instruction out - opcode = insn.bytes[3] & 0b00111111 + idx = 3 if big_endian else 0 + opcode = insn.bytes[idx] & 0b00111111 + op_str += ", $zero" if opcode == 37: @@ -95,7 +104,7 @@ class N64SegCodeSubsegment(Segment): elif opcode == 33: mnemonic = "addu" else: - print("INVALID INSTRUCTION " + insn) + print("INVALID INSTRUCTION " + str(insn), opcode) elif mnemonic == "jal": jal_addr = int(op_str, 0) jump_func = self.parent.get_symbol(jal_addr, type="func", create=True, reference=True) @@ -115,8 +124,9 @@ class N64SegCodeSubsegment(Segment): label_name = f".L{branch_target[2:].upper()}" op_str = " ".join(op_str_split[:-1] + [label_name]) - elif mnemonic == "mtc0" or mnemonic == "mfc0": - rd = (insn.bytes[2] & 0xF8) >> 3 + elif mnemonic in ["mtc0", "mfc0", "mtc2", "mfc2"]: + idx = 2 if big_endian else 1 + rd = (insn.bytes[idx] & 0xF8) >> 3 op_str = op_str.split(" ")[0] + " $" + str(rd) func.append((insn, mnemonic, op_str, rom_addr)) @@ -322,8 +332,8 @@ class N64SegCodeSubsegment(Segment): if insn[0].mnemonic != "branch" and insn[0].mnemonic.startswith("b") or insn[0].mnemonic.startswith("j"): indent_next = True - - if addsuffix: + + if addsuffix: func_text.append(f"endlabel {sym.name}") ret[func] = (func_text, rom_addr) @@ -373,6 +383,6 @@ class N64SegCodeSubsegment(Segment): def should_scan(self) -> bool: return options.mode_active("code") and self.rom_start != "auto" and self.rom_end != "auto" - + def should_split(self) -> bool: return self.extract and options.mode_active("code") diff --git a/tools/splat/segtypes/n64/data.py b/tools/splat/segtypes/common/data.py similarity index 89% rename from tools/splat/segtypes/n64/data.py rename to tools/splat/segtypes/common/data.py index 62ad5bbcdd..4b729cbf85 100644 --- a/tools/splat/segtypes/n64/data.py +++ b/tools/splat/segtypes/common/data.py @@ -1,12 +1,12 @@ -from segtypes.n64.code import N64SegCode -from segtypes.n64.codesubsegment import N64SegCodeSubsegment -from segtypes.n64.group import N64SegGroup +from segtypes.common.code import CommonSegCode +from segtypes.common.codesubsegment import CommonSegCodeSubsegment +from segtypes.common.group import CommonSegGroup from pathlib import Path from typing import List, Optional from util.symbols import Symbol from util import floats, options -class N64SegData(N64SegCodeSubsegment, N64SegGroup): +class CommonSegData(CommonSegCodeSubsegment, CommonSegGroup): def out_path(self) -> Optional[Path]: if self.type.startswith("."): if self.sibling: @@ -20,7 +20,7 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): return options.get_asm_path() / "data" / self.dir / f"{self.name}.{self.type}.s" def scan(self, rom_bytes: bytes): - N64SegGroup.scan(self, rom_bytes) + CommonSegGroup.scan(self, rom_bytes) if super().should_scan(): self.file_text = self.disassemble_data(rom_bytes) @@ -28,11 +28,11 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): self.file_text = None def split(self, rom_bytes: bytes): - N64SegGroup.split(self, rom_bytes) + CommonSegGroup.split(self, rom_bytes) if not self.type.startswith(".") and self.file_text: path = self.out_path() - + if path: path.parent.mkdir(parents=True, exist_ok=True) @@ -46,15 +46,16 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): return True def cache(self): - return [N64SegCodeSubsegment.cache(self), N64SegGroup.cache(self)] + return [CommonSegCodeSubsegment.cache(self), CommonSegGroup.cache(self)] def get_linker_section(self) -> str: return ".data" def get_linker_entries(self): - return N64SegCodeSubsegment.get_linker_entries(self) + return CommonSegCodeSubsegment.get_linker_entries(self) def check_jtbls(self, rom_bytes, syms: List[Symbol]): + endian = options.get_endianess() for i, sym in enumerate(syms): if sym.type == "jtbl": start = self.get_most_parent().ram_to_rom(syms[i].vram_start) @@ -65,7 +66,7 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): b = 0 last_bits = 0 while b < len(sym_bytes): - bits = int.from_bytes(sym_bytes[b : b + 4], "big") + bits = int.from_bytes(sym_bytes[b : b + 4], endian) if last_bits != 0 and bits != 0 and abs(last_bits - bits) > 0x100000: new_sym_rom_start = start + b @@ -83,10 +84,11 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): def get_symbols(self, rom_bytes) -> List[Symbol]: symset = set() + endian = options.get_endianess() # Find inter-data symbols for i in range(self.rom_start, self.rom_end, 4): - bits = int.from_bytes(rom_bytes[i : i + 4], "big") + bits = int.from_bytes(rom_bytes[i : i + 4], endian) if self.contains_vram(bits): symset.add(self.get_most_parent().get_symbol(bits, create=True, define=True, local_only=True)) @@ -146,7 +148,7 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): # TODO: if we find null bytes in the middle, break this into multiple strings ? if c == null_char: if true_end is None: - if N64SegData.are_null(chars[i:]): + if CommonSegData.are_null(chars[i:]): true_end = i else: pass @@ -181,8 +183,9 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): return True return False - + def disassemble_symbol(self, sym_bytes, sym_type): + endian = options.get_endianess() if sym_type == "jtbl": sym_str = ".word " else: @@ -212,7 +215,7 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): i = 0 while i < len(sym_bytes): adv_amt = min(slen, len(sym_bytes) - i) - bits = int.from_bytes(sym_bytes[i : i + adv_amt], "big") + bits = int.from_bytes(sym_bytes[i : i + adv_amt], endian) if sym_type == "jtbl": if bits == 0: @@ -254,7 +257,7 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): sym_str += ", " return sym_str - + def disassemble_data(self, rom_bytes): rodata_encountered = "rodata" in self.type ret = ".include \"macro.inc\"\n\n" @@ -268,7 +271,7 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): for i in range(len(syms) - 1): mnemonic = syms[i].access_mnemonic sym = self.get_most_parent().get_symbol(syms[i].vram_start, create=True, define=True, local_only=True) - + sym_str = f"\n\nglabel {sym.name}\n" dis_start = self.get_most_parent().ram_to_rom(syms[i].vram_start) dis_end = self.get_most_parent().ram_to_rom(syms[i + 1].vram_start) @@ -284,13 +287,13 @@ class N64SegData(N64SegCodeSubsegment, N64SegGroup): stype = "ascii" elif syms[i].type == "jtbl": stype = "jtbl" - elif len(sym_bytes) % 8 == 0 and mnemonic in N64SegCodeSubsegment.double_mnemonics: + elif len(sym_bytes) % 8 == 0 and mnemonic in CommonSegCodeSubsegment.double_mnemonics: stype = "double" - elif len(sym_bytes) % 4 == 0 and mnemonic in N64SegCodeSubsegment.float_mnemonics: + elif len(sym_bytes) % 4 == 0 and mnemonic in CommonSegCodeSubsegment.float_mnemonics: stype = "float" - elif len(sym_bytes) % 4 == 0 and sym.vram_start % 4 == 0 and (mnemonic in N64SegCodeSubsegment.word_mnemonics or not mnemonic): + elif len(sym_bytes) % 4 == 0 and sym.vram_start % 4 == 0 and (mnemonic in CommonSegCodeSubsegment.word_mnemonics or not mnemonic): stype = "word" - elif len(sym_bytes) % 2 == 0 and sym.vram_start % 2 == 0 and (mnemonic in N64SegCodeSubsegment.short_mnemonics or not mnemonic): + elif len(sym_bytes) % 2 == 0 and sym.vram_start % 2 == 0 and (mnemonic in CommonSegCodeSubsegment.short_mnemonics or not mnemonic): stype = "short" else: stype = "byte" diff --git a/tools/splat/segtypes/n64/group.py b/tools/splat/segtypes/common/group.py similarity index 98% rename from tools/splat/segtypes/n64/group.py rename to tools/splat/segtypes/common/group.py index 759fefe089..bcdbc98fa3 100644 --- a/tools/splat/segtypes/n64/group.py +++ b/tools/splat/segtypes/common/group.py @@ -3,17 +3,17 @@ from typing import List, Dict import sys from util.range import Range from util import log, options -from segtypes.n64.segment import N64Segment +from segtypes.common.segment import CommonSegment from segtypes.segment import RomAddr, Segment from util.symbols import Symbol -class N64SegGroup(N64Segment): +class CommonSegGroup(CommonSegment): def __init__(self, rom_start, rom_end, type, name, vram_start, extract, given_subalign, given_is_overlay, given_dir, args, yaml): super().__init__(rom_start, rom_end, type, name, vram_start, extract, given_subalign, given_is_overlay, given_dir, args, yaml) self.rodata_syms: Dict[int, List[Symbol]] = {} - # TODO: move this to N64SegCode + # TODO: move this to CommonSegCode if isinstance(yaml, dict): # TODO Note: These start/end vram options don't really do anything yet data_vram = Range(yaml.get("data_vram_start"), yaml.get("data_vram_end")) @@ -229,7 +229,7 @@ class N64SegGroup(N64Segment): def should_split(self) -> bool: return self.extract - + def should_scan(self) -> bool: return self.extract @@ -238,7 +238,7 @@ class N64SegGroup(N64Segment): for sub in self.subsegments: c.append(sub.cache()) - + return c def get_most_parent(self): diff --git a/tools/splat/segtypes/common/header.py b/tools/splat/segtypes/common/header.py new file mode 100644 index 0000000000..a76d931cff --- /dev/null +++ b/tools/splat/segtypes/common/header.py @@ -0,0 +1,39 @@ +from segtypes.common.segment import CommonSegment +from pathlib import Path +from util import options + +class CommonSegHeader(CommonSegment): + def should_split(self): + return self.extract and options.mode_active("code") + + @staticmethod + def get_line(typ, data, comment): + if typ == "ascii": + text = data.decode("ASCII").strip() + text = text.replace("\x00", "\\0") # escape NUL chars + dstr = "\"" + text + "\"" + else: # .word, .byte + dstr = "0x" + data.hex().upper() + + dstr = dstr.ljust(20 - len(typ)) + + return f".{typ} {dstr} /* {comment} */" + + def out_path(self) -> Path: + return options.get_asm_path() / self.dir / f"{self.name}.s" + + def parse_header(self, rom_bytes): + return [] + + def split(self, rom_bytes): + header_lines = self.parse_header(rom_bytes) + + src_path = self.out_path() + src_path.parent.mkdir(parents=True, exist_ok=True) + with open(src_path, "w", newline="\n") as f: + f.write("\n".join(header_lines)) + self.log(f"Wrote {self.name} to {src_path}") + + @staticmethod + def get_default_name(addr): + return "header" diff --git a/tools/splat/segtypes/common/rodata.py b/tools/splat/segtypes/common/rodata.py new file mode 100644 index 0000000000..8cf1164fb8 --- /dev/null +++ b/tools/splat/segtypes/common/rodata.py @@ -0,0 +1,5 @@ +from segtypes.common.data import CommonSegData + +class CommonSegRodata(CommonSegData): + def get_linker_section(self) -> str: + return ".rodata" diff --git a/tools/splat/segtypes/common/segment.py b/tools/splat/segtypes/common/segment.py new file mode 100644 index 0000000000..2ccda48156 --- /dev/null +++ b/tools/splat/segtypes/common/segment.py @@ -0,0 +1,4 @@ +from segtypes.segment import Segment + +class CommonSegment(Segment): + pass diff --git a/tools/splat/segtypes/linker_entry.py b/tools/splat/segtypes/linker_entry.py index 60172ced38..0daa120110 100644 --- a/tools/splat/segtypes/linker_entry.py +++ b/tools/splat/segtypes/linker_entry.py @@ -20,7 +20,7 @@ def clean_up_path(path: Path) -> Path: return path_resolved.relative_to(cwd) except ValueError: pass - + # If it wasn't relative to that too, then just return the path as-is return path @@ -44,7 +44,7 @@ def to_cname(symbol: str) -> str: if symbol[0] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: symbol = "_" + symbol - + return symbol def get_segment_cname(segment: Segment) -> str: @@ -86,6 +86,7 @@ class LinkerWriter(): self._write_symbol(f"{seg_name}_TEXT_START", ".") + force_new_section = False text_ended = False data_started = False data_ended = False @@ -122,6 +123,18 @@ class LinkerWriter(): bss_started = True self._write_symbol(f"{seg_name}_BSS_START", ".") + if options.get("enable_ld_alignment_hack", False): + start = entry.segment.rom_start + if isinstance(start, int): + # Create new sections for non-subalign alignment (hack) + if start % 0x10 != 0 and i != 0 or force_new_section: + self._end_block() + self._begin_segment(entry.segment, mid_segment=True) + force_new_section = False + + if start % 0x10 != 0 and i != 0: + force_new_section = True + if entry.object_path and cur_section == ".data": path_cname = re.sub(r"[^0-9a-zA-Z_]", "_", str(entry.segment.dir / entry.segment.name) + ".".join(entry.object_path.suffixes[:-1])) self._write_symbol(path_cname, ".") @@ -206,7 +219,7 @@ class LinkerWriter(): vram_str = f"0x{vram:X} " if isinstance(vram, int) else "" name = get_segment_cname(segment) - + if mid_segment: name += to_cname(segment.type) diff --git a/tools/splat/segtypes/n64/asm.py b/tools/splat/segtypes/n64/asm.py index 2f78e616de..d3522c9e0d 100644 --- a/tools/splat/segtypes/n64/asm.py +++ b/tools/splat/segtypes/n64/asm.py @@ -1,14 +1,11 @@ -from segtypes.n64.codesubsegment import N64SegCodeSubsegment +from segtypes.common.asm import CommonSegAsm from typing import Optional from pathlib import Path from util import options -class N64SegAsm(N64SegCodeSubsegment): - def out_path(self) -> Optional[Path]: - return options.get_asm_path() / self.dir / f"{self.name}.s" - +class N64SegAsm(CommonSegAsm): @staticmethod def get_file_header(): ret = [] @@ -24,24 +21,3 @@ class N64SegAsm(N64SegCodeSubsegment): ret.append("") return ret - - def scan(self, rom_bytes: bytes): - if self.rom_start != "auto" and self.rom_end != "auto" and self.rom_start != self.rom_end: - self.funcs_text = self.disassemble_code(rom_bytes, options.get("asm_endlabels", False)) - - def split(self, rom_bytes: bytes): - if not self.rom_start == self.rom_end: - out_path = self.out_path() - if out_path: - out_path.parent.mkdir(parents=True, exist_ok=True) - - out_lines = self.get_file_header() - for func in self.funcs_text: - out_lines.extend(self.funcs_text[func][0]) - out_lines.append("") - - self.split_write(out_path, out_lines) - - def split_write(self, out_path, out_lines): - with open(out_path, "w", newline="\n") as f: - f.write("\n".join(out_lines)) diff --git a/tools/splat/segtypes/n64/hasm.py b/tools/splat/segtypes/n64/hasm.py index dc7e65598a..d097e0b0fb 100644 --- a/tools/splat/segtypes/n64/hasm.py +++ b/tools/splat/segtypes/n64/hasm.py @@ -1,7 +1,7 @@ from segtypes.n64.asm import N64SegAsm -class N64SegHasm(N64SegAsm): +class N64SegHasm(N64SegAsm): def split_write(self, out_path, out_lines): if not out_path.exists(): with open(out_path, "w", newline="\n") as f: diff --git a/tools/splat/segtypes/n64/header.py b/tools/splat/segtypes/n64/header.py index 629035d05f..e3b548af6a 100644 --- a/tools/splat/segtypes/n64/header.py +++ b/tools/splat/segtypes/n64/header.py @@ -1,26 +1,9 @@ -from segtypes.n64.segment import N64Segment +from segtypes.common.header import CommonSegHeader from pathlib import Path from util import options -class N64SegHeader(N64Segment): - def should_split(self): - return self.extract and options.mode_active("code") - - @staticmethod - def get_line(typ, data, comment): - if typ == "ascii": - dstr = "\"" + data.decode("ASCII").strip() + "\"" - else: # .word, .byte - dstr = "0x" + data.hex().upper() - - dstr = dstr.ljust(20 - len(typ)) - - return f".{typ} {dstr} /* {comment} */" - - def out_path(self) -> Path: - return options.get_asm_path() / self.dir / f"{self.name}.s" - - def split(self, rom_bytes): +class N64SegHeader(CommonSegHeader): + def parse_header(self, rom_bytes): encoding = options.get("header_encoding", "ASCII") header_lines = [] @@ -47,12 +30,4 @@ class N64SegHeader(N64Segment): header_lines.append(self.get_line("byte", rom_bytes[0x3F:0x40], "Version")) header_lines.append("") - src_path = self.out_path() - src_path.parent.mkdir(parents=True, exist_ok=True) - with open(src_path, "w", newline="\n") as f: - f.write("\n".join(header_lines)) - self.log(f"Wrote {self.name} to {src_path}") - - @staticmethod - def get_default_name(addr): - return "header" + return header_lines diff --git a/tools/splat/segtypes/n64/rodata.py b/tools/splat/segtypes/n64/rodata.py deleted file mode 100644 index 64c5832724..0000000000 --- a/tools/splat/segtypes/n64/rodata.py +++ /dev/null @@ -1,5 +0,0 @@ -from segtypes.n64.data import N64SegData - -class N64SegRodata(N64SegData): - def get_linker_section(self) -> str: - return ".rodata" diff --git a/tools/splat/segtypes/n64/vtx.py b/tools/splat/segtypes/n64/vtx.py index 4ecc5d4780..483fe98481 100644 --- a/tools/splat/segtypes/n64/vtx.py +++ b/tools/splat/segtypes/n64/vtx.py @@ -12,9 +12,9 @@ from pathlib import Path from util.log import error from util import options -from segtypes.n64.codesubsegment import N64SegCodeSubsegment +from segtypes.common.codesubsegment import CommonSegCodeSubsegment -class N64SegVtx(N64SegCodeSubsegment): +class N64SegVtx(CommonSegCodeSubsegment): def __init__(self, rom_start, rom_end, type, name, vram_start, extract, given_subalign, given_is_overlay, given_dir, args = [], yaml = {}): super().__init__(rom_start, rom_end, type, name, vram_start, extract, @@ -61,12 +61,12 @@ class N64SegVtx(N64SegCodeSubsegment): def split(self, rom_bytes: bytes): if self.file_text and self.out_path(): self.out_path().parent.mkdir(parents=True, exist_ok=True) - + with open(self.out_path(), "w", newline="\n") as f: f.write(self.file_text) def should_scan(self) -> bool: return options.mode_active("vtx") and self.rom_start != "auto" and self.rom_end != "auto" - + def should_split(self) -> bool: - return self.extract and options.mode_active("vtx") \ No newline at end of file + return self.extract and options.mode_active("vtx") diff --git a/tools/splat/segtypes/psx/__init__.py b/tools/splat/segtypes/psx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/splat/segtypes/psx/asm.py b/tools/splat/segtypes/psx/asm.py new file mode 100644 index 0000000000..b476892ddd --- /dev/null +++ b/tools/splat/segtypes/psx/asm.py @@ -0,0 +1,22 @@ +from segtypes.common.asm import CommonSegAsm +from typing import Optional +from pathlib import Path + +from util import options + + +class PsxSegAsm(CommonSegAsm): + @staticmethod + def get_file_header(): + ret = [] + + ret.append(".include \"macro.inc\"") + ret.append("") + ret.append("# assembler directives") + ret.append(".set noat # allow manual use of $at") + ret.append(".set noreorder # don't insert nops after branches") + ret.append("") + ret.append(".section .text, \"ax\"") + ret.append("") + + return ret diff --git a/tools/splat/segtypes/psx/hasm.py b/tools/splat/segtypes/psx/hasm.py new file mode 100644 index 0000000000..161a8d3050 --- /dev/null +++ b/tools/splat/segtypes/psx/hasm.py @@ -0,0 +1,8 @@ +from segtypes.psx.asm import PsxSegAsm + + +class PsxSegHasm(PsxSegAsm): + def split_write(self, out_path, out_lines): + if not out_path.exists(): + with open(out_path, "w", newline="\n") as f: + f.write("\n".join(out_lines)) diff --git a/tools/splat/segtypes/psx/header.py b/tools/splat/segtypes/psx/header.py new file mode 100644 index 0000000000..b3f3e537ff --- /dev/null +++ b/tools/splat/segtypes/psx/header.py @@ -0,0 +1,34 @@ +from segtypes.common.header import CommonSegHeader +from pathlib import Path +from util import options + + +class PsxSegHeader(CommonSegHeader): + # little endian so reverse words, TODO: use struct.unpack(" common -> fail try: segmodule = importlib.import_module(f"segtypes.{platform}.{seg_type}") + is_platform_seg = True except ModuleNotFoundError: - return None + try: + segmodule = importlib.import_module(f"segtypes.common.{seg_type}") + except ModuleNotFoundError: + return None - return getattr(segmodule, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}") + seg_prefix = platform.capitalize() if is_platform_seg else "Common" + return getattr(segmodule, f"{seg_prefix}Seg{seg_type.capitalize()}") @staticmethod def get_extension_segment_class(seg_type): diff --git a/tools/splat/split.py b/tools/splat/split.py index d4689b9a23..daad202655 100755 --- a/tools/splat/split.py +++ b/tools/splat/split.py @@ -14,7 +14,7 @@ from util import options from util import symbols from util import palettes -VERSION = "0.7.9" +VERSION = "0.7.10" parser = argparse.ArgumentParser(description="Split a rom given a rom, a config, and output directory") parser.add_argument("config", help="path to a compatible config .yaml file") diff --git a/tools/splat/util/options.py b/tools/splat/util/options.py index 29b3d6e785..efa5f40160 100644 --- a/tools/splat/util/options.py +++ b/tools/splat/util/options.py @@ -29,6 +29,9 @@ def get(opt, default=None): def get_platform() -> str: return opts.get("platform", "n64") +def get_endianess() -> str: + return opts.get("endianess", "little" if get_platform().upper() == "PSX" else "big") + def get_compiler() -> str: return opts.get("compiler", "IDO") diff --git a/tools/splat/util/palettes.py b/tools/splat/util/palettes.py index 3e03394049..9bb9ab05cb 100644 --- a/tools/splat/util/palettes.py +++ b/tools/splat/util/palettes.py @@ -1,9 +1,9 @@ from typing import Dict from segtypes.n64.palette import N64SegPalette as Palette from segtypes.n64.ci8 import N64SegCi8 as Raster -from segtypes.n64.group import N64SegGroup +from segtypes.common.group import CommonSegGroup -# Resolve Raster#palette and Palette#raster links +# Resolve Raster#palette and Palette#raster links def initialize(all_segments): global palettes_remaining @@ -18,9 +18,9 @@ def initialize(all_segments): if isinstance(segment, Raster): raster_map[segment.name] = segment - if isinstance(segment, N64SegGroup): + if isinstance(segment, CommonSegGroup): process(segment.subsegments) - + for raster_name in raster_map: raster = raster_map[raster_name] #print(f"{raster_name} -> {raster.palette_name}") diff --git a/ver/us/asm/nonmatchings/341d0/update_effects.s b/ver/us/asm/nonmatchings/341d0/update_effects.s deleted file mode 100644 index 43657a59b9..0000000000 --- a/ver/us/asm/nonmatchings/341d0/update_effects.s +++ /dev/null @@ -1,121 +0,0 @@ -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches - -glabel update_effects -/* 35150 80059D50 3C02800A */ lui $v0, %hi(gOverrideFlags) -/* 35154 80059D54 8C42A650 */ lw $v0, %lo(gOverrideFlags)($v0) -/* 35158 80059D58 27BDFFD8 */ addiu $sp, $sp, -0x28 -/* 3515C 80059D5C AFBF0020 */ sw $ra, 0x20($sp) -/* 35160 80059D60 AFB3001C */ sw $s3, 0x1c($sp) -/* 35164 80059D64 AFB20018 */ sw $s2, 0x18($sp) -/* 35168 80059D68 AFB10014 */ sw $s1, 0x14($sp) -/* 3516C 80059D6C 30420C00 */ andi $v0, $v0, 0xc00 -/* 35170 80059D70 14400059 */ bnez $v0, .L80059ED8 -/* 35174 80059D74 AFB00010 */ sw $s0, 0x10($sp) -/* 35178 80059D78 0000902D */ daddu $s2, $zero, $zero -/* 3517C 80059D7C 3C10800A */ lui $s0, %hi(gEffectGraphicsData) -/* 35180 80059D80 26104000 */ addiu $s0, $s0, %lo(gEffectGraphicsData) -/* 35184 80059D84 24040003 */ addiu $a0, $zero, 3 -.L80059D88: -/* 35188 80059D88 8E030000 */ lw $v1, ($s0) -/* 3518C 80059D8C 30620001 */ andi $v0, $v1, 1 -/* 35190 80059D90 10400007 */ beqz $v0, .L80059DB0 -/* 35194 80059D94 26520001 */ addiu $s2, $s2, 1 -/* 35198 80059D98 30620002 */ andi $v0, $v1, 2 -/* 3519C 80059D9C 14400004 */ bnez $v0, .L80059DB0 -/* 351A0 80059DA0 00000000 */ nop -/* 351A4 80059DA4 34620002 */ ori $v0, $v1, 2 -/* 351A8 80059DA8 AE020000 */ sw $v0, ($s0) -/* 351AC 80059DAC AE04000C */ sw $a0, 0xc($s0) -.L80059DB0: -/* 351B0 80059DB0 2A42000F */ slti $v0, $s2, 0xf -/* 351B4 80059DB4 1440FFF4 */ bnez $v0, .L80059D88 -/* 351B8 80059DB8 26100020 */ addiu $s0, $s0, 0x20 -/* 351BC 80059DBC 0000902D */ daddu $s2, $zero, $zero -/* 351C0 80059DC0 2413FFFD */ addiu $s3, $zero, -3 -/* 351C4 80059DC4 3C11800B */ lui $s1, %hi(gEffectInstances) -/* 351C8 80059DC8 26314398 */ addiu $s1, $s1, %lo(gEffectInstances) -.L80059DCC: -/* 351CC 80059DCC 8E300000 */ lw $s0, ($s1) -/* 351D0 80059DD0 52000020 */ beql $s0, $zero, .L80059E54 -/* 351D4 80059DD4 26520001 */ addiu $s2, $s2, 1 -/* 351D8 80059DD8 8E020000 */ lw $v0, ($s0) -/* 351DC 80059DDC 30420001 */ andi $v0, $v0, 1 -/* 351E0 80059DE0 5040001C */ beql $v0, $zero, .L80059E54 -/* 351E4 80059DE4 26520001 */ addiu $s2, $s2, 1 -/* 351E8 80059DE8 8E030010 */ lw $v1, 0x10($s0) -/* 351EC 80059DEC 8C620000 */ lw $v0, ($v1) -/* 351F0 80059DF0 3C048007 */ lui $a0, %hi(gGameStatusPtr) -/* 351F4 80059DF4 8C84419C */ lw $a0, %lo(gGameStatusPtr)($a0) -/* 351F8 80059DF8 00531024 */ and $v0, $v0, $s3 -/* 351FC 80059DFC AC620000 */ sw $v0, ($v1) -/* 35200 80059E00 80820070 */ lb $v0, 0x70($a0) -/* 35204 80059E04 10400007 */ beqz $v0, .L80059E24 -/* 35208 80059E08 00000000 */ nop -/* 3520C 80059E0C 8E020000 */ lw $v0, ($s0) -/* 35210 80059E10 30420004 */ andi $v0, $v0, 4 -/* 35214 80059E14 5040000F */ beql $v0, $zero, .L80059E54 -/* 35218 80059E18 26520001 */ addiu $s2, $s2, 1 -/* 3521C 80059E1C 0801678D */ j .L80059E34 -/* 35220 80059E20 00000000 */ nop -.L80059E24: -/* 35224 80059E24 8E020000 */ lw $v0, ($s0) -/* 35228 80059E28 30420004 */ andi $v0, $v0, 4 -/* 3522C 80059E2C 54400009 */ bnel $v0, $zero, .L80059E54 -/* 35230 80059E30 26520001 */ addiu $s2, $s2, 1 -.L80059E34: -/* 35234 80059E34 8E020010 */ lw $v0, 0x10($s0) -/* 35238 80059E38 8C420010 */ lw $v0, 0x10($v0) -/* 3523C 80059E3C 0040F809 */ jalr $v0 -/* 35240 80059E40 0200202D */ daddu $a0, $s0, $zero -/* 35244 80059E44 8E020000 */ lw $v0, ($s0) -/* 35248 80059E48 34420008 */ ori $v0, $v0, 8 -/* 3524C 80059E4C AE020000 */ sw $v0, ($s0) -/* 35250 80059E50 26520001 */ addiu $s2, $s2, 1 -.L80059E54: -/* 35254 80059E54 2A420060 */ slti $v0, $s2, 0x60 -/* 35258 80059E58 1440FFDC */ bnez $v0, .L80059DCC -/* 3525C 80059E5C 26310004 */ addiu $s1, $s1, 4 -/* 35260 80059E60 0000902D */ daddu $s2, $zero, $zero -/* 35264 80059E64 3C10800A */ lui $s0, %hi(gEffectGraphicsData) -/* 35268 80059E68 26104000 */ addiu $s0, $s0, %lo(gEffectGraphicsData) -/* 3526C 80059E6C 2611001C */ addiu $s1, $s0, 0x1c -.L80059E70: -/* 35270 80059E70 8E030000 */ lw $v1, ($s0) -/* 35274 80059E74 30620001 */ andi $v0, $v1, 1 -/* 35278 80059E78 50400013 */ beql $v0, $zero, .L80059EC8 -/* 3527C 80059E7C 26520001 */ addiu $s2, $s2, 1 -/* 35280 80059E80 30620002 */ andi $v0, $v1, 2 -/* 35284 80059E84 50400010 */ beql $v0, $zero, .L80059EC8 -/* 35288 80059E88 26520001 */ addiu $s2, $s2, 1 -/* 3528C 80059E8C 8E22FFF0 */ lw $v0, -0x10($s1) -/* 35290 80059E90 10400003 */ beqz $v0, .L80059EA0 -/* 35294 80059E94 2442FFFF */ addiu $v0, $v0, -1 -/* 35298 80059E98 080167B1 */ j .L80059EC4 -/* 3529C 80059E9C AE22FFF0 */ sw $v0, -0x10($s1) -.L80059EA0: -/* 352A0 80059EA0 8E240000 */ lw $a0, ($s1) -/* 352A4 80059EA4 50800005 */ beql $a0, $zero, .L80059EBC -/* 352A8 80059EA8 AE000000 */ sw $zero, ($s0) -/* 352AC 80059EAC 0C00AB1E */ jal general_heap_free -/* 352B0 80059EB0 00000000 */ nop -/* 352B4 80059EB4 AE200000 */ sw $zero, ($s1) -/* 352B8 80059EB8 AE000000 */ sw $zero, ($s0) -.L80059EBC: -/* 352BC 80059EBC 0C019A48 */ jal osUnmapTLB -/* 352C0 80059EC0 0240202D */ daddu $a0, $s2, $zero -.L80059EC4: -/* 352C4 80059EC4 26520001 */ addiu $s2, $s2, 1 -.L80059EC8: -/* 352C8 80059EC8 26310020 */ addiu $s1, $s1, 0x20 -/* 352CC 80059ECC 2A42000F */ slti $v0, $s2, 0xf -/* 352D0 80059ED0 1440FFE7 */ bnez $v0, .L80059E70 -/* 352D4 80059ED4 26100020 */ addiu $s0, $s0, 0x20 -.L80059ED8: -/* 352D8 80059ED8 8FBF0020 */ lw $ra, 0x20($sp) -/* 352DC 80059EDC 8FB3001C */ lw $s3, 0x1c($sp) -/* 352E0 80059EE0 8FB20018 */ lw $s2, 0x18($sp) -/* 352E4 80059EE4 8FB10014 */ lw $s1, 0x14($sp) -/* 352E8 80059EE8 8FB00010 */ lw $s0, 0x10($sp) -/* 352EC 80059EEC 03E00008 */ jr $ra -/* 352F0 80059EF0 27BD0028 */ addiu $sp, $sp, 0x28 diff --git a/ver/us/asm/nonmatchings/effects/effect_6/func_E000C000.s b/ver/us/asm/nonmatchings/effects/effect_6/func_E000C000.s deleted file mode 100644 index 12ebc8a610..0000000000 --- a/ver/us/asm/nonmatchings/effects/effect_6/func_E000C000.s +++ /dev/null @@ -1,41 +0,0 @@ -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches - -glabel func_E000C000 -/* 328110 E000C000 C4840018 */ lwc1 $f4, 0x18($a0) -/* 328114 E000C004 C480001C */ lwc1 $f0, 0x1c($a0) -/* 328118 E000C008 46002100 */ add.s $f4, $f4, $f0 -/* 32811C E000C00C C4880014 */ lwc1 $f8, 0x14($a0) -/* 328120 E000C010 46044200 */ add.s $f8, $f8, $f4 -/* 328124 E000C014 C4800010 */ lwc1 $f0, 0x10($a0) -/* 328128 E000C018 46080000 */ add.s $f0, $f0, $f8 -/* 32812C E000C01C C4820030 */ lwc1 $f2, 0x30($a0) -/* 328130 E000C020 46020082 */ mul.s $f2, $f0, $f2 -/* 328134 E000C024 00000000 */ nop -/* 328138 E000C028 E4800010 */ swc1 $f0, 0x10($a0) -/* 32813C E000C02C 46000286 */ mov.s $f10, $f0 -/* 328140 E000C030 C4800034 */ lwc1 $f0, 0x34($a0) -/* 328144 E000C034 46005282 */ mul.s $f10, $f10, $f0 -/* 328148 E000C038 00000000 */ nop -/* 32814C E000C03C C48C0004 */ lwc1 $f12, 4($a0) -/* 328150 E000C040 C4860020 */ lwc1 $f6, 0x20($a0) -/* 328154 E000C044 C4800028 */ lwc1 $f0, 0x28($a0) -/* 328158 E000C048 46026300 */ add.s $f12, $f12, $f2 -/* 32815C E000C04C C482002C */ lwc1 $f2, 0x2c($a0) -/* 328160 E000C050 E4840018 */ swc1 $f4, 0x18($a0) -/* 328164 E000C054 46020000 */ add.s $f0, $f0, $f2 -/* 328168 E000C058 C482000C */ lwc1 $f2, 0xc($a0) -/* 32816C E000C05C C4840024 */ lwc1 $f4, 0x24($a0) -/* 328170 E000C060 460A1080 */ add.s $f2, $f2, $f10 -/* 328174 E000C064 E4880014 */ swc1 $f8, 0x14($a0) -/* 328178 E000C068 46002100 */ add.s $f4, $f4, $f0 -/* 32817C E000C06C E4800028 */ swc1 $f0, 0x28($a0) -/* 328180 E000C070 C4800008 */ lwc1 $f0, 8($a0) -/* 328184 E000C074 46043180 */ add.s $f6, $f6, $f4 -/* 328188 E000C078 E48C0004 */ swc1 $f12, 4($a0) -/* 32818C E000C07C E482000C */ swc1 $f2, 0xc($a0) -/* 328190 E000C080 46060000 */ add.s $f0, $f0, $f6 -/* 328194 E000C084 E4840024 */ swc1 $f4, 0x24($a0) -/* 328198 E000C088 E4860020 */ swc1 $f6, 0x20($a0) -/* 32819C E000C08C 03E00008 */ jr $ra -/* 3281A0 E000C090 E4800008 */ swc1 $f0, 8($a0) diff --git a/ver/us/asm/nonmatchings/effects/effect_6/fx_6_appendGfx.s b/ver/us/asm/nonmatchings/effects/effect_6/fx_6_appendGfx.s deleted file mode 100644 index 74b5cff275..0000000000 --- a/ver/us/asm/nonmatchings/effects/effect_6/fx_6_appendGfx.s +++ /dev/null @@ -1,298 +0,0 @@ -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches - -glabel fx_6_appendGfx -/* 3288A8 E000C798 27BDFEF8 */ addiu $sp, $sp, -0x108 -/* 3288AC E000C79C AFBE0100 */ sw $fp, 0x100($sp) -/* 3288B0 E000C7A0 0080F02D */ daddu $fp, $a0, $zero -/* 3288B4 E000C7A4 3C07DB06 */ lui $a3, 0xdb06 -/* 3288B8 E000C7A8 34E70024 */ ori $a3, $a3, 0x24 -/* 3288BC E000C7AC 27A40018 */ addiu $a0, $sp, 0x18 -/* 3288C0 E000C7B0 AFB200E8 */ sw $s2, 0xe8($sp) -/* 3288C4 E000C7B4 3C120001 */ lui $s2, 1 -/* 3288C8 E000C7B8 36521630 */ ori $s2, $s2, 0x1630 -/* 3288CC E000C7BC AFB300EC */ sw $s3, 0xec($sp) -/* 3288D0 E000C7C0 3C13800A */ lui $s3, %hi(gMasterGfxPos) -/* 3288D4 E000C7C4 2673A66C */ addiu $s3, $s3, %lo(gMasterGfxPos) -/* 3288D8 E000C7C8 AFBF0104 */ sw $ra, 0x104($sp) -/* 3288DC E000C7CC AFB700FC */ sw $s7, 0xfc($sp) -/* 3288E0 E000C7D0 AFB600F8 */ sw $s6, 0xf8($sp) -/* 3288E4 E000C7D4 AFB500F4 */ sw $s5, 0xf4($sp) -/* 3288E8 E000C7D8 AFB400F0 */ sw $s4, 0xf0($sp) -/* 3288EC E000C7DC AFB100E4 */ sw $s1, 0xe4($sp) -/* 3288F0 E000C7E0 AFB000E0 */ sw $s0, 0xe0($sp) -/* 3288F4 E000C7E4 8E650000 */ lw $a1, ($s3) -/* 3288F8 E000C7E8 8FD4000C */ lw $s4, 0xc($fp) -/* 3288FC E000C7EC 00A0182D */ daddu $v1, $a1, $zero -/* 328900 E000C7F0 24A50008 */ addiu $a1, $a1, 8 -/* 328904 E000C7F4 AE650000 */ sw $a1, ($s3) -/* 328908 E000C7F8 96950000 */ lhu $s5, ($s4) -/* 32890C E000C7FC 24A20008 */ addiu $v0, $a1, 8 -/* 328910 E000C800 AE620000 */ sw $v0, ($s3) -/* 328914 E000C804 8E880040 */ lw $t0, 0x40($s4) -/* 328918 E000C808 00151080 */ sll $v0, $s5, 2 -/* 32891C E000C80C 3C17E001 */ lui $s7, %hi(D_E000CC10) -/* 328920 E000C810 02E2B821 */ addu $s7, $s7, $v0 -/* 328924 E000C814 8EF7CC10 */ lw $s7, %lo(D_E000CC10)($s7) -/* 328928 E000C818 3C06E001 */ lui $a2, %hi(D_E000CC24) -/* 32892C E000C81C 00C23021 */ addu $a2, $a2, $v0 -/* 328930 E000C820 8CC6CC24 */ lw $a2, %lo(D_E000CC24)($a2) -/* 328934 E000C824 3C02E700 */ lui $v0, 0xe700 -/* 328938 E000C828 AC620000 */ sw $v0, ($v1) -/* 32893C E000C82C AC600004 */ sw $zero, 4($v1) -/* 328940 E000C830 ACA70000 */ sw $a3, ($a1) -/* 328944 E000C834 8FC30010 */ lw $v1, 0x10($fp) -/* 328948 E000C838 24A20010 */ addiu $v0, $a1, 0x10 -/* 32894C E000C83C AE620000 */ sw $v0, ($s3) -/* 328950 E000C840 3C02DE00 */ lui $v0, 0xde00 -/* 328954 E000C844 8C63001C */ lw $v1, 0x1c($v1) -/* 328958 E000C848 310B0007 */ andi $t3, $t0, 7 -/* 32895C E000C84C ACA20008 */ sw $v0, 8($a1) -/* 328960 E000C850 3C028000 */ lui $v0, 0x8000 -/* 328964 E000C854 ACA6000C */ sw $a2, 0xc($a1) -/* 328968 E000C858 00621821 */ addu $v1, $v1, $v0 -/* 32896C E000C85C 31020038 */ andi $v0, $t0, 0x38 -/* 328970 E000C860 0002B080 */ sll $s6, $v0, 2 -/* 328974 E000C864 ACA30004 */ sw $v1, 4($a1) -/* 328978 E000C868 8E850004 */ lw $a1, 4($s4) -/* 32897C E000C86C 8E860008 */ lw $a2, 8($s4) -/* 328980 E000C870 8E87000C */ lw $a3, 0xc($s4) -/* 328984 E000C874 31080040 */ andi $t0, $t0, 0x40 -/* 328988 E000C878 AFAB00D8 */ sw $t3, 0xd8($sp) -/* 32898C E000C87C 0C080108 */ jal shim_guTranslateF -/* 328990 E000C880 AFA800DC */ sw $t0, 0xdc($sp) -/* 328994 E000C884 27B00058 */ addiu $s0, $sp, 0x58 -/* 328998 E000C888 3C038007 */ lui $v1, %hi(gCurrentCameraID) -/* 32899C E000C88C 8C637410 */ lw $v1, %lo(gCurrentCameraID)($v1) -/* 3289A0 E000C890 44800000 */ mtc1 $zero, $f0 -/* 3289A4 E000C894 00031080 */ sll $v0, $v1, 2 -/* 3289A8 E000C898 00431021 */ addu $v0, $v0, $v1 -/* 3289AC E000C89C 00021080 */ sll $v0, $v0, 2 -/* 3289B0 E000C8A0 00431023 */ subu $v0, $v0, $v1 -/* 3289B4 E000C8A4 000218C0 */ sll $v1, $v0, 3 -/* 3289B8 E000C8A8 00431021 */ addu $v0, $v0, $v1 -/* 3289BC E000C8AC 000210C0 */ sll $v0, $v0, 3 -/* 3289C0 E000C8B0 44060000 */ mfc1 $a2, $f0 -/* 3289C4 E000C8B4 3C01800B */ lui $at, %hi(gCameras+0x6C) -/* 3289C8 E000C8B8 00220821 */ addu $at, $at, $v0 -/* 3289CC E000C8BC C4201DEC */ lwc1 $f0, %lo(gCameras+0x6C)($at) -/* 3289D0 E000C8C0 3C073F80 */ lui $a3, 0x3f80 -/* 3289D4 E000C8C4 46000007 */ neg.s $f0, $f0 -/* 3289D8 E000C8C8 44050000 */ mfc1 $a1, $f0 -/* 3289DC E000C8CC 0200202D */ daddu $a0, $s0, $zero -/* 3289E0 E000C8D0 0C080104 */ jal shim_guRotateF -/* 3289E4 E000C8D4 AFA60010 */ sw $a2, 0x10($sp) -/* 3289E8 E000C8D8 0200202D */ daddu $a0, $s0, $zero -/* 3289EC E000C8DC 27A50018 */ addiu $a1, $sp, 0x18 -/* 3289F0 E000C8E0 27B00098 */ addiu $s0, $sp, 0x98 -/* 3289F4 E000C8E4 0C080114 */ jal shim_guMtxCatF -/* 3289F8 E000C8E8 0200302D */ daddu $a2, $s0, $zero -/* 3289FC E000C8EC 0200202D */ daddu $a0, $s0, $zero -/* 328A00 E000C8F0 3C108007 */ lui $s0, %hi(gMatrixListPos) -/* 328A04 E000C8F4 261041F0 */ addiu $s0, $s0, %lo(gMatrixListPos) -/* 328A08 E000C8F8 3C11800A */ lui $s1, %hi(gDisplayContext) -/* 328A0C E000C8FC 2631A674 */ addiu $s1, $s1, %lo(gDisplayContext) -/* 328A10 E000C900 96050000 */ lhu $a1, ($s0) -/* 328A14 E000C904 8E220000 */ lw $v0, ($s1) -/* 328A18 E000C908 00052980 */ sll $a1, $a1, 6 -/* 328A1C E000C90C 00B22821 */ addu $a1, $a1, $s2 -/* 328A20 E000C910 0C080118 */ jal shim_guMtxF2L -/* 328A24 E000C914 00452821 */ addu $a1, $v0, $a1 -/* 328A28 E000C918 8E650000 */ lw $a1, ($s3) -/* 328A2C E000C91C 00A0202D */ daddu $a0, $a1, $zero -/* 328A30 E000C920 24A50008 */ addiu $a1, $a1, 8 -/* 328A34 E000C924 AE650000 */ sw $a1, ($s3) -/* 328A38 E000C928 96020000 */ lhu $v0, ($s0) -/* 328A3C E000C92C 3C03DA38 */ lui $v1, 0xda38 -/* 328A40 E000C930 AC830000 */ sw $v1, ($a0) -/* 328A44 E000C934 24430001 */ addiu $v1, $v0, 1 -/* 328A48 E000C938 3042FFFF */ andi $v0, $v0, 0xffff -/* 328A4C E000C93C 00021180 */ sll $v0, $v0, 6 -/* 328A50 E000C940 00521021 */ addu $v0, $v0, $s2 -/* 328A54 E000C944 A6030000 */ sh $v1, ($s0) -/* 328A58 E000C948 8E230000 */ lw $v1, ($s1) -/* 328A5C E000C94C 02C0802D */ daddu $s0, $s6, $zero -/* 328A60 E000C950 00621821 */ addu $v1, $v1, $v0 -/* 328A64 E000C954 06A0001D */ bltz $s5, .LE000C9CC -/* 328A68 E000C958 AC830004 */ sw $v1, 4($a0) -/* 328A6C E000C95C 2AA20002 */ slti $v0, $s5, 2 -/* 328A70 E000C960 1040000D */ beqz $v0, .LE000C998 -/* 328A74 E000C964 3C03E6DE */ lui $v1, 0xe6de -/* 328A78 E000C968 3463DE6E */ ori $v1, $v1, 0xde6e -/* 328A7C E000C96C 24A20008 */ addiu $v0, $a1, 8 -/* 328A80 E000C970 AE620000 */ sw $v0, ($s3) -/* 328A84 E000C974 3C02FA00 */ lui $v0, 0xfa00 -/* 328A88 E000C978 ACA20000 */ sw $v0, ($a1) -/* 328A8C E000C97C 24A20010 */ addiu $v0, $a1, 0x10 -/* 328A90 E000C980 ACA30004 */ sw $v1, 4($a1) -/* 328A94 E000C984 AE620000 */ sw $v0, ($s3) -/* 328A98 E000C988 3C02FB00 */ lui $v0, 0xfb00 -/* 328A9C E000C98C ACA20008 */ sw $v0, 8($a1) -/* 328AA0 E000C990 08003273 */ j .LE000C9CC -/* 328AA4 E000C994 ACB6000C */ sw $s6, 0xc($a1) -.LE000C998: -/* 328AA8 E000C998 2AA20005 */ slti $v0, $s5, 5 -/* 328AAC E000C99C 1040000B */ beqz $v0, .LE000C9CC -/* 328AB0 E000C9A0 3463DE82 */ ori $v1, $v1, 0xde82 -/* 328AB4 E000C9A4 24A20008 */ addiu $v0, $a1, 8 -/* 328AB8 E000C9A8 AE620000 */ sw $v0, ($s3) -/* 328ABC E000C9AC 3C02FA00 */ lui $v0, 0xfa00 -/* 328AC0 E000C9B0 ACA20000 */ sw $v0, ($a1) -/* 328AC4 E000C9B4 24A20010 */ addiu $v0, $a1, 0x10 -/* 328AC8 E000C9B8 ACA30004 */ sw $v1, 4($a1) -/* 328ACC E000C9BC AE620000 */ sw $v0, ($s3) -/* 328AD0 E000C9C0 3C02FB00 */ lui $v0, 0xfb00 -/* 328AD4 E000C9C4 ACA20008 */ sw $v0, 8($a1) -/* 328AD8 E000C9C8 ACB0000C */ sw $s0, 0xc($a1) -.LE000C9CC: -/* 328ADC E000C9CC 16A00002 */ bnez $s5, .LE000C9D8 -/* 328AE0 E000C9D0 24040020 */ addiu $a0, $zero, 0x20 -/* 328AE4 E000C9D4 24040018 */ addiu $a0, $zero, 0x18 -.LE000C9D8: -/* 328AE8 E000C9D8 3C0A800A */ lui $t2, %hi(gMasterGfxPos) -/* 328AEC E000C9DC 254AA66C */ addiu $t2, $t2, %lo(gMasterGfxPos) -/* 328AF0 E000C9E0 3C09F200 */ lui $t1, 0xf200 -/* 328AF4 E000C9E4 8FAB00D8 */ lw $t3, 0xd8($sp) -/* 328AF8 E000C9E8 8D450000 */ lw $a1, ($t2) -/* 328AFC E000C9EC 01640018 */ mult $t3, $a0 -/* 328B00 E000C9F0 00043880 */ sll $a3, $a0, 2 -/* 328B04 E000C9F4 00A0182D */ daddu $v1, $a1, $zero -/* 328B08 E000C9F8 24A50008 */ addiu $a1, $a1, 8 -/* 328B0C E000C9FC AD450000 */ sw $a1, ($t2) -/* 328B10 E000CA00 00004012 */ mflo $t0 -/* 328B14 E000CA04 00081380 */ sll $v0, $t0, 0xe -/* 328B18 E000CA08 00491025 */ or $v0, $v0, $t1 -/* 328B1C E000CA0C AC620000 */ sw $v0, ($v1) -/* 328B20 E000CA10 01041021 */ addu $v0, $t0, $a0 -/* 328B24 E000CA14 00023380 */ sll $a2, $v0, 0xe -/* 328B28 E000CA18 00C71025 */ or $v0, $a2, $a3 -/* 328B2C E000CA1C AC620004 */ sw $v0, 4($v1) -/* 328B30 E000CA20 8FAB00DC */ lw $t3, 0xdc($sp) -/* 328B34 E000CA24 1160000C */ beqz $t3, .LE000CA58 -/* 328B38 E000CA28 24A20008 */ addiu $v0, $a1, 8 -/* 328B3C E000CA2C AD420000 */ sw $v0, ($t2) -/* 328B40 E000CA30 00E91025 */ or $v0, $a3, $t1 -/* 328B44 E000CA34 00C21025 */ or $v0, $a2, $v0 -/* 328B48 E000CA38 ACA20000 */ sw $v0, ($a1) -/* 328B4C E000CA3C 00041040 */ sll $v0, $a0, 1 -/* 328B50 E000CA40 01021021 */ addu $v0, $t0, $v0 -/* 328B54 E000CA44 00021380 */ sll $v0, $v0, 0xe -/* 328B58 E000CA48 000418C0 */ sll $v1, $a0, 3 -/* 328B5C E000CA4C 3C040100 */ lui $a0, 0x100 -/* 328B60 E000CA50 0800329E */ j .LE000CA78 -/* 328B64 E000CA54 00641825 */ or $v1, $v1, $a0 -.LE000CA58: -/* 328B68 E000CA58 AD420000 */ sw $v0, ($t2) -/* 328B6C E000CA5C 00C91025 */ or $v0, $a2, $t1 -/* 328B70 E000CA60 ACA20000 */ sw $v0, ($a1) -/* 328B74 E000CA64 00041040 */ sll $v0, $a0, 1 -/* 328B78 E000CA68 01021021 */ addu $v0, $t0, $v0 -/* 328B7C E000CA6C 00021380 */ sll $v0, $v0, 0xe -/* 328B80 E000CA70 3C030100 */ lui $v1, 0x100 -/* 328B84 E000CA74 00E31825 */ or $v1, $a3, $v1 -.LE000CA78: -/* 328B88 E000CA78 00431025 */ or $v0, $v0, $v1 -/* 328B8C E000CA7C ACA20004 */ sw $v0, 4($a1) -/* 328B90 E000CA80 24020002 */ addiu $v0, $zero, 2 -/* 328B94 E000CA84 16A2000A */ bne $s5, $v0, .LE000CAB0 -/* 328B98 E000CA88 00000000 */ nop -/* 328B9C E000CA8C 3C03800A */ lui $v1, %hi(gMasterGfxPos) -/* 328BA0 E000CA90 2463A66C */ addiu $v1, $v1, %lo(gMasterGfxPos) -/* 328BA4 E000CA94 8C620000 */ lw $v0, ($v1) -/* 328BA8 E000CA98 0040202D */ daddu $a0, $v0, $zero -/* 328BAC E000CA9C 24420008 */ addiu $v0, $v0, 8 -/* 328BB0 E000CAA0 AC620000 */ sw $v0, ($v1) -/* 328BB4 E000CAA4 3C02DE00 */ lui $v0, 0xde00 -/* 328BB8 E000CAA8 AC820000 */ sw $v0, ($a0) -/* 328BBC E000CAAC AC970004 */ sw $s7, 4($a0) -.LE000CAB0: -/* 328BC0 E000CAB0 8FC20008 */ lw $v0, 8($fp) -/* 328BC4 E000CAB4 24110001 */ addiu $s1, $zero, 1 -/* 328BC8 E000CAB8 0222102A */ slt $v0, $s1, $v0 -/* 328BCC E000CABC 10400036 */ beqz $v0, .LE000CB98 -/* 328BD0 E000CAC0 26940044 */ addiu $s4, $s4, 0x44 -/* 328BD4 E000CAC4 3C16800A */ lui $s6, %hi(gDisplayContext) -/* 328BD8 E000CAC8 26D6A674 */ addiu $s6, $s6, %lo(gDisplayContext) -/* 328BDC E000CACC 3C158007 */ lui $s5, %hi(gMatrixListPos) -/* 328BE0 E000CAD0 26B541F0 */ addiu $s5, $s5, %lo(gMatrixListPos) -/* 328BE4 E000CAD4 3C130001 */ lui $s3, 1 -/* 328BE8 E000CAD8 36731630 */ ori $s3, $s3, 0x1630 -/* 328BEC E000CADC 3C12800A */ lui $s2, %hi(gMasterGfxPos) -/* 328BF0 E000CAE0 2652A66C */ addiu $s2, $s2, %lo(gMasterGfxPos) -/* 328BF4 E000CAE4 2690000C */ addiu $s0, $s4, 0xc -.LE000CAE8: -/* 328BF8 E000CAE8 27A40018 */ addiu $a0, $sp, 0x18 -/* 328BFC E000CAEC 26310001 */ addiu $s1, $s1, 1 -/* 328C00 E000CAF0 8E05FFF8 */ lw $a1, -8($s0) -/* 328C04 E000CAF4 8E06FFFC */ lw $a2, -4($s0) -/* 328C08 E000CAF8 8E070000 */ lw $a3, ($s0) -/* 328C0C E000CAFC 0C080108 */ jal shim_guTranslateF -/* 328C10 E000CB00 26100044 */ addiu $s0, $s0, 0x44 -/* 328C14 E000CB04 27A40018 */ addiu $a0, $sp, 0x18 -/* 328C18 E000CB08 96A50000 */ lhu $a1, ($s5) -/* 328C1C E000CB0C 8EC20000 */ lw $v0, ($s6) -/* 328C20 E000CB10 00052980 */ sll $a1, $a1, 6 -/* 328C24 E000CB14 00B32821 */ addu $a1, $a1, $s3 -/* 328C28 E000CB18 0C080118 */ jal shim_guMtxF2L -/* 328C2C E000CB1C 00452821 */ addu $a1, $v0, $a1 -/* 328C30 E000CB20 3C06D838 */ lui $a2, 0xd838 -/* 328C34 E000CB24 8E440000 */ lw $a0, ($s2) -/* 328C38 E000CB28 34C60002 */ ori $a2, $a2, 2 -/* 328C3C E000CB2C 0080282D */ daddu $a1, $a0, $zero -/* 328C40 E000CB30 24840008 */ addiu $a0, $a0, 8 -/* 328C44 E000CB34 AE440000 */ sw $a0, ($s2) -/* 328C48 E000CB38 96A20000 */ lhu $v0, ($s5) -/* 328C4C E000CB3C 3C03DA38 */ lui $v1, 0xda38 -/* 328C50 E000CB40 ACA30000 */ sw $v1, ($a1) -/* 328C54 E000CB44 24430001 */ addiu $v1, $v0, 1 -/* 328C58 E000CB48 3042FFFF */ andi $v0, $v0, 0xffff -/* 328C5C E000CB4C 00021180 */ sll $v0, $v0, 6 -/* 328C60 E000CB50 A6A30000 */ sh $v1, ($s5) -/* 328C64 E000CB54 8EC30000 */ lw $v1, ($s6) -/* 328C68 E000CB58 00531021 */ addu $v0, $v0, $s3 -/* 328C6C E000CB5C 00621821 */ addu $v1, $v1, $v0 -/* 328C70 E000CB60 24820008 */ addiu $v0, $a0, 8 -/* 328C74 E000CB64 ACA30004 */ sw $v1, 4($a1) -/* 328C78 E000CB68 AE420000 */ sw $v0, ($s2) -/* 328C7C E000CB6C 3C02DE00 */ lui $v0, 0xde00 -/* 328C80 E000CB70 AC820000 */ sw $v0, ($a0) -/* 328C84 E000CB74 24020040 */ addiu $v0, $zero, 0x40 -/* 328C88 E000CB78 AC970004 */ sw $s7, 4($a0) -/* 328C8C E000CB7C AC860008 */ sw $a2, 8($a0) -/* 328C90 E000CB80 AC82000C */ sw $v0, 0xc($a0) -/* 328C94 E000CB84 8FC20008 */ lw $v0, 8($fp) -/* 328C98 E000CB88 24840010 */ addiu $a0, $a0, 0x10 -/* 328C9C E000CB8C 0222102A */ slt $v0, $s1, $v0 -/* 328CA0 E000CB90 1440FFD5 */ bnez $v0, .LE000CAE8 -/* 328CA4 E000CB94 AE440000 */ sw $a0, ($s2) -.LE000CB98: -/* 328CA8 E000CB98 3C03D838 */ lui $v1, 0xd838 -/* 328CAC E000CB9C 3C04800A */ lui $a0, %hi(gMasterGfxPos) -/* 328CB0 E000CBA0 2484A66C */ addiu $a0, $a0, %lo(gMasterGfxPos) -/* 328CB4 E000CBA4 8C820000 */ lw $v0, ($a0) -/* 328CB8 E000CBA8 34630002 */ ori $v1, $v1, 2 -/* 328CBC E000CBAC 0040282D */ daddu $a1, $v0, $zero -/* 328CC0 E000CBB0 24420008 */ addiu $v0, $v0, 8 -/* 328CC4 E000CBB4 AC820000 */ sw $v0, ($a0) -/* 328CC8 E000CBB8 ACA30000 */ sw $v1, ($a1) -/* 328CCC E000CBBC 24030040 */ addiu $v1, $zero, 0x40 -/* 328CD0 E000CBC0 ACA30004 */ sw $v1, 4($a1) -/* 328CD4 E000CBC4 24430008 */ addiu $v1, $v0, 8 -/* 328CD8 E000CBC8 AC830000 */ sw $v1, ($a0) -/* 328CDC E000CBCC 3C03E700 */ lui $v1, 0xe700 -/* 328CE0 E000CBD0 AC430000 */ sw $v1, ($v0) -/* 328CE4 E000CBD4 AC400004 */ sw $zero, 4($v0) -/* 328CE8 E000CBD8 8FBF0104 */ lw $ra, 0x104($sp) -/* 328CEC E000CBDC 8FBE0100 */ lw $fp, 0x100($sp) -/* 328CF0 E000CBE0 8FB700FC */ lw $s7, 0xfc($sp) -/* 328CF4 E000CBE4 8FB600F8 */ lw $s6, 0xf8($sp) -/* 328CF8 E000CBE8 8FB500F4 */ lw $s5, 0xf4($sp) -/* 328CFC E000CBEC 8FB400F0 */ lw $s4, 0xf0($sp) -/* 328D00 E000CBF0 8FB300EC */ lw $s3, 0xec($sp) -/* 328D04 E000CBF4 8FB200E8 */ lw $s2, 0xe8($sp) -/* 328D08 E000CBF8 8FB100E4 */ lw $s1, 0xe4($sp) -/* 328D0C E000CBFC 8FB000E0 */ lw $s0, 0xe0($sp) -/* 328D10 E000CC00 03E00008 */ jr $ra -/* 328D14 E000CC04 27BD0108 */ addiu $sp, $sp, 0x108 -/* 328D18 E000CC08 00000000 */ nop -/* 328D1C E000CC0C 00000000 */ nop diff --git a/ver/us/symbol_addrs.txt b/ver/us/symbol_addrs.txt index 507f0b4a1b..217dbf2332 100644 --- a/ver/us/symbol_addrs.txt +++ b/ver/us/symbol_addrs.txt @@ -882,7 +882,7 @@ contPakFileState = 0x8005F8A4; // type:func rom:0x3ACA4 contPakFileNum = 0x8005F8D0; // type:func rom:0x3ACD0 contPakRepairId = 0x8005F8FC; // type:func rom:0x3ACFC osMotorStop = 0x8005F920; // type:func rom:0x3AD20 -contRmbControl = 0x8005FB10; // type:func rom:0x3AF10 +contRmbRetrace = 0x8005FB10; // type:func rom:0x3AF10 nuContRmbMgrInit = 0x8005FC8C; // type:func rom:0x3B08C nuContRmbMgrRemove = 0x8005FCEC; // type:func rom:0x3B0EC contRmbCheckMesg = 0x8005FD10; // type:func rom:0x3B110 @@ -4894,7 +4894,7 @@ func_E000C0C8 = 0xE000C0C8; // type:func rom:0x3281D8 fx_6_main = 0xE000C160; // type:func rom:0x328270 fx_6_init = 0xE000C64C; // type:func rom:0x32875C fx_6_update = 0xE000C654; // type:func rom:0x328764 -fx_6_renderWorld = 0xE000C754; // type:func rom:0x328864 +fx_6_render = 0xE000C754; // type:func rom:0x328864 func_E000C798 = 0xE000C798; // type:func rom:0x3288A8 fx_7_main = 0xE000E000; // type:func rom:0x32C110 func_E000E16C = 0xE000E16C; // type:func rom:0x32C27C @@ -18529,9 +18529,7 @@ D_800DA47C = 0x800DA47C; // type:data D_800DA4B4 = 0x800DA4B4; // type:data D_800DA55C = 0x800DA55C; // type:data D_800DAAA8 = 0x800DAAA8; // type:data -D_800DAAAC = 0x800DAAAC; // type:data D_800DAAB8 = 0x800DAAB8; // type:data -D_800DAABC = 0x800DAABC; // type:data D_800DAABD = 0x800DAABD; // type:data D_800DAAC0 = 0x800DAAC0; // type:data nuContPfs = 0x800DAAD8; // @@ -18539,10 +18537,6 @@ nuSiMesgQ = 0x800DAC78; // D_800DAC90 = 0x800DAC90; // type:data D_800DACA8 = 0x800DACA8; // type:data gCurrentSaveFile = 0x800DACC0; // -D_800DBC70 = 0x800DBC70; // type:data -D_800DBD70 = 0x800DBD70; // type:data -D_800DBF70 = 0x800DBF70; // type:data -D_800DBF90 = 0x800DBF90; // type:data nuContDataMutexQ = 0x800DC040; // type:data D_800DC060 = 0x800DC060; // type:data D_800DC064 = 0x800DC064; // type:data @@ -20584,7 +20578,7 @@ D_E00027B0 = 0xE00027B0; // type:data D_E00027D8 = 0xE00027D8; // type:data D_E0002800 = 0xE0002800; // type:data D_E000CC10 = 0xE000CC10; // type:data -D_E000CC24 = 0xE000CC24; // type:data +sDlists2 = 0xE000CC24; // type:data D_E000CD24 = 0xE000CD24; // type:data jtbl_E000CD70 = 0xE000CD70; // type:data D_E000E684 = 0xE000E684; // type:data diff --git a/ver/us/undefined_syms.txt b/ver/us/undefined_syms.txt index 763deaf755..7aa9a4d948 100644 --- a/ver/us/undefined_syms.txt +++ b/ver/us/undefined_syms.txt @@ -1411,7 +1411,6 @@ D_800A0F44 = 0x800A0F44; D_800A0F50 = 0x800A0F50; D_800A0F58 = 0x800A0F58; D_800A1530 = 0x800A1530; -dead_gCurrentCamID = 0x800A158C; D_800A15A4 = 0x800A15A4; D_800A15A8 = 0x800A15A8; D_800A15C4 = 0x800A15C4; @@ -1539,7 +1538,6 @@ D_800B4520 = 0x800B4520; D_800B6590 = 0x800B6590; D_800B7EF0 = 0x800B7EF0; nuYieldBuf = 0x800B8590; -dead_gCameras = 0x800B8D80; D_800B8DEC = 0x800B8DEC; D_800B91A0 = 0x800B91A0; D_800B91D0 = 0x800B91D0; @@ -1566,9 +1564,7 @@ D_800DA47C = 0x800DA47C; D_800DA4B4 = 0x800DA4B4; D_800DA55C = 0x800DA55C; D_800DAAA8 = 0x800DAAA8; -D_800DAAAC = 0x800DAAAC; D_800DAAB8 = 0x800DAAB8; -D_800DAABC = 0x800DAABC; D_800DAABD = 0x800DAABD; D_800DAAC0 = 0x800DAAC0; nuContPfs = 0x800DAAD8; @@ -1576,10 +1572,6 @@ nuSiMesgQ = 0x800DAC78; D_800DAC90 = 0x800DAC90; D_800DACA8 = 0x800DACA8; gCurrentSaveFile = 0x800DACC0; -D_800DBC70 = 0x800DBC70; -D_800DBD70 = 0x800DBD70; -D_800DBF70 = 0x800DBF70; -D_800DBF90 = 0x800DBF90; nuContDataMutexQ = 0x800DC040; D_800DC060 = 0x800DC060; D_800DC064 = 0x800DC064; @@ -1599,6 +1591,8 @@ D_800DC4F0 = 0x800DC4F0; D_800DC4F4 = 0x800DC4F4; D_800DC4F8 = 0x800DC4F8; D_800DC4FC = 0x800DC4FC; +// end of first segment's BSS + D_8010C920 = 0x8010C920; D_8010C924 = 0x8010C924; D_8010C928 = 0x8010C928; @@ -1722,6 +1716,8 @@ gSpinHistoryPosAngle = 0x8010F6B8; // Invalid pointers from dead code dead_atan2 = 0x8002AF70; +dead_gCurrentCamID = 0x800A158C; +dead_gCameras = 0x800B8D80; D_80100060 = 0x80100060; D_801000A0 = 0x801000A0; dead_evt_get_variable = 0x802D4E8C; @@ -1741,8 +1737,8 @@ dead_get_enemy = 0x80042AF4; dead_dist2D = 0x8002B1C4; dead_get_clamped_angle_diff = 0x8002AF18; dead_clamp_angle = 0x8002AE14; -dead_ai_enemy_play_sound = 0x8004D800; dead_add_vec2D_polar = 0x8002B28C; +dead_ai_enemy_play_sound = 0x8004D800; func_8004D9C0 = 0x8004D9C0; func_8004DAD8 = 0x8004DAD8; func_8004DF64 = 0x8004DF64;