The Witcher 3 5.0 on Proton/Linux: re-enable DLSS, DLSS-G and ray tracing (wine_get_version check patch)
The Witcher 3 5.0 on Proton: re-enable DLSS, DLSS-G (and ray tracing)
What this is: an unofficial workaround until CD PROJEKT RED or Valve fix this properly.
Supports only: bin/x64_dx12/witcher3.exe 5.0.0.1041720 (the 2026-09-29 "Remastered" update, Steam build 25575366, sha256 c272b2c2…c7e25). The script refuses any other file.
Why DLSS is missing under Proton
The 5.0 executable asks ntdll for wine_get_version. When it finds it (Wine/Proton), the game does three things:
- Skips NVIDIA Streamline entirely. It never calls
slInit, so DLSS Super Resolution, Ray Reconstruction, DLSS Frame Generation and Reflex are all reported as unsupported. - Sets a cached "Wine or Steam hardware" flag. That flag forces DLSS-G unsupported, zeroes the ray tracing capability (all RT and path tracing options greyed out), skips the path-tracing shaders, disables FSR frame generation, and changes buffer alignment.
- Nothing else. It is not the NVAPI IDs or the
D3DKMTQueryAdapterInfotype 31 queries seen in the logs.
Root-cause details: ValveSoftware/Proton#607. RT hang details: HansKristian-Work/vkd3d-proton#3226.
Usage
Close the game first. Python 3, no dependencies.
python3 w3_proton_patch.py # DLSS SR/RR, Reflex, DLSS-G (default Steam path) python3 w3_proton_patch.py /path/to/witcher3.exe python3 w3_proton_patch.py --dry-run # show the state, change nothing python3 w3_proton_patch.py --restore # undo everything
- A copy of the original is saved once as
witcher3.exe.orig-5.0.0.1041720. - Steam's "Verify integrity of game files" also restores the original.
- A game update replaces the exe and the offsets change. Run the script again only if it says the build is supported.
Then choose DLSS and Frame Generation in Options → Graphics.
Ray tracing / path tracing (--rt) — read this first
python3 w3_proton_patch.py --rt
This clears the Wine flag, so RT and path tracing become selectable. On NVIDIA drivers 590 and newer, RT then hangs the GPU within about a minute (Xid 109 / VK_ERROR_DEVICE_LOST). This is vkd3d-proton#3226 and is not fixed upstream.
What was stable here (RTX 4090, driver 615.71.09, GE-Proton11-7, 20+ minutes with RT, path tracing, DLSS and DLSS-G):
- A vkd3d-proton built with
vkd3d-rtas-prebuild-x8.patch(in this gist). It multiplies the acceleration-structure prebuild sizes by 8;VKD3D_RTAS_SIZE_MULT=overrides the factor at runtime. Build vkd3d-proton at the commit your Proton ships (GE-Proton11-7:af89350c) and drop the x64d3d12.dll/d3d12core.dllinto a copy of your Proton underfiles/lib/wine/vkd3d-proton/x86_64-windows/. --rtalso raises the game's RT scratch buffer from 32 MiB to 256 MiB. ×8 alone still hung once; ×8 plus the 256 MiB buffer did not.
--rt also keeps the buffer alignment behaviour Wine gets. Clearing the flag alone makes the game request tight-aligned 256-byte buffers, which vkd3d-proton rejects, and the game then crashes within seconds. This part is only needed on vkd3d-proton builds older than #3308 (merged 2026-09-21), such as the one in GE-Proton11-7. On newer builds the request works, and keeping the patch is harmless.
Patches (file offsets)
| Offset | Original → new | Effect |
|---|---|---|
0x1B709CB | 75 42 → 90 90 | Creates the Streamline manager under Wine (DLSS SR/RR, Reflex) |
0x1B759A0 | 74 → EB | DLSS-G support comes from Streamline instead of the Wine flag |
0x1EE2A53 | 75 0B → 90 90 | --rt: the Wine flag no longer set (RT, path tracing, FSR FG) |
0x1EE09F7 | 75 → EB | --rt: keep Wine's buffer alignment (needed only on vkd3d-proton older than #3308) |
0x106E0D | 20 00 00 00 → 00 01 00 00 | --rt: RT BLAS scratch default 32 → 256 MiB |
Notes
- Tested on Linux with an RTX 4090. DLSS-G needs an RTX 40 or newer.
- Modifying the executable may be against the game's EULA. It does not touch any DRM, only the platform check, and it is fully reversible.
| diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c | |
| index 44685730..3b531c9d 100644 | |
| --- a/libs/vkd3d/device.c | |
| +++ b/libs/vkd3d/device.c | |
| @@ -7946,6 +7946,25 @@ static void STDMETHODCALLTYPE d3d12_device_GetRaytracingAccelerationStructurePre | |
| info->UpdateScratchDataSizeInBytes = size_info.updateScratchSize; | |
| } | |
| + /* LOCAL WORKAROUND (vkd3d-proton #3226): Witcher 3 RT hangs on NVIDIA 590+ unless the RTAS result and | |
| + * scratch sizes reported to the application are inflated. x8 by default; | |
| + * VKD3D_RTAS_SIZE_MULT= overrides it at runtime (1 = stock behaviour). */ | |
| + { | |
| + static uint32_t rtas_size_mult; | |
| + uint32_t mult = vkd3d_atomic_uint32_load_explicit(&rtas_size_mult, vkd3d_memory_order_relaxed); | |
| + | |
| + if (!mult) | |
| + { | |
| + mult = max(1u, vkd3d_env_var_as_uint("VKD3D_RTAS_SIZE_MULT", 8)); | |
| + vkd3d_atomic_uint32_store_explicit(&rtas_size_mult, mult, vkd3d_memory_order_relaxed); | |
| + INFO("RTAS prebuild sizes are multiplied by %u (local workaround).\n", mult); | |
| + } | |
| + | |
| + info->ResultDataMaxSizeInBytes *= mult; | |
| + info->ScratchDataSizeInBytes *= mult; | |
| + info->UpdateScratchDataSizeInBytes *= mult; | |
| + } | |
| + | |
| TRACE("ResultDataMaxSizeInBytes: %"PRIu64".\n", info->ResultDataMaxSizeInBytes); | |
| TRACE("ScratchDatSizeInBytes: %"PRIu64".\n", info->ScratchDataSizeInBytes); | |
| TRACE("UpdateScratchDataSizeInBytes: %"PRIu64".\n", info->UpdateScratchDataSizeInBytes); |
| #!/usr/bin/env python3 | |
| """Re-enable DLSS / DLSS-G (and optionally ray tracing) in The Witcher 3 5.0 under Proton/Wine. | |
| The Witcher 3 5.0.0.1041720 ("Remastered", DX12, Steam build 25575366) detects Wine through | |
| GetProcAddress(ntdll, "wine_get_version") and then deliberately skips NVIDIA Streamline (so DLSS SR/RR, | |
| DLSS Frame Generation and Reflex are unavailable) and forces ray tracing / path tracing off. | |
| This script flips those checks in bin/x64_dx12/witcher3.exe. It only accepts that exact build. | |
| python3 w3_proton_patch.py [path/to/witcher3.exe] DLSS SR/RR, Reflex and DLSS-G | |
| python3 w3_proton_patch.py --rt [path/to/witcher3.exe] also RT and path tracing (read the README first: | |
| needs a vkd3d-proton with the #3226 workaround, | |
| or the GPU hangs within a minute) | |
| python3 w3_proton_patch.py --restore [path/to/witcher3.exe] undo every patch (Steam "Verify integrity" also does) | |
| add --dry-run to only report the current state. | |
| Unofficial; use at your own risk. Close the game before running it. | |
| """ | |
| import argparse | |
| import hashlib | |
| import os | |
| import shutil | |
| import sys | |
| ORIGINAL_SHA256 = "c272b2c2e61f84c758e28fab69ab2915944dd1e539dbb435fae9fc67494c7e25" | |
| ORIGINAL_SIZE = 90832336 | |
| DEFAULT_PATH = os.path.expanduser( | |
| "~/.local/share/Steam/steamapps/common/The Witcher 3/bin/x64_dx12/witcher3.exe") | |
| # (file offset, original bytes, patched bytes, description) | |
| BASE = [ | |
| (0x1B709CB, b"\x75\x42", b"\x90\x90", "create the Streamline manager (slInit) under Wine: DLSS SR/RR, Reflex"), | |
| (0x1B759A0, b"\x74", b"\xEB", "do not force DLSS-G 'unsupported' on the Wine flag"), | |
| ] | |
| RT = [ | |
| (0x1EE2A53, b"\x75\x0B", b"\x90\x90", "clear the cached Wine flag: RT, path tracing, FSR FG"), | |
| (0x1EE09F7, b"\x75", b"\xEB", "keep buffer alignment as under Wine (vkd3d-proton rejects the tight-aligned 256 B buffers)"), | |
| (0x106E0D, b"\x20\x00\x00\x00", b"\x00\x01\x00\x00", "RT BLAS scratch buffer default 32 MiB -> 256 MiB"), | |
| ] | |
| ALL = BASE + RT | |
| def state_of(data, patch): | |
| off, orig, new, _ = patch | |
| cur = data[off:off + len(orig)] | |
| return "original" if cur == orig else "patched" if cur == new else "unknown" | |
| def main(): | |
| ap = argparse.ArgumentParser( | |
| description="Re-enable DLSS / DLSS-G (and optionally RT) in The Witcher 3 5.0 under Proton/Wine.") | |
| ap.add_argument("exe", nargs="?", default=DEFAULT_PATH) | |
| ap.add_argument("--rt", action="store_true", help="also enable ray tracing and path tracing") | |
| ap.add_argument("--restore", action="store_true", help="revert every patch") | |
| ap.add_argument("--dry-run", action="store_true", help="report only, change nothing") | |
| args = ap.parse_args() | |
| path = args.exe | |
| if not os.path.isfile(path): | |
| sys.exit(f"not found: {path}") | |
| data = bytearray(open(path, "rb").read()) | |
| if len(data) != ORIGINAL_SIZE: | |
| sys.exit(f"unexpected size {len(data)}: this script only supports witcher3.exe 5.0.0.1041720 (DX12)") | |
| states = [state_of(data, p) for p in ALL] | |
| if "unknown" in states: | |
| sys.exit("unexpected bytes at a patch site: different build or modified exe; nothing changed") | |
| # Rebuild the original image in memory and check it is exactly the supported build. | |
| orig = bytearray(data) | |
| for off, o, _, _ in ALL: | |
| orig[off:off + len(o)] = o | |
| if hashlib.sha256(orig).hexdigest() != ORIGINAL_SHA256: | |
| sys.exit("sha256 mismatch: not witcher3.exe 5.0.0.1041720 (DX12); nothing changed") | |
| for p, s in zip(ALL, states): | |
| print(f" 0x{p[0]:07X} {s:8} {p[3]}") | |
| if args.restore: | |
| target = bytes(orig) | |
| else: | |
| target = bytearray(data) | |
| for off, _, new, _ in (ALL if args.rt else BASE): | |
| target[off:off + len(new)] = new | |
| target = bytes(target) | |
| if target == bytes(data): | |
| print("nothing to do") | |
| return | |
| if args.dry_run: | |
| print("dry run: would write changes") | |
| return | |
| backup = path + ".orig-5.0.0.1041720" | |
| if not os.path.exists(backup): | |
| with open(backup, "wb") as f: | |
| f.write(bytes(orig)) | |
| print(f"backup of the original: {backup}") | |
| tmp = path + ".tmp-patch" | |
| with open(tmp, "wb") as f: | |
| f.write(target) | |
| shutil.copymode(path, tmp) | |
| os.replace(tmp, path) | |
| print("restored the original" if args.restore else | |
| "patched: DLSS/DLSS-G" + (" + RT/path tracing" if args.rt else "")) | |
| if args.rt and not args.restore: | |
| print("WARNING: RT under Proton needs a vkd3d-proton with the RTAS prebuild-size workaround " | |
| "(HansKristian-Work/vkd3d-proton#3226); without it the GPU hangs (Xid 109). See README.") | |
| if __name__ == "__main__": | |
| main() |