isle-portable/tools/isledecomp/tests/test_islebin.py
2024-02-04 19:37:37 +01:00

146 lines
4.3 KiB
Python

"""Tests for the Bin (or IsleBin) module that:
1. Parses relevant data from the PE header and other structures.
2. Provides an interface to read from the DLL or EXE using a virtual address.
These are some basic smoke tests."""
import hashlib
from typing import Tuple
import pytest
from isledecomp.bin import (
Bin as IsleBin,
SectionNotFoundError,
InvalidVirtualAddressError,
)
# LEGO1.DLL: v1.1 English, September
LEGO1_SHA256 = "14645225bbe81212e9bc1919cd8a692b81b8622abb6561280d99b0fc4151ce17"
@pytest.fixture(name="binfile", scope="session")
def fixture_binfile(pytestconfig) -> IsleBin:
filename = pytestconfig.getoption("--lego1")
# Skip this if we have not provided the path to LEGO1.dll.
if filename is None:
pytest.skip(allow_module_level=True, reason="No path to LEGO1")
with open(filename, "rb") as f:
digest = hashlib.sha256(f.read()).hexdigest()
if digest != LEGO1_SHA256:
pytest.fail(reason="Did not match expected LEGO1.DLL")
with IsleBin(filename, find_str=True) as islebin:
yield islebin
def test_basic(binfile: IsleBin):
assert binfile.entry == 0x1008C860
assert len(binfile.sections) == 6
with pytest.raises(SectionNotFoundError):
binfile.get_section_by_name(".hello")
SECTION_INFO = (
(".text", 0x10001000, 0xD2A66, 0xD2C00),
(".rdata", 0x100D4000, 0x1B5B6, 0x1B600),
(".data", 0x100F0000, 0x1A734, 0x12C00),
(".idata", 0x1010B000, 0x1006, 0x1200),
(".rsrc", 0x1010D000, 0x21D8, 0x2200),
(".reloc", 0x10110000, 0x10C58, 0x10E00),
)
@pytest.mark.parametrize("name, v_addr, v_size, raw_size", SECTION_INFO)
def test_sections(name: str, v_addr: int, v_size: int, raw_size: int, binfile: IsleBin):
section = binfile.get_section_by_name(name)
assert section.virtual_address == v_addr
assert section.virtual_size == v_size
assert section.size_of_raw_data == raw_size
DOUBLE_PI_BYTES = b"\x18\x2d\x44\x54\xfb\x21\x09\x40"
# Now that's a lot of pi
PI_ADDRESSES = (
0x100D4000,
0x100D4700,
0x100D7180,
0x100DB8F0,
0x100DC030,
)
@pytest.mark.parametrize("addr", PI_ADDRESSES)
def test_read_pi(addr: int, binfile: IsleBin):
assert binfile.read(addr, 8) == DOUBLE_PI_BYTES
def test_unusual_reads(binfile: IsleBin):
"""Reads that return an error or some specific value based on context"""
# Reading an address earlier than the imagebase
with pytest.raises(InvalidVirtualAddressError):
binfile.read(0, 1)
# Really big address
with pytest.raises(InvalidVirtualAddressError):
binfile.read(0xFFFFFFFF, 1)
# Uninitialized part of .data
assert binfile.read(0x1010A600, 4) is None
# Past the end of virtual size in .text
assert binfile.read(0x100D3A70, 4) == b"\x00\x00\x00\x00"
STRING_ADDRESSES = (
(0x100DB588, b"November"),
(0x100F0130, b"Helicopter"),
(0x100F0144, b"HelicopterState"),
(0x100F0BE4, b"valerie"),
(0x100F4080, b"TARGET"),
)
@pytest.mark.parametrize("addr, string", STRING_ADDRESSES)
def test_strings(addr: int, string: bytes, binfile: IsleBin):
"""Test string read utility function and the string search feature"""
assert binfile.read_string(addr) == string
assert binfile.find_string(string) == addr
def test_relocation(binfile: IsleBin):
# n.b. This is not the number of *relocations* read from .reloc.
# It is the set of unique addresses in the binary that get relocated.
assert len(binfile.get_relocated_addresses()) == 14066
# Score::Score is referenced only by CALL instructions. No need to relocate.
assert binfile.is_relocated_addr(0x10001000) is False
# MxEntity::SetEntityId is in the vtable and must be relocated.
assert binfile.is_relocated_addr(0x10001070) is True
# Not sanitizing dll name case. Do we care?
IMPORT_REFS = (
("KERNEL32.dll", "CreateMutexA", 0x1010B3D0),
("WINMM.dll", "midiOutPrepareHeader", 0x1010B550),
)
@pytest.mark.parametrize("import_ref", IMPORT_REFS)
def test_imports(import_ref: Tuple[str, str, int], binfile: IsleBin):
assert import_ref in binfile.imports
# Location of the JMP instruction and the import address.
THUNKS = (
(0x100D3728, 0x1010B32C), # DirectDrawCreate
(0x10098F9E, 0x1010B3D4), # RtlUnwind
)
@pytest.mark.parametrize("thunk_ref", THUNKS)
def test_thunks(thunk_ref: Tuple[int, int], binfile: IsleBin):
assert thunk_ref in binfile.thunks