diff --git a/README.md b/README.md index 7862528..296aa39 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,79 @@ See [SETUP.md](./SETUP.md). To apply patcher on patch files run this command: -`python main.py [path to folder with patches] [path to clang++ executable] [path to linker executable (ld)] [path to g++ executable]` +`python main.py [path to config file]` + +config structure: +```json +{ + // path to target folder. Can be either relative or absolute. + // if relative path then it will be {config path}/{target_folder_path} + "target_folder_path": "FA-Binary-Patches", + // path to build folder. Defaults to "{target_folder_path}/build" + "build_folder_path": null, + // paths of input and output files + "input_exe_path": "ForgedAlliance_base.exe", + "output_exe_path": "C:\\ProgramData\\FAForever\\bin\\ForgedAlliance_exxt.exe", + // path to clang++ compiler. Defaults to "clang++" + "clang": "clang++.exe", + // path to g++ compiler. Defaults to "g++" + "gcc": "g++.exe", + // path to linker. Defaults to "ld" + "linker": "ld.exe", + // flags for compilers + "clang_flags": [ + "-pipe", + "-m32", + "-O3", + "-nostdlib", + "-Werror", + "-masm=intel", + "-std=c++20", + "-march=core2" + ], + "gcc_flags": [ + "-pipe", + "-m32", + "-Os", + "-fno-exceptions", + "-nostdlib", + "-nostartfiles", + "-fpermissive", + "-masm=intel", + "-std=c++20", + "-march=core2", + "-mfpmath=both" + ], + "asm_flags": [ + "-pipe", + "-m32", + "-Os", + "-fno-exceptions", + "-nostdlib", + "-nostartfiles", + "-w", + "-fpermissive", + "-masm=intel", + "-std=c++20", + "-march=core2", + "-mfpmath=both" + ] +} +``` ## Patches folder structure - **/build**: Here happens build for all source files. Patcher leaves address maps after build for debug purposes. +- **/hooks**: All files with asm that is injected by specified addresses. - **/include**: Header files. - **/section**: All files with patches that involve logic written with C/C++. - `*.cpp` files are built with *g++* - `*.cxx` files are built with *clang++* - Can contain nested folders - Can contain header files -- **/hooks**: All files with asm that is injected by specified addresses. -- ***define.h***: Generated file for hooks to use. + - Can contain `.hook` files +- ***config.json***: Config file for patcher. +- ***section.ld***: Main linker script. - ***SigPatches.txt***: File with signature patches. Replaces one binary sequence with another. Applied after build. - ***ForgedAlliance_base.exe***: Base executable of the game for patching. - ***ForgedAlliance_exxt.exe***: Result executable, run with [debugger](https://github.com/FAForever/FADeepProbe) for more information in case of crashes. @@ -33,18 +93,18 @@ To apply patcher on patch files run this command: Versions of compilers and linkers used. clang++ compiler: -* version 18.1.8 +* clang version 21.1.0 * Target: x86_64-pc-windows-msvc * Thread model: posix ld linker: -* GNU ld (GNU Binutils) 2.40 +* GNU ld (GNU Binutils) 2.39 g++ compiler: -* g++ (Rev6, Built by MSYS2 project) 13.1.0 +* g++ (i686-posix-dwarf-rev0, Built by MinGW-Builds project) 13.2.0 python: -* 3.12.4 +* 3.14.2 # HumanUserCalls.py diff --git a/config_example.json b/config_example.json new file mode 100644 index 0000000..7d09c0f --- /dev/null +++ b/config_example.json @@ -0,0 +1,45 @@ +{ + "target_folder_path": "FA-Binary-Patches", + "input_exe_path": "ForgedAlliance_base.exe", + "output_exe_path": "C:\\ProgramData\\FAForever\\bin\\ForgedAlliance_exxt.exe", + "clang": "clang++.exe", + "gcc": "g++.exe", + "linker": "ld.exe", + "clang_flags": [ + "-pipe", + "-m32", + "-O3", + "-nostdlib", + "-Werror", + "-masm=intel", + "-std=c++20", + "-march=core2" + ], + "gcc_flags": [ + "-pipe", + "-m32", + "-Os", + "-fno-exceptions", + "-nostdlib", + "-nostartfiles", + "-fpermissive", + "-masm=intel", + "-std=c++20", + "-march=core2", + "-mfpmath=both" + ], + "asm_flags": [ + "-pipe", + "-m32", + "-Os", + "-fno-exceptions", + "-nostdlib", + "-nostartfiles", + "-w", + "-fpermissive", + "-masm=intel", + "-std=c++20", + "-march=core2", + "-mfpmath=both" + ] +} \ No newline at end of file diff --git a/main.py b/main.py index 398a860..fdd8f8d 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,6 @@ if __name__ == "__main__": start = time.time() - patcher.patch(*sys.argv) + patcher.patch(*sys.argv[1:]) end = time.time() print(f"Patched in {end-start:.2f}s") diff --git a/patcher/Config.py b/patcher/Config.py new file mode 100644 index 0000000..dd611c2 --- /dev/null +++ b/patcher/Config.py @@ -0,0 +1,74 @@ +import json +from pathlib import Path +from dataclasses import dataclass, field +from typing import Self + + +@dataclass +class Config: + path: Path + target_folder_path: Path + build_folder_path: Path + input_exe_path: Path = "ForgedAlliance_base.exe" + output_exe_path: Path = "ForgedAlliance_exxt.exe" + + clang_path: Path = "clang++" + gcc_path: Path = "g++" + linker_path: Path = "ld" + + clang_flags: tuple[str] = () + gcc_flags: tuple[str] = () + asm_flags: tuple[str] = () + + @classmethod + def load_from_json(cls, path: Path) -> Self: + path = Path(path).resolve() + with open(path, 'r') as f: + config = json.load(f) + + return cls( + path=path, + target_folder_path=config.get("target_folder_path", path.parent), + build_folder_path=config.get("build_folder_path"), + input_exe_path=config.get("input_exe_path", Config.input_exe_path), + output_exe_path=config.get( + "output_exe_path", Config.output_exe_path), + clang_path=config.get("clang", Config.clang_path), + gcc_path=config.get("gcc", Config.gcc_path), + linker_path=config.get("linker", Config.linker_path), + clang_flags=config.get("clang_flags", Config.clang_flags), + gcc_flags=config.get("gcc_flags", Config.gcc_flags), + asm_flags=config.get("asm_flags", Config.asm_flags), + ) + + def __post_init__(self): + self.target_folder_path = Path(self.target_folder_path) + self.input_exe_path = Path(self.input_exe_path) + self.output_exe_path = Path(self.output_exe_path) + + if not self.target_folder_path.is_absolute(): + self.target_folder_path = self.path.parent / self.target_folder_path + + self.build_folder_path = Path(self.build_folder_path)\ + if self.build_folder_path \ + else self.target_folder_path / "build" + + self.clang_path = Path(self.clang_path) + self.gcc_path = Path(self.gcc_path) + self.linker_path = Path(self.linker_path) + + if not self.build_folder_path.is_absolute(): + raise ValueError( + "build_folder_path must be an absolute path to folder") + + @property + def input_path(self) -> Path: + if self.input_exe_path.is_absolute(): + return self.input_exe_path + return self.target_folder_path / self.input_exe_path + + @property + def output_path(self) -> Path: + if self.output_exe_path.is_absolute(): + return self.output_exe_path + return self.build_folder_path / self.output_exe_path diff --git a/patcher/Hook.py b/patcher/Hook.py index 8efc233..2bb1b76 100644 --- a/patcher/Hook.py +++ b/patcher/Hook.py @@ -3,29 +3,40 @@ ADDRESS_RE = re.compile(r"^(0[xX][0-9A-Fa-f]{6,8})\:$") FUNCTION_NAME_RE = re.compile(r"@([a-zA-Z\_][a-zA-Z0-9\_]+)") +ESCAPE_TRANSLATION = str.maketrans({"\"": r"\"", "\\": r"\\", }) class Section: - def __init__(self, address: str, lines: list[str]) -> None: + def __init__(self, address: str, lines: list[str], addresses: dict[str, str]) -> None: self._address: str = address self._lines: list[str] = lines + self._addresses: dict[str, str] = addresses def lines_to_cpp(self): - s = "" + s = [] for line in self._lines: - line = line.translate(str.maketrans({"\"": r"\"", "\\": r"\\", })) + # line = line.translate(ESCAPE_TRANSLATION) + + def replace_address(match: re.Match[str]) -> str: + func_name = match.group(1) + if func_name in self._addresses: + return f'{match.group(1)} /* {self._addresses[func_name]} */' + return match.group(0) if FUNCTION_NAME_RE.findall(line): - line = FUNCTION_NAME_RE.subn(r'"QU(\1)"', line)[0] + line = FUNCTION_NAME_RE.subn(replace_address, line)[0] - s += f'"{line};"\n' - return s + s.append(f'{line};') + return "\n".join(s) - def to_cpp(self, index: int) -> str: + def header(self, index: int) -> str: if self._address is None: - return self.lines_to_cpp() - return f'SECTION({index:X}, {self._address})\n{self.lines_to_cpp()}' + return "" + return f'.section h{index:X}; .set h{index:X},{self._address};' + + def to_cpp(self, index: int) -> str: + return f'{self.header(index)}\n{self.lines_to_cpp()}' class Hook: @@ -33,15 +44,16 @@ def __init__(self, sections: list[Section]) -> None: self._sections: list[Section] = sections def to_cpp(self): - s = '#include "../asm.h"\n#include "../define.h"\n' + s = '' if len(self._sections) > 0: sections_lines = (section.to_cpp(i).split("\n") for i, section in enumerate(self._sections)) - s += f"asm(\n{''.join((f" {line}\n" for lines in sections_lines for line in lines))});" + s += f"asm(R\"(\n{''.join((f" {line}\n" if not line.startswith(".section") else f'\n{line}\n' + for lines in sections_lines for line in lines))})\");" return s -def load_hook(file_path: Path) -> Hook: +def load_hook(file_path: Path, addresses: dict[str, str]) -> Hook: sections: list[Section] = [] lines = [] address = None @@ -56,11 +68,11 @@ def load_hook(file_path: Path) -> Hook: if match := ADDRESS_RE.match(line): if len(lines) > 0: - sections.append(Section(address, lines)) + sections.append(Section(address, lines, addresses)) lines = [] address = match.group(1) continue lines.append(line) if len(lines) > 0: - sections.append(Section(address, lines)) + sections.append(Section(address, lines, addresses)) return Hook(sections) diff --git a/patcher/PEData.py b/patcher/PEData.py index d369937..6dcbb95 100644 --- a/patcher/PEData.py +++ b/patcher/PEData.py @@ -76,4 +76,4 @@ def find_sect(self, name: str) -> Optional[PESect]: for sect in self.sects: if sect.name == name: return sect - return None + raise Exception(f"Couldn't find section {name}") diff --git a/patcher/Patcher.py b/patcher/Patcher.py index 6d760f5..a032ac4 100644 --- a/patcher/Patcher.py +++ b/patcher/Patcher.py @@ -3,24 +3,19 @@ import os from pathlib import Path import re +import json from typing import Optional import struct import itertools from patcher import Hook +from .Config import Config +from string.templatelib import Template +import subprocess -CLANG_FLAGS = " ".join(["-pipe -m32 -Os -nostdlib -Werror -masm=intel -std=c++20 -march=core2 -c", - ]) - -GCC_FLAGS = " ".join(["-pipe -m32 -Os -fno-exceptions -nostdlib -nostartfiles -fpermissive -masm=intel -std=c++20 -march=core2 -mfpmath=both", - ]) -GCC_FLAGS_ASM = " ".join(["-pipe -m32 -Os -fno-exceptions -nostdlib -nostartfiles -w -fpermissive -masm=intel -std=c++20 -march=core2 -mfpmath=both", - ]) SECT_SIZE = 0x80000 ASM_RE = re.compile(r"(asm\(\"(0[xX][0-9a-fA-F]{1,8})\"\);)", re.IGNORECASE) -CALL_RE = re.compile( - r"((call|jmp|je|jne)\s+(0[xX][0-9a-fA-F]{1,8}))", re.IGNORECASE) SPACES_RE = re.compile(" +") @@ -36,7 +31,7 @@ def scan_header_files(target_path: Path) -> list[str]: return functions_addresses -def list_files_at(folder: Path, pattern: str, excluded: Optional[list[str]] = None) -> list[str]: +def list_files_at(folder: Path, pattern: str, excluded: Optional[set[str]] = None) -> list[str]: pathlist = folder.glob(pattern) paths = [str(path.relative_to(folder)) for path in pathlist] @@ -45,10 +40,6 @@ def list_files_at(folder: Path, pattern: str, excluded: Optional[list[str]] = No return paths -def find_patch_files(folder_path: Path) -> list[str]: - return list_files_at(folder_path, "**/*.cpp", ["main.cpp"]) - - def read_files_contents(dir_path: Path, paths: list[str]) -> dict[str, list[str]]: files_contents: dict[str, list[str]] = {} for path in paths: @@ -61,86 +52,12 @@ def read_files_contents(dir_path: Path, paths: list[str]) -> dict[str, list[str] return files_contents -def preprocess_lines(files_contents: dict[str, list[str]]) -> tuple[list[str], dict[str, str]]: - new_lines = [] - address_names = {} - for file_name, contents in files_contents.items(): - file_lines = [] - file_addresses = {} - for line in contents: - matches = CALL_RE.finditer(line) - new_line = line - for match in matches: - full_s = match.group(0) - address = match.group(3) - address_name = "_" + address[1::] - s_start, s_end = match.span() - file_addresses[address_name] = address - new_line = new_line[:s_start] + \ - full_s.replace(address, address_name) + new_line[s_end:] - file_lines.append(new_line) - if len(file_addresses) == 0: - new_lines.append(f"#include \"{file_name}\"\n") - else: - new_lines.extend(file_lines) - address_names |= file_addresses - return new_lines, address_names - - -def create_sections_file(path: Path, address_map: dict[str, str]): - - HEADER = """ -OUTPUT_FORMAT(pei-i386) -OUTPUT(section.pe) - """ - FUNC_NAMES = """ -_atexit = 0xA8211E; -__Znwj = 0xA825B9; -__ZdlPvj = 0x958C40; -"__imp__GetModuleHandleA@4" = 0xC0F378; -"__imp__GetProcAddress@8" = 0xC0F48C; -"___CxxFrameHandler3" = 0xA8958C; -"___std_terminate" = 0xA994FB; /*idk addr*/ -"??_7type_info@@6B@" = 0xD72A88; -"__CxxThrowException@8" = 0x00A89950; - """ - SECTIONS = """ - SECTIONS { - . = __image_base__ + 0x1000; - .text : { - *(.text*) - *(.data*) - *(.bss*) - *(.rdata) - } - .ctors : { - _FIRST_CTOR = .; - *(.ctors) - *(.CRT*) - _END_CTOR = .; - } - .clang : { - clangfile.o - } - /DISCARD/ : { - *(.rdata$zzz) - *(.eh_frame*) - *(.reloc) - *(.idata*) - } - } - """ - - with open(path, "w") as f: - f.write(HEADER) - f.write(FUNC_NAMES) - for name, address in address_map.items(): - f.write(f"\"{name}\" = {address};\n") - f.write(SECTIONS) +def create_sections_file(input_path: Path, output_path: Path, address_map: dict[str, str]): + with open(input_path, "r") as f: + contents = f.read() - -def create_cxx_sections_file(path, address_map): - with open(path, "w") as f: + with open(output_path, "w") as f: + f.write(contents) for name, address in address_map.items(): f.write(f"\"{name}\" = {address};\n") @@ -154,7 +71,8 @@ def parse_sect_map(file_path: Path) -> dict[str, str]: line = f.readline() while not line.startswith(" *(.data*)"): - items = SPACES_RE.sub(" ", line.strip().replace("::", "__")).split("(")[0].split(" ") + items = SPACES_RE.sub(" ", line.strip().replace( + "::", "__")).split("(")[0].split(" ") if len(items) != 2 or items[1].startswith("?"): line = f.readline() continue @@ -171,7 +89,8 @@ def parse_sect_map(file_path: Path) -> dict[str, str]: line = f.readline() while not line.startswith(" *(.bss*)"): - items = SPACES_RE.sub(" ", line.strip().replace("::", "__")).split(" ") + items = SPACES_RE.sub( + " ", line.strip().replace("::", "__")).split(" ") if len(items) != 2 or items[1].startswith("?"): line = f.readline() continue @@ -188,7 +107,8 @@ def parse_sect_map(file_path: Path) -> dict[str, str]: line = f.readline() while not line.startswith(" *(.rdata)"): - items = SPACES_RE.sub(" ", line.strip().replace("::", "__")).split(" ") + items = SPACES_RE.sub( + " ", line.strip().replace("::", "__")).split(" ") if len(items) != 2 or items[1].startswith("?"): line = f.readline() continue @@ -297,21 +217,15 @@ def yield_sig_locations(data: bytearray, sig: list[bytes | int]): i += 2 -def scan_for_headers_in_section(sections_path: Path): - paths = (Path(s) for s in list_files_at(sections_path, "**/*.h")) - folders = {str(path.parent) for path in paths} - return folders - - -def run_system(command: str) -> int: - print(command) - return os.system(command.replace("\n", " ")) +def run_process(args, cwd: Optional[str] = None) -> bool: + result = subprocess.run(args, cwd=cwd) + return result.returncode != 0 -def patch(_, target_folder, clang_compiler_path, linker_path, gcc_compiler_path, * args): - target_path = Path(target_folder) +def patch(config_path): + config = Config.load_from_json(Path(config_path)) - base_pe = PEData(target_path / "ForgedAlliance_base.exe") + base_pe = PEData(config.input_path) new_v_offset = 0 new_f_offset = 0 @@ -324,84 +238,81 @@ def align(v, a): new_v_offset = align(new_v_offset, base_pe.sectalign) new_f_offset = align(new_f_offset, base_pe.filealign) - print(f"Image base: {base_pe.imgbase + new_v_offset - 0x1000:x}") + image_base = base_pe.imgbase + new_v_offset - 0x1000 + print(f"Image base: {image_base:x}") - section_folder_path = target_path / "section" - build_folder_path = target_path / "build" + section_folder_path = config.target_folder_path / "section" + include_folder_path = config.target_folder_path / "include" + hooks_folder_path = config.target_folder_path / "hooks" + build_folder_path = config.build_folder_path + build_hooks_folder = build_folder_path / "hooks" - paths = find_patch_files(section_folder_path) + build_folder_path.mkdir(parents=True, exist_ok=True) + build_hooks_folder.mkdir(parents=True, exist_ok=True) - # files_contents = read_files_contents(f"{target_path}/section/", paths) - # files_contents, address_names = preprocess_lines(files_contents) + remove_files_at(build_folder_path, "**/*.o") + remove_files_at(build_hooks_folder, "*.hook.cpp") with open(section_folder_path / "main.cpp", "w") as main_file: - for path in paths: + for path in list_files_at(section_folder_path, "**/*.cpp", {"main.cpp"}): main_file.writelines(f"#include \"{path}\"\n") - # main_file.writelines(files_contents) function_addresses = { - name: name for name in scan_header_files(target_path)} - - cxx_files_contents = read_files_contents(section_folder_path, list_files_at( - section_folder_path, "**/*.cxx", ["main.cxx"])) - cxx_files_contents, cxx_address_names = preprocess_lines( - cxx_files_contents) + name: name for name in scan_header_files(config.target_folder_path)} with open(section_folder_path / "main.cxx", "w") as main_file: - main_file.writelines(cxx_files_contents) - - folders = scan_for_headers_in_section(section_folder_path) - includes = " ".join((f"-I ../section/{folder}/" for folder in folders)) + for path in list_files_at(section_folder_path, "**/*.cxx", {"main.cxx"}): + main_file.writelines(f"#include \"{path}\"\n") - if run_system( - f"""cd {build_folder_path} & - {clang_compiler_path} {CLANG_FLAGS} - -I ../include/ {includes} - ../section/main.cxx -o clangfile.o"""): + if run_process(( + config.clang_path, + "-c", *config.clang_flags, + "-I", include_folder_path, + section_folder_path / "main.cxx", + "-o", build_folder_path / "clangfile.o" + )): raise Exception("Errors occurred during building of cxx files") - create_sections_file(target_path / "section.ld", - function_addresses | cxx_address_names) - if run_system( - f"""cd {build_folder_path} & - {gcc_compiler_path} {GCC_FLAGS} - -I ../include/ {includes} - -Wl,-T,../section.ld,--image-base,{base_pe.imgbase + new_v_offset - 0x1000},-s,-Map,sectmap.txt,-o,section.pe - ../section/main.cpp"""): - raise Exception("Errors occurred during building of patch files") + create_sections_file(config.target_folder_path / "section.ld", build_folder_path / "section.ld", + function_addresses) - remove_files_at(build_folder_path, "**/*.o") + if run_process(( + config.gcc_path, *config.gcc_flags, "-I", include_folder_path, + f"-Wl,-T,section.ld,--image-base,{image_base},-s,-Map,sectmap.txt,-o,section.pe", section_folder_path / "main.cpp" + ), cwd=build_folder_path): + raise Exception("Errors occurred during building of patch files") addresses = parse_sect_map(build_folder_path / "sectmap.txt") - def create_defines_file(path: Path, addresses: dict[str, str]): - with open(path, "w") as f: - f.writelines([ - "#define QUAUX(X) #X\n", - "#define QU(X) QUAUX(X)\n\n" - ]) - for name, address in addresses.items(): - f.write(f"#define {name} {address}\n") - create_defines_file(target_path / "define.h", addresses) - - def generate_hook_files(folder_path: Path): - for file_path in list_files_at(folder_path, "**/*.hook"): - hook = Hook. load_hook(folder_path/file_path) + def generate_hook_files(source_path: Path, output_path: Path): + for file_path in list_files_at(source_path, "**/*.hook"): + hook = Hook.load_hook(source_path/file_path, addresses) hook_path = file_path.replace(os.sep, "_") + ".cpp" print(f"Generating {hook_path}") - with open(folder_path/hook_path, "w") as f: + with open(output_path/hook_path, "w") as f: f.write(hook.to_cpp()) - generate_hook_files(target_path/"hooks") + generate_hook_files(config.target_folder_path/"hooks", build_hooks_folder) + generate_hook_files(config.target_folder_path / + "section", build_hooks_folder) - if run_system( - f"""cd {build_folder_path} & - {gcc_compiler_path} -c {GCC_FLAGS_ASM} ../hooks/*.cpp"""): + if run_process(( + config.gcc_path, "-c", *config.asm_flags, build_hooks_folder / "*.cpp" + ), cwd=build_hooks_folder): + raise Exception( + "Errors occurred during building of generated hooks files") + + for hook_path in list_files_at(hooks_folder_path, "**/*.cpp"): + print(f"Hook file {hook_path} is deprecated, use .hook file instead") + + if run_process( + (config.gcc_path, "-c", *config.asm_flags, hooks_folder_path / "*.cpp"), + cwd=build_hooks_folder): raise Exception("Errors occurred during building of hooks files") hooks: list[COFFData] = [] - for path in list_files_at(build_folder_path, "**/*.o"): - coff_data = COFFData(build_folder_path / path, f"build/{path}") + for path in list_files_at(build_hooks_folder, "**/*.o"): + coff_data = COFFData(build_hooks_folder / path, path) for sect in coff_data.sects: if len(sect.name) >= 8: raise Exception(f"sect name too long {sect.name}") @@ -416,28 +327,28 @@ def generate_hook_files(folder_path: Path): ssize = section_pe.sects[-1].v_offset + \ section_pe.sects[-1].v_size + section_pe.sects[0].v_offset - with open(target_path / "patch.ld", "w") as pld: + with open(build_folder_path / "patch.ld", "w") as pld: pld.writelines([ "OUTPUT_FORMAT(pei-i386)\n", - "OUTPUT(build/patch.pe)\n", + "OUTPUT(patch.pe)\n\n", ]) for name, address in addresses.items(): pld.write(f"\"{name}\" = {address};\n") - pld.writelines(["SECTIONS {\n" + pld.writelines(["\nSECTIONS {\n" ]) hi = 0 for hook in hooks: for sect in hook.sects: pld.writelines([ f" .h{hi:X} 0x{sect.offset:x} : SUBALIGN(1) {{\n", - f" {hook.name}({sect.name})\n", + f" hooks/{hook.name}({sect.name}) /* size : {sect.size} */\n", " }\n", ]) hi += 1 pld.writelines([ - f" .exxt 0x{base_pe.imgbase + new_v_offset:x}: {{\n", + f"\n .exxt 0x{base_pe.imgbase + new_v_offset:x}: {{\n", f" . = . + {ssize};\n", " *(.data)\n", " *(.bss)\n", @@ -453,9 +364,11 @@ def generate_hook_files(folder_path: Path): " }\n", "}" ]) - if run_system( - f"""cd {target_path} & - {linker_path} -T patch.ld --image-base {base_pe.imgbase} -s -Map build/patchmap.txt"""): + + if run_process(( + config.linker_path, "-T", "patch.ld", "--image-base", + str(base_pe.imgbase), "-s", "-Map", "patchmap.txt" + ), cwd=build_folder_path): raise Exception("Errors occurred during linking") base_file_data = bytearray(base_pe.data) @@ -489,7 +402,7 @@ def replace_data(new_data, offset): if SECT_SIZE > 0: if SECT_SIZE < exxt_sect.f_size: raise Exception( - f"Section size too small. Required: 0x{exxt_sect.f_size:x}") + f"Section size too small. Required: 0x{exxt_sect.f_size: x}") exxt_sect.v_size = SECT_SIZE exxt_sect.f_size = SECT_SIZE @@ -501,10 +414,11 @@ def replace_data(new_data, offset): replace_data(section_pe.data[s.f_offset:s.f_offset+s.f_size], nsect.f_offset+s.v_offset-section_pe.sects[0].v_offset) - apply_sig_patches(target_path / "SigPatches.txt", base_file_data) + apply_sig_patches(config.target_folder_path / + "SigPatches.txt", base_file_data) def save_new_base_data(data: bytearray): - with open(target_path / "ForgedAlliance_exxt.exe", "wb") as nf: + with open(config.output_path, "wb") as nf: sect_count = len(base_pe.sects) nf.write(data) nf.seek(base_pe.offset+0x6) @@ -519,6 +433,3 @@ def save_new_base_data(data: bytearray): nf.write(sect.to_bytes()) save_new_base_data(base_file_data) - - remove_files_at(build_folder_path, "**/*.o") - remove_files_at(target_path/"hooks", "*.hook.cpp") diff --git a/section_example.ld b/section_example.ld new file mode 100644 index 0000000..a1f3c79 --- /dev/null +++ b/section_example.ld @@ -0,0 +1,41 @@ +OUTPUT_FORMAT(pei-i386) +OUTPUT(section.pe) + +"_atexit" = 0xA8211E; +"__Znwj" = 0xA825B9; +"__ZdlPvj" = 0x958C40; +"__imp__GetModuleHandleA@4" = 0xC0F378; +"__imp__GetProcAddress@8" = 0xC0F48C; +"___CxxFrameHandler3" = 0xA8958C; +"___std_terminate" = 0xA994FB; +"??_7type_info@@6B@" = 0xD72A88; +"__CxxThrowException@8" = 0xA89950; +"_memset" = 0xA89110; +"__invoke_watson" = 0xA848E8; +"_strcpy" = 0xA944E0; +"_memcpy" = 0xA84A60; + +SECTIONS { + . = __image_base__ + 0x1000; + .text : { + *(.text*) + *(.data*) + *(.bss*) + *(.rdata) + } + .ctors : { + _FIRST_CTOR = .; + *(.ctors) + *(.CRT*) + _END_CTOR = .; + } + .clang : { + clangfile.o + } + /DISCARD/ : { + *(.rdata$zzz) + *(.eh_frame*) + *(.reloc) + *(.idata*) + } +}