Example #1
0
def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "csv_path",
        help=
        "Path to a list of functions to identify (in the same format as the main function CSV)"
    )
    parser.add_argument("candidates_path",
                        help="Path to a list of candidates (names only)")
    args = parser.parse_args()

    csv_path = Path(args.csv_path)
    candidates_path = Path(args.candidates_path)

    candidates = read_candidates(candidates_path)

    new_matches: Dict[int, str] = dict()
    checker = util.checker.FunctionChecker()

    # Given a list L of functions to identify and a small list of candidates C, this tool will attempt to
    # automatically identify matches by checking each function in L against each function in C.
    #
    # This matching algorithm is quite naive (quadratic time complexity if both lists have about the same size)
    # but this should work well enough for short lists of candidates...
    for func in utils.get_functions(csv_path):
        if func.status != utils.FunctionStatus.NotDecompiled:
            continue

        match_name = ""

        for candidate_name, candidate in candidates.items():
            if len(candidate.data) != func.size:
                continue
            if checker.check(
                    util.elf.get_fn_from_base_elf(func.addr, func.size),
                    candidate):
                match_name = candidate_name
                break

        if match_name:
            new_matches[func.addr] = match_name
            utils.print_note(
                f"found new match: {Fore.BLUE}{match_name}{Fore.RESET} ({func.addr | 0x71_00000000:#018x})"
            )
            # This is no longer a candidate.
            del candidates[match_name]
        else:
            utils.warn(
                f"no match found for {Fore.BLUE}{func.name}{Fore.RESET} ({func.addr | 0x71_00000000:#018x})"
            )

    # Output the modified function CSV.
    writer = csv.writer(sys.stdout, lineterminator="\n")
    for func in utils.get_functions():
        if func.status == utils.FunctionStatus.NotDecompiled and func.addr in new_matches:
            func.raw_row[3] = new_matches[func.addr]
        writer.writerow(func.raw_row)
Example #2
0
def find_function_info(name: str):
    for info in utils.get_functions():
        if info.decomp_name == name or (find_wip and info.status
                                        == utils.FunctionStatus.Wip):
            return info

    for info in utils.get_functions():
        if name in cxxfilt.demangle(info.decomp_name):
            return info

    return None
Example #3
0
 def load_data_for_project(self) -> None:
     self.decompiled_fns = {
         func.addr: func.decomp_name
         for func in utils.get_functions() if func.decomp_name
     }
     self.get_data_symtab().load_from_csv(utils.get_repo_root() / "data" /
                                          "data_symbols.csv")
Example #4
0
def main() -> None:
    all_vtables = ai_common.get_vtables()
    names = ai_common.get_ai_vtable_names()
    not_decompiled = {func.addr for func in utils.get_functions() if func.status == utils.FunctionStatus.NotDecompiled}

    new_names: Dict[int, str] = dict()

    order = ai_common.topologically_sort_vtables(all_vtables, "AI")
    for vtable_addr in order:
        if vtable_addr in BaseClasses:
            continue

        class_name = names.get(vtable_addr)
        for i, fn_ea in enumerate(iterate_vtable(vtable_addr)):
            if idaapi.get_name(fn_ea) == "__cxa_pure_virtual":
                continue

            real_fn_ea = fn_ea & ~_ida_base
            if real_fn_ea not in new_names:
                if i < len(_vtable_fn_names):
                    new_names[real_fn_ea] = format_fn_name(_vtable_fn_names[i], class_name)
                else:
                    # Unknown member function.
                    new_names[real_fn_ea] = f"uking::ai::{class_name}::m{i}"

                if real_fn_ea in not_decompiled:
                    idaapi.set_name(fn_ea, new_names[real_fn_ea])

    utils.add_decompiled_functions(dict(), new_names)
Example #5
0
def main() -> None:
    parser = argparse.ArgumentParser(
        "Identifies matching functions by looking at function calls in matching functions"
    )
    parser.add_argument("-f", "--fn", help="Functions to analyze", nargs="*")
    args = parser.parse_args()

    functions_to_analyze = set(args.fn) if args.fn else set()

    functions_by_addr: Dict[int, utils.FunctionInfo] = {
        fn.addr: fn
        for fn in utils.get_functions()
    }
    fn_checker = Checker()
    for fn in functions_by_addr.values():
        if functions_to_analyze and fn.decomp_name not in functions_to_analyze:
            continue

        if fn.status != utils.FunctionStatus.Matching:
            continue

        base_fn = elf.get_fn_from_base_elf(fn.addr, fn.size)
        try:
            my_fn = elf.get_fn_from_my_elf(fn.decomp_name)
        except KeyError:
            utils.warn(f"could not find function {fn.decomp_name}")
            continue

        fn_checker.checking = fn.decomp_name
        fn_checker.check(base_fn, my_fn)

    if fn_checker.invalid_call_descriptions:
        for x in fn_checker.invalid_call_descriptions:
            utils.print_note(x)
        utils.fail("invalid calls detected")

    new_matches: Dict[int, str] = dict()
    calls = fn_checker.get_possible_calls().copy()
    for base_target, my_target in calls.items():
        target_info = functions_by_addr.get(base_target)
        if target_info is None:
            continue
        if target_info.status != utils.FunctionStatus.NotDecompiled:
            continue

        base_fn = elf.get_fn_from_base_elf(target_info.addr, target_info.size)
        try:
            name = fn_checker.addr_to_symbol[my_target]
            my_fn = elf.get_fn_from_my_elf(name)
        except KeyError:
            continue

        if fn_checker.check(base_fn, my_fn):
            new_matches[base_target] = name
            utils.print_note(
                f"new match: {Fore.BLUE}{cxxfilt.demangle(name)}{Fore.RESET}")

    utils.add_decompiled_functions(new_matches)
Example #6
0
def main() -> None:
    failed = False

    nonmatching_fns_with_dump = {
        p.stem: util.elf.Function(p.read_bytes(), 0)
        for p in (utils.get_repo_root() / "expected").glob("*.bin")
    }

    checker = util.checker.FunctionChecker(log_mismatch_cause=True)

    for func in utils.get_functions():
        if not func.decomp_name:
            continue

        try:
            util.elf.get_fn_from_my_elf(func.decomp_name)
        except KeyError:
            utils.warn(
                f"couldn't find {utils.format_symbol_name_for_msg(func.decomp_name)}"
            )
            continue

        if func.status == utils.FunctionStatus.Matching:
            if not check_function(checker, func.addr, func.size,
                                  func.decomp_name):
                utils.print_error(
                    f"function {utils.format_symbol_name_for_msg(func.decomp_name)} is marked as matching but does not match"
                )
                a1, a2, reason = checker.get_mismatch()
                if a1 != -1:
                    sys.stderr.write(
                        f"       at {a1 | 0x7100000000:#x} : {Fore.CYAN}{reason}{Fore.RESET}\n"
                    )
                failed = True
        elif func.status == utils.FunctionStatus.Equivalent or func.status == utils.FunctionStatus.NonMatching:
            if check_function(checker, func.addr, func.size, func.decomp_name):
                utils.print_note(
                    f"function {utils.format_symbol_name_for_msg(func.decomp_name)} is marked as non-matching but matches"
                )

            fn_dump = nonmatching_fns_with_dump.get(func.decomp_name, None)
            if fn_dump is not None and not check_function(
                    checker, func.addr, len(fn_dump), func.decomp_name,
                    fn_dump):
                utils.print_error(
                    f"function {utils.format_symbol_name_for_msg(func.decomp_name)} does not match expected output"
                )
                failed = True

    if failed:
        sys.exit(1)
Example #7
0
def dump_table(name: str) -> None:
    try:
        symbols = util.elf.build_addr_to_symbol_table(util.elf.my_symtab)
        decomp_symbols = {
            fn.decomp_name
            for fn in utils.get_functions() if fn.decomp_name
        }

        offset, size = util.elf.get_symbol_file_offset_and_size(
            util.elf.my_elf, util.elf.my_symtab, name)
        util.elf.my_elf.stream.seek(offset)
        vtable_bytes = util.elf.my_elf.stream.read(size)

        if not vtable_bytes:
            utils.fail(
                "empty vtable; has the key function been implemented? (https://lld.llvm.org/missingkeyfunction.html)"
            )

        print(
            f"{Fore.WHITE}{Style.BRIGHT}{cxxfilt.demangle(name)}{Style.RESET_ALL}"
        )
        print(f"{Fore.YELLOW}{Style.BRIGHT}vtable @ 0x0{Style.RESET_ALL}")

        assert size % 8 == 0
        for i in range(size // 8):
            word: int = struct.unpack_from("<Q", vtable_bytes, 8 * i)[0]
            name = symbols.get(word, None)
            if word == 0:
                pass
            elif name is not None:
                demangled_name: str = cxxfilt.demangle(name)
                color = Fore.GREEN if name in decomp_symbols else Fore.BLUE
                print(f"{color}{bold(demangled_name)}{Style.RESET_ALL}")
                print(f"    {name}")
            elif word & (1 << 63):
                offset = -struct.unpack_from("<q", vtable_bytes, 8 * i)[0]
                print()
                print(
                    f"{Fore.YELLOW}{Style.BRIGHT}vtable @ {offset:#x}{Style.RESET_ALL}"
                )
            else:
                print(f"{Fore.RED}unknown data: {word:016x}{Style.RESET_ALL}")

    except KeyError:
        utils.fail("could not find symbol")
Example #8
0
from colorama import Fore, Style
import diff_settings
import subprocess
from util import utils

parser = argparse.ArgumentParser(description="Prints build/uking.elf symbols")
parser.add_argument("--print-undefined", "-u",
                    help="Print symbols that are undefined", action="store_true")
parser.add_argument("--print-c2-d2", "-c",
                    help="Print C2/D2 (base object constructor/destructor) symbols", action="store_true")
parser.add_argument("--hide-unknown", "-H",
                    help="Hide symbols that are not present in the original game", action="store_true")
parser.add_argument("--all", "-a", action="store_true")
args = parser.parse_args()

listed_decomp_symbols = {info.decomp_name for info in utils.get_functions()}
original_symbols = {info.name for info in utils.get_functions()}

config: dict = dict()
diff_settings.apply(config, {})
myimg: str = config["myimg"]

entries = [x.strip().split() for x in subprocess.check_output(["nm", "-n", myimg], universal_newlines=True).split("\n")]

for entry in entries:
    if len(entry) == 3:
        addr = int(entry[0], 16)
        symbol_type: str = entry[1]
        name = entry[2]

        if (symbol_type == "t" or symbol_type == "T" or symbol_type == "W") and (
Example #9
0
code_size: tp.DefaultDict[FunctionStatus, int] = defaultdict(int)
counts: tp.DefaultDict[FunctionStatus, int] = defaultdict(int)

nonmatching_fns_with_dump = {
    p.stem
    for p in (Path(__file__).parent.parent / "expected").glob("*.bin")
}


def should_hide_nonmatching(name: str) -> bool:
    if not args.hide_nonmatchings_with_dumps:
        return False
    return name in nonmatching_fns_with_dump


for info in utils.get_functions():
    code_size_total += info.size
    num_total += 1

    if not info.decomp_name:
        continue

    counts[info.status] += 1
    code_size[info.status] += info.size

    if not args.csv:
        if info.status == FunctionStatus.NonMatching:
            if args.print_nm and not should_hide_nonmatching(info.decomp_name):
                print(
                    f"{Fore.RED}NM{Fore.RESET} {utils.format_symbol_name(info.decomp_name)}"
                )
def main() -> None:
    parser = argparse.ArgumentParser(description="Identifies matching AI class functions.")
    parser.add_argument("aidef")
    parser.add_argument("--type", choices=["Action", "AI", "Behavior", "Query"], required=True)
    args = parser.parse_args()

    type_: str = args.type

    new_matches: Dict[int, str] = dict()
    checker = util.checker.FunctionChecker()
    functions: Dict[str, utils.FunctionInfo] = {fn.name: fn for fn in utils.get_functions()}

    aidef = oead.byml.from_text(Path(args.aidef).read_text(encoding="utf-8"))

    def get_query_pairs(orig_name, name):
        prefix = f"AI_Query_{orig_name}::"
        return [
            (f"{prefix}ctor", f"_ZN5uking5query{len(name)}{name}C1ERKN4ksys3act2ai5Query7InitArgE"),
            (f"{prefix}dtor", f"_ZN5uking5query{len(name)}{name}D1Ev"),
            (f"{prefix}dtorDelete", f"_ZN5uking5query{len(name)}{name}D0Ev"),
            (f"{prefix}m10", f"_ZN5uking5query{len(name)}{name}10loadParamsERKN4evfl8QueryArgE"),
            (f"{prefix}loadParams", f"_ZN5uking5query{len(name)}{name}10loadParamsEv"),
            (f"{prefix}rtti1",
             f"_ZNK5uking5query{len(name)}{name}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE"),
            (f"{prefix}rtti2", f"_ZNK5uking5query{len(name)}{name}18getRuntimeTypeInfoEv"),
            (f"AI_F_Query_{orig_name}",
             f"_ZN4ksys3act2ai12QueryFactory4makeIN5uking5query{len(name)}{name}EEEPNS1_5QueryERKNS7_7InitArgEPN4sead4HeapE"),
        ]

    def get_action_pairs(orig_name, name):
        pairs = []

        def add_pair(x):
            pairs.append((x, x))

        pairs.append(
            (f"AI_Action_{orig_name}::ctor",
             f"_ZN5uking6action{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
        pairs.append(
            (f"AI_Action{orig_name}::ctor",
             f"_ZN5uking6action{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
        pairs.append((f"AI_F_Action_{orig_name}",
                      f"_ZN4ksys3act2ai13ActionFactory4makeIN5uking6action{len(name)}{name}EEEPNS1_6ActionERKNS1_10ActionBase7InitArgEPN4sead4HeapE"))
        add_pair(f"_ZN5uking6action{len(name)}{name}D1Ev")
        add_pair(f"_ZN5uking6action{len(name)}{name}D0Ev")
        add_pair(f"_ZN5uking6action{len(name)}{name}11loadParams_Ev")
        add_pair(f"_ZN5uking6action{len(name)}{name}5init_EPN4sead4HeapE")
        add_pair(f"_ZN5uking6action{len(name)}{name}6enter_EPN4ksys3act2ai15InlineParamPackE")
        add_pair(f"_ZN5uking6action{len(name)}{name}6leave_Ev")
        add_pair(f"_ZN5uking6action{len(name)}{name}5calc_Ev")
        add_pair(
            f"_ZNK5uking6action{len(name)}{name}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE")
        add_pair(f"_ZNK5uking6action{len(name)}{name}18getRuntimeTypeInfoEv")
        return pairs

    def get_ai_pairs(orig_name, name):
        pairs = []

        def add_pair(x):
            pairs.append((x, x))

        pairs.append(
            (f"AI_AI_{orig_name}::ctor", f"_ZN5uking2ai{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
        pairs.append(
            (f"AI_AI{orig_name}::ctor", f"_ZN5uking2ai{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
        pairs.append((f"AI_F_AI_{orig_name}",
                      f"_ZN4ksys3act2ai9AiFactory4makeIN5uking2ai{len(name)}{name}EEEPNS1_2AiERKNS1_10ActionBase7InitArgEPN4sead4HeapE"))
        add_pair(f"_ZN5uking2ai{len(name)}{name}D1Ev")
        add_pair(f"_ZN5uking2ai{len(name)}{name}D0Ev")
        add_pair(f"_ZN5uking2ai{len(name)}{name}11loadParams_Ev")
        add_pair(f"_ZN5uking2ai{len(name)}{name}5init_EPN4sead4HeapE")
        add_pair(f"_ZN5uking2ai{len(name)}{name}6enter_EPN4ksys3act2ai15InlineParamPackE")
        add_pair(f"_ZN5uking2ai{len(name)}{name}6leave_Ev")
        add_pair(f"_ZN5uking2ai{len(name)}{name}5calc_Ev")
        add_pair(f"_ZNK5uking2ai{len(name)}{name}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE")
        add_pair(f"_ZNK5uking2ai{len(name)}{name}18getRuntimeTypeInfoEv")
        return pairs

    if type_ == "Action":
        action_vtable_names = ai_common.get_action_vtable_names()
        identify(functions, checker, new_matches, action_vtable_names.values(), get_action_pairs)
    if type_ == "AI":
        ai_vtable_names = ai_common.get_ai_vtable_names()
        identify(functions, checker, new_matches, ai_vtable_names.values(), get_ai_pairs)
    elif type_ == "Query":
        identify(functions, checker, new_matches, aidef["Querys"].keys(), get_query_pairs)

    utils.add_decompiled_functions(new_matches)
def main() -> None:
    new_matches: Dict[int, str] = dict()
    functions_by_addr: Dict[int, utils.FunctionInfo] = {
        fn.addr: fn
        for fn in utils.get_functions()
    }

    md = cs.Cs(cs.CS_ARCH_ARM64, cs.CS_MODE_ARM)
    md.detail = True
    decomp_addr_to_symbol = elf.build_addr_to_symbol_table(elf.my_symtab)
    decomp_glob_data_table = elf.build_glob_data_table(elf.my_elf)

    processed: Set[int] = set()
    for fn in functions_by_addr.values():
        if fn.status != utils.FunctionStatus.Matching:
            continue

        if fn.size != 0x5C or (
                not fn.decomp_name.endswith("8getRuntimeTypeInfoEv")
                and not fn.name.endswith("rtti2")):
            continue

        base_fn = elf.get_fn_from_base_elf(fn.addr, fn.size)
        try:
            my_fn = elf.get_fn_from_my_elf(fn.decomp_name)
        except KeyError:
            utils.warn(f"could not find function {fn.decomp_name}")
            continue

        assert len(base_fn.data) == len(my_fn.data)

        vtable_ptr1 = 0
        vtable_ptr2 = 0
        for j, (i1, i2) in enumerate(
                zip(md.disasm(base_fn.data, base_fn.addr),
                    md.disasm(my_fn.data, my_fn.addr))):
            assert i1.mnemonic == i2.mnemonic
            if j == 10:
                assert i1.mnemonic == "adrp"
                assert i1.operands[0].reg == i2.operands[0].reg
                vtable_ptr1 = i1.operands[1].imm
                vtable_ptr2 = i2.operands[1].imm
            elif j == 11:
                assert i1.mnemonic == "ldr"
                assert i1.operands[0].reg == i2.operands[0].reg
                assert i1.operands[1].value.mem.base == i2.operands[
                    1].value.mem.base
                vtable_ptr1 += i1.operands[1].value.mem.disp
                vtable_ptr2 += i2.operands[1].value.mem.disp
                break

        assert vtable_ptr1 != 0 and vtable_ptr2 != 0
        if vtable_ptr1 in processed:
            continue
        processed.add(vtable_ptr1)
        ptr1, = struct.unpack("<Q",
                              elf.read_from_elf(elf.base_elf, vtable_ptr1, 8))
        ptr2 = decomp_glob_data_table[vtable_ptr2]

        vtable1 = elf.get_vtable_fns_from_base_elf(ptr1 + 0x10, num_entries=1)
        vtable2 = elf.unpack_vtable_fns(elf.read_from_elf(elf.my_elf,
                                                          addr=ptr2 + 0x10,
                                                          size=8),
                                        num_entries=1)

        if functions_by_addr[
                vtable1[0]].status == utils.FunctionStatus.Matching:
            continue

        decomp_derive_fn_addr = vtable2[0]
        if decomp_derive_fn_addr == 0:
            decomp_derive_fn_addr = decomp_glob_data_table.get(ptr2 + 0x10, 0)
        if decomp_derive_fn_addr == 0:
            raise RuntimeError(
                f"Derive virtual function pointer is null "
                f"(fn: {fn.decomp_name}, decomp vtable at {ptr2:#x})")

        name = decomp_addr_to_symbol[decomp_derive_fn_addr]
        new_matches[vtable1[0]] = name
        utils.print_note(
            f"new match: {Fore.BLUE}{cxxfilt.demangle(name)}{Fore.RESET} (from {fn.decomp_name})"
        )

    # overwrite the original names because they are likely to be incorrect
    utils.add_decompiled_functions(new_matches, new_orig_names=new_matches)