summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-04-03 09:52:39 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-04-03 09:52:39 -0400
commita6955e828b1dee7b14f8021a8a470dd51d77e33e (patch)
tree32824be9f9a76966f1d6fc38c50284bb5ec98e09
parentd1ce48eba2a38909f33df965ab672249156dc47d (diff)
downloadDecky-Framegen-a6955e828b1dee7b14f8021a8a470dd51d77e33e.tar.gz
Decky-Framegen-a6955e828b1dee7b14f8021a8a470dd51d77e33e.zip
feat: proxy DLL name picker
Expose the proxy DLL rename as a user-selectable option across all injection paths. Previously hardcoded to dxgi.dll with no way to change it short of manually prepending DLL=<name> to the Steam launch option. src/utils/constants.ts - Add PROXY_DLL_OPTIONS (7 entries matching _create_renamed_copies) each with a label and one-line hint - Add DEFAULT_PROXY_DLL constant (dxgi.dll) and ProxyDllValue type src/api/index.ts - runManualPatch now takes [directory, dll_name] so the chosen name reaches the backend src/components/OptiScalerControls.tsx - Own dllName state (default: dxgi.dll) - Render a DropdownItem (visible when installed) showing the 7 options with the selected option's hint as the description - Pass dllName down to both ClipboardCommands and ManualPatchControls src/components/ClipboardCommands.tsx - Accept dllName prop - Patch command is plain ~/fgmod/fgmod %command% for the default; prefixed DLL=<name> ~/fgmod/fgmod %command% for any other choice src/components/CustomPathOverride.tsx - Accept dllName prop - Pass it to runManualPatch - Manual launch cmd clipboard button builds WINEDLLOVERRIDES="<stem>=n,b" dynamically; emits bare SteamDeck=0 %command% for OptiScaler.asi (ASI loader path needs no Wine DLL override) main.py - Add VALID_DLL_NAMES set (whitelist matching the renames dir) - manual_patch_directory validates dll_name against the whitelist and returns an error for unknown values - _manual_patch_directory_impl accepts dll_name param; removes the hardcoded "dxgi.dll" line defaults/assets/fgmod.sh - Fix longstanding bug: WINEDLLOVERRIDES was hardcoded to dxgi=n,b regardless of the DLL= env var selection. Now derives the stem from $dll_name and skips the override entirely for .asi files.
-rwxr-xr-xdefaults/assets/fgmod.sh8
-rw-r--r--main.py19
-rw-r--r--src/api/index.ts2
-rw-r--r--src/components/ClipboardCommands.tsx17
-rw-r--r--src/components/CustomPathOverride.tsx13
-rw-r--r--src/components/OptiScalerControls.tsx23
-rw-r--r--src/utils/constants.ts14
7 files changed, 77 insertions, 19 deletions
diff --git a/defaults/assets/fgmod.sh b/defaults/assets/fgmod.sh
index efc1d59..99ea447 100755
--- a/defaults/assets/fgmod.sh
+++ b/defaults/assets/fgmod.sh
@@ -223,7 +223,13 @@ if [[ $# -gt 1 ]]; then
# Execute the original command
export SteamDeck=0
- export WINEDLLOVERRIDES="$WINEDLLOVERRIDES,dxgi=n,b"
+ # Build WINEDLLOVERRIDES from the actual proxy DLL name (strip extension to get the stem)
+ if [[ "$dll_name" == *.dll ]]; then
+ _wine_dll="${dll_name%.dll}"
+ export WINEDLLOVERRIDES="$WINEDLLOVERRIDES,${_wine_dll}=n,b"
+ unset _wine_dll
+ fi
+ # .asi files are loaded by an ASI loader — no WINEDLLOVERRIDES entry needed
# Filter out leading -- separators (from Steam launch options)
while [[ $# -gt 0 && "$1" == "--" ]]; do
diff --git a/main.py b/main.py
index 4092ab0..ea80561 100644
--- a/main.py
+++ b/main.py
@@ -10,6 +10,16 @@ from pathlib import Path
# Set to False or comment out this constant to skip the overwrite by default.
UPSCALER_OVERWRITE_ENABLED = True
+VALID_DLL_NAMES = {
+ "dxgi.dll",
+ "winmm.dll",
+ "dbghelp.dll",
+ "version.dll",
+ "wininet.dll",
+ "winhttp.dll",
+ "OptiScaler.asi",
+}
+
INJECTOR_FILENAMES = [
"dxgi.dll",
"winmm.dll",
@@ -523,7 +533,7 @@ class Plugin:
decky.logger.info(f"Resolved directory {directory} to absolute path {target}")
return target
- def _manual_patch_directory_impl(self, directory: Path) -> dict:
+ def _manual_patch_directory_impl(self, directory: Path, dll_name: str = "dxgi.dll") -> dict:
fgmod_path = Path(decky.HOME) / "fgmod"
if not fgmod_path.exists():
return {
@@ -538,7 +548,6 @@ class Plugin:
"message": "OptiScaler.dll not found in ~/fgmod. Reinstall OptiScaler.",
}
- dll_name = "dxgi.dll"
preserve_ini = True
try:
@@ -772,14 +781,16 @@ class Plugin:
async def log_error(self, error: str) -> None:
decky.logger.error(f"FRONTEND: {error}")
- async def manual_patch_directory(self, directory: str) -> dict:
+ async def manual_patch_directory(self, directory: str, dll_name: str = "dxgi.dll") -> dict:
+ if dll_name not in VALID_DLL_NAMES:
+ return {"status": "error", "message": f"Invalid proxy DLL name: {dll_name}"}
try:
target_dir = self._resolve_target_directory(directory)
except (FileNotFoundError, NotADirectoryError, PermissionError) as exc:
decky.logger.error(f"Manual patch validation failed: {exc}")
return {"status": "error", "message": str(exc)}
- return self._manual_patch_directory_impl(target_dir)
+ return self._manual_patch_directory_impl(target_dir, dll_name)
async def manual_unpatch_directory(self, directory: str) -> dict:
try:
diff --git a/src/api/index.ts b/src/api/index.ts
index df52fee..226f29f 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -28,7 +28,7 @@ export const getPathDefaults = callable<
>("get_path_defaults");
export const runManualPatch = callable<
- [string],
+ [string, string],
{ status: string; message?: string; output?: string }
>("manual_patch_directory");
diff --git a/src/components/ClipboardCommands.tsx b/src/components/ClipboardCommands.tsx
index 5a6f38f..b8cf6bf 100644
--- a/src/components/ClipboardCommands.tsx
+++ b/src/components/ClipboardCommands.tsx
@@ -1,20 +1,27 @@
import { SmartClipboardButton } from "./SmartClipboardButton";
+import { DEFAULT_PROXY_DLL } from "../utils/constants";
interface ClipboardCommandsProps {
pathExists: boolean | null;
+ dllName: string;
}
-export function ClipboardCommands({ pathExists }: ClipboardCommandsProps) {
+export function ClipboardCommands({ pathExists, dllName }: ClipboardCommandsProps) {
if (pathExists !== true) return null;
+ const launchCommand =
+ dllName === DEFAULT_PROXY_DLL
+ ? "~/fgmod/fgmod %command%"
+ : `DLL=${dllName} ~/fgmod/fgmod %command%`;
+
return (
<>
- <SmartClipboardButton
- command="~/fgmod/fgmod %command%"
+ <SmartClipboardButton
+ command={launchCommand}
buttonText="Copy Patch Command"
/>
-
- <SmartClipboardButton
+
+ <SmartClipboardButton
command="~/fgmod/fgmod-uninstaller.sh %command%"
buttonText="Copy Unpatch Command"
/>
diff --git a/src/components/CustomPathOverride.tsx b/src/components/CustomPathOverride.tsx
index ffc4b1f..14a0905 100644
--- a/src/components/CustomPathOverride.tsx
+++ b/src/components/CustomPathOverride.tsx
@@ -36,6 +36,7 @@ const ensureDirectory = (value: string) => {
interface ManualPatchControlsProps {
isAvailable: boolean;
onManualModeChange?: (enabled: boolean) => void;
+ dllName: string;
}
interface PickerState {
@@ -56,7 +57,7 @@ const formatResultMessage = (result: ApiResponse | null) => {
return result.message || result.output || "Operation failed.";
};
-export const ManualPatchControls = ({ isAvailable, onManualModeChange }: ManualPatchControlsProps) => {
+export const ManualPatchControls = ({ isAvailable, onManualModeChange, dllName }: ManualPatchControlsProps) => {
const [isEnabled, setEnabled] = useState(false);
const [defaults, setDefaults] = useState<PathDefaults>(INITIAL_DEFAULTS);
const [pickerState, setPickerState] = useState<PickerState>(INITIAL_PICKER_STATE);
@@ -165,7 +166,7 @@ export const ManualPatchControls = ({ isAvailable, onManualModeChange }: ManualP
try {
const response =
action === "patch"
- ? await runManualPatch(selectedPath)
+ ? await runManualPatch(selectedPath, dllName)
: await runManualUnpatch(selectedPath);
setOperationResult(response ?? { status: "error", message: "No response from backend." });
} catch (err) {
@@ -177,7 +178,7 @@ export const ManualPatchControls = ({ isAvailable, onManualModeChange }: ManualP
setBusy(false);
}
},
- [selectedPath]
+ [selectedPath, dllName]
);
const handleToggle = (value: boolean) => {
@@ -216,7 +217,11 @@ export const ManualPatchControls = ({ isAvailable, onManualModeChange }: ManualP
{canInteract && (
<>
<SmartClipboardButton
- command='WINEDLLOVERRIDES="dxgi=n,b" SteamDeck=0 %command%'
+ command={
+ dllName === "OptiScaler.asi"
+ ? "SteamDeck=0 %command%"
+ : `WINEDLLOVERRIDES="${dllName.replace(".dll", "")}=n,b" SteamDeck=0 %command%`
+ }
buttonText="Manual launch cmd"
/>
<PanelSectionRow>
diff --git a/src/components/OptiScalerControls.tsx b/src/components/OptiScalerControls.tsx
index 468683c..fb5d2f8 100644
--- a/src/components/OptiScalerControls.tsx
+++ b/src/components/OptiScalerControls.tsx
@@ -1,9 +1,9 @@
import { useState, useEffect } from "react";
-import { PanelSection } from "@decky/ui";
+import { DropdownItem, PanelSection, PanelSectionRow } from "@decky/ui";
import { runInstallFGMod, runUninstallFGMod } from "../api";
import { OperationResult } from "./ResultDisplay";
import { createAutoCleanupTimer } from "../utils";
-import { TIMEOUTS } from "../utils/constants";
+import { TIMEOUTS, PROXY_DLL_OPTIONS, DEFAULT_PROXY_DLL } from "../utils/constants";
import { InstallationStatus } from "./InstallationStatus";
import { OptiScalerHeader } from "./OptiScalerHeader";
import { ClipboardCommands } from "./ClipboardCommands";
@@ -23,6 +23,7 @@ export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerCont
const [installResult, setInstallResult] = useState<OperationResult | null>(null);
const [uninstallResult, setUninstallResult] = useState<OperationResult | null>(null);
const [manualModeEnabled, setManualModeEnabled] = useState(false);
+ const [dllName, setDllName] = useState<string>(DEFAULT_PROXY_DLL);
useEffect(() => {
if (installResult) {
return createAutoCleanupTimer(() => setInstallResult(null), TIMEOUTS.resultDisplay);
@@ -76,15 +77,29 @@ export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerCont
/>
<OptiScalerHeader pathExists={pathExists} />
-
+
+ {pathExists === true && (
+ <PanelSectionRow>
+ <DropdownItem
+ label="Proxy DLL name"
+ description={PROXY_DLL_OPTIONS.find((o) => o.value === dllName)?.hint}
+ menuLabel="Proxy DLL name"
+ selectedOption={dllName}
+ rgOptions={PROXY_DLL_OPTIONS.map((o) => ({ data: o.value, label: o.label }))}
+ onChange={(option) => setDllName(String(option.data))}
+ />
+ </PanelSectionRow>
+ )}
+
<ManualPatchControls
isAvailable={pathExists === true}
onManualModeChange={setManualModeEnabled}
+ dllName={dllName}
/>
{!manualModeEnabled && (
<>
- <ClipboardCommands pathExists={pathExists} />
+ <ClipboardCommands pathExists={pathExists} dllName={dllName} />
<InstructionCard pathExists={pathExists} />
</>
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 1f583c0..ce61263 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -45,6 +45,20 @@ export const STYLES = {
}
};
+// Proxy DLL name options for OptiScaler injection
+export const PROXY_DLL_OPTIONS = [
+ { value: "dxgi.dll", label: "dxgi.dll", hint: "Works for most DX12 games. Default." },
+ { value: "winmm.dll", label: "winmm.dll", hint: "Use when dxgi.dll conflicts with an existing game file." },
+ { value: "version.dll", label: "version.dll", hint: "Common fallback; works well with many launchers." },
+ { value: "dbghelp.dll", label: "dbghelp.dll", hint: "Use for debug helper hook paths." },
+ { value: "winhttp.dll", label: "winhttp.dll", hint: "Use when other DLL names conflict." },
+ { value: "wininet.dll", label: "wininet.dll", hint: "Use when other DLL names conflict." },
+ { value: "OptiScaler.asi", label: "OptiScaler.asi", hint: "For ASI loaders. Requires an ASI loader already installed in the game." },
+] as const;
+
+export type ProxyDllValue = typeof PROXY_DLL_OPTIONS[number]["value"];
+export const DEFAULT_PROXY_DLL: ProxyDllValue = "dxgi.dll";
+
// Common timeout values
export const TIMEOUTS = {
resultDisplay: 5000, // 5 seconds