Skip to content
18 changes: 7 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,32 @@ jobs:
# clone everything that we need
- uses: actions/checkout@v4
with:
repository: FAForever/FA-Binary-Patches
ref: 'master'
path: .

- uses: actions/checkout@v4
with:
path: fa-python-binary-patcher
repository: FAForever/FA-Binary-Patches
ref: 'master'
path: FA-Binary-Patches

# install everything that we need
- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.14"

- name: Install MinGW i686
run: choco install mingw --x86 -y --no-progress

- name: Update PATH
run: echo C:/ProgramData/mingw64/mingw32/bin/ >> $env:GITHUB_PATH

- name: Install fa-python-binary-patcher
run: |
pip install -r ./fa-python-binary-patcher/requirements.txt

# download base executable
- name: Download base executable
run: |
curl -L "https://content.faforever.com/build/ForgedAlliance_base.exe" -o ForgedAlliance_base.exe
curl -L "https://content.faforever.com/build/ForgedAlliance_base.exe" -o FA-Binary-Patches/ForgedAlliance_base.exe

# patch it, if it works then we're good
- name: Patch base executable
run: |
mkdir build
python ./fa-python-binary-patcher/main.py "$(pwd)" clang++ ld g++
mkdir FA-Binary-Patches/build
cd fa-python-binary-patcher & python main.py config_example.json
58 changes: 58 additions & 0 deletions config_example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"target_folder_path": "FA-Binary-Patches",
"input_name": "ForgedAlliance_base.exe",
"output_name": "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"
],
"functions": {
"_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": "0x00A89950",
"_memset": "0xA89110",
"__invoke_watson": "0x00A848E8"
}
}
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
70 changes: 70 additions & 0 deletions patcher/Config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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_name: str = "ForgedAlliance_base.exe"
output_name: str = "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] = ()

functions: dict[str, str] = field(default_factory=dict)

@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_name=config.get("input_name", Config.input_name),
output_name=config.get("output_name", Config.output_name),
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),
functions=config.get("functions", {}),
)

def __post_init__(self):
self.target_folder_path = Path(self.target_folder_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:
return self.target_folder_path / self.input_name

@property
def output_path(self) -> Path:
return self.build_folder_path / self.output_name
2 changes: 1 addition & 1 deletion patcher/PEData.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Loading