chore: add openfut-launcher submodule, CLAUDE.md, and setup.sh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,3 +4,6 @@
|
|||||||
[submodule "openfut-bridge"]
|
[submodule "openfut-bridge"]
|
||||||
path = openfut-bridge
|
path = openfut-bridge
|
||||||
url = https://git.aleshym.co/funman300/OpenFUT-Bridge.git
|
url = https://git.aleshym.co/funman300/OpenFUT-Bridge.git
|
||||||
|
[submodule "openfut-launcher"]
|
||||||
|
path = openfut-launcher
|
||||||
|
url = https://git.aleshym.co/funman300/openfut-launcher.git
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
# 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 two 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 |
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full-stack via setup.sh (Linux, from repo root)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./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:**
|
||||||
|
1. Capture it in `captures/` or `/_bridge/unknown`
|
||||||
|
2. Document it in `docs/endpoint-map.md`
|
||||||
|
3. Add a mapping in `src/mapper.rs` (`EXACT` array or `prefix_routes()`)
|
||||||
|
4. Implement the handler in Core if needed
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
Both `openfut-core` and `openfut-bridge` are git submodules with their own independent history. Use `tea` (Gitea CLI) for remote operations, not `gh`.
|
||||||
Submodule
+1
Submodule openfut-launcher added at 099241d9ca
@@ -0,0 +1,424 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# OpenFUT Setup Script
|
||||||
|
# Builds, configures, and starts the OpenFUT offline FUT emulator for FIFA 23.
|
||||||
|
# Run as a regular user; the script will sudo only for hosts/cert/iptables steps.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CORE_DIR="$SCRIPT_DIR/openfut-core"
|
||||||
|
BRIDGE_DIR="$SCRIPT_DIR/openfut-bridge"
|
||||||
|
|
||||||
|
CORE_PORT="${CORE_PORT:-8080}"
|
||||||
|
BRIDGE_PORT="${BRIDGE_PORT:-8443}" # internal port; iptables forwards 443 → this
|
||||||
|
TLS_ENABLED="${TLS_ENABLED:-true}"
|
||||||
|
|
||||||
|
CORE_PID_FILE="/tmp/openfut-core.pid"
|
||||||
|
BRIDGE_PID_FILE="/tmp/openfut-bridge.pid"
|
||||||
|
CORE_LOG="/tmp/openfut-core.log"
|
||||||
|
BRIDGE_LOG="/tmp/openfut-bridge.log"
|
||||||
|
|
||||||
|
# EA FUT domains to redirect.
|
||||||
|
# Run './setup.sh watch-domains' while starting FIFA 23 to discover additional domains.
|
||||||
|
EA_HOSTS=(
|
||||||
|
# Core FUT API
|
||||||
|
"fut.ea.com"
|
||||||
|
"utas.mob.v4.fut.ea.com"
|
||||||
|
# EA account / auth
|
||||||
|
"accounts.ea.com"
|
||||||
|
"signin.ea.com"
|
||||||
|
"gateway.ea.com"
|
||||||
|
# Origin / EA Desktop API
|
||||||
|
"api1.origin.com"
|
||||||
|
"api2.origin.com"
|
||||||
|
"api3.origin.com"
|
||||||
|
# Telemetry / assets (can safely be silenced)
|
||||||
|
"eas.ea.com"
|
||||||
|
"eaassets-a.akamaihd.net"
|
||||||
|
"eaassets-b.akamaihd.net"
|
||||||
|
)
|
||||||
|
HOSTS_MARKER="# OpenFUT — managed block (do not edit manually)"
|
||||||
|
|
||||||
|
# ── Colours ────────────────────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${CYAN}[INFO]${RESET} $*"; }
|
||||||
|
ok() { echo -e "${GREEN}[OK]${RESET} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
||||||
|
heading() { echo -e "\n${BOLD}$*${RESET}"; }
|
||||||
|
|
||||||
|
# ── Helpers ────────────────────────────────────────────────────────────────────
|
||||||
|
require_cmd() {
|
||||||
|
command -v "$1" &>/dev/null || { error "Required command not found: $1"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
is_running() {
|
||||||
|
local pid_file="$1"
|
||||||
|
[[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
local pid_file="$1" name="$2"
|
||||||
|
if is_running "$pid_file"; then
|
||||||
|
kill "$(cat "$pid_file")" 2>/dev/null && ok "Stopped $name"
|
||||||
|
rm -f "$pid_file"
|
||||||
|
else
|
||||||
|
warn "$name is not running"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Commands ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
cmd_build() {
|
||||||
|
heading "Building OpenFUT Core and Bridge…"
|
||||||
|
require_cmd cargo
|
||||||
|
|
||||||
|
info "Building Core (release)…"
|
||||||
|
cargo build --release --manifest-path "$CORE_DIR/Cargo.toml" \
|
||||||
|
2>&1 | grep -E "^(error|warning\[)" || true
|
||||||
|
ok "Core built"
|
||||||
|
|
||||||
|
info "Building Bridge (release)…"
|
||||||
|
cargo build --release --manifest-path "$BRIDGE_DIR/Cargo.toml" \
|
||||||
|
2>&1 | grep -E "^(error|warning\[)" || true
|
||||||
|
ok "Bridge built"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_start() {
|
||||||
|
heading "Starting OpenFUT services…"
|
||||||
|
|
||||||
|
# ── Core ──
|
||||||
|
if is_running "$CORE_PID_FILE"; then
|
||||||
|
warn "Core is already running (PID $(cat "$CORE_PID_FILE"))"
|
||||||
|
else
|
||||||
|
# Run from CORE_DIR so the default sqlite://openfut.db relative path works
|
||||||
|
(
|
||||||
|
cd "$CORE_DIR"
|
||||||
|
LISTEN_ADDR="127.0.0.1:$CORE_PORT" \
|
||||||
|
DATA_DIR="$CORE_DIR/data" \
|
||||||
|
"$CORE_DIR/target/release/openfut-core" \
|
||||||
|
>"$CORE_LOG" 2>&1
|
||||||
|
) &
|
||||||
|
echo $! > "$CORE_PID_FILE"
|
||||||
|
ok "Core started (PID $!) → http://127.0.0.1:$CORE_PORT log: $CORE_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Give Core a moment to bind before Bridge starts
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# ── Bridge ──
|
||||||
|
if is_running "$BRIDGE_PID_FILE"; then
|
||||||
|
warn "Bridge is already running (PID $(cat "$BRIDGE_PID_FILE"))"
|
||||||
|
else
|
||||||
|
BRIDGE_LISTEN_ADDR="0.0.0.0:$BRIDGE_PORT" \
|
||||||
|
CORE_URL="http://127.0.0.1:$CORE_PORT" \
|
||||||
|
TLS_ENABLED="$TLS_ENABLED" \
|
||||||
|
PLACEHOLDER_MODE="true" \
|
||||||
|
CAPTURES_DIR="$BRIDGE_DIR/captures" \
|
||||||
|
"$BRIDGE_DIR/target/release/openfut-bridge" \
|
||||||
|
>"$BRIDGE_LOG" 2>&1 &
|
||||||
|
echo $! > "$BRIDGE_PID_FILE"
|
||||||
|
local scheme="http"; [[ "$TLS_ENABLED" == "true" ]] && scheme="https"
|
||||||
|
ok "Bridge started (PID $!) → ${scheme}://0.0.0.0:$BRIDGE_PORT log: $BRIDGE_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
info "Dashboard: http://127.0.0.1:$CORE_PORT/_bridge/dashboard (via bridge: https://127.0.0.1:$BRIDGE_PORT/_bridge/dashboard)"
|
||||||
|
info "Admin: https://127.0.0.1:$BRIDGE_PORT/_bridge/admin"
|
||||||
|
info "Setup guide: https://127.0.0.1:$BRIDGE_PORT/_bridge/guide"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_stop() {
|
||||||
|
heading "Stopping OpenFUT services…"
|
||||||
|
stop_service "$BRIDGE_PID_FILE" "Bridge"
|
||||||
|
stop_service "$CORE_PID_FILE" "Core"
|
||||||
|
cmd_unhosts
|
||||||
|
cmd_unnat
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
heading "OpenFUT service status"
|
||||||
|
if is_running "$CORE_PID_FILE"; then
|
||||||
|
ok "Core is RUNNING (PID $(cat "$CORE_PID_FILE"))"
|
||||||
|
else
|
||||||
|
warn "Core is STOPPED"
|
||||||
|
fi
|
||||||
|
if is_running "$BRIDGE_PID_FILE"; then
|
||||||
|
ok "Bridge is RUNNING (PID $(cat "$BRIDGE_PID_FILE"))"
|
||||||
|
else
|
||||||
|
warn "Bridge is STOPPED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if grep -q "$HOSTS_MARKER" /etc/hosts 2>/dev/null; then
|
||||||
|
ok "Hosts entries are IN PLACE"
|
||||||
|
else
|
||||||
|
warn "Hosts entries are NOT configured (run: ./setup.sh hosts)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if sudo iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "redir ports $BRIDGE_PORT"; then
|
||||||
|
ok "NAT redirect 443→$BRIDGE_PORT is ACTIVE"
|
||||||
|
else
|
||||||
|
warn "NAT redirect is NOT active (run: ./setup.sh nat)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_logs() {
|
||||||
|
heading "Tailing logs (Ctrl-C to stop)…"
|
||||||
|
tail -f "$CORE_LOG" "$BRIDGE_LOG" 2>/dev/null || warn "No log files yet — start the services first."
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_hosts() {
|
||||||
|
heading "Adding EA FUT hosts entries…"
|
||||||
|
if grep -q "$HOSTS_MARKER" /etc/hosts 2>/dev/null; then
|
||||||
|
ok "Hosts entries already present — skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local block
|
||||||
|
block="\n$HOSTS_MARKER\n"
|
||||||
|
for host in "${EA_HOSTS[@]}"; do
|
||||||
|
block+="127.0.0.1 $host\n"
|
||||||
|
done
|
||||||
|
block+="# end OpenFUT\n"
|
||||||
|
|
||||||
|
echo -e "$block" | sudo tee -a /etc/hosts > /dev/null
|
||||||
|
ok "Added to /etc/hosts:"
|
||||||
|
for host in "${EA_HOSTS[@]}"; do
|
||||||
|
echo " 127.0.0.1 $host"
|
||||||
|
done
|
||||||
|
info "Flush DNS if needed: sudo resolvectl flush-caches (or: sudo systemd-resolve --flush-caches)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_unhosts() {
|
||||||
|
if grep -q "$HOSTS_MARKER" /etc/hosts 2>/dev/null; then
|
||||||
|
sudo sed -i "/^$HOSTS_MARKER/,/^# end OpenFUT/d" /etc/hosts
|
||||||
|
ok "Removed OpenFUT hosts entries"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_nat() {
|
||||||
|
heading "Setting up iptables redirect 443 → $BRIDGE_PORT…"
|
||||||
|
if sudo iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "redir ports $BRIDGE_PORT"; then
|
||||||
|
ok "NAT rule already active"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port "$BRIDGE_PORT"
|
||||||
|
ok "iptables: OUTPUT 443 → $BRIDGE_PORT"
|
||||||
|
warn "This rule is NOT persistent across reboots. Re-run './setup.sh nat' after reboot or use iptables-save."
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_unnat() {
|
||||||
|
if sudo iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "redir ports $BRIDGE_PORT"; then
|
||||||
|
sudo iptables -t nat -D OUTPUT -p tcp --dport 443 -j REDIRECT --to-port "$BRIDGE_PORT"
|
||||||
|
ok "Removed iptables NAT rule"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_cert() {
|
||||||
|
heading "Installing TLS certificate…"
|
||||||
|
|
||||||
|
local scheme="https"
|
||||||
|
local cert_url="${scheme}://127.0.0.1:$BRIDGE_PORT/_bridge/cert.pem"
|
||||||
|
local cert_dest="/tmp/openfut-ca.pem"
|
||||||
|
|
||||||
|
if ! is_running "$BRIDGE_PID_FILE"; then
|
||||||
|
error "Bridge is not running. Start it first with './setup.sh start'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Downloading certificate from $cert_url …"
|
||||||
|
curl -sk "$cert_url" -o "$cert_dest" || { error "Failed to download cert. Is the Bridge running with TLS_ENABLED=true?"; exit 1; }
|
||||||
|
|
||||||
|
if [[ ! -s "$cert_dest" ]]; then
|
||||||
|
error "Downloaded cert is empty."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok "Certificate saved to $cert_dest"
|
||||||
|
|
||||||
|
# Detect distro cert store
|
||||||
|
if command -v update-ca-trust &>/dev/null; then
|
||||||
|
# Arch / Fedora / RHEL
|
||||||
|
sudo cp "$cert_dest" /etc/ca-certificates/trust-source/anchors/openfut-ca.pem
|
||||||
|
sudo update-ca-trust
|
||||||
|
ok "Certificate installed (update-ca-trust)"
|
||||||
|
elif command -v update-ca-certificates &>/dev/null; then
|
||||||
|
# Debian / Ubuntu
|
||||||
|
sudo cp "$cert_dest" /usr/local/share/ca-certificates/openfut-ca.crt
|
||||||
|
sudo update-ca-certificates
|
||||||
|
ok "Certificate installed (update-ca-certificates)"
|
||||||
|
else
|
||||||
|
warn "Cannot auto-install cert — manual steps:"
|
||||||
|
echo " Copy $cert_dest to your system's trusted CA store."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "If using Steam/Proton, you may also need to add it to the game's certificate store."
|
||||||
|
info "Wine cert path (Proton): WINEDEBUG=-all wine certutil -addstore Root $cert_dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_proton_cert() {
|
||||||
|
heading "Installing certificate into Proton / Wine (for Steam FIFA 23)…"
|
||||||
|
|
||||||
|
local cert_dest="/tmp/openfut-ca.pem"
|
||||||
|
if [[ ! -f "$cert_dest" ]]; then
|
||||||
|
error "No cert found at $cert_dest — run './setup.sh cert' first"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find Proton wine binary
|
||||||
|
local proton_wine
|
||||||
|
proton_wine=$(find "$HOME/.steam/steam/steamapps/common" -name "wine" -path "*/Proton*/wine" 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
if [[ -z "$proton_wine" ]]; then
|
||||||
|
warn "Could not auto-detect Proton wine binary."
|
||||||
|
echo " Manually run: wine certutil -addstore Root $cert_dest"
|
||||||
|
echo " with the correct WINEPREFIX for FIFA 23."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find FIFA 23 Proton prefix
|
||||||
|
local steam_id_fifa23="1811260"
|
||||||
|
local prefix="$HOME/.steam/steam/steamapps/compatdata/$steam_id_fifa23/pfx"
|
||||||
|
|
||||||
|
if [[ ! -d "$prefix" ]]; then
|
||||||
|
warn "FIFA 23 Proton prefix not found at $prefix"
|
||||||
|
echo " Launch FIFA 23 at least once to create the prefix, then re-run this step."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
WINEPREFIX="$prefix" WINEDEBUG=-all "$proton_wine" certutil -addstore Root "$cert_dest" \
|
||||||
|
&& ok "Certificate added to FIFA 23 Proton prefix" \
|
||||||
|
|| warn "certutil failed — you may need to add it manually."
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_quickstart() {
|
||||||
|
heading "OpenFUT Quick Start"
|
||||||
|
echo "This will: build → start services → add hosts entries → set up NAT → install cert"
|
||||||
|
echo
|
||||||
|
read -rp "Continue? [y/N] " confirm
|
||||||
|
[[ "${confirm,,}" == "y" ]] || { info "Aborted."; exit 0; }
|
||||||
|
|
||||||
|
cmd_build
|
||||||
|
cmd_start
|
||||||
|
|
||||||
|
info "Waiting for services to initialise…"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Verify Core is actually up before proceeding
|
||||||
|
if ! is_running "$CORE_PID_FILE"; then
|
||||||
|
error "Core failed to start. Check the log:"
|
||||||
|
tail -20 "$CORE_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! is_running "$BRIDGE_PID_FILE"; then
|
||||||
|
error "Bridge failed to start. Check the log:"
|
||||||
|
tail -20 "$BRIDGE_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# sudo-gated steps — try them, print manual instructions if sudo not available
|
||||||
|
echo
|
||||||
|
heading "System configuration (requires sudo)…"
|
||||||
|
|
||||||
|
if sudo -n true 2>/dev/null; then
|
||||||
|
cmd_hosts
|
||||||
|
cmd_nat
|
||||||
|
sleep 1
|
||||||
|
cmd_cert
|
||||||
|
cmd_proton_cert
|
||||||
|
else
|
||||||
|
warn "Cannot acquire sudo non-interactively. Run these steps yourself:"
|
||||||
|
echo
|
||||||
|
echo " 1. sudo ./setup.sh hosts"
|
||||||
|
echo " 2. sudo ./setup.sh nat"
|
||||||
|
echo " 3. ./setup.sh cert (after the above two)"
|
||||||
|
echo " 4. ./setup.sh proton-cert"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
heading "Setup complete!"
|
||||||
|
ok "FIFA 23 FUT traffic will now route to OpenFUT."
|
||||||
|
echo
|
||||||
|
echo -e " Dashboard: ${CYAN}http://127.0.0.1:$CORE_PORT/_bridge/dashboard${RESET}"
|
||||||
|
echo -e " Admin panel: ${CYAN}https://127.0.0.1:$BRIDGE_PORT/_bridge/admin${RESET}"
|
||||||
|
echo -e " Logs: ${CYAN}./setup.sh logs${RESET}"
|
||||||
|
echo -e " Stop all: ${CYAN}./setup.sh stop${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_watch_domains() {
|
||||||
|
heading "Watching for unknown EA domains (Ctrl-C to stop)…"
|
||||||
|
if ! is_running "$BRIDGE_PID_FILE"; then
|
||||||
|
warn "Bridge is not running. Start it first with './setup.sh start'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "Launch FIFA 23 now. Any domain that reaches the real EA servers will appear here."
|
||||||
|
echo
|
||||||
|
# Watch the bridge log for lines that contain host/domain info not already in our list
|
||||||
|
tail -f "$BRIDGE_LOG" 2>/dev/null | grep --line-buffered -i "unknown\|placeholder\|host\|domain\|CONNECT" \
|
||||||
|
| grep -v --line-buffered "$(IFS='|'; echo "${EA_HOSTS[*]}")" \
|
||||||
|
| while IFS= read -r line; do
|
||||||
|
echo -e "${YELLOW}$(date +'%H:%M:%S')${RESET} $line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_help() {
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
${BOLD}OpenFUT Setup Script${RESET}
|
||||||
|
Offline FIFA 23 FUT emulator — for legitimate game owners only.
|
||||||
|
|
||||||
|
${BOLD}Usage:${RESET} ./setup.sh <command>
|
||||||
|
|
||||||
|
${BOLD}Commands:${RESET}
|
||||||
|
quickstart Full setup in one step (build + start + hosts + nat + cert)
|
||||||
|
build Compile Core and Bridge in release mode
|
||||||
|
start Start Core and Bridge in the background
|
||||||
|
stop Stop both services and clean up hosts/NAT
|
||||||
|
restart Stop then start
|
||||||
|
status Show running state, hosts, and NAT status
|
||||||
|
logs Tail live logs from both services
|
||||||
|
watch-domains Show unknown EA domains hitting the bridge (add them to hosts)
|
||||||
|
|
||||||
|
hosts Add EA FUT domains to /etc/hosts (requires sudo)
|
||||||
|
unhosts Remove OpenFUT entries from /etc/hosts
|
||||||
|
nat Add iptables redirect 443 → $BRIDGE_PORT (requires sudo)
|
||||||
|
unnat Remove iptables NAT rule
|
||||||
|
|
||||||
|
cert Download TLS cert from Bridge and install system-wide
|
||||||
|
proton-cert Install TLS cert into the FIFA 23 Proton/Wine prefix
|
||||||
|
|
||||||
|
${BOLD}Environment overrides:${RESET}
|
||||||
|
CORE_PORT Core listen port (default: 8080)
|
||||||
|
BRIDGE_PORT Bridge internal port (default: 8443)
|
||||||
|
TLS_ENABLED Enable HTTPS on Bridge (default: true)
|
||||||
|
|
||||||
|
${BOLD}Example:${RESET}
|
||||||
|
./setup.sh quickstart # first-time full setup
|
||||||
|
./setup.sh start # start after a reboot
|
||||||
|
./setup.sh stop # clean shutdown
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Dispatch ───────────────────────────────────────────────────────────────────
|
||||||
|
case "${1:-help}" in
|
||||||
|
quickstart) cmd_quickstart ;;
|
||||||
|
build) cmd_build ;;
|
||||||
|
start) cmd_start ;;
|
||||||
|
stop) cmd_stop ;;
|
||||||
|
restart) cmd_stop; cmd_start ;;
|
||||||
|
status) cmd_status ;;
|
||||||
|
logs) cmd_logs ;;
|
||||||
|
hosts) cmd_hosts ;;
|
||||||
|
unhosts) cmd_unhosts ;;
|
||||||
|
nat) cmd_nat ;;
|
||||||
|
unnat) cmd_unnat ;;
|
||||||
|
cert) cmd_cert ;;
|
||||||
|
proton-cert) cmd_proton_cert ;;
|
||||||
|
watch-domains) cmd_watch_domains ;;
|
||||||
|
help|--help|-h) cmd_help ;;
|
||||||
|
*) error "Unknown command: $1"; cmd_help; exit 1 ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user