3 Commits
Author SHA1 Message Date
ckochandClaude Opus 4.8 9bdb4284d6 Migrate to self-hosted Gitea
- origin now points at git.lazypugs.com/ckoch/UltraWidePatches
- rewrite tools/publish.sh for Gitea's API (token via GITEA_TOKEN env, never
  stored on disk); creates/updates the release and uploads the game zip
- update the remaining self-referential link to the Gitea URL

Upstream links (SUWSF, Ultimate ASI Loader, Lyall's patterns) intentionally
still point at GitHub - those are third-party projects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 12:03:21 -04:00
ckochandClaude Opus 4.8 5d6fd277c9 Restructure into multi-game UltraWidePatches umbrella
Generalize the project from a single GB4 fix into a collection that can host
ultrawide patches for many games.

- games/<Game>/{SUWSF.ini,game.conf,README.md}; GB4 moved in as the first entry
- game.conf carries all game-specific data (appid, exe, paths, loader, launch
  option), so install.sh is fully generic: ./install.sh <Game> [uninstall]
- installer gains GAME_DIR / PREFIX_INI_FILE / LOADER_NAME overrides; verified
  install -> idempotent re-run -> uninstall leaves the tree pristine
- tools de-hardcoded: dump_decrypted.py takes an exe name, read_protonlog.sh
  takes an appid, aob.py takes a dump + game ini, build_release.sh takes a game
- README rewritten as an umbrella index with an "adding a game" guide
- LICENSE copyright -> programmingPug

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 11:21:20 -04:00
ckochandClaude Opus 4.8 b37677cbc1 Add GitHub publish script
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:21:52 -04:00
14 changed files with 349 additions and 158 deletions
+1
View File
@@ -10,6 +10,7 @@ __pycache__/
/vendor/SUWSF-x64.zip
# Runtime dumps / scratch
/dumps/
gb4dump/
*.bin
*.bak-bg4uw
+2 -2
View File
@@ -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.
+35 -48
View File
@@ -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/<pid>/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/<Game>/SUWSF.ini` | That game's patch definitions |
| `games/<Game>/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/<GameName>` 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.
+10 -10
View File
@@ -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://git.lazypugs.com/ckoch/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.
+1 -1
View File
@@ -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.
+64
View File
@@ -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 <dump> games/GundamBreaker4/SUWSF.ini
```
## Known / not yet verified
- HUD/UI scaling and cutscene framing at 32:9 have not been formally checked.
+28
View File
@@ -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/<APPID>/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"
+103 -65
View File
@@ -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 <Game> # nonstandard install
# PREFIX_INI_FILE=/path/to/Engine.ini ./install.sh <Game> # nonstandard prefix
# LOADER_NAME=version.dll ./install.sh <Game> # 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 <GameName> [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 <<EOF
[+] Installed: SUWSF.asi, SUWSF.ini, $LOADER
NEXT (Proton) — REQUIRED, or the loader is ignored and the fix won't apply in gameplay.
Steam > 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
+7 -8
View File
@@ -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 <dump.bin> [games/<Game>/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()
+19 -13
View File
@@ -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/<Game>_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 <GameName>}"
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"
+9 -8
View File
@@ -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 <ExeName.exe> [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/<Game>/SUWSF.ini")
if __name__ == "__main__":
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Publish a release to Gitea (git.lazypugs.com).
#
# Auth: needs a Gitea token with 'write:repository'. Never hardcode it.
# export GITEA_TOKEN=... # from your shell / password manager
# ./tools/publish.sh # or: GAME=GundamBreaker4 TAG=v1.0.1 ./tools/publish.sh
#
# Steps: build the game's zip -> push branch+tag -> create release -> upload asset.
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; cd "$HERE"
HOST="${GITEA_HOST:-https://git.lazypugs.com}"
OWNER="${GITEA_OWNER:-ckoch}"
REPO="${GITEA_REPO:-UltraWidePatches}"
GAME="${GAME:-GundamBreaker4}"
TAG="${TAG:-v1.0.0}"
NOTES="${NOTES:-RELEASE_NOTES.md}"
API="$HOST/api/v1"
[ -n "${GITEA_TOKEN:-}" ] || { echo "[!] GITEA_TOKEN is not set. Create one at $HOST/user/settings/applications (scope: write:repository)"; exit 1; }
AUTH=(-H "Authorization: token $GITEA_TOKEN")
ZIPNAME="${GAME}_UltraWide_Fix.zip"
# 1. build the artifact
bash tools/build_release.sh "$GAME" >/dev/null
ZIP="dist/$ZIPNAME"
[ -f "$ZIP" ] || { echo "[!] build produced no $ZIP"; exit 1; }
echo "[+] Built $ZIP"
# 2. push code + tag
git push origin main
git push origin "$TAG" 2>/dev/null || echo "[i] tag $TAG already on remote"
# 3. create the release (or reuse if it already exists)
REL_ID=$(curl -sf "${AUTH[@]}" "$API/repos/$OWNER/$REPO/releases/tags/$TAG" 2>/dev/null \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])" 2>/dev/null || true)
if [ -z "$REL_ID" ]; then
BODY=$(python3 -c "import json,sys;print(json.dumps(open(sys.argv[1]).read()))" "$NOTES")
REL_ID=$(curl -sf "${AUTH[@]}" -H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$BODY}" \
"$API/repos/$OWNER/$REPO/releases" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
echo "[+] Created release $TAG (id=$REL_ID)"
else
echo "[i] Release $TAG exists (id=$REL_ID); replacing asset"
OLD=$(curl -sf "${AUTH[@]}" "$API/repos/$OWNER/$REPO/releases/$REL_ID" \
| ZIPNAME="$ZIPNAME" python3 -c "
import sys, json, os
d = json.load(sys.stdin)
want = os.environ['ZIPNAME']
print(next((str(a['id']) for a in d.get('assets', []) if a['name'] == want), ''))" 2>/dev/null || true)
if [ -n "$OLD" ]; then
curl -sf -X DELETE "${AUTH[@]}" "$API/repos/$OWNER/$REPO/releases/$REL_ID/assets/$OLD" >/dev/null \
&& echo "[i] removed previous asset"
fi
fi
# 4. upload the zip
curl -sf "${AUTH[@]}" -F "attachment=@$ZIP" \
"$API/repos/$OWNER/$REPO/releases/$REL_ID/assets?name=$ZIPNAME" >/dev/null
echo "[+] Uploaded $ZIPNAME"
echo
echo "[+] Done: $HOST/$OWNER/$REPO/releases/tag/$TAG"
+5 -3
View File
@@ -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 <APPID> (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 <APPID>}"
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