Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/1-contribution-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Contribution template
description: Template for an extensive description, to enable other contributors to discuss about and implement a feature of the patcher.
labels: ["status: novel issue"]

body:
- type: textarea
id: description
attributes:
label: Description
- type: textarea
id: action
attributes:
label: Course of Action
- type: textarea
id: test
attributes:
label: Test Plan
14 changes: 14 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
contact_links:
- name: FAForever Discord Server
url: https://discord.gg/GEseYDXVRA
about: Discuss the game, report bugs, and get help with modding and development!
- name: FAForever Forums
url: https://forum.faforever.com/
about: Discuss the game and learn about the latest tournaments or news in the community!
- name: Looking for contributors
url: https://discord.gg/Rnpv8RwhdU
about: There's so much to do - we're always looking for contributors to help out with the project!
- name: Repository with assembly patches
url: https://github.com/FAForever/FA-Binary-Patches
about: This is where the actual assembly patches live and prosper to improve the FAForever experience.

76 changes: 76 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright (c) FAForever
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

name: Test

# Note: due to piracy concerns we should at no point upload the executable as
# an artifact. We do not want the lua-compatible executable available to the
# public without verification that they own the game. This verification is
# done via the client.

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
test:
runs-on: windows-latest
permissions:
contents: write

steps:
# 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

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

- 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

# 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++
13 changes: 11 additions & 2 deletions Debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,21 @@ def load_sect_map(map_path: Path) -> list[tuple[int, str]]:
return map


def can_convert_to_hex(num: str):
try:
int(num, 16)
return True
except ValueError:
return False


def get_stack_trace(data: list[str]) -> list[int]:
stack_trace = []
for line in data:
if line.startswith("Stacktrace:"):
_, * trace = line.split(" ")
stack_trace.extend((int(i, 16) for i in trace))
stack_trace.extend((int(i, 16)
for i in trace if can_convert_to_hex(i)))
break
return stack_trace

Expand Down Expand Up @@ -118,7 +127,7 @@ def main(patches_folder_path, args):
else:
break

exe_map = load_exe_map(Path("./exe.map"))
exe_map = load_exe_map(Path("./statistics/exe.map"))
sect = load_sect_map(Path(patches_folder_path)/"build"/"sectmap.txt")
exe_map.extend(sect)

Expand Down
155 changes: 155 additions & 0 deletions Patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
During REing you will notice certain patterns.

## Object ctor and dtor

These patterns will help with noticing certain structures when REing code.

### std::vector

In ctor you won't notice it at first glance. However if you look closely:

```cpp
...
*(_DWORD *)(a + 1) = 0;
*(_DWORD *)(a + 2) = 0;
*(_DWORD *)(a + 3) = 0;
...
```
You'll notice that `*a` wasn't set to anything, and if you find dtor:
```cpp
...
if(*(a + 1))
{
...
free(*(_DWORD *)(a + 1)); // or operator delete(*(a + 1));
}
*(_DWORD *)(a + 1) = 0;
*(_DWORD *)(a + 2) = 0;
*(_DWORD *)(a + 3) = 0;
...
```
This is a definitely std::vector and function before free (if it presents) may suggest you the type of vector.

```cpp
...
if(a->v.begin)
{
...
free(a->v.begin); // or operator delete(*(a + 1));
}
a->v.begin = 0;
a->v.end = 0;
a->v.capacity_end = 0;
...
```

**Note: offsets may be different, but overall idea stays**

An example:

```cpp
v2 = *(std::string **)(a1 + 8);
if ( v2 )
{
func_FreeStringsRange(v2, *(std::string **)(a1 + 12));
operator delete(*(void **)(a1 + 8));
}
*(_DWORD *)(a1 + 8) = 0;
*(_DWORD *)(a1 + 12) = 0;
*(_DWORD *)(a1 + 16) = 0;
```

This is a vector of std::string.

### linked list

ctor
```cpp
*(_DWORD *)(a1 + 4) = a1 + 4;
*(_DWORD *)(a1 + 8) = a1 + 4;
```

dtor
```cpp
*(_DWORD *)(*(_DWORD *)(a1 + 4) + 4) = *(_DWORD *)(a1 + 8);
**(_DWORD **)(a1 + 8) = *(_DWORD *)(a1 + 4);
*(_DWORD *)(a1 + 4) = a1 + 4;
*(_DWORD *)(a1 + 8) = a1 + 4;
```

This is a linked list

```cpp
a1->l.prev = &a1->l;
a1->l.next = &a1->l;
```

```cpp
a1->l.next->prev = a1->l.prev;
a1->l.prev->next = a1->l.next;
a1->l.prev = &a1->l;
a1->l.next = &a1->l;
```

### std::map (binary tree)

```cpp
v1 = sub_465480(); // this function has weird stuff and call to *new* with size we'll use later
// no first field set
*((_DWORD *)this + 1) = v1; // doing some stuff with the second field
*(_BYTE *)(v1 + 45/*any offset*/) = 1; // setting some value to 1
*(_DWORD *)(*((_DWORD *)this + 1) + 4) = *((_DWORD *)this + 1);
**((_DWORD **)this + 1) = *((_DWORD *)this + 1);
*(_DWORD *)(*((_DWORD *)this + 1) + 8) = *((_DWORD *)this + 1);
*((_DWORD *)this + 2) = 0; // setting third field to zero
```

This is a map based on binary tree.

```cpp
node = create_node();
this->m.root = node;
node->is_leaf = 1;
this->m.root->parent = this->m.root;
this->m.root->left = this->m.root;
this->m.root->right = this->m.root;
this->m.size = 0;
```

Where dtor will look very fancy and you'll guess it very fast

```cpp
... // maybe an iteration over map before
some_function(&this->m, &node, this->m.root->left, this->m.root);
operator delete(this->m.root);
this->m.root = 0;
this->m.size = 0;
...
```

### std::shared_ptr

```cpp
if ( v10 )
{
if ( !_InterlockedExchangeAdd(v10 + 1, 0xFFFFFFFF) )
{
(*(void (__thiscall **)(volatile signed __int32 *))(*v10 + 4))(v10);
if ( !_InterlockedExchangeAdd(v10 + 2, 0xFFFFFFFF) )
(*(void (__thiscall **)(volatile signed __int32 *))(*v10 + 8))(v10);
}
}
```
You'll see this very frequently. It is inlined dtor of shared pointer. So if `v10` is a counter block, then field before it is pointer to data associated with it.

```cpp
if ( pi )
{
if ( !_InterlockedExchangeAdd(&pi->use_count_, 0xFFFFFFFF) )
{
pi->vtable->dispose(pi);
if ( !_InterlockedExchangeAdd(&pi->weak_count_, 0xFFFFFFFF) )
pi->vtable->destroy(pi);
}
}
```
26 changes: 26 additions & 0 deletions RE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
We are looking for experienced reverse engineers to improve SCFA's engine and expand it's functions.
Right now we are having goal to improve and expose some of the UI's functionality to have more control over it in Lua.
Specifically user's input and what user can see in UI.

There are 2 main fields where we need help:

* Reverse engineering: revesing
* structures and classes of engine and their names
* functions' logic and their names
* Engine recreation: with given decompiled parts of engine
* recreate functions in C/C++
* recreate structures and classes in C/C++
* recreate generic structures and algorithms in C/C++

Tooling:

* C/C++
* GCC inline x86 asm
* IDA + x64dbg
* a bit of Lua + its API

Dedicated repositories:

* [C++ patcher](https://github.com/FAForever/FA_Patcher)
* [Python patcher](https://github.com/4z0t/SCFA-python-patcher)
* [Game's executable patches](https://github.com/FAForever/FA-Binary-Patches)
Loading