diff --git a/tools/windows/README.md b/tools/windows/README.md new file mode 100644 index 0000000..9dde3b2 --- /dev/null +++ b/tools/windows/README.md @@ -0,0 +1,90 @@ +# OpenFUT FIFA 17 - native Windows client + +The FIFA 17 client host (`10.10.0.105`, Windows 11 Pro) runs FIFA 17 **natively** +(no Wine/Proton/umu). This directory holds the read-only preflight verifier and +documents the native launch/routing/rollback model. + +## Install layout (`C:\FIFA 17`) + +| File | Role | +|---|---| +| `FIFA17.exe` | retail game exe (sha256 `29C31CEF…`). **Never modify/patch.** ImageBase `0x140000000`. | +| `_fifa17.exe` | native crack loader (Chemicalflood). This is what you launch. `asInvoker` manifest -> must be elevated externally. | +| `version.dll` | **OpenFUT hook** (in-process via the version.dll load-order hijack). ImageBase `0x180000000`. | +| `version.dll.stale-849k.bak` | **rollback** copy of the previous hook. | +| `CardsDLL_Win64_retail.dll` | FUT card/SBC/kit logic. ImageBase `0x180000000`. | +| `powdll_Win64_retail.dll` | Pack-Opening-World (EASFC store). ImageBase `0x180000000`. | +| `sysdll_Win64_retail.dll` | EA networking / ProtoSSL (cert, ea.com). | +| `stp-origin_emu.dll` + `stp-origin_emu.ini` | Origin/LSX login emulator (in-process; opens LSX `:4216` locally at runtime). Persona configured in the `.ini`. | +| `stp-selector.exe` | ssl/LSX selector companion. | +| `openfut.cfg` | operator-facing routing override (see below). | + +## Launch (native - there is NO launcher script by design) + +Run `C:\FIFA 17\_fifa17.exe` **as Administrator**. The correct, reproducible way: + +- Double-click the **"FIFA 17 (OpenFUT)"** shortcut (Desktop and Start Menu). + It targets `_fifa17.exe`, working dir `C:\FIFA 17`, with the RunAsAdmin bit set. +- `_fifa17.exe` is also flagged `RUNASADMIN` in + `HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers`, + so any launch (Explorer double-click included) elevates via UAC. + +On launch the Windows loader maps `version.dll` from the game directory (hijack), +`stp-origin_emu.dll` emulates Origin login for the configured persona, and the +hook redirects EA endpoints to the OpenFUT backend. + +> Do **not** wrap the launch in a script. The elevation + shortcut is the +> supported mechanism. FIFA under native Windows also ignores synthetic input, +> so in-game steps are performed by the operator one at a time. + +## Routing (`openfut.cfg`) + +``` +host=10.10.0.120 +https_port=8443 +blaze_redirector_port=42127 +blaze_main_port=42130 +``` + +The hook carries `10.10.0.120` as its baked-in default; `openfut.cfg` is the +override. `10.10.0.120` hosts **both** production and staging. + +> **Production safety:** `blaze_main_port=42130` is the **production** Blaze. +> Before any match/economy exercise, repoint `blaze_main_port` (and the matching +> UTAS/HTTPS route) to the staging port so no traffic reaches the prod container. +> The preflight raises a WARN whenever `42130` is configured. + +## Rollback + +The hook is a single file swap; no installer state. + +```powershell +# disable OpenFUT hook (restore previous DLL) +Copy-Item 'C:\FIFA 17\version.dll' 'C:\FIFA 17\version.dll.disabled.bak' -Force +Copy-Item 'C:\FIFA 17\version.dll.stale-849k.bak' 'C:\FIFA 17\version.dll' -Force +# re-arm: copy the desired hook build over version.dll again +``` + +Always keep a `*.bak` of the live hook before redeploying (the preflight checks +that a rollback backup exists and differs from the live DLL). + +## Preflight + +`openfut-client-preflight.ps1` is **read-only**: it never launches the game, +never elevates, never writes game files, never mutates economy state. It verifies +the retail exe hash, companion DLLs, hook + rollback, routing + backend +reachability, login persona, launcher elevation, and the RE toolchain +(x64dbg, cargo). Exit 0 = OK, 1 = blocking failure. + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\openfut-client-preflight.ps1 +``` + +## Runtime RE (x64dbg) + +See the Vault note **`02 Reverse Engineering/FIFA 17/Windows Client Runtime & x64dbg.md`** +for the attach workflow and the RVA<->VA (ASLR) math. In short: these modules' +preferred ImageBase is `0x180000000` (`0x140000000` for `FIFA17.exe`); in x64dbg +a module name evaluates to its runtime (ASLR) base, so a Ghidra address maps to a +breakpoint as `bp CardsDLL_Win64_retail.dll+` where +`RVA = ghidra_addr - 0x180000000`. diff --git a/tools/windows/openfut-client-preflight.ps1 b/tools/windows/openfut-client-preflight.ps1 new file mode 100644 index 0000000..4848967 --- /dev/null +++ b/tools/windows/openfut-client-preflight.ps1 @@ -0,0 +1,129 @@ +<# +.SYNOPSIS + OpenFUT FIFA 17 Windows client preflight - READ ONLY. + +.DESCRIPTION + Non-destructive verification of the native Windows FIFA 17 OpenFUT client on + this machine. It NEVER launches the game, never elevates, never writes to + game files, and never mutates any economy state. It only reads files, + registry, and performs TCP connect probes to the configured backend. + + Exit code 0 = all PASS/WARN, 1 = one or more FAIL. + + Native launch model (there is NO launcher script by design): + run C:\FIFA 17\_fifa17.exe as Administrator + (use the "FIFA 17 (OpenFUT)" shortcut, which carries the RunAsAdmin bit). +#> +[CmdletBinding()] +param( + [string]$FifaRoot = 'C:\FIFA 17' +) + +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'SilentlyContinue' + +# --- known-good fingerprints ------------------------------------------------- +# Retail executable MUST NOT be modified/patched (Denuvo + anti-cheat sensitive). +$EXPECT_FIFA17_EXE_SHA256 = '29C31CEF12B0C3C2A7305220617C7B4FA139AB76B8C857851BDBE88987962899' +# Currently deployed OpenFUT hook (base-supply + VEH build). Update on redeploy. +$EXPECT_HOOK_SHA256 = '8844A6BCEE7BE37DD55B989246B312AF52FA711CCBC1220D25FEC64CBAD077D8' +$ROLLBACK_BAK = Join-Path $FifaRoot 'version.dll.stale-849k.bak' + +$script:fail = 0 +function Say([string]$level, [string]$msg) { + switch ($level) { + 'PASS' { $c = 'Green' } + 'WARN' { $c = 'Yellow' } + 'FAIL' { $c = 'Red'; $script:fail++ } + default { $c = 'Gray' } + } + Write-Host ('[{0}] {1}' -f $level, $msg) -ForegroundColor $c +} +function Sha([string]$p) { if (Test-Path $p) { (Get-FileHash $p -Algorithm SHA256).Hash } else { $null } } + +Write-Host '=== OpenFUT FIFA 17 Windows client preflight (read-only) ===' -ForegroundColor Cyan +Say 'INFO' ("host={0} user={1} {2}" -f $env:COMPUTERNAME, $env:USERNAME, (Get-CimInstance Win32_OperatingSystem).Caption) + +# --- 1. FIFA install + retail exe integrity --------------------------------- +$exe = Join-Path $FifaRoot 'FIFA17.exe' +$loader = Join-Path $FifaRoot '_fifa17.exe' +if (Test-Path $exe) { + $h = Sha $exe + if ($h -eq $EXPECT_FIFA17_EXE_SHA256) { Say 'PASS' "FIFA17.exe present and unmodified ($($h.Substring(0,16))...)" } + else { Say 'FAIL' "FIFA17.exe hash MISMATCH - retail exe changed! got $($h.Substring(0,16))... expected $($EXPECT_FIFA17_EXE_SHA256.Substring(0,16))..." } +} else { Say 'FAIL' "FIFA17.exe missing at $exe" } +if (Test-Path $loader) { Say 'PASS' "native loader _fifa17.exe present ($((Sha $loader).Substring(0,16))...)" } +else { Say 'FAIL' "_fifa17.exe (native loader) missing - cannot launch" } + +# --- 2. companion DLLs ------------------------------------------------------- +$companions = 'CardsDLL_Win64_retail.dll','powdll_Win64_retail.dll','sysdll_Win64_retail.dll', + 'FootballCompEng_Win64_retail.dll','stp-origin_emu.dll','stp-selector.exe' +foreach ($c in $companions) { + $p = Join-Path $FifaRoot $c + if (Test-Path $p) { Say 'PASS' "companion present: $c" } else { Say 'FAIL' "companion MISSING: $c" } +} + +# --- 3. OpenFUT hook (version.dll) + rollback backup ------------------------ +$hook = Join-Path $FifaRoot 'version.dll' +if (Test-Path $hook) { + $hh = Sha $hook + if ($hh -eq $EXPECT_HOOK_SHA256) { Say 'PASS' "hook version.dll deployed (expected build $($hh.Substring(0,16))...)" } + else { Say 'WARN' "hook version.dll present but hash differs from recorded build ($($hh.Substring(0,16))...) - may be a newer/older hook" } + $bytes = [IO.File]::ReadAllBytes($hook); $ascii = [Text.Encoding]::ASCII.GetString($bytes) + $markers = @('OpenFUT','fifa17','CardsDLL','SBC_DISPATCH') | Where-Object { $ascii -match [regex]::Escape($_) } + if ($markers.Count -ge 3) { Say 'PASS' "hook markers found: $($markers -join ', ')" } + else { Say 'WARN' "hook markers thin: $($markers -join ', ') - is this the OpenFUT hook?" } +} else { Say 'FAIL' "hook version.dll NOT deployed - client will run vanilla (no OpenFUT)" } +if (Test-Path $ROLLBACK_BAK) { + if ((Sha $ROLLBACK_BAK) -ne (Sha $hook)) { Say 'PASS' "rollback backup present and differs from live: $(Split-Path $ROLLBACK_BAK -Leaf)" } + else { Say 'WARN' "rollback backup equals live version.dll - rollback would be a no-op" } +} else { Say 'WARN' "no rollback backup ($(Split-Path $ROLLBACK_BAK -Leaf)) - keep one before redeploying the hook" } + +# --- 4. routing config + backend reachability ------------------------------- +$cfg = Join-Path $FifaRoot 'openfut.cfg' +$host120 = $null; $ports = @() +if (Test-Path $cfg) { + $kv = @{}; foreach ($l in Get-Content $cfg) { if ($l -match '^\s*([^=#]+)=(.+)$') { $kv[$matches[1].Trim()] = $matches[2].Trim() } } + $host120 = $kv['host'] + Say 'PASS' "openfut.cfg routing: host=$($kv['host']) https=$($kv['https_port']) redirector=$($kv['blaze_redirector_port']) blaze=$($kv['blaze_main_port'])" + foreach ($k in 'https_port','blaze_redirector_port','blaze_main_port') { if ($kv[$k]) { $ports += [int]$kv[$k] } } + if ($kv['blaze_main_port'] -eq '42130') { Say 'WARN' 'blaze_main_port=42130 targets PRODUCTION - repoint to a staging port before match/economy testing' } +} else { Say 'WARN' "openfut.cfg absent - hook uses its baked-in default host" } +if ($host120) { + foreach ($p in $ports) { + $t = New-Object Net.Sockets.TcpClient + try { + $ar = $t.BeginConnect($host120, $p, $null, $null) + if ($ar.AsyncWaitHandle.WaitOne(2500) -and $t.Connected) { Say 'PASS' "backend reachable ${host120}:$p" } + else { Say 'FAIL' "backend UNREACHABLE ${host120}:$p" } + } catch { Say 'FAIL' "backend probe error ${host120}:$p - $($_.Exception.Message)" } finally { $t.Close() } + } +} + +# --- 5. login persona (stp origin emulator) --------------------------------- +$ini = Join-Path $FifaRoot 'stp-origin_emu.ini' +if (Test-Path $ini) { + $persona = (Get-Content $ini | Select-String 'PersonaId|PersonaName') -join ' ' + Say 'PASS' "login emulator config: $persona" +} else { Say 'FAIL' "stp-origin_emu.ini missing - no login persona" } + +# --- 6. native launcher elevation setup ------------------------------------- +$lk = 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers' +$layer = (Get-ItemProperty $lk).$loader +if ($layer -match 'RUNASADMIN') { Say 'PASS' "_fifa17.exe RUNASADMIN flag set ($layer)" } +else { Say 'WARN' '_fifa17.exe has no RUNASADMIN flag - launch must be manual "Run as administrator"' } +$sc = Join-Path $env:USERPROFILE 'Desktop\FIFA 17 (OpenFUT).lnk' +if (Test-Path $sc) { Say 'PASS' "desktop launcher shortcut present: $(Split-Path $sc -Leaf)" } +else { Say 'WARN' 'no desktop launcher shortcut' } + +# --- 7. RE toolchain --------------------------------------------------------- +$x = Get-ChildItem "$env:LOCALAPPDATA\Microsoft\WinGet\Packages" -Recurse -Depth 4 -Include x64dbg.exe -Attributes !ReparsePoint -EA SilentlyContinue | Select-Object -First 1 -Expand FullName +if (-not $x) { $x = (Get-Command x64dbg.exe -EA SilentlyContinue).Source } +if ($x) { Say 'PASS' "x64dbg present: $x (v$((Get-Item $x).VersionInfo.FileVersion))" } else { Say 'WARN' 'x64dbg not located - install for runtime RE' } +$cargo = (Get-Command cargo -EA SilentlyContinue).Source +if ($cargo) { Say 'PASS' "rust toolchain: $cargo" } else { Say 'WARN' 'cargo not found - needed to rebuild the hook natively' } + +# --- summary ----------------------------------------------------------------- +Write-Host '' +if ($script:fail -eq 0) { Write-Host 'PREFLIGHT: PASS (no blocking failures)' -ForegroundColor Green; exit 0 } +else { Write-Host "PREFLIGHT: FAIL ($script:fail blocking issue(s))" -ForegroundColor Red; exit 1 }