Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repository Layout
This is a monorepo containing three independent Rust crates as git submodules:
| Submodule | Role |
|---|---|
openfut-core/ |
Game-independent offline FUT backend (Axum + SQLite) |
openfut-bridge/ |
FIFA 23 integration layer and reverse-engineering proxy |
openfut-launcher/ |
Native GUI launcher (egui/eframe) for starting/stopping services and managing setup |
Each submodule has its own Cargo.toml, src/, tests/, and target/. Commands must be run from within the submodule directory or with --manifest-path.
Common Commands
All commands run from within the relevant submodule directory.
openfut-core
# Run (dev mode; creates openfut.db in current dir)
cargo run
# Run (release)
cargo build --release && ./target/release/openfut-core
# Test (full integration suite against in-memory SQLite)
cargo test
# Run a single test
cargo test test_health_endpoint
# Lint
cargo clippy -- -D warnings
# Format
cargo fmt
openfut-bridge
# Run (Core must be running first on port 8080)
cargo run
# Test
cargo test
# Run a single test
cargo test test_bridge_health_endpoint
# Lint / format same as core
cargo clippy -- -D warnings
cargo fmt
openfut-launcher
# Run (native GUI window)
cargo run
# Build release
cargo build --release
# Build the hook DLL (Windows cross-compile, required for FIFA 23 integration)
cd openfut-hook
cargo build --release --target x86_64-pc-windows-gnu
Full-stack via setup.sh (Linux, from repo root)
./setup.sh quickstart # build + start + hosts + NAT + TLS cert (first-time)
./setup.sh start # start both services in background
./setup.sh stop # stop both + clean up hosts/NAT
./setup.sh status # show running state
./setup.sh logs # tail both logs
./setup.sh watch-domains # find unknown EA domains hitting the bridge
Environment variables
Core (openfut-core/):
| Variable | Default |
|---|---|
LISTEN_ADDR |
127.0.0.1:8080 |
DATABASE_URL |
sqlite://openfut.db |
DATA_DIR |
data |
Bridge (openfut-bridge/):
| Variable | Default |
|---|---|
BRIDGE_LISTEN_ADDR |
127.0.0.1:8443 |
CORE_URL |
http://127.0.0.1:8080 |
CAPTURES_DIR |
captures |
PLACEHOLDER_MODE |
true |
TLS_ENABLED |
true |
Architecture
FIFA 23 client
│
▼
openfut-bridge (port 8443)
│
├── Known route → openfut-core (port 8080)
└── Unknown route → placeholder JSON + saved to captures/
│
SQLite database
│
data/ (JSON)
Core has zero knowledge of FIFA 23. It exposes a clean REST API and stores everything in SQLite. Bridge has zero game logic — it only translates FIFA 23's wire protocol into Core API calls.
openfut-core internals
Module map:
| Path | Purpose |
|---|---|
src/main.rs |
Entry point: tracing, config, pool, migrations, seed, serve |
src/lib.rs |
build_app(pool, data_dir) — used by integration tests |
src/app.rs |
Router construction, AppState definition |
src/config.rs |
Config struct from env vars |
src/db.rs |
Pool init + migration runner |
src/error.rs |
AppError enum + IntoResponse impl |
src/models/ |
Pure data types (Serde + SQLx FromRow) |
src/services/ |
Business logic; all DB access lives here |
src/routes/ |
Axum handler functions; one file per domain |
src/seed/ |
First-run starter pack grant |
src/modding/ |
Generic JSON directory loader |
data/ |
Moddable JSON content: cards, packs, objectives, SBCs, events |
migrations/ |
SQLx SQL migrations (numbered sequentially) |
AppState is cloned into every handler via State<AppState>. It holds the SQLite pool and Arc-wrapped in-memory registries loaded at startup from data/ (CardDb, Vec<PackDefinition>, etc.).
Layer discipline: routes extract state and call services; services own all DB access and data-loading logic; models are pure data.
Single-profile design: Only one profile per database. All services fetch "the active profile" by selecting the first row — this is intentional.
openfut-bridge internals
Module map:
| Path | Purpose |
|---|---|
src/main.rs |
Entry point: config, TLS setup, serve |
src/lib.rs |
Library root — re-exports modules |
src/config.rs |
Config struct |
src/proxy.rs |
catch_all_handler — the central routing brain |
src/mapper.rs |
map_to_core(method, path) — FIFA 23 → Core path translation |
src/capture.rs |
CapturedRequest struct + disk serialization |
src/shaper.rs |
Response body transformation (Core → FIFA 23 wire format) |
src/tls.rs |
Self-signed cert generation + rustls acceptor |
src/routes/ |
Bridge admin endpoints (/_bridge/*) |
captures/ |
JSON files written for every intercepted request |
Request flow: all traffic hits catch_all_handler → mapper::map_to_core() → if known, proxy to Core; if unknown, return placeholder JSON and write to captures/.
Adding a new FIFA 23 endpoint:
- Capture it in
captures/or/_bridge/unknown - Document it in
docs/endpoint-map.md - Add a mapping in
src/mapper.rs(EXACTarray orprefix_routes()) - Implement the handler in Core if needed
openfut-launcher internals
A native desktop GUI (egui/eframe) that wraps Core and Bridge as managed child processes. It is the recommended way to run OpenFUT on a desktop — no terminal or setup.sh required.
Module map:
| Path | Purpose |
|---|---|
src/main.rs |
Entry point: eframe window setup |
src/app.rs |
LauncherApp — main UI state + four tabs (Dashboard, Logs, Setup, Config) |
src/config.rs |
LauncherConfig — persisted settings (binary paths, ports, game dir) |
src/process.rs |
ServiceHandle — spawns/kills Core and Bridge child processes, tracks status |
src/logs.rs |
LogBuffer — ring buffer collecting stdout/stderr from child processes |
src/setup.rs |
Hook DLL deployment, TLS cert installation into Wine/Proton cert store |
openfut-hook/ |
Windows DLL (version.dll) injected into FIFA 23 via Proton to redirect EA hostnames to the local bridge (cross-compiled with x86_64-pc-windows-gnu) |
Setup flow the launcher automates:
- Deploy
openfut_hook.dll→ FIFA 23 game dir asversion.dll(intercepts network calls inside Wine/Proton, no/etc/hostschanges needed) - Configure redirect IP written into
openfut.cfgalongside the DLL - Install bridge's self-signed TLS cert into the Wine/Proton cert store
- Start Core then Bridge (Dashboard tab shows live status)
Testing approach
Core uses a single tests/integration_test.rs that spins up a full Axum app against an in-memory SQLite database (build_app(pool, "data")). Tests use raw HTTP via tower::ServiceExt::oneshot. The data/ directory is always required at test time (tests run from openfut-core/).
Bridge tests are in tests/proxy_test.rs. They build a bridge app in placeholder_mode: true pointing at a dummy Core URL — no live Core required.
Modding / data files
All game content lives in openfut-core/data/. Files are JSON arrays loaded at startup. To add content, drop a JSON file into the right subdirectory and restart Core:
data/cards/ ← CardDefinition[]
data/packs/ ← PackDefinition[]
data/objectives/ ← ObjectiveDefinition[]
data/sbcs/ ← SbcDefinition[]
data/events/ ← EventDefinition[]
data/achievements/← AchievementDefinition[]
Version control
All three submodules (openfut-core, openfut-bridge, openfut-launcher) have their own independent git history. Use tea (Gitea CLI) for remote operations, not gh.