Skip to content
Merged
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
211 changes: 96 additions & 115 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,123 +1,104 @@
# AGENTS.md
# Agent guide

This file provides guidance to AI coding agents working with code in this repository.
STFC Community Mod hooks a Unity IL2CPP game to add desktop features on Windows and macOS.
Read [CONTRIBUTING.md](CONTRIBUTING.md) for builds and tests.
Before changing bindings or hooks, read the [IL2CPP reference](docs/IL2CPP.md).

## Project Overview
## Keep changes scoped

Community mod for Star Trek Fleet Command (STFC) — a desktop game that runs via Unity/IL2CPP. The mod hooks into the game's IL2CPP runtime to add QoL features (UI scaling, zoom controls, hotkeys, chat improvements, cargo viewers, data sync, etc.). Supports Windows (DLL proxy injection) and macOS (dylib injection).
- Stage only task-related files. Leave unrelated edits and generated artifacts alone.
- Preserve client enum catalogs in `src/mod/game/`, including unused IDs for future features.
- Run `git diff --check` and the narrowest relevant xmake build before finishing C++ or patch work.
- Review hooks, platform guards, config defaults, and the English settings example in the final diff.
- Put specialized subtree rules in a nearby `AGENTS.md`.

## Build System
## Build and check

This project uses **XMake** (not CMake). All build configuration is in `xmake.lua` files. Language standard is C++23 with multi-threaded static runtime (`/MT`).
The project uses xmake and C++23. Build on the target platform; the Swift launcher requires macOS and Xcode.

### Build Commands

```bash
# Configure and build (command line)
xmake # Build default target
xmake f -p macosx -a arm64 -m debug --target_minver=14.6 # Configure for macOS ARM debug
xmake f -p windows -m release # Configure for Windows release

# Generate Visual Studio solution
xmake project -k vsxmake -m "debug,release"

# Clean
xmake clean -a

# macOS dev script (build, run, debug, crashlogs)
scripts/mac-build-test-debug.sh [build|run|debug|crashlogs] [-m debug|release|releasedbg]
```sh
xmake f -c -p macosx -a arm64 -m release -y
xmake build -y
xmake build -y il2cpp-bindings-tests il2cpp-gc-tests mod-logic-tests sync-logic-tests config-reload-tests unity-tracker-tests launcher-rsync-tests
```

### Build Modes
- `debug` — development
- `release` — production
- `releasedbg` — release with debug info, enables `_MODDBG` define

### Reset Build
Delete the `build/` folder to reset. Also delete `.vs/` for a full Visual Studio reset.

## Repository Expectations

- Keep changes scoped. Do not stage unrelated dirty files or generated artifacts unless the user explicitly asks.
- Before finishing C++ or patch work, run `git diff --check` and the narrowest relevant xmake build.
- For macOS core mod changes, use `xmake f -p macosx -a arm64 -m debug --target_minver=14.6 -y && xmake -y mods`.
- Review the final diff for risky hooks, platform guards, config default mismatches, and missing example config updates.
- If a subtree such as `macos-launcher/` needs specialized guidance, prefer a nested `AGENTS.md` near that code instead of overloading this root file.

## Architecture

### Build Targets (xmake.lua files)

| Target | Type | Platform | Description |
|---|---|---|---|
| `mods` | static lib | all | Core mod logic — patches, config, IL2CPP bindings |
| `stfc-community-mod` (win-proxy-dll) | shared DLL | Windows | Proxy DLL (`version.dll`) that loads into the game process |
| `stfc-community-mod` (macos-dylib) | shared dylib | macOS | Injected dylib equivalent |
| `stfc-community-mod-loader` | binary | macOS | Loader that injects the dylib into the game |
| `macOSLauncher` | Xcode app | macOS | Swift GUI launcher app |

### Source Layout

- **`src/mod/`** — Core mod code (the main codebase)
- `config.h/.cc` — Singleton `Config` class, loads TOML settings, controls which patches are enabled
- `patches/patches.cc` — Entry point: hooks `il2cpp_init`, then conditionally installs each patch
- `patches/parts/` — Individual patch implementations (zoom, hotkeys, chat, UI scale, sync, etc.)
- `patches/key.h`, `mapkey.h`, `modifierkey.h` — Keyboard input mapping system
- `prime/` — Header-only IL2CPP type definitions mirroring the game's C# classes
- `prime/proto/` — Protobuf definitions for game data sync
- `il2cpp/` — IL2CPP helper functions for resolving methods, classes, and icalls at runtime
- **`src/platform/windows/`** — Windows DLL proxy entry point
- **`src/platform/macos/dylib/`** — macOS dylib entry point
- **`src/platform/macos/loader/`** — macOS loader (finds game, injects dylib)
- **`macos-launcher/`** — Swift macOS GUI app
- **`third_party/libil2cpp/`** — IL2CPP SDK headers
- **`xmake-packages/`** — Custom xmake package definitions (e.g., `spud`)

### Key Patterns

**Hooking pattern** — All game function hooks use `spud` (function detour library) via `SPUD_STATIC_DETOUR`. Each hook function takes `auto original` as the first parameter to call through to the original:
```cpp
void SomeFunction_Hook(auto original, SomeClass* _this, ...) {
// custom logic
original(_this, ...);
}
```
macOS does not tolerate repeated hooks of the same function. If multiple features need to intercept the same game method, consolidate the behavior behind one detour or add platform guards instead of installing overlapping hooks.
Do not over-focus on hidden IL2CPP `MethodInfo*` parameters during drift repair; match the game-visible signature from `dump.cs` unless there is concrete runtime evidence that the hidden parameter is the issue.
Before adding a detour to a small IL2CPP wrapper or thunk, verify its native method extent against SPUD's architecture-specific overwrite size on every supported architecture for the exact client build. A successful platform build does not validate hook fit; prefer a substantive downstream method when exact per-architecture extent is unavailable.

**IL2CPP class resolution** — Game classes are resolved at runtime using helpers:
```cpp
static auto class_helper = il2cpp_get_class_helper("Assembly.Name", "Namespace", "ClassName");
static auto method = class_helper.GetMethodInfo("MethodName");
```

**Adding a new patch** — Create a `.cc` file in `src/mod/patches/parts/`, write an `InstallXxxHooks()` function, declare it in `patches.cc`, add a `bool installXxx` to `Config`, and register it in the `patches[]` array in `patches.cc`. Hook-install toggles are read from the `[patches]` TOML table in all build modes and should default to `true`; they exist as emergency compatibility switches for hook collisions or client drift.

**Separate hook installation from feature enablement** — Do not use a user-facing feature setting to decide whether its hooks are installed. Normally install all supported hooks, then have each hook check the feature setting and immediately preserve native behavior (usually by calling `original`) when the feature is disabled. Keep hook-install switches under `[patches]` and feature settings in their relevant sections such as `[ui]`, `[graphics]`, or `[audio]`; never place a feature-only toggle under `[patches]`. If an installer contains an early return based on a feature setting, or a `patches[]` entry points directly at a feature setting, treat that as a design error. Helpers that install no hooks, such as shortcut-only behavior, do not belong in `patches[]` at all.

**Config** — User settings are in TOML files. The `Config` singleton (`Config::Get()`) is loaded once during `il2cpp_init_hook`. Add new settings to `config.h`, add defaults in `defaultconfig.h`, and load them in `config.cc`. For user-facing settings, update only the English example (`example_community_patch_settings_en.toml`). Do not update the other localized examples unless the user explicitly asks for translation updates. The unsuffixed `example_community_patch_settings.toml` is only a pointer to these localized examples and should not be changed when adding or modifying settings.

### Dependencies (via xmake packages)

- `spud` — Function hooking/detour library
- `eastl` — EA's STL replacement
- `spdlog` — Logging
- `toml++` — TOML config parsing
- `nlohmann_json` — JSON handling
- `cpr` / `libcurl` — HTTP requests (for data sync)
- `protobuf` — Protocol buffers (game data)
- `simdutf` — UTF encoding
- `libil2cpp` — Local package pointing to `third_party/libil2cpp`

## Code Style

- Uses `.clang-format` — 2-space indent, 120 column limit, Linux brace style, aligned assignments/declarations
- Version is defined in `src/mod/version.h` (VERSION_MAJOR/MINOR/REVISION/PATCH)
- Prefer narrow platform guards such as `#if _WIN32`, `#if !_WIN32`, and `#if __APPLE__`; do not assume every non-Windows path is macOS.
- Logging via `spdlog::info()`, `spdlog::debug()`, etc.

## Branches

- `main` — stable releases
- `dev` — active development (PR target)
Run each matching test with `xmake run <target>`.
The x64-only `spud-relocation-tests` target checks detour relocation.
See [contributor checks](CONTRIBUTING.md#run-checks-before-submitting) for coverage.

## Source map

| Path | Responsibility |
| --- | --- |
| `src/libs/il2cpp/` | Generic runtime bindings, calls, strings, GC handles, and detours |
| `src/libs/unity/` | Unity core, UI, TextMeshPro, input, and instance tracking |
| `src/mod/mod.h`, `mod.cc` | Startup, feature installation, and the frame tick |
| `src/mod/config.h`, `config.cc` | Typed settings, defaults, parsing, and reload |
| `src/mod/file.h`, `file.cc` | Settings, vars, log paths, and command-line flags |
| `src/mod/shortcuts.h`, `shortcuts.cc`, `shortcuts.def` | Chord parsing, matching, actions, defaults, tiers, and aliases |
| `src/mod/features/` | One file per feature domain, with its hooks, shortcuts, and state |
| `src/mod/game/` | Header-only STFC bindings, grouped by domain |
| `src/mod/game/proto/` | Protobuf definitions for sync |
| `src/mod/sync/` | Response processing, records, acknowledgements, uploads, battle journals, and fleet state |
| `src/mod/text.h`, `features/toast_text.h` | Game-free text helpers used by tests |
| `src/platform/` | Windows proxy DLL, macOS dylib entry point, and macOS loader |
| `src/launcher/macos/` | Swift launcher for updates, entitlements, and game launch |
| `tests/` | Controlled-runtime, mod, sync, detour, and launcher tests |
| `zig/`, `third_party/` | Build logic and vendored dependencies |
| `docs/`, `scripts/` | User guides, example settings, and release or verification tools |

Features own behavior; bindings own game access.
Reusable IL2CPP or Unity helpers belong in their libraries.
Feature files never declare bindings or call raw `il2cpp_*` APIs.
Dependency pins and notices are listed in [THIRD_PARTY.md](docs/THIRD_PARTY.md).

## Add or change a feature

1. Extend the domain file in `src/mod/features/`, or add one named after the feature's purpose.
2. Keep installation private. Export a descriptor such as `const Feature kXxx{"Xxx", &Config::Patches::group, InstallXxx};`.
3. Declare the descriptor in `features/features.h` and add it to `kFeatures` in `mod.cc`.
4. For a new hook group, add a `[patches]` member defaulting to `true` and read it in `Config::Read`.

Shortcut-only helpers need no descriptor.
Frame features supply their frame function as the descriptor's fourth member and return early when disabled.

Only `[patches]` keys decide which hooks install. They are restart-only switches for conflicts and client drift.
Feature settings belong in sections such as `[ui]`, `[graphics]`, or `[audio]`.
Install hooks even when a feature is disabled. Each hook reads the current setting and cheaply calls `original` when off.

## Hooks and frame work

- Use `IL2CPP_HOOK` or `IL2CPP_DETOUR` from `<il2cpp/hook.h>`. Name callbacks `Class_Method_Hook` and take `auto original` first.
- Each macro expansion owns one target. Prefer one detour per game method; chained detours depend on installation order.
- Before hooking a small wrapper or thunk, verify its native extent against SPUD's overwrite size on every supported architecture for the exact client. A build does not prove fit.
- Match game-visible signatures from `dump.cs`. Change hidden `MethodInfo*` handling only with runtime evidence.
- Keep the single `ScreenManager.Update` hook in `mod.cc`. Other hooks that read keys must call `unity::Key::Poll()` first.

The frame tick polls input, checks for settings changes, runs installed feature frame functions, then dispatches shortcuts.
Hooks run in no fixed order within a frame. Reads before polling see the previous frame's input.

## Settings and shortcuts

`Config::Get()` is game-thread-only. Call it where needed; a reload replaces the object, so do not retain its reference.
Background sync workers retain immutable `Config::Sync::Get()` snapshots.
`Config::Parse` reads a TOML table without touching files or live bindings.

When changing settings:

1. Add section members in `config.h`, named after TOML keys and initialized to their defaults.
2. Read them with `r.Read(section, key, member)` in `Config::Read`.
3. Update only the [English example](docs/configuration/example_community_patch_settings_en.toml). Update translations only when requested.
4. Run `zig build test-mod`; it checks agreement between the reader and English example.

The unsuffixed example is a pointer to translations. Do not change it for new settings.
Actions, TOML keys, defaults, optional tiers, and aliases belong in `shortcuts.def`.
Feature files keep dispatch order and context checks.

## Code and writing style

- Use `.clang-format` through `zig build fmt`: two-space indentation, 120 columns, Linux braces, and aligned declarations. It skips `.mm` files.
- Use fixed-width numeric types. Prefer narrow platform guards such as `#if _WIN32`, `#if !_WIN32`, and `#if __APPLE__`.
- Log with `spdlog`. Version components live in `src/mod/version.h` as `VERSION_MAJOR`, `VERSION_MINOR`, `VERSION_REVISION`, and `VERSION_PATCH`.
- Keep docs task-first, concise, and written in plain technical English. Use short paragraphs and varied sentence lengths.

`main` holds stable upstream releases. `dev` is the upstream development and pull-request target.
Loading
Loading