#!/usr/bin/env python3 """Recover the kit caption/localisation vocabulary from the UNPACKED CardsDLL. Why CardsDLL and not FIFA17.exe: CardsDLL is not packed, so a MISS here is meaningful. FIFA17.exe is Denuvo-packed and only partially readable -- a hit there is useful, a miss proves nothing. Every run therefore prints a positive control first; if the control fails, the run is void and no negative may be quoted from it. Usage: python3 cardsdll_kit_strings.py [path-to-CardsDLL] """ from __future__ import annotations import os import re import sys DEFAULT = os.path.expanduser( "~/.cache/openfut-investigation/bin/CardsDLL_Win64_retail.dll" ) # Strings that MUST be present. If any is missing the search is broken. CONTROLS = [b"activeHomeKit", b"cardsubtypeid", b"resourceId", b"activeAwayKit"] # The kit caption vocabulary this project has referred to, plus neighbours worth # knowing about either way. PROBES = [ b"FUT_UC_KITS", b"TeamName_Abbr15_", b"TeamName_Abbr15", b"TeamName_", b"FUT_UC_", b"StadiumName_", b"Badge", b"Stadium", b"activeBadge", b"activeBall", b"activeStadium", b"kit", b"Kit", b"KIT", b"home", b"Home", b"HOME", b"away", b"Away", b"AWAY", b"locked", b"Locked", b"LOCKED", b"unlock", b"category", b"year", b"teamid", b"teamId", b"DataProvider", b"itemData", b"itemType", b"itemState", ] def ascii_strings(data, minlen=4): for m in re.finditer(rb"[ -~]{%d,}" % minlen, data): yield m.start(), m.group() def main(): path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT data = open(path, "rb").read() print(f"{os.path.basename(path)} {len(data)} bytes") print("\n-- positive control (a miss voids every negative below) --") ok = True for c in CONTROLS: n = data.count(c) print(f" {c.decode():16s} {n}") if n == 0: ok = False if not ok: print(" CONTROL FAILED — do not quote negatives from this run.") return 1 print("\n-- probe counts --") for p in PROBES: print(f" {p.decode():18s} {data.count(p)}") # Whole-string table: every standalone string containing kit-ish substrings. print("\n-- standalone strings matching kit/team/caption vocabulary --") pat = re.compile(rb"(?i)(kit|teamname|abbr|stadiumname|fut_uc|locked|unlock)") seen = set() for off, s in ascii_strings(data, 5): if pat.search(s) and s not in seen: seen.add(s) print(f" @{off:#08x} {s.decode('latin1')[:110]}") print(f" ({len(seen)} distinct)") return 0 if __name__ == "__main__": sys.exit(main())