diff --git a/.gitignore b/.gitignore index 443be91..d7be2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ /vendor/SUWSF-x64.zip # Runtime dumps / scratch +/dumps/ gb4dump/ *.bin *.bak-bg4uw diff --git a/LICENSE b/LICENSE index 173b257..61faa05 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 progkoch +Copyright (c) 2026 programmingPug Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,5 +22,5 @@ SOFTWARE. --------------------------------------------------------------------------- This license covers the original work in this repository: the SUWSF patch -definitions (patch/SUWSF.ini), the installer (install.sh), and the tools/. +definitions (games/*/SUWSF.ini), the installer (install.sh), and the tools/. Bundled third-party components are under their own licenses; see THIRD_PARTY.md. diff --git a/README.md b/README.md index 7dcbab5..93d9a1a 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,53 @@ -# Gundam Breaker 4 — UltraWide Fix +# UltraWidePatches -UltraWide (32:9) fix for **Gundam Breaker 4** on PC, targeting the **Samsung 49" Odyssey G93SC** (5120×1440). Works on **Linux/Proton** and Windows. +Ultrawide (**32:9 / 21:9**) fixes for PC games that ship with poor or no ultrawide support — built and tested on a **Samsung 49" Odyssey G93SC (5120×1440)**, and **Proton/Linux-first**. -## What it fixes +Most of these force **Hor+ FOV** (a wider screen shows you *more*, instead of zooming the camera in) and disable pillarboxing, by patching the game in memory at runtime with [SUWSF](https://github.com/PhantomGamers/SUWSF) + [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader). -At 32:9 the game keeps a fixed *horizontal* FOV, so a wider display just **zooms the camera in** instead of showing more. This forces the camera to keep *vertical* FOV constant and expand horizontally (**Hor+**), and disables the pillarbox/letterbox clamp — the correct ultrawide behavior. +## Supported games -## How it works (and why it has to work this way) +| Game | Engine | Status | +|---|---|---| +| [Gundam Breaker 4](games/GundamBreaker4/) | Unreal Engine 4.27 | ✅ Confirmed working in gameplay at 5120×1440 | -- GB4 is **Unreal Engine 4.27**. The classic fix is to set `AspectRatioAxisConstraint = MaintainYFOV`, but doing it in `Engine.ini` **reverts once a mission loads** — the game re-sets the camera constraint per view at runtime. -- The shipping exe (`GB4-Win64-Shipping.exe`) is wrapped in **SteamStub DRM**: `.text` is encrypted on disk (entropy 8.0), so a static hex patch of the file is impossible. The real code only exists decrypted in memory. -- So the fix is a **runtime memory patch** via [SUWSF](https://github.com/PhantomGamers/SUWSF) (Somewhat Universal Widescreen Fix) loaded by [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader). SUWSF scans the *decrypted* image at launch and rewrites the instruction that selects the FOV axis — surviving into missions. - -## Install (Linux / Proton) +## Install ```bash -./install.sh +./install.sh # list available patches +./install.sh GundamBreaker4 # install one +./install.sh GundamBreaker4 uninstall ``` -This drops `SUWSF.asi`, `dsound.dll` (ASI loader), and `SUWSF.ini` next to the shipping exe, and adds the `MaintainYFOV` tweak to the Proton-prefix `Engine.ini` (fixes menus/lobby instantly; SUWSF covers missions). It **never modifies the exe or any game file** and keeps an `Engine.ini.bak-bg4uw` backup. +The installer finds the game across your Steam libraries, drops the loader + patch next to the executable, and applies any config-level tweak (with a backup). **It never modifies game files.** -**REQUIRED on Proton** — add this to GB4's **Steam → Properties → Launch Options**, or the ASI loader is ignored and the fix only works in the lobby: +> **Proton users:** each game needs a `WINEDLLOVERRIDES` launch option or the loader is silently ignored — the installer prints the exact line for that game. This is the single most common reason a fix "doesn't work." -``` -WINEDLLOVERRIDES="dsound=n,b" %command% -``` +Windows users can extract a release zip straight into the game's exe folder; no launch option needed. -Then set the in-game resolution to **5120×1440** fullscreen. (If `dsound` ever collides, `LOADER_NAME=version.dll ./install.sh` with `version=n,b` works too.) +## Why runtime patching -> ✅ Confirmed working in gameplay at 5120×1440 (32:9) on Proton — Hor+ FOV holds through missions. +Config-file tweaks (e.g. UE4's `AspectRatioAxisConstraint=MaintainYFOV`) typically fix menus but **revert once gameplay loads**, because the game re-sets the camera per view. Many shipping executables are also DRM-encrypted on disk (GB4's is SteamStub-wrapped, `.text` entropy 8.0), which makes static exe patching impossible. Patching the *decrypted image in memory* solves both problems at once. -### Install (Windows) -Extract `dist/GB4_UltraWide_Fix.zip` into `...\GB4\Binaries\Win64\` (next to `GB4-Win64-Shipping.exe`). - -### Uninstall -```bash -./install.sh uninstall -``` - -## Status & the one open step - -The byte patterns in [`patch/SUWSF.ini`](patch/SUWSF.ini) are **candidate UE4 patterns** proven on other UE4 titles (Trepang2, Lies of P). Because the DRM encrypts the exe, they can only be verified against GB4's **decrypted memory at runtime**. If they hit (likely — same engine family), you're done. If a patch reports no change: - -```bash -# with the game running at the main menu: -python3 tools/dump_decrypted.py # dumps decrypted image via /proc//mem -python3 tools/aob.py # reports which patterns matched (OK / MISS / MANY) -``` - -Send me the `aob.py` output and I'll derive GB4's exact pattern from the dump. - -## Layout +## Repo layout | Path | What | |---|---| -| `patch/SUWSF.ini` | The ultrawide patch definitions (HOR+, pillarbox, fallbacks) | -| `install.sh` | Proton/Linux installer + uninstaller | -| `tools/dump_decrypted.py` | Dumps the decrypted exe image from the running process | -| `tools/aob.py` | Verifies/derives AOB patterns against a dump | -| `tools/find_projfunc.py` | Static fingerprint scan (confirmed the DRM encryption) | -| `dist/GB4_UltraWide_Fix.zip` | Drop-in bundle for manual/Windows install | -| `vendor/SUWSF-x64/` | Upstream SUWSF 2.3.0 + Ultimate ASI Loader | +| `games//SUWSF.ini` | That game's patch definitions | +| `games//game.conf` | Game metadata (appid, exe, paths, launch option) | +| `install.sh` | Generic installer / uninstaller | +| `tools/build_release.sh` | Builds a per-game drop-in release zip | +| `tools/publish.sh` | Pushes + cuts a GitHub release | +| `tools/dump_decrypted.py` | Dumps a DRM-decrypted image from a running process | +| `tools/aob.py` | Verifies/derives byte patterns against a dump | +| `tools/read_protonlog.sh` | Confirms ASI injection + per-patch match counts | +| `vendor/SUWSF-x64/` | Upstream SUWSF 2.3.0 + Ultimate ASI Loader (MIT) | -## Credits -[SUWSF](https://github.com/PhantomGamers/SUWSF) by PhantomGamers · [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) by ThirteenAG · AspectRatioAxisConstraint / pillarbox patterns adapted from [Lyall's UltrawidePatches](https://codeberg.org/Lyall/UltrawidePatches). +## Adding a game + +1. `mkdir games/` with a `game.conf` (copy GB4's as a template) and a `SUWSF.ini`. +2. Start from the known UE4 patterns in [`games/GundamBreaker4/SUWSF.ini`](games/GundamBreaker4/SUWSF.ini) — they match across many UE4 titles. +3. If a pattern misses, use `tools/dump_decrypted.py` + `tools/aob.py` to derive the game's exact bytes. + +## License + +MIT — see [LICENSE](LICENSE). Bundled third-party components (SUWSF, Ultimate ASI Loader) are MIT and credited in [THIRD_PARTY.md](THIRD_PARTY.md). You must own the games; nothing here modifies or circumvents copy protection. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 594a2ea..2ae72b4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,27 +1,27 @@ # Gundam Breaker 4 — UltraWide Fix v1.0.0 -Native-feeling **32:9 / 21:9 ultrawide** support for **Gundam Breaker 4** (PC). Forces **Hor+ FOV** (you see *more*, the camera doesn't zoom in) and disables pillarboxing. Confirmed working in gameplay at **5120×1440** on Linux/Proton (Samsung 49" Odyssey G93SC). +Part of [UltraWidePatches](https://github.com/programmingPug/UltraWidePatches). + +Ultrawide **32:9 / 21:9** support for **Gundam Breaker 4** (PC). Forces **Hor+ FOV** — a wider screen shows you *more*, instead of zooming the camera in — and disables pillarboxing. Confirmed working in gameplay at **5120×1440** on Linux/Proton (Samsung 49" Odyssey G93SC). ## Install (Linux / Proton) -1. Extract `GB4_UltraWide_Fix.zip` into `…/GB4/Binaries/Win64/` (next to `GB4-Win64-Shipping.exe`), or run `./install.sh` from the repo. -2. **Required:** add to GB4's **Steam → Properties → Launch Options**: +1. Extract the zip into `…/GB4/Binaries/Win64/` (next to `GB4-Win64-Shipping.exe`), or run `./install.sh GundamBreaker4` from the repo. +2. **Required** — Steam → Properties → Launch Options: ``` WINEDLLOVERRIDES="dsound=n,b" %command% ``` Without this, Proton ignores the loader and the fix only works in the lobby. -3. Set the in-game resolution to your display's native ultrawide res, fullscreen. +3. Set the in-game resolution to your native ultrawide res, fullscreen. ## Install (Windows) -Extract `GB4_UltraWide_Fix.zip` into `…\GB4\Binaries\Win64\`. No launch option needed. +Extract the zip into `…\GB4\Binaries\Win64\`. No launch option needed. ## How it works GB4 is Unreal Engine 4.27 and its shipping exe is SteamStub-encrypted, so a static exe patch isn't possible. This uses [SUWSF](https://github.com/PhantomGamers/SUWSF) + [Ultimate ASI Loader](https://github.com/ThirteenAG/Ultimate-ASI-Loader) to patch the decrypted UE4 `AspectRatioAxisConstraint` in memory at runtime — which is why it holds through missions (the `Engine.ini` trick alone reverts in-mission). ## Notes -- Do not use the HOR+ patch at 16:9 or narrower. -- You must own the game. This does not modify or circumvent any game copy protection. -- Uninstall: delete `SUWSF.asi`, `SUWSF.ini`, `dsound.dll` from `Binaries/Win64` (or `./install.sh uninstall`). - -**Checksum (GB4_UltraWide_Fix.zip):** `c1df7e963003f6b5c0583104908be0430e2b2ae1c5087c014ecf14c06b1cc7d3` +- Do not enable the HOR+ patch at 16:9 or narrower. +- You must own the game. Nothing here modifies or circumvents copy protection. +- Uninstall: `./install.sh GundamBreaker4 uninstall`, or delete `SUWSF.asi`, `SUWSF.ini`, `dsound.dll`. Credits: SUWSF by PhantomGamers · Ultimate ASI Loader by ThirteenAG · patterns adapted from Lyall's UltrawidePatches. diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md index cbcfabe..c527286 100644 --- a/THIRD_PARTY.md +++ b/THIRD_PARTY.md @@ -19,7 +19,7 @@ them; all runtime patching is performed by SUWSF. ## Patch patterns The `AspectRatioAxisConstraint` (HOR+) and `ConstrainAspectRatio` (pillarbox) -byte patterns in `patch/SUWSF.ini` are adapted from the UE4 fixes in +byte patterns in `games/*/SUWSF.ini` are adapted from the UE4 fixes in [Lyall's UltrawidePatches](https://codeberg.org/Lyall/UltrawidePatches) (notably Trepang2 and Lies of P), which are provided for community use with SUWSF. diff --git a/games/GundamBreaker4/README.md b/games/GundamBreaker4/README.md new file mode 100644 index 0000000..de450c8 --- /dev/null +++ b/games/GundamBreaker4/README.md @@ -0,0 +1,64 @@ +# Gundam Breaker 4 — UltraWide Fix + +✅ **Confirmed working in gameplay at 5120×1440 (32:9) on Proton.** + +Forces **Hor+ FOV** so an ultrawide display shows *more of the battlefield* instead of zooming the camera in, and disables pillarboxing. + +- **Engine:** Unreal Engine 4.27 +- **Steam AppID:** 1672500 +- **Executable:** `GB4-Win64-Shipping.exe` (SteamStub-encrypted → runtime patch required) + +## Install (Linux / Proton) + +```bash +./install.sh GundamBreaker4 +``` + +Then **required** — Steam → right-click *Gundam Breaker 4* → Properties → Launch Options: + +``` +WINEDLLOVERRIDES="dsound=n,b" %command% +``` + +Without it, Proton ignores the native `dsound.dll` and the fix appears to work **only in the lobby** (that part is the `Engine.ini` tweak) while reverting in missions. + +Finally, set the in-game resolution to **5120×1440**, fullscreen. + +## Install (Windows) + +Extract the release zip into `…\GB4\Binaries\Win64\` (next to `GB4-Win64-Shipping.exe`). No launch option needed. + +## Uninstall + +```bash +./install.sh GundamBreaker4 uninstall +``` + +## What it patches + +| Patch | Effect | +|---|---| +| `AspectRatioAxisConstraint` | Forces `MaintainYFOV` (0) → Hor+ FOV. **Do not use at 16:9 or narrower.** | +| `ConstrainAspectRatio` | NOPs the pillarbox/letterbox clamp | +| `HardcodedAspect_16by9_*` | Optional fallbacks, disabled by default | + +The patterns are UE4-generic (adapted from Trepang2 / Lies of P in [Lyall's UltrawidePatches](https://codeberg.org/Lyall/UltrawidePatches)) and matched GB4's 4.27 code as-is. + +## Troubleshooting + +Add `PROTON_LOG=1` to the launch options, relaunch, then: + +```bash +./tools/read_protonlog.sh 1672500 +``` + +That reports whether the ASI injected and how many matches each pattern found. If a pattern shows `No pattern found`, derive the exact bytes: + +```bash +python3 tools/dump_decrypted.py GB4-Win64-Shipping.exe # while the game runs +python3 tools/aob.py games/GundamBreaker4/SUWSF.ini +``` + +## Known / not yet verified + +- HUD/UI scaling and cutscene framing at 32:9 have not been formally checked. diff --git a/patch/SUWSF.ini b/games/GundamBreaker4/SUWSF.ini similarity index 100% rename from patch/SUWSF.ini rename to games/GundamBreaker4/SUWSF.ini diff --git a/games/GundamBreaker4/game.conf b/games/GundamBreaker4/game.conf new file mode 100644 index 0000000..e198606 --- /dev/null +++ b/games/GundamBreaker4/game.conf @@ -0,0 +1,28 @@ +# Game metadata consumed by ../../install.sh +# Everything game-specific lives here so the installer stays generic. + +TITLE="Gundam Breaker 4" +APPID=1672500 + +# Executable to locate, and its path under steamapps/common/ +EXE="GB4-Win64-Shipping.exe" +COMMON_SUBDIR="GBBBB/GB4/Binaries/Win64" + +# Ultimate ASI Loader proxy name. On Proton this REQUIRES the matching +# WINEDLLOVERRIDES launch option (see LAUNCH_OPTION below). +LOADER="dsound.dll" + +# Engine .ini inside the Proton prefix, relative to compatdata//pfx/ +# Leave empty to skip the config-level tweak. +PREFIX_INI="drive_c/users/steamuser/AppData/Local/GB4/Saved/Config/WindowsNoEditor/Engine.ini" + +# Block appended to PREFIX_INI. Fixes menus/lobby immediately; the runtime +# SUWSF patch is what carries it through gameplay. +read -r -d '' INI_BLOCK <<'EOF' || true +[/Script/Engine.LocalPlayer] +AspectRatioAxisConstraint=AspectRatio_MaintainYFOV +EOF + +LAUNCH_OPTION='WINEDLLOVERRIDES="dsound=n,b" %command%' +ENGINE="Unreal Engine 4.27" +STATUS="Confirmed working in gameplay at 5120x1440 (32:9) on Proton" diff --git a/install.sh b/install.sh index 0dab88e..0841c76 100755 --- a/install.sh +++ b/install.sh @@ -1,108 +1,146 @@ #!/usr/bin/env bash # ============================================================================ -# Gundam Breaker 4 - UltraWide Fix installer (Linux / Proton) -# Installs SUWSF + Ultimate ASI Loader + our GB4 SUWSF.ini next to the -# shipping exe. Idempotent. Use ./install.sh uninstall to remove. +# UltraWidePatches - installer (Linux / Proton, also usable on Windows via +# manual extraction of the release zip) +# +# Usage: +# ./install.sh # list available games +# ./install.sh GundamBreaker4 # install that game's patch +# ./install.sh GundamBreaker4 uninstall +# +# Overrides: +# GAME_DIR=/path/to/Binaries/Win64 ./install.sh # nonstandard install +# PREFIX_INI_FILE=/path/to/Engine.ini ./install.sh # nonstandard prefix +# LOADER_NAME=version.dll ./install.sh # loader collision # ============================================================================ set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LOADER_NAME="${LOADER_NAME:-dsound.dll}" # override: LOADER_NAME=version.dll ./install.sh +GAMES_DIR="$HERE/games" -# --- locate GB4 shipping exe dir across Steam libraries ----------------------- +list_games() { + echo "Available patches:" + for d in "$GAMES_DIR"/*/; do + [ -f "$d/game.conf" ] || continue + ( TITLE=""; STATUS=""; . "$d/game.conf" >/dev/null 2>&1 || true + printf " %-22s %s\n" "$(basename "$d")" "${TITLE:-} — ${STATUS:-}" ) + done + echo + echo "Usage: ./install.sh [uninstall]" +} + +GAME="${1:-}" +ACTION="${2:-install}" +if [ -z "$GAME" ]; then list_games; exit 0; fi + +GAME_CONF="$GAMES_DIR/$GAME/game.conf" +if [ ! -f "$GAME_CONF" ]; then + echo "[!] Unknown game: $GAME"; echo; list_games; exit 1 +fi +# shellcheck disable=SC1090 +. "$GAME_CONF" +LOADER="${LOADER_NAME:-${LOADER:-dsound.dll}}" + +echo "=== ${TITLE:-$GAME} ===" + +# --- locate the game's exe directory across Steam libraries ------------------- find_gamedir() { local cand for base in \ "$HOME/.steam/steam" "$HOME/.local/share/Steam" \ - "/media/$USER/Data/steam" "/media/$USER"/*/steam \ + "/media/$USER"/*/steam "/media/$USER"/*/SteamLibrary \ "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam"; do - cand="$base/steamapps/common/GBBBB/GB4/Binaries/Win64" - [ -f "$cand/GB4-Win64-Shipping.exe" ] && { echo "$cand"; return 0; } + cand="$base/steamapps/common/$COMMON_SUBDIR" + [ -f "$cand/$EXE" ] && { echo "$cand"; return 0; } done - # brute-force fallback - cand=$(find /home /media /mnt -maxdepth 8 -name GB4-Win64-Shipping.exe 2>/dev/null | head -1) + cand=$(find /home /media /mnt -maxdepth 8 -name "$EXE" 2>/dev/null | head -1) [ -n "$cand" ] && { dirname "$cand"; return 0; } return 1 } -GAMEDIR="${GB4_WIN64_DIR:-$(find_gamedir || true)}" -if [ -z "${GAMEDIR:-}" ] || [ ! -f "$GAMEDIR/GB4-Win64-Shipping.exe" ]; then - echo "[!] Could not find GB4-Win64-Shipping.exe." - echo " Set it manually: GB4_WIN64_DIR=/path/to/GB4/Binaries/Win64 ./install.sh" +GAMEDIR="${GAME_DIR:-$(find_gamedir || true)}" +if [ -z "${GAMEDIR:-}" ] || [ ! -f "$GAMEDIR/$EXE" ]; then + echo "[!] Could not find $EXE." + echo " Set it manually: GAME_DIR=/path/to/exe/dir ./install.sh $GAME" exit 1 fi echo "[+] Game dir: $GAMEDIR" -ASI="$HERE/vendor/SUWSF-x64/SUWSF.asi" -LOADER_SRC="$HERE/vendor/SUWSF-x64/dsound.dll" # Ultimate ASI Loader (adapts by filename) -INI="$HERE/patch/SUWSF.ini" -for f in "$ASI" "$LOADER_SRC" "$INI"; do - [ -f "$f" ] || { echo "[!] missing $f"; exit 1; } -done - -# --- locate the Proton prefix Engine.ini (for the immediate menu/lobby win) --- -find_engine_ini() { +# --- locate the Proton prefix ini (optional, per-game) ------------------------ +find_prefix_ini() { + [ -n "${PREFIX_INI_FILE:-}" ] && { [ -f "$PREFIX_INI_FILE" ] && echo "$PREFIX_INI_FILE" && return 0; return 1; } + [ -z "${PREFIX_INI:-}" ] && return 1 local cand for base in \ - "/media/$USER/Data/steam" "$HOME/.local/share/Steam" \ - "$HOME/.steam/steam" "/media/$USER"/*/steam; do - cand="$base/steamapps/compatdata/1672500/pfx/drive_c/users/steamuser/AppData/Local/GB4/Saved/Config/WindowsNoEditor/Engine.ini" + "$HOME/.steam/steam" "$HOME/.local/share/Steam" \ + "/media/$USER"/*/steam "/media/$USER"/*/SteamLibrary \ + "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam"; do + cand="$base/steamapps/compatdata/$APPID/pfx/$PREFIX_INI" [ -f "$cand" ] && { echo "$cand"; return 0; } done return 1 } -MARK_BEGIN="; >>> BG4 UltraWide Fix (MaintainYFOV) >>>" -MARK_END="; <<< BG4 UltraWide Fix <<<" +MARK_BEGIN="; >>> UltraWidePatches ($GAME) >>>" +MARK_END="; <<< UltraWidePatches ($GAME) <<<" -apply_engine_ini() { - local ini; ini="$(find_engine_ini || true)" - [ -z "$ini" ] && { echo "[i] Engine.ini not found (launch the game once first); skipping menu-level win."; return; } - if grep -qF "$MARK_BEGIN" "$ini"; then echo "[i] Engine.ini tweak already present."; return; fi - cp -v "$ini" "$ini.bak-bg4uw" - { echo ""; echo "$MARK_BEGIN"; echo "[/Script/Engine.LocalPlayer]"; - echo "AspectRatioAxisConstraint=AspectRatio_MaintainYFOV"; echo "$MARK_END"; } >> "$ini" - echo "[+] Engine.ini: added MaintainYFOV (fixes menus/lobby immediately; SUWSF covers missions)." +apply_ini() { + [ -z "${INI_BLOCK:-}" ] && return + local ini; ini="$(find_prefix_ini || true)" + [ -z "$ini" ] && { echo "[i] Prefix ini not found (launch the game once first); skipping config tweak."; return; } + if grep -qF "$MARK_BEGIN" "$ini"; then echo "[i] Config tweak already present."; return; fi + cp "$ini" "$ini.bak-uwp" + { echo ""; echo "$MARK_BEGIN"; echo "$INI_BLOCK"; echo "$MARK_END"; } >> "$ini" + echo "[+] Config tweak applied (backup: $(basename "$ini").bak-uwp)" } -remove_engine_ini() { - local ini; ini="$(find_engine_ini || true)" +remove_ini() { + local ini; ini="$(find_prefix_ini || true)" [ -z "$ini" ] && return if grep -qF "$MARK_BEGIN" "$ini"; then - sed -i "/$(printf '%s' "$MARK_BEGIN" | sed 's/[][\/.*^$]/\\&/g')/,/$(printf '%s' "$MARK_END" | sed 's/[][\/.*^$]/\\&/g')/d" "$ini" - echo "[+] Engine.ini: removed BG4 UltraWide block." + local esc_b esc_e + esc_b=$(printf '%s' "$MARK_BEGIN" | sed 's/[][\/.*^$]/\\&/g') + esc_e=$(printf '%s' "$MARK_END" | sed 's/[][\/.*^$]/\\&/g') + sed -i "/$esc_b/,/$esc_e/d" "$ini" + echo "[+] Config tweak removed." fi } -uninstall() { +if [ "$ACTION" = "uninstall" ]; then echo "[+] Uninstalling..." for f in SUWSF.asi SUWSF.ini dsound.dll dinput8.dll version.dll winmm.dll; do - [ -f "$GAMEDIR/$f" ] && { rm -v "$GAMEDIR/$f"; } + [ -f "$GAMEDIR/$f" ] && rm -v "$GAMEDIR/$f" done - remove_engine_ini - echo "[+] Done. (game files untouched; nothing was ever modified in the exe)" -} - -if [ "${1:-}" = "uninstall" ]; then uninstall; exit 0; fi + remove_ini + echo "[+] Done. No game file was ever modified." + exit 0 +fi # --- install ------------------------------------------------------------------ +ASI="$HERE/vendor/SUWSF-x64/SUWSF.asi" +LOADER_SRC="$HERE/vendor/SUWSF-x64/dsound.dll" # Ultimate ASI Loader (adapts by filename) +INI="$GAMES_DIR/$GAME/SUWSF.ini" +for f in "$ASI" "$LOADER_SRC" "$INI"; do + [ -f "$f" ] || { echo "[!] missing $f"; exit 1; } +done + cp -v "$ASI" "$GAMEDIR/SUWSF.asi" cp -v "$INI" "$GAMEDIR/SUWSF.ini" -cp -v "$LOADER_SRC" "$GAMEDIR/$LOADER_NAME" -apply_engine_ini +cp -v "$LOADER_SRC" "$GAMEDIR/$LOADER" +apply_ini -echo -echo "[+] Installed: SUWSF.asi, SUWSF.ini, $LOADER_NAME" -echo -echo "NEXT (Steam):" -echo " 1. If the game does not apply the fix, add this to GB4's Steam" -echo " Launch Options (right-click game > Properties > Launch Options):" -echo " WINEDLLOVERRIDES=\"${LOADER_NAME%.dll}=n,b\" %command%" -echo " 2. Set the in-game resolution to 5120x1440 (fullscreen)." -echo " 3. Launch. Camera should widen (Hor+) instead of zooming in." -echo -echo "If nothing changes, the runtime AOB may differ for GB4. Run:" -echo " python3 tools/dump_decrypted.py (while game is at main menu)" -echo " python3 tools/aob.py (reports which patterns matched)" -echo -echo "To try a different loader name: LOADER_NAME=version.dll ./install.sh" -echo "To remove everything: ./install.sh uninstall" +cat < right-click ${TITLE:-$GAME} > Properties > Launch Options: + ${LAUNCH_OPTION:-WINEDLLOVERRIDES="${LOADER%.dll}=n,b" %command%} + + Then set the in-game resolution to your native ultrawide res, fullscreen. + +Troubleshooting: + Different loader name: LOADER_NAME=version.dll ./install.sh $GAME + Diagnose injection: add PROTON_LOG=1 to launch options, then + ./tools/read_protonlog.sh $APPID + Remove everything: ./install.sh $GAME uninstall +EOF diff --git a/tools/aob.py b/tools/aob.py index b313b9d..5ccdd74 100755 --- a/tools/aob.py +++ b/tools/aob.py @@ -1,21 +1,20 @@ #!/usr/bin/env python3 -"""Verify (or derive) SUWSF AOB patterns against a decrypted GB4 dump. +"""Verify (or derive) SUWSF AOB patterns against a decrypted game dump. -- Reads patch/SUWSF.ini, extracts every [Patch:*] Pattern, and reports how many +- Reads a game's SUWSF.ini, extracts every [Patch:*] Pattern, and reports how many matches each has in the dump produced by dump_decrypted.py. - 1 clean match -> pattern is good, ship it. -- 0 matches -> pattern needs deriving for GB4 (see notes printed). +- 0 matches -> pattern needs deriving for this game (see notes printed). - many matches -> pattern too loose; tighten or set Match to a specific index. Usage: - python3 tools/aob.py [dump.bin] [SUWSF.ini] + python3 tools/aob.py [games//SUWSF.ini] """ import re, sys, os -DUMP = sys.argv[1] if len(sys.argv) > 1 else \ - "/tmp/claude-1000/-home-ckoch-Documents-Development-BG4-UltraWide-fix/1a8ebba4-6fbe-45ae-a873-0e69d867e0db/scratchpad/gb4dump/gb4_decrypted.bin" +DUMP = sys.argv[1] if len(sys.argv) > 1 else "" INI = sys.argv[2] if len(sys.argv) > 2 else \ - os.path.join(os.path.dirname(__file__), "..", "patch", "SUWSF.ini") + os.path.join(os.path.dirname(__file__), "..", "games", "GundamBreaker4", "SUWSF.ini") def parse_pattern(pat): @@ -65,7 +64,7 @@ def load_patches(ini_path): def main(): - if not os.path.exists(DUMP): + if not DUMP or not os.path.exists(DUMP): print(f"[!] dump not found: {DUMP}\n Run tools/dump_decrypted.py while the game is running.") sys.exit(1) data = open(DUMP, "rb").read() diff --git a/tools/build_release.sh b/tools/build_release.sh index bc66485..d84041c 100755 --- a/tools/build_release.sh +++ b/tools/build_release.sh @@ -1,21 +1,27 @@ #!/usr/bin/env bash -# Build the drop-in release zip from committed sources + vendored SUWSF. -# Output: dist/GB4_UltraWide_Fix.zip (attach to the GitHub Release) +# Build a drop-in release zip for one game. +# ./tools/build_release.sh GundamBreaker4 +# Output: dist/_UltraWide_Fix.zip (attach to the GitHub Release) set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$HERE" -V="${1:-vendor/SUWSF-x64}" -for f in "$V/SUWSF.asi" "$V/dsound.dll" "$V/LICENSE" patch/SUWSF.ini; do +GAME="${1:?usage: build_release.sh }" +GDIR="games/$GAME" +V="vendor/SUWSF-x64" +[ -d "$GDIR" ] || { echo "[!] no such game: $GDIR"; exit 1; } +for f in "$V/SUWSF.asi" "$V/dsound.dll" "$V/LICENSE" "$GDIR/SUWSF.ini"; do [ -f "$f" ] || { echo "[!] missing $f"; exit 1; } done -rm -rf dist/GB4_UltraWide_Fix dist/GB4_UltraWide_Fix.zip -mkdir -p dist/GB4_UltraWide_Fix -cp "$V/SUWSF.asi" "$V/dsound.dll" patch/SUWSF.ini dist/GB4_UltraWide_Fix/ -cp "$V/LICENSE" dist/GB4_UltraWide_Fix/SUWSF-LICENSE.txt -cp README.md THIRD_PARTY.md dist/GB4_UltraWide_Fix/ 2>/dev/null || true -( cd dist && zip -r -q GB4_UltraWide_Fix.zip GB4_UltraWide_Fix ) -echo "[+] Built dist/GB4_UltraWide_Fix.zip" -( cd dist && sha256sum GB4_UltraWide_Fix.zip ) -unzip -l dist/GB4_UltraWide_Fix.zip +OUT="dist/${GAME}_UltraWide_Fix" +rm -rf "$OUT" "$OUT.zip" +mkdir -p "$OUT" +cp "$V/SUWSF.asi" "$V/dsound.dll" "$GDIR/SUWSF.ini" "$OUT/" +cp "$V/LICENSE" "$OUT/SUWSF-LICENSE.txt" +cp "$GDIR/README.md" "$OUT/README.md" 2>/dev/null || true +cp THIRD_PARTY.md "$OUT/" 2>/dev/null || true +( cd dist && zip -r -q "${GAME}_UltraWide_Fix.zip" "${GAME}_UltraWide_Fix" ) +echo "[+] Built dist/${GAME}_UltraWide_Fix.zip" +( cd dist && sha256sum "${GAME}_UltraWide_Fix.zip" ) +unzip -l "dist/${GAME}_UltraWide_Fix.zip" diff --git a/tools/dump_decrypted.py b/tools/dump_decrypted.py index dcdcccf..51e8d53 100755 --- a/tools/dump_decrypted.py +++ b/tools/dump_decrypted.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Dump the DECRYPTED GB4-Win64-Shipping.exe image from a running (Proton) process. +"""Dump a DRM-DECRYPTED game image from a running (Proton) process. Why: the on-disk exe is SteamStub-encrypted (.text entropy 8.0). The real UE4 code only exists in memory after the stub decrypts it at launch. This grabs that @@ -8,7 +8,7 @@ memory so we can verify/derive the exact SUWSF byte patterns offline. Usage: # 1. Launch Gundam Breaker 4 (get to the main menu / lobby). # 2. Run: - python3 tools/dump_decrypted.py + python3 tools/dump_decrypted.py [pid] # -> writes scratch dump + region metadata, then prints AOB match report. If you get "Operation not permitted", either run with sudo, or temporarily: @@ -16,8 +16,8 @@ If you get "Operation not permitted", either run with sudo, or temporarily: """ import ctypes, ctypes.util, os, re, sys, json, struct, glob -MODULE_HINT = "GB4-Win64-Shipping.exe" -OUTDIR = os.environ.get("GB4_DUMP_DIR", "/tmp/claude-1000/-home-ckoch-Documents-Development-BG4-UltraWide-fix/1a8ebba4-6fbe-45ae-a873-0e69d867e0db/scratchpad/gb4dump") +MODULE_HINT = next((a for a in sys.argv[1:] if a.lower().endswith(".exe")), "GB4-Win64-Shipping.exe") +OUTDIR = os.environ.get("UWP_DUMP_DIR", os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "dumps")) libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) @@ -109,18 +109,19 @@ def main(): meta["regions"].append({"start": start, "end": end, "rva": rva, "perms": perms, "size": size, "got": len(data)}) print(f" dumped 0x{start:x}-0x{end:x} {perms} rva=0x{rva:x} ({len(data)} bytes)") - dump_path = os.path.join(OUTDIR, "gb4_decrypted.bin") + stem = os.path.splitext(os.path.basename(MODULE_HINT))[0] + dump_path = os.path.join(OUTDIR, f"{stem}_decrypted.bin") with open(dump_path, "wb") as f: f.write(blob) - with open(os.path.join(OUTDIR, "gb4_dump_meta.json"), "w") as f: + with open(os.path.join(OUTDIR, f"{stem}_meta.json"), "w") as f: json.dump(meta, f, indent=2) print(f"[+] wrote {dump_path} ({len(blob)} bytes, rva-aligned)") - print(f"[+] wrote {os.path.join(OUTDIR, 'gb4_dump_meta.json')}") + print(f"[+] wrote {os.path.join(OUTDIR, stem + '_meta.json')}") # quick sanity: is it decrypted? look for the UE4 version string / a UTF-16 hint if b"++UE4+Release-4.27" in blob or b"CalculateProjectionMatrix" in blob: print("[+] decrypted UE4 code confirmed in dump.") - print("\nNext: python3 tools/aob.py (verifies SUWSF patterns against this dump)") + print(f"\nNext: python3 tools/aob.py {dump_path} games//SUWSF.ini") if __name__ == "__main__": diff --git a/tools/publish.sh b/tools/publish.sh index f882d23..ad19cca 100755 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -7,11 +7,12 @@ # # Usage: # ./tools/publish.sh [REPO_NAME] [--private] -# (defaults: repo name "GB4-UltraWide-Fix", public) +# (defaults: repo name "UltraWidePatches", public) +# GAME=GundamBreaker4 selects which game's zip to attach (default: GundamBreaker4) set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; cd "$HERE" -REPO="${1:-GB4-UltraWide-Fix}" +REPO="${1:-UltraWidePatches}" VIS="--public"; [ "${2:-}" = "--private" ] && VIS="--private" TAG="v1.0.0" @@ -21,8 +22,9 @@ OWNER="$(gh api user -q .login)" echo "[+] Publishing as: $OWNER repo: $REPO $VIS" # 1. build the release artifact fresh -bash tools/build_release.sh >/dev/null -echo "[+] Built dist/GB4_UltraWide_Fix.zip" +GAME="${GAME:-GundamBreaker4}" +bash tools/build_release.sh "$GAME" >/dev/null +echo "[+] Built dist/${GAME}_UltraWide_Fix.zip" # 2. create the repo (skip if it already exists) and set as origin if gh repo view "$OWNER/$REPO" >/dev/null 2>&1; then @@ -30,7 +32,7 @@ if gh repo view "$OWNER/$REPO" >/dev/null 2>&1; then git remote get-url origin >/dev/null 2>&1 || git remote add origin "https://github.com/$OWNER/$REPO.git" else gh repo create "$OWNER/$REPO" $VIS --source=. --remote=origin \ - --description="UltraWide (32:9/21:9) fix for Gundam Breaker 4 — Hor+ FOV via SUWSF. Proton-ready." + --description="Ultrawide (32:9/21:9) fixes for PC games — Hor+ FOV via SUWSF. Proton-first." fi # 3. push branch + tag @@ -39,10 +41,10 @@ git push origin "$TAG" # 4. create the release with notes + zip asset (idempotent) if gh release view "$TAG" -R "$OWNER/$REPO" >/dev/null 2>&1; then - gh release upload "$TAG" dist/GB4_UltraWide_Fix.zip -R "$OWNER/$REPO" --clobber + gh release upload "$TAG" "dist/${GAME}_UltraWide_Fix.zip" -R "$OWNER/$REPO" --clobber else - gh release create "$TAG" dist/GB4_UltraWide_Fix.zip \ - -R "$OWNER/$REPO" -t "GB4 UltraWide Fix $TAG" -F RELEASE_NOTES.md + gh release create "$TAG" "dist/${GAME}_UltraWide_Fix.zip" \ + -R "$OWNER/$REPO" -t "$TAG" -F RELEASE_NOTES.md fi echo diff --git a/tools/read_protonlog.sh b/tools/read_protonlog.sh index 0726aea..817cee6 100755 --- a/tools/read_protonlog.sh +++ b/tools/read_protonlog.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash -# Reads the newest Proton log for GB4 (appid 1672500) and extracts SUWSF + DLL-load evidence. +# Reads the newest Proton log for a game and extracts SUWSF + DLL-load evidence. +# Usage: ./tools/read_protonlog.sh (e.g. 1672500 for Gundam Breaker 4) set -uo pipefail -LOG=$(ls -t "$HOME"/steam-1672500.log "$HOME"/.steam/steam/logs/steam-1672500.log 2>/dev/null | head -1) -[ -z "${LOG:-}" ] && { echo "[!] No steam-1672500.log found. Add PROTON_LOG=1 to launch options and relaunch."; exit 1; } +APPID="${1:?usage: read_protonlog.sh }" +LOG=$(ls -t "$HOME"/steam-$APPID.log "$HOME"/.steam/steam/logs/steam-$APPID.log 2>/dev/null | head -1) +[ -z "${LOG:-}" ] && { echo "[!] No steam-$APPID.log found. Add PROTON_LOG=1 to launch options and relaunch."; exit 1; } echo "[+] Log: $LOG ($(stat -c%s "$LOG") bytes, modified $(stat -c%y "$LOG"))" echo "=== Did our native dsound.dll load? ===" grep -iE "dsound\.dll|SUWSF\.asi|Ultimate" "$LOG" | grep -iE "load|builtin|native|override" | head