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>
This commit is contained in:
+7
-8
@@ -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
@@ -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"
|
||||
|
||||
@@ -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__":
|
||||
|
||||
+10
-8
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user