#!/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
}

# Refuse any DLL that is not a FIFA-17-profile build.
#
# openfut-hook builds TWO mutually exclusive injection paths from one crate: the
# default (FIFA 23) path installs getaddrinfo/connect/ProtoSSL/origin hooks, while
# `--features fifa17` installs ONLY the FIFA-17-safe logic (module map, FIFA 17
# cert-verify, SBC dispatch, store tab bind). Deploying a default-feature build
# into FIFA 17 hijacks the login transport and the client reports "Unable to
# connect to the EA servers", with none of the FIFA 17 repairs present.
#
# That exact mistake happened on 2026-08-19 (artifact 1c71a17a, hand-built without
# the feature): two failed launches, diagnosed only by comparing embedded strings.
# `build` below passes the feature, but a hand-built DLL can reach `stage`/`deploy`
# via OPENFUT_FIFA17_HOOK_DLL, so assert the profile on the bytes themselves.
verify_fifa17_profile() {
  local dll=$1 marker
  # Markers that MUST be present: the FIFA 17 target module and its repairs.
  for marker in 'CardsDLL_Win64_retail.dll' 'SBC_DISPATCH'; do
    grep -qaF -- "$marker" "$dll" ||
      die "$dll is not a --features fifa17 build (missing $marker); refusing to stage/deploy"
  done
  # Markers that MUST be absent: the FIFA-23-only transport hooking.
  for marker in 'getaddrinfo IAT patched' 'connect: inline-hooked' 'origin_spy'; do
    if grep -qaF -- "$marker" "$dll"; then
      die "$dll contains FIFA-23-only hook '$marker'; build with --features fifa17"
    fi
  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"
  verify_fifa17_profile "$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"
  verify_fifa17_profile "$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
