-- squad-injector/snapshot_lineup_tables.lua -- FIFA Live Editor Lua script (v2 API). -- -- Dumps every DB table whose name looks lineup/formation/squad-related, -- full rows + fields, to a timestamped JSON file. Run it once BEFORE using -- FLE's GUI "Freeze Lineup" feature (Formation Editor -> arrange players on -- pitch -> tick "Freeze Lineup" -> Data -> Save) and once AFTER, then diff -- the two snapshots with diff_snapshots.py to find which table/field(s) the -- GUI feature actually writes. -- -- This exists because "Freeze Lineup" is a confirmed, documented mechanism -- for forcing a starting XI (FLE wiki: Formation-Editor.md) but it's GUI-only -- — its underlying DB write is not documented anywhere. Diffing before/after -- snapshots is how we find it, the same way export_squad.lua's exhaustive -- table dump was the oracle for Track C. -- -- Output: C:\FIFA 23 Live Editor\openfut_snapshot_.json local OUT_DIR = "C:\\FIFA 23 Live Editor\\" -- Keyword filter, broader than squad-exporter's FUT-table filter — Freeze -- Lineup is a formation/tactics feature so the write could land in any of -- these families. "players" is included because position fields on the -- player row itself may also change. local KEYWORDS = { "squad", "lineup", "formation", "tactic", "teamsheet", "selection", "players", "teams" } local function json_val(v) local t = type(v) if t == "nil" then return "null" elseif t == "boolean" then return tostring(v) elseif t == "number" then if v ~= v then return "null" end return string.format("%.6g", v) elseif t == "string" then return '"' .. v:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n') .. '"' elseif t == "table" then local n = #v if n > 0 then local parts = {} for i = 1, n do parts[i] = json_val(v[i]) end return "[" .. table.concat(parts, ",") .. "]" else local parts = {} for k, vv in pairs(v) do parts[#parts + 1] = json_val(tostring(k)) .. ":" .. json_val(vv) end return "{" .. table.concat(parts, ",") .. "}" end end return "null" end local function export_table(tbl_name) local fields = GetDBTableFields(tbl_name) or {} local field_names = {} for _, f in ipairs(fields) do field_names[#field_names + 1] = f.name end local rows = GetDBTableRows(tbl_name) if not rows or #rows == 0 then Log("[snapshot] table '" .. tbl_name .. "': empty or not found") return { fields = field_names, rows = {} } end local out_rows = {} for _, row in ipairs(rows) do local rec = {} for _, fname in ipairs(field_names) do local cell = row[fname] if cell then rec[fname] = cell.value end end out_rows[#out_rows + 1] = rec end Log("[snapshot] table '" .. tbl_name .. "': " .. #out_rows .. " rows, " .. #field_names .. " fields") return { fields = field_names, rows = out_rows } end local all_table_names = GetDBTablesNames() local matched = {} for _, tname in ipairs(all_table_names) do local lower = tname:lower() for _, kw in ipairs(KEYWORDS) do if lower:find(kw) then matched[#matched + 1] = tname break end end end local snapshot = { captured_at = os.date("!%Y-%m-%dT%H:%M:%SZ"), is_career_mode = IsInCM(), matched_table_names = matched, tables = {}, } for _, tname in ipairs(matched) do snapshot.tables[tname] = export_table(tname) end local out_path = OUT_DIR .. "openfut_snapshot_" .. os.date("!%Y%m%dT%H%M%SZ") .. ".json" local f = io.open(out_path, "w") if f then f:write(json_val(snapshot)) f:close() Log("[snapshot] written to " .. out_path) MessageBox("OpenFUT Snapshot", "Snapshotted " .. #matched .. " tables.\n" .. out_path .. "\n\nTake one snapshot BEFORE Freeze Lineup, one AFTER, then run diff_snapshots.py on the two files.") else Log("[snapshot] ERROR: could not open " .. out_path) MessageBox("OpenFUT Snapshot", "ERROR writing to " .. out_path) end