diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 05369174..87e43d4e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,14 +45,8 @@ jobs: pip install -r requirements.txt pip install types-PyYAML - - name: ruff split - run: ruff check split.py - - - name: ruff create_config - run: ruff check create_config.py - - - name: ruff test - run: ruff check test.py + - name: ruff + run: ruff check . mypy_splat_checks: runs-on: ubuntu-latest @@ -94,11 +88,5 @@ jobs: pip install -r requirements.txt pip install types-PyYAML - - name: mypy split - run: mypy --show-column-numbers --hide-error-context split.py - - - name: mypy create_config - run: mypy --show-column-numbers --hide-error-context create_config.py - - - name: mypy test - run: mypy --show-column-numbers --hide-error-context test.py + - name: mypy + run: mypy --show-column-numbers --hide-error-context *.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e873fcb3..46acbbb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # splat Release Notes +### 0.37.3 + +* create_config: Avoid emitting relocations for addresses paired by using `ori` + ### 0.37.2 + * Add new option `sort_segments_by_vram_dependency` to help with non-matching builds for binaries with complicated memory layouts. See the wiki for details. * Fix create_config missing bss segments due to unsigned LO instructions. diff --git a/README.md b/README.md index 315943ba..4a8d33d6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The brackets corresponds to the optional dependencies to install while installin If you use a `requirements.txt` file in your repository, then you can add this library with the following line: ```txt -splat64[mips]>=0.37.2,<1.0.0 +splat64[mips]>=0.37.3,<1.0.0 ``` ### Optional dependencies diff --git a/pyproject.toml b/pyproject.toml index 07b7a43c..9c4d794e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "splat64" # Should be synced with src/splat/__init__.py -version = "0.37.2" +version = "0.37.3" description = "A binary splitting tool to assist with decompilation and modding projects" readme = "README.md" license = {file = "LICENSE"} diff --git a/src/splat/__init__.py b/src/splat/__init__.py index 87fbd9cb..301b77a9 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -1,7 +1,7 @@ __package_name__ = __name__ # Should be synced with pyproject.toml -__version__ = "0.37.2" +__version__ = "0.37.3" __author__ = "ethteck" from . import util as util diff --git a/src/splat/scripts/create_config.py b/src/splat/scripts/create_config.py index 11425f7a..eed507cd 100644 --- a/src/splat/scripts/create_config.py +++ b/src/splat/scripts/create_config.py @@ -193,32 +193,37 @@ def create_n64_config(rom_path: Path): file_presets.write_all_files() # Write reloc_addrs.txt file - reloc_addrs = [] - if rom.entrypoint_info.bss_start_address is not None: - reloc_addrs.append( - f"rom:0x{rom.entrypoint_info.bss_start_address.rom_hi:06X} reloc:MIPS_HI16 symbol:main_BSS_START" - ) - reloc_addrs.append( - f"rom:0x{rom.entrypoint_info.bss_start_address.rom_lo:06X} reloc:MIPS_LO16 symbol:main_BSS_START" - ) - reloc_addrs.append("") - if rom.entrypoint_info.bss_size is not None: - reloc_addrs.append( - f"rom:0x{rom.entrypoint_info.bss_size.rom_hi:06X} reloc:MIPS_HI16 symbol:main_BSS_SIZE" - ) - reloc_addrs.append( - f"rom:0x{rom.entrypoint_info.bss_size.rom_lo:06X} reloc:MIPS_LO16 symbol:main_BSS_SIZE" - ) - reloc_addrs.append("") - if rom.entrypoint_info.bss_end_address is not None: + reloc_addrs: list[str] = [] + + addresses_info: list[tuple[Optional[rominfo.EntryAddressInfo], str]] = [ + (rom.entrypoint_info.main_address, "main"), + (rom.entrypoint_info.bss_start_address, "main_BSS_START"), + (rom.entrypoint_info.bss_size, "main_BSS_SIZE"), + (rom.entrypoint_info.bss_end_address, "main_BSS_END"), + ] + + for addr_info, sym_name in addresses_info: + if addr_info is None: + continue + if addr_info.ori: + # Avoid emitting relocations for `ori`s since `%lo` doesn't support it. + continue + if addr_info.rom_hi == addr_info.rom_lo: + # hi and lo may be the same for the "main" address, i.e. a direct jal. + continue + reloc_addrs.append( - f"rom:0x{rom.entrypoint_info.bss_end_address.rom_hi:06X} reloc:MIPS_HI16 symbol:main_BSS_END" + f"rom:0x{addr_info.rom_hi:06X} reloc:MIPS_HI16 symbol:{sym_name}" ) reloc_addrs.append( - f"rom:0x{rom.entrypoint_info.bss_end_address.rom_lo:06X} reloc:MIPS_LO16 symbol:main_BSS_END" + f"rom:0x{addr_info.rom_lo:06X} reloc:MIPS_LO16 symbol:{sym_name}" ) reloc_addrs.append("") - if rom.entrypoint_info.stack_top is not None: + + if ( + rom.entrypoint_info.stack_top is not None + and not rom.entrypoint_info.stack_top.ori + ): reloc_addrs.append( '// This entry corresponds to the "stack top", which is the end of the array used as the stack for the main segment.' ) diff --git a/src/splat/util/n64/rominfo.py b/src/splat/util/n64/rominfo.py index 04b6c07e..772c273d 100755 --- a/src/splat/util/n64/rominfo.py +++ b/src/splat/util/n64/rominfo.py @@ -74,13 +74,14 @@ class EntryAddressInfo: value: int rom_hi: int rom_lo: int + ori: bool @staticmethod def new( - value: Optional[int], hi: Optional[int], lo: Optional[int] + value: Optional[int], hi: Optional[int], lo: Optional[int], ori: Optional[int] ) -> Optional["EntryAddressInfo"]: if value is not None and hi is not None and lo is not None: - return EntryAddressInfo(value, hi, lo) + return EntryAddressInfo(value, hi, lo, ori == lo) return None @@ -94,6 +95,7 @@ class N64EntrypointInfo: main_address: Optional[EntryAddressInfo] stack_top: Optional[EntryAddressInfo] traditional_entrypoint: bool + ori_entrypoint: bool def segment_size(self) -> int: if self.data_size is not None: @@ -120,6 +122,10 @@ def parse_rom_bytes( completed_pair = [False for _ in range(32)] hi_assignments: List[Optional[int]] = [None for _ in range(32)] lo_assignments: List[Optional[int]] = [None for _ in range(32)] + # We need to track if something was paired using an ori instead of an + # addiu or similar, because if that's the case we can't emit normal + # relocations in the generated symbol_addrs file for it. + ori_assignments: List[Optional[int]] = [None for _ in range(32)] register_bss_address: Optional[int] = None register_bss_size: Optional[int] = None @@ -130,6 +136,7 @@ def parse_rom_bytes( bss_end_address: Optional[EntryAddressInfo] = None traditional_entrypoint = True + ori_entrypoint = False decrementing_bss_routine = True data_size: Optional[int] = None func_call_target: Optional[EntryAddressInfo] = None @@ -163,6 +170,9 @@ def parse_rom_bytes( ) completed_pair[insn.rt.value] = True lo_assignments[insn.rt.value] = current_rom + if insn.isUnsigned(): + ori_assignments[insn.rt.value] = current_rom + ori_entrypoint = True elif insn.doesStore(): if insn.rt == rabbitizer.RegGprO32.zero: # Try to detect the zero-ing bss algorithm @@ -208,11 +218,13 @@ def parse_rom_bytes( register_values[insn.rs.value], hi_assignments[insn.rs.value], lo_assignments[insn.rs.value], + ori_assignments[insn.rs.value], ) bss_end_address = EntryAddressInfo.new( register_values[insn.rt.value], hi_assignments[insn.rt.value], lo_assignments[insn.rt.value], + ori_assignments[insn.rt.value], ) elif insn.isFunctionCall(): @@ -221,7 +233,7 @@ def parse_rom_bytes( # entrypoint to actual code. traditional_entrypoint = False func_call_target = EntryAddressInfo( - insn.getInstrIndexAsVram(), current_rom, current_rom + insn.getInstrIndexAsVram(), current_rom, current_rom, False ) elif insn.uniqueId == rabbitizer.InstrId.cpu_break: @@ -254,12 +266,14 @@ def parse_rom_bytes( register_values[register_bss_address], hi_assignments[register_bss_address], lo_assignments[register_bss_address], + ori_assignments[register_bss_address], ) if register_bss_size is not None: bss_size = EntryAddressInfo.new( register_values[register_bss_size], hi_assignments[register_bss_size], lo_assignments[register_bss_size], + ori_assignments[register_bss_size], ) if register_main_address is not None: @@ -267,6 +281,7 @@ def parse_rom_bytes( register_values[register_main_address], hi_assignments[register_main_address], lo_assignments[register_main_address], + ori_assignments[register_main_address], ) else: main_address = None @@ -275,6 +290,7 @@ def parse_rom_bytes( register_values[rabbitizer.RegGprO32.sp.value], hi_assignments[rabbitizer.RegGprO32.sp.value], lo_assignments[rabbitizer.RegGprO32.sp.value], + ori_assignments[rabbitizer.RegGprO32.sp.value], ) if not traditional_entrypoint: @@ -299,6 +315,7 @@ def parse_rom_bytes( main_address, stack_top, traditional_entrypoint, + ori_entrypoint, ) diff --git a/test_n64_entrypoints.py b/test_n64_entrypoints.py index 4efb5091..c918a4e2 100644 --- a/test_n64_entrypoints.py +++ b/test_n64_entrypoints.py @@ -180,10 +180,10 @@ def test_traditional_a(self): lui(T1, UHI(BSS_SIZE)), addiu(T0, T0, LO(BSS_START)), ori(T1, T1, LO(BSS_SIZE)), - addi(T1, T1, 0xFFF8), + addi(T1, T1, -0x8), sw(ZERO, 0, T0), sw(ZERO, 4, T0), - bnez(T1, 0xFFFC), + bnez(T1, -0x4), addi(T0, T0, 8), lui(T2, HI(MAIN_ADDR)), lui(SP, HI(STACK_TOP)), @@ -192,11 +192,26 @@ def test_traditional_a(self): addiu(SP, SP, LO(STACK_TOP)), ] info = parse(words, vram=0x80246000) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertTrue(info.bss_size.ori) + self.assertIsNone(info.bss_end_address) def test_traditional_a_li(self): @@ -207,10 +222,10 @@ def test_traditional_a_li(self): lui(T0, HI(BSS_START)), addiu(T0, T0, LO(BSS_START)), addiu(T1, ZERO, LO(BSS_SIZE)), - addi(T1, T1, 0xFFF8), + addi(T1, T1, -0x8), sw(ZERO, 0, T0), sw(ZERO, 4, T0), - bnez(T1, 0xFFFC), + bnez(T1, -0x4), addi(T0, T0, 8), lui(T2, HI(MAIN_ADDR)), lui(SP, HI(STACK_TOP)), @@ -219,24 +234,36 @@ def test_traditional_a_li(self): addiu(SP, SP, LO(STACK_TOP)), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + self.assertIsNone(info.bss_size) def test_traditional_a_li_ori(self): """bss_size loaded via ori $t1,$zero,imm.""" - BSS_SIZE = BSS_SIZE_SMALL # TODO: parse bss_size from li/ori pattern + BSS_SIZE = BSS_SIZE_SMALL # TODO: parse bss_size from plain ori pattern words = [ lui(T0, HI(BSS_START)), addiu(T0, T0, LO(BSS_START)), ori(T1, ZERO, LO(BSS_SIZE)), - addi(T1, T1, 0xFFF8), + addi(T1, T1, -0x8), sw(ZERO, 0, T0), sw(ZERO, 4, T0), - bnez(T1, 0xFFFC), + bnez(T1, -0x4), addi(T0, T0, 8), lui(T2, HI(MAIN_ADDR)), lui(SP, HI(STACK_TOP)), @@ -245,10 +272,22 @@ def test_traditional_a_li_ori(self): addiu(SP, SP, LO(STACK_TOP)), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + self.assertIsNone(info.bss_size) def test_traditional_b(self): @@ -261,8 +300,8 @@ def test_traditional_b(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bnez(T1, 0xFFFB), + addi(T1, T1, -0x8), + bnez(T1, -0x5), nop(), lui(T2, HI(MAIN_ADDR)), addiu(T2, T2, LO(MAIN_ADDR)), @@ -271,11 +310,25 @@ def test_traditional_b(self): addiu(SP, SP, LO(STACK_TOP)), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertFalse(info.bss_size.ori) def test_traditional_b_nop(self): """traditional_b with nop as jr delay slot.""" @@ -287,8 +340,8 @@ def test_traditional_b_nop(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bnez(T1, 0xFFFB), + addi(T1, T1, -0x8), + bnez(T1, -0x5), nop(), lui(T2, HI(MAIN_ADDR)), addiu(T2, T2, LO(MAIN_ADDR)), @@ -298,11 +351,25 @@ def test_traditional_b_nop(self): nop(), ] info = parse(words, vram=0x80000450) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertFalse(info.bss_size.ori) def test_traditional_b_ori_sp(self): """traditional_b with ori for $sp lo half.""" @@ -314,8 +381,8 @@ def test_traditional_b_ori_sp(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bnez(T1, 0xFFFB), + addi(T1, T1, -0x8), + bnez(T1, -0x5), nop(), lui(T2, HI(MAIN_ADDR)), addiu(T2, T2, LO(MAIN_ADDR)), @@ -324,11 +391,25 @@ def test_traditional_b_ori_sp(self): ori(SP, SP, LO(STACK_TOP)), ] info = parse(words, vram=0x80300000) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertFalse(info.bss_size.ori) def test_traditional_c(self): """lui order swapped (lui/lui/addiu/addiu).""" @@ -340,8 +421,8 @@ def test_traditional_c(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bnez(T1, 0xFFFB), + addi(T1, T1, -0x8), + bnez(T1, -0x5), nop(), lui(T2, HI(MAIN_ADDR)), lui(SP, HI(STACK_TOP)), @@ -351,11 +432,25 @@ def test_traditional_c(self): nop(), ] info = parse(words, vram=0x80180000) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertFalse(info.bss_size.ori) def test_traditional_d(self): """$sp set before BSS loop.""" @@ -367,8 +462,8 @@ def test_traditional_d(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bnez(T1, 0xFFFB), + addi(T1, T1, -0x8), + bnez(T1, -0x5), nop(), lui(SP, HI(STACK_TOP)), addiu(SP, SP, LO(STACK_TOP)), @@ -378,11 +473,25 @@ def test_traditional_d(self): nop(), ] info = parse(words, vram=0x80125C00) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertFalse(info.bss_size.ori) def test_traditional_d_ori(self): """bss_size uses ori for lo half (lui+ori pair).""" @@ -394,8 +503,8 @@ def test_traditional_d_ori(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bnez(T1, 0xFFFB), + addi(T1, T1, -0x8), + bnez(T1, -0x5), nop(), lui(SP, HI(STACK_TOP)), addiu(SP, SP, LO(STACK_TOP)), @@ -405,11 +514,25 @@ def test_traditional_d_ori(self): nop(), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_size is not None self.assertEqual(info.bss_size.value, BSS_SIZE) + self.assertTrue(info.bss_size.ori) def test_traditional_d_bgtz(self): """uses bgtz instead of bnez for BSS loop.""" @@ -423,8 +546,8 @@ def test_traditional_d_bgtz(self): sw(ZERO, 0, T0), sw(ZERO, 4, T0), addi(T0, T0, 8), - addi(T1, T1, 0xFFF8), - bgtz(T1, 0xFFFB), + addi(T1, T1, -0x8), + bgtz(T1, -0x5), nop(), lui(T2, HI(MAIN_ADDR)), addiu(T2, T2, LO(MAIN_ADDR)), @@ -438,10 +561,22 @@ def test_traditional_d_bgtz(self): nop(), ] info = parse(words, vram=0x80025C00) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + self.assertIsNone(info.bss_size) def test_direct_jump(self): @@ -454,10 +589,20 @@ def test_direct_jump(self): addiu(SP, SP, LO(STACK_TOP)), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + self.assertIsNone(info.bss_start_address) + self.assertIsNone(info.bss_size) @@ -477,19 +622,34 @@ def test_sltu_clear(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), break_(), ] info = parse(words, vram=0x80100000) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) + self.assertIsNone(info.bss_size) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) self.assertEqual(info.entry_size, 60) def test_sltu_clear_ori_sp(self): @@ -505,18 +665,33 @@ def test_sltu_clear_ori_sp(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), break_(), ] info = parse(words, vram=0x80100400) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + self.assertEqual(info.entry_size, 60) def test_sltu_clear_ori_sp_double(self): @@ -535,8 +710,8 @@ def test_sltu_clear_ori_sp_double(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(T0, HI(BSS2_START)), addiu(T0, T0, LO(BSS2_START)), lui(T1, HI(BSS2_END)), @@ -545,17 +720,31 @@ def test_sltu_clear_ori_sp_double(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), ] info = parse(words, vram=0x8004B400) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) def test_sltu_clear_ori_sp_double_gp(self): """sltu with ori $sp, double BSS clear, and $gp setup.""" @@ -570,8 +759,8 @@ def test_sltu_clear_ori_sp_double_gp(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(T0, HI(BSS_START)), addiu(T0, T0, LO(BSS_START)), lui(T1, HI(BSS_START)), @@ -580,19 +769,33 @@ def test_sltu_clear_ori_sp_double_gp(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(GP, 0x0000), addiu(GP, GP, 0x0000), jal(MAIN_ADDR), nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) def test_sltu_clear_ori_sp_t2(self): """sltu with ori $sp, TLB setup after loop (no jal).""" @@ -610,8 +813,8 @@ def test_sltu_clear_ori_sp_t2(self): nop(), addiu(T0, T0, 4), sltu(T2, T0, T1), - bnez(T2, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(T2, -0x3), + sw(ZERO, -0x4, T0), addiu(A0, ZERO, TLB_COUNT), mfc0(T0, 10), mtc0(A0, 0), @@ -624,11 +827,23 @@ def test_sltu_clear_ori_sp_t2(self): nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + self.assertIsNone(info.main_address) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) def test_sltu_clear_double(self): """sltu with double BSS clear, addiu $sp.""" @@ -643,8 +858,8 @@ def test_sltu_clear_double(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(T0, HI(BSS_START)), addiu(T0, T0, LO(BSS_START)), lui(T1, HI(BSS_START)), @@ -653,17 +868,31 @@ def test_sltu_clear_double(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) def test_sltu_clear_double_gp(self): """sltu with double BSS clear and $gp.""" @@ -678,8 +907,8 @@ def test_sltu_clear_double_gp(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(T0, HI(BSS_START)), addiu(T0, T0, LO(BSS_START)), lui(T1, HI(BSS_START)), @@ -688,19 +917,33 @@ def test_sltu_clear_double_gp(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(GP, 0x0000), addiu(GP, GP, 0x0000), jal(MAIN_ADDR), nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) def test_sltu_clear_jal(self): """sltu without beq guard, jal + break.""" @@ -713,18 +956,32 @@ def test_sltu_clear_jal(self): addiu(T1, T1, LO(BSS_END)), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), break_(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) self.assertEqual(info.entry_size, 52) def test_sltu_clear_size(self): @@ -743,18 +1000,32 @@ def test_sltu_clear_size(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), break_(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) self.assertEqual(info.entry_size, 64) def test_sltu_clear_tlb(self): @@ -773,8 +1044,8 @@ def test_sltu_clear_tlb(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), addiu(A0, ZERO, TLB_COUNT), mfc0(T0, 10), mtc0(A0, 0), @@ -787,11 +1058,23 @@ def test_sltu_clear_tlb(self): nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + self.assertIsNone(info.main_address) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) def test_sltu_clear_magic(self): """sltu with magic constant (FACEFACE) store after BSS clear.""" @@ -808,8 +1091,8 @@ def test_sltu_clear_magic(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), lui(T0, HI(BSS_START)), addiu(T0, T0, LO(BSS_START)), lui(AT, UHI(MAGIC)), @@ -821,11 +1104,25 @@ def test_sltu_clear_magic(self): break_(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) self.assertEqual(info.entry_size, 84) @@ -841,9 +1138,18 @@ def test_sn64_jal(self): nop(), ] info = parse(words, vram=0x80200400) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + self.assertIsNone(info.bss_start_address) def test_sn64_jal_addiu(self): @@ -856,9 +1162,18 @@ def test_sn64_jal_addiu(self): break_(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertFalse(info.stack_top.ori) + self.assertIsNone(info.bss_start_address) self.assertEqual(info.entry_size, 20) @@ -882,9 +1197,15 @@ def test_sn64_tlb(self): nop(), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + self.assertIsNone(info.main_address) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) def test_sn64_tlb_li(self): """SN64 TLB with li for loop counter.""" @@ -906,9 +1227,15 @@ def test_sn64_tlb_li(self): nop(), ] info = parse(words, vram=0x80300000) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + self.assertIsNone(info.main_address) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) class TestSpecialEntrypoints(unittest.TestCase): @@ -931,18 +1258,32 @@ def test_excitebike(self): nop(), addiu(T0, T0, 4), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), - sw(ZERO, 0xFFFC, T0), + bnez(AT, -0x3), + sw(ZERO, -0x4, T0), jal(MAIN_ADDR), nop(), break_(), ] info = parse(words, vram=0x80100400) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) self.assertEqual(info.entry_size, 68) def test_factor5_jump(self): @@ -955,9 +1296,19 @@ def test_factor5_jump(self): ori(SP, SP, LO(STACK_TOP)), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + self.assertIsNone(info.main_address) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + self.assertIsNone(info.bss_start_address) + self.assertIsNone(info.bss_size) + self.assertIsNone(info.bss_end_address) def test_factor5_cache(self): """multiple jals, last one overrides main_address.""" @@ -990,9 +1341,17 @@ def test_factor5_cache(self): nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) def test_vigilante8(self): """sltu loop + j instruction (not tracked as main).""" @@ -1007,17 +1366,29 @@ def test_vigilante8(self): addiu(V1, V1, LO(BSS_END)), sw(ZERO, 0, V0), sltu(AT, V0, V1), - bnez(AT, 0xFFFD), + bnez(AT, -0x3), addiu(V0, V0, 4), j(MAIN_ADDR), nop(), ] info = parse(words, vram=0x80125800) + self.assertTrue(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + self.assertIsNone(info.main_address) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) + + assert info.bss_start_address is not None self.assertEqual(info.bss_start_address.value, BSS_START) + self.assertFalse(info.bss_start_address.ori) + + assert info.bss_end_address is not None self.assertEqual(info.bss_end_address.value, BSS_END) + self.assertFalse(info.bss_end_address.ori) def test_acclaim_jump(self): """bare j instruction (not recognized by parser).""" @@ -1027,8 +1398,12 @@ def test_acclaim_jump(self): nop(), ] info = parse(words) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + self.assertIsNone(info.main_address) + self.assertIsNone(info.stack_top) def test_army_men(self): @@ -1050,7 +1425,7 @@ def test_army_men(self): addiu(A1, A1, LO(STACK_TOP)), addu(SP, A1, ZERO), addu(FP, A1, ZERO), - addiu(GP, ZERO, 0xFFFF), + addiu(GP, ZERO, -0x1), lui(A0, HI(RANGE1_START)), addiu(A0, A0, LO(RANGE1_START)), lui(A0, HI(RANGE1_END)), @@ -1086,18 +1461,30 @@ def test_army_men(self): nop(), ] info = parse(words) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + self.assertIsNone(info.stack_top) def test_empty_entry(self): """All nops (no meaningful code at entrypoint).""" words = [nop()] * 16 info = parse(words, vram=0x80190000) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + self.assertIsNone(info.main_address) + self.assertIsNone(info.stack_top) + self.assertIsNone(info.bss_start_address) + self.assertIsNone(info.bss_size) def test_cheat_device(self): @@ -1129,22 +1516,22 @@ def test_cheat_device(self): sw(AT, 0, V0), addiu(V1, V1, 4), addiu(V0, V0, 4), - addiu(T0, T0, 0xFFFC), - bgtz(T0, 0xFFF9), + addiu(T0, T0, -0x4), + bgtz(T0, -0x7), nop(), lui(T0, UHI(CACHE1_START)), addiu(T1, T0, CACHE1_MID_OFFSET), addiu(T1, T1, CACHE1_END_ADJUST), cache(1, 0, T0), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), + bnez(AT, -0x3), addiu(T0, T0, CACHE1_LINE), lui(T0, UHI(CACHE2_START)), addiu(T1, T0, CACHE2_MID_OFFSET), addiu(T1, T1, CACHE2_END_ADJUST), cache(0, 0, T0), sltu(AT, T0, T1), - bnez(AT, 0xFFFD), + bnez(AT, -0x3), addiu(T0, T0, CACHE2_LINE), lui(SP, UHI(STACK_TOP)), ori(SP, SP, LO(STACK_TOP)), @@ -1156,9 +1543,17 @@ def test_cheat_device(self): nop(), ] info = parse(words, vram=0x80280000) + self.assertFalse(info.traditional_entrypoint) + self.assertTrue(info.ori_entrypoint) + + assert info.main_address is not None self.assertEqual(info.main_address.value, MAIN_ADDR) + self.assertFalse(info.main_address.ori) + + assert info.stack_top is not None self.assertEqual(info.stack_top.value, STACK_TOP) + self.assertTrue(info.stack_top.ori) def test_cheat_device_bal(self): """bgezal (BAL) + DMA loop.""" @@ -1179,13 +1574,17 @@ def test_cheat_device_bal(self): sw(AT, 0, V0), addiu(V1, V1, 4), addiu(V0, V0, 4), - addiu(T0, T0, 0xFFFC), - bgtz(T0, 0xFFF7), + addiu(T0, T0, -0x4), + bgtz(T0, -0x9), nop(), ] info = parse(words, vram=0x80401000) + self.assertTrue(info.traditional_entrypoint) + self.assertFalse(info.ori_entrypoint) + self.assertIsNone(info.main_address) + self.assertIsNone(info.stack_top)