Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 357501f549 | |||
| c2772132c1 | |||
| d1a71bd5a1 | |||
| 0d3f33cede | |||
| ca7ce267a0 | |||
| 13339c1478 | |||
| d619c992c1 | |||
| 958ff24546 | |||
| 09ed26ba16 | |||
| 3d895fb7ac | |||
| 7dcf610b71 | |||
| ff4b5a87f5 | |||
| 493b9e0573 | |||
| c1d1f03ec8 | |||
| 3c3dc32fc6 | |||
| feaff0443f | |||
| 87241acc1a | |||
| 3b965f1f3f | |||
| 099241d9ca | |||
| dfa2c9afc7 | |||
| 9b5da57f12 | |||
| fc894a7f77 |
+4
-36
@@ -1,38 +1,6 @@
|
||||
# Rust build artifacts
|
||||
target/
|
||||
**/target/
|
||||
|
||||
# SQLite databases
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Captures (runtime data, not source)
|
||||
openfut-bridge/captures/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Frozen baseline archives / inspects / manifests
|
||||
/docker-backups/
|
||||
|
||||
# local dev screenshots (not versioned)
|
||||
fifa17-recon/.screens/
|
||||
|
||||
# local hook backup
|
||||
*.pre-storeguard.bak
|
||||
# runtime SQLite DB (created when services run from this dir)
|
||||
openfut.db
|
||||
openfut.db-shm
|
||||
openfut.db-wal
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
[submodule "openfut-core"]
|
||||
path = openfut-core
|
||||
url = https://git.aleshym.co/funman300/OpenFUT-Core.git
|
||||
[submodule "openfut-bridge"]
|
||||
path = openfut-bridge
|
||||
url = https://git.aleshym.co/funman300/OpenFUT-Bridge.git
|
||||
[submodule "openfut-launcher"]
|
||||
path = openfut-launcher
|
||||
url = https://git.aleshym.co/funman300/openfut-launcher.git
|
||||
[submodule "fifa-blaze"]
|
||||
path = fifa-blaze
|
||||
url = https://git.aleshym.co/funman300/fifa-blaze.git
|
||||
@@ -1,217 +0,0 @@
|
||||
# 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
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### openfut-launcher
|
||||
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```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
|
||||
|
||||
## 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:**
|
||||
1. Deploy `openfut_hook.dll` → FIFA 23 game dir as `version.dll` (intercepts network calls inside Wine/Proton, no `/etc/hosts` changes needed)
|
||||
2. Configure redirect IP written into `openfut.cfg` alongside the DLL
|
||||
3. Install bridge's self-signed TLS cert into the Wine/Proton cert store
|
||||
4. 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`.
|
||||
Generated
+229
-2316
File diff suppressed because it is too large
Load Diff
+18
-10
@@ -1,10 +1,18 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"openfut-core",
|
||||
"openfut-bridge",
|
||||
"openfut-launcher",
|
||||
"openfut-launcher/openfut-hook",
|
||||
"fifa-blaze/crates/blaze-proto",
|
||||
"fifa-blaze/crates/server",
|
||||
]
|
||||
[package]
|
||||
name = "openfut-launcher"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
eframe = "0.29"
|
||||
egui = "0.29"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
anyhow = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
dirs = "5"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
openfut-common = { path = "openfut-common" }
|
||||
# parking_lot over std::sync: every lock here is taken and used immediately, so
|
||||
# the poisoning unwrap at each call site is pure noise (project rule).
|
||||
parking_lot = "0.12"
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
# OpenFUT — project handoff
|
||||
|
||||
Self-contained briefing for an assistant with **no access to this repo or machine**.
|
||||
Everything needed to understand the project and reason about its open problems is here.
|
||||
|
||||
---
|
||||
|
||||
## 1. What this is
|
||||
|
||||
**OpenFUT** is a clean-room, fully offline re-implementation of the server backend for
|
||||
**FIFA 17 Ultimate Team (FUT)**. EA's servers for FIFA 17 are long dead. The goal is to
|
||||
make the retail game's FUT mode fully playable again — open packs, build squads, use the
|
||||
transfer market, play matches and earn rewards — by emulating every server the client
|
||||
talks to, on localhost.
|
||||
|
||||
Nothing is decompiled *into* the project. The game's binaries are read to learn the
|
||||
**wire format** (which JSON keys, of which types, each response must contain), and the
|
||||
servers are written from scratch in Python against that spec.
|
||||
|
||||
The client is unmodified retail FIFA 17 running under Wine/Proton on Linux.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture — four independent servers
|
||||
|
||||
FIFA 17 does not talk to one backend. It talks to four, on different protocols, and all
|
||||
four must be satisfied in sequence before FUT loads.
|
||||
|
||||
```
|
||||
FIFA 17 (Wine/Proton)
|
||||
│
|
||||
├─ LSX / Origin :4216 XML over TCP. Local Origin client emulation.
|
||||
│ Login, entitlements, persona.
|
||||
├─ Blaze :42127 redirector (TLS) → :42130 game server, :42131 nucleus
|
||||
│ EA's binary "Fire2/TDF" RPC protocol. Session, auth,
|
||||
│ and — critically — the CLIENT-CONFIG STORE.
|
||||
├─ UTAS / RS4 :8099 The FUT REST API. JSON over HTTP. ~45 endpoints.
|
||||
│ Club, squads, packs, market, matches. The bulk of it.
|
||||
└─ POW / EASFC :8094 (+ :8080 content) A third HTTP API, discovered late.
|
||||
Online status bar, level, EASFC credits, catalogue.
|
||||
```
|
||||
|
||||
Plus two helpers: a **roster** server (:8081) serving a roster-update XML the FUT
|
||||
loading screen blocks on, and **autopatch**, which patches the running process's
|
||||
ProtoSSL certificate verification so the client accepts our self-signed TLS.
|
||||
|
||||
### How the client is redirected
|
||||
|
||||
Three mechanisms, in decreasing order of preference:
|
||||
|
||||
1. **Blaze client-config keys** — the cleanest. Blaze serves a key/value config store
|
||||
and the client reads its own service URLs from it. `FUT_RS4_APIURL_<MODULE>` and
|
||||
`FUT_RS4_URL_<CALL>` point the FUT API at `127.0.0.1:8099`; `FIFA_POW_URL` points
|
||||
the EASFC layer at `127.0.0.1:8094`. **No root, no DNS games.**
|
||||
2. **`/etc/hosts`** — for hosts baked into the binary (`easw.easports.com`,
|
||||
`gosredirector.ea.com`).
|
||||
3. **iptables DNAT** — for a hardcoded IP (`159.153.51.20` → the Blaze redirector).
|
||||
|
||||
---
|
||||
|
||||
## 3. The wire format, and why it is unforgiving
|
||||
|
||||
FUT responses are JSON, but the client does **not** use a general JSON object model. Each
|
||||
response class has a hand-written SAX-style deserializer that walks tokens and dispatches
|
||||
on a **hashed key id** (an "atom"). This has three consequences that dominate the project:
|
||||
|
||||
**Atoms.** Every JSON key name maps to a 16-bit atom id via FNV-1a. There is a recovered
|
||||
table of ~900 (id → name). A response is really "which atoms does deserializer X read,
|
||||
and of what type".
|
||||
|
||||
**Type fidelity is fatal.** Feeding a scalar where the parser expects an object or array
|
||||
does not error — it **desyncs the token reader and the game hard-freezes** in a busy loop
|
||||
at `0x1801c7f1a`. This is the single most common way to break the game, and it has bitten
|
||||
this project repeatedly. Arrays must be arrays; nested objects must be objects.
|
||||
|
||||
**Unknown keys are usually skipped safely** — most deserializers route an unrecognised
|
||||
atom to a value-skip handler (`FUN_180135ff0`), so extra fields are inert. This document
|
||||
previously named `FutMoveCard` (`0x180128600`) as an exception with no skip handler at
|
||||
all. **That was wrong**, and the retraction is in §5a: it has two skip-handler call sites
|
||||
and parses seven atoms. No deserializer in this project is currently known to lack one.
|
||||
Treat any future "this class has no skip handler" claim as unproven until the search is
|
||||
shown to have covered the whole function.
|
||||
|
||||
### Working method
|
||||
|
||||
For any endpoint: find the response class's deserializer, extract the atom ids it
|
||||
compares against, map them to names, note the getter used for each (int / string / bool /
|
||||
nested), and build the minimal body. Omit nested members unless their shape is known —
|
||||
omission is skip-safe, a wrong shape freezes the game.
|
||||
|
||||
---
|
||||
|
||||
## 4. What works today (live-verified)
|
||||
|
||||
| Area | Status |
|
||||
|---|---|
|
||||
| Boot to the FUT hub | ✅ |
|
||||
| Club identity, coins, W/D/L record | ✅ |
|
||||
| Active squad — renders 11 real players, chemistry links | ✅ |
|
||||
| Squad building — `PUT /squad/<id>` fires, persists across relaunch | ✅ |
|
||||
| Squad roster ("MY SQUADS") | ✅ |
|
||||
| Transfer market — browse, bid, buy-now, sell, watchlist | ✅ |
|
||||
| Packs — buy, reveal, Send to Club | ✅ (the workaround is retired, see §5a) |
|
||||
| Quick sell — destroys cards, credits coins | ✅ |
|
||||
| Match loop — create/ready/play/destroy + coin rewards | ✅ implemented, **never played in-game** |
|
||||
| Online/EASFC status bar (no "servers unreachable") | ✅ when enabled |
|
||||
| Seasons / tournaments / leaderboards / champions | ⚠️ routed with reversed schemas, never live-tested |
|
||||
|
||||
Current save state: 99 club items, 8,400 coins, 0-0-0 record.
|
||||
|
||||
**Design convention:** every risky change ships behind an environment flag, default set
|
||||
to whatever is live-proven (`FUT_MASSINFO`, `FUT_USERINFO`, `FUT_MODES`,
|
||||
`FUT_PACK_AUTOCLUB`, `FUT_POW`, `FUT_STORE_GROUPS`, …). This exists because two working
|
||||
screens were broken by shipping "corrections" on by default.
|
||||
|
||||
---
|
||||
|
||||
## 5. Open problems
|
||||
|
||||
### 5a. "Send to Club" kills the FUT session — SOLVED 2026-08-04
|
||||
|
||||
Opening a pack shows the cards correctly; choosing **Send to Club** used to produce
|
||||
*"there has been an error connecting to FIFA 17 Ultimate Team"* and a logout, seven
|
||||
attempts running. The cards always moved server-side; only the acknowledgement was
|
||||
rejected.
|
||||
|
||||
The cause was the response body. `PUT ut/%s/item` returns per-item **verdict** records,
|
||||
not an acknowledgement, and the completion handler raises
|
||||
`EVENT_CARDS_MOVE_CARD_FAILURE` when the record vector is empty or when `success != 1`.
|
||||
Every body the project returned, `{}` included, reported the move as failed. The fix is
|
||||
the real shape:
|
||||
|
||||
```json
|
||||
{"itemData":[{"id":100000125,"pile":"club","success":true}, ...]}
|
||||
```
|
||||
|
||||
Live: five cards to the club, session survived, cards persisted, no `ut/delete/auth`.
|
||||
The autoclub workaround is retired.
|
||||
|
||||
Two corrections this closed, both worth carrying forward:
|
||||
|
||||
- The repo's claim that this deserializer had **no skip handler** and parsed only two
|
||||
keys was false. It came from searching a truncated decompile. It implied the body
|
||||
could not be at fault, which is what sent seven attempts after client-side state.
|
||||
Never conclude an absence from a truncated or unverified-length extraction.
|
||||
- The quick-sell asymmetry was not evidence of client state. Quick sell's callbacks
|
||||
read only the transport status code and never touch the body.
|
||||
|
||||
### 5b. "MY CLUB" counter always reads 0 — UNSOLVED
|
||||
|
||||
The hub tab bar shows `MY CLUB 0` despite the club holding 99 items.
|
||||
|
||||
**Eliminated by live test:**
|
||||
- **Not the item list** — the user opened MY CLUB, the client fetched the club and
|
||||
**displayed all 99 players correctly**, and the counter still read 0.
|
||||
- **Not `pileSizeClientData`** — the massinfo member that carries pile sizes as
|
||||
`{"entries":[{"key":int,"value":int}]}`. A probe sent 16 entries with uniquely
|
||||
identifiable values; the counter stayed 0.
|
||||
- **Not lazy loading** — the club endpoint was fetched 6 times that session.
|
||||
|
||||
**Unexplored contrast:** the `ACTIVE SQUAD` tab in the same bar correctly shows `11/23`.
|
||||
So some counters work. Whatever differs between that one and the club one is likely the
|
||||
answer.
|
||||
|
||||
### 5c. Store tiles render "unknown" — DIAGNOSED, NOT FIXED
|
||||
|
||||
Pack tiles show `unknown` with zero item counts. The `"unknown"` string is an
|
||||
unconditional **default** in a string constructor — the field simply never gets written.
|
||||
The store renders *display groups*, and `displayGroup` is parsed **recursively by the same
|
||||
element parser**. Sending it populated **froze the store** (the type-desync busy loop), so
|
||||
it is behind a flag, default off. Doing it properly needs the group's own field set worked
|
||||
out rather than a self-referential copy of the pack.
|
||||
|
||||
### 5d. Seasons and Draft refuse — UNSOLVED, and not obviously server-side
|
||||
|
||||
Selecting **single-player Seasons** raises *"There was a problem communicating with the
|
||||
FIFA Ultimate Team servers"* while making **zero requests to any layer**. UTAS, Blaze and
|
||||
POW logs show only pings and one census subscription across the whole failure window. No
|
||||
response can be wrong because no request was made. POW is eliminated (same failure with it
|
||||
enabled and disabled).
|
||||
|
||||
**Online Draft** hangs the client rather than crashing it (process alive, no dump). The
|
||||
one suspicious thing on the wire is `GET ut/%s/squad/mode/draft/state`, which our generic
|
||||
`/squad` route answers with a full active-squad object: 23 slots, nested `itemData`, a
|
||||
33-integer formation string. The real class wants `roundsInfo` plus a state enum, so this
|
||||
is a textbook type-desync candidate and the timing matches. **Nothing has isolated it**;
|
||||
it is a suspect, not a cause.
|
||||
|
||||
Both matter beyond themselves, because they are the only two routes into a match, and the
|
||||
`/match` request shape has therefore never been captured.
|
||||
|
||||
---
|
||||
|
||||
## 6. Notable reverse-engineering findings
|
||||
|
||||
- **Class → deserializer resolution.** A response class's name literal is preceded by a
|
||||
**4-byte header**, and the constructing factory's `lea` points at *the header*, not the
|
||||
text. Lookups must use `name_address - 4`. Six attempts failed on this off-by-four;
|
||||
four of them returned zero results and nearly got recorded as "this class has no
|
||||
deserializer".
|
||||
- **The request-template table is a floor, not a ceiling.** Several real endpoints are
|
||||
built by the caller appending a suffix and therefore never appear in the binary's URL
|
||||
table: `squad/list`, `user/club`, `club/stats/*`, `clientdata/<key>`. Only live traffic
|
||||
reveals them. This has caught the project three separate times.
|
||||
- **The documentation lies.** The project's own `ENDPOINT_MAP.md` (~1,360 lines, ~100
|
||||
reversed structs) has been wrong repeatedly: it claimed `FutMoveCard` parses
|
||||
`chemistry` (it does not); it called seven store pack fields "skipped no-ops" (all are
|
||||
parsed); it gave price-object keys as `amount`/`currency` (the parsers read
|
||||
`externalPriceId`). **Verify against the decompiler before relying on any row.**
|
||||
- **The online layer was hiding in an unpacked DLL.** "EA FC servers are unreachable"
|
||||
comes from a third HTTP API implemented in a *loose, unpacked, string-rich* library —
|
||||
not the packed executable, and not any layer previously emulated. It is redirectable
|
||||
purely through the Blaze config store.
|
||||
- **The main executable is Denuvo-packed.** Its code exists only in a live process. Live
|
||||
memory is readable via `/proc/PID/mem` (the PE is mapped flat), which is how a crash
|
||||
site was disassembled. Any logic living there cannot be reversed statically.
|
||||
|
||||
---
|
||||
|
||||
## 7. Tooling built
|
||||
|
||||
- A **PyGhidra harness** with helpers for decompiling, xrefs, vtables, byte scanning and
|
||||
class→deserializer resolution. (Ghidra's Java/OSGi scripting is broken on this machine;
|
||||
PyGhidra bypasses it entirely.)
|
||||
- A **minidump reader** — exception record, fault-time registers, module map, and a stack
|
||||
walk that recovers a usable backtrace.
|
||||
- A **live code grabber** that reads and disassembles unpacked code out of a running
|
||||
process.
|
||||
- A **read-only live probe** pattern for polling client model state while playing.
|
||||
- **Two test suites**: 380 live contract checks (freeze-safety: asserts every response
|
||||
field's type against the reversed schema) and 51 pure unit checks for match rewards.
|
||||
- A **traffic-replay audit** that diffs current responses against a known-good session —
|
||||
this is what proves a change did not alter what the client sees.
|
||||
|
||||
---
|
||||
|
||||
## 8. Documentation in-repo
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
| `ENDPOINT_MAP.md` | ~100 reversed response structs, atoms, types, freeze risks |
|
||||
| `FUT_RESPONSE_REBUILD_PLAN.md` | Squad family, massinfo, the service-layer call graph |
|
||||
| `REBUILD_RESEARCH.md` | Complete API surface, gap analysis, POW layer, and every eliminated hypothesis with its evidence |
|
||||
| `CARD_SYSTEM.md` | How card identity resolves locally from the game's own database |
|
||||
| `REPACK_INTEL.md` | Origin/LSX emulation notes |
|
||||
|
||||
---
|
||||
|
||||
## 9. Where help would be most valuable
|
||||
|
||||
1. **The MY CLUB counter (§5b).** Given the client demonstrably *has* the items and
|
||||
*renders* them, what else could a tab counter read from? Note that a sibling counter in
|
||||
the same bar works correctly.
|
||||
2. **Seasons refusing with zero requests to any server (§5d).** The client raises a
|
||||
"problem communicating with the FIFA Ultimate Team servers" without contacting
|
||||
anything. Nothing on the wire can be wrong because nothing went on the wire.
|
||||
3. **Whether the remaining problems are fixable server-side at all**, or whether the
|
||||
deciding logic lives in the Denuvo-packed executable and only live instrumentation can
|
||||
settle it. Note that this question was asked about `Send to Club` too, and there the
|
||||
answer turned out to be a plain wire fix, so treat "it must be client-side" as a
|
||||
hypothesis needing evidence rather than a fallback explanation.
|
||||
|
||||
Useful framing: this project's failures have almost always come from proposing a fix
|
||||
before testing the assumption under it. Hypotheses that come with a cheap way to
|
||||
disconfirm them are worth far more than plausible ones.
|
||||
|
||||
## FIFA 17 network-redirect milestone (2026-08-09)
|
||||
|
||||
Hook now installs a GENERIC network redirect on the fifa17 feature path (fifa17.rs
|
||||
install_network_redirect): getaddrinfo IAT patch + inline connect detour + WSAConnect
|
||||
IAT, with a configurable destination (connect_hook::set_target_ipv4) read from
|
||||
openfut.cfg (single-line IP). Deployed DLL md5 bc9e0bc6, cfg=10.10.0.120.
|
||||
|
||||
RESULT of live launch (client 105 -> server 120):
|
||||
- Error changed: "servers shut down" -> "Unable to connect to EA servers / check
|
||||
network". Redirect IS firing (progress).
|
||||
- BLOCKER A: getaddrinfo IAT patched 0+0 -> FIFA 17 does NOT resolve via IAT
|
||||
getaddrinfo in the main exe or EAWebKit.dll. Names resolved via another path
|
||||
(gethostbyname or internal DirtySDK resolver). So no hostname reached 120.
|
||||
- BLOCKER B (architectural): FIFA 17 online = Blaze binary TCP on high ports. Log
|
||||
shows connect 20.51.153.159:42230 sock_type=1 -> wsa_err=10035 (WOULDBLOCK->dead).
|
||||
Port 42230 is NOT in the remap set (443,10041,42127,3216) so it was not redirected.
|
||||
Even if redirected, the Docker bridge only speaks HTTPS on 8443 -- no Blaze
|
||||
listener exists for FIFA 17. This is a server-side build, not a hook tweak.
|
||||
|
||||
NEXT (evidence-first): add gethostbyname (and possibly a DirtySDK resolver) capture
|
||||
to learn the hostname behind 20.51.153.159; widen Blaze port remap; then scope a
|
||||
Blaze-speaking bridge listener before expecting the error to clear.
|
||||
|
||||
## DNS/getaddrinfo fix — RESOLVED (2026-08-09, hook md5 67e3639b)
|
||||
|
||||
Added src/resolver_hook.rs: INLINE detours at ws2_32 export addresses for
|
||||
getaddrinfo + GetAddrInfoW + gethostbyname (same unhook/rehook pattern as
|
||||
connect_hook). Replaces the IAT approach that patched 0 slots on FIFA 17.
|
||||
Wired into fifa17.rs install_network_redirect; hooks.rs gained redirect_ip_cstr()
|
||||
and redirect_ip_str() helpers.
|
||||
|
||||
LIVE RESULT (client 105 -> server 120):
|
||||
- resolver detours 3/3 installed.
|
||||
- getaddrinfo(winter15.gosredirector.ea.com) -> redirect. Game now dials
|
||||
10.10.0.120 (was 20.51.153.159 before). DNS BLOCKER A = SOLVED.
|
||||
|
||||
REMAINING BLOCKER B (architectural, NOT DNS): FIFA 17 online = EA Blaze binary
|
||||
TCP. Game connects 10.10.0.120:42230 (gosredirector/Blaze redirector) ->
|
||||
wsa_err=10035 (nothing listening). Two gaps: (1) connect_hook remap set lacks
|
||||
42230; (2) even remapped, the Docker bridge only serves HTTPS on 8443 — no Blaze
|
||||
listener exists. Clearing Unable to connect requires a Blaze redirector+main
|
||||
server on the bridge side (real server build), not a hook change.
|
||||
NOTE: the 3s TLS-handshake-EOF spam in bridge logs on :8443 is the LAUNCHER health
|
||||
poller, not the game.
|
||||
@@ -1,313 +0,0 @@
|
||||
# OpenFUT — Project Report
|
||||
|
||||
**Goal:** make FIFA 17 Ultimate Team fully playable offline, forever, by re-implementing
|
||||
every server the game talks to.
|
||||
|
||||
**Status:** FUT boots, loads, and is playable. Packs, squads, the transfer market, coins
|
||||
and progression all work. Two cosmetic/flow problems remain open.
|
||||
|
||||
**Timeline:** 2026-06-25 → 2026-08-04 · 44 commits · ~12,900 lines of Python across 39
|
||||
tools · ~3,600 lines of reverse-engineering documentation.
|
||||
|
||||
---
|
||||
|
||||
## 1. What the project is
|
||||
|
||||
EA shut down FIFA 17's servers years ago, which kills Ultimate Team — the mode is entirely
|
||||
server-driven. Your club, squads, packs, market and progression all live server-side, so
|
||||
without a backend the mode is dead even though the game still installs and runs.
|
||||
|
||||
OpenFUT replaces that backend with local servers. The game is **unmodified retail
|
||||
FIFA 17** running under Wine/Proton on Linux; nothing is patched into the game except a
|
||||
single runtime tweak so it accepts our TLS certificate.
|
||||
|
||||
This is **clean-room work**. No EA code is copied or redistributed. The game's own
|
||||
binaries are read to learn the *wire format* — which JSON keys, of which types, each
|
||||
response must carry — and the servers are written from scratch against that specification.
|
||||
|
||||
---
|
||||
|
||||
## 2. Project history
|
||||
|
||||
The project changed target twice before finding its footing. That arc matters, because
|
||||
each pivot was driven by hitting a hard wall.
|
||||
|
||||
**Phase 1 — FIFA 23 (June 2026).** Began as an offline FUT backend for FIFA 23: a Rust
|
||||
core (`openfut-core`, Axum + SQLite), a protocol bridge (`openfut-bridge`), and a GUI
|
||||
launcher. All three built and passed tests. The architecture was sound but the client
|
||||
never got far enough to exercise it.
|
||||
|
||||
**Phase 2 — the FIFA 23 wall.** FIFA 23 refused to go online at all. Extensive reverse
|
||||
engineering of the connection state machine, live-memory probing, and forcing the
|
||||
"go online" gate directly all failed — the client's internal coherence checks were the
|
||||
wall, not any single flag. Documented as a dead end rather than fought.
|
||||
|
||||
**Phase 3 — the FIFA 17 pivot (late July).** FIFA 17 turned out to be a far better target:
|
||||
its network library is **unprotected and fully symboled**, exposing 979 RPC names. The
|
||||
insight was to crack FIFA 17 first and port the understanding back.
|
||||
|
||||
That worked, quickly:
|
||||
- **TLS pinning defeated** — the client's certificate verification is patched at runtime
|
||||
in memory, so a self-signed cert is accepted.
|
||||
- **Origin/LSX emulation** — the local Origin client protocol was reverse engineered,
|
||||
including the repack's own crypto layer, beating "log in to Origin" and
|
||||
"title version outdated".
|
||||
- **Blaze cracked end to end** — EA's binary RPC protocol: redirector, second-hop
|
||||
handshake, and the encoded pre-auth exchange. This was the big one.
|
||||
- **Full FUT API mapped** — ~100 response structures reverse engineered to field level.
|
||||
|
||||
**Phase 4 — building the FUT backend (August).** With the protocol understood, the work
|
||||
became making FUT actually *play*: card rendering, packs, the store, the transfer market,
|
||||
squads, and match rewards. This is where the project stands.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
FIFA 17 does not talk to one backend. It talks to **four**, on different protocols, and
|
||||
all four must be satisfied in sequence before FUT loads.
|
||||
|
||||
```
|
||||
FIFA 17 (Wine/Proton)
|
||||
│
|
||||
├─ LSX / Origin :4216 XML/TCP — local Origin client emulation
|
||||
│ login, entitlements, persona
|
||||
├─ Blaze :42127 redirector (TLS) → :42130 game, :42131 nucleus
|
||||
│ EA's binary Fire2/TDF RPC
|
||||
│ session, auth, and the CLIENT-CONFIG STORE
|
||||
├─ UTAS / RS4 :8099 the FUT REST API — JSON/HTTP, ~45 endpoints
|
||||
│ club, squads, packs, market, matches
|
||||
└─ POW / EASFC :8094 a third HTTP API (+ :8080 content)
|
||||
online status, level, credits, catalogue
|
||||
```
|
||||
|
||||
Plus **roster** (:8081), serving an XML file the FUT loading screen blocks on, and
|
||||
**autopatch**, which patches certificate verification in the running process.
|
||||
|
||||
### Redirection, in order of preference
|
||||
|
||||
1. **Blaze client-config keys** — the client reads its own service URLs from a key/value
|
||||
store that Blaze serves. Pointing FUT and EASFC at localhost needs **no root and no
|
||||
DNS manipulation**. This is the clean mechanism and most redirection uses it.
|
||||
2. **`/etc/hosts`** — for hostnames baked into the binary.
|
||||
3. **iptables DNAT** — for one hardcoded IP address.
|
||||
|
||||
---
|
||||
|
||||
## 4. The wire format — and why it is unforgiving
|
||||
|
||||
FUT responses are JSON, but the client does not use a general JSON object model. Each
|
||||
response class has a hand-written SAX-style deserializer that walks tokens and dispatches
|
||||
on a **hashed key id** ("atom"). Three consequences dominate the project:
|
||||
|
||||
**Atoms.** Every JSON key maps to a 16-bit id via FNV-1a. A recovered table of ~900
|
||||
id→name pairs is the Rosetta stone. A response spec is really "which atoms does this
|
||||
deserializer read, of what type".
|
||||
|
||||
**Type fidelity is fatal.** A scalar where an object or array is expected does not error —
|
||||
it **desyncs the reader and hard-freezes the game** in a busy loop. This is the primary
|
||||
failure mode of the entire project.
|
||||
|
||||
**Unknown keys are usually skipped — but not always.** Most deserializers route
|
||||
unrecognised atoms to a skip handler, making extra fields inert. At least one does not,
|
||||
so any unexpected key desyncs it.
|
||||
|
||||
**Working method:** locate the deserializer, extract its atom set and per-atom getter
|
||||
types, build the minimal body, and omit nested members whose shape isn't known — omission
|
||||
is safe, a wrong shape freezes the game.
|
||||
|
||||
---
|
||||
|
||||
## 5. What works
|
||||
|
||||
| Capability | State |
|
||||
|---|---|
|
||||
| Boot: Origin → Blaze → FUT hub | ✅ |
|
||||
| Club identity, coins, W/D/L record | ✅ |
|
||||
| Active squad — 11 real players, ratings, chemistry | ✅ |
|
||||
| Squad building — saves and survives relaunch | ✅ |
|
||||
| Squad roster ("MY SQUADS") | ✅ |
|
||||
| Transfer market — browse, bid, buy-now, list, watchlist | ✅ |
|
||||
| Store — buy packs | ✅ |
|
||||
| Packs — cards land in the club | ✅ via workaround (§6a) |
|
||||
| Quick sell — credits coins | ✅ |
|
||||
| Match loop — create/ready/play/destroy + rewards | ⚠️ built, **never requested by the client** (see below) |
|
||||
| Online/EASFC status bar | ✅ when enabled |
|
||||
| Seasons, tournaments, leaderboards, champions | ⚠️ routed, **never requested by the client** (see below) |
|
||||
| Card identity (names, faces, ratings) | ✅ resolves from the game's own local database |
|
||||
|
||||
### "Untested" is two different things, and the difference matters
|
||||
|
||||
The server log records the User-Agent of every request. The real client identifies as
|
||||
`ProtoHttp`; this project's own curl and Python probes do not. Separating them shows that
|
||||
several endpoints previously filed as "built but untested" have in fact **never been
|
||||
requested by the game at all**, and everything recorded against them was self-inflicted
|
||||
traffic:
|
||||
|
||||
| endpoint | client requests | project probes |
|
||||
|---|---|---|
|
||||
| `/leaderboards/options` | 5 | 1 |
|
||||
| `/clientdata/userHubData` | 13 | 2 |
|
||||
| `/user/accountinfo` | 23 | 49 |
|
||||
| `/season`, `/season/user` | **0** | 2 |
|
||||
| `/tournament`, `/tournament/user` | **0** | 3 |
|
||||
| `/leaderboards` (bare) | **0** | 2 |
|
||||
| `/champion` | **0** | 2 |
|
||||
| `/match` | **0** | 2 |
|
||||
| `/clubUser` | **0** | 93 |
|
||||
| `/user/list` | **0** | 180 |
|
||||
| `/sbs` (SBC), `/draft/mode` | **0** | 0 |
|
||||
|
||||
`/clubUser` and `/user/list` are the starkest: 273 requests between them, none from the
|
||||
game. Work was done on both on the assumption the client wanted them.
|
||||
|
||||
A zero in the client column does **not** mean the client never wants that endpoint. In
|
||||
most cases it means **nobody has navigated to that part of the game yet**. It does mean no
|
||||
claim about those endpoints has been tested against the client, and any analysis that does
|
||||
not apply this filter is misleading by default.
|
||||
|
||||
**Requirement:** every capture and analysis tool in this project should apply the
|
||||
User-Agent split by default rather than as an afterthought.
|
||||
|
||||
**Design convention.** Every risky change ships behind an environment flag whose default
|
||||
is whatever is live-proven. This exists because shipping "corrections" on by default broke
|
||||
two working screens — once freezing the store outright.
|
||||
|
||||
---
|
||||
|
||||
## 6. Open problems
|
||||
|
||||
### 6a. "Send to Club" ends the FUT session — SOLVED 2026-08-04
|
||||
|
||||
Opening a pack displayed the cards correctly, but choosing **Send to Club** produced
|
||||
*"there has been an error connecting to FIFA 17 Ultimate Team"* and a logout, seven
|
||||
attempts running. The cards always moved correctly server-side; only the
|
||||
acknowledgement was rejected.
|
||||
|
||||
**It was the response body all along.** `PUT ut/%s/item` does not parse an
|
||||
acknowledgement, it builds per-item **verdict** records, and the completion handler
|
||||
raises `EVENT_CARDS_MOVE_CARD_FAILURE` when the record vector is empty or when
|
||||
`success != 1`. Every body this project returned, `{}` included, therefore told the
|
||||
client the move had failed, and the client ended the FUT session because that is what
|
||||
that event does. Serving the real shape fixed it in one launch:
|
||||
|
||||
```json
|
||||
{"itemData":[{"id":100000125,"pile":"club","success":true}, ...]}
|
||||
```
|
||||
|
||||
Live result: five cards sent to the club, session survived, cards persisted, no
|
||||
`ut/delete/auth` logout. Both flags are now defaults and the autoclub workaround is
|
||||
retired.
|
||||
|
||||
**Why it took seven attempts,** which is the part worth keeping: the project had
|
||||
recorded that this deserializer had *no skip handler* and parsed only two keys. That
|
||||
was false, produced by searching a **truncated** decompile (the first 4,000 characters
|
||||
of a 6,193-character function). It implied "the body cannot be the problem", which is
|
||||
what redirected the investigation to client-side state. The quick-sell asymmetry that
|
||||
seemed to confirm it has a mundane explanation: quick sell's callbacks read only the
|
||||
transport status code and never touch the body, so its tolerance of `{}` said nothing
|
||||
about this endpoint.
|
||||
|
||||
The general lesson, now a standing rule: **never conclude an absence from a truncated
|
||||
or unverified-length extraction**, and treat every negative claim in the endpoint docs
|
||||
as weaker than the corresponding positive one.
|
||||
|
||||
### 6b. "MY CLUB" counter reads 0
|
||||
|
||||
The hub shows `MY CLUB 0` despite 99 items. Not the item list (the client *displays* all
|
||||
99), not the pile-size data (a 16-entry probe changed nothing), not lazy loading (fetched
|
||||
6 times). Unexplored: the neighbouring `ACTIVE SQUAD` counter works correctly — the
|
||||
difference between them is likely the answer.
|
||||
|
||||
### 6c. Store tiles read "unknown"
|
||||
|
||||
`"unknown"` is an unconditional default in a string constructor — the field is never
|
||||
written. The store renders *display groups*, and that member is parsed recursively by the
|
||||
same parser; sending it populated **froze the store**, so it is flagged off pending a
|
||||
correct group schema.
|
||||
|
||||
### 6d. Large parts of the game have never been opened
|
||||
|
||||
Distinct from 6a to 6c, which are things that misbehave. Per the User-Agent table in
|
||||
section 5, entire modes have never issued a single client request: Seasons, Tournaments,
|
||||
FUT Champions, Draft, SBC, and the match loop itself. Their endpoints are routed and their
|
||||
schemas are reversed, but no claim about any of them has been tested against the game.
|
||||
|
||||
This is not a bug list. It is unmeasured surface, and it is the cheapest information
|
||||
available to the project because most of it costs nothing but navigating menus. It is
|
||||
recorded here because "routed from a reversed schema" reads like a stronger claim than it
|
||||
is, and section 7 warns that this project's notes have described things differently from
|
||||
what is true.
|
||||
|
||||
*A multi-agent investigation into 6a and 6b is currently running.*
|
||||
|
||||
---
|
||||
|
||||
## 7. Notable findings
|
||||
|
||||
- **Class → deserializer resolution.** A response class's name literal is preceded by a
|
||||
4-byte header and the factory points at *the header*. Six attempts failed on that
|
||||
off-by-four; four returned nothing and were nearly recorded as "no deserializer exists".
|
||||
- **The URL table is a floor, not a ceiling.** Several real endpoints are built by
|
||||
appending a suffix at the call site and never appear in the binary's template table.
|
||||
Only live traffic reveals them — this caught the project three separate times.
|
||||
- **The project's own documentation has been wrong repeatedly** — fields described as
|
||||
inert turned out to be parsed, and documented key names didn't match the parsers.
|
||||
Verify against the decompiler, not the notes.
|
||||
- **The online layer was hiding in plain sight.** "EA FC servers unreachable" comes from a
|
||||
third HTTP API in a *loose, unpacked, string-rich* library — not the protected
|
||||
executable, and not any previously emulated layer. Redirectable purely by config.
|
||||
- **The main executable is Denuvo-packed**, so its code exists only in a live process.
|
||||
Live memory is readable, which is how a crash site was disassembled — but logic living
|
||||
there cannot be reverse engineered statically.
|
||||
|
||||
---
|
||||
|
||||
## 8. Tooling and quality
|
||||
|
||||
- **PyGhidra harness** with decompile / xref / vtable / byte-scan / class-resolution
|
||||
helpers (Ghidra's own Java scripting is broken on this machine).
|
||||
- **Minidump reader** — exception record, fault-time registers, module map, stack walk.
|
||||
- **Live code grabber** — reads and disassembles unpacked code from a running process.
|
||||
- **Read-only live model probes** for watching client state while playing.
|
||||
- **Test suites:** 380 live contract checks (type/freeze safety per reversed schema) plus
|
||||
51 pure unit checks. Both green.
|
||||
- **Traffic-replay audit** — diffs current responses against a known-good session to prove
|
||||
a change didn't alter what the client sees.
|
||||
- **~3,600 lines of RE documentation** across six files, including every eliminated
|
||||
hypothesis with its supporting evidence.
|
||||
|
||||
---
|
||||
|
||||
## 9. Roadmap
|
||||
|
||||
**Immediate (free, no code):** play a match — the reward loop is built and unit-tested but
|
||||
has never run in-game. Enable the game-mode endpoints and see whether four more modes
|
||||
light up.
|
||||
|
||||
**Near term:** close the two open problems, most likely via live instrumentation rather
|
||||
than more static analysis. Implement SBC and Draft (schemas already recovered). Fix the
|
||||
store display groups properly.
|
||||
|
||||
**Longer term:** the stated destination is porting this into the Rust `openfut-core`
|
||||
behind a FIFA-17 bridge. Everything currently lives in Python prototypes; the
|
||||
documentation is now good enough to write the port against.
|
||||
|
||||
---
|
||||
|
||||
## 10. Honest assessment
|
||||
|
||||
**What went well.** The FIFA 17 pivot was the decisive call — recognising that an
|
||||
unprotected binary was worth more than persisting against a hardened one. Blaze, Origin
|
||||
and the FUT API were all cracked end to end. The freeze-safety test suite has repeatedly
|
||||
caught regressions before they reached the game.
|
||||
|
||||
**What went badly.** Progress has been slowest where fixes were proposed before the
|
||||
assumption under them was tested. Both open problems absorbed many attempts built on
|
||||
plausible but unverified theories; several were disproved in a single measurement that
|
||||
could have been taken first. Two working screens were broken by shipping unverified
|
||||
"corrections" on by default — which is precisely why the flag convention exists now.
|
||||
|
||||
**The most reliable technique** has been comparing a working case against a failing one:
|
||||
diffing live traffic against a known-good session, and contrasting a succeeding endpoint
|
||||
with its failing sibling. That has produced more answers than any amount of decompilation.
|
||||
@@ -1,94 +0,0 @@
|
||||
# OpenFUT
|
||||
|
||||
**Offline Ultimate Team — like SPT, but for FIFA 23.**
|
||||
|
||||
OpenFUT replaces EA's retired FUT servers with a fully offline, single-player backend. You own FIFA 23 legitimately. You just want to keep playing after EA shut down the servers.
|
||||
|
||||
---
|
||||
|
||||
## Repositories
|
||||
|
||||
| Repo | Purpose |
|
||||
|---|---|
|
||||
| [`openfut-core`](./openfut-core) | Game-independent offline FUT backend |
|
||||
| [`openfut-bridge`](./openfut-bridge) | FIFA 23 integration layer + reverse-engineering proxy |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
FIFA 23 client
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ openfut-bridge │ ← intercepts FUT traffic, maps routes, logs unknown
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ openfut-core │ ← offline FUT backend: profiles, packs, squads, SBCs
|
||||
└─────────────────┘
|
||||
│
|
||||
▼
|
||||
SQLite database
|
||||
```
|
||||
|
||||
**Core** is game-independent. It speaks a clean REST API and knows nothing about FIFA 23 internals.
|
||||
|
||||
**Bridge** is FIFA-specific. It speaks FIFA 23's wire protocol and translates it into Core API calls. Bridge is where all reverse engineering work happens.
|
||||
|
||||
---
|
||||
|
||||
## Current Status
|
||||
|
||||
| Feature | Status |
|
||||
|---|---|
|
||||
| Local profiles + clubs | ✅ Working |
|
||||
| Coin system | ✅ Working |
|
||||
| Pack generator | ✅ Working |
|
||||
| Card collection | ✅ Working |
|
||||
| Squad builder | ✅ Working |
|
||||
| Objectives engine | ✅ Working |
|
||||
| SBC engine | ✅ Working |
|
||||
| Match rewards | ✅ Working |
|
||||
| NPC transfer market | ✅ Working |
|
||||
| Statistics | ✅ Working |
|
||||
| FIFA 23 integration | 🔴 Reverse engineering in progress |
|
||||
| Chemistry calculation | 🟡 In progress |
|
||||
| Full Draft mode | 🟡 In progress |
|
||||
| Squad Battles AI generator | 🟡 In progress |
|
||||
| Objectives claim flow | 🟡 In progress |
|
||||
|
||||
---
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
# Start the offline backend
|
||||
cd openfut-core
|
||||
cargo run
|
||||
|
||||
# Start the proxy (for traffic capture / FIFA integration)
|
||||
cd openfut-bridge
|
||||
cargo run
|
||||
```
|
||||
|
||||
Core listens on `http://127.0.0.1:8080` by default.
|
||||
Bridge listens on `http://127.0.0.1:8443` by default.
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **Offline-first.** No EA account required. No internet connection needed.
|
||||
- **Single-player only.** This is not a multiplayer server emulator.
|
||||
- **Data-driven.** All cards, packs, SBCs, and objectives are JSON files. Everything is moddable.
|
||||
- **Clean architecture.** Core has zero knowledge of FIFA 23. Bridge has zero game logic.
|
||||
- **No copyrighted assets.** No real player images, no EA trademarks in data files.
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is not affiliated with EA Sports. You must own FIFA 23 legitimately to use this software. This project does not bypass any ownership verification and is intended only to restore offline playability of a game whose online services have been retired.
|
||||
@@ -1,340 +0,0 @@
|
||||
{
|
||||
"metadata": {
|
||||
"reportDate": "2026-07-28",
|
||||
"codebaseName": "OpenFUT",
|
||||
"version": "0.1.0",
|
||||
"submodulesCovered": [
|
||||
"openfut-core",
|
||||
"openfut-bridge",
|
||||
"openfut-launcher"
|
||||
],
|
||||
"language": "Rust",
|
||||
"framework": "Axum + SQLite"
|
||||
},
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"severity": "critical",
|
||||
"category": "authentication",
|
||||
"file": "openfut-core/src/services/profile.rs",
|
||||
"line": 8,
|
||||
"cwe": "CWE-287",
|
||||
"title": "Missing Authentication on All Endpoints",
|
||||
"description": "No authentication or authorization checks on any API endpoint. The system uses single-profile design with get_active_profile() returning the first row (LIMIT 1) without any token validation, session management, or per-user isolation. In a networked context, any HTTP client can access all endpoints without credentials.",
|
||||
"impact": "Complete compromise of data confidentiality and integrity. Any attacker can view, modify, or delete all user data without authentication.",
|
||||
"exploitPath": "curl http://127.0.0.1:8080/clubs - accesses club data without any auth headers or tokens",
|
||||
"recommendation": "Implement stateless JWT tokens or session-based authentication. Add middleware to validate tokens on all endpoints. Implement per-user authorization checks in services."
|
||||
},
|
||||
{
|
||||
"severity": "critical",
|
||||
"category": "injection",
|
||||
"file": "openfut-core/src/routes/auth.rs",
|
||||
"line": 87,
|
||||
"cwe": "CWE-89",
|
||||
"title": "SQL Injection via String Interpolation",
|
||||
"description": "SQL table names are interpolated using string formatting: sqlx::query(&format!(\"DELETE FROM {table}\")). Although currently hardcoded in a loop, this violates parameterized query principles and creates a risk if the table list ever becomes user-controlled or the pattern is copied elsewhere.",
|
||||
"impact": "Potential remote code execution via database manipulation. If extended to user input, attackers could modify arbitrary tables or drop the database.",
|
||||
"exploitPath": "Currently mitigated by hardcoded table names, but the pattern is dangerous and violates secure coding practices.",
|
||||
"recommendation": "Use SQLx's dynamic query builders or identifier types that properly escape table/column names. Replace format! string interpolation with sqlx::query_builder for dynamic identifiers."
|
||||
},
|
||||
{
|
||||
"severity": "high",
|
||||
"category": "configuration",
|
||||
"file": "openfut-bridge/src/proxy.rs",
|
||||
"line": 44,
|
||||
"cwe": "CWE-295",
|
||||
"title": "TLS Certificate Validation Disabled",
|
||||
"description": "HTTP client explicitly disables TLS certificate validation: .danger_accept_invalid_certs(true). This bypasses all certificate pinning, expiration, and hostname verification, making the bridge vulnerable to man-in-the-middle attacks.",
|
||||
"impact": "Attacker positioned between bridge and upstream can intercept, modify, or read all traffic. Compromises confidentiality and integrity of requests to Core and external services.",
|
||||
"exploitPath": "MITM attack between openfut-bridge and openfut-core or upstream services. ARP spoofing on localhost subnet would redirect traffic.",
|
||||
"recommendation": "Remove .danger_accept_invalid_certs(true) in production. If testing requires it, gate behind a development-only environment variable with strong warning. Use proper certificate management (CA bundles, cert pinning)."
|
||||
},
|
||||
{
|
||||
"severity": "high",
|
||||
"category": "dos",
|
||||
"file": "openfut-core/src/services/season.rs",
|
||||
"line": 23,
|
||||
"cwe": "CWE-248",
|
||||
"title": "Unguarded expect() Causes Denial of Service",
|
||||
"description": "Multiple unchecked expect() calls that will panic and crash the server if database queries fail or return unexpected results: Ok(fetch(pool, profile_id).await?.expect(\"just inserted\"))",
|
||||
"impact": "Denial of service. A single database inconsistency or race condition crashes the entire server, making the application unavailable.",
|
||||
"exploitPath": "Trigger race conditions during concurrent requests (e.g., rapid profile deletion + season fetch). Database corruption or migration failure crashes the service immediately.",
|
||||
"recommendation": "Replace expect() with proper error handling (Result types, error logging, graceful degradation). Handle database query failures without panicking. Add integration tests for race conditions."
|
||||
},
|
||||
{
|
||||
"severity": "high",
|
||||
"category": "dos",
|
||||
"file": "openfut-core/src/services/season.rs",
|
||||
"line": 69,
|
||||
"cwe": "CWE-248",
|
||||
"title": "Unguarded expect() in season fetch",
|
||||
"description": "let season = fetch(pool, profile_id).await?.expect(\"season must exist\"); Panics if season is not found.",
|
||||
"impact": "Server crash on missing or deleted season records.",
|
||||
"exploitPath": "Delete a season via concurrent requests, then call /seasons endpoint. Server panics.",
|
||||
"recommendation": "Return proper error (AppError::NotFound) instead of panicking."
|
||||
},
|
||||
{
|
||||
"severity": "high",
|
||||
"category": "dos",
|
||||
"file": "openfut-core/src/services/season.rs",
|
||||
"line": 144,
|
||||
"cwe": "CWE-248",
|
||||
"title": "Unguarded expect() in season update",
|
||||
"description": "let updated = fetch(pool, profile_id).await?.expect(\"season must exist\");",
|
||||
"impact": "Server crash on concurrent season modifications.",
|
||||
"exploitPath": "Rapid concurrent season updates that fail race conditions.",
|
||||
"recommendation": "Handle missing records gracefully."
|
||||
},
|
||||
{
|
||||
"severity": "high",
|
||||
"category": "cors",
|
||||
"file": "openfut-core/src/app.rs",
|
||||
"line": 257,
|
||||
"cwe": "CWE-346",
|
||||
"title": "Permissive CORS Configuration Allows All Origins",
|
||||
"description": ".layer(CorsLayer::permissive()) enables CORS for all origins (*), methods, and headers. Any website can make cross-origin requests to the API and access/modify data.",
|
||||
"impact": "Cross-site request forgery (CSRF) attacks. Malicious websites can issue API requests on behalf of users. Data exfiltration via JavaScript from any origin.",
|
||||
"exploitPath": "Attacker website:\n <img src=\"http://127.0.0.1:8080/clubs\" />\n Fetch API calls to delete profiles, modify squads, etc.",
|
||||
"recommendation": "Restrict CORS to specific origins (e.g., localhost:3000 for web UI, or the game process if exposed). Use CorsLayer::very_restrictive() as default and explicitly allowlist origins."
|
||||
},
|
||||
{
|
||||
"severity": "high",
|
||||
"category": "dos",
|
||||
"file": "openfut-bridge/src/proxy.rs",
|
||||
"line": 47,
|
||||
"cwe": "CWE-248",
|
||||
"title": "HTTP Client Construction Panic",
|
||||
"description": ".expect(\"failed to build HTTP client\") will panic if the HTTP client fails to initialize, crashing the entire proxy service on startup.",
|
||||
"impact": "Service unavailability. Bridge cannot start if HTTP client configuration is invalid.",
|
||||
"exploitPath": "Invalid system configuration or missing TLS libraries causes HTTP client build to fail, crashing bridge during startup.",
|
||||
"recommendation": "Return Result<ProxyState, Error> from new() and handle construction errors. Use anyhow::Context for better error messages."
|
||||
},
|
||||
{
|
||||
"severity": "medium",
|
||||
"category": "information-disclosure",
|
||||
"file": "openfut-core/src/error.rs",
|
||||
"line": 54,
|
||||
"cwe": "CWE-209",
|
||||
"title": "Error Messages Leak Implementation Details",
|
||||
"description": "JSON parsing errors are returned directly to clients: format!(\"json parse error: {e}\"). Exposes serde_json parser internals and syntax details useful for crafting attacks.",
|
||||
"impact": "Information disclosure. Attackers learn the JSON parser implementation and can tailor payloads to bypass validation or find parser-specific quirks.",
|
||||
"exploitPath": "Send malformed JSON to any endpoint. Response includes parser error details (e.g., 'expected `,` at line 2 col 5') that aid in crafting exploits.",
|
||||
"recommendation": "Return generic error message to clients: 'invalid request format'. Log detailed errors internally with tracing for debugging."
|
||||
},
|
||||
{
|
||||
"severity": "medium",
|
||||
"category": "information-disclosure",
|
||||
"file": "openfut-core/src/error.rs",
|
||||
"line": 40,
|
||||
"cwe": "CWE-215",
|
||||
"title": "Database Errors Logged with Full Details",
|
||||
"description": "Database errors are logged with full SQL/query details: tracing::error!(\"Database error: {e}\"). If logs are exposed or compromised, schema, query patterns, and data structure are revealed.",
|
||||
"impact": "Information disclosure in logs. Compromised log files expose database schema and query logic useful for SQL injection or data exfiltration planning.",
|
||||
"exploitPath": "Access server logs (via log aggregation service, file access, etc.) and extract database schema and query patterns.",
|
||||
"recommendation": "Log only error type and ID to clients. Sanitize logs before exporting. Use structured logging with field masking for queries."
|
||||
},
|
||||
{
|
||||
"severity": "medium",
|
||||
"category": "input-validation",
|
||||
"file": "openfut-core/src/routes/auth.rs",
|
||||
"line": 17,
|
||||
"cwe": "CWE-1025",
|
||||
"title": "Hardcoded Default Credentials",
|
||||
"description": "Default username 'Player 1' is hardcoded with no unique identifier enforcement. Multiple profiles can be created with identical usernames, and weak defaults are used.",
|
||||
"impact": "Weak account creation, potential for account confusion or conflicts. No strong identity guarantees.",
|
||||
"exploitPath": "Multiple users create profiles with default 'Player 1' username. No way to distinguish profiles programmatically.",
|
||||
"recommendation": "Require explicit username on profile creation. Use UUIDs as primary identifiers. Validate username uniqueness and minimum length."
|
||||
},
|
||||
{
|
||||
"severity": "medium",
|
||||
"category": "input-validation",
|
||||
"file": "openfut-core/src/services/",
|
||||
"line": 0,
|
||||
"cwe": "CWE-400",
|
||||
"title": "Missing Input Length Validation",
|
||||
"description": "No maximum length checks on string fields (usernames, club names, squad names, etc.). Large inputs can cause database bloat, memory exhaustion, or DoS.",
|
||||
"impact": "Denial of service via large payloads. Database bloat. Memory exhaustion. While DefaultBodyLimit::max(256KB) provides some protection, field-level validation is missing.",
|
||||
"exploitPath": "POST /auth/local with username = 256KB string. Database receives bloated data. Repeated calls exhaust storage.",
|
||||
"recommendation": "Add input validation for all user-submitted strings. Set maximum lengths (e.g., username: 50 chars, club name: 100 chars). Validate at route handler level."
|
||||
},
|
||||
{
|
||||
"severity": "medium",
|
||||
"category": "configuration",
|
||||
"file": "openfut-core/src/db.rs",
|
||||
"line": 13,
|
||||
"cwe": "CWE-315",
|
||||
"title": "Unencrypted SQLite Database on Disk",
|
||||
"description": "SQLite database file (openfut.db) is stored unencrypted on disk. All user data, profiles, squads, cards, etc., are readable by anyone with filesystem access.",
|
||||
"impact": "Data breach if server filesystem is compromised. No protection against:local file access, stolen backups, forensic recovery.",
|
||||
"exploitPath": "Attacker gains filesystem access (compromised server, stolen disk). Reads openfut.db directly. All game data is readable without authentication.",
|
||||
"recommendation": "Use SQLite encryption (e.g., sqlcipher crate) or migrate to PostgreSQL with TLS. Implement file-level encryption. Use restrictive filesystem permissions (0600)."
|
||||
},
|
||||
{
|
||||
"severity": "medium",
|
||||
"category": "rate-limiting",
|
||||
"file": "openfut-core/src/app.rs",
|
||||
"line": 0,
|
||||
"cwe": "CWE-770",
|
||||
"title": "No Rate Limiting on Endpoints",
|
||||
"description": "No per-IP or per-user rate limiting. Endpoints like POST /auth/reset can be called repeatedly without restriction, allowing attackers to repeatedly wipe all data.",
|
||||
"impact": "Denial of service and data destruction. Attacker can spam /auth/reset to destroy user data or exhaust server resources.",
|
||||
"exploitPath": "for i in 1..1000: POST /auth/reset with confirm='reset'. All data wiped repeatedly.",
|
||||
"recommendation": "Implement rate limiting middleware using tower_governor or similar. Add per-IP limits (e.g., 10 requests/min) and per-endpoint limits. Use exponential backoff."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"category": "audit-logging",
|
||||
"file": "openfut-core/src/services/",
|
||||
"line": 0,
|
||||
"cwe": "CWE-778",
|
||||
"title": "Missing Audit Logging",
|
||||
"description": "No audit trail of user actions (profile creation, data deletion, squad modifications). Cannot detect unauthorized access, data tampering, or compliance violations.",
|
||||
"impact": "Incident response and forensics are impossible. Cannot determine who did what and when. Compliance risks (GDPR, etc.).",
|
||||
"exploitPath": "Attacker deletes all profiles, modifies squads. No audit log shows what happened or who did it.",
|
||||
"recommendation": "Add audit logging for all data mutations. Log: timestamp, user (profile) ID, action, resource affected, before/after state. Store in separate immutable table."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"category": "dependencies",
|
||||
"file": "openfut-bridge/Cargo.toml",
|
||||
"line": 0,
|
||||
"cwe": "CWE-1035",
|
||||
"title": "Older Dependency Versions (reqwest, rustls)",
|
||||
"description": "openfut-bridge uses reqwest 0.11 (latest is 0.12) and rustls 0.21 (latest is 0.23). Intentional for version matching, but creates a larger surface area for known CVEs.",
|
||||
"impact": "Potential vulnerabilities in older dependencies. Delayed access to security patches.",
|
||||
"exploitPath": "Known CVE in reqwest 0.11 or rustls 0.21 could be exploited. Combined with danger_accept_invalid_certs, TLS bypass becomes easier.",
|
||||
"recommendation": "Upgrade dependencies to latest versions when possible. Monitor CVE databases (CVE, RustSec) for the versions in use. Pin versions and set up automated dependency updates."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"category": "error-handling",
|
||||
"file": "openfut-core/src/app.rs",
|
||||
"line": 256,
|
||||
"cwe": "CWE-248",
|
||||
"title": "Body Size Limit Without Per-Field Validation",
|
||||
"description": "DefaultBodyLimit::max(256KB) limits the entire request body, but individual fields are not validated. A single large field can consume most of the limit.",
|
||||
"impact": "Mild DoS. Large field values cause database bloat. Not a critical issue due to body limit, but field-level validation would be better.",
|
||||
"exploitPath": "POST /auth/local with 250KB club_name field. Database receives bloated data.",
|
||||
"recommendation": "Add per-field validation in addition to body limits. Validate and sanitize fields before database insertion."
|
||||
}
|
||||
],
|
||||
"riskScore": 82,
|
||||
"riskCategory": "CRITICAL",
|
||||
"riskSummary": "OpenFUT has critical security issues that would make it unsafe for production or networked deployment. The most severe are the complete absence of authentication/authorization and the SQL injection pattern in the auth.rs module. The system is designed as single-player (single-profile) with no multi-tenant isolation, which is dangerous if exposed to the network.",
|
||||
"recommendations": [
|
||||
{
|
||||
"priority": "CRITICAL",
|
||||
"area": "Authentication & Authorization",
|
||||
"recommendation": "Implement JWT-based or session-based authentication on all endpoints. Add middleware to validate auth tokens on every request. Implement per-profile authorization checks. Currently any HTTP client can access all endpoints.",
|
||||
"effort": "High",
|
||||
"impact": "Blocks all data breaches from unauthenticated access"
|
||||
},
|
||||
{
|
||||
"priority": "CRITICAL",
|
||||
"area": "SQL Injection Prevention",
|
||||
"recommendation": "Replace sqlx::query(&format!(...)) in auth.rs:87 with proper parameterized identifiers. Use sqlx::query_builder for dynamic table/column names instead of string interpolation.",
|
||||
"effort": "Low",
|
||||
"impact": "Prevents SQL injection even if pattern is copied to user input"
|
||||
},
|
||||
{
|
||||
"priority": "HIGH",
|
||||
"area": "TLS & Transport Security",
|
||||
"recommendation": "Remove .danger_accept_invalid_certs(true) from proxy.rs:44. If development requires it, gate behind an environment variable (e.g., DEV_SKIP_TLS_VERIFICATION) with strong warnings in logs.",
|
||||
"effort": "Low",
|
||||
"impact": "Prevents MITM attacks on bridge-to-core communication"
|
||||
},
|
||||
{
|
||||
"priority": "HIGH",
|
||||
"area": "Error Handling",
|
||||
"recommendation": "Replace all expect() calls with proper Result handling. Use anyhow::Context or custom error types. Add logging for debugging but return generic errors to clients.",
|
||||
"effort": "Medium",
|
||||
"impact": "Prevents DoS via server panics"
|
||||
},
|
||||
{
|
||||
"priority": "HIGH",
|
||||
"area": "CORS",
|
||||
"recommendation": "Replace CorsLayer::permissive() with CorsLayer::very_restrictive() or explicit allowlist. For single-player use, restrict to localhost and the game process only.",
|
||||
"effort": "Low",
|
||||
"impact": "Prevents CSRF and cross-origin attacks"
|
||||
},
|
||||
{
|
||||
"priority": "HIGH",
|
||||
"area": "Rate Limiting",
|
||||
"recommendation": "Add per-IP rate limiting using tower_governor or similar. Implement limits on destructive endpoints (e.g., POST /auth/reset: 1 request per hour per IP).",
|
||||
"effort": "Medium",
|
||||
"impact": "Prevents DoS and repeated data destruction"
|
||||
},
|
||||
{
|
||||
"priority": "MEDIUM",
|
||||
"area": "Input Validation",
|
||||
"recommendation": "Add maximum length validation for all string fields (username, club_name, squad_name, etc.). Enforce at route handler level. Example: username max 50 chars, club_name max 100 chars.",
|
||||
"effort": "Medium",
|
||||
"impact": "Prevents database bloat and data validation failures"
|
||||
},
|
||||
{
|
||||
"priority": "MEDIUM",
|
||||
"area": "Data Encryption",
|
||||
"recommendation": "Use SQLite encryption (sqlcipher) or migrate to PostgreSQL with TLS. Set restrictive filesystem permissions (0600) on openfut.db.",
|
||||
"effort": "High",
|
||||
"impact": "Protects data at rest from filesystem access"
|
||||
},
|
||||
{
|
||||
"priority": "MEDIUM",
|
||||
"area": "Error Message Handling",
|
||||
"recommendation": "Return generic error messages to clients. Log detailed errors internally. Example: client sees 'invalid request', server logs 'JSON parse error: expected `,` at line 2'.",
|
||||
"effort": "Low",
|
||||
"impact": "Reduces information disclosure"
|
||||
},
|
||||
{
|
||||
"priority": "MEDIUM",
|
||||
"area": "Audit Logging",
|
||||
"recommendation": "Add audit trail for all data mutations (create, update, delete). Log timestamp, profile ID, action, resource, and before/after state. Store in immutable audit_log table.",
|
||||
"effort": "Medium",
|
||||
"impact": "Enables incident response and forensics"
|
||||
},
|
||||
{
|
||||
"priority": "LOW",
|
||||
"area": "Dependency Management",
|
||||
"recommendation": "Upgrade reqwest to 0.12 and rustls to 0.23 when possible. Set up Dependabot or RustSec monitoring for CVEs. Regularly audit dependencies.",
|
||||
"effort": "Low",
|
||||
"impact": "Reduces attack surface from known CVEs"
|
||||
},
|
||||
{
|
||||
"priority": "LOW",
|
||||
"area": "Default Values",
|
||||
"recommendation": "Remove hardcoded default username 'Player 1'. Require explicit username on profile creation. Use UUIDs for profile identification.",
|
||||
"effort": "Low",
|
||||
"impact": "Improves account identity and prevents confusion"
|
||||
}
|
||||
],
|
||||
"securityDesignNotes": {
|
||||
"intendedUse": "OpenFUT is designed for single-player offline use. Single-profile design is intentional for local FIFA 23 emulation.",
|
||||
"deploymentContext": "Localhost only (127.0.0.1:8080). Not intended for networked or multi-user deployment.",
|
||||
"implicationForSecurity": "Many security issues (no auth, permissive CORS) are acceptable for localhost-only use. However, the code structure lacks security boundaries, so if ever exposed to the network, it would be completely unsecured. Recommend adding security gates now rather than retrofitting later.",
|
||||
"suggestedDefensiveApproach": "Even for single-player use, add security layers (basic auth, CORS restrictions, rate limiting) to prevent accidental misuse if deployed in an unsafe context."
|
||||
},
|
||||
"positiveFindingsAndStrengths": [
|
||||
"✓ SQLx is used throughout with parameterized queries (except auth.rs:87)",
|
||||
"✓ Foreign key constraints are enforced in SQLite",
|
||||
"✓ UUIDs are used for entity IDs instead of sequential IDs (reduces enumeration attacks)",
|
||||
"✓ Request body size is limited to 256KB (prevents large payload DoS)",
|
||||
"✓ Concurrency is limited to 256 concurrent requests",
|
||||
"✓ Sensitive tokens (X-UT-SID, X-UT-PHISHING-TOKEN) are stripped from captures",
|
||||
"✓ Logging is structured using tracing crate (good for audit trails)",
|
||||
"✓ Services layer properly encapsulates database access"
|
||||
],
|
||||
"testingRecommendations": [
|
||||
"Add integration tests for authentication bypass (attempt to access endpoints without tokens)",
|
||||
"Test SQL injection payloads in auth.rs:87 pattern (if table names become dynamic)",
|
||||
"Test CORS with cross-origin requests from external origins",
|
||||
"Test rate limiting with rapid concurrent requests to /auth/reset",
|
||||
"Test input validation with oversized strings (100MB+ usernames)",
|
||||
"Test panic handling with corrupted database state",
|
||||
"Test TLS MITM scenarios (certificate pinning validation)",
|
||||
"Add fuzz testing for JSON parsing to find edge cases"
|
||||
],
|
||||
"complianceNotes": {
|
||||
"gdpr": "No explicit data handling policy. If user data is processed, GDPR requires consent, data retention limits, and audit trails. Not currently implemented.",
|
||||
"dataProtection": "Unencrypted database at rest violates most data protection frameworks.",
|
||||
"logging": "Audit logging is missing, violating compliance requirements."
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,250 +0,0 @@
|
||||
# OpenFUT — Direction Document
|
||||
*The pivot: FUT lives in the app; FIFA 23 is the match renderer.*
|
||||
*Supersedes the Blaze-backend approach as the primary plan. Last updated 2026-06-30.*
|
||||
|
||||
---
|
||||
|
||||
## 1. Goal (revised)
|
||||
|
||||
Deliver an **intuitive way to play a FUT-style experience with FIFA 23**, where:
|
||||
|
||||
- The entire **FUT experience** — cards, squads, packs, SBCs, coins, chemistry,
|
||||
progression — lives in a **custom app** (web UI or desktop) built on the
|
||||
already-complete OpenFUT Core economy backend.
|
||||
- **FIFA 23 is demoted to a match renderer.** Its only job is to play a
|
||||
single-player match using the squad the app built. No FUT mode, no online, no
|
||||
Blaze, no EA servers.
|
||||
|
||||
This deliberately drops in-game FUT cards/UI (they live in the app) in exchange
|
||||
for a project that **converges** instead of being gated behind months of
|
||||
backend reverse-engineering.
|
||||
|
||||
### Why this replaces the backend plan
|
||||
|
||||
The status review confirmed the backend route (faking EA's online stack) is
|
||||
blocked at an upstream in-process EbisuSDK gate, with Blaze/Fire2 unconfirmed
|
||||
beyond it — realistically 3–6 months of expert RE that may not converge. The
|
||||
app-centric route sidesteps **every** wall in that review by never making FIFA's
|
||||
own FUT mode run.
|
||||
|
||||
---
|
||||
|
||||
## 2. Base mode: Career, not Kick-Off
|
||||
|
||||
**Career mode is the base.** Reasons:
|
||||
|
||||
- FLE's live-editing API (`EditDBTableField`, Freeze Lineup) is **confirmed to
|
||||
work in career mode** and explicitly does NOT work in FUT/online modes.
|
||||
- Career already provides the FUT-shaped scaffolding we'd otherwise fake:
|
||||
persistent club, a fixture schedule, recorded results, progression across a
|
||||
season.
|
||||
- **Match results are written into the career DB**, making result capture a DB
|
||||
read rather than a fragile live-memory grab.
|
||||
|
||||
**Kick-Off is the prototype sandbox.** Use it first to prove squad injection
|
||||
works with nothing to corrupt (no save to break), then move the real loop onto
|
||||
career. Run the foundational injection test in BOTH.
|
||||
|
||||
---
|
||||
|
||||
## 3. Core architecture: the bidirectional FLE bridge
|
||||
|
||||
The backbone is a **bidirectional channel between the app and a resident FLE Lua
|
||||
script running inside the game.** Everything else is messages over this channel.
|
||||
|
||||
```
|
||||
Custom App (FUT experience)
|
||||
│ squad push ──────────────► ┌─────────────────────────────┐
|
||||
│ │ Resident FLE Lua script │
|
||||
│ ◄────────── game state │ (inside FIFA 23, career) │
|
||||
│ ◄────────── match result │ - reads game state │
|
||||
└────────────────────────────► │ - applies squad live │
|
||||
(file-watch or local socket) │ - reads results from DB │
|
||||
└─────────────────────────────┘
|
||||
│
|
||||
FIFA 23 plays the match
|
||||
```
|
||||
|
||||
Three message types over the bridge:
|
||||
|
||||
1. **App → Game: squad push.** The app's chosen XI + stats applied LIVE via
|
||||
`EditDBTableField`, replicating whatever DB write FLE's "Freeze Lineup"
|
||||
feature performs (see `docs/foundational-xi-injection-test.md` — the exact
|
||||
field(s) are found by diffing, not assumed). No restart, no
|
||||
file-copy-reload. (File-load remains a fallback.)
|
||||
|
||||
2. **Game → App: game state.** The resident script polls the game's current
|
||||
screen/menu state and reports "safe to apply" vs "not safe", driving a smart
|
||||
Apply button in the app (see §5).
|
||||
|
||||
3. **Game → App: match result.** After full-time, the script reads the result
|
||||
from the career DB and pushes score/scorers to the app, which awards
|
||||
coins/progression. (Manual entry is the baseline fallback.)
|
||||
|
||||
The bridge transport can be a watched file the in-game Lua polls, or a local
|
||||
socket — decided in build (see §7). Either way the *game keeps running*; a file,
|
||||
if used, is just the message channel, not a reload.
|
||||
|
||||
---
|
||||
|
||||
## 4. Tiered mod scope
|
||||
|
||||
Build in tiers matched to risk. The core tier is all the SAME kind of DB write,
|
||||
so it lands together once squad injection works.
|
||||
|
||||
### Tier 1 — Core writes (ride the same live DB-edit mechanism)
|
||||
- **Squad / custom XI** — the load-bearing primitive (Freeze Lineup's
|
||||
underlying write, replicated via script — see §6).
|
||||
- **Player stats as "cards"** — card tiers, in-form versions, SBC upgrades all
|
||||
expressed as written attribute values.
|
||||
- **Chemistry as stat adjustment** — app computes FUT chemistry, applies it as
|
||||
small stat bumps when writing players in (no in-game chem UI; that's in the app).
|
||||
- **Appearance / identity** — kits, names, team assignment, so the club looks
|
||||
like your club on the pitch.
|
||||
- **Formation / tactics** — squad structure carries the app's build onto the pitch.
|
||||
|
||||
### Tier 2 — Confirm-then-add
|
||||
- **Match difficulty per game** — to drive a Squad-Battles-style "this opponent is
|
||||
World Class". Settable in-game trivially; programmatic drive needs confirming.
|
||||
- **Match rules / modifiers** (half length, etc.) — for app-defined challenges.
|
||||
|
||||
### Tier 3 — Result capture (manual baseline + automated stretch)
|
||||
- **Manual:** user enters the score in the app after the match. Zero RE, ships
|
||||
first.
|
||||
- **Automated:** resident script reads the career-DB result (or, for Kick-Off,
|
||||
reads the in-match score from memory at full-time — precedent exists: the
|
||||
CM cheat table's `export_season_stats.lua` already reads goals/cards from
|
||||
memory via known offsets). Push to app → auto-award progression.
|
||||
|
||||
### Out of scope (stays in the app, by design)
|
||||
- In-game FUT cards, FUT menus, pack-opening animation, chemistry board, FUT
|
||||
presentation. The app is where it looks/feels like FUT.
|
||||
|
||||
---
|
||||
|
||||
## 5. The smart Apply button (state-aware)
|
||||
|
||||
Live DB edits only "stick" in safe menu states (the in-game "Edit Player" screen,
|
||||
for example, overwrites edits). So the bridge reads game state and gates applying:
|
||||
|
||||
- Resident Lua script polls the game's current-screen value (a few Hz),
|
||||
classifies **safe / not safe**, reports to the app.
|
||||
- App's **Apply button is enabled only when the script confirms a safe state**
|
||||
(squad hub, main menu); greyed otherwise.
|
||||
- **Safe-by-default-OFF:** unknown state → button greyed → never a risky write.
|
||||
Expand the known-safe list incrementally as states are confirmed.
|
||||
- **v2 (more seamless):** instead of greying, the app always lets you click and
|
||||
the script **queues** the apply, executing the moment a safe state is entered,
|
||||
then confirms back. Greying is v1; queue-and-apply is v2.
|
||||
|
||||
`IsInCM()` is a confirmed state-read; the specific screen-state address + the
|
||||
value→screen mapping is one-time reconnaissance (same technique as result reading).
|
||||
|
||||
---
|
||||
|
||||
## 6. What's confirmed vs what needs validating
|
||||
|
||||
**Confirmed (from FLE's own Lua API docs/wiki, checked 2026-06-30):**
|
||||
- FLE live-edits the running career DB without restart, via `EditDBTableField`
|
||||
(real signature: `EditDBTableField(cell)` where `cell = row["fieldname"]`
|
||||
with `.value` mutated first — not the table/index/field/value form an
|
||||
earlier draft of this doc assumed).
|
||||
- FLE reads game state via `IsInCM()`.
|
||||
- A `MEMORY` Lua class exists (`ReadInt`/`WriteInt`/`ReadMultilevelPointer`/
|
||||
etc.) for arbitrary process memory — confirms the result-reading fallback
|
||||
in §4 Tier 3 is a real, documented capability, not just cheat-table analogy.
|
||||
- `GetPlayersStats()` is a documented function returning per-player
|
||||
goals/assists/cards/etc. — a better confirmed path for match-result capture
|
||||
than raw memory offsets.
|
||||
- **Freeze Lineup** (Formation Editor → arrange XI → tick "Freeze Lineup" →
|
||||
`Data → Save`) is FLE's actual documented mechanism for forcing a starting
|
||||
XI in career mode. This **replaces** "selection bias" below.
|
||||
- OpenFUT Core (economy) is complete and tested.
|
||||
|
||||
**Walked back — not actually confirmed:**
|
||||
- "Selection bias forces specific players into the starting XI" — no such
|
||||
field appears anywhere in FLE's documented Lua API or its own example
|
||||
scripts. This was an unverified assumption carried over from general FIFA
|
||||
modding precedent (other titles), not anything checked against FLE/FIFA 23.
|
||||
See `docs/foundational-xi-injection-test.md` for the corrected plan, which
|
||||
uses Freeze Lineup instead.
|
||||
|
||||
**Needs validating (the foundational tests — see §7):**
|
||||
- Whether Freeze Lineup actually holds into a played match (FLE's wiki
|
||||
documents the feature but not a live-match test of it).
|
||||
- What DB table/field Freeze Lineup's `Data → Save` actually writes — it's
|
||||
GUI-only and undocumented at that level; finding it is part of the
|
||||
foundational test.
|
||||
- Whether that write can be replicated by a script (`EditDBTableField`) well
|
||||
enough to drive it from an EXTERNAL trigger, not just the Formation Editor
|
||||
UI — required for the app↔game bridge.
|
||||
- The app↔game bridge transport (file-watch vs socket) works cleanly under the
|
||||
run setup.
|
||||
- The screen-state address + safe/not-safe classification (FLE's `Events`
|
||||
API page exists in the wiki index but its content is currently empty/
|
||||
undocumented — this is more open than previously assumed).
|
||||
- Result read-back from the career DB after a match.
|
||||
|
||||
**Standing caveat:** the whole stack rides on **EAAC staying neutralized**
|
||||
(FLE's fake-launcher bypass). If a game update re-enables it, hooks fail. Keep
|
||||
game updates off; confirm neutralized state each session.
|
||||
|
||||
---
|
||||
|
||||
## 7. Build order / next steps
|
||||
|
||||
Each is a bounded, verifiable step. Do them in order; later ones depend on
|
||||
earlier answers.
|
||||
|
||||
1. **FOUNDATIONAL TEST — live custom XI in career.** Confirm Freeze Lineup
|
||||
holds into a played match, reverse-engineer the DB write it makes, then
|
||||
replicate that write from a script so it can be triggered externally
|
||||
instead of through the Formation Editor UI. See
|
||||
`docs/foundational-xi-injection-test.md` for the full procedure. *Done =
|
||||
a script-driven write produces a match that fields the squad you
|
||||
specified.* Everything rests on this.
|
||||
|
||||
2. **Pick the bridge transport.** Decide file-watch vs local socket for app↔game
|
||||
messaging; implement the minimal app→game squad push. *Done = app sends a
|
||||
squad, the resident script receives and applies it.*
|
||||
|
||||
3. **Game-state reader + smart Apply.** Find the screen-state address, classify
|
||||
safe/not-safe, expose to the app, gate the Apply button. *Done = button greys
|
||||
when you enter a match/edit screen, enables in the squad hub.*
|
||||
|
||||
4. **Result read-back.** Read the career-DB match result post-game, push to app,
|
||||
award progression. Manual entry ships alongside as the fallback. *Done = app
|
||||
updates coins from a played match.*
|
||||
|
||||
5. **Tier 1 breadth.** Extend the squad push to carry stats, appearance,
|
||||
formation (same write mechanism). *Done = the club looks and plays like the
|
||||
app's build.*
|
||||
|
||||
6. **Tier 2 + economy loop polish.** Difficulty drive, challenges, and the full
|
||||
pack → SBC → squad → match → reward loop closed end-to-end.
|
||||
|
||||
### Decision still open
|
||||
- **App form factor:** web UI vs desktop app. This affects the bridge transport
|
||||
(a desktop app can hold a local socket more naturally; a web UI leans toward a
|
||||
small local helper/file-watch). Decide before step 2.
|
||||
|
||||
---
|
||||
|
||||
## 8. Provenance
|
||||
|
||||
Clean-room throughout. This route relies on FLE's documented public API and the
|
||||
game's own supported career mode — no EA backend, no Blaze, and nothing derived
|
||||
from leaked EA source. The earlier backend RE remains clean-room and is preserved
|
||||
as a spec artifact; it is simply no longer the primary path.
|
||||
|
||||
---
|
||||
|
||||
## 9. One-paragraph summary
|
||||
|
||||
OpenFUT becomes a **FUT companion app that uses FIFA 23 as a match engine.** The
|
||||
app owns the entire FUT experience; a resident FLE Lua script in career mode
|
||||
applies the app's squad live (no restart), reports game state to drive a safe
|
||||
Apply button, and reads match results back to feed progression. This sidesteps
|
||||
every backend wall, runs on confirmed FLE capabilities, builds on the finished
|
||||
economy core, and delivers the intuitive, offline, FUT-flavored loop that is the
|
||||
actual goal.
|
||||
@@ -1,108 +0,0 @@
|
||||
# FIFA 23 PC Startup Flow (Offline / Proton)
|
||||
|
||||
Observed via FLE log, hook log, and file inspection on 2026-06-26.
|
||||
|
||||
## Launch chain
|
||||
|
||||
```
|
||||
umu-run / Steam → FIFA23.exe (via Proton/Wine)
|
||||
│
|
||||
├─ DLL load order (before entry point)
|
||||
│ ntdll.dll, kernel32.dll, ws2_32.dll …
|
||||
│ version.dll ← our hook DLL slot (loads here)
|
||||
│ FIFALiveEditor.DLL ← injected by FLE launcher after ~100 ms
|
||||
│
|
||||
├─ anadius / LSX emulator (anadius64.dll)
|
||||
│ Fakes EA App / Origin session
|
||||
│ Reads HKLM\SOFTWARE\Wow6432Node\Origin\ClientPath
|
||||
│ Writes AppData\Local\anadius\LSX emu\achievement-*.xml
|
||||
│ Provides fake PersonaId=1144668899 / UserId=1000200030000
|
||||
│
|
||||
├─ EA Anti-Cheat (EAAntiCheat.GameServiceLauncher.exe)
|
||||
│ Spawns as child; checks EAAntiCheat.cfg
|
||||
│ Not active in offline/cracked builds (FakeEAACLauncher present)
|
||||
│
|
||||
└─ FIFA23.exe entry point
|
||||
Frostbite engine init (BuildDate 2023-07-05, changelist 5417699)
|
||||
Reads Data\initfs_Win32 ← Frostbite package manifest
|
||||
Reads Data\layout.toc ← file-system layout
|
||||
Reads Patch\initfs_Win32 ← patches on top of base
|
||||
Reads Documents\FIFA 23\fifasetup.ini ← display settings
|
||||
Reads Data\locale.ini ← language table
|
||||
Reads Data\db_meta.xml (via FLE) ← DB schema for all tables
|
||||
```
|
||||
|
||||
## Phase timing (observed, single machine)
|
||||
|
||||
| Phase | Time after launch | Trigger |
|
||||
|------------------------------|-------------------|----------------------------------|
|
||||
| DLL load + FLE injection | 0 – 0.3 s | OS loader |
|
||||
| Engine + DirectX init | 0.3 – 5 s | FIFA23 entry point |
|
||||
| "Press any key" splash | ~5 s | First rendered frame |
|
||||
| Main menu | ~25 s | After key press |
|
||||
| FUT mode entry (attempted) | user-driven | User selects FUT tile |
|
||||
| Network calls to EA services | at FUT entry | DirtySDK / EAWebKit |
|
||||
|
||||
## Files read at startup (observed)
|
||||
|
||||
| File | Format | Purpose |
|
||||
|------|--------|---------|
|
||||
| `Data/initfs_Win32` | Frostbite pkg | Base asset manifest |
|
||||
| `Data/layout.toc` | Frostbite TOC | File layout index |
|
||||
| `Patch/initfs_Win32` | Frostbite pkg | Patch layer |
|
||||
| `Data/locale.ini` | INI | String localisation |
|
||||
| `Data/db_meta.xml` | XML | DB schema (loaded by FLE) |
|
||||
| `Data/id_map.json` | JSON | Player/team ID→name map |
|
||||
| `Data/char_conv.json` | JSON | Character conversion table |
|
||||
| `Documents/FIFA 23/fifasetup.ini` | INI | Display/audio settings |
|
||||
| `AppData/Local/Temp/FIFA 23/_replay0.bin` | binary | Replay buffer |
|
||||
| `anadius.cfg` | VDF | Fake EA persona config |
|
||||
| `AppData/Local/anadius/LSX emu/achievement-*.xml` | XML | Achievement state |
|
||||
|
||||
## Files written during a session (observed)
|
||||
|
||||
| File | When written | Content |
|
||||
|------|-------------|---------|
|
||||
| `Documents/FIFA 23/settings/Settings*` | Main menu reached | FBCHUNKS — controller/display prefs |
|
||||
| `Documents/FIFA 23/settings/ProfileOptions` | Profile load | FBCHUNKS — 1.5 MB profile blob |
|
||||
| `Documents/FIFA 23/filesystemcache/survey.state` | Startup | Empty state file |
|
||||
| `Documents/FIFA 23/filesystemcache/atlPlayTimeJson/playtime_*.json` | Ongoing | Playtime tracking |
|
||||
| `FIFA 23 Live Editor/config.json` | FLE ready | FLE settings (rewritten each session) |
|
||||
| `Logs/log_DD-MM-YYYY.txt` | Throughout | FLE debug log |
|
||||
|
||||
## Save file formats
|
||||
|
||||
### FBCHUNKS (Frostbite chunk container)
|
||||
- Magic: `46 42 43 48 55 4E 4B 53` (`FBCHUNKS`)
|
||||
- Byte 8: version (01 seen)
|
||||
- Offset 0x12: null-terminated label string (e.g. "Personal Settings 1", "Career - Player Progress 1")
|
||||
- Remainder: compressed/binary chunk data — no public spec; requires Frostbite tooling to fully parse
|
||||
- Tools: [Frosty Tool Suite](https://github.com/CadeEvs/FrostyToolSuite) can read/write these
|
||||
|
||||
### fifasetup.ini
|
||||
- Plain `KEY = VALUE` ini, fully human-readable
|
||||
- Safe to edit (display resolution, locale, vsync)
|
||||
|
||||
## Network calls at FUT entry (observed with iptables redirect)
|
||||
|
||||
Traffic pattern captured before changing strategy:
|
||||
- Multiple TLS connections to port 443 (destination: EA servers, resolved as various EA IPs)
|
||||
- TLS 1.3, AES-256-GCM (DirtySDK's copy of ProtoSSL, inline in FIFA23.exe)
|
||||
- No SNI sent (DirtySDK does not set `server_name` extension)
|
||||
- Connections originate from Wine/Proton network stack via Linux kernel TCP
|
||||
|
||||
Specific EA hostnames used (from openfut-bridge captures, not decoded from TLS):
|
||||
- `fut.ea.com` (FUT API)
|
||||
- `accounts.ea.com` (auth)
|
||||
- `gateway.ea.com` (entitlements)
|
||||
- `pin-river.data.ea.com` (telemetry)
|
||||
|
||||
## Key FLE Lua API hooks
|
||||
|
||||
FLE injects `FIFALiveEditor.DLL` and exposes a Lua engine that can:
|
||||
- Read any in-memory DB table via `GetDBTableRows(tableName)`
|
||||
- Write any cell via `EditDBTableField`
|
||||
- Query career mode state via `IsInCM()`
|
||||
- Get player/team names via `GetPlayerName`, `GetTeamName`
|
||||
|
||||
This is the primary safe integration path (see `fut-integration-options.md`).
|
||||
@@ -1,191 +0,0 @@
|
||||
# Foundational test — live custom XI via Freeze Lineup
|
||||
|
||||
**Status: PENDING — test has not yet been run.**
|
||||
|
||||
This is build-order step 1 from `docs/direction.md`: the test everything else
|
||||
in the direction pivot depends on.
|
||||
|
||||
## What changed since the first draft of this doc
|
||||
|
||||
The first version of this test guessed at a "selection bias" DB field and a
|
||||
candidate squad/lineup table name, based on general FIFA-modding precedent
|
||||
that turned out not to hold for FLE's documented API — no such field appears
|
||||
anywhere in FLE's actual Lua API docs or its own example scripts. While
|
||||
researching an unrelated hotkey issue, a **confirmed, FLE-documented**
|
||||
mechanism for forcing a starting XI turned up instead: the **Formation
|
||||
Editor's "Freeze Lineup" feature** (FLE wiki, `Formation-Editor.md`):
|
||||
|
||||
> This feature can be used in player career mode if you want to manage the
|
||||
> starting lineup of your team. Can be also used in manager career mode to
|
||||
> manually manage your next opponent's starting lineup.
|
||||
|
||||
Steps (GUI, no scripting): open Formation Editor for a team → arrange players
|
||||
on the pitch → tick **Freeze Lineup** → `Data → Save`.
|
||||
|
||||
This is real and documented, but it's GUI-only — there is no Lua function for
|
||||
it, and what DB write it actually performs under the hood is undocumented.
|
||||
This test is now two phases: confirm the GUI feature works at all, then
|
||||
reverse the DB write it makes so it can be replicated programmatically
|
||||
(required for the app→game bridge in build-order step 2, which needs this
|
||||
driven from outside the game, not from a person clicking checkboxes).
|
||||
|
||||
Also fixed in this pass: `EditDBTableField`'s real signature, confirmed from
|
||||
FLE's own docs and `lua/scripts/99ovr_99pot.lua`, is
|
||||
`EditDBTableField(cell)` where `cell` is `row["fieldname"]` with `.value`
|
||||
mutated in place — **not** `EditDBTableField(table, row_index, field, value)`
|
||||
as originally (incorrectly) written into the first draft of the injector
|
||||
script.
|
||||
|
||||
## What this test settles
|
||||
|
||||
Whether a *specific, externally-chosen* 11 players can be forced into a
|
||||
career (or Kick-Off) match's starting lineup, live, with no restart — and
|
||||
whether the mechanism that does it (Freeze Lineup's underlying DB write) can
|
||||
be driven by a script instead of a person clicking through the Formation
|
||||
Editor UI.
|
||||
|
||||
If Freeze Lineup itself doesn't actually hold under match start (the wiki
|
||||
doesn't show it being tested against a live match, only "you should be able
|
||||
to see... when you play against them"), the whole bridge architecture in
|
||||
`docs/direction.md` §3 needs rethinking — there is no other documented
|
||||
mechanism for forcing a lineup.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- FIFA 23 launched normally (FLE injected, EAAC neutralized — same baseline
|
||||
as `track-c-fut-table-test.md`)
|
||||
- A career save loaded (Freeze Lineup is documented for career mode
|
||||
specifically — confirm separately whether it does anything in Kick-Off,
|
||||
don't assume it does)
|
||||
- Note 11 player IDs from your club (`tools/squad-exporter/export_squad.lua`
|
||||
output, `playerid` field) that are NOT currently your starting XI
|
||||
|
||||
## Phase 1 — confirm Freeze Lineup actually holds into a match
|
||||
|
||||
This has zero scripting and should be done first since everything else is
|
||||
wasted effort if it fails.
|
||||
|
||||
1. Open the Live Editor overlay (F9, or `Windows → Settings` from the
|
||||
overlay's own menu bar if the hotkey isn't registering — see the umu/Wine
|
||||
hotkey note below).
|
||||
2. `Features → Teams` → find your team → `Edit`.
|
||||
3. `Team → Formation` to open the Formation Editor.
|
||||
4. Swap players around on the pitch so the XI differs from your current
|
||||
actual starting XI in some checkable way (e.g. swap two outfield players'
|
||||
positions, or bench/start a specific player).
|
||||
5. Tick **Freeze Lineup**.
|
||||
6. `Data → Save`.
|
||||
7. Hide Live Editor (F9), save your career **on a new slot** (don't overwrite
|
||||
your main save in case this corrupts something), exit to main menu, reload
|
||||
that save, and check the team's lineup screen / play a match and watch who
|
||||
starts.
|
||||
|
||||
**Record in the Results table below whether the frozen lineup actually took
|
||||
the pitch.** If not, stop here — Phase 2 is moot.
|
||||
|
||||
## Phase 2 — find the underlying DB write
|
||||
|
||||
Only proceed if Phase 1 confirmed Freeze Lineup works.
|
||||
|
||||
1. In FLE's Lua Engine, run `tools/squad-injector/snapshot_lineup_tables.lua`.
|
||||
This dumps every DB table whose name contains `squad`, `lineup`,
|
||||
`formation`, `tactic`, `teamsheet`, `selection`, `players`, or `teams` to
|
||||
`C:\FIFA 23 Live Editor\openfut_snapshot_<timestamp>.json`. Note this
|
||||
filename — this is your **before** snapshot.
|
||||
2. Without restarting or reloading, repeat the Formation Editor steps from
|
||||
Phase 1 (steps 2–6 only — open Formation Editor, change the lineup, tick
|
||||
Freeze Lineup, `Data → Save`). Don't save/reload the career between
|
||||
snapshot and this step — keep it to a single live session so the diff
|
||||
isn't polluted by other state changes.
|
||||
3. Run `snapshot_lineup_tables.lua` again. This is your **after** snapshot.
|
||||
4. Copy both JSON files out of the Wine prefix (same path pattern as
|
||||
`track-c-fut-table-test.md`: `~/Games/umu/.../drive_c/FIFA 23 Live
|
||||
Editor/`) and run:
|
||||
|
||||
```bash
|
||||
python3 tools/squad-injector/diff_snapshots.py before.json after.json
|
||||
```
|
||||
|
||||
5. The output shows exactly which table(s) and field(s) changed. This is the
|
||||
real, confirmed write Freeze Lineup performs — record it in the Results
|
||||
table below.
|
||||
|
||||
## Phase 3 — replicate the write via script
|
||||
|
||||
1. Open `tools/squad-injector/apply_lineup_write.lua` and fill in
|
||||
`TARGET_TABLE` and `TARGET_FIELDS` using Phase 2's diff output.
|
||||
2. Edit `C:\FIFA 23 Live Editor\openfut_test_xi.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"team_id": 12345,
|
||||
"xi": [
|
||||
{ "player_id": 111111, "position": 0 },
|
||||
{ "player_id": 222222, "position": 5 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Use 11 entries. Position codes are **confirmed numeric 0–27**
|
||||
(`GK=0, SW=1, RWB=2, RB=3, RCB=4, CB=5, LCB=6, LB=7, LWB=8, RDM=9, CDM=10,
|
||||
LDM=11, RM=12, RCM=13, CM=14, LCM=15, LM=16, RAM=17, CAM=18, LAM=19,
|
||||
RF=20, CF=21, LF=22, RW=23, RS=24, ST=25, LS=26, LW=27`) — from
|
||||
`lua/scripts/export_season_stats.lua`'s `get_pos_name` table in FLE's own
|
||||
repo, not a guess.
|
||||
3. Run `apply_lineup_write.lua` from FLE's Lua Engine.
|
||||
4. Repeat the save-to-new-slot / reload / check-lineup verification from
|
||||
Phase 1, but this time without ever opening the Formation Editor — the
|
||||
write was made entirely from the script.
|
||||
|
||||
## Classification criteria
|
||||
|
||||
### "Confirmed — full mechanism works"
|
||||
|
||||
Phase 1 holds, Phase 2 finds a clean diff, Phase 3's scripted write produces
|
||||
the same in-match result as the manual GUI path.
|
||||
|
||||
**Verdict:** Build-order step 1 done. Proceed to step 2 (bridge transport) in
|
||||
`docs/direction.md`.
|
||||
|
||||
### "GUI works, script doesn't"
|
||||
|
||||
Phase 1 holds but Phase 3's replicated write doesn't stick, even though the
|
||||
diffed fields matched what changed in Phase 2.
|
||||
|
||||
**Verdict:** Freeze Lineup likely does more than a single DB field write
|
||||
(e.g. an internal engine call beyond `EditDBTableField`'s reach, or a second
|
||||
write the diff missed because it happened in a table outside the `KEYWORDS`
|
||||
filter in `snapshot_lineup_tables.lua` — widen the filter and redo Phase 2).
|
||||
|
||||
### "Freeze Lineup doesn't hold at all"
|
||||
|
||||
Phase 1 fails — the lineup reverts to the game's own AI-picked XI regardless.
|
||||
|
||||
**Verdict:** No confirmed mechanism exists for forcing a lineup. This kills
|
||||
the bridge architecture as designed in `direction.md` §3 and needs a return
|
||||
to first principles — there is no fallback documented anywhere in FLE's wiki
|
||||
for this specific case.
|
||||
|
||||
## A note on the umu/Wine F9/F11 hotkey issue
|
||||
|
||||
If FLE's F9 (hide/show) hotkey isn't registering under umu, this is plausibly
|
||||
a Wine keyboard-hook limitation (FLE's global hotkey detection likely uses a
|
||||
low-level hook that doesn't translate cleanly through Wine's input layer) —
|
||||
not something documented anywhere in FLE's own troubleshooting docs, which
|
||||
don't mention Linux/Wine at all. F11 specifically has **no documented FLE
|
||||
function** — F9 is the only documented toggle. Workaround: click directly
|
||||
into the FLE overlay window (it should still be visible/clickable even if the
|
||||
hotkey doesn't fire) and use its own menu bar instead of relying on the
|
||||
hotkey.
|
||||
|
||||
## Results
|
||||
|
||||
*(To be filled in after the test is run.)*
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Date run | — |
|
||||
| Phase 1: Freeze Lineup holds into a match? | — |
|
||||
| Phase 2: table(s)/field(s) changed | — |
|
||||
| Phase 3: scripted write reproduces Phase 1 result? | — |
|
||||
| **Classification** | **PENDING** |
|
||||
@@ -1,136 +0,0 @@
|
||||
# FUT Integration Options
|
||||
|
||||
How to connect FIFA 23 to the OpenFUT local simulator, ranked by safety and feasibility.
|
||||
|
||||
## Option A — FLE Lua scripting (RECOMMENDED)
|
||||
|
||||
**What it does:** Use FIFA Live Editor's in-memory Lua API to read and write the game's
|
||||
database tables at runtime. FLE is already injected; no additional hooking needed.
|
||||
|
||||
**Why it's the right path:**
|
||||
- Fully offline, no EA servers touched
|
||||
- FLE is already trusted by the user (it's the launch mechanism)
|
||||
- `GetDBTableRows` / `EditDBTableField` expose the full Frostbite DB in memory
|
||||
- Scripts run inside the game process; no IPC complexity
|
||||
- Same mechanism used by modders for career mode edits today
|
||||
|
||||
**Integration design:**
|
||||
|
||||
```
|
||||
openfut-core (SQLite)
|
||||
│
|
||||
│ HTTP REST (localhost)
|
||||
▼
|
||||
openfut-bridge (port 8080, plain HTTP, no TLS)
|
||||
│ pulls club/squad/player data as JSON
|
||||
▼
|
||||
FLE Lua bridge script
|
||||
│ calls GetDBTableRows, EditDBTableField
|
||||
▼
|
||||
FIFA 23 in-memory DB (Frostbite)
|
||||
```
|
||||
|
||||
The Lua script polls openfut-core's REST API at intervals (or on FUT menu entry)
|
||||
and writes simulator data (coins, items, squad) into the appropriate DB tables.
|
||||
|
||||
**Tables likely involved (to verify with export_squad.lua):**
|
||||
|
||||
| Table | Expected FUT content |
|
||||
|-------|---------------------|
|
||||
| `players` | Player attributes (OVR, potential, stats) |
|
||||
| `teams` | Club identity, stadium, colors |
|
||||
| `fut_clubs` | FUT club record (if in memory when FUT loads) |
|
||||
| `fut_items` | Card inventory (if in memory) |
|
||||
| `fut_squads` | Active squad (if in memory) |
|
||||
|
||||
**Steps to implement:**
|
||||
1. Run `tools/squad-exporter/export_squad.lua` from FLE Lua Engine while in FUT to discover which tables are live
|
||||
2. Map openfut-core's data model to the discovered table fields
|
||||
3. Write a Lua polling script that fetches `/api/v1/club`, `/api/v1/squad`, etc. from openfut-core and calls `EditDBTableField` to populate them
|
||||
4. Optionally add a small HTTP client to the Lua script using LuaSocket (FLE ships with Lua 5.4)
|
||||
|
||||
**Limitations:**
|
||||
- Changes are in-memory only; they reset on game restart (acceptable for a simulator)
|
||||
- Only works while FLE is running (always true in our setup)
|
||||
- FUT tables may only be populated when the FUT hub is loaded; test with the exporter
|
||||
|
||||
---
|
||||
|
||||
## Option B — Local save file injection (career mode proxy)
|
||||
|
||||
**What it does:** Generate or modify offline career mode save files that contain FUT-like
|
||||
squad/player data, using Frostbite's FBCHUNKS format.
|
||||
|
||||
**Feasibility:** Medium
|
||||
- FBCHUNKS format is not publicly documented but has been partially reverse-engineered by the Frosty Tool Suite project
|
||||
- Career saves are 16 MB — large and complex
|
||||
- Changes take effect only after a game restart
|
||||
|
||||
**Best use:** Pre-populating a career club with the same players as the FUT simulator squad, so offline Squad Battles use "your" players.
|
||||
|
||||
**Steps:**
|
||||
1. Use Frosty Tool Suite to open a career save and map the schema
|
||||
2. Build a Python exporter that writes a valid FBCHUNKS save with simulator squad data
|
||||
3. Test: replace the career save, launch FIFA, verify squad is correct
|
||||
|
||||
---
|
||||
|
||||
## Option C — Local companion web UI
|
||||
|
||||
**What it does:** The user manages their FUT simulator entirely in a web browser (openfut-core already has this). A button exports the current squad/club state to a format that a Lua script or file injector can consume.
|
||||
|
||||
**This is already implemented** — openfut-core serves the FUT simulator REST API. The missing piece is the Lua bridge script (Option A) that reads from it.
|
||||
|
||||
---
|
||||
|
||||
## Option D — Local proxy for non-secured local calls only
|
||||
|
||||
**What it does:** Intercept FIFA 23's calls to `localhost:*` or a known local endpoint (not EA servers) and respond with simulator data.
|
||||
|
||||
**Feasibility:** Low value in isolation
|
||||
- FIFA 23 does not make calls to localhost in normal operation (except EA App on port 10853)
|
||||
- All FUT API calls go to EA's servers over TLS
|
||||
- Intercepting those would require the approach we explicitly ruled out
|
||||
|
||||
**Not recommended as a primary path.** Could be combined with Option A if the Lua script exposes a local socket that a coordinator process writes to.
|
||||
|
||||
---
|
||||
|
||||
## Option E — Memory bridge (Cheat Engine / FLE offsets)
|
||||
|
||||
**What it does:** Use known memory offsets (FLE's `offset_cache.json`) to read/write FUT state directly in FIFA23.exe's heap.
|
||||
|
||||
**Feasibility:** Medium — FLE already does this for career mode
|
||||
- FLE's `offset_cache.json` contains addresses for many game structures
|
||||
- FUT in-memory structs are separate from career structs and may not be mapped yet
|
||||
- This is fragile (offsets change with game updates)
|
||||
|
||||
**Not recommended** unless Options A and B both fail — too brittle.
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
**Start with Option A (FLE Lua scripting).**
|
||||
|
||||
1. Run `tools/squad-exporter/export_squad.lua` in-game to discover which DB tables exist in FUT mode
|
||||
2. Use `tools/file-watch-diff/watch.sh` to snapshot file state entering FUT and identify any new local files
|
||||
3. Use `tools/network-metadata-logger/netlog.sh` to log which EA hosts FIFA contacts at FUT entry (metadata only, no decryption)
|
||||
4. Map findings back to openfut-core's data model
|
||||
5. Implement the Lua bridge script that calls openfut-core's REST API and writes to discovered tables
|
||||
|
||||
If FUT tables are not exposed by FLE's DB API (they may not be — FUT data lives server-side in online mode), fall back to **Option B** (career save injection) to provide a squad that mirrors the simulator's club.
|
||||
|
||||
---
|
||||
|
||||
## Safety boundary
|
||||
|
||||
The following are out of scope and must not be implemented:
|
||||
|
||||
- Decrypting or inspecting EA's TLS traffic
|
||||
- Spoofing EA domain names or impersonating EA servers
|
||||
- Sending modified clients to EA's production services
|
||||
- Bypassing EA App login or account verification
|
||||
- Anything that could constitute online cheating or violate EA's ToS for online play
|
||||
|
||||
All integration must remain local/offline/single-player.
|
||||
@@ -1,208 +0,0 @@
|
||||
# OpenFUT Status Review
|
||||
*Generated 2026-06-30 — read-only stocktake, no code changed.*
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
OpenFUT has a mature offline FUT economy backend (Core, 25 phases, fully functional in
|
||||
isolation) and a sophisticated hook DLL that loads into FIFA 23, redirects EA hostnames
|
||||
to loopback, and bypasses TLS certificate verification. The Blaze/ProtoSSL layer is
|
||||
structurally ready: framing code exists, a TLS listener runs, cert-verify is patched.
|
||||
However the project is currently blocked before any Blaze traffic is ever seen.
|
||||
The fundamental problem is that FIFA 23 submits `GoOnline` to EbisuSDK and then
|
||||
**waits for an asynchronous ONLINE_STATUS_EVENT push** from the EA-app LSX server —
|
||||
a push that current code never sends. Every approach tried so far (flipping poll
|
||||
return values, forcing the state flags, read-only probes) confirms the gate is
|
||||
event-driven, not poll-driven. The Blaze captures directory contains six empty files.
|
||||
No Fire2 frame from FIFA 23 has ever been decoded. Until the ONLINE_STATUS_EVENT push
|
||||
is synthesized and delivered correctly, Milestones 2–7 are all waiting on the same
|
||||
single wall.
|
||||
|
||||
---
|
||||
|
||||
## 1. Proven vs Assumed
|
||||
|
||||
| Claim | Status | Evidence |
|
||||
|---|---|---|
|
||||
| FIFA 23 uses DirtySDK / ProtoSSL | **Proven** | String scan hit `ProtoSSLSend`, `ProtoSSLRecv`, `gosredirector` in FIFA23.exe memory (Task 1) |
|
||||
| `version.dll` loads and runs hook code | **Proven** | `hook.log` written at DLL_PROCESS_ATTACH |
|
||||
| `getaddrinfo` IAT hook redirects EA domains to loopback | **Proven** | Hook log records every EA `getaddrinfo` call; connect_hook log confirms port redirects |
|
||||
| ProtoSSL cert-verify prologue found and patched (FIFA23.exe) | **Proven** | ssl_patch.rs prologue confirmed at file offset 0xf0c850; hook log "ssl: main exe cert-verify patched" |
|
||||
| ProtoSSL cert-verify patched in EAWebKit.dll | **Proven** (if loaded) | Lazy patch fires on first EA getaddrinfo call; hook log message confirms |
|
||||
| Gate is upstream of DirtySDK — no DNS/connect fires on FUT entry | **Proven** | getaddrinfo, connect, WSASend/Recv hooks all show zero external traffic during "connecting to EA Servers" |
|
||||
| `GoOnline` is called by the game | **Proven** | Read-only detour on `anadius64.dll+0x2BB90` confirmed hit |
|
||||
| anadius returns GoOnline success | **Proven** | Handler observed returning successfully; game still retries every ~7 s |
|
||||
| Gate is downstream of GoOnline | **Proven** | GoOnline called + returns success; no Blaze connect follows |
|
||||
| Connection-state function: `GetInternetConnectedState @ anadius64.dll+0x27790` | **Proven** | Located via anadius LSX command-registration table; two-flag branch decoded (`+0xCAB1A`, `+0xCAB1B`) |
|
||||
| Gate is event-driven (game waits for async push, not a poll return) | **Proven** | Forced both state flags AND GoOnline return to "1"; game kept retrying; worker-thread stack scan confirms handler runs on anadius IOCP thread, not FIFA's thread |
|
||||
| GoOnline runs on anadius worker thread, not FIFA's call thread | **Proven** | Stack scan from inside detour found zero FIFA23.exe frames, sp ~2.4 KB from thread stack top |
|
||||
| `protossl-scan` live toolkit is exhausted for finding GoOnline in FIFA23.exe | **Proven** | No `"GoOnline"` string in image; worker-thread call stack has no FIFA frames; jmpscan yields ~3875 hits (overwhelmingly data false positives) |
|
||||
| FIFA 23 redirector config references `Authorization:` header (Nucleus token) | **Proven** | Found in FIFA23.exe .rdata pointer table @ `+0x83FC858` |
|
||||
| openfut-core REST API complete and tested | **Proven** | 25 phases, 15 migrations, passing integration tests |
|
||||
| Bridge LSX server starts and handles request-response | **Proven** (code) | `openfut-bridge/src/lsx.rs` + `main.rs` — server starts on 127.0.0.1:3216 |
|
||||
| Bridge LSX server ACTUALLY receives FIFA's LSX connections | **UNCONFIRMED** | anadius may intercept the same calls in-process before the TCP connection reaches the bridge |
|
||||
| Bridge LSX server `GetInternetConnectedState → connected="1"` unblocks the gate | **UNCONFIRMED (known to fail in-process)** | Flipping the value via anadius in-process failed; bridge path not yet confirmed working |
|
||||
| ONLINE_STATUS_EVENT push XML format | **UNKNOWN** | No capture; format not derived |
|
||||
| Fire2 framing is correct for FIFA 23 | **UNCONFIRMED** | Implemented based on post-2012 EA convention; all blaze captures are empty (0 bytes) |
|
||||
| Blaze component / command IDs for FIFA 23 | **UNKNOWN** | Zero captures; dispatch table entirely empty placeholders |
|
||||
| ProtoSSL recv-injection convention (non-blocking return values etc.) | **UNCONFIRMED** | Never reached M4; recv_hook module removed from active install path |
|
||||
| FUT REST endpoint paths in mapper.rs | **SPECULATIVE** | Based on community knowledge of older FIFA titles; the one actual capture in `captures/` is an early GET from before the Blaze strategy |
|
||||
| FLE Lua API exposes FUT DB tables in memory | **UNKNOWN** | `export_squad.lua` has never been run; FUT data may only exist server-side in online mode |
|
||||
|
||||
---
|
||||
|
||||
## 2. Milestone Status
|
||||
|
||||
| Milestone | Status | Blocker | Depends on unconfirmed assumption? |
|
||||
|---|---|---|---|
|
||||
| **M1** — Locate connection-state decision point | ✅ Done | — | No |
|
||||
| **M2** — Flip gate, force "connected" | ⛔ Blocked | Game waits for async ONLINE_STATUS_EVENT push; no current code sends it | Yes — unknown event XML format |
|
||||
| **M3** — First ProtoSSL plaintext on Blaze connection | 🔲 Not started | Depends on M2 | Yes — Fire2 framing unconfirmed |
|
||||
| **M4** — Answer redirector + decode first Fire2 frame | 🔲 Not started | Hard wall: Fire2 framing, recv-injection convention, component/command IDs all unconfirmed | Yes — all three unknown |
|
||||
| **M5** — Blaze preauth / login / postauth | 🔲 Not started | Depends on M4 | Yes — Blaze auth TDF body layout unknown |
|
||||
| **M6** — FUT entry + hub load | 🔲 Not started | Depends on M5; also requires FUT REST response shapes confirmed | Yes — endpoint paths speculative |
|
||||
| **M7** — Squad Battles (AI FUT) | 🔲 Not started | Depends on M6 | Yes |
|
||||
|
||||
**Note on roadmap.md wording:** Under M2–M4, roadmap.md uses `**Done (observable):**` bullets. These describe the *success criterion* for each milestone, not an achieved state. The authoritative status is in `connection-gate-findings.md` (M2 attempts failed; M3/M4 never started). The roadmap has not been updated to reflect M2 failure.
|
||||
|
||||
### M4 is the first hard wall in detail
|
||||
|
||||
Even assuming M2 is solved, M4 requires three unconfirmed things simultaneously:
|
||||
1. **Fire2 framing** — the 12-byte header layout is assumed; if FIFA 23 uses an older Fire variant or a custom delta, the codec will misparse every packet.
|
||||
2. **ProtoSSL recv-injection** — delivering responses to the game via recv hook requires knowing what return values and buffer conventions ProtoSSL expects; recv_hook.rs exists but is not installed.
|
||||
3. **Blaze component/command IDs** — the dispatch table is entirely empty; we cannot answer any request until IDs are known from captures.
|
||||
|
||||
All three are resolved by getting one real captured frame. M4 is primarily a capture problem, not a decoding problem — once bytes exist, the framing and IDs are immediately readable.
|
||||
|
||||
---
|
||||
|
||||
## 3. Blockers, Risks, Unknowns
|
||||
|
||||
### Blockers (stop progress now)
|
||||
|
||||
1. **ONLINE_STATUS_EVENT push not synthesized** *(M2 wall)*
|
||||
The game calls GoOnline, gets success, then waits indefinitely for a push event on the LSX socket that never arrives. This is the single gate blocking all Blaze work. Options: (a) trace the event format via Ghidra on FIFA23.exe (xref `ONLINE_STATUS_EVENT` string + the game's EbisuSDK listener), (b) RE anadius's LSX event-send path (find what it would push in an "online" scenario), (c) brute-force push candidate event XMLs and observe whether the game advances.
|
||||
|
||||
2. **Bridge LSX server delivery unconfirmed** *(architectural risk converted to blocker)*
|
||||
The hook passes port 3216 connections through, assuming the bridge LSX server on the Linux host receives them. If anadius's in-process hooks intercept the winsock calls before they reach the TCP stack, the bridge server is never reached. This must be confirmed by checking `openfut_hook.log` for a getaddrinfo on the LSX host, or by observing the bridge server's accept logs.
|
||||
|
||||
### Risks (could derail later)
|
||||
|
||||
3. **Fire2 framing wrong** *(M4 risk)*
|
||||
If FIFA 23 uses Fire (pre-2012) or a modified frame layout, the codec misparses. Mitigation: the server has a `Raw` fallback mode for capturing raw bytes when framing fails.
|
||||
|
||||
4. **Secondary auth-token gate** *(M5 risk)*
|
||||
`connection-gate-findings.md` noted the redirector request carries an `Authorization:` header. M1's final conclusion said `GetAuthCode` returns a fake token that appears accepted — but this was inferred, not confirmed by seeing the redirector request actually constructed with that token.
|
||||
|
||||
5. **EAAC not fully neutralized** *(persistent risk)*
|
||||
`FakeEAACLauncher` bypasses the anticheat launcher. The hook DLL is unsigned. If EAAC is ever active (e.g., after a game update re-enables it), all hooks fail silently. Marked as "not active in offline/cracked builds" — assumed, not confirmed on every launch.
|
||||
|
||||
6. **FUT REST response shapes wrong** *(M6 risk)*
|
||||
The 61 endpoint mappings in mapper.rs and the shaper stubs in shaper.rs are based on community guesses about older FIFA FUT APIs, not FIFA 23 captures. Response JSON shapes may differ enough to cause the client to fail silently or crash.
|
||||
|
||||
### Unknowns (open questions)
|
||||
|
||||
7. **ONLINE_STATUS_EVENT XML format** — exact tag names, field order, sender attribute, and any nonces/tokens required.
|
||||
8. **GoOnline event sequence** — whether ONLINE_STATUS_EVENT alone is sufficient or a sequence of events (e.g., PROFILE_EVENT, LOGIN_EVENT, COMMERCE_EVENT) is expected.
|
||||
9. **Whether FLE exposes FUT DB tables** — FUT card inventory and squad data likely live server-side in online mode; FLE may not surface them for in-process editing.
|
||||
10. **Blaze component/command IDs for FIFA 23** — entirely unknown; no captures.
|
||||
11. **openfut_hook.log current content** — we have the code but no log output in any document. Whether the current hook (with connect, ssl_patch, tls_bypass, WSAIoctl, origin_spy all installed) fires correctly and what it observes is unverified in this review.
|
||||
|
||||
---
|
||||
|
||||
## 4. Track Comparison
|
||||
|
||||
### Track A — Full EA-backend fake (M1–M7, playable FUT vs AI)
|
||||
|
||||
**What it delivers:** The FIFA 23 FUT hub loads from OpenFUT Core; Squad Battles matches play and reward economy items.
|
||||
|
||||
**Effort:** Research-grade. Minimum path: synthesize ONLINE_STATUS_EVENT (unknown format, 1–2 weeks of RE), then capture Fire2 frames (days once M2 is solved), then implement Blaze auth handlers (weeks), then implement FUT entry (weeks), then Squad Battles (weeks). Realistic minimum: 3–6 months of expert RE work.
|
||||
|
||||
**Proven support:** Hook loads and redirects correctly. TLS bypass patched. Core economy backend complete. Blaze framing code and TLS listener exist.
|
||||
|
||||
**Assumed:** Fire2 framing correct; component/command IDs discoverable from captures; FUT REST shapes close enough to community guesses; no additional undiscovered gates.
|
||||
|
||||
**Evidence for:** Architecture is coherent. The M1 finding (gate precisely named and decoded) was achieved cleanly. The in-process hook approach is validated.
|
||||
|
||||
**Evidence against:** M2 was attempted and failed with the in-process approach. The event-driven architecture adds a full EbisuSDK emulation layer before even one Blaze byte is seen. The live toolkit is exhausted (Path A verdict); Ghidra-level work on a 505 MB binary is required. Six capture files with zero bytes.
|
||||
|
||||
---
|
||||
|
||||
### Track B — Clean-room spec deliverable (M1–M5 documented)
|
||||
|
||||
**What it delivers:** A documented map of the connection gate, LSX event sequence, Blaze auth surface (transport, framing, gate conditions, component IDs, TDF schemas). Valuable as an archival/community artifact even if Track A stalls.
|
||||
|
||||
**Effort:** Medium. M1 is done. M2–M5 documentation emerges as a by-product of engineering work. The spec itself (writing) is lightweight; the engineering to produce the captures is the cost.
|
||||
|
||||
**Proven support:** M1 complete and documented. connection-gate-findings.md is already a high-quality spec artifact.
|
||||
|
||||
**Assumed:** Same as Track A for the unconfirmed values, but the spec can mark them `TODO/CONFIRM` rather than needing to implement them.
|
||||
|
||||
**Evidence for:** The clean-room constraint means a spec is the only artifact that can be safely published. connection-gate-findings.md shows this approach produces real value. B finishes even if A is never fully playable.
|
||||
|
||||
**Evidence against:** Track B alone doesn't produce a playable FUT; it is a foundation, not an end-user product.
|
||||
|
||||
---
|
||||
|
||||
### Track C — FLE Lua bridge (local-match path, skip the backend gate)
|
||||
|
||||
**What it delivers:** FIFA 23 career mode or Kick-Off with an OpenFUT club's players and squad loaded via FLE's in-memory DB API. No online gate, no Blaze, no TLS. Fully offline from day one.
|
||||
|
||||
**Effort:** Low-to-medium. FLE is already loaded in the normal launch path. Tools exist (`tools/squad-exporter/`, `tools/profile-exporter/`). Primary unknown is whether FUT-relevant DB tables are accessible.
|
||||
|
||||
**Proven support:** FLE Lua API exposes `GetDBTableRows` / `EditDBTableField` for career mode. `fifa23-startup-flow.md` confirms FLE injects at load. `fut-integration-options.md` documents the integration path in detail and rates this as the recommended option.
|
||||
|
||||
**Assumed:** FUT card/club/squad data has in-memory DB table representations that FLE can write. If FUT data is purely server-side (loaded from EA servers, not from the Frostbite DB layer), Track C produces no FUT simulation at all — only career mode player stats.
|
||||
|
||||
**Evidence for:** Career mode already works with FLE edits (community precedent). Tools are present and designed for this path. No infrastructure work needed.
|
||||
|
||||
**Evidence against:** FUT in FIFA 23 uses server-side data. The cards in a player's FUT club, the coins, the squad — these are fetched from `fut.ea.com` REST APIs, not from the Frostbite embedded DB. FLE's `GetDBTableRows` likely exposes base player stats tables but not FUT item tables. The crucial test (run `export_squad.lua` while in FUT mode) has never been done.
|
||||
|
||||
---
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Start Track C immediately as a parallel, low-cost validation.**
|
||||
|
||||
Run `export_squad.lua` in FLE while inside the FUT hub (or attempting to enter it). If FUT tables appear in the export, Track C is viable and is the fastest path to something a user can interact with. This test takes one session and costs nothing.
|
||||
|
||||
Simultaneously, **continue Track A/B with the next concrete RE step:** synthesize the ONLINE_STATUS_EVENT push. The most actionable option is to run `origin_spy` logs from the current hook to see what LSX events fire during a session, then attempt to push candidate event XMLs via the bridge LSX server and watch whether the game advances. This is bounded, testable work that either unblocks M2 or produces the spec value for Track B.
|
||||
|
||||
**Do not abandon Track A/B for Track C** — they are complementary. Core is already built; the bridge is mostly built. The gap is purely the RE wall at M2.
|
||||
|
||||
---
|
||||
|
||||
## 5. Architecture and Provenance Sanity-Check
|
||||
|
||||
### Hook + Brain coherence
|
||||
|
||||
The CLAUDE.md bridge architecture diagram (hook intercepts ProtoSSL → plain localhost TCP → blaze_brain → Core) remains coherent. The M1/M2 findings revealed one additional layer (EbisuSDK LSX event) that must precede the Blaze connection. The bridge has been updated to handle LSX directly. The overall design is sound; the M2 blocker is an implementation gap (event synthesis), not an architectural flaw.
|
||||
|
||||
**One inconsistency to flag:** The hook's `lsx.rs` contains a complete in-process LSX emulator (AES-128-ECB, CRandom, all response builders), but the recv/send hooks that activate it are explicitly removed (`lib.rs`: "recv/send hooks removed — LSX is now handled by the native openfut-bridge LSX server"). This is dead code. The bridge's LSX server is the current path. The in-process lsx.rs should either be deleted or documented as a fallback; its presence is confusing.
|
||||
|
||||
### Clean-room status
|
||||
|
||||
No evidence of EA leaked source anywhere in the tree. All RE work is derived from:
|
||||
- Running the shipping binary and observing behavior (function return values, network traffic patterns)
|
||||
- Memory scanning of the live process (string search, xref, disasm of observed addresses)
|
||||
- Reading anadius's own compiled output (its exported symbols, its LSX XML format — which is anadius's own implementation, not EA's)
|
||||
- Community FUT API knowledge (mapper.rs endpoint paths — plausible but speculative)
|
||||
|
||||
The Blaze framing in `fifa-blaze/crates/blaze-proto/src/frame.rs` cites "Fire2 used by ME3, BF3, and most post-2012 titles" — this is sourced from public community documentation of those older titles, not from any leaked EA source. **Clean-room intact.**
|
||||
|
||||
The `AES_KEY` in the hook's lsx.rs (`[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]`) is a placeholder key used for the LSX session encryption. The real session key is derived from the challenge seed via CRandom — this algorithm was RE'd from anadius's own binary. No EA source required.
|
||||
|
||||
---
|
||||
|
||||
## 6. If You Read Only This
|
||||
|
||||
- **The project is blocked at M2.** FIFA 23 submits `GoOnline`, gets success, then waits for an async `ONLINE_STATUS_EVENT` push on the LSX socket that no current code ever sends. All six Blaze capture files are empty (0 bytes). No Fire2 frame has ever been decoded.
|
||||
|
||||
- **M1 is the only completed milestone.** The gate function (`GetInternetConnectedState @ anadius64.dll+0x27790`) is precisely named and its two-flag branch decoded. Everything after M1 is either blocked or not started.
|
||||
|
||||
- **The next concrete action** is synthesizing the ONLINE_STATUS_EVENT push XML and testing whether the bridge's LSX server can deliver it to the game. This is the single thing that unblocks all Blaze work.
|
||||
|
||||
- **Track C (FLE Lua) is untested but cheap to validate.** Run `export_squad.lua` while in FUT to find out if FUT DB tables are accessible. If yes, it is the fastest path to user-visible results. If no, it is ruled out with one session.
|
||||
|
||||
- **openfut-core is complete and ready** — 25 phases, 15 migrations, full economy REST API, passing tests. It is not blocking anything; it is waiting for the bridge to connect to it.
|
||||
@@ -1,93 +0,0 @@
|
||||
# Track C — FUT DB table viability test
|
||||
|
||||
**Status: PENDING — test has not yet been run.**
|
||||
|
||||
## What this test settles
|
||||
|
||||
Track C ("FLE Lua bridge") would inject OpenFUT club data directly into FIFA 23's
|
||||
in-memory Frostbite DB tables at runtime, bypassing the entire backend/Blaze stack.
|
||||
It is only viable for FUT (not just career mode) if FUT-specific tables — card
|
||||
inventory, squad composition with FUT fields, coins — are accessible in memory when
|
||||
the game is in the FUT area.
|
||||
|
||||
FUT data in online mode is fetched server-side from `fut.ea.com`. It is not known
|
||||
whether FIFA 23 mirrors any of this into the Frostbite in-memory DB that FLE
|
||||
can read/write. This test settles that question directly.
|
||||
|
||||
## Test procedure
|
||||
|
||||
**Prerequisites:**
|
||||
- FIFA 23 launched normally via umu-run/Steam
|
||||
- FLE (FIFA Live Editor) injected and active (normal launch path)
|
||||
- EAAC in offline/neutralized state
|
||||
- Game navigated as deep into FUT as possible (FUT hub if reachable; otherwise the
|
||||
furthest FUT screen before the gate blocks it)
|
||||
|
||||
**Run the exporter:**
|
||||
1. In FLE's Lua Engine, open and run `tools/squad-exporter/export_squad.lua`
|
||||
(full path on the Windows side: `C:\<game>\openfut_squad_export.json`)
|
||||
2. Wait for the MessageBox "Done! N players, M teams." or "ERROR writing..."
|
||||
3. Retrieve the output file from the Wine prefix:
|
||||
`~/Games/umu/fifa23-tools/drive_c/FIFA 23 Live Editor/openfut_squad_export.json`
|
||||
(or wherever `C:\FIFA 23 Live Editor\` maps in the active prefix)
|
||||
|
||||
**What to inspect in the output:**
|
||||
- `all_db_tables` array — the complete list of table names visible to FLE right now
|
||||
- `fut_tables` object — any table whose name contains `fut`, `club`, `pack`, `item`, or
|
||||
`market` (the script auto-extracts these)
|
||||
- `is_career_mode` — confirms whether FUT or career mode was active
|
||||
|
||||
## Classification criteria
|
||||
|
||||
### "FUT tables present"
|
||||
|
||||
`fut_tables` is non-empty AND contains FUT-specific fields beyond base player stats:
|
||||
- e.g., `fut_items` with card-type / rating / chemistry fields
|
||||
- e.g., a squad table with FUT formation / chemistry / loan-flag fields
|
||||
- e.g., a coins or points balance field
|
||||
|
||||
**Verdict:** Track C is viable for FUT. Fastest path to user-visible results.
|
||||
|
||||
### "only base player tables"
|
||||
|
||||
`fut_tables` is empty (no `fut_*` / `club_*` / `item_*` / `market_*` table names found
|
||||
in `all_db_tables`), OR those tables exist but contain only base player attributes
|
||||
(OVR, potential, position, pace, …) — the same fields visible in career mode.
|
||||
|
||||
**Verdict:** Track C cannot produce FUT. It could at most provide a custom Kick-Off or
|
||||
career-mode match with players sourced from OpenFUT Core. FUT items and coins exist
|
||||
only on EA's servers (not in the in-memory DB in offline mode).
|
||||
|
||||
### "FUT area unreachable to test"
|
||||
|
||||
The connection gate blocked entering FUT deeply enough for FUT tables to be populated.
|
||||
Record which tables were visible and at what screen the test was run.
|
||||
|
||||
**Verdict:** Retest after M2 is unblocked, OR test with `TLS_ENABLED=false` bridge
|
||||
handling the entry check stub.
|
||||
|
||||
## Results
|
||||
|
||||
*(To be filled in after the test is run.)*
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Date run | — |
|
||||
| FIFA screen at test time | — |
|
||||
| `is_career_mode` | — |
|
||||
| Total tables in `all_db_tables` | — |
|
||||
| FUT-specific table names found | — |
|
||||
| Key FUT fields present | — |
|
||||
| **Classification** | **PENDING** |
|
||||
|
||||
## Honest prior
|
||||
|
||||
`fut-integration-options.md` rates this as the recommended path and lists `fut_clubs`,
|
||||
`fut_items`, `fut_squads` as "expected" tables. However those expectations are based on
|
||||
analogy with career mode (which does store club/squad in the DB). FUT's data model is
|
||||
architecturally different — it is account-bound server-side. The expectation may be
|
||||
wrong. This test is the oracle.
|
||||
|
||||
The `export_squad.lua` script checks `GetDBTablesNames()` exhaustively (not just
|
||||
assumed names), so it will surface any FUT tables that actually exist, regardless of
|
||||
what name they use.
|
||||
-1
Submodule fifa-blaze deleted from d2a9a01ec9
@@ -1,16 +0,0 @@
|
||||
# Keep the authoritative-tree build context lean: only tools/ and data/ runtime
|
||||
# files (plus the Dockerfile's own entrypoint/manifest) are needed in-image.
|
||||
.git
|
||||
.gitignore
|
||||
artifacts
|
||||
captures
|
||||
futmem
|
||||
staging
|
||||
docs
|
||||
FUT-RUNBOOK.md
|
||||
README.md
|
||||
data/memdump
|
||||
**/__pycache__
|
||||
*.pyc
|
||||
*.pem
|
||||
*.key
|
||||
@@ -1,13 +0,0 @@
|
||||
# Heavy / game-derived / volatile — never commit
|
||||
*.asm
|
||||
*.bin
|
||||
*.strings
|
||||
*.dll
|
||||
*.exe
|
||||
*.pem
|
||||
*.key
|
||||
*.log
|
||||
__pycache__/
|
||||
captures/
|
||||
staging/
|
||||
tools/fifa17_profile.json
|
||||
@@ -1,67 +0,0 @@
|
||||
# FIFA 17 offline FUT — runbook
|
||||
|
||||
Brings FIFA 17 **Ultimate Team** up against a 100% offline, clean-room emulated backend
|
||||
(no EA servers, no internet). Proven working end-to-end 2026-08-01 (auth → Blaze login →
|
||||
device-trust → the FUT hub).
|
||||
|
||||
## One-command start
|
||||
|
||||
```bash
|
||||
cd fifa17-recon/tools
|
||||
./openfut-fut.sh start # arms the host + starts all 5 servers
|
||||
```
|
||||
|
||||
`start` is idempotent and re-arms everything, so **just re-run it after a reboot**. It will pop a
|
||||
graphical password prompt (via `pkexec`) the first time to arm the host, then skip it while armed.
|
||||
|
||||
Then, **in this order**:
|
||||
|
||||
1. Launch FIFA 17 **fresh** (a clean launch avoids the "FUT Squad Update"/live-DB error caused by
|
||||
stale in-process state): `~/Desktop/launch-fifa17.sh`
|
||||
2. In-game, select **Ultimate Team**.
|
||||
3. At the **security question** ("system not trusted"): type **any answer** → Continue → OK.
|
||||
(Our server accepts any answer and marks the device trusted.)
|
||||
4. → the **FUT hub**.
|
||||
|
||||
`./openfut-fut.sh status` shows what's up; `stop` / `restart` do the obvious. The servers must be
|
||||
up **before** launching FIFA — they bind the ports the game dials.
|
||||
|
||||
## What it stands up
|
||||
|
||||
| Component | Port(s) | Role |
|
||||
|---|---|---|
|
||||
| `lsx_responder_v2.py` | 4216 | Origin LSX (login, GetProfile, GetAuthCode, events) |
|
||||
| `blaze_responder_v3b.py` | 42127 / 42130 / 42131 | Blaze redirector (TLS) / Blaze / Nucleus |
|
||||
| `roster_server.py` | 8081 | FUT roster-update XML |
|
||||
| `utas_server.py` | 8099 | FUT/UTAS (RS4) API: auth, device-trust, boot calls, hub |
|
||||
| `autopatch.py` | — | patches FIFA17.exe's ProtoSSL cert-verify on launch |
|
||||
|
||||
Privileged host state (armed by `root_arm.sh` via `pkexec`): `kernel.yama.ptrace_scope=0`,
|
||||
`net.ipv4.conf.lo.route_localnet=1`, iptables DNAT `159.153.51.20 → 127.0.0.1:42127`, and
|
||||
`/etc/hosts: 127.0.0.1 easw.easports.com` (the last persists across reboot; the rest don't).
|
||||
|
||||
## Persistence / reboot
|
||||
|
||||
Sysctls, iptables and the TLS cert are volatile — `./openfut-fut.sh start` rebuilds them, so the
|
||||
supported recovery is simply to re-run it after boot. (For hands-off auto-start you can wrap
|
||||
`root_arm.sh` in a root `systemd` oneshot at boot and the servers in a user service, but the
|
||||
one-command flow above is the sanctioned path.)
|
||||
|
||||
## Troubleshooting — the gate ladder (each fixed; if one regresses this is where)
|
||||
|
||||
Watch `/tmp/{lsx,blaze,roster,utas,autopatch}.log`. The screens you may see and their cause:
|
||||
|
||||
| Screen | Cause / fix |
|
||||
|---|---|
|
||||
| "log in to Origin" | LSX `GetInternetConnectedState` → `connected="1"` |
|
||||
| "title version outdated" | LSX `GetGameInfo UPTODATE` → `"true"` |
|
||||
| "Unable to retrieve account information" | LSX response `sender` must echo the request `recipient`; `AuthCode value=` |
|
||||
| "not eligible … age restriction" | mislabeled — the `AuthCode` reply needed the `value=` attribute |
|
||||
| "Unable to connect to the EA servers" | Blaze `CONF` durations must be unit-suffixed (`"30s"`, not `30000000`) |
|
||||
| FUT loading spinner (forever) | Blaze `CensusData` subscribe reply needs non-zero `CNP/NTMT`; and `ROSTERUPDATE_URL` served + roster_server up |
|
||||
| "error connecting to Ultimate Team" | `easw.easports.com` → 127.0.0.1 (`/etc/hosts`) + `utas_server` on :8099 |
|
||||
| "error downloading the FUT Squad Update" | stale in-process state — **relaunch FIFA fresh** |
|
||||
| Security question | type any answer → our `utas_server` `/phishing/validate` accepts it |
|
||||
|
||||
Full reverse-engineering write-ups: `login_dump/*.md`, `docs/*.md`. All findings are clean-room
|
||||
(from binaries we own); nothing from any leak. The whole protocol maps to FIFA 23 (identical wire format).
|
||||
@@ -1,196 +0,0 @@
|
||||
# FIFA 17 Blaze Recon (Rosetta Stone for FIFA 23)
|
||||
|
||||
Clean-room reverse engineering: all findings derive from observing our own running
|
||||
FIFA 17 client + static disassembly of the shipped binary we own. **No leaked EA
|
||||
source is used or referenced.**
|
||||
|
||||
> ## ✅ WORKING: FIFA 17 Ultimate Team, 100% offline
|
||||
> The full online + FUT stack is emulated. **Quick start → [`FUT-RUNBOOK.md`](FUT-RUNBOOK.md):**
|
||||
> ```bash
|
||||
> cd tools && ./openfut-fut.sh start # arm host + start all servers (re-run after reboot)
|
||||
> ~/Desktop/launch-fifa17.sh # then launch FIFA FRESH and select Ultimate Team
|
||||
> ```
|
||||
> Proven end-to-end 2026-08-01: auth → Blaze login → device-trust → the FUT hub. The rest of this
|
||||
> file is the reverse-engineering history that got there (see also `login_dump/*.md`, `docs/*.md`).
|
||||
|
||||
## Breakthrough — 2026-07-30: ProtoSSL cert pin DEFEATED, redirector handshake captured
|
||||
|
||||
FIFA 17 dials the **secure** Blaze redirector `winter15.gosredirector.ea.com` over
|
||||
TLS 1.2 (RSA-kx). We MITM it with a self-signed cert and defeated DirtySDK/ProtoSSL's
|
||||
cert pinning with two live `/proc/PID/mem` patches, then captured the **plaintext**
|
||||
first-hop handshake.
|
||||
|
||||
### Key architectural finding
|
||||
The secure redirector is **HTTPS + XML (ProtoHttp)**, NOT raw Fire2/Heat2:
|
||||
```
|
||||
POST /redirector/getServerInstance HTTP/1.1
|
||||
Host: winter15.gosredirector.ea.com:42230
|
||||
User-Agent: ProtoHttp 1.3/DS 15.1.2.1.0 (Windows)
|
||||
Content-Type: application/xml
|
||||
<serverinstancerequest>...</serverinstancerequest>
|
||||
```
|
||||
Fire2/Heat2 binary is the **second hop** — the redirector replies with a
|
||||
`<serverinstance>` XML naming a Blaze server IP:port; the client then connects THERE
|
||||
for the binary protocol. Full request body in `captures/getServerInstance_request.http`.
|
||||
|
||||
## Reproduce (after reboot — all live state is volatile)
|
||||
|
||||
Binary maps flat at base `0x140000000` under Wine/Proton (UMU-Proton-10.0-4,
|
||||
prefix `~/Games/umu/fifa17`). VAs below are stable across launches.
|
||||
|
||||
### 1. Root arm (scratchpad/root_arm.sh via pkexec)
|
||||
- `sysctl kernel.yama.ptrace_scope=0` (enables /proc/mem WRITES)
|
||||
- `sysctl net.ipv4.conf.lo.route_localnet=1`
|
||||
- `iptables -t nat -A OUTPUT -p tcp -d 159.153.51.20 -j DNAT --to-destination 127.0.0.1:42127`
|
||||
(winter15 resolves to 159.153.51.20; a /etc/hosts entry for winter15 would
|
||||
short-circuit the DNAT and must be ABSENT)
|
||||
|
||||
### 2. TLS capture server
|
||||
`scratchpad/blaze_tls_capture.py` on 127.0.0.1:42127, presents `redir_cert.pem`
|
||||
(self-signed, CN+SAN=winter15.gosredirector.ea.com), ciphers `ALL:@SECLEVEL=0`.
|
||||
|
||||
### 3. The two cert-verify patches (via scratchpad/memtool.py)
|
||||
The cert handler lives at ~`0x14613252x`. Two gates:
|
||||
|
||||
| VA | Role | Patch |
|
||||
|---|---|---|
|
||||
| `0x146132548` | Gate 1: `jne 0x1461326c4` (UNKNOWN_CA branch after chain-verify `call 0x146136410`) | 6 bytes → `90 90 90 90 90 90` (NOP) |
|
||||
| `0x1461361b0` | **Gate 2: the real pin** — cert verify helper; returned `-51 (0xffffffcd)` live | 3 bytes → `31 c0 c3` (`xor eax,eax; ret`) |
|
||||
|
||||
Gate 2 (`0x1461361b0`) is the decisive one — a shared verify helper (also called from
|
||||
`0x146131f86`). Forcing it to return 0 makes `r12d=0`, the `je 0x14613262d` at
|
||||
`0x14613256c` is taken, and the accept path at `0x146132675` is reached (skips the
|
||||
UNKNOWN_CA alert send at `0x146135250`).
|
||||
|
||||
NB: last session's patch of `0x146136410` (chain-verify callee) did NOT work — it was
|
||||
not the function returning the live failure. gdb breakpoint on `0x1461361b0` proved
|
||||
Gate 2 was the wall (`eax=0xffffffcd`).
|
||||
|
||||
## Breakthrough #2 — 2026-07-30: BOTH HOPS DEFEATED, Fire2/Heat2 decoded
|
||||
|
||||
Built `tools/blaze_responder.py`: answers `getServerInstance` over TLS with a
|
||||
`<serverinstanceinfo>` that redirects the client to a local plain Blaze port, and
|
||||
captures the second-hop Fire2 binary. The client **accepted the redirect and connected**,
|
||||
sending its `Util::preAuth` handshake in binary Heat2. `tools/decode_fire2.py` decodes it.
|
||||
|
||||
### getServerInstance response schema (the redirect)
|
||||
`ServerInstanceInfo.address` is a `ServerAddress` **union**; Heat2 XML encodes a union as
|
||||
`<field member="N"><valu>...</valu></field>`. Working response (member=0 = ipAddress variant):
|
||||
```xml
|
||||
<serverinstanceinfo>
|
||||
<address member="0"><valu>
|
||||
<hostname>127.0.0.1</hostname><ip>2130706433</ip><port>42130</port>
|
||||
</valu></address>
|
||||
<secure>0</secure>
|
||||
<trialservicename></trialservicename>
|
||||
<defaultdnsaddress>0</defaultdnsaddress>
|
||||
</serverinstanceinfo>
|
||||
```
|
||||
`<ip>` is a **decimal uint32** host-order (2130706433 = 127.0.0.1). `<secure>` 0/1 picks
|
||||
plaintext vs TLS for the Blaze connection. (Schema cross-confirmed clean-room vs MEC
|
||||
Catalyst private-server projects; response types reversed from the client's own TDF
|
||||
reflection tables at ~0x143891xxx / 0x144873xxx.)
|
||||
|
||||
### Fire2 frame header (16 bytes, big-endian)
|
||||
```
|
||||
[0:4] u32 payloadLength [6:8] u16 component [8:10] u16 command
|
||||
[10:12] u16 error/msgId [12] u8 msgType [13:16] reserved
|
||||
```
|
||||
First RPC observed: **component 0x0009 = Util, command 0x0007 = preAuth, msgType 0x02**.
|
||||
Ping/pong keep-alives: Util command 0x0002, empty payload, msgType 0x01/0x03.
|
||||
|
||||
### Heat2 TDF encoding (decoded in decode_fire2.py)
|
||||
Per field: 3-byte tag (4 chars, 6-bit packed, char = v?v+0x20:' ') + 1 type byte + value.
|
||||
Types: 0x00 int(varint, first byte 6 data bits + continue@0x80), 0x01 string(varint len incl
|
||||
null + bytes), 0x02 blob, 0x03 struct(nested, 0x00 terminator), 0x04 list, 0x05 map, 0x06 union.
|
||||
|
||||
### preAuth codebook (Util::preAuth PreAuthRequest) — captures/blaze/preauth_decoded.txt
|
||||
```
|
||||
CDAT{ IITO:int LANG:int SVCN:str='fifa-2017-pc' TYPE:int }
|
||||
CINF{ BSDK='15.1.1.3.0' BTIM='Jun 9 2017 16:15:40' CLNT='FIFA17' CPFT:int=4
|
||||
CSKU='FIFAPC' CVER='3175939' DSDK='15.1.2.1.0' ENV='prod' LOC:int PTVR='1.1' }
|
||||
FCCR{ CFID='BlazeSDK' }
|
||||
LADD:int
|
||||
```
|
||||
Same fields as the XML getServerInstance request → XML and Fire2 are the two encodings of
|
||||
the same TDFs (the Rosetta mapping).
|
||||
|
||||
(Fire2 header was later CORRECTED: byte[12] is the low octet of a 24-bit msgNum, not msgType;
|
||||
msgType lives in byte[13] high bits = (msgType<<5)|userIndex. REPLY=1→0x20, NOTIFICATION=2→0x40.
|
||||
metadataLen is u16 at [4:6]. See tools/heat2.py / blaze_responder_v3b.py.)
|
||||
|
||||
## Breakthrough #3 — Origin/LSX layer defeated (PreAuthResponse + login flow work)
|
||||
`tools/blaze_responder_v3b.py` answers preAuth, ping, fetchClientConfig, login (1/0x0A),
|
||||
getAccount(1/0x1E)=AccountInfo, getPersona/listPersonas, and pushes UserAuthenticated (0x7802/8).
|
||||
But Blaze isn't the online gate — **Origin is**, via its own in-process LSX layer:
|
||||
|
||||
- The Steampunks `stp-origin_emu.dll` serves **LSX** (length-prefixed, NUL-terminated XML) IN-PROCESS
|
||||
on 127.0.0.1:4216. It's a blind fixed-script replayer that reports OFFLINE. **Replace it**:
|
||||
bind 4216 BEFORE launching FIFA (`tools/lsx_responder_v2.py`; the stub has no SO_REUSEADDR and
|
||||
stands down cleanly), serve real request-driven LSX.
|
||||
- **LSX crypto (reversed + verified byte-exact):** server sends `<Challenge key="<32hex>">`; client
|
||||
replies `<ChallengeResponse response="<96hex>" key="<32hex>">`; **H = hex(AES128-ECB(K=000102..0f,
|
||||
PKCS7pad16(clientKey_ascii)))** (32 ASCII → 48 bytes/3 blocks); server sends `<ChallengeAccepted
|
||||
response="H">`; session key = srand(7) LCG of H; later msgs = hex(AES-ECB(pkcs7(xml)))+NUL.
|
||||
- **LSX verbs to answer:** GetProfile(PersonaId=33068179 Persona=CAGE US), GetSetting UPPERCASE
|
||||
(ENVIRONMENT→"production", LANGUAGE→"en_US", else "false"), GetGameInfo (LANGUAGES→locales,
|
||||
**UPTODATE→"true"** [else "title version outdated"], FREETRIAL→"false"),
|
||||
**GetInternetConnectedState→connected="1"** [the online gate], etc.
|
||||
- Gates cleared this way: "log in to Origin" ✓ and "title version outdated" ✓.
|
||||
|
||||
## Breakthrough #4 — 2026-07-30: repack fully reversed (LSX contract is a byte-exact oracle)
|
||||
The Steampunks repack ships two UPX-packed helpers; we unpacked and clean-room reversed BOTH
|
||||
(multi-agent workflow, adversarially verified — full report `docs/REPACK_INTEL.md`, emu disasm
|
||||
`docs/emu.asm`). Unpack recipe: `upx -d stp-origin_emu.dll` and `upx -d _fifa17.exe` (emu base
|
||||
0x180000000, loader base 0x140000000; both are NORMAL PEs — objdump works, unlike the encrypted
|
||||
FIFA17.exe). Findings that matter:
|
||||
- **`stp-origin_emu.dll` = the reference LSX server, offline BY CONSTRUCTION.** It is a blind
|
||||
18-step straight-line script with NO parser and NO dispatch branch; its ONLY unsolicited frame is
|
||||
the plaintext Challenge; it hardcodes `connected="0"` and has NO `<Login>` event / no auth vocab
|
||||
anywhere in its 19,456 bytes. **Structural proof (not absence-of-evidence): nothing in the repack
|
||||
can flip `m_isLoggedIn`.** The login mechanism lives ONLY in FIFA17.exe's live-decrypted code.
|
||||
- **Our `lsx_responder_v2.py` is CONFIRMED byte-exact** on framing (NUL-terminated, NUL counted in
|
||||
send len), crypto (AES-128 K_FIXED=000102..0f, PKCS7, srand(7)→61 session-key LCG), event shape,
|
||||
sender values (EALS / EbisuSDK / ""), and encryption timing (plaintext through ChallengeAccepted
|
||||
id=1, encrypted from id=2). Applied hardening C1–C3 (emu-exact `challenge_response` + tail assert,
|
||||
extract `response="`, partial-frame buffering). Selftest still green (session key unchanged).
|
||||
- The loader is an offline keygen/launcher (no WS2_32, no injection, no Blaze/Nucleus strings); its
|
||||
`.dlf` GameToken is a local ENTITLEMENT grant, not a session — will not help login. Shared build
|
||||
constants: UserId/PersonaId **33068179**, MachineHash == LSX Challenge key **2b8ee7fa…e32** (fixed).
|
||||
|
||||
## CURRENT WALL — "Unable to retrieve account information" (m_isLoggedIn stays 0)
|
||||
FIFA has two Origin flags — "internet reachable" (fed by GetInternetConnectedState, DONE) and
|
||||
**"user LOGGED IN" = OriginMgr.m_isLoggedIn @[OriginMgr+0x13]**, whose only setter is dispatcher
|
||||
case-2 @0x146f1e0ab, driven by a server-PUSHED `<Event sender="LOGIN_EVENT"><Login IsLoggedIn="true"/>`.
|
||||
**Pushing it 90× did NOT flip the flag.** Breakthrough #4 RULED OUT three causes: framing, event
|
||||
shape, and encryption timing are all confirmed correct. **Surviving hypotheses, narrowed:**
|
||||
(1) **encrypted mid-session Events are dropped** — the emu's only Event is plaintext+pre-key, so
|
||||
there is zero evidence FIFA routes an *encrypted* Event to the same parser (STRONGEST); (2) `sender`
|
||||
name mismatch; (3) handler-registration timing. Deeper residual: LoginStatePCLogin @0x1471b58e0 may
|
||||
gate on a session OBJECT [0x144b86bf8]->vtbl+0x60, not the flag.
|
||||
|
||||
### THE decisive next experiment (observe, don't guess) — new tooling ready
|
||||
1. Relaunch harness+game (below), run responder with an UNBOUNDED heartbeat so pushes stay in flight:
|
||||
`OPENFUT_LSX_EVENT_COUNT=100000 python3 -u tools/lsx_responder_v2.py`
|
||||
2. `bash tools/trace_login.sh` — attaches gdb, traces the sender matcher (0x147102880), the <Login>
|
||||
parser (0x147138660), and dispatcher case-2 (0x146f1e09e / set-1 0x146f1e0ab / set-0 0x146f1e0b8).
|
||||
Answers the 3-question ladder in ONE run: does the frame reach the matcher? what sender does it
|
||||
strcmp against (dumps the table entry)? does case-2 run and the flag flip?
|
||||
3. If the trace shows the ENCRYPTED frame never reaches the matcher → run the A/B:
|
||||
`OPENFUT_LSX_LOGIN_PLAINTEXT=1 …` pushes the Login Event in plaintext right after ChallengeAccepted.
|
||||
4. `tools/dump_login_code.py` — dumps + disassembles the decrypted login machinery at true VAs for a
|
||||
follow-up static pass if the trace points below the dispatcher.
|
||||
|
||||
## How to resume (rebuild the volatile harness)
|
||||
1. `pkexec sh tools/../scratchpad/root_arm.sh` (ptrace_scope=0, route_localnet, DNAT 159.153.51.20→42127).
|
||||
2. `python3 -u tools/lsx_responder_v2.py` — bind :4216 BEFORE launching FIFA.
|
||||
3. `python3 -u tools/blaze_responder_v3b.py` — :42127 (redir TLS) / :42130 (blaze) / :42131 (nucleus).
|
||||
4. `python3 tools/autopatch.py` — re-applies the two ProtoSSL cert patches to any relaunched FIFA17.exe.
|
||||
5. Launch FIFA via `~/Desktop/launch-fifa17.sh`; go Online.
|
||||
6. Watch /tmp/lsx.log (LSX) + /tmp/blaze_responder.log (Blaze); use tools/origin_login_probe.py to read
|
||||
m_isLoggedIn. Everything is volatile across reboot; VAs are stable (base 0x140000000).
|
||||
|
||||
## Live-state note
|
||||
Volatile across reboot: cert patches, responders, DNAT, ptrace_scope. `tools/autopatch.py`
|
||||
re-applies both cert patches automatically to any relaunched FIFA17.exe (VAs are stable).
|
||||
Everything ported here (framing, Heat2, LSX crypto, tags) applies to FIFA 23 (identical wire format).
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,416 +0,0 @@
|
||||
{
|
||||
"_source": "data/tables/*.json dumped read-only from the running client by tools/db_dump.py",
|
||||
"_key": "resourceId == carddbid, RAW u32 (the staff branches of FUN_180141660 do NOT mask, unlike players)",
|
||||
"families": {
|
||||
"headcoach": {
|
||||
"cardsubtypeid": 5,
|
||||
"table": "headcoachcards",
|
||||
"record_4c": 3,
|
||||
"rowcount": 124,
|
||||
"carddbid_band": [
|
||||
2000004,
|
||||
2000328
|
||||
],
|
||||
"absent_ids_in_band": 201,
|
||||
"miss_fill_assetid": 2000148,
|
||||
"seeds": [
|
||||
{
|
||||
"carddbid": 2000004,
|
||||
"assetid": 2000004,
|
||||
"attribute": 0,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 2000008,
|
||||
"assetid": 2000008,
|
||||
"attribute": 2,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 2000016,
|
||||
"assetid": 2000016,
|
||||
"attribute": 5,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 2000024,
|
||||
"assetid": 2000024,
|
||||
"attribute": 4,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 2000032,
|
||||
"assetid": 2000032,
|
||||
"attribute": 5,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 2000044,
|
||||
"assetid": 2000044,
|
||||
"attribute": 1,
|
||||
"value": 64,
|
||||
"amount": 5,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 2000064,
|
||||
"assetid": 2000064,
|
||||
"attribute": 1,
|
||||
"value": 80,
|
||||
"amount": 15,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 2000084,
|
||||
"assetid": 2000084,
|
||||
"attribute": 3,
|
||||
"value": 66,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 2000124,
|
||||
"assetid": 2000124,
|
||||
"attribute": 0,
|
||||
"value": 70,
|
||||
"amount": 10,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 2000164,
|
||||
"assetid": 2000164,
|
||||
"attribute": 1,
|
||||
"value": 77,
|
||||
"amount": 10,
|
||||
"rare": 0
|
||||
}
|
||||
],
|
||||
"bad_controls": [
|
||||
2000005,
|
||||
2000089,
|
||||
2000177,
|
||||
2000259
|
||||
]
|
||||
},
|
||||
"gkcoach": {
|
||||
"cardsubtypeid": 6,
|
||||
"table": "gkcoachcards",
|
||||
"record_4c": 10,
|
||||
"rowcount": 121,
|
||||
"carddbid_band": [
|
||||
9000001,
|
||||
9000324
|
||||
],
|
||||
"absent_ids_in_band": 203,
|
||||
"miss_fill_assetid": 9000258,
|
||||
"seeds": [
|
||||
{
|
||||
"carddbid": 9000001,
|
||||
"assetid": 9000001,
|
||||
"attribute": 0,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 9000017,
|
||||
"assetid": 9000017,
|
||||
"attribute": 4,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 9000021,
|
||||
"assetid": 9000021,
|
||||
"attribute": 5,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 9000025,
|
||||
"assetid": 9000025,
|
||||
"attribute": 0,
|
||||
"value": 80,
|
||||
"amount": 15,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 9000037,
|
||||
"assetid": 9000037,
|
||||
"attribute": 3,
|
||||
"value": 64,
|
||||
"amount": 5,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 9000081,
|
||||
"assetid": 9000081,
|
||||
"attribute": 2,
|
||||
"value": 66,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 9000117,
|
||||
"assetid": 9000117,
|
||||
"attribute": 5,
|
||||
"value": 80,
|
||||
"amount": 15,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 9000121,
|
||||
"assetid": 9000121,
|
||||
"attribute": 0,
|
||||
"value": 75,
|
||||
"amount": 10,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 9000125,
|
||||
"assetid": 9000125,
|
||||
"attribute": 1,
|
||||
"value": 74,
|
||||
"amount": 10,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 9000308,
|
||||
"assetid": 9000308,
|
||||
"attribute": 5,
|
||||
"value": 80,
|
||||
"amount": 15,
|
||||
"rare": 1
|
||||
}
|
||||
],
|
||||
"bad_controls": [
|
||||
9000002,
|
||||
9000086,
|
||||
9000174,
|
||||
9000280
|
||||
]
|
||||
},
|
||||
"physio": {
|
||||
"cardsubtypeid": 7,
|
||||
"table": "physiocards",
|
||||
"record_4c": 5,
|
||||
"rowcount": 51,
|
||||
"carddbid_band": [
|
||||
4000002,
|
||||
4000259
|
||||
],
|
||||
"absent_ids_in_band": 207,
|
||||
"miss_fill_assetid": 4000146,
|
||||
"seeds": [
|
||||
{
|
||||
"carddbid": 4000002,
|
||||
"assetid": 4000002,
|
||||
"attribute": 5,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 4000018,
|
||||
"assetid": 4000018,
|
||||
"attribute": 6,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 4000022,
|
||||
"assetid": 4000022,
|
||||
"attribute": 0,
|
||||
"value": 80,
|
||||
"amount": 15,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 4000026,
|
||||
"assetid": 4000026,
|
||||
"attribute": 2,
|
||||
"value": 55,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 4000046,
|
||||
"assetid": 4000046,
|
||||
"attribute": 3,
|
||||
"value": 64,
|
||||
"amount": 5,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 4000078,
|
||||
"assetid": 4000078,
|
||||
"attribute": 4,
|
||||
"value": 66,
|
||||
"amount": 5,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 4000122,
|
||||
"assetid": 4000122,
|
||||
"attribute": 3,
|
||||
"value": 74,
|
||||
"amount": 10,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 4000170,
|
||||
"assetid": 4000170,
|
||||
"attribute": 1,
|
||||
"value": 75,
|
||||
"amount": 10,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 4000194,
|
||||
"assetid": 4000194,
|
||||
"attribute": 6,
|
||||
"value": 75,
|
||||
"amount": 10,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 4000254,
|
||||
"assetid": 4000254,
|
||||
"attribute": 6,
|
||||
"value": 80,
|
||||
"amount": 15,
|
||||
"rare": 1
|
||||
}
|
||||
],
|
||||
"bad_controls": [
|
||||
4000003,
|
||||
4000089,
|
||||
4000175,
|
||||
4000257
|
||||
]
|
||||
},
|
||||
"fitnesscoach": {
|
||||
"cardsubtypeid": 8,
|
||||
"table": "fitnesscoachcards",
|
||||
"record_4c": 4,
|
||||
"rowcount": 115,
|
||||
"carddbid_band": [
|
||||
3000019,
|
||||
3000328
|
||||
],
|
||||
"absent_ids_in_band": 195,
|
||||
"miss_fill_assetid": 3000259,
|
||||
"seeds": [
|
||||
{
|
||||
"carddbid": 3000019,
|
||||
"assetid": 3000019,
|
||||
"value": 55,
|
||||
"amount": 1,
|
||||
"posbonus": 3,
|
||||
"fieldpos": 1,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 3000023,
|
||||
"assetid": 3000023,
|
||||
"value": 55,
|
||||
"amount": 1,
|
||||
"posbonus": 5,
|
||||
"fieldpos": 1,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 3000035,
|
||||
"assetid": 3000035,
|
||||
"value": 55,
|
||||
"amount": 1,
|
||||
"posbonus": 5,
|
||||
"fieldpos": 0,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 3000043,
|
||||
"assetid": 3000043,
|
||||
"value": 64,
|
||||
"amount": 2,
|
||||
"posbonus": 6,
|
||||
"fieldpos": 2,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 3000047,
|
||||
"assetid": 3000047,
|
||||
"value": 64,
|
||||
"amount": 2,
|
||||
"posbonus": 2,
|
||||
"fieldpos": 1,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 3000059,
|
||||
"assetid": 3000059,
|
||||
"value": 64,
|
||||
"amount": 2,
|
||||
"posbonus": 1,
|
||||
"fieldpos": 0,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 3000083,
|
||||
"assetid": 3000083,
|
||||
"value": 66,
|
||||
"amount": 2,
|
||||
"posbonus": 5,
|
||||
"fieldpos": 0,
|
||||
"rare": 0
|
||||
},
|
||||
{
|
||||
"carddbid": 3000091,
|
||||
"assetid": 3000091,
|
||||
"value": 80,
|
||||
"amount": 5,
|
||||
"posbonus": 5,
|
||||
"fieldpos": 2,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 3000127,
|
||||
"assetid": 3000127,
|
||||
"value": 70,
|
||||
"amount": 3,
|
||||
"posbonus": 5,
|
||||
"fieldpos": 1,
|
||||
"rare": 1
|
||||
},
|
||||
{
|
||||
"carddbid": 3000171,
|
||||
"assetid": 3000171,
|
||||
"value": 77,
|
||||
"amount": 3,
|
||||
"posbonus": 5,
|
||||
"fieldpos": 3,
|
||||
"rare": 0
|
||||
}
|
||||
],
|
||||
"bad_controls": [
|
||||
3000020,
|
||||
3000100,
|
||||
3000178,
|
||||
3000307
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
# Raw /proc/PID/mem captures: regenerate with tools/db_dump.py, never commit.
|
||||
# One of these directories reached 2.3GB.
|
||||
data/memdump/*.bin
|
||||
data/memdump/*.raw
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"20801": 27,
|
||||
"41236": 25,
|
||||
"48717": 0,
|
||||
"48940": 0,
|
||||
"52091": 7,
|
||||
"53612": 5,
|
||||
"53914": 5,
|
||||
"106231": 25,
|
||||
"108080": 3,
|
||||
"112253": 10,
|
||||
"120533": 5,
|
||||
"121944": 14,
|
||||
"137186": 5,
|
||||
"138956": 5,
|
||||
"139668": 0,
|
||||
"139720": 5,
|
||||
"139968": 0,
|
||||
"142780": 5,
|
||||
"142784": 3,
|
||||
"143745": 14,
|
||||
"146530": 3,
|
||||
"146562": 18,
|
||||
"146954": 14,
|
||||
"150724": 0,
|
||||
"152729": 5,
|
||||
"153244": 25,
|
||||
"156616": 16,
|
||||
"157481": 5,
|
||||
"158121": 0,
|
||||
"159147": 5,
|
||||
"161648": 18,
|
||||
"162240": 14,
|
||||
"162895": 14,
|
||||
"163711": 10,
|
||||
"165153": 25,
|
||||
"167431": 14,
|
||||
"168354": 0,
|
||||
"168651": 14,
|
||||
"171877": 14,
|
||||
"172879": 5,
|
||||
"175943": 27,
|
||||
"176769": 25,
|
||||
"177413": 14,
|
||||
"177610": 5,
|
||||
"178509": 25,
|
||||
"179783": 0,
|
||||
"179944": 5,
|
||||
"181458": 16,
|
||||
"182494": 0,
|
||||
"183497": 0,
|
||||
"184144": 16,
|
||||
"184432": 7,
|
||||
"185239": 5,
|
||||
"188152": 18,
|
||||
"189125": 16,
|
||||
"189461": 14,
|
||||
"189560": 10,
|
||||
"190547": 5,
|
||||
"191740": 14
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"155862": "CB",
|
||||
"158023": "RW",
|
||||
"167495": "GK",
|
||||
"176580": "ST",
|
||||
"177003": "CM",
|
||||
"182521": "CM",
|
||||
"183277": "LM",
|
||||
"183907": "CB",
|
||||
"184941": "CB",
|
||||
"188545": "ST",
|
||||
"189332": "LB",
|
||||
"190871": "LW",
|
||||
"192985": "RM",
|
||||
"197445": "LB",
|
||||
"200389": "GK",
|
||||
"202126": "ST",
|
||||
"20801": "LW"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
{"table":"BigAttendance","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":7,"rowsize_bytes":4,"rows_emitted":7,"rowblock":"0x7be0210","rowblock_bytes":104,"descriptor":"0x7bf0848","schema":[{"name":"max","bit":0,"width":4,"kind":"int","min":0,"max":10,"storage":"int"},{"name":"emotion","bit":4,"width":3,"kind":"int","min":0,"max":6,"storage":"int"},{"name":"min","bit":7,"width":4,"kind":"int","min":0,"max":10,"storage":"int"}],"rows":[{"max":6,"emotion":0,"min":0},{"max":6,"emotion":1,"min":0},{"max":7,"emotion":2,"min":0},{"max":7,"emotion":3,"min":0},{"max":10,"emotion":4,"min":1},{"max":9,"emotion":5,"min":1},{"max":9,"emotion":6,"min":1}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"MatchIntensity","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":9,"rowsize_bytes":4,"rows_emitted":9,"rowblock":"0x7c31058","rowblock_bytes":0,"descriptor":"0x42e9a3a8","schema":[{"name":"scorediff","bit":0,"width":4,"kind":"int","min":-4,"max":4,"storage":"int"},{"name":"time60","bit":4,"width":2,"kind":"int","min":-1,"max":2,"storage":"int"},{"name":"time90","bit":6,"width":2,"kind":"int","min":-1,"max":2,"storage":"int"},{"name":"time120","bit":8,"width":3,"kind":"int","min":-1,"max":3,"storage":"int"},{"name":"time75","bit":11,"width":3,"kind":"int","min":-1,"max":3,"storage":"int"},{"name":"time45","bit":14,"width":2,"kind":"int","min":-1,"max":2,"storage":"int"},{"name":"time30","bit":16,"width":2,"kind":"int","min":-1,"max":2,"storage":"int"},{"name":"time15","bit":18,"width":2,"kind":"int","min":-1,"max":2,"storage":"int"}],"rows":[{"scorediff":-4,"time60":-1,"time90":-1,"time120":-1,"time75":-1,"time45":-1,"time30":-1,"time15":-1},{"scorediff":-3,"time60":0,"time90":-1,"time120":-1,"time75":0,"time45":0,"time30":-1,"time15":-1},{"scorediff":-2,"time60":1,"time90":-1,"time120":0,"time75":0,"time45":0,"time30":0,"time15":-1},{"scorediff":-1,"time60":-1,"time90":2,"time120":3,"time75":3,"time45":1,"time30":0,"time15":1},{"scorediff":0,"time60":-1,"time90":2,"time120":3,"time75":3,"time45":0,"time30":-1,"time15":0},{"scorediff":1,"time60":0,"time90":1,"time120":2,"time75":2,"time45":1,"time30":0,"time15":1},{"scorediff":2,"time60":0,"time90":0,"time120":0,"time75":1,"time45":1,"time30":1,"time15":2},{"scorediff":3,"time60":-1,"time90":0,"time120":0,"time75":0,"time45":-1,"time30":-1,"time15":0},{"scorediff":4,"time60":0,"time90":0,"time120":-1,"time75":-1,"time45":0,"time30":-1,"time15":-1}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"NoAttendance","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":7,"rowsize_bytes":4,"rows_emitted":7,"rowblock":"0x7be01e8","rowblock_bytes":114,"descriptor":"0x7bf0f08","schema":[{"name":"max","bit":0,"width":4,"kind":"int","min":0,"max":10,"storage":"int"},{"name":"emotion","bit":4,"width":3,"kind":"int","min":0,"max":6,"storage":"int"},{"name":"min","bit":7,"width":4,"kind":"int","min":0,"max":10,"storage":"int"}],"rows":[{"max":7,"emotion":0,"min":0},{"max":8,"emotion":1,"min":0},{"max":9,"emotion":2,"min":0},{"max":9,"emotion":3,"min":0},{"max":10,"emotion":4,"min":1},{"max":9,"emotion":5,"min":1},{"max":8,"emotion":6,"min":0}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"assetcryptokeys","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":44,"rows_emitted":0,"rowblock":"0x42750c68","rowblock_bytes":3553,"descriptor":"0x7bf0e48","schema":[{"name":"key","bit":0,"width":256,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"keyid","bit":256,"width":64,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"artificialkey","bit":320,"width":7,"kind":"int","min":0,"max":127,"storage":"int"}],"rows":[]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"audiostadium","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":86,"rowsize_bytes":4,"rows_emitted":86,"rowblock":"0x42e9a4c8","rowblock_bytes":369,"descriptor":"0x7b57908","schema":[{"name":"stadiumpalanguageindex","bit":0,"width":6,"kind":"int","min":-1,"max":31,"storage":"int"},{"name":"stadiumid","bit":6,"width":9,"kind":"int","min":0,"max":511,"storage":"int"}],"rows":[{"stadiumpalanguageindex":0,"stadiumid":1},{"stadiumpalanguageindex":4,"stadiumid":2},{"stadiumpalanguageindex":3,"stadiumid":5},{"stadiumpalanguageindex":2,"stadiumid":9},{"stadiumpalanguageindex":4,"stadiumid":10},{"stadiumpalanguageindex":0,"stadiumid":13},{"stadiumpalanguageindex":1,"stadiumid":14},{"stadiumpalanguageindex":7,"stadiumid":15},{"stadiumpalanguageindex":-1,"stadiumid":26},{"stadiumpalanguageindex":0,"stadiumid":28},{"stadiumpalanguageindex":1,"stadiumid":29},{"stadiumpalanguageindex":2,"stadiumid":30},{"stadiumpalanguageindex":-1,"stadiumid":32},{"stadiumpalanguageindex":-1,"stadiumid":33},{"stadiumpalanguageindex":-1,"stadiumid":34},{"stadiumpalanguageindex":-1,"stadiumid":35},{"stadiumpalanguageindex":2,"stadiumid":41},{"stadiumpalanguageindex":4,"stadiumid":42},{"stadiumpalanguageindex":0,"stadiumid":100},{"stadiumpalanguageindex":13,"stadiumid":104},{"stadiumpalanguageindex":0,"stadiumid":112},{"stadiumpalanguageindex":0,"stadiumid":113},{"stadiumpalanguageindex":0,"stadiumid":115},{"stadiumpalanguageindex":0,"stadiumid":116},{"stadiumpalanguageindex":2,"stadiumid":135},{"stadiumpalanguageindex":2,"stadiumid":137},{"stadiumpalanguageindex":-1,"stadiumid":147},{"stadiumpalanguageindex":-1,"stadiumid":149},{"stadiumpalanguageindex":-1,"stadiumid":153},{"stadiumpalanguageindex":0,"stadiumid":155},{"stadiumpalanguageindex":0,"stadiumid":156},{"stadiumpalanguageindex":3,"stadiumid":157},{"stadiumpalanguageindex":-1,"stadiumid":158},{"stadiumpalanguageindex":-1,"stadiumid":172},{"stadiumpalanguageindex":-1,"stadiumid":175},{"stadiumpalanguageindex":-1,"stadiumid":176},{"stadiumpalanguageindex":-1,"stadiumid":178},{"stadiumpalanguageindex":-1,"stadiumid":179},{"stadiumpalanguageindex":-1,"stadiumid":180},{"stadiumpalanguageindex":-1,"stadiumid":181},{"stadiumpalanguageindex":-1,"stadiumid":182},{"stadiumpalanguageindex":-1,"stadiumid":183},{"stadiumpalanguageindex":-1,"stadiumid":192},{"stadiumpalanguageindex":-1,"stadiumid":193},{"stadiumpalanguageindex":-1,"stadiumid":194},{"stadiumpalanguageindex":-1,"stadiumid":195},{"stadiumpalanguageindex":-1,"stadiumid":196},{"stadiumpalanguageindex":-1,"stadiumid":197},{"stadiumpalanguageindex":-1,"stadiumid":212},{"stadiumpalanguageindex":-1,"stadiumid":228},{"stadiumpalanguageindex":-1,"stadiumid":229},{"stadiumpalanguageindex":0,"stadiumid":246},{"stadiumpalanguageindex":3,"stadiumid":247},{"stadiumpalanguageindex":0,"stadiumid":248},{"stadiumpalanguageindex":-1,"stadiumid":249},{"stadiumpalanguageindex":0,"stadiumid":260},{"stadiumpalanguageindex":-1,"stadiumid":261},{"stadiumpalanguageindex":-1,"stadiumid":262},{"stadiumpalanguageindex":14,"stadiumid":264},{"stadiumpalanguageindex":0,"stadiumid":265},{"stadiumpalanguageindex":-1,"stadiumid":316},{"stadiumpalanguageindex":0,"stadiumid":326},{"stadiumpalanguageindex":0,"stadiumid":327},{"stadiumpalanguageindex":0,"stadiumid":329},{"stadiumpalanguageindex":0,"stadiumid":330},{"stadiumpalanguageindex":0,"stadiumid":331},{"stadiumpalanguageindex":0,"stadiumid":332},{"stadiumpalanguageindex":0,"stadiumid":333},{"stadiumpalanguageindex":0,"stadiumid":335},{"stadiumpalanguageindex":0,"stadiumid":336},{"stadiumpalanguageindex":0,"stadiumid":337},{"stadiumpalanguageindex":0,"stadiumid":341},{"stadiumpalanguageindex":2,"stadiumid":343},{"stadiumpalanguageindex":14,"stadiumid":344},{"stadiumpalanguageindex":-1,"stadiumid":345},{"stadiumpalanguageindex":0,"stadiumid":347},{"stadiumpalanguageindex":0,"stadiumid":348},{"stadiumpalanguageindex":0,"stadiumid":349},{"stadiumpalanguageindex":-1,"stadiumid":352},{"stadiumpalanguageindex":-1,"stadiumid":353},{"stadiumpalanguageindex":17,"stadiumid":354},{"stadiumpalanguageindex":0,"stadiumid":355},{"stadiumpalanguageindex":-1,"stadiumid":357},{"stadiumpalanguageindex":-1,"stadiumid":358},{"stadiumpalanguageindex":-1,"stadiumid":359},{"stadiumpalanguageindex":-1,"stadiumid":360}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_calendar","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":1,"rowsize_bytes":20,"rows_emitted":1,"rowblock":"0x7c20708","rowblock_bytes":0,"descriptor":"0x4254fc28","schema":[{"name":"transferwindowend1","bit":0,"width":11,"kind":"int","min":101,"max":1231,"storage":"int"},{"name":"transferwindowstart1","bit":11,"width":11,"kind":"int","min":101,"max":1231,"storage":"int"},{"name":"transferwindowend2","bit":22,"width":11,"kind":"int","min":101,"max":1231,"storage":"int"},{"name":"setupdate","bit":33,"width":19,"kind":"int","min":20080101,"max":20601231,"storage":"int"},{"name":"dateid","bit":52,"width":1,"kind":"int","min":0,"max":1,"storage":"int"},{"name":"enddate","bit":53,"width":19,"kind":"int","min":20080101,"max":20601231,"storage":"int"},{"name":"currdate","bit":72,"width":19,"kind":"int","min":20080101,"max":20601231,"storage":"int"},{"name":"startdate","bit":91,"width":19,"kind":"int","min":20080101,"max":20601231,"storage":"int"},{"name":"transferwindowstart2","bit":110,"width":11,"kind":"int","min":101,"max":1231,"storage":"int"},{"name":"objectivecheckdate","bit":121,"width":19,"kind":"int","min":20080101,"max":20601231,"storage":"int"}],"rows":[{"transferwindowend1":831,"transferwindowstart1":701,"transferwindowend2":131,"setupdate":20080101,"dateid":0,"enddate":20080101,"currdate":20080101,"startdate":20080101,"transferwindowstart2":101,"objectivecheckdate":20080101}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_clinchedobjectives","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":20,"rows_emitted":0,"rowblock":"0x42e982a8","rowblock_bytes":513,"descriptor":"0x7b102a8","schema":[{"name":"predictedclinchfordrawflags","bit":0,"width":31,"kind":"int","min":0,"max":2147483520,"storage":"int"},{"name":"clinchedobjectivesflags","bit":31,"width":31,"kind":"int","min":0,"max":2147483520,"storage":"int"},{"name":"predictedclinchforwinflags","bit":62,"width":31,"kind":"int","min":0,"max":2147483520,"storage":"int"},{"name":"teamid","bit":93,"width":18,"kind":"int","min":-1,"max":200000,"storage":"int"},{"name":"predictedclinchforlossflags","bit":111,"width":31,"kind":"int","min":0,"max":2147483520,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_commonnames","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":8,"rows_emitted":0,"rowblock":"0x0","rowblock_bytes":null,"descriptor":"0x7b408f8","schema":[{"name":"firstname","bit":0,"width":15,"kind":"int","min":0,"max":29999,"storage":"int"},{"name":"lastname","bit":15,"width":15,"kind":"int","min":0,"max":29999,"storage":"int"},{"name":"groupid","bit":30,"width":7,"kind":"int","min":0,"max":127,"storage":"int"},{"name":"commonnameid","bit":37,"width":10,"kind":"int","min":1,"max":1024,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_firstnames","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":8,"rows_emitted":0,"rowblock":"0x0","rowblock_bytes":null,"descriptor":"0x7bf0c08","schema":[{"name":"firstnameid","bit":0,"width":12,"kind":"int","min":1,"max":4096,"storage":"int"},{"name":"firstname","bit":12,"width":15,"kind":"int","min":0,"max":29999,"storage":"int"},{"name":"groupid","bit":27,"width":7,"kind":"int","min":0,"max":127,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_lastnames","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":8,"rows_emitted":0,"rowblock":"0x0","rowblock_bytes":null,"descriptor":"0x7bf12c8","schema":[{"name":"lastname","bit":0,"width":15,"kind":"int","min":0,"max":29999,"storage":"int"},{"name":"lastnameid","bit":15,"width":12,"kind":"int","min":1,"max":4096,"storage":"int"},{"name":"groupid","bit":27,"width":7,"kind":"int","min":0,"max":127,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_playerlastmatchhistory","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":12,"rows_emitted":0,"rowblock":"0x42521898","rowblock_bytes":120033,"descriptor":"0x7ba3408","schema":[{"name":"minsplayed","bit":0,"width":8,"kind":"int","min":0,"max":150,"storage":"int"},{"name":"position","bit":8,"width":6,"kind":"int","min":-1,"max":50,"storage":"int"},{"name":"artificialkey","bit":14,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"},{"name":"playerfact","bit":33,"width":5,"kind":"int","min":-1,"max":15,"storage":"int"},{"name":"teamid","bit":38,"width":18,"kind":"int","min":-1,"max":200000,"storage":"int"},{"name":"playeroverall","bit":56,"width":7,"kind":"int","min":-1,"max":100,"storage":"int"},{"name":"playerid","bit":63,"width":19,"kind":"int","min":-1,"max":300000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_playermatchratinghistory","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":12,"rows_emitted":0,"rowblock":"0x42493f28","rowblock_bytes":7585,"descriptor":"0x7b205a8","schema":[{"name":"minsplayed","bit":0,"width":8,"kind":"int","min":-1,"max":140,"storage":"int"},{"name":"position","bit":8,"width":5,"kind":"int","min":0,"max":31,"storage":"int"},{"name":"date","bit":13,"width":19,"kind":"int","min":20080101,"max":20601231,"storage":"int"},{"name":"artificialkey","bit":32,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"},{"name":"rating","bit":51,"width":7,"kind":"int","min":-1,"max":100,"storage":"int"},{"name":"playerid","bit":58,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"career_squadranking","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":8,"rows_emitted":0,"rowblock":"0x42e97c18","rowblock_bytes":449,"descriptor":"0x7bf0488","schema":[{"name":"curroverall","bit":0,"width":10,"kind":"int","min":0,"max":1000,"storage":"int"},{"name":"playerid","bit":10,"width":19,"kind":"int","min":-1,"max":300000,"storage":"int"},{"name":"lastoverall","bit":29,"width":10,"kind":"int","min":0,"max":1000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"celebrations","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":13,"rowsize_bytes":4,"rows_emitted":13,"rowblock":"0x7bc3088","rowblock_bytes":5,"descriptor":"0x7b701e8","schema":[{"name":"celebrationid","bit":0,"width":4,"kind":"int","min":0,"max":13,"storage":"int"}],"rows":[{"celebrationid":0},{"celebrationid":1},{"celebrationid":2},{"celebrationid":3},{"celebrationid":4},{"celebrationid":5},{"celebrationid":6},{"celebrationid":7},{"celebrationid":8},{"celebrationid":9},{"celebrationid":10},{"celebrationid":11},{"celebrationid":12}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"customteamstyles","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":12,"rows_emitted":0,"rowblock":"0x4254bdf8","rowblock_bytes":369,"descriptor":"0x42816ee8","schema":[{"name":"defmentality","bit":0,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"teamstyleid","bit":7,"width":7,"kind":"int","min":900,"max":1000,"storage":"int"},{"name":"basestyle","bit":14,"width":18,"kind":"int","min":-2,"max":200000,"storage":"int"},{"name":"buspassing","bit":32,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"defteamwidth","bit":39,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"busdribbling","bit":46,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"defaggression","bit":53,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"buspositioning","bit":60,"width":1,"kind":"int","min":1,"max":2,"storage":"int"},{"name":"ccpositioning","bit":61,"width":1,"kind":"int","min":1,"max":2,"storage":"int"},{"name":"busbuildupspeed","bit":62,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"ccshooting","bit":69,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"ccpassing","bit":76,"width":7,"kind":"int","min":1,"max":100,"storage":"int"},{"name":"defdefenderline","bit":83,"width":1,"kind":"int","min":1,"max":2,"storage":"int"},{"name":"cccrossing","bit":84,"width":7,"kind":"int","min":1,"max":100,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"cz_assets","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":248,"rows_emitted":0,"rowblock":"0x427ce458","rowblock_bytes":26817,"descriptor":"0x4254ffa8","schema":[{"name":"crestid","bit":0,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"dbid","bit":32,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"publishdate","bit":64,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"rating","bit":96,"width":32,"kind":"unknown","min":0,"max":0,"storage":"int"},{"name":"kitid","bit":128,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"xms_media_id","bit":160,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"type","bit":192,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"author","bit":224,"width":720,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"assetname","bit":944,"width":1008,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"assetyear","bit":1952,"width":4,"kind":"int","min":0,"max":15,"storage":"int"},{"name":"playerposition","bit":1956,"width":6,"kind":"int","min":0,"max":50,"storage":"int"},{"name":"version","bit":1962,"width":15,"kind":"int","min":0,"max":30000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"cz_leagues","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":164,"rows_emitted":0,"rowblock":"0x424a1908","rowblock_bytes":849,"descriptor":"0x424af898","schema":[{"name":"overlaybgcolour3r","bit":0,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"tournamentballid","bit":8,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour3r","bit":16,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour1g","bit":24,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour3b","bit":32,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour1g","bit":40,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"championcupslotallotment","bit":48,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour3b","bit":56,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"finalstadiumid","bit":64,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour2b","bit":72,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour1r","bit":80,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour2b","bit":88,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour1r","bit":96,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour3g","bit":104,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour1b","bit":112,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour3g","bit":120,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour1b","bit":128,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour2r","bit":136,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour2g","bit":144,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"eurocupslotallotment","bit":152,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaytextcolour2r","bit":160,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"overlaybgcolour2g","bit":168,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"leaguedescription","bit":176,"width":1080,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"leaguetype","bit":1256,"width":2,"kind":"int","min":0,"max":2,"storage":"int"},{"name":"numteams","bit":1258,"width":6,"kind":"int","min":0,"max":32,"storage":"int"},{"name":"trophyid","bit":1264,"width":9,"kind":"int","min":-1,"max":255,"storage":"int"},{"name":"teamadvancingpergroup","bit":1273,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"leagueid","bit":1276,"width":12,"kind":"int","min":1,"max":3000,"storage":"int"},{"name":"fixturevsgroup","bit":1288,"width":2,"kind":"int","min":0,"max":2,"storage":"int"},{"name":"teampergroup","bit":1290,"width":5,"kind":"int","min":0,"max":16,"storage":"int"},{"name":"finalmatchlegs","bit":1295,"width":2,"kind":"int","min":0,"max":2,"storage":"int"},{"name":"fixturevseachteam","bit":1297,"width":2,"kind":"int","min":0,"max":2,"storage":"int"},{"name":"subonbench","bit":1299,"width":3,"kind":"int","min":0,"max":7,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"cz_players","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":8,"rows_emitted":0,"rowblock":"0x42614398","rowblock_bytes":12033,"descriptor":"0x7bf0608","schema":[{"name":"assetid","bit":0,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"},{"name":"commentaryid","bit":19,"width":20,"kind":"int","min":-1,"max":1000000,"storage":"int"},{"name":"playerid","bit":39,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"}],"rows":[]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"cz_teams","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":24,"rows_emitted":0,"rowblock":"0x4254c208","rowblock_bytes":1473,"descriptor":"0x7b110a8","schema":[{"name":"hascrestimage","bit":0,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"hassponsorimage","bit":32,"width":32,"kind":"int","min":-2147483648,"max":2147483647,"storage":"int"},{"name":"teamabbrev3","bit":64,"width":72,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"teamid","bit":136,"width":18,"kind":"int","min":0,"max":200000,"storage":"int"},{"name":"commentaryid","bit":154,"width":20,"kind":"int","min":-1,"max":1000000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"dcplayernames","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":40,"rows_emitted":0,"rowblock":"0x426df378","rowblock_bytes":200032,"descriptor":"0x7b579b8","schema":[{"name":"name","bit":0,"width":304,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"nameid","bit":304,"width":13,"kind":"int","min":30000,"max":35000,"storage":"int"}],"rows":[]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"dlcballs","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":52,"rows_emitted":0,"rowblock":"0x424a0528","rowblock_bytes":2625,"descriptor":"0x7b576f8","schema":[{"name":"name","bit":0,"width":400,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"assetid","bit":400,"width":11,"kind":"int","min":0,"max":2000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"dlcboots","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":52,"rows_emitted":0,"rowblock":"0x424980e8","rowblock_bytes":2624,"descriptor":"0x7b57dd8","schema":[{"name":"name","bit":0,"width":400,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"assetid","bit":400,"width":11,"kind":"int","min":0,"max":2000,"storage":"int"}],"rows":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"dna","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":260,"rows_emitted":0,"rowblock":"0x0","rowblock_bytes":null,"descriptor":"0x7b57b18","schema":[{"name":"dna","bit":0,"width":2040,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"playerid","bit":2040,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"}],"rows":[]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"editedplayernames","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":0,"rowsize_bytes":184,"rows_emitted":0,"rowblock":"0x42a7f108","rowblock_bytes":281553,"descriptor":"0x7b10b68","schema":[{"name":"firstname","bit":0,"width":360,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"commonname","bit":360,"width":360,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"playerjerseyname","bit":720,"width":360,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"surname","bit":1080,"width":360,"kind":"string","min":0,"max":0,"storage":"inline-string"},{"name":"playerid","bit":1440,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"}],"rows":[]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_GrandStandPlayers","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":113,"rowsize_bytes":4,"rows_emitted":113,"rowblock":"0x424b4358","rowblock_bytes":481,"descriptor":"0x7988528","schema":[{"name":"playerid","bit":0,"width":19,"kind":"int","min":0,"max":300000,"storage":"int"}],"rows":[{"playerid":1},{"playerid":41},{"playerid":51},{"playerid":195},{"playerid":240},{"playerid":241},{"playerid":244},{"playerid":246},{"playerid":388},{"playerid":393},{"playerid":570},{"playerid":805},{"playerid":942},{"playerid":1025},{"playerid":1040},{"playerid":1041},{"playerid":1075},{"playerid":1088},{"playerid":1109},{"playerid":1116},{"playerid":1183},{"playerid":1198},{"playerid":1201},{"playerid":1419},{"playerid":1605},{"playerid":1620},{"playerid":1845},{"playerid":4000},{"playerid":4202},{"playerid":4833},{"playerid":5419},{"playerid":5467},{"playerid":5589},{"playerid":5673},{"playerid":5680},{"playerid":5681},{"playerid":6235},{"playerid":6975},{"playerid":7289},{"playerid":7512},{"playerid":7518},{"playerid":7743},{"playerid":9014},{"playerid":10264},{"playerid":13038},{"playerid":13128},{"playerid":20170},{"playerid":20801},{"playerid":41236},{"playerid":46747},{"playerid":51539},{"playerid":52241},{"playerid":53769},{"playerid":117106},{"playerid":121939},{"playerid":146530},{"playerid":153079},{"playerid":155862},{"playerid":156353},{"playerid":156616},{"playerid":158023},{"playerid":161840},{"playerid":162895},{"playerid":164000},{"playerid":164240},{"playerid":166120},{"playerid":166124},{"playerid":166906},{"playerid":167495},{"playerid":168473},{"playerid":168542},{"playerid":173731},{"playerid":176580},{"playerid":176635},{"playerid":176676},{"playerid":177003},{"playerid":177845},{"playerid":181872},{"playerid":182521},{"playerid":183277},{"playerid":183898},{"playerid":183907},{"playerid":184941},{"playerid":188350},{"playerid":188545},{"playerid":189332},{"playerid":189511},{"playerid":190043},{"playerid":190044},{"playerid":190053},{"playerid":190871},{"playerid":191189},{"playerid":191695},{"playerid":192119},{"playerid":192181},{"playerid":192883},{"playerid":192985},{"playerid":193080},{"playerid":195864},{"playerid":197445},{"playerid":198710},{"playerid":214098},{"playerid":214100},{"playerid":214101},{"playerid":214267},{"playerid":214649},{"playerid":215558},{"playerid":215732},{"playerid":222000},{"playerid":222257},{"playerid":222481},{"playerid":222680},{"playerid":226764}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_bonusvalues","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":12,"rowsize_bytes":8,"rows_emitted":12,"rowblock":"0x77e13b8","rowblock_bytes":1919903238,"descriptor":"0x7b401a8","schema":[{"name":"bonusvalue","bit":0,"width":32,"kind":"unknown","min":0,"max":0,"storage":"int"},{"name":"bonuslevel","bit":32,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"bonustype","bit":40,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"bonusid","bit":48,"width":8,"kind":"int","min":0,"max":255,"storage":"int"}],"rows":[{"bonusvalue":0,"bonuslevel":0,"bonustype":0,"bonusid":0},{"bonusvalue":1065353216,"bonuslevel":1,"bonustype":0,"bonusid":1},{"bonusvalue":1073741824,"bonuslevel":2,"bonustype":0,"bonusid":2},{"bonusvalue":1077936128,"bonuslevel":3,"bonustype":0,"bonusid":3},{"bonusvalue":1065353216,"bonuslevel":0,"bonustype":1,"bonusid":4},{"bonusvalue":1067450368,"bonuslevel":1,"bonustype":1,"bonusid":5},{"bonusvalue":1069547520,"bonuslevel":2,"bonustype":1,"bonusid":6},{"bonusvalue":1073741824,"bonuslevel":3,"bonustype":1,"bonusid":7},{"bonusvalue":0,"bonuslevel":0,"bonustype":3,"bonusid":8},{"bonusvalue":1065353216,"bonuslevel":1,"bonustype":3,"bonusid":9},{"bonusvalue":1077936128,"bonuslevel":2,"bonustype":3,"bonusid":10},{"bonusvalue":1084227584,"bonuslevel":3,"bonustype":3,"bonusid":11}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_coinrewards","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":17,"rowsize_bytes":8,"rows_emitted":17,"rowblock":"0x79883e8","rowblock_bytes":0,"descriptor":"0x7b40758","schema":[{"name":"leaderboard","bit":0,"width":12,"kind":"int","min":-1024,"max":1024,"storage":"int"},{"name":"coin","bit":12,"width":12,"kind":"int","min":-1024,"max":1024,"storage":"int"},{"name":"cap","bit":24,"width":7,"kind":"int","min":0,"max":100,"storage":"int"},{"name":"objectiveid","bit":31,"width":7,"kind":"int","min":1,"max":100,"storage":"int"}],"rows":[{"leaderboard":500,"coin":40,"cap":5,"objectiveid":1},{"leaderboard":500,"coin":5,"cap":10,"objectiveid":2},{"leaderboard":500,"coin":1,"cap":25,"objectiveid":3},{"leaderboard":500,"coin":5,"cap":10,"objectiveid":4},{"leaderboard":500,"coin":75,"cap":1,"objectiveid":5},{"leaderboard":500,"coin":1,"cap":80,"objectiveid":6},{"leaderboard":500,"coin":1,"cap":60,"objectiveid":7},{"leaderboard":500,"coin":15,"cap":1,"objectiveid":8},{"leaderboard":-500,"coin":-20,"cap":3,"objectiveid":9},{"leaderboard":-500,"coin":-5,"cap":5,"objectiveid":10},{"leaderboard":-500,"coin":-10,"cap":5,"objectiveid":11},{"leaderboard":-500,"coin":-40,"cap":5,"objectiveid":12},{"leaderboard":-500,"coin":-1,"cap":10,"objectiveid":13},{"leaderboard":500,"coin":0,"cap":0,"objectiveid":15},{"leaderboard":500,"coin":325,"cap":1,"objectiveid":16},{"leaderboard":-500,"coin":0,"cap":0,"objectiveid":17},{"leaderboard":500,"coin":0,"cap":0,"objectiveid":18}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"fcc_contractcards","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":13,"rowsize_bytes":16,"rows_emitted":13,"rowblock":"0x7b10ee8","rowblock_bytes":13,"descriptor":"0x42814a68","schema":[{"name":"carddbid","bit":0,"width":31,"kind":"int","min":0,"max":2001001001,"storage":"int"},{"name":"cardsubtype","bit":31,"width":14,"kind":"int","min":0,"max":10000,"storage":"int"},{"name":"weightrare","bit":45,"width":14,"kind":"int","min":0,"max":10000,"storage":"int"},{"name":"cardassetid","bit":59,"width":14,"kind":"int","min":0,"max":10000,"storage":"int"},{"name":"gold","bit":73,"width":7,"kind":"int","min":0,"max":100,"storage":"int"},{"name":"rating","bit":80,"width":7,"kind":"int","min":0,"max":100,"storage":"int"},{"name":"bronze","bit":87,"width":7,"kind":"int","min":0,"max":100,"storage":"int"},{"name":"silver","bit":94,"width":7,"kind":"int","min":0,"max":100,"storage":"int"}],"rows":[{"carddbid":5001001,"cardsubtype":201,"weightrare":0,"cardassetid":7,"gold":1,"rating":50,"bronze":8,"silver":2},{"carddbid":5001002,"cardsubtype":201,"weightrare":0,"cardassetid":7,"gold":8,"rating":65,"bronze":10,"silver":10},{"carddbid":5001003,"cardsubtype":201,"weightrare":0,"cardassetid":7,"gold":13,"rating":80,"bronze":15,"silver":11},{"carddbid":5001004,"cardsubtype":201,"weightrare":100,"cardassetid":7,"gold":3,"rating":60,"bronze":15,"silver":6},{"carddbid":5001005,"cardsubtype":201,"weightrare":100,"cardassetid":7,"gold":18,"rating":70,"bronze":20,"silver":24},{"carddbid":5001006,"cardsubtype":201,"weightrare":100,"cardassetid":7,"gold":28,"rating":90,"bronze":28,"silver":24},{"carddbid":5001007,"cardsubtype":202,"weightrare":0,"cardassetid":8,"gold":1,"rating":50,"bronze":8,"silver":2},{"carddbid":5001008,"cardsubtype":202,"weightrare":0,"cardassetid":8,"gold":8,"rating":65,"bronze":8,"silver":10},{"carddbid":5001009,"cardsubtype":202,"weightrare":0,"cardassetid":8,"gold":13,"rating":80,"bronze":11,"silver":11},{"carddbid":5001010,"cardsubtype":202,"weightrare":10,"cardassetid":8,"gold":3,"rating":60,"bronze":15,"silver":6},{"carddbid":5001011,"cardsubtype":202,"weightrare":10,"cardassetid":8,"gold":18,"rating":70,"bronze":18,"silver":24},{"carddbid":5001012,"cardsubtype":202,"weightrare":10,"cardassetid":8,"gold":28,"rating":90,"bronze":24,"silver":24},{"carddbid":5001013,"cardsubtype":201,"weightrare":0,"cardassetid":7,"gold":99,"rating":90,"bronze":99,"silver":99}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_healingcards","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":27,"rowsize_bytes":12,"rows_emitted":27,"rowblock":"0x42814488","rowblock_bytes":353,"descriptor":"0x7b200f8","schema":[{"name":"carddbid","bit":0,"width":31,"kind":"int","min":0,"max":2001001001,"storage":"int"},{"name":"cardsubtype","bit":31,"width":14,"kind":"int","min":0,"max":10000,"storage":"int"},{"name":"weightrare","bit":45,"width":14,"kind":"int","min":0,"max":10000,"storage":"int"},{"name":"cardassetid","bit":59,"width":14,"kind":"int","min":0,"max":10000,"storage":"int"},{"name":"amount","bit":73,"width":7,"kind":"int","min":0,"max":100,"storage":"int"},{"name":"rating","bit":80,"width":7,"kind":"int","min":0,"max":100,"storage":"int"}],"rows":[{"carddbid":5002001,"cardsubtype":219,"weightrare":0,"cardassetid":10,"amount":20,"rating":55},{"carddbid":5002002,"cardsubtype":219,"weightrare":0,"cardassetid":10,"amount":40,"rating":70},{"carddbid":5002003,"cardsubtype":219,"weightrare":0,"cardassetid":10,"amount":60,"rating":80},{"carddbid":5002004,"cardsubtype":220,"weightrare":70,"cardassetid":10,"amount":10,"rating":55},{"carddbid":5002005,"cardsubtype":220,"weightrare":70,"cardassetid":10,"amount":20,"rating":70},{"carddbid":5002006,"cardsubtype":220,"weightrare":70,"cardassetid":10,"amount":30,"rating":80},{"carddbid":5002007,"cardsubtype":211,"weightrare":0,"cardassetid":9,"amount":1,"rating":55},{"carddbid":5002008,"cardsubtype":211,"weightrare":0,"cardassetid":9,"amount":2,"rating":70},{"carddbid":5002009,"cardsubtype":211,"weightrare":0,"cardassetid":9,"amount":5,"rating":80},{"carddbid":5002010,"cardsubtype":212,"weightrare":0,"cardassetid":9,"amount":1,"rating":55},{"carddbid":5002011,"cardsubtype":212,"weightrare":0,"cardassetid":9,"amount":2,"rating":70},{"carddbid":5002012,"cardsubtype":212,"weightrare":0,"cardassetid":9,"amount":5,"rating":80},{"carddbid":5002013,"cardsubtype":213,"weightrare":0,"cardassetid":9,"amount":1,"rating":55},{"carddbid":5002014,"cardsubtype":213,"weightrare":0,"cardassetid":9,"amount":2,"rating":70},{"carddbid":5002015,"cardsubtype":213,"weightrare":0,"cardassetid":9,"amount":5,"rating":80},{"carddbid":5002019,"cardsubtype":215,"weightrare":0,"cardassetid":9,"amount":1,"rating":55},{"carddbid":5002020,"cardsubtype":215,"weightrare":0,"cardassetid":9,"amount":2,"rating":70},{"carddbid":5002021,"cardsubtype":215,"weightrare":0,"cardassetid":9,"amount":5,"rating":80},{"carddbid":5002022,"cardsubtype":216,"weightrare":0,"cardassetid":9,"amount":1,"rating":55},{"carddbid":5002023,"cardsubtype":216,"weightrare":0,"cardassetid":9,"amount":2,"rating":70},{"carddbid":5002024,"cardsubtype":216,"weightrare":0,"cardassetid":9,"amount":5,"rating":80},{"carddbid":5002025,"cardsubtype":217,"weightrare":0,"cardassetid":9,"amount":1,"rating":55},{"carddbid":5002026,"cardsubtype":217,"weightrare":0,"cardassetid":9,"amount":2,"rating":70},{"carddbid":5002027,"cardsubtype":217,"weightrare":0,"cardassetid":9,"amount":5,"rating":80},{"carddbid":5002028,"cardsubtype":218,"weightrare":20,"cardassetid":9,"amount":1,"rating":60},{"carddbid":5002029,"cardsubtype":218,"weightrare":20,"cardassetid":9,"amount":2,"rating":74},{"carddbid":5002030,"cardsubtype":218,"weightrare":20,"cardassetid":9,"amount":4,"rating":85}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_leagues","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":43,"rowsize_bytes":12,"rows_emitted":43,"rowblock":"0x428136d8","rowblock_bytes":545,"descriptor":"0x7b405b8","schema":[{"name":"leaguename","bit":0,"width":32,"kind":"string","min":0,"max":0,"storage":"offset-unresolved"},{"name":"leagueid","bit":32,"width":13,"kind":"int","min":0,"max":5000,"storage":"int"},{"name":"fifacountryid","bit":45,"width":13,"kind":"int","min":0,"max":5000,"storage":"int"},{"name":"futcountryid","bit":58,"width":13,"kind":"int","min":0,"max":5000,"storage":"int"}],"rows":[{"leaguename":236,"leagueid":1,"fifacountryid":13,"futcountryid":212},{"leaguename":250,"leagueid":4,"fifacountryid":7,"futcountryid":212},{"leaguename":269,"leagueid":7,"fifacountryid":54,"futcountryid":211},{"leaguename":290,"leagueid":10,"fifacountryid":34,"futcountryid":212},{"leaguename":305,"leagueid":13,"fifacountryid":14,"futcountryid":14},{"leaguename":321,"leagueid":14,"fifacountryid":14,"futcountryid":14},{"leaguename":342,"leagueid":16,"fifacountryid":18,"futcountryid":18},{"leaguename":354,"leagueid":17,"fifacountryid":18,"futcountryid":18},{"leaguename":366,"leagueid":19,"fifacountryid":21,"futcountryid":21},{"leaguename":383,"leagueid":20,"fifacountryid":21,"futcountryid":21},{"leaguename":401,"leagueid":31,"fifacountryid":27,"futcountryid":27},{"leaguename":413,"leagueid":32,"fifacountryid":27,"futcountryid":27},{"leaguename":425,"leagueid":39,"fifacountryid":95,"futcountryid":211},{"leaguename":443,"leagueid":41,"fifacountryid":36,"futcountryid":212},{"leaguename":458,"leagueid":50,"fifacountryid":42,"futcountryid":212},{"leaguename":474,"leagueid":53,"fifacountryid":45,"futcountryid":45},{"leaguename":491,"leagueid":54,"fifacountryid":45,"futcountryid":45},{"leaguename":504,"leagueid":56,"fifacountryid":46,"futcountryid":212},{"leaguename":519,"leagueid":60,"fifacountryid":14,"futcountryid":14},{"leaguename":534,"leagueid":61,"fifacountryid":14,"futcountryid":14},{"leaguename":549,"leagueid":63,"fifacountryid":22,"futcountryid":212},{"leaguename":564,"leagueid":65,"fifacountryid":25,"futcountryid":212},{"leaguename":586,"leagueid":66,"fifacountryid":37,"futcountryid":212},{"leaguename":608,"leagueid":67,"fifacountryid":40,"futcountryid":211},{"leaguename":624,"leagueid":68,"fifacountryid":48,"futcountryid":212},{"leaguename":639,"leagueid":78,"fifacountryid":75,"futcountryid":211},{"leaguename":648,"leagueid":80,"fifacountryid":4,"futcountryid":212},{"leaguename":663,"leagueid":83,"fifacountryid":167,"futcountryid":211},{"leaguename":681,"leagueid":189,"fifacountryid":47,"futcountryid":212},{"leaguename":699,"leagueid":308,"fifacountryid":38,"futcountryid":212},{"leaguename":714,"leagueid":322,"fifacountryid":17,"futcountryid":212},{"leaguename":731,"leagueid":332,"fifacountryid":49,"futcountryid":212},{"leaguename":746,"leagueid":335,"fifacountryid":55,"futcountryid":211},{"leaguename":772,"leagueid":336,"fifacountryid":56,"futcountryid":211},{"leaguename":788,"leagueid":341,"fifacountryid":83,"futcountryid":211},{"leaguename":802,"leagueid":347,"fifacountryid":140,"futcountryid":211},{"leaguename":820,"leagueid":349,"fifacountryid":163,"futcountryid":211},{"leaguename":833,"leagueid":350,"fifacountryid":183,"futcountryid":211},{"leaguename":856,"leagueid":351,"fifacountryid":195,"futcountryid":211},{"leaguename":876,"leagueid":353,"fifacountryid":52,"futcountryid":211},{"leaguename":897,"leagueid":2025,"fifacountryid":54,"futcountryid":211},{"leaguename":914,"leagueid":2134,"fifacountryid":221,"futcountryid":211},{"leaguename":925,"leagueid":2150,"fifacountryid":221,"futcountryid":211}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"fcc_managerbonusvalues","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":9,"rowsize_bytes":4,"rows_emitted":9,"rowblock":"0x7c31088","rowblock_bytes":16472,"descriptor":"0x7b10388","schema":[{"name":"bonuslevel","bit":0,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"bonustype","bit":8,"width":8,"kind":"int","min":0,"max":255,"storage":"int"},{"name":"cardlevel","bit":16,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"bonusvalue","bit":19,"width":5,"kind":"int","min":0,"max":16,"storage":"int"},{"name":"bonusid","bit":24,"width":5,"kind":"int","min":0,"max":16,"storage":"int"}],"rows":[{"bonuslevel":3,"bonustype":1,"cardlevel":1,"bonusvalue":2,"bonusid":0},{"bonuslevel":3,"bonustype":1,"cardlevel":2,"bonusvalue":5,"bonusid":1},{"bonuslevel":3,"bonustype":1,"cardlevel":3,"bonusvalue":10,"bonusid":2},{"bonuslevel":2,"bonustype":2,"cardlevel":1,"bonusvalue":1,"bonusid":3},{"bonuslevel":2,"bonustype":2,"cardlevel":2,"bonusvalue":2,"bonusid":4},{"bonuslevel":2,"bonustype":2,"cardlevel":3,"bonusvalue":3,"bonusid":5},{"bonuslevel":3,"bonustype":2,"cardlevel":1,"bonusvalue":1,"bonusid":6},{"bonuslevel":3,"bonustype":2,"cardlevel":2,"bonusvalue":1,"bonusid":7},{"bonuslevel":3,"bonustype":2,"cardlevel":3,"bonusvalue":1,"bonusid":8}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_myclubs","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":2,"rowsize_bytes":8,"rows_emitted":2,"rowblock":"0x7c20748","rowblock_bytes":0,"descriptor":"0x7b57a68","schema":[{"name":"myclubname","bit":0,"width":32,"kind":"string","min":0,"max":0,"storage":"offset-unresolved"},{"name":"myclubid","bit":32,"width":8,"kind":"int","min":0,"max":128,"storage":"int"}],"rows":[{"myclubname":0,"myclubid":1},{"myclubname":16,"myclubid":2}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"fcc_myclubscategories","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":12,"rowsize_bytes":12,"rows_emitted":12,"rowblock":"0x7988488","rowblock_bytes":0,"descriptor":"0x7b20878","schema":[{"name":"categoryname","bit":0,"width":32,"kind":"string","min":0,"max":0,"storage":"offset-unresolved"},{"name":"futcountryid","bit":32,"width":16,"kind":"int","min":0,"max":50000,"storage":"int"},{"name":"myclubid","bit":48,"width":8,"kind":"int","min":0,"max":128,"storage":"int"},{"name":"id","bit":56,"width":8,"kind":"int","min":0,"max":128,"storage":"int"},{"name":"isteamcategory","bit":64,"width":8,"kind":"int","min":0,"max":128,"storage":"int"},{"name":"categoryid","bit":72,"width":8,"kind":"int","min":0,"max":128,"storage":"int"}],"rows":[{"categoryname":0,"futcountryid":0,"myclubid":0,"id":1,"isteamcategory":0,"categoryid":16},{"categoryname":13,"futcountryid":14,"myclubid":0,"id":2,"isteamcategory":1,"categoryid":1},{"categoryname":32,"futcountryid":18,"myclubid":0,"id":3,"isteamcategory":1,"categoryid":2},{"categoryname":50,"futcountryid":21,"myclubid":0,"id":4,"isteamcategory":1,"categoryid":3},{"categoryname":69,"futcountryid":27,"myclubid":0,"id":5,"isteamcategory":1,"categoryid":4},{"categoryname":86,"futcountryid":45,"myclubid":0,"id":6,"isteamcategory":1,"categoryid":5},{"categoryname":103,"futcountryid":212,"myclubid":0,"id":7,"isteamcategory":1,"categoryid":6},{"categoryname":121,"futcountryid":211,"myclubid":0,"id":8,"isteamcategory":1,"categoryid":7},{"categoryname":138,"futcountryid":0,"myclubid":2,"id":10,"isteamcategory":0,"categoryid":18},{"categoryname":155,"futcountryid":0,"myclubid":2,"id":11,"isteamcategory":0,"categoryid":8},{"categoryname":173,"futcountryid":0,"myclubid":2,"id":12,"isteamcategory":0,"categoryid":10},{"categoryname":190,"futcountryid":0,"myclubid":2,"id":13,"isteamcategory":0,"categoryid":11}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"table":"fcc_preferredformationcalcback","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":16,"rowsize_bytes":12,"rows_emitted":16,"rowblock":"0x7b40a98","rowblock_bytes":0,"descriptor":"0x42811bb8","schema":[{"name":"formations","bit":0,"width":32,"kind":"string","min":0,"max":0,"storage":"offset-unresolved"},{"name":"form10","bit":32,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form2","bit":35,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"id","bit":38,"width":6,"kind":"int","min":0,"max":34,"storage":"int"},{"name":"form3","bit":44,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form5","bit":47,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form4","bit":50,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form13","bit":53,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form7","bit":56,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form9","bit":59,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form14","bit":62,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form12","bit":65,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form15","bit":68,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form8","bit":71,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form6","bit":74,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form16","bit":77,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form11","bit":80,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form1","bit":83,"width":3,"kind":"int","min":0,"max":4,"storage":"int"}],"rows":[{"formations":0,"form10":1,"form2":0,"id":3,"form3":0,"form5":1,"form4":0,"form13":2,"form7":1,"form9":2,"form14":0,"form12":1,"form15":0,"form8":1,"form6":3,"form16":0,"form11":1,"form1":0},{"formations":6,"form10":2,"form2":0,"id":6,"form3":0,"form5":1,"form4":0,"form13":1,"form7":1,"form9":2,"form14":0,"form12":1,"form15":0,"form8":3,"form6":1,"form16":0,"form11":1,"form1":0},{"formations":12,"form10":2,"form2":0,"id":7,"form3":0,"form5":1,"form4":0,"form13":1,"form7":1,"form9":3,"form14":0,"form12":1,"form15":0,"form8":2,"form6":2,"form16":0,"form11":1,"form1":0},{"formations":18,"form10":3,"form2":0,"id":8,"form3":0,"form5":1,"form4":0,"form13":1,"form7":1,"form9":2,"form14":0,"form12":1,"form15":0,"form8":2,"form6":1,"form16":0,"form11":1,"form1":0},{"formations":25,"form10":1,"form2":0,"id":13,"form3":0,"form5":2,"form4":0,"form13":1,"form7":3,"form9":1,"form14":0,"form12":2,"form15":0,"form8":1,"form6":1,"form16":0,"form11":2,"form1":0},{"formations":31,"form10":1,"form2":0,"id":14,"form3":0,"form5":3,"form4":0,"form13":1,"form7":2,"form9":1,"form14":0,"form12":2,"form15":0,"form8":1,"form6":1,"form16":0,"form11":2,"form1":0},{"formations":37,"form10":1,"form2":0,"id":16,"form3":0,"form5":2,"form4":0,"form13":1,"form7":2,"form9":1,"form14":0,"form12":3,"form15":0,"form8":1,"form6":1,"form16":0,"form11":2,"form1":0},{"formations":44,"form10":1,"form2":0,"id":19,"form3":0,"form5":2,"form4":0,"form13":1,"form7":2,"form9":1,"form14":0,"form12":2,"form15":0,"form8":1,"form6":1,"form16":0,"form11":3,"form1":0},{"formations":51,"form10":1,"form2":0,"id":21,"form3":0,"form5":1,"form4":0,"form13":3,"form7":1,"form9":1,"form14":0,"form12":1,"form15":0,"form8":1,"form6":2,"form16":0,"form11":1,"form1":0},{"formations":58,"form10":0,"form2":2,"id":23,"form3":2,"form5":0,"form4":1,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":3},{"formations":64,"form10":0,"form2":3,"id":24,"form3":2,"form5":0,"form4":1,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":2},{"formations":70,"form10":0,"form2":2,"id":25,"form3":3,"form5":0,"form4":1,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":2},{"formations":76,"form10":0,"form2":1,"id":27,"form3":1,"form5":0,"form4":3,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":1},{"formations":82,"form10":0,"form2":0,"id":29,"form3":0,"form5":0,"form4":0,"form13":0,"form7":0,"form9":0,"form14":3,"form12":0,"form15":2,"form8":0,"form6":0,"form16":1,"form11":0,"form1":0},{"formations":89,"form10":0,"form2":0,"id":30,"form3":0,"form5":0,"form4":0,"form13":0,"form7":0,"form9":0,"form14":2,"form12":0,"form15":3,"form8":0,"form6":0,"form16":1,"form11":0,"form1":0},{"formations":96,"form10":0,"form2":0,"id":31,"form3":0,"form5":0,"form4":0,"form13":0,"form7":0,"form9":0,"form14":1,"form12":0,"form15":1,"form8":0,"form6":0,"form16":3,"form11":0,"form1":0}]}
|
||||
@@ -1 +0,0 @@
|
||||
{"table":"fcc_preferredformationcalcgk","source":"FIFA17.exe resident database (tools/db_dump.py)","rowcount":16,"rowsize_bytes":12,"rows_emitted":16,"rowblock":"0x7b40828","rowblock_bytes":7,"descriptor":"0x428119f8","schema":[{"name":"formations","bit":0,"width":32,"kind":"string","min":0,"max":0,"storage":"offset-unresolved"},{"name":"form10","bit":32,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form2","bit":35,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"id","bit":38,"width":6,"kind":"int","min":0,"max":34,"storage":"int"},{"name":"form3","bit":44,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form5","bit":47,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form4","bit":50,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form13","bit":53,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form7","bit":56,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form9","bit":59,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form14","bit":62,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form12","bit":65,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form15","bit":68,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form8","bit":71,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form6","bit":74,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form16","bit":77,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form11","bit":80,"width":3,"kind":"int","min":0,"max":4,"storage":"int"},{"name":"form1","bit":83,"width":3,"kind":"int","min":0,"max":4,"storage":"int"}],"rows":[{"formations":0,"form10":2,"form2":0,"id":3,"form3":0,"form5":2,"form4":0,"form13":2,"form7":2,"form9":2,"form14":0,"form12":2,"form15":0,"form8":2,"form6":3,"form16":0,"form11":2,"form1":0},{"formations":6,"form10":2,"form2":0,"id":6,"form3":0,"form5":2,"form4":0,"form13":2,"form7":2,"form9":2,"form14":0,"form12":2,"form15":0,"form8":3,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":12,"form10":2,"form2":0,"id":7,"form3":0,"form5":2,"form4":0,"form13":2,"form7":2,"form9":3,"form14":0,"form12":2,"form15":0,"form8":2,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":18,"form10":3,"form2":0,"id":8,"form3":0,"form5":2,"form4":0,"form13":2,"form7":2,"form9":2,"form14":0,"form12":2,"form15":0,"form8":2,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":25,"form10":2,"form2":0,"id":13,"form3":0,"form5":2,"form4":0,"form13":2,"form7":3,"form9":2,"form14":0,"form12":2,"form15":0,"form8":2,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":31,"form10":2,"form2":0,"id":14,"form3":0,"form5":3,"form4":0,"form13":2,"form7":2,"form9":2,"form14":0,"form12":2,"form15":0,"form8":2,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":37,"form10":2,"form2":0,"id":16,"form3":0,"form5":2,"form4":0,"form13":2,"form7":2,"form9":2,"form14":0,"form12":3,"form15":0,"form8":2,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":44,"form10":2,"form2":0,"id":19,"form3":0,"form5":2,"form4":0,"form13":2,"form7":2,"form9":2,"form14":0,"form12":2,"form15":0,"form8":2,"form6":2,"form16":0,"form11":3,"form1":0},{"formations":51,"form10":2,"form2":0,"id":21,"form3":0,"form5":2,"form4":0,"form13":3,"form7":2,"form9":2,"form14":0,"form12":2,"form15":0,"form8":2,"form6":2,"form16":0,"form11":2,"form1":0},{"formations":58,"form10":0,"form2":2,"id":23,"form3":2,"form5":0,"form4":2,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":3},{"formations":64,"form10":0,"form2":3,"id":24,"form3":2,"form5":0,"form4":2,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":2},{"formations":70,"form10":0,"form2":2,"id":25,"form3":3,"form5":0,"form4":2,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":2},{"formations":76,"form10":0,"form2":2,"id":27,"form3":2,"form5":0,"form4":3,"form13":0,"form7":0,"form9":0,"form14":0,"form12":0,"form15":0,"form8":0,"form6":0,"form16":0,"form11":0,"form1":2},{"formations":82,"form10":0,"form2":0,"id":29,"form3":0,"form5":0,"form4":0,"form13":0,"form7":0,"form9":0,"form14":3,"form12":0,"form15":2,"form8":0,"form6":0,"form16":2,"form11":0,"form1":0},{"formations":89,"form10":0,"form2":0,"id":30,"form3":0,"form5":0,"form4":0,"form13":0,"form7":0,"form9":0,"form14":2,"form12":0,"form15":3,"form8":0,"form6":0,"form16":2,"form11":0,"form1":0},{"formations":96,"form10":0,"form2":0,"id":31,"form3":0,"form5":0,"form4":0,"form13":0,"form7":0,"form9":0,"form14":2,"form12":0,"form15":2,"form8":0,"form6":0,"form16":3,"form11":0,"form1":0}]}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user