#!/usr/bin/env bash set -euo pipefail # Launches a Windows game through a GE-Proton build's integrated OptiScaler # (PROTON_USE_OPTISCALER) with OptiScaler's DLSS backend routed into the d4r # shim, i.e. official NVIDIA DLSS executed through the Wine CUDA bridge and # ZLUDA on the AMD GPU. # # usage: run_game_d4r_optiscaler_proton.sh PROTON_DIR COMPAT_DATA_DIR GAME_EXE [ARGS...] # # The Proton installation and game directory are not modified. The prefix's # system32/umu/OptiScaler.ini (rewritten by GE-Proton from # PROTON_OPTISCALER_CONFIG) is restored when the game exits, and the real # DXVK-NVAPI is placed next to the identity bridge as d4r_nvapi64_real.dll. # With D4R_VKD3D_DIR (a directory holding d3d12.dll and d3d12core.dll, e.g. the # d4r-patched vkd3d-proton), those replace Proton's vkd3d-proton in the prefix # for this launch; the prefix's previous copies are backed up to the log # directory and restored when the game exits. # With D4R_UE_ENGINE_INI (the game's Saved/Config/Windows/Engine.ini) and # D4R_UE_CVARS ("r.A=0;r.B=1"), those console variables are added to its # [SystemSettings] for this launch only: an existing file is backed up to the # log directory and restored on exit, a new one is removed again. Unreal games # apply tonemapper sharpening and chromatic fringe after the upscaler; e.g. # Townfall's r.Tonemapper.Sharpen and r.SceneColorFringeQuality turn DLSS's # clean thin lines into haloed, stippled and beaded ones. # Logs go to D4R_GAME_LOG_DIR (default: a timestamped capture directory). if [[ $# -lt 3 ]]; then printf 'usage: %s PROTON_DIR COMPAT_DATA_DIR GAME_EXE [ARGS...]\n' "$0" >&2 exit 2 fi ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PROTON_DIR="$(realpath "$1")" COMPAT_DIR="$(realpath "$2")" GAME_EXE="$(realpath "$3")" shift 3 # User settings from d4r.ini (config/d4r.ini.default documents them); explicit environment wins. # D4R_NO_CONFIG=1 ignores the file (automated tests); D4R_CONFIG picks another file. if [[ "${D4R_NO_CONFIG:-0}" != 1 ]]; then python3 "$ROOT/scripts/d4r_config.py" --print >&2 || true D4R_CONFIG_EXPORTS="$(python3 "$ROOT/scripts/d4r_config.py")" || exit 2 eval "$D4R_CONFIG_EXPORTS" fi source "$ROOT/scripts/d4r_proton_env.sh" PREFIX="$COMPAT_DIR/pfx" UMU="$PREFIX/drive_c/windows/system32/umu" LOG_DIR="${D4R_GAME_LOG_DIR:-$HOME/.cache/d4r-dlss-captures/game-$(basename "$GAME_EXE" .exe)-$(date +%Y%m%d-%H%M%S)}" mkdir -p "$LOG_DIR" [[ -d "$PREFIX" ]] || { printf 'No Proton prefix at %s\n' "$PREFIX" >&2; exit 2; } # A second launch would join the running wineserver, and whichever run exits # first would restore its OptiScaler.ini backup underneath the other. SERVER_DIR="/tmp/.wine-$(id -u)/server-$(stat -c '%D' "$PREFIX" | sed 's/^0*//')-$(printf '%x' "$(stat -c '%i' "$PREFIX")")" if [[ -S "$SERVER_DIR/socket" ]] && fuser "$SERVER_DIR/socket" >/dev/null 2>&1; then printf 'A wineserver is already running for %s; close that game first\n' "$PREFIX" >&2 exit 2 fi "$ROOT/scripts/build_d4r_nvapi_identity.sh" >/dev/null NVAPI_REAL="$PROTON_DIR/files/lib/wine/nvapi/x86_64-windows/nvapi64.dll" [[ -f "$NVAPI_REAL" ]] || { printf 'Cannot find DXVK-NVAPI at %s\n' "$NVAPI_REAL" >&2; exit 2; } cp -f "$NVAPI_REAL" "$PREFIX/drive_c/windows/system32/d4r_nvapi64_real.dll" # Temporary launcher: identical to the Proton build except that the NVAPI copy # installs the identity bridge (so OptiScaler enables its DLSS backend). LAUNCHER_DIR="$(mktemp -d "${TMPDIR:-/tmp}/d4r-game-proton.XXXXXXXX")" for entry in "$PROTON_DIR"/*; do [[ "$(basename "$entry")" == proton ]] || ln -s "$entry" "$LAUNCHER_DIR/$(basename "$entry")" done cp -f "$PROTON_DIR/proton" "$LAUNCHER_DIR/proton" LAUNCHER="$LAUNCHER_DIR/proton" python3 - <<'PY' import os from pathlib import Path path = Path(os.environ["LAUNCHER"]) source = path.read_text() variants = [ 'g_proton.arch_pe_dir("wine/nvapi", False) + "nvapi64.dll"', 'g_proton.arch_pe_dir("wine/" + nvapi_path, False) + "nvapi64.dll"', ] for old in variants: target = f'try_copy({old}, "drive_c/windows/system32",' if source.count(target) == 1: new = f'try_copy(os.environ.get("D4R_NVAPI_IDENTITY_DLL", {old}), "drive_c/windows/system32",' path.write_text(source.replace(target, new)) break else: raise SystemExit("Proton NVAPI copy point not found; cannot stage identity bridge") if os.environ.get("D4R_VKD3D_DIR"): source = path.read_text() old = 'try_copy(g_proton.arch_pe_dir("wine/vkd3d-proton", False) + f + ".dll", "drive_c/windows/system32",' new = ('try_copy(os.path.join(os.environ["D4R_VKD3D_DIR"], f + ".dll"), "drive_c/windows/system32",') if source.count(old) != 1: raise SystemExit("Proton vkd3d-proton copy point not found; cannot stage D4R_VKD3D_DIR") path.write_text(source.replace(old, new)) PY # GE-Proton rewrites OptiScaler.ini from PROTON_OPTISCALER_CONFIG; keep the # user's configuration and put it back afterwards. INI="$UMU/OptiScaler.ini" [[ -f "$INI" ]] && cp -f "$INI" "$LOG_DIR/OptiScaler.ini.before" VKD3D_BACKUP="$LOG_DIR/vkd3d-prefix-backup" if [[ -n "${D4R_VKD3D_DIR:-}" ]]; then export D4R_VKD3D_DIR="$(realpath "$D4R_VKD3D_DIR")" for dll in d3d12.dll d3d12core.dll; do [[ -f "$D4R_VKD3D_DIR/$dll" ]] || { printf 'D4R_VKD3D_DIR lacks %s\n' "$dll" >&2; exit 2; } done mkdir -p "$VKD3D_BACKUP" for dll in d3d12.dll d3d12core.dll; do [[ -f "$PREFIX/drive_c/windows/system32/$dll" ]] && cp -f "$PREFIX/drive_c/windows/system32/$dll" "$VKD3D_BACKUP/$dll" done fi ENGINE_INI="${D4R_UE_ENGINE_INI:-}" ENGINE_INI_STATE="" restore_prefix() { [[ -f "$LOG_DIR/OptiScaler.ini.before" ]] && cp -f "$LOG_DIR/OptiScaler.ini.before" "$INI" case "$ENGINE_INI_STATE" in restore) cp -f "$LOG_DIR/Engine.ini.before" "$ENGINE_INI" ;; remove) rm -f "$ENGINE_INI" ;; esac if [[ -n "${D4R_VKD3D_DIR:-}" ]]; then for dll in d3d12.dll d3d12core.dll; do [[ -f "$VKD3D_BACKUP/$dll" ]] && cp -f "$VKD3D_BACKUP/$dll" "$PREFIX/drive_c/windows/system32/$dll" done fi rm -rf "$LAUNCHER_DIR" } trap restore_prefix EXIT if [[ -n "$ENGINE_INI" && -n "${D4R_UE_CVARS:-}" ]]; then if [[ -f "$ENGINE_INI" ]]; then cp -f "$ENGINE_INI" "$LOG_DIR/Engine.ini.before" ENGINE_INI_STATE=restore else mkdir -p "$(dirname "$ENGINE_INI")" ENGINE_INI_STATE=remove fi { printf '\n[SystemSettings]\n'; tr ';' '\n' <<<"$D4R_UE_CVARS"; printf '\n'; } >> "$ENGINE_INI" fi OPTI_CONFIG="Upscalers.Dx12Upscaler=dlss;DLSS.Enabled=true" OPTI_CONFIG+=";Libraries.NvngxPath=$(d4r_winpath "$D4R_RUNTIME_DIR/bin/d4r_nvngx.dll")" OPTI_CONFIG+=";Libraries.NvngxDlssPath=$(d4r_winpath "$D4R_RUNTIME_DIR/bin")" # Game-side DLSS inputs stay hooked; only NGX loads outside the game directory # are redirected, which lets the shim request the official core by an # exe-relative path. OPTI_CONFIG+=";Inputs.EnableDlssInputs=true;Hooks.HookOriginalNvngxOnly=true" OPTI_CONFIG+=";NvApi.OverrideNvapiDll=false;FrameGen.Enabled=false;FrameGen.FGInput=nofg;FrameGen.FGOutput=nofg" OPTI_CONFIG+=";Log.LogToFile=true;Log.LogLevel=${D4R_OPTISCALER_LOG_LEVEL:-2}" # Detailed FPS overlay with frame-time graph (the user's own INI default too). OPTI_CONFIG+=";Menu.ShowFps=${D4R_OPTISCALER_SHOW_FPS:-true};Menu.FpsOverlayType=${D4R_OPTISCALER_FPS_OVERLAY_TYPE:-3}" OPTI_CONFIG+="${D4R_EXTRA_OPTISCALER_CONFIG:+;$D4R_EXTRA_OPTISCALER_CONFIG}" export PROTON_USE_OPTISCALER=1 export PROTON_OPTISCALER_CONFIG="$OPTI_CONFIG" export D4R_NVAPI_IDENTITY_DLL="$ROOT/build/nvapi64.dll" export DXVK_CONFIG="${DXVK_CONFIG:-dxgi.customVendorId = 10de}" export D4R_SHIM_LOG="$(d4r_winpath "$LOG_DIR/d4r_nvngx.log")" export STEAM_COMPAT_CLIENT_INSTALL_PATH="${STEAM_COMPAT_CLIENT_INSTALL_PATH:-$HOME/.local/share/Steam}" export STEAM_COMPAT_DATA_PATH="$COMPAT_DIR" export PROTON_LOG=1 PROTON_LOG_DIR="$LOG_DIR" export WINEDEBUG="${WINEDEBUG:--all}" # Games load dozens of modules per launch; do not capture them unless asked. export D4R_CUDA_CAPTURE="${D4R_CUDA_CAPTURE:-0}" printf 'd4r game launch: %s\nlogs: %s\n' "$GAME_EXE" "$LOG_DIR" status=0 (cd "$(dirname "$GAME_EXE")" && "$LAUNCHER_DIR/proton" waitforexitandrun "$GAME_EXE" "$@") \ >"$LOG_DIR/launcher.log" 2>&1 || status=$? cp -f "$UMU/OptiScaler.log" "$LOG_DIR/OptiScaler.log" 2>/dev/null || true printf 'game exited with status %s\n' "$status" exit "$status"