Gundam Breaker 4 UltraWide Fix v1.0.0
32:9 / 21:9 ultrawide support for Gundam Breaker 4 (UE4.27) via SUWSF + Ultimate ASI Loader. Forces Hor+ FOV (AspectRatioAxisConstraint=MaintainYFOV) and disables pillarboxing, patched in decrypted memory at runtime so it holds through missions (the exe is SteamStub-encrypted; a static patch is impossible). Confirmed working in gameplay at 5120x1440 (Samsung Odyssey G93SC) on Proton. Requires WINEDLLOVERRIDES="dsound=n,b" on Proton so the loader injects. Includes: patch/SUWSF.ini, Proton installer with clean uninstall, release-zip builder, and runtime dump/verify tooling. Third-party bundles under MIT (see THIRD_PARTY.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Python
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Build artifacts (release zip is generated by tools/build_release.sh and attached to the Release)
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# Upstream zip (extracted binaries in vendor/SUWSF-x64/ are committed; the raw zip is not)
|
||||||
|
/vendor/SUWSF-x64.zip
|
||||||
|
|
||||||
|
# Runtime dumps / scratch
|
||||||
|
gb4dump/
|
||||||
|
*.bin
|
||||||
|
*.bak-bg4uw
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 progkoch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
This license covers the original work in this repository: the SUWSF patch
|
||||||
|
definitions (patch/SUWSF.ini), the installer (install.sh), and the tools/.
|
||||||
|
Bundled third-party components are under their own licenses; see THIRD_PARTY.md.
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Gundam Breaker 4 — UltraWide Fix
|
||||||
|
|
||||||
|
UltraWide (32:9) fix for **Gundam Breaker 4** on PC, targeting the **Samsung 49" Odyssey G93SC** (5120×1440). Works on **Linux/Proton** and Windows.
|
||||||
|
|
||||||
|
## What it fixes
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## How it works (and why it has to work this way)
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
**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:
|
||||||
|
|
||||||
|
```
|
||||||
|
WINEDLLOVERRIDES="dsound=n,b" %command%
|
||||||
|
```
|
||||||
|
|
||||||
|
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.)
|
||||||
|
|
||||||
|
> ✅ Confirmed working in gameplay at 5120×1440 (32:9) on Proton — Hor+ FOV holds through missions.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
## 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).
|
||||||
@@ -0,0 +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).
|
||||||
|
|
||||||
|
## 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**:
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Install (Windows)
|
||||||
|
Extract `GB4_UltraWide_Fix.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`
|
||||||
|
|
||||||
|
Credits: SUWSF by PhantomGamers · Ultimate ASI Loader by ThirteenAG · patterns adapted from Lyall's UltrawidePatches.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Third-Party Components
|
||||||
|
|
||||||
|
This project bundles the following components, redistributed under their
|
||||||
|
respective licenses. This ultrawide fix is a configuration + installer around
|
||||||
|
them; all runtime patching is performed by SUWSF.
|
||||||
|
|
||||||
|
## SUWSF (Somewhat Universal Widescreen Fix)
|
||||||
|
- Author: PhantomGamers
|
||||||
|
- Source: https://github.com/PhantomGamers/SUWSF
|
||||||
|
- License: MIT (see `vendor/SUWSF-x64/LICENSE`)
|
||||||
|
- Files: `vendor/SUWSF-x64/SUWSF.asi`
|
||||||
|
- Version bundled: 2.3.0
|
||||||
|
|
||||||
|
## Ultimate ASI Loader
|
||||||
|
- Author: ThirteenAG
|
||||||
|
- Source: https://github.com/ThirteenAG/Ultimate-ASI-Loader
|
||||||
|
- License: MIT
|
||||||
|
- Files: `vendor/SUWSF-x64/dsound.dll` (renamed proxy; adapts by filename)
|
||||||
|
|
||||||
|
## Patch patterns
|
||||||
|
The `AspectRatioAxisConstraint` (HOR+) and `ConstrainAspectRatio` (pillarbox)
|
||||||
|
byte patterns in `patch/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.
|
||||||
|
|
||||||
|
## Note
|
||||||
|
This project does not modify, crack, or circumvent any game copy protection.
|
||||||
|
SUWSF applies runtime aspect-ratio/FOV adjustments in memory for the benefit of
|
||||||
|
owners playing on ultrawide displays. You must own the game.
|
||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#!/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.
|
||||||
|
# ============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
LOADER_NAME="${LOADER_NAME:-dsound.dll}" # override: LOADER_NAME=version.dll ./install.sh
|
||||||
|
|
||||||
|
# --- locate GB4 shipping exe dir across Steam libraries -----------------------
|
||||||
|
find_gamedir() {
|
||||||
|
local cand
|
||||||
|
for base in \
|
||||||
|
"$HOME/.steam/steam" "$HOME/.local/share/Steam" \
|
||||||
|
"/media/$USER/Data/steam" "/media/$USER"/*/steam \
|
||||||
|
"$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; }
|
||||||
|
done
|
||||||
|
# brute-force fallback
|
||||||
|
cand=$(find /home /media /mnt -maxdepth 8 -name GB4-Win64-Shipping.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"
|
||||||
|
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() {
|
||||||
|
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"
|
||||||
|
[ -f "$cand" ] && { echo "$cand"; return 0; }
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
MARK_BEGIN="; >>> BG4 UltraWide Fix (MaintainYFOV) >>>"
|
||||||
|
MARK_END="; <<< BG4 UltraWide Fix <<<"
|
||||||
|
|
||||||
|
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)."
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_engine_ini() {
|
||||||
|
local ini; ini="$(find_engine_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."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
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"; }
|
||||||
|
done
|
||||||
|
remove_engine_ini
|
||||||
|
echo "[+] Done. (game files untouched; nothing was ever modified in the exe)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${1:-}" = "uninstall" ]; then uninstall; exit 0; fi
|
||||||
|
|
||||||
|
# --- install ------------------------------------------------------------------
|
||||||
|
cp -v "$ASI" "$GAMEDIR/SUWSF.asi"
|
||||||
|
cp -v "$INI" "$GAMEDIR/SUWSF.ini"
|
||||||
|
cp -v "$LOADER_SRC" "$GAMEDIR/$LOADER_NAME"
|
||||||
|
apply_engine_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"
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
[UserSettings]
|
||||||
|
; ==========================================================================
|
||||||
|
; Gundam Breaker 4 - UltraWide Fix (SUWSF patch)
|
||||||
|
; Target display: Samsung 49" Odyssey G93SC (5120x1440, 32:9)
|
||||||
|
; Engine: Unreal Engine 4.27 | Exe is SteamStub-encrypted -> runtime patch
|
||||||
|
; ==========================================================================
|
||||||
|
; Your screen resolution used in patch calculations. "auto" reads primary
|
||||||
|
; display; set explicitly if auto misreads under Proton/DPI scaling.
|
||||||
|
Resolution="5120x1440"
|
||||||
|
; Globally enable or disable all patches (default: true)
|
||||||
|
Enabled=true
|
||||||
|
|
||||||
|
; --------------------------------------------------------------------------
|
||||||
|
; WARNING: PATCHES BELOW. These are CANDIDATE UE4 patterns adapted from
|
||||||
|
; known-good UE4 fixes (Trepang2 / Lies of P). Because GB4's .text is
|
||||||
|
; SteamStub-encrypted on disk we cannot verify the exact bytes statically;
|
||||||
|
; SUWSF scans the DECRYPTED image in memory at runtime, so if GB4's UE4.27
|
||||||
|
; codegen matches these generic patterns they apply directly.
|
||||||
|
;
|
||||||
|
; If a patch reports 0 matches (see SUWSF log / no visual change), run
|
||||||
|
; tools/dump_decrypted.sh while the game is running and send me the dump so
|
||||||
|
; I can derive GB4's exact pattern. Each patch is independent - a miss on one
|
||||||
|
; does not stop the others.
|
||||||
|
; --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
; [1] HOR+ FOV -- the primary fix.
|
||||||
|
; Forces ULocalPlayer AspectRatioAxisConstraint to 0 (AspectRatio_MaintainYFOV).
|
||||||
|
; This makes the camera keep VERTICAL FOV constant and expand horizontally
|
||||||
|
; (Hor+), removing the zoomed-in feel at 32:9. Do NOT use at 16:9 or narrower.
|
||||||
|
; Value BA 00 00 00 00 = "mov edx, 0" ; 90 90 90 = pad nops.
|
||||||
|
[Patch:AspectRatioAxisConstraint]
|
||||||
|
Enabled=true
|
||||||
|
Pattern="0F ?? ?? ?? 0F ?? ?? ?? ?? ?? 41 ?? ?? ?? ?? ?? 00 00 48 ?? ?? ?? ?? 00 00 4C ?? ??"
|
||||||
|
Offset=10
|
||||||
|
Value="BA 00 00 00 00 90 90 90"
|
||||||
|
ValueType="byte"
|
||||||
|
Match="1"
|
||||||
|
Module="auto"
|
||||||
|
|
||||||
|
; [2] Disable pillarboxing/letterboxing (bConstrainAspectRatio = 0).
|
||||||
|
; Prevents the game clamping the render to 16:9 with black side bars.
|
||||||
|
[Patch:ConstrainAspectRatio]
|
||||||
|
Enabled=true
|
||||||
|
Pattern="33 ?? ?? 83 ?? 02 31 ?? ?? 0F ?? ?? ?? ?? 00 00"
|
||||||
|
Offset=0
|
||||||
|
Value="90 90 90 90 90 90"
|
||||||
|
ValueType="byte"
|
||||||
|
Match="1"
|
||||||
|
Module="auto"
|
||||||
|
|
||||||
|
; [3] OPTIONAL fallback: some UE builds hardcode 16:9 (1.777778) as a float
|
||||||
|
; (39 8E E3 3F) or (3B 8E E3 3F) and clamp to it. Disabled by default because
|
||||||
|
; it can double-apply with patch [1]. Enable ONLY if [1] misses and you still
|
||||||
|
; see a squeezed/zoomed image. Writes your true aspect ratio in its place.
|
||||||
|
[Patch:HardcodedAspect_16by9_a]
|
||||||
|
Enabled=false
|
||||||
|
Pattern="39 8E E3 3F"
|
||||||
|
Offset=0
|
||||||
|
Value="aspectratio"
|
||||||
|
ValueType="float"
|
||||||
|
Match="all"
|
||||||
|
Module="auto"
|
||||||
|
|
||||||
|
[Patch:HardcodedAspect_16by9_b]
|
||||||
|
Enabled=false
|
||||||
|
Pattern="3B 8E E3 3F"
|
||||||
|
Offset=0
|
||||||
|
Value="aspectratio"
|
||||||
|
ValueType="float"
|
||||||
|
Match="all"
|
||||||
|
Module="auto"
|
||||||
Executable
+91
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify (or derive) SUWSF AOB patterns against a decrypted GB4 dump.
|
||||||
|
|
||||||
|
- Reads patch/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).
|
||||||
|
- many matches -> pattern too loose; tighten or set Match to a specific index.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 tools/aob.py [dump.bin] [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"
|
||||||
|
INI = sys.argv[2] if len(sys.argv) > 2 else \
|
||||||
|
os.path.join(os.path.dirname(__file__), "..", "patch", "SUWSF.ini")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pattern(pat):
|
||||||
|
"""'0F ?? 3F' -> (regex bytes). ?? = wildcard byte."""
|
||||||
|
out = bytearray()
|
||||||
|
mask = []
|
||||||
|
for tok in pat.split():
|
||||||
|
if tok in ("??", "?"):
|
||||||
|
out.append(0)
|
||||||
|
mask.append(False)
|
||||||
|
else:
|
||||||
|
out.append(int(tok, 16))
|
||||||
|
mask.append(True)
|
||||||
|
# build a regex over raw bytes
|
||||||
|
rx = b""
|
||||||
|
for b, m in zip(out, mask):
|
||||||
|
rx += re.escape(bytes([b])) if m else b"[\\x00-\\xff]"
|
||||||
|
return re.compile(rx, re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def find_all(data, rx, limit=50):
|
||||||
|
hits, pos = [], 0
|
||||||
|
while len(hits) < limit:
|
||||||
|
m = rx.search(data, pos)
|
||||||
|
if not m:
|
||||||
|
break
|
||||||
|
hits.append(m.start())
|
||||||
|
pos = m.start() + 1
|
||||||
|
return hits
|
||||||
|
|
||||||
|
|
||||||
|
def load_patches(ini_path):
|
||||||
|
patches = []
|
||||||
|
cur = None
|
||||||
|
with open(ini_path) as f:
|
||||||
|
for raw in f:
|
||||||
|
line = raw.strip()
|
||||||
|
if line.startswith("[") and "Patch" in line:
|
||||||
|
cur = {"name": line.strip("[]")}
|
||||||
|
patches.append(cur)
|
||||||
|
elif line.startswith("[") :
|
||||||
|
cur = None
|
||||||
|
elif cur is not None and "=" in line and not line.startswith(";"):
|
||||||
|
k, _, v = line.partition("=")
|
||||||
|
cur[k.strip()] = v.strip().strip('"')
|
||||||
|
return patches
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if 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()
|
||||||
|
print(f"[+] dump: {len(data):,} bytes")
|
||||||
|
patches = load_patches(os.path.abspath(INI))
|
||||||
|
print(f"[+] {len(patches)} patches in {os.path.abspath(INI)}\n")
|
||||||
|
for p in patches:
|
||||||
|
pat = p.get("Pattern")
|
||||||
|
if not pat:
|
||||||
|
continue
|
||||||
|
rx = parse_pattern(pat)
|
||||||
|
hits = find_all(data, rx)
|
||||||
|
enabled = p.get("Enabled", "true").lower()
|
||||||
|
status = "OK " if len(hits) == 1 else ("MISS" if not hits else "MANY")
|
||||||
|
print(f"[{status}] {p['name']:32s} enabled={enabled:5s} matches={len(hits)} {pat[:40]}...")
|
||||||
|
for h in hits[:4]:
|
||||||
|
ctx = data[h:h + 24].hex(" ")
|
||||||
|
print(f" @rva 0x{h:x}: {ctx}")
|
||||||
|
print("\nLegend: OK=exactly 1 match (ship it) | MISS=derive new pattern | MANY=tighten/Match=n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/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)
|
||||||
|
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
|
||||||
|
[ -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
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Dump the DECRYPTED GB4-Win64-Shipping.exe 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
|
||||||
|
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
|
||||||
|
# -> writes scratch dump + region metadata, then prints AOB match report.
|
||||||
|
|
||||||
|
If you get "Operation not permitted", either run with sudo, or temporarily:
|
||||||
|
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope (reset to 1 after)
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
|
||||||
|
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
|
||||||
|
|
||||||
|
|
||||||
|
class iovec(ctypes.Structure):
|
||||||
|
_fields_ = [("iov_base", ctypes.c_void_p), ("iov_len", ctypes.c_size_t)]
|
||||||
|
|
||||||
|
|
||||||
|
libc.process_vm_readv.restype = ctypes.c_ssize_t
|
||||||
|
libc.process_vm_readv.argtypes = [ctypes.c_int, ctypes.POINTER(iovec), ctypes.c_ulong,
|
||||||
|
ctypes.POINTER(iovec), ctypes.c_ulong, ctypes.c_ulong]
|
||||||
|
|
||||||
|
|
||||||
|
def find_pid():
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1].isdigit():
|
||||||
|
return int(sys.argv[1])
|
||||||
|
for p in glob.glob("/proc/[0-9]*"):
|
||||||
|
try:
|
||||||
|
with open(f"{p}/cmdline", "rb") as f:
|
||||||
|
cl = f.read().replace(b"\0", b" ").decode("utf-8", "replace")
|
||||||
|
if MODULE_HINT in cl:
|
||||||
|
return int(os.path.basename(p))
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def read_mem(pid, addr, size):
|
||||||
|
buf = ctypes.create_string_buffer(size)
|
||||||
|
local = iovec(ctypes.cast(buf, ctypes.c_void_p), size)
|
||||||
|
remote = iovec(ctypes.c_void_p(addr), size)
|
||||||
|
n = libc.process_vm_readv(pid, ctypes.byref(local), 1, ctypes.byref(remote), 1, 0)
|
||||||
|
if n < 0:
|
||||||
|
# fallback: /proc/pid/mem pread
|
||||||
|
try:
|
||||||
|
with open(f"/proc/{pid}/mem", "rb", 0) as m:
|
||||||
|
m.seek(addr)
|
||||||
|
return m.read(size)
|
||||||
|
except OSError as e:
|
||||||
|
raise OSError(f"read fail @0x{addr:x}: {os.strerror(ctypes.get_errno())} / {e}")
|
||||||
|
return buf.raw[:n]
|
||||||
|
|
||||||
|
|
||||||
|
def module_regions(pid):
|
||||||
|
"""Return list of (start,end,perms,path) VMAs backed by the module exe."""
|
||||||
|
regs = []
|
||||||
|
modpath = None
|
||||||
|
with open(f"/proc/{pid}/maps") as f:
|
||||||
|
for line in f:
|
||||||
|
m = re.match(r"([0-9a-f]+)-([0-9a-f]+) (\S{4}) \S+ \S+ \S+ *(.*)", line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
start, end, perms, path = int(m[1], 16), int(m[2], 16), m[3], m[4]
|
||||||
|
if MODULE_HINT in path:
|
||||||
|
if modpath is None:
|
||||||
|
modpath = path
|
||||||
|
regs.append((start, end, perms, path))
|
||||||
|
return modpath, regs
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
pid = find_pid()
|
||||||
|
if not pid:
|
||||||
|
print(f"[!] No running process matching '{MODULE_HINT}'. Launch the game first.")
|
||||||
|
sys.exit(1)
|
||||||
|
print(f"[+] pid = {pid}")
|
||||||
|
modpath, regs = module_regions(pid)
|
||||||
|
if not regs:
|
||||||
|
print("[!] Module not mapped yet. Reach the main menu, then retry.")
|
||||||
|
sys.exit(1)
|
||||||
|
base = min(r[0] for r in regs)
|
||||||
|
print(f"[+] module base = 0x{base:x} ({modpath})")
|
||||||
|
os.makedirs(OUTDIR, exist_ok=True)
|
||||||
|
meta = {"pid": pid, "module": modpath, "base": base, "regions": []}
|
||||||
|
blob = bytearray()
|
||||||
|
for start, end, perms, path in regs:
|
||||||
|
size = end - start
|
||||||
|
try:
|
||||||
|
data = read_mem(pid, start, size)
|
||||||
|
except OSError as e:
|
||||||
|
print(f" skip 0x{start:x}-0x{end:x} {perms}: {e}")
|
||||||
|
continue
|
||||||
|
rva = start - base
|
||||||
|
# pad blob so file offset == rva (sparse-ish, capped)
|
||||||
|
if rva >= 0 and rva < 0x20000000:
|
||||||
|
if len(blob) < rva:
|
||||||
|
blob.extend(b"\x00" * (rva - len(blob)))
|
||||||
|
blob[rva:rva + len(data)] = data
|
||||||
|
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")
|
||||||
|
with open(dump_path, "wb") as f:
|
||||||
|
f.write(blob)
|
||||||
|
with open(os.path.join(OUTDIR, "gb4_dump_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')}")
|
||||||
|
|
||||||
|
# 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)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+78
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Locate FMinimalViewInfo::CalculateProjectionMatrixGivenView in GB4-Win64-Shipping.exe.
|
||||||
|
|
||||||
|
Fingerprint: the function computes max(0.001f, FOV) * (PI/360) — so it
|
||||||
|
references both float constants 0.001f and pi/360 within a short span.
|
||||||
|
We find all RIP-relative references to those constants and cluster them.
|
||||||
|
"""
|
||||||
|
import pefile, struct, math, sys
|
||||||
|
|
||||||
|
EXE = sys.argv[1] if len(sys.argv) > 1 else \
|
||||||
|
"/media/ckoch/Data/steam/steamapps/common/GBBBB/GB4/Binaries/Win64/GB4-Win64-Shipping.exe"
|
||||||
|
|
||||||
|
pe = pefile.PE(EXE, fast_load=True)
|
||||||
|
base = pe.OPTIONAL_HEADER.ImageBase
|
||||||
|
|
||||||
|
sections = {}
|
||||||
|
for s in pe.sections:
|
||||||
|
name = s.Name.rstrip(b"\0").decode()
|
||||||
|
sections[name] = (s.VirtualAddress, s.SizeOfRawData, s.get_data())
|
||||||
|
print(f"section {name:8s} va=0x{base+s.VirtualAddress:x} rawsize=0x{s.SizeOfRawData:x}")
|
||||||
|
|
||||||
|
text_rva, _, text = sections[".text"]
|
||||||
|
|
||||||
|
# --- find constant locations anywhere outside .text (rdata usually) ---
|
||||||
|
consts = {
|
||||||
|
"0.001f": struct.pack("<f", 0.001),
|
||||||
|
"pi/360": struct.pack("<f", math.pi / 360),
|
||||||
|
}
|
||||||
|
const_vas = {k: [] for k in consts}
|
||||||
|
for name, (rva, size, data) in sections.items():
|
||||||
|
if name == ".text":
|
||||||
|
continue
|
||||||
|
for key, pat in consts.items():
|
||||||
|
off = -1
|
||||||
|
while True:
|
||||||
|
off = data.find(pat, off + 1)
|
||||||
|
if off < 0:
|
||||||
|
break
|
||||||
|
va = base + rva + off
|
||||||
|
# only aligned-ish hits to cut noise
|
||||||
|
const_vas[key].append(va)
|
||||||
|
for k, v in const_vas.items():
|
||||||
|
print(f"{k}: {len(v)} occurrences in data sections")
|
||||||
|
|
||||||
|
# --- find rip-relative references in .text to any of those VAs ---
|
||||||
|
targets = {}
|
||||||
|
for k, vas in const_vas.items():
|
||||||
|
for va in vas:
|
||||||
|
targets.setdefault(va, k)
|
||||||
|
|
||||||
|
refs = [] # (text_off, va, key)
|
||||||
|
tbase = base + text_rva
|
||||||
|
n = len(text)
|
||||||
|
for i in range(n - 4):
|
||||||
|
disp = struct.unpack_from("<i", text, i)[0]
|
||||||
|
va = tbase + i + 4 + disp
|
||||||
|
k = targets.get(va)
|
||||||
|
if k:
|
||||||
|
refs.append((i, va, k))
|
||||||
|
print(f"total rip refs to candidate consts: {len(refs)}")
|
||||||
|
|
||||||
|
# cluster: find 0.001f refs with a pi/360 ref within 0x100 bytes
|
||||||
|
refs.sort()
|
||||||
|
by_key = {}
|
||||||
|
for off, va, k in refs:
|
||||||
|
by_key.setdefault(k, []).append(off)
|
||||||
|
|
||||||
|
import bisect
|
||||||
|
p360 = by_key.get("pi/360", [])
|
||||||
|
hits = []
|
||||||
|
for off in by_key.get("0.001f", []):
|
||||||
|
j = bisect.bisect_left(p360, off - 0x100)
|
||||||
|
while j < len(p360) and p360[j] < off + 0x100:
|
||||||
|
hits.append((off, p360[j]))
|
||||||
|
j += 1
|
||||||
|
print(f"\nclustered candidates (0.001f + pi/360 within 0x100):")
|
||||||
|
for a, b in hits:
|
||||||
|
print(f" 0.001f@text+0x{a:x} (va 0x{tbase+a:x}) pi/360@text+0x{b:x} (va 0x{tbase+b:x})")
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Reads the newest Proton log for GB4 (appid 1672500) and extracts SUWSF + DLL-load evidence.
|
||||||
|
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; }
|
||||||
|
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
|
||||||
|
echo "=== SUWSF patch results ==="
|
||||||
|
grep -iE "Searching for patches|Found patch|patches found| matches|No pattern found|skipping patch|Patches disabled|INITIALIZED" "$LOG" | head -60
|
||||||
|
echo "=== (if both sections empty, the ASI did not inject: check WINEDLLOVERRIDES) ==="
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 PhantomGamers
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
Vendored
+113
@@ -0,0 +1,113 @@
|
|||||||
|
# SUWSF
|
||||||
|
|
||||||
|
Somewhat Universal Widescreen Fix is intended to enable widescreen aspect ratios (e.g. 21:9, 32:9, 48:9) in games where it is unsupported.
|
||||||
|
|
||||||
|
## WARNING IT IS NOT RECOMMENDED TO USE THIS FIX IN GAMES THAT CONTAIN ANTICHEAT
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- **Ensure you have the latest [64bit VC Redist](https://aka.ms/vs/17/release/vc_redist.x64.exe) or [32bit VC Redist](https://aka.ms/vs/17/release/vc_redist.x86.exe) installed.**
|
||||||
|
- Download [the latest release](https://github.com/phantomgamers/suwsf/releases/latest)
|
||||||
|
- For 32bit (x86) games download SUWSF-x86.zip, for 64bit (x86_64) games download SUWSF-x64.zip.
|
||||||
|
- Extract it so that `SUWSF.asi`, `SUWSF.ini`, and `dsound.dll` are in the same folder as the game executable that you are trying to fix.
|
||||||
|
- If `dsound.dll` would collide with a file included in the game try a different name on [this list](https://github.com/ThirteenAG/Ultimate-ASI-Loader#description) which is marked as supporting the architecture (x86 or x64) of the game you are trying to fix.
|
||||||
|
|
||||||
|
Note that it must be alongside the actual game executable, it will not work if it is only placed alongside a game's launcher.
|
||||||
|
|
||||||
|
## Uninstall
|
||||||
|
|
||||||
|
To uninstall, delete the `SUWSF.asi`, `SUWSF.ini`, and `dsound.dll` files that you extracted to the game executable location.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
In `SUWSF.ini` you can configure the following settings:
|
||||||
|
|
||||||
|
### **[UserSettings]**
|
||||||
|
|
||||||
|
A group of settings specific to the user.
|
||||||
|
|
||||||
|
#### **Resolution**
|
||||||
|
|
||||||
|
The user's screen resolution that should be used in patch calculations.
|
||||||
|
|
||||||
|
Valid options: A screen resolution (e.g. "3440x1440"), "auto" (automatically detect from primary screen, may be affected by DPI scaling)
|
||||||
|
Default: "auto"
|
||||||
|
|
||||||
|
### **[Patch]**
|
||||||
|
|
||||||
|
A group of settings related to a patch. Patches can optionally have names (e.g. `[Patch:AspectRatio]` or `[Patch:FOV]`). Names do not matter as long as they contain the word `Patch`.
|
||||||
|
As many patch groups as needed can be used.
|
||||||
|
|
||||||
|
#### **Enabled**
|
||||||
|
|
||||||
|
Whether a patch should be enabled. Use this for optional patches that users can choose to enable or disable.
|
||||||
|
|
||||||
|
Valid options: "true", "false"
|
||||||
|
Default: "true"
|
||||||
|
|
||||||
|
#### **Pattern**
|
||||||
|
|
||||||
|
This should be set to a pattern that matches the bytes you would like to patch.
|
||||||
|
|
||||||
|
Commonly this will be 16/9 in bytes, which can either be `39 8E E3 3F` or `3B 8E E3 3F` depending on the game.
|
||||||
|
|
||||||
|
Wildcards are supported for bytes that can vary between game versions (e.g. `39 8E ?? 3F`).
|
||||||
|
|
||||||
|
#### **Offset**
|
||||||
|
|
||||||
|
The offset from the pattern match that should be written. Use this if you need to include bytes before the thing you are trying to patch.
|
||||||
|
e.g. to get to `39` in `90 90 39 8E E3 3F` set to 2
|
||||||
|
|
||||||
|
Default: 0
|
||||||
|
|
||||||
|
#### **Value**
|
||||||
|
|
||||||
|
The value to write to the matched bytes.
|
||||||
|
|
||||||
|
If `ValueType` is `float` then this can contain numbers or included variables. This can also contain an expression!
|
||||||
|
If `ValueType` is `byte` then this should contain an array of bytes! e.g. `90 90 90 90`
|
||||||
|
|
||||||
|
Included variables:
|
||||||
|
`aspectratio` - This will be replaced with the user's screen's aspect ratio.
|
||||||
|
`width` - This will be replaced with the user's screen's width.
|
||||||
|
`height` - This will be replaced with the user's screen's height.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
`Value="width/height"`
|
||||||
|
|
||||||
|
#### **ValueType**
|
||||||
|
|
||||||
|
What type the Value setting contains.
|
||||||
|
|
||||||
|
Valid options: "float", "int", "byte"
|
||||||
|
Default: `float`
|
||||||
|
|
||||||
|
#### **Match**
|
||||||
|
|
||||||
|
Which match should be targeted. Useful for cases where a pattern can have multiple matches but only one should be changed.
|
||||||
|
|
||||||
|
Valid options: number of match (starting from 1), "last", "all"
|
||||||
|
**Note that `last` might match different bytes than the last match in a hex editor.** This is because SUWSF works from memory while a hex editor works on the file on disk.
|
||||||
|
Default: "all"
|
||||||
|
|
||||||
|
|
||||||
|
#### **Module**
|
||||||
|
|
||||||
|
Which file should be targeted by patch. Useful for cases where the patch needs to be applied to a file other than the main game executable. (e.g. "UnityPlayer.dll")
|
||||||
|
|
||||||
|
Valid options: "auto" (detects main game executable name), any string.
|
||||||
|
Default: "auto"
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you get an error such as "Unable to load SUWSF.asi. Error:126", ensure you have the latest [64bit VC Redist](https://aka.ms/vs/17/release/vc_redist.x64.exe) or [32bit VC Redist](https://aka.ms/vs/17/release/vc_redist.x86.exe) installed depending on which version of SUWSF you are trying to use.
|
||||||
|
|
||||||
|
If this does not work for you, try renaming `dsound.dll` to `dinput8.dll` or any other name on [this list](https://github.com/ThirteenAG/Ultimate-ASI-Loader#description) and see if the problem remains.
|
||||||
|
|
||||||
|
`version.dll` is a good alternative to try.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- [@ThirteenAG](https://github.com/ThirteenAG) for [Hooking.Patterns](https://github.com/ThirteenAG/Hooking.Patterns), [IniReader](https://github.com/ThirteenAG/IniReader) and [UltimateASILoader](https://github.com/ThirteenAG/Ultimate-ASI-Loader)
|
||||||
|
- [@sergey-shandar](https://github.com/sergey-shandar) for [getboost](https://github.com/sergey-shandar/getboost)
|
||||||
|
- [@codeplea](https://github.com/codeplea) for [tinyexpr](https://github.com/codeplea/tinyexpr)
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
[UserSettings]
|
||||||
|
; Set to your screen resolution (e.g. "3440x1440") (default: "auto")
|
||||||
|
Resolution="auto"
|
||||||
|
; Globally enable or disable patches (default: true)
|
||||||
|
Enabled=true
|
||||||
|
|
||||||
|
; WARNING: PATCHES BELOW. DO NOT MODIFY UNLESS YOU KNOW WHAT YOU ARE DOING.
|
||||||
|
; Normally users should not need to edit this
|
||||||
|
|
||||||
|
; This is the default example patch but any number of these can be added and named anything as long as they contain the word "Patch"
|
||||||
|
[Patch:AspectRatio]
|
||||||
|
; Whether this patch should be enabled (default: true)
|
||||||
|
Enabled=true
|
||||||
|
; Byte pattern to search for. E.g. "39 8E E3 3F" (16/9 as float). Wildcards accepted (use ?? for bytes that can change)
|
||||||
|
Pattern="39 8E E3 3F"
|
||||||
|
; Offset in number of bytes to target. e.g. to get to "39" in "90 90 39 8E E3 3F" set to 2 (default: 0)
|
||||||
|
Offset=0
|
||||||
|
; Value to write. Numbers or variables (aspectratio, width, height) are accepted. Bytes are accepted if ValueType="byte"
|
||||||
|
Value="aspectratio"
|
||||||
|
; Type of value. Accepted values are: "float", "double", "int", "byte" (default: "float")
|
||||||
|
ValueType="float"
|
||||||
|
; Which match to write to. Accepted values are: number of match (starting from 1), last, all. (default: "all")
|
||||||
|
Match="all"
|
||||||
|
; Which file to search for pattern in. "auto" detects process name automatically (default: "auto")
|
||||||
|
Module="auto"
|
||||||
Vendored
BIN
Binary file not shown.
Reference in New Issue
Block a user