21 Commits
Author SHA1 Message Date
ckochandClaude Opus 4.8 1176df8f20 Add CHANGELOG; require an explicit release TAG
Adding a game is a minor bump, so the collection is at v1.2.0: v1.0.0 shipped
GB4, Burglin' Gnomes was a minor bump that was never cut, and Expedition 33 is
the second. Skipping the unreleased 1.1.0 reflects the real history better than
collapsing two games' worth of additions into one bump.

publish.sh no longer defaults TAG to v1.0.0, which could silently re-cut the
existing release; it now requires TAG explicitly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:23:23 -04:00
ckochandClaude Opus 4.8 15d0169f56 publish.sh: one release for the whole collection
GAME now accepts a space-separated list and defaults to 'all', building and
attaching every game's zip to a single release instead of one game per tag -
which matches the repo being a collection. Release body is the game's own
README for a single game, or the collection README for several.

Asset upload replaces same-named assets, so re-running is idempotent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:16:54 -04:00
ckochandClaude Opus 4.8 6f24502956 Expedition 33: record version drift honestly, ship reduced profile
Running ClairObscurFix v0.0.10 with upstream defaults crashed on save load.
Removing it entirely let the game load normally, isolating the fix as the cause
rather than Proton or the year-old save. The fix is archived and predates the
current build; two pattern scans already failed outright, and the crash
coincides with its Level Load hook.

Ship a reduced profile with only Center HUD enabled - the one option actually
needed at 32:9 - and mark the game PARTIAL rather than implying full support.
Adds a revalidation checklist since vendored archived fixes decay as the game
patches, plus a note that the game has no DRM, so a dedicated HUD-centering
patch is a viable replacement if Center HUD eventually breaks too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 17:15:12 -04:00
ckochandClaude Opus 4.8 0833cf8495 Add Clair Obscur: Expedition 33 via new METHOD=external
Expedition 33 is UE5 with no DRM, but a mature MIT-licensed fix already exists:
ClairObscurFix by Lyall. Rather than reimplement it, install.sh gains a third
method that vendors a third-party fix unmodified and layers our own tuned
config on top, with the upstream license shipped in the release zip as MIT
requires. Upstream is archived, so vendoring also keeps it available.

Our only change vs upstream defaults is [Center HUD] Enabled = true, since at
5120px wide the edge-anchored HUD elements end up absurdly far apart.

Install/uninstall round-trip verified against a scratch game dir, including
that our tuned ini overrides the vendored default.

Credit to Lyall is recorded in THIRD_PARTY.md, the game README, and the repo
README's method table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:58:05 -04:00
ckochandClaude Opus 4.8 efaab8bd32 Fix release pipeline: bepinex packaging + no silent failures
'GAME=BurglinGnomes ./tools/publish.sh' exited with zero output. Two causes:

1. build_release.sh was SUWSF-only and hard-failed on a missing SUWSF.ini for
   BepInEx games. It now reads METHOD from game.conf and packages BepInEx plus
   the prebuilt plugin instead.
2. publish.sh ran the build with stdout to /dev/null, so that error was
   invisible and  killed the script silently. Build output is now shown
   and failure reported explicitly.

Also: publish.sh creates the tag locally when missing (v1.1.0 never existed, so
the tag push would have failed too), and defaults the release body to the
game's own README so per-game notes are always accurate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:40:41 -04:00
ckochandClaude Opus 4.8 b97a8a73b1 Credit BepInEx/UnityExplorer, fix stale README refs
BepInEx install/uninstall verified end-to-end against a scratch game dir:
installs loader + plugin, uninstall removes both and leaves BepInEx/ intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:34:34 -04:00
ckochandClaude Opus 4.8 59ee13de9d Burglin' Gnomes: finalize fix, docs, and BepInEx install support
Final tuned values baked in as defaults: HudScale=0.8, WorldLabelScale=0.8.

- install.sh gains METHOD=bepinex: installs BepInEx from vendor + the prebuilt
  plugin, with a matching uninstall that leaves BepInEx/ alone (other mods may
  depend on it). Success banner is now method-aware.
- Ship the prebuilt plugin DLL so users don't need a .NET SDK.
- Per-game README documenting why the camera is deliberately untouched
  (orthographic, constant orthographicSize = already Hor+), the two-canvas UI
  problem, tuning, and where the plugin log actually lives.
- Root README lists both games and both fix methods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:33:56 -04:00
ckochandClaude Opus 4.8 ed2e0016ba Plugin: never cache a zero baseline for world-space canvases
The in-game log showed 'World Canvas localScale (0,0,0) -> (0,0,0)'. Games hide
world-space UI by zeroing its scale; if that zero got cached as the baseline,
multiplying it would pin the canvas to zero permanently and the UI could never
reappear. Now skips canvases that are currently zeroed - both when capturing the
baseline and when applying - so hidden UI is left alone and picked up once the
game shows it again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:25:27 -04:00
ckochandClaude Opus 4.8 40d057ac6e Plugin: reload config live and always log HUD changes
BepInEx does not pick up external edits to a plugin's .cfg, so tuning values
while the game ran silently did nothing - the plugin kept the values loaded at
startup. Now watches the config file's mtime and calls Config.Reload() when it
changes, so edits apply within ~2s with no restart. Reload is wrapped in
try/catch so a malformed .cfg warns instead of spamming exceptions.

HUD adjustments now always log (not just under Verbose) with the resulting
scale, so it is obvious whether a value took effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:18:05 -04:00
ckochandClaude Opus 4.8 cbcc53c2e9 Add Burglin' Gnomes BepInEx ultrawide UI plugin
Config-driven so values are tunable without recompiling or pasting console
scripts. Camera is deliberately untouched - it is orthographic with constant
orthographicSize, which is already correct Hor+ at 32:9.

Fixes the two UI families separately, each via the lever that actually works:
- HUD (ScreenSpaceOverlay/ScaleWithScreenSize): match=1 + referenceResolution.y
  raised by HudScale, shrinking the HUD while giving the layout more room.
- World labels (WorldSpace): canvas transform localScale, because
  CanvasScaler.scaleFactor is overwritten by HandleWorldCanvas every frame.

Baselines are cached per canvas so periodic re-apply never compounds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:08:11 -04:00
ckochandClaude Opus 4.8 b9e68fd284 UI tuner: fix stale readback and World-Space label lever
Two defects in the tuner, both visible in the in-game logs:

1. AFTER lines read Canvas.scaleFactor immediately after writing, but that is
   recomputed in CanvasScaler.Update(), so it reported last frame's value
   (showed 1.3333 after ref was set to 1350). Now prints the mathematically
   expected scale instead.

2. WORLD_SCALE set CanvasScaler.scaleFactor on WorldSpace canvases, which
   HandleWorldCanvas() overwrites with dynamicPixelsPerUnit every frame - the
   log showed 'APPLIED scaleFactor 1 -> 0.8' followed by effectiveScale=1.0.
   World-space label size is driven by the canvas transform localScale, so it
   now scales that, stashing a baseline child so repeated runs don't compound.

Also documents that MATCH must stay 1.0: match .8 + ref 1350 yields 1.281 vs
1.067 at match 1, largely cancelling the shrink.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:04:56 -04:00
ckochandClaude Opus 4.8 ae994c27e6 Bake real test values into UI tuner defaults
Pasting the file verbatim was a no-op because the knobs shipped at 1.0, so
every run reported KNOBS ...=1 and changed nothing. Default to HUD_SCALE=0.85
and WORLD_SCALE=0.8 so copying the file straight from the editor actually
exercises the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:00:45 -04:00
ckochandClaude Opus 4.8 4cd18fb9f4 UI tuner: handle both canvas modes, echo knob values
In-game capture revealed two canvas families: ScreenSpaceOverlay/
ScaleWithScreenSize (the HUD, ref 1920x1080 match=1) and WorldSpace/
ConstantPixelSize (NamePlate and friends). Unity ignores referenceResolution
in ConstantPixelSize mode, so the old HUD_SCALE knob could never affect the
in-world labels. Split into HUD_SCALE (referenceResolution) and WORLD_SCALE
(scaleFactor), applied per mode.

Also echoes the knob values and logs APPLIED lines, so a stale console edit is
immediately obvious instead of looking like the fix did nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:58:37 -04:00
ckochandClaude Opus 4.8 bd9fdc50f6 Fix UI tuner: scaleFactor is ignored in ScaleWithScreenSize
CanvasScaler.scaleFactor only applies to ConstantPixelSize mode, so the old
SCALE_MULT knob would have done nothing. Replaced with HUD_SCALE, implemented
via referenceResolution.y (effective scale = screenH/refH), which genuinely
shrinks the HUD while giving the layout MORE logical room - the right lever
for overlapping text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:52:19 -04:00
ckochandClaude Opus 4.8 3525c79dba Burglin' Gnomes: camera confirmed correct, add live UI tuner
orthographicSize is constant at 5.0 across 1920x1080 and 5120x1440 (visible
height 10.0, width 17.8 -> 35.6). The camera is already textbook Hor+ and needs
no patch - the entire ultrawide problem is HUD scaling.

CanvasScaler match=0.453 gave scaleFactor 1.948 (ballooned HUD); match=1 gives
1.333 with logical height exactly 1080 (the design height). Adds a live tuner
with independent match / scaleFactor / referenceResolution knobs to chase the
remaining text overlap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:51:38 -04:00
ckochandClaude Opus 4.8 8bbc1cfac8 Correct Burglin' Gnomes diagnosis: camera is ORTHOGRAPHIC
fieldOfView is meaningless here (reads 60 at every resolution because nothing
uses it). The governing value is orthographicSize. Diagnostic now reports
orthoSize plus computed visible width/height, and checks Cinemachine lens
ortho size too.

Also confirmed the HUD cause: CanvasScaler match=0.453 (width-biased) with a
1920x1080 reference, which balloons the HUD at 5120px wide. Added a live
fix-test script to try match=1 and an orthoSize override in-game.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:31:05 -04:00
ckochandClaude Opus 4.8 e21c6c3c44 Add UnityExplorer camera/UI diagnostic script
Reflection-based so it compiles across Cinemachine versions. Dumps cameras,
Cinemachine lens FOV, and CanvasScaler settings to identify what drives FOV
and what breaks the HUD at ultrawide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:06:02 -04:00
ckochandClaude Opus 4.8 b6ab1bf5ab Scaffold Burglin' Gnomes (Unity 6 Mono) as second target
Recon: Unity 6000.0.62f1, Mono backend (readable C# assemblies), URP +
Cinemachine, no DRM. No FOV logic in game code, so the horizontal-FOV lock is
a serialized camera setting - needs runtime inspection via BepInEx/UnityExplorer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 13:16:18 -04:00
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
35 changed files with 1337 additions and 163 deletions
+5
View File
@@ -10,6 +10,11 @@ __pycache__/
/vendor/SUWSF-x64.zip
# Runtime dumps / scratch
/dumps/
gb4dump/
*.bin
*.bak-bg4uw
# .NET plugin build output
**/plugin/bin/
**/plugin/obj/
+52
View File
@@ -0,0 +1,52 @@
# Changelog
Versioning applies to the collection as a whole: **adding a game is a minor bump**,
fixes/tuning to existing entries are patch bumps.
## v1.2.0 — Clair Obscur: Expedition 33
- **New game: Clair Obscur: Expedition 33** (Unreal Engine 5, no DRM) — ⚠️ partial support.
Adds a third install method, `external`: where a good third-party fix already
exists and its license allows redistribution, it is vendored **unmodified**
with our tuned config and full credit, rather than reimplemented.
The fix itself is [ClairObscurFix](https://github.com/Lyall/ClairObscurFix)
by **Lyall** (MIT).
- Ships a **reduced profile** (only `Center HUD` enabled). Upstream v0.0.10 is
archived and predates current game builds; with full features it crashed on
save load. Documented with a revalidation checklist rather than marked working.
- `publish.sh` now cuts **one release for the whole collection**, attaching every
game's zip, and replaces same-named assets so re-runs are idempotent.
## v1.1.0 — Burglin' Gnomes *(never released; folded into v1.2.0)*
- **New game: Burglin' Gnomes** (Unity 6, Mono) — ✅ confirmed working at 5120×1440.
Adds the `bepinex` install method and a purpose-built C# plugin.
- Camera deliberately untouched: it is orthographic with a constant
`orthographicSize`, which is already correct Hor+ at 32:9. The entire problem
was UI scaling.
- Fixes HUD canvases (`match=1` + raised `referenceResolution.y`) and world-space
labels (canvas `localScale``CanvasScaler.scaleFactor` is overwritten every
frame for WorldSpace canvases).
- Config reloads live (~2s) without a game restart.
## v1.0.0 — Gundam Breaker 4
- **New game: Gundam Breaker 4** (Unreal Engine 4.27, SteamStub DRM) — ✅ confirmed
working at 5120×1440.
- Forces Hor+ FOV via `AspectRatioAxisConstraint = MaintainYFOV` and disables
pillarboxing, patched in decrypted memory at runtime with
[SUWSF](https://github.com/PhantomGamers/SUWSF) — the exe is DRM-encrypted on
disk, so static patching is impossible.
---
### Unreleased infrastructure changes
These landed between v1.0.0 and v1.2.0 and are not tied to a single game:
- Restructured from a single-game repo into a multi-game collection
(`games/<Game>/` + `game.conf`), so `install.sh` is game-agnostic.
**Note:** `./install.sh` now requires a game name; run it bare to list games.
- Migrated hosting from GitHub to self-hosted Gitea.
- `publish.sh` rewritten for the Gitea API; reads `GITEA_TOKEN` from the
environment and never stores it.
+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.
+53 -47
View File
@@ -1,66 +1,72 @@
# 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
Fixes use whichever approach the engine calls for:
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.
- **SUWSF + Ultimate ASI Loader** — runtime memory patching, for Unreal titles (and anything DRM-wrapped, where static patching is impossible).
- **BepInEx plugin** — a proper C# mod, for Unity titles with the Mono backend.
- **External** — where a good third-party fix already exists, it is vendored (license permitting) with our tuned config and full credit, rather than reimplemented. Vendored fixes can go stale against a patched game, so those entries carry a revalidation checklist.
## How it works (and why it has to work this way)
Most force **Hor+ FOV** (a wider screen shows you *more* rather than zooming in) and fix UI that assumes 16:9. Some games already render correctly and only need UI work — [Burglin' Gnomes](games/BurglinGnomes/) is one, and the fix deliberately leaves its camera alone.
- 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.
## Supported games
## Install (Linux / Proton)
| Game | Engine | Method | Status |
|---|---|---|---|
| [Gundam Breaker 4](games/GundamBreaker4/) | Unreal Engine 4.27 | SUWSF | ✅ Confirmed working at 5120×1440 |
| [Burglin' Gnomes](games/BurglinGnomes/) | Unity 6 (Mono) | BepInEx plugin | ✅ Confirmed working at 5120×1440 |
| [Clair Obscur: Expedition 33](games/Expedition33/) | Unreal Engine 5 | External ([ClairObscurFix](https://github.com/Lyall/ClairObscurFix) by Lyall) | ⚠️ Partial — reduced profile, see notes |
## 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` | Patch definitions (SUWSF games) |
| `games/<Game>/plugin/` | C# plugin source + prebuilt DLL (BepInEx games) |
| `games/<Game>/*.ini` | Our tuned config for a vendored external fix |
| `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 Gitea 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 |
| `tools/unity_diag.cs`, `tools/unity_uitune.cs` | UnityExplorer console diagnostics/tuners |
| `vendor/SUWSF-x64/` | Upstream SUWSF 2.3.0 + Ultimate ASI Loader (MIT) |
| `vendor/BepInEx_win_x64_*.zip` | Upstream BepInEx 5.4.23.5 (LGPL-2.1) |
## 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. Set `METHOD=` in `game.conf` to `suwsf`, `bepinex`, or `external`.
**Check for an existing fix first** — if a good one exists and its license
allows redistribution, vendor it with credit instead of rewriting it.
3. **Unreal:** start from the UE4 patterns in [`games/GundamBreaker4/SUWSF.ini`](games/GundamBreaker4/SUWSF.ini) — they match across many UE4 titles. If a pattern misses, use `tools/dump_decrypted.py` + `tools/aob.py` to derive the exact bytes.
4. **Unity:** install BepInEx + UnityExplorer and inspect live with [`tools/unity_diag.cs`](tools/unity_diag.cs) / [`tools/unity_uitune.cs`](tools/unity_uitune.cs), then write a plugin (copy `games/BurglinGnomes/plugin/`).
## Changelog
Version history and the versioning policy (adding a game = minor bump) are in [CHANGELOG.md](CHANGELOG.md).
## 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.
+24 -1
View File
@@ -17,9 +17,32 @@ them; all runtime patching is performed by SUWSF.
- License: MIT
- Files: `vendor/SUWSF-x64/dsound.dll` (renamed proxy; adapts by filename)
## BepInEx
- Author: BepInEx contributors
- Source: https://github.com/BepInEx/BepInEx
- License: LGPL-2.1
- Files: `vendor/BepInEx_win_x64_5.4.23.5.zip` (redistributed unmodified)
- Used by Unity games (e.g. Burglin' Gnomes) to load the C# fix plugin.
## UnityExplorer (development only)
- Author: sinai-dev
- Source: https://github.com/sinai-dev/UnityExplorer
- License: GPL-3.0
- Files: `vendor/UnityExplorer.BepInEx5.Mono.zip`
- Used only for runtime inspection while developing a fix; it is NOT part of any
release package and is not required by end users.
## ClairObscurFix (the entire Expedition 33 fix)
- Author: **Lyall** — https://github.com/Lyall/ClairObscurFix
- License: MIT (see `vendor/ClairObscurFix/LICENSE`)
- Files: `vendor/ClairObscurFix/**` — vendored **unmodified**
- Version: v0.0.10 (upstream repo is archived)
- The ultrawide fix for Clair Obscur: Expedition 33 is entirely Lyall's work.
This repo contributes only a 32:9-tuned `ClairObscurFix.ini` and packaging.
## 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.
Binary file not shown.
+98
View File
@@ -0,0 +1,98 @@
# Burglin' Gnomes — UltraWide Fix
**Confirmed working at 5120×1440 (32:9) on Proton.**
- **Engine:** Unity 6 (6000.0.62f1), Mono backend, URP + Cinemachine
- **Steam AppID:** 3844970
- **Executable:** `Gnomium.exe`
- **Method:** BepInEx plugin (no DRM, no memory patching needed)
## What it fixes — and what it deliberately doesn't
**The camera is already correct.** It's *orthographic* with a constant `orthographicSize` of 5.0, so a wider display genuinely shows more world (visible width 17.8 → 35.6 going 16:9 → 32:9). That's textbook Hor+, and this fix does not touch it. `fieldOfView` reads 60 at every resolution simply because orthographic cameras don't use it — a red herring if you go looking.
**The problem is entirely UI**, in two separate places needing two different levers:
| Canvas | Problem | Lever |
|---|---|---|
| HUD (`ScreenSpaceOverlay` + `ScaleWithScreenSize`) | Ships `matchWidthOrHeight = 0.453` (width-biased). At 5120px that yields scaleFactor **1.948** — a ballooned HUD with overlapping bottom/centre text. | `match = 1` (scale by height) + raise `referenceResolution.y`, shrinking the HUD *and* giving the layout more logical room |
| In-world labels (`WorldSpace`, e.g. `NamePlate`) | At 32:9 you see twice as much world, so far more labels are on screen and can collide | Canvas `transform.localScale` |
> **Gotcha:** `CanvasScaler.scaleFactor` does nothing on a WorldSpace canvas — `CanvasScaler.HandleWorldCanvas()` overwrites it with `dynamicPixelsPerUnit` every frame. Scaling the transform is the only thing that works.
## Install (Linux / Proton)
```bash
./install.sh BurglinGnomes
```
Then **required** — Steam → Properties → Launch Options:
```
WINEDLLOVERRIDES="winhttp=n,b" %command%
```
Without it Proton ignores BepInEx's `winhttp.dll` loader and the plugin never runs.
## Install (Windows)
Extract the release zip into the game folder (next to `Gnomium.exe`). No launch option needed.
## Tuning
Config is generated on first launch:
```
<game>/BepInEx/config/com.programmingpug.burglingnomes.ultrawidefix.cfg
```
**Edits apply live (~2s) — no restart.** BepInEx doesn't reload config on its own, so the plugin watches the file's mtime and calls `Config.Reload()` itself.
| Setting | Default | Notes |
|---|---|---|
| `HudScale` | `0.8` | HUD size. Lower = smaller HUD *and* more layout room (cures overlapping text) |
| `WorldLabelScale` | `0.8` | In-world nameplate size |
| `MatchWidthOrHeight` | `1.0` | **Leave at 1.** Lower re-introduces width scaling and cramps the layout vertically |
| `DesignHeight` | `1080` | The game's design height; don't change |
| `Verbose` | `false` | Log every canvas adjustment |
`HudScale` → resulting HUD scale at 5120×1440 (stock is 1.333):
| HudScale | 1.0 | 0.85 | **0.8** | 0.6 |
|---|---|---|---|---|
| scale | 1.333 | 1.133 | **1.067** | 0.800 |
## Verifying it works
```
<game>/BepInEx/LogOutput.log
```
That's the plugin's log — **not** UnityExplorer's in-game Log window, which only shows Unity `Debug.Log`. You should see:
```
Burglin' Gnomes UltraWide Fix 1.0.0 loaded.
HUD 'Canvas' ref (1920.00, 1080.00) -> (1920.00, 1350.00) match=1 => scale=1.067
World 'NamePlate' localScale (1.00, 1.00, 1.00) -> (0.80, 0.80, 0.80)
```
If a line is absent, that adjustment didn't happen.
## Uninstall
```bash
./install.sh BurglinGnomes uninstall
```
Removes the loader and plugin. `BepInEx/` is left in place in case other mods use it.
## Building the plugin from source
Prebuilt `BurglinGnomes.UltraWideFix.dll` ships here, so this is only needed if you change it:
```bash
cd plugin
dotnet build -c Release -p:GameDir="/path/to/Burglin' Gnomes"
```
Requires the .NET SDK; it references the game's own assemblies and redistributes none of them.
+16
View File
@@ -0,0 +1,16 @@
# Burglin' Gnomes — metadata for ../../install.sh
# NOTE: this game uses a DIFFERENT fix method than the SUWSF/UE4 games.
TITLE="Burglin' Gnomes"
APPID=3844970
EXE="Gnomium.exe"
COMMON_SUBDIR="Burglin' Gnomes"
ENGINE="Unity 6 (6000.0.62f1), Mono backend, URP + Cinemachine"
# Fix method: BepInEx plugin (not SUWSF — no DRM, managed C# assemblies)
METHOD="bepinex"
LOADER="winhttp.dll" # BepInEx's Doorstop proxy
PLUGIN_DLL="BurglinGnomes.UltraWideFix.dll"
LAUNCH_OPTION='WINEDLLOVERRIDES="winhttp=n,b" %command%'
PREFIX_INI="" # no config-level tweak identified yet
STATUS="Confirmed working at 5120x1440 (32:9) — HUD + nameplate scaling"
+177
View File
@@ -0,0 +1,177 @@
using BepInEx;
using BepInEx.Configuration;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
namespace BurglinGnomes.UltraWideFix
{
/// <summary>
/// Ultrawide UI fix for Burglin' Gnomes (Unity 6, Mono).
///
/// The camera needs no patching: it is orthographic with a constant
/// orthographicSize (5.0), so a wider display already shows proportionally
/// more world (visible width 17.8 -> 35.6 going 16:9 -> 32:9). That is
/// correct Hor+ behaviour.
///
/// The actual problem is UI scaling:
/// * HUD canvases (ScreenSpaceOverlay + ScaleWithScreenSize) ship
/// matchWidthOrHeight = 0.453, which is width-biased. At 5120px wide that
/// yields scaleFactor 1.948 and a ballooned HUD. Matching HEIGHT (1.0)
/// gives 1.333, and raising referenceResolution.y shrinks it further
/// while giving the layout MORE logical room (cures overlapping text).
/// * World-space label canvases (NamePlate, ...) are NOT affected by
/// CanvasScaler.scaleFactor - CanvasScaler.HandleWorldCanvas() overwrites
/// it with dynamicPixelsPerUnit every frame. Their size comes from the
/// canvas transform's localScale, so that is what we scale.
/// </summary>
[BepInPlugin(PluginGuid, PluginName, PluginVersion)]
public class UltraWideFixPlugin : BaseUnityPlugin
{
public const string PluginGuid = "com.programmingpug.burglingnomes.ultrawidefix";
public const string PluginName = "Burglin' Gnomes UltraWide Fix";
public const string PluginVersion = "1.0.0";
private ConfigEntry<bool> _enabled;
private ConfigEntry<float> _match;
private ConfigEntry<float> _hudScale;
private ConfigEntry<float> _worldScale;
private ConfigEntry<float> _designHeight;
private ConfigEntry<bool> _verbose;
// Baselines captured once per canvas so repeated applies never compound.
private readonly System.Collections.Generic.Dictionary<int, Vector2> _refBaseline
= new System.Collections.Generic.Dictionary<int, Vector2>();
private readonly System.Collections.Generic.Dictionary<int, Vector3> _scaleBaseline
= new System.Collections.Generic.Dictionary<int, Vector3>();
private float _timer;
private void Awake()
{
_enabled = Config.Bind("General", "Enabled", true,
"Master switch for the ultrawide UI fix.");
_match = Config.Bind("HUD", "MatchWidthOrHeight", 1.0f,
new ConfigDescription(
"CanvasScaler match for HUD canvases. 0 = scale by width, 1 = by height. " +
"Keep at 1 for ultrawide; lower values re-introduce width scaling and " +
"cramp the layout vertically.",
new AcceptableValueRange<float>(0f, 1f)));
_hudScale = Config.Bind("HUD", "HudScale", 0.8f,
new ConfigDescription(
"HUD size multiplier. Below 1 shrinks the HUD AND gives the layout more " +
"logical room, which is what cures overlapping text. 1 = stock size.",
new AcceptableValueRange<float>(0.4f, 1.5f)));
_designHeight = Config.Bind("HUD", "DesignHeight", 1080f,
"The game's design reference height. Do not change unless you know why.");
_worldScale = Config.Bind("World", "WorldLabelScale", 0.8f,
new ConfigDescription(
"Size multiplier for in-world labels (nameplates). At 32:9 you see twice " +
"as much world, so more labels are on screen and can collide; lower this " +
"to shrink them. 1 = stock size.",
new AcceptableValueRange<float>(0.3f, 1.5f)));
_verbose = Config.Bind("Debug", "Verbose", false,
"Log every canvas adjustment.");
SceneManager.sceneLoaded += (_, __) => Apply();
Logger.LogInfo($"{PluginName} {PluginVersion} loaded.");
}
private void Start() => Apply();
// Canvases are created lazily (menus, popups), so re-apply periodically.
// We also re-read the config file each tick: BepInEx does NOT pick up
// external edits on its own, so without this you'd have to restart the
// game after every tweak.
private void Update()
{
_timer += Time.unscaledDeltaTime;
if (_timer < 2f) return;
_timer = 0f;
try
{
var stamp = System.IO.File.GetLastWriteTimeUtc(Config.ConfigFilePath);
if (stamp != _configStamp)
{
_configStamp = stamp;
Config.Reload();
Logger.LogInfo($"Config reloaded: HudScale={_hudScale.Value} " +
$"WorldLabelScale={_worldScale.Value} " +
$"Match={_match.Value} Enabled={_enabled.Value}");
// values changed -> re-derive from baselines on this pass
}
}
catch (System.Exception e)
{
Logger.LogWarning($"Config reload failed (check the .cfg syntax): {e.Message}");
}
Apply();
}
private System.DateTime _configStamp;
private void Apply()
{
if (!_enabled.Value) return;
foreach (var cs in FindObjectsOfType<CanvasScaler>())
{
var canvas = cs.GetComponent<Canvas>();
if (canvas == null) continue;
int id = cs.GetInstanceID();
if (canvas.renderMode == RenderMode.WorldSpace)
{
if (Mathf.Approximately(_worldScale.Value, 1f)) continue;
var cur = cs.transform.localScale;
// Games hide world-space UI by zeroing its scale. Never cache a
// zero baseline: if we did, multiplying it would pin the canvas
// to zero forever and the UI could never reappear.
if (!_scaleBaseline.TryGetValue(id, out var base3))
{
if (cur.x <= 0.0001f) continue; // hidden right now; revisit later
base3 = cur;
_scaleBaseline[id] = base3;
}
// Respect the game hiding it: leave zeroed canvases alone.
if (cur.x <= 0.0001f) continue;
var want = base3 * _worldScale.Value;
if (cs.transform.localScale != want)
{
cs.transform.localScale = want;
if (_verbose.Value)
Logger.LogInfo($"World '{cs.name}' localScale {base3} -> {want}");
}
continue;
}
if (cs.uiScaleMode != CanvasScaler.ScaleMode.ScaleWithScreenSize) continue;
if (!_refBaseline.TryGetValue(id, out var baseRef))
{
baseRef = cs.referenceResolution;
_refBaseline[id] = baseRef;
}
float targetH = _designHeight.Value / Mathf.Max(0.01f, _hudScale.Value);
var targetRef = new Vector2(baseRef.x, targetH);
if (!Mathf.Approximately(cs.matchWidthOrHeight, _match.Value))
cs.matchWidthOrHeight = _match.Value;
if (cs.referenceResolution != targetRef)
{
cs.referenceResolution = targetRef;
// expected scale now; Canvas.scaleFactor lags a frame
float exp = Mathf.Pow(Screen.width / targetRef.x, 1f - _match.Value) *
Mathf.Pow(Screen.height / targetRef.y, _match.Value);
Logger.LogInfo($"HUD '{cs.name}' ref {baseRef} -> {targetRef} " +
$"match={_match.Value} => scale={exp:F3}");
}
}
}
}
}
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>BurglinGnomes.UltraWideFix</AssemblyName>
<RootNamespace>BurglinGnomes.UltraWideFix</RootNamespace>
<Version>1.0.0</Version>
<LangVersion>latest</LangVersion>
<!-- Plugin references the game's own assemblies; nothing is redistributed. -->
<AssemblySearchPaths>$(AssemblySearchPaths);$(GameManaged)</AssemblySearchPaths>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<NoWarn>$(NoWarn);CS0436</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="UltraWideFix.cs" />
</ItemGroup>
<!--
GameDir must point at the Burglin' Gnomes install. Override on the CLI:
dotnet build -c Release -p:GameDir="/path/to/Burglin' Gnomes"
-->
<PropertyGroup>
<GameDir Condition="'$(GameDir)' == ''">/media/ckoch/Data/steam/steamapps/common/Burglin' Gnomes</GameDir>
<GameManaged>$(GameDir)/Gnomium_Data/Managed</GameManaged>
<BepInExCore>$(GameDir)/BepInEx/core</BepInExCore>
</PropertyGroup>
<ItemGroup>
<Reference Include="BepInEx">
<HintPath>$(BepInExCore)/BepInEx.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>$(BepInExCore)/0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>$(GameManaged)/UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(GameManaged)/UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>$(GameManaged)/UnityEngine.UI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>$(GameManaged)/UnityEngine.UIModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>
+52
View File
@@ -0,0 +1,52 @@
;;;;;;;;;; General ;;;;;;;;;;
[Developer Console]
; Set "Enabled" to true to enable the console.
; By default you can access it with the key "Tilde".
Enabled = false
[Skip Intro Logos]
; Skips splash screen logos, epilepsy warning and autosave warning.
Enabled = false
[Uncap Cutscene FPS]
; Removes 30fps cap in cutscenes.
; DISABLED: bisecting a crash-on-save-load with game build > fix v0.0.10.
Enabled = false
[Adjust Resolution Checks]
; Adjusts resolution checks to use the monitor's maximum supported resolution, rather than the "native" resolution.
; DISABLED: its pattern scan already fails on this game build ("Resolution Checks: Pattern scan failed").
Enabled = false
[Background Audio]
; Set "Enabled" to true to allow audio to continue playing when the game window has lost focus.
Enabled = false
;;;;;;;;;; Ultrawide/Narrower ;;;;;;;;;;
[Cutscene Letterboxing]
; Set "Enabled" to true to re-enable pillarboxing/letterboxing in cutscenes.
Enabled = false
[Fix Movies]
; Crops letterboxing from pre-rendered movies at ultrawide resolutions.
; DISABLED: bisecting; re-enable once the crash source is known.
Enabled = false
[Center HUD]
; Set "Enabled" to true to center certain parts of the HUD to 16:9 at ultrawide resolutions.
Enabled = true
; Adjust "AspectRatio" to set the aspect ratio of the HUD. i.e 1.77778 for 16:9, 2.33333 for 21:9 etc
AspectRatio = 1.77778
;;;;;;;;;; Graphics ;;;;;;;;;;
[Disable Subtitle Blur]
; Set "Enabled" to true to disable the background blur on cutscene subtitle lines.
Enabled = false
[Sharpening]
; Adjust "Strength" to control the intensity of the forced post-process sharpening.
; Valid range: 0 to 2.0 Game Default = 1.0
Strength = 0
+101
View File
@@ -0,0 +1,101 @@
# Clair Obscur: Expedition 33 — UltraWide
⚠️ **Partial support — reduced feature profile.** ClairObscurFix v0.0.10 is archived and predates the current game build; running it with upstream defaults **crashed on save load**. This repo ships a reduced profile with only the 32:9 HUD centering enabled.
- **Engine:** Unreal Engine 5 — **no DRM** (no SteamStub; `.text` entropy 6.74)
- **Steam AppID:** 1903340
- **Executable:** `SandFall-Win64-Shipping.exe`
- **Method:** `external` — vendored third-party fix + our 32:9 config
## We did not write this fix
The ultrawide work here is **[ClairObscurFix](https://github.com/Lyall/ClairObscurFix) by [Lyall](https://github.com/Lyall)** (MIT), vendored unmodified. It is mature and already handles the hard parts; reimplementing it would be duplicated effort for no benefit. **All credit for the fix itself goes to Lyall.**
What this repo adds is the **32:9 configuration** verified on a Samsung 49" G93SC, packaging, and a one-command installer consistent with the other games here.
> The upstream repo is **archived**, so it will not receive further updates. Vendoring it here means the version you need stays available.
## Install (Linux / Proton)
```bash
./install.sh Expedition33
```
Then **required** — Steam → Properties → Launch Options:
```
WINEDLLOVERRIDES="dsound=n,b" %command%
```
Set **5120×1440** fullscreen in-game.
## Install (Windows)
Extract the release zip into `…\Expedition 33\Sandfall\Binaries\Win64\`.
## Our 32:9 config
`ClairObscurFix.ini` ships with upstream defaults **except**:
| Option | Upstream | Ours | Why |
|---|---|---|---|
| `[Center HUD] Enabled` | `false` | **`true`** | At 5120px wide, HUD elements anchored to screen edges end up absurdly far apart |
Everything else is left at upstream defaults, which are already ultrawide-friendly:
| Option | Default | Effect |
|---|---|---|
| `Cutscene Letterboxing` | `false` | No bars in cutscenes |
| `Fix Movies` | `true` | Crops letterboxing from pre-rendered video |
| `Adjust Resolution Checks` | `true` | Uses the monitor's max resolution rather than "native" |
| `Uncap Cutscene FPS` | `true` | Removes the 30fps cutscene cap |
### Tuning
Edit `…/Sandfall/Binaries/Win64/ClairObscurFix.ini` (requires a restart — this fix has no live reload):
- **HUD too cramped?** Raise `[Center HUD] AspectRatio` from `1.77778` (16:9) to `2.33333` (21:9) — usually the sweet spot at 32:9.
- **Prefer the HUD spanning the full width?** Set `[Center HUD] Enabled = false`.
- **Want cinematic bars back?** `[Cutscene Letterboxing] Enabled = true`.
## Uninstall
```bash
./install.sh Expedition33 uninstall
```
## Known issue: version drift
**ClairObscurFix v0.0.10 (May 2025) is archived and no longer tracks the game.**
Observed on a current build (2026-07-21), with upstream defaults:
```
[error] Resolution Checks: Pattern scan failed.
[error] Resolution Scale Bug: Pattern scan failed.
```
…followed by a **crash when loading a save**. Removing the fix entirely made the
game load normally, isolating the fix as the cause. The crash coincides with the
fix's `Level Load` hook, which is consistent with a hook attaching to shifted
code in a newer binary.
**Mitigation:** ship a reduced profile — everything disabled except `Center HUD`,
the only option genuinely needed at 32:9. The failing scans are disabled outright
since they were already inert.
## Maintenance / revalidation
Because upstream is archived, this entry decays as the game patches. Re-check
after any significant game update:
1. Launch and load a save. If it crashes, remove the fix (`./install.sh Expedition33 uninstall`) and retry to confirm the fix is the cause rather than the game or Proton.
2. Read `Sandfall/Binaries/Win64/ClairObscurFix.log``Pattern scan failed` lines mark hooks that no longer match the binary.
3. Disable any newly-failing feature in `ClairObscurFix.ini`; a failed scan is inert, but a *wrongly-attached* hook is what crashes.
4. If `Center HUD` itself stops working, this entry should be marked unsupported. The game has **no DRM**, so writing a small dedicated HUD-centering patch is a viable replacement.
## Still unverified at 32:9
- [ ] Is `Center HUD` at 16:9 right, or is 21:9 (`2.33333`) better?
- [ ] Camera FOV — Hor+ (see more) or Vert- (zoomed)?
- [ ] Pillarboxing in cutscenes / pre-rendered movies (`Fix Movies` currently **off**)
+19
View File
@@ -0,0 +1,19 @@
# Clair Obscur: Expedition 33 — metadata for ../../install.sh
TITLE="Clair Obscur: Expedition 33"
APPID=1903340
EXE="SandFall-Win64-Shipping.exe"
COMMON_SUBDIR="Expedition 33/Sandfall/Binaries/Win64"
ENGINE="Unreal Engine 5 (no DRM)"
# Fix method: EXTERNAL — we ship an upstream third-party fix (MIT) plus our own
# 32:9-tuned config. We deliberately do NOT reimplement it; see README.md.
METHOD="external"
VENDOR_PAYLOAD="ClairObscurFix/Sandfall/Binaries/Win64" # contents copied into the exe dir
EXTERNAL_CONFIG="ClairObscurFix.ini" # our tuned copy overrides upstream's
EXTERNAL_FILES="ClairObscurFix.asi ClairObscurFix.ini dsound.dll"
LOADER="dsound.dll"
LAUNCH_OPTION='WINEDLLOVERRIDES="dsound=n,b" %command%'
PREFIX_INI=""
STATUS="PARTIAL — reduced profile (Center HUD only); upstream v0.0.10 is archived and crashes with full features on current builds"
+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"
+151 -67
View File
@@ -1,108 +1,192 @@
#!/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() {
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)"
}
METHOD="${METHOD:-suwsf}"
if [ "${1:-}" = "uninstall" ]; then uninstall; exit 0; fi
if [ "$ACTION" = "uninstall" ]; then
echo "[+] Uninstalling..."
if [ "$METHOD" = "external" ]; then
for f in $EXTERNAL_FILES; do [ -f "$GAMEDIR/$f" ] && rm -v "$GAMEDIR/$f"; done
elif [ "$METHOD" = "bepinex" ]; then
rm -vf "$GAMEDIR/winhttp.dll" "$GAMEDIR/doorstop_config.ini" "$GAMEDIR/.doorstop_version" \
"$GAMEDIR/changelog.txt" "$GAMEDIR/BepInEx/plugins/$PLUGIN_DLL"
# leave BepInEx/ itself: the user may have other mods installed
echo "[i] Left BepInEx/ in place (other mods may rely on it). Delete it manually if unused."
else
for f in SUWSF.asi SUWSF.ini dsound.dll dinput8.dll version.dll winmm.dll; do
[ -f "$GAMEDIR/$f" ] && rm -v "$GAMEDIR/$f"
done
fi
remove_ini
echo "[+] Done. No game file was ever modified."
exit 0
fi
# --- install ------------------------------------------------------------------
if [ "$METHOD" = "external" ]; then
SRC="$HERE/vendor/$VENDOR_PAYLOAD"
[ -d "$SRC" ] || { echo "[!] missing vendored payload: $SRC"; exit 1; }
cp -v "$SRC"/* "$GAMEDIR/" 2>/dev/null || true
# our tuned config wins over the upstream default
if [ -n "${EXTERNAL_CONFIG:-}" ] && [ -f "$GAMES_DIR/$GAME/$EXTERNAL_CONFIG" ]; then
cp -v "$GAMES_DIR/$GAME/$EXTERNAL_CONFIG" "$GAMEDIR/$EXTERNAL_CONFIG"
fi
elif [ "$METHOD" = "bepinex" ]; then
BEPZIP=$(ls "$HERE"/vendor/BepInEx_win_x64_*.zip 2>/dev/null | head -1)
PLUGIN="$GAMES_DIR/$GAME/$PLUGIN_DLL"
[ -f "$BEPZIP" ] || { echo "[!] missing BepInEx zip in vendor/"; exit 1; }
[ -f "$PLUGIN" ] || { echo "[!] missing prebuilt plugin: $PLUGIN"; exit 1; }
if [ -f "$GAMEDIR/winhttp.dll" ]; then
echo "[i] BepInEx already present; updating plugin only."
else
command -v unzip >/dev/null || { echo "[!] unzip required"; exit 1; }
unzip -o -q "$BEPZIP" -d "$GAMEDIR"
echo "[+] Installed BepInEx ($(basename "$BEPZIP"))"
fi
mkdir -p "$GAMEDIR/BepInEx/plugins"
cp -v "$PLUGIN" "$GAMEDIR/BepInEx/plugins/"
else
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"
fi
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 ($METHOD): $(case "$METHOD" in
bepinex) echo "BepInEx + $PLUGIN_DLL + $LOADER" ;;
external) echo "$EXTERNAL_FILES" ;;
*) echo "SUWSF.asi, SUWSF.ini, $LOADER" ;;
esac)
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.
Tuning (METHOD=bepinex games):
Config is generated on first launch at
<game>/BepInEx/config/*.cfg (edits apply live, ~2s, no restart)
Plugin log (proves whether it applied):
<game>/BepInEx/LogOutput.log
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()
+48 -13
View File
@@ -1,21 +1,56 @@
#!/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. Handles both fix methods.
# ./tools/build_release.sh GundamBreaker4 (METHOD=suwsf)
# ./tools/build_release.sh BurglinGnomes (METHOD=bepinex)
# Output: dist/<Game>_UltraWide_Fix.zip
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"
[ -d "$GDIR" ] || { echo "[!] no such game: $GDIR"; exit 1; }
[ -f "$GDIR/game.conf" ] || { echo "[!] missing $GDIR/game.conf"; exit 1; }
# shellcheck disable=SC1090
. "$GDIR/game.conf"
METHOD="${METHOD:-suwsf}"
OUT="dist/${GAME}_UltraWide_Fix"
rm -rf "$OUT" "$OUT.zip"
mkdir -p "$OUT"
if [ "$METHOD" = "external" ]; then
SRC="vendor/$VENDOR_PAYLOAD"
[ -d "$SRC" ] || { echo "[!] missing vendored payload: $SRC"; exit 1; }
cp "$SRC"/* "$OUT/" 2>/dev/null || true
[ -n "${EXTERNAL_CONFIG:-}" ] && [ -f "$GDIR/$EXTERNAL_CONFIG" ] && cp "$GDIR/$EXTERNAL_CONFIG" "$OUT/"
# ship the upstream license alongside, as MIT requires
UPLIC="vendor/$(dirname "$VENDOR_PAYLOAD" | cut -d/ -f1)/LICENSE"
[ -f "$UPLIC" ] || UPLIC="vendor/${VENDOR_PAYLOAD%%/*}/LICENSE"
[ -f "$UPLIC" ] && cp "$UPLIC" "$OUT/UPSTREAM-LICENSE.txt"
echo "[+] Packaged vendored external fix + our config"
elif [ "$METHOD" = "bepinex" ]; then
BEPZIP=$(ls vendor/BepInEx_win_x64_*.zip 2>/dev/null | head -1)
[ -f "$BEPZIP" ] || { echo "[!] missing BepInEx zip in vendor/"; exit 1; }
[ -f "$GDIR/$PLUGIN_DLL" ] || { echo "[!] missing prebuilt plugin $GDIR/$PLUGIN_DLL"; exit 1; }
command -v unzip >/dev/null || { echo "[!] unzip required"; exit 1; }
unzip -o -q "$BEPZIP" -d "$OUT"
mkdir -p "$OUT/BepInEx/plugins"
cp "$GDIR/$PLUGIN_DLL" "$OUT/BepInEx/plugins/"
echo "[+] Packaged BepInEx + $PLUGIN_DLL"
else
V="vendor/SUWSF-x64"
for f in "$V/SUWSF.asi" "$V/dsound.dll" "$V/LICENSE" "$GDIR/SUWSF.ini"; do
[ -f "$f" ] || { echo "[!] missing $f"; exit 1; }
done
cp "$V/SUWSF.asi" "$V/dsound.dll" "$GDIR/SUWSF.ini" "$OUT/"
cp "$V/LICENSE" "$OUT/SUWSF-LICENSE.txt"
echo "[+] Packaged SUWSF + patch ini"
fi
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
cp "$GDIR/README.md" "$OUT/README.md" 2>/dev/null || true
cp THIRD_PARTY.md LICENSE "$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" )
+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__":
+87
View File
@@ -0,0 +1,87 @@
#!/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 may be a space-separated list; empty/"all" = every game in games/
GAME="${GAME:-all}"
if [ "$GAME" = "all" ]; then
GAME=$(for d in games/*/; do [ -f "$d/game.conf" ] && basename "$d"; done | tr '\n' ' ')
fi
TAG="${TAG:?set TAG explicitly, e.g. TAG=v1.2.0 (see CHANGELOG.md)}"
# Single game -> its own README; multiple -> the collection README.
if [ "$(echo "$GAME" | wc -w)" -eq 1 ] && [ -f "games/$GAME/README.md" ]; then
NOTES="${NOTES:-games/$GAME/README.md}"
else
NOTES="${NOTES:-README.md}"
fi
[ -f "$NOTES" ] || NOTES="README.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")
echo "[+] Games in this release: $GAME"
# 1. build every artifact -- do NOT swallow output; a failure here used to exit
# the whole script silently because stdout went to /dev/null under `set -e`.
ZIPS=""
for g in $GAME; do
if ! bash tools/build_release.sh "$g"; then
echo "[!] build_release.sh failed for game '$g' (see above)"; exit 1
fi
z="dist/${g}_UltraWide_Fix.zip"
[ -f "$z" ] || { echo "[!] build produced no $z"; exit 1; }
ZIPS="$ZIPS $z"
done
echo "[+] Built:$ZIPS"
# 2. push code + tag
git push origin main
if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
git tag -a "$TAG" -m "$TAG"
echo "[+] Created tag $TAG at $(git rev-parse --short HEAD)"
fi
git push origin "$TAG" 2>&1 | tail -1
# 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 assets"
fi
# 4. upload each zip, replacing any same-named asset already attached
for z in $ZIPS; do
n=$(basename "$z")
OLD=$(curl -sf "${AUTH[@]}" "$API/repos/$OWNER/$REPO/releases/$REL_ID" \
| ZIPNAME="$n" 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)
[ -n "$OLD" ] && curl -sf -X DELETE "${AUTH[@]}" \
"$API/repos/$OWNER/$REPO/releases/$REL_ID/assets/$OLD" >/dev/null && echo "[i] replaced $n"
curl -sf "${AUTH[@]}" -F "attachment=@$z" \
"$API/repos/$OWNER/$REPO/releases/$REL_ID/assets?name=$n" >/dev/null
echo "[+] Uploaded $n"
done
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
+59
View File
@@ -0,0 +1,59 @@
// UnityExplorer C# console diagnostic — paste into the C# Console tab, hit Run.
// Run once at ultrawide and once at 16:9, then compare.
//
// NOTE: Burglin' Gnomes uses an ORTHOGRAPHIC camera, so fieldOfView is meaningless.
// The governing value is orthographicSize (half the visible world HEIGHT):
// visible height = 2 * orthographicSize
// visible width = visible height * aspect
// If orthographicSize is CONSTANT across resolutions -> already Hor+ (correct).
// If it SHRINKS at wider aspect -> the game holds world-width constant (Vert- lock).
var sb = new System.Text.StringBuilder();
sb.AppendLine($"SCREEN {UnityEngine.Screen.width}x{UnityEngine.Screen.height} " +
$"aspect={(float)UnityEngine.Screen.width / UnityEngine.Screen.height:F4} " +
$"full={UnityEngine.Screen.fullScreenMode}");
foreach (var c in UnityEngine.Object.FindObjectsOfType<UnityEngine.Camera>())
{
if (c.orthographic)
sb.AppendLine($"CAM {c.name} ORTHO size={c.orthographicSize:F4} aspect={c.aspect:F4} " +
$"=> visibleW={2f * c.orthographicSize * c.aspect:F3} visibleH={2f * c.orthographicSize:F3} " +
$"depth={c.depth} enabled={c.enabled}");
else
sb.AppendLine($"CAM {c.name} PERSP fov={c.fieldOfView:F2} aspect={c.aspect:F4} " +
$"phys={c.usePhysicalProperties} gate={c.gateFit} depth={c.depth} enabled={c.enabled}");
}
// --- Cinemachine (any version) via reflection: may drive ortho size each frame ---
var bf = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance;
foreach (var mb in UnityEngine.Object.FindObjectsOfType<UnityEngine.MonoBehaviour>())
{
var t = mb.GetType();
if (t.FullName == null || t.FullName.IndexOf("Cinemachine", System.StringComparison.OrdinalIgnoreCase) < 0)
continue;
object lens = null;
foreach (var name in new[] { "Lens", "m_Lens" })
{
var p = t.GetProperty(name, bf); if (p != null) { lens = p.GetValue(mb); break; }
var f = t.GetField(name, bf); if (f != null) { lens = f.GetValue(mb); break; }
}
if (lens != null)
{
var lt = lens.GetType();
object osize = null, fov = null;
foreach (var n in new[] { "OrthographicSize", "m_OrthographicSize" })
{ var f = lt.GetField(n, bf); if (f != null) { osize = f.GetValue(lens); break; } }
foreach (var n in new[] { "FieldOfView", "m_FieldOfView" })
{ var f = lt.GetField(n, bf); if (f != null) { fov = f.GetValue(lens); break; } }
sb.AppendLine($"CM {t.Name} '{mb.name}' lensOrthoSize={osize} lensFOV={fov} enabled={mb.enabled}");
}
else sb.AppendLine($"CM {t.Name} '{mb.name}' (no lens) enabled={mb.enabled}");
}
// --- uGUI canvas scalers: match=0 scales by WIDTH (bad at ultrawide), 1 = by HEIGHT ---
foreach (var cs in UnityEngine.Object.FindObjectsOfType<UnityEngine.UI.CanvasScaler>())
sb.AppendLine($"UI {cs.name} mode={cs.uiScaleMode} ref={cs.referenceResolution} " +
$"match={cs.matchWidthOrHeight} screenMatch={cs.screenMatchMode} scaleFactor={cs.scaleFactor}");
UnityExplorer.ExplorerCore.Log(sb.ToString());
+36
View File
@@ -0,0 +1,36 @@
// LIVE FIX TEST — paste into UnityExplorer's C# Console while running at ultrawide.
// Applies both candidate fixes immediately so you can see the result on screen.
// Nothing is persisted: relaunching the game reverts everything.
var log = new System.Text.StringBuilder();
// ---- FIX 1: HUD scaling ----------------------------------------------------
// CanvasScaler.matchWidthOrHeight: 0 = scale by WIDTH, 1 = scale by HEIGHT.
// The game ships 0.453 (width-biased), which balloons the HUD at 5120px wide.
// Matching HEIGHT keeps the HUD the same physical size as it is at 16:9.
foreach (var cs in UnityEngine.Object.FindObjectsOfType<UnityEngine.UI.CanvasScaler>())
{
log.AppendLine($"UI {cs.name}: match {cs.matchWidthOrHeight} -> 1.0");
cs.matchWidthOrHeight = 1f;
}
// ---- FIX 2: orthographic view width ---------------------------------------
// For an ortho camera, visible height = 2*orthographicSize and width follows
// aspect. If the game shrank orthographicSize at ultrawide, restoring the 16:9
// value gives back the vertical view (true Hor+).
// Set TARGET_ORTHO to the size you measured at 1920x1080; leave null to only report.
float? TARGET_ORTHO = null; // e.g. 5.5f
foreach (var c in UnityEngine.Object.FindObjectsOfType<UnityEngine.Camera>())
{
if (!c.orthographic) continue;
log.AppendLine($"CAM {c.name}: orthoSize={c.orthographicSize:F4} aspect={c.aspect:F4} " +
$"visibleW={2f * c.orthographicSize * c.aspect:F3} visibleH={2f * c.orthographicSize:F3}");
if (TARGET_ORTHO.HasValue)
{
c.orthographicSize = TARGET_ORTHO.Value;
log.AppendLine($" -> forced orthoSize={TARGET_ORTHO.Value}");
}
}
UnityExplorer.ExplorerCore.Log(log.ToString());
+82
View File
@@ -0,0 +1,82 @@
// LIVE UI TUNER — paste into UnityExplorer's C# Console. Edit knobs, Run, look, repeat.
// Nothing persists across a relaunch.
//
// TWO canvas families, each needing a DIFFERENT lever:
//
// 1. ScreenSpaceOverlay + ScaleWithScreenSize ("Canvas", "Canvas in front")
// The HUD. Lever = matchWidthOrHeight + referenceResolution.
// KEEP MATCH AT 1.0 — lowering it re-introduces width-based scaling and
// undoes the shrink (match .8 + ref 1350 => 1.281, vs match 1 => 1.067).
//
// 2. WorldSpace canvases ("NamePlate", etc.)
// In-world labels. CanvasScaler.scaleFactor DOES NOT WORK here: for a
// WorldSpace canvas, CanvasScaler.HandleWorldCanvas() overwrites
// scaleFactor with dynamicPixelsPerUnit every frame. The real lever is
// the canvas transform's localScale.
//
// NOTE: Canvas.scaleFactor is recomputed in CanvasScaler.Update(), so reading it
// right after writing returns LAST frame's value. We print the mathematically
// expected scale instead, and echo BEFORE state (which is accurate) each run.
// ---- KNOBS -----------------------------------------------------------------
float MATCH = 1.0f; // HUD. Leave at 1.0.
float HUD_SCALE = 0.85f; // HUD. <1 = smaller HUD + more layout room.
float WORLD_SCALE = 0f; // In-world labels. 0 = don't touch.
// Absolute multiplier vs the BASELINE printed below.
// Run once with 0 to read baseline, then e.g. 0.8f.
bool LIST_ONLY = false; // true = report only, change nothing
// ----------------------------------------------------------------------------
var log = new System.Text.StringBuilder();
log.AppendLine($"KNOBS match={MATCH} hudScale={HUD_SCALE} worldScale={WORLD_SCALE} listOnly={LIST_ONLY}");
log.AppendLine($"SCREEN {UnityEngine.Screen.width}x{UnityEngine.Screen.height} " +
$"aspect={(float)UnityEngine.Screen.width / UnityEngine.Screen.height:F4}");
foreach (var cs in UnityEngine.Object.FindObjectsOfType<UnityEngine.UI.CanvasScaler>())
{
var cv = cs.GetComponent<UnityEngine.Canvas>();
if (cv == null) continue;
if (cv.renderMode == UnityEngine.RenderMode.WorldSpace)
{
var ls = cv.transform.localScale;
log.AppendLine($"WORLD {cs.name}: localScale={ls} (dynPPU={cs.dynamicPixelsPerUnit})");
if (!LIST_ONLY && WORLD_SCALE > 0f)
{
// Store the pristine scale on first touch so repeated runs don't compound.
var marker = cv.transform.Find("__uwp_baseline");
float bx;
if (marker == null)
{
var go = new UnityEngine.GameObject("__uwp_baseline");
go.transform.SetParent(cv.transform, false);
go.transform.localScale = ls; // stash baseline here
bx = ls.x;
}
else bx = marker.localScale.x;
cv.transform.localScale = new UnityEngine.Vector3(bx, bx, bx) * WORLD_SCALE;
log.AppendLine($" APPLIED localScale baseline={bx:F5} -> {cv.transform.localScale}");
}
continue;
}
log.AppendLine($"BEFORE {cs.name} [{cs.uiScaleMode}/{cv.renderMode}] ref={cs.referenceResolution} " +
$"match={cs.matchWidthOrHeight}");
if (!LIST_ONLY && cs.uiScaleMode == UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize)
{
cs.matchWidthOrHeight = MATCH;
if (HUD_SCALE > 0f && System.Math.Abs(HUD_SCALE - 1f) > 0.0001f)
cs.referenceResolution = new UnityEngine.Vector2(cs.referenceResolution.x, 1080f / HUD_SCALE);
// expected scale (accurate NOW; Canvas.scaleFactor lags by a frame)
var r = cs.referenceResolution;
double exp = System.Math.Pow(UnityEngine.Screen.width / r.x, 1.0 - MATCH) *
System.Math.Pow(UnityEngine.Screen.height / r.y, MATCH);
log.AppendLine($" APPLIED ref={r} match={MATCH} => expectedScale={exp:F4} " +
$"logicalCanvas={UnityEngine.Screen.width / exp:F0}x{UnityEngine.Screen.height / exp:F0}");
}
}
UnityExplorer.ExplorerCore.Log(log.ToString());
Binary file not shown.
BIN
View File
Binary file not shown.
View File
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Lyall
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.
Binary file not shown.
@@ -0,0 +1,49 @@
;;;;;;;;;; General ;;;;;;;;;;
[Developer Console]
; Set "Enabled" to true to enable the console.
; By default you can access it with the key "Tilde".
Enabled = false
[Skip Intro Logos]
; Skips splash screen logos, epilepsy warning and autosave warning.
Enabled = false
[Uncap Cutscene FPS]
; Removes 30fps cap in cutscenes.
Enabled = true
[Adjust Resolution Checks]
; Adjusts resolution checks to use the monitor's maximum supported resolution, rather than the "native" resolution.
Enabled = true
[Background Audio]
; Set "Enabled" to true to allow audio to continue playing when the game window has lost focus.
Enabled = false
;;;;;;;;;; Ultrawide/Narrower ;;;;;;;;;;
[Cutscene Letterboxing]
; Set "Enabled" to true to re-enable pillarboxing/letterboxing in cutscenes.
Enabled = false
[Fix Movies]
; Crops letterboxing from pre-rendered movies at ultrawide resolutions.
Enabled = true
[Center HUD]
; Set "Enabled" to true to center certain parts of the HUD to 16:9 at ultrawide resolutions.
Enabled = false
; Adjust "AspectRatio" to set the aspect ratio of the HUD. i.e 1.77778 for 16:9, 2.33333 for 21:9 etc
AspectRatio = 1.77778
;;;;;;;;;; Graphics ;;;;;;;;;;
[Disable Subtitle Blur]
; Set "Enabled" to true to disable the background blur on cutscene subtitle lines.
Enabled = false
[Sharpening]
; Adjust "Strength" to control the intensity of the forced post-process sharpening.
; Valid range: 0 to 2.0 Game Default = 1.0
Strength = 0
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 ThirteenAG
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.
Binary file not shown.