Files
OpenFUT/fifa17-recon/tools/find_apt_in_cas.py
T
funman300 5bf2c7ddc1 kits: the pre-match kit screen is futSelectTeam, found without Frosty
The Frostbite .cas chunks holding APT ActionScript are greppable, so screens can
be identified and their whole symbol table recovered without driving the GUI.
Control: KitAssignmentPopup (a string from an already-exported BIG) hits 43 times
across the 52 cas files, so a miss would have been meaningful.

FUT_GET_MATCH_KITS_DP hits 10 times. The binding screen is
external.ion_fut.screens.futSelectTeam
(fifa_installpackage_01/cas_01.cas @ 0x3707ecd7), which no exported BIG contained
after 33 attempts at guessing names.

It binds FUT_GET_MATCH_KITS_DP, KitSelectDP and TeamSetupDP, and carries exactly
the vocabulary the native side implies:

  panels/locks  mcKitHome mcKitAway mcLockHome mcLockAway m_arrKitPanels
  sides         HOME_SIDE AWAY_SIDE NEUTRAL_SIDE SIDE_HOME SIDE_AWAY
  DP fields     KITS_AVAILABLE  KIT_  HOME_KIT_ID  AWAY_KIT_ID
  flow          InitializeKitConfig GetKitArrayForFUT InitializeKitsFromArray
                EnterKitSelect IsKitSelectCreated ExitKitSelect SaveKitsForMatch
  lock          CheckIsKitLocked  RemoveKitLocks  SetKitReady  SetKitUnReady
  uniform       SetUniform ION_Uniform GetNonConflictingUniformID

CheckIsKitLocked is the lock predicate the native side does not own — recall
sub_180033430 only ever writes LOCKED = 0, so the "kit is currently locked" dialog
is raised here.

Adds find_apt_in_cas.py (control-guarded) and the recovered 934-symbol table.
2026-08-23 21:52:36 +00:00

87 lines
2.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Find an APT/ActionScript symbol inside the FIFA 17 Frostbite .cas archives.
Frosty is a GUI-only tool and its Legacy Explorer is the documented way to reach
these assets, but the chunks holding APT ActionScript are stored plainly enough to
grep — so a screen can be identified, and its whole symbol table recovered,
without driving the GUI at all.
ALWAYS passes a control first: `KitAssignmentPopup` is a string from an
already-exported BIG, so if it misses, the archives are packed differently than
assumed and no negative from this tool may be quoted.
python3 find_apt_in_cas.py FUT_GET_MATCH_KITS_DP
python3 find_apt_in_cas.py --dump 0x3707ecd7 fifa_installpackage_01/cas_01.cas
"""
import argparse
import glob
import os
import re
import sys
ROOT = "/mnt/games/FIFA 17"
CONTROL = b"KitAssignmentPopup"
def cas_files():
return sorted(glob.glob(os.path.join(ROOT, "**", "*.cas"), recursive=True))
def find(needle: bytes):
control_total = 0
hits = []
for p in cas_files():
d = open(p, "rb").read()
control_total += d.count(CONTROL)
start = 0
while True:
i = d.find(needle, start)
if i < 0:
break
hits.append((p, i))
start = i + 1
return control_total, hits
def dump(path, off, span=90000):
with open(path, "rb") as f:
f.seek(max(0, off - span // 2))
d = f.read(span)
seen = []
for m in re.finditer(rb"[ -~]{4,}", d):
t = m.group().decode("latin1")
if t not in seen:
seen.append(t)
return seen
def main():
ap = argparse.ArgumentParser()
ap.add_argument("needle", nargs="?")
ap.add_argument("--dump", metavar="OFFSET")
ap.add_argument("--file")
args = ap.parse_args()
if args.dump:
path = args.file if os.path.isabs(args.file or "") else os.path.join(
ROOT, "Data/Win32/superbundlelayout", args.file or "")
for s in dump(path, int(args.dump, 0)):
print(s)
return 0
if not args.needle:
ap.error("needle required")
ctl, hits = find(args.needle.encode())
print(f"control {CONTROL.decode()}: {ctl} hit(s)")
if ctl == 0:
print("CONTROL FAILED — archives not greppable this way; no negative is valid.")
return 1
print(f"{args.needle}: {len(hits)} hit(s)")
for p, i in hits[:20]:
print(f" {os.path.relpath(p, ROOT)} @ {i:#x}")
return 0
if __name__ == "__main__":
sys.exit(main())