fifa17-recon: CARD_SYSTEM -- record the solved identity mechanism and the oracle
Supersedes the parts of this document that were wrong: the CardsDb map is not empty offline, and dbdata.dll is not the player database. Records the merge dispatch table, the field-fill asymmetry that the pool design depends on (send zero for what the client knows, send our own only where it knows nothing), the three-state oracle with all three fingerprints, the 5000-item ingest ceiling, the fact that the map is WIPED on every club fetch, and where the 17,547 player roster came from plus its independent cross-validation. Also records the state of the other card families so the next session starts from the manager branch writing no miss-fill, rather than rediscovering it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
@@ -194,3 +194,97 @@ getting the real id list out of `dbdata.dll`, and not a code-injection problem.
|
||||
the client's record happens to agree. The attributes settle it (they were invented) but
|
||||
rating specifically has not been isolated. Send a deliberately wrong rating for a known
|
||||
good id and look.
|
||||
|
||||
---
|
||||
|
||||
# SOLVED 2026-08-04 (evening). Card identity, end to end.
|
||||
|
||||
Everything above this line is superseded where it disagrees. Two of its premises were
|
||||
wrong: the CardsDb map is NOT empty offline, and `dbdata.dll` is NOT the player
|
||||
database.
|
||||
|
||||
## The mechanism
|
||||
|
||||
Every item object in every response is inserted into the CardsDb map by the item
|
||||
parser tail (`0x18014115b` -> registrar vtable `+0xa08` = `0x18011cca0`, a
|
||||
find-or-insert). There is no fetch to trigger; `item?idList` was a red herring.
|
||||
|
||||
IMMEDIATELY BEFORE registering, `FUN_180141660` merges in the client's OWN local
|
||||
database. It switches on `record+0x4c`, which `FUN_1800d8330` derives from the JSON
|
||||
atom `0x6c cardsubtypeid` alone:
|
||||
|
||||
0..3 -> 1 players 4 -> 2 managercards
|
||||
5 -> 3 headcoachcards 6 -> 10 gkcoachcards
|
||||
7 -> 5 physiocards 8 -> 4 fitnesscoachcards
|
||||
9..b -> 7 UNIDENTIFIED absent -> 0x156 -> 0, NO merge at all
|
||||
|
||||
For players `FUN_180135890` queries `players` by `playerid = record+0x18 & 0xffffff`,
|
||||
where `record+0x18` is atom `0x287 resourceId`. Atom `0x23 assetId` lands at `+0x20`
|
||||
and is NEVER read by the merge: sending assetId alone does nothing.
|
||||
|
||||
On a HIT it fills the name (`+0xb8` first, `+0xc8` last, `+0xdd` knownAs, all inline
|
||||
char arrays), fills `nation +0x148` and `teamid +0x94` ONLY IF THEY ARRIVED AS ZERO,
|
||||
and always recomputes `leagueid +0x154`. It never touches `rating +0xb4`,
|
||||
`position +0x146` or `attributes +0x98..+0xac`.
|
||||
|
||||
So: send zero for everything the client knows better than us, and send our own value
|
||||
only where the client has nothing. That asymmetry is the whole design of `fut_cards`.
|
||||
|
||||
## The three-state oracle (all three confirmed live)
|
||||
|
||||
NAMED sentinel rating survives, real name -> the id is REAL
|
||||
placeholder sentinel survives, name is "Jamal Blackman", team 0 -> the row
|
||||
exists but is an EMPTY SLOT. This is the trap: 169193 does this and
|
||||
it was in VERIFIED_ASSET_IDS.
|
||||
MISS rating 0x32, teamid 0x78d, nation 0xe, position 2, attributes 1,
|
||||
name " " -> the id does NOT exist. This fingerprint matched a
|
||||
user's blank pack cards field for field.
|
||||
|
||||
`FUT_ID_SWEEP` (utas_server) serves a window of candidate ids as a synthetic club;
|
||||
`tools/card_identity_probe.py` reads back what the client resolved. 5000 items per
|
||||
response ingests cleanly; 20000 was served and silently NOT ingested. THE MAP IS
|
||||
WIPED ON EVERY CLUB FETCH, so an auto sweep must be collected continuously
|
||||
(`sweep_collect.py --watch`) and not once at the end.
|
||||
|
||||
## Where the roster came from
|
||||
|
||||
`tools/dbdata_extract.py` reads FIFA's own rating-sorted index out of a running
|
||||
process (0x40 stride, self-validating {begin,end,end+1} name-pointer triple, anchored
|
||||
on 20801 = Ronaldo 94) -> `data/roster.json`, 17,547 players. Cross-validated against
|
||||
the sweep oracle, a completely independent method: 573 of 573 overlapping names
|
||||
agreed. The one id in the sweep and not the index is 26501, the target of the
|
||||
documented 22800..22879 Legends remap, which is also what produced "Alex Hunter x80"
|
||||
in a sweep and had looked like a bug.
|
||||
|
||||
`dbdata.dll` is an anti-tamper decoy. Its single export `getTableData` returns a fixed
|
||||
759-byte self-integrity blob and the file contains no tables. Do not re-attempt it.
|
||||
|
||||
## Still missing for players
|
||||
|
||||
`position`, `nationality`, `teamId` and the six attributes are NOT in the rating
|
||||
index. Two live sources were found and BOTH are per-materialised-card caches, not
|
||||
tables: the 0x180-stride resolved card records, and a 32-byte keyed container
|
||||
(entries `{playerId, position | hash<<32, rating, ?}`). 59 positions came from the
|
||||
second. A full-roster position source has NOT been found.
|
||||
|
||||
## The other card families, as of 2026-08-04
|
||||
|
||||
Mechanism known, id spaces NOT. `FUN_1801356c0` queries `managercards` by column
|
||||
`carddbid` taken RAW from `record+0x18` (no mask), selecting firstname, lastname,
|
||||
assetid, value, talkrating, negotiation, rare; it writes firstname to `+0xb8`,
|
||||
assetid to `+0x20`, a byte to `+0xb4`, bytes to `+0xe2/+0xe3`, and `+0x58 = (rare==1)`.
|
||||
|
||||
Sweeps of carddbid 1..5000 and 6000..8000 both produced 2000+ manager records with
|
||||
cardtype 2 and NOTHING written. THE MANAGER BRANCH WRITES NO MISS-FILL, so a wrong id
|
||||
is SILENT and a negative result does not distinguish a wrong id from a wrong
|
||||
mechanism. Manager name strings are in memory near 0x42800000 but are inline with NO
|
||||
inbound pointers, so the players trick (find the index that points into the name pool)
|
||||
does not transfer.
|
||||
|
||||
Tables named in the DLL and not yet probed: `headcoachcards`, `fitnesscoachcards`,
|
||||
`gkcoachcards`, `physiocards`, `fancards`, `newcards`. Consumables are a separate
|
||||
family (consumablesContract/Fitness/Healing/Position/Training/Formation plus
|
||||
`FUT_CONSUMABLE_NAME_*` loc keys) and may be enum-driven rather than DB-driven.
|
||||
|
||||
`GET /club?type=` has only ever been observed with two values: `player` (paged,
|
||||
start=N&count=11) and `manager` (count=200, from the STAFF tab).
|
||||
|
||||
Reference in New Issue
Block a user