#!/usr/bin/env bash # FIFA 17 hook M1 staging/deployment helper. # # Safe defaults: # inspect (the default) is read-only; # stage writes only below the repository; # deploy and launch require separate, exact confirmation variables. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" hook_root="${repo_root}/openfut-launcher/openfut-hook" default_dll="${hook_root}/target/x86_64-pc-windows-gnu/release/openfut_hook.dll" stage_root="${repo_root}/fifa17-recon/staging/fifa17-hook-m1" game_dir="${OPENFUT_FIFA17_GAME_DIR:-/mnt/games/FIFA 17}" wine_prefix="${OPENFUT_FIFA17_WINEPREFIX:-/home/alex/Games/umu/fifa17}" proton_path="${OPENFUT_FIFA17_PROTONPATH:-UMU-Proton-10.0-4}" hook_dll="${OPENFUT_FIFA17_HOOK_DLL:-${default_dll}}" system_version="${wine_prefix}/drive_c/windows/system32/version.dll" deployed_dll="${game_dir}/version.dll" required_exports=( GetFileVersionInfoA GetFileVersionInfoExA GetFileVersionInfoExW GetFileVersionInfoSizeA GetFileVersionInfoSizeExA GetFileVersionInfoSizeExW GetFileVersionInfoSizeW GetFileVersionInfoW VerFindFileA VerFindFileW VerInstallFileA VerInstallFileW VerLanguageNameA VerLanguageNameW VerQueryValueA VerQueryValueW ) die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } note() { printf '%s\n' "$*"; } need_file() { [[ -f "$1" ]] || die "missing file: $1"; } sha256() { sha256sum -- "$1" | awk '{print $1}'; } pe_exports() { x86_64-w64-mingw32-objdump -p "$1" | awk '/\[Ordinal\/Name Pointer\] Table/{in_names=1; next} in_names && /\+base\[/ {print $NF}' } verify_pe64() { local dll=$1 local format format="$(x86_64-w64-mingw32-objdump -f "$dll" | awk '/file format/{print $NF}')" [[ "$format" == "pei-x86-64" ]] || die "$dll is not a 64-bit PE DLL (format=${format:-unknown})" } verify_exports() { local dll=$1 export_name local exports exports="$(pe_exports "$dll")" for export_name in "${required_exports[@]}"; do grep -Fxq "$export_name" <<<"$exports" || die "$dll lacks VERSION export $export_name; refusing to stage/deploy" done } verify_inputs() { command -v sha256sum >/dev/null || die "sha256sum is required" command -v x86_64-w64-mingw32-objdump >/dev/null || die "x86_64-w64-mingw32-objdump is required" need_file "$hook_dll" need_file "$system_version" verify_pe64 "$hook_dll" } inspect() { verify_inputs note "mode=inspect (read-only)" note "hook=$hook_dll" note "hook_sha256=$(sha256 "$hook_dll")" note "system_version=$system_version" note "system_version_sha256=$(sha256 "$system_version")" note "game_dir=$game_dir" if [[ -f "$deployed_dll" ]]; then note "deployed_version_sha256=$(sha256 "$deployed_dll")" else note "deployed_version=absent" fi verify_exports "$hook_dll" note "version_exports=complete" } build() { command -v cargo >/dev/null || die "cargo is required" note "Building the inert FIFA 17 hook into the package-local staging source path." CARGO_TARGET_DIR="${hook_root}/target" \ cargo build --offline --release --features fifa17 \ --target x86_64-pc-windows-gnu --manifest-path "${hook_root}/Cargo.toml" inspect } stage() { verify_inputs verify_exports "$hook_dll" need_file "${game_dir}/CardsDLL_Win64_retail.dll" need_file "${game_dir}/FIFA17.exe" mkdir -p "$stage_root" local staged="${stage_root}/version.dll" cp -- "$hook_dll" "$staged" { printf 'artifact=%s\n' "$staged" printf 'artifact_sha256=%s\n' "$(sha256 "$staged")" printf 'source=%s\n' "$hook_dll" printf 'source_sha256=%s\n' "$(sha256 "$hook_dll")" printf 'system_version=%s\n' "$system_version" printf 'system_version_sha256=%s\n' "$(sha256 "$system_version")" printf 'cards_dll_sha256=%s\n' "$(sha256 "${game_dir}/CardsDLL_Win64_retail.dll")" printf 'fifa17_exe_sha256=%s\n' "$(sha256 "${game_dir}/FIFA17.exe")" } >"${stage_root}/manifest.txt" note "staged=$staged" note "manifest=${stage_root}/manifest.txt" note "No game-directory file was changed." } require_game_stopped() { if pgrep -fi '(FIFA17|_fifa17)\.exe' >/dev/null; then die "FIFA 17 appears to be running; close it before deployment" fi } deploy() { [[ "${OPENFUT_FIFA17_DEPLOY:-}" == "I_ACCEPT_VERSION_DLL_REPLACEMENT" ]] || die "deploy requires OPENFUT_FIFA17_DEPLOY=I_ACCEPT_VERSION_DLL_REPLACEMENT" require_game_stopped local staged="${stage_root}/version.dll" local manifest="${stage_root}/manifest.txt" need_file "$staged" need_file "$manifest" verify_pe64 "$staged" verify_exports "$staged" local recorded actual recorded="$(awk -F= '$1=="artifact_sha256"{print $2}' "$manifest")" actual="$(sha256 "$staged")" [[ -n "$recorded" && "$recorded" == "$actual" ]] || die "staged artifact hash does not match manifest" local backup_dir="${game_dir}/openfut-backups" mkdir -p "$backup_dir" if [[ -f "$deployed_dll" ]]; then local old_hash backup old_hash="$(sha256 "$deployed_dll")" backup="${backup_dir}/version.dll.${old_hash}.bak" if [[ ! -e "$backup" ]]; then cp -- "$deployed_dll" "$backup" fi [[ "$(sha256 "$backup")" == "$old_hash" ]] || die "backup verification failed: $backup" note "backup=$backup" fi cp -- "$staged" "$deployed_dll" [[ "$(sha256 "$deployed_dll")" == "$actual" ]] || die "deployed DLL hash verification failed" note "deployed=$deployed_dll" note "deployed_sha256=$actual" } launch() { local mode=${1:-baseline} local hook_enabled=0 local trace_enabled=0 local request_trace_enabled=0 local notifier_trace_enabled=0 local dispatch_enabled=0 case "$mode" in baseline) [[ "${OPENFUT_FIFA17_LAUNCH:-}" == "I_ACCEPT_M1_BASELINE_LAUNCH" ]] || die "launch requires OPENFUT_FIFA17_LAUNCH=I_ACCEPT_M1_BASELINE_LAUNCH" ;; resolve) [[ "${OPENFUT_FIFA17_RESOLVE:-}" == "I_ACCEPT_M2_RESOLVE_LAUNCH" ]] || die "launch-resolve requires OPENFUT_FIFA17_RESOLVE=I_ACCEPT_M2_RESOLVE_LAUNCH" hook_enabled=1 ;; trace) [[ "${OPENFUT_FIFA17_TRACE:-}" == "I_ACCEPT_M3_PASSIVE_TRACE" ]] || die "launch-trace requires OPENFUT_FIFA17_TRACE=I_ACCEPT_M3_PASSIVE_TRACE" hook_enabled=1 trace_enabled=1 request_trace_enabled=1 notifier_trace_enabled=1 ;; dispatch) [[ "${OPENFUT_FIFA17_DISPATCH:-}" == "I_ACCEPT_GUARDED_NATIVE_DISPATCH" ]] || die "launch-dispatch requires OPENFUT_FIFA17_DISPATCH=I_ACCEPT_GUARDED_NATIVE_DISPATCH" request_trace_enabled=1 dispatch_enabled=1 ;; *) die "unknown launch mode: $mode" ;; esac need_file "$deployed_dll" local staged="${stage_root}/version.dll" local manifest="${stage_root}/manifest.txt" need_file "$staged" need_file "$manifest" verify_pe64 "$deployed_dll" verify_exports "$deployed_dll" local recorded recorded="$(awk -F= '$1=="artifact_sha256"{print $2}' "$manifest")" [[ -n "$recorded" && "$(sha256 "$staged")" == "$recorded" ]] || die "staged artifact hash does not match manifest" [[ "$(sha256 "$deployed_dll")" == "$recorded" ]] || die "deployed version.dll does not match the staged M1 artifact" command -v umu-run >/dev/null || die "umu-run is required" for name in OPENFUT_SBC_DISPATCH OPENFUT_SBC_ARM_ONLY OPENFUT_SBC_POPULATE; do [[ -z "${!name:-}" || "${!name}" == "0" ]] || die "$name must be unset or 0 for this launch" done mkdir -p "${wine_prefix}/dosdevices" ln -sfn /mnt "${wine_prefix}/dosdevices/w:" note "Launching $mode mode (SBC_HOOK=$hook_enabled; SBC_TRACE=$trace_enabled; SBC_REQUEST_TRACE=$request_trace_enabled; SBC_NOTIFIER_TRACE=$notifier_trace_enabled; SBC_DISPATCH=$dispatch_enabled); log=/tmp/fifa17-hook-m1-launch.log" cd "$game_dir" env \ GAMEID=fifa17 \ PROTONPATH="$proton_path" \ WINEPREFIX="$wine_prefix" \ WINEDLLOVERRIDES='version=n,b' \ OPENFUT_SBC_HOOK="$hook_enabled" \ OPENFUT_SBC_TRACE="$trace_enabled" \ OPENFUT_SBC_REQUEST_TRACE="$request_trace_enabled" \ OPENFUT_SBC_NOTIFIER_TRACE="$notifier_trace_enabled" \ OPENFUT_SBC_DISPATCH="$dispatch_enabled" \ OPENFUT_SBC_DISPATCH_TRACE=0 \ OPENFUT_SBC_ARM_ONLY=0 \ OPENFUT_SBC_POPULATE=0 \ umu-run _fifa17.exe 2>&1 | tee /tmp/fifa17-hook-m1-launch.log } usage() { cat <<'EOF' Usage: fifa17-hook-m1.sh [inspect|build|stage|deploy|launch|launch-resolve|launch-trace|launch-dispatch] inspect Read-only PE/hash/export preflight (default). build Cross-build the inert FIFA17 hook, then run inspect. stage Copy a verified DLL into repo-local staging and write a hash manifest. deploy Back up and install version.dll; requires: OPENFUT_FIFA17_DEPLOY=I_ACCEPT_VERSION_DLL_REPLACEMENT launch Start the M1 inert-hook baseline; requires: OPENFUT_FIFA17_LAUNCH=I_ACCEPT_M1_BASELINE_LAUNCH launch-resolve Start M2 resolve-only mode (guarded reads/logging, no detours/writes); requires: OPENFUT_FIFA17_RESOLVE=I_ACCEPT_M2_RESOLVE_LAUNCH launch-trace Start the M3-M6 passive parser/request/notifier trace; requires: OPENFUT_FIFA17_TRACE=I_ACCEPT_M3_PASSIVE_TRACE launch-dispatch Trace and repair only a fully validated native status-999 completion; requires: OPENFUT_FIFA17_DISPATCH=I_ACCEPT_GUARDED_NATIVE_DISPATCH Optional path overrides: OPENFUT_FIFA17_HOOK_DLL, OPENFUT_FIFA17_GAME_DIR, OPENFUT_FIFA17_WINEPREFIX, OPENFUT_FIFA17_PROTONPATH EOF } case "${1:-inspect}" in inspect) inspect ;; build) build ;; stage) stage ;; deploy) deploy ;; launch) launch baseline ;; launch-resolve) launch resolve ;; launch-trace) launch trace ;; launch-dispatch) launch dispatch ;; -h|--help|help) usage ;; *) usage >&2; die "unknown command: $1" ;; esac