summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/index.ts53
-rw-r--r--src/components/ClipboardCommands.tsx33
-rw-r--r--src/components/CustomPathOverride.tsx7
-rw-r--r--src/components/InstalledGamesSection.tsx4
-rw-r--r--src/components/OptiScalerControls.tsx106
-rw-r--r--src/components/SteamGamePatcher.tsx21
-rw-r--r--src/index.tsx15
-rw-r--r--src/utils/constants.ts18
8 files changed, 227 insertions, 30 deletions
diff --git a/src/api/index.ts b/src/api/index.ts
index c205a87..b1bf0b8 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -1,8 +1,15 @@
import { callable } from "@decky/api";
export const runInstallFGMod = callable<
- [],
- { status: string; message?: string; output?: string }
+ [selected_default_variant?: string],
+ {
+ status: string;
+ message?: string;
+ output?: string;
+ version?: string;
+ selected_default_variant?: string;
+ selected_default_variant_label?: string;
+ }
>("run_install_fgmod");
export const runUninstallFGMod = callable<
@@ -10,9 +17,27 @@ export const runUninstallFGMod = callable<
{ status: string; message?: string; output?: string }
>("run_uninstall_fgmod");
+export const setDefaultFsr4Variant = callable<
+ [selected_default_variant?: string],
+ {
+ status: string;
+ message?: string;
+ output?: string;
+ version?: string;
+ selected_default_variant?: string;
+ selected_default_variant_label?: string;
+ }
+>("set_default_fsr4_variant");
+
export const checkFGModPath = callable<
[],
- { exists: boolean }
+ {
+ exists: boolean;
+ version?: string | null;
+ selected_fsr4_variant?: string | null;
+ selected_fsr4_variant_label?: string | null;
+ install_manifest_present?: boolean;
+ }
>("check_fgmod_path");
export const listInstalledGames = callable<
@@ -28,8 +53,16 @@ export const getPathDefaults = callable<
>("get_path_defaults");
export const runManualPatch = callable<
- [string, string],
- { status: string; message?: string; output?: string }
+ [string, string, string],
+ {
+ status: string;
+ message?: string;
+ output?: string;
+ fsr4_variant?: string;
+ fsr4_variant_label?: string;
+ fsr4_upscaler_sha256?: string;
+ optiscaler_version?: string | null;
+ }
>("manual_patch_directory");
export const runManualUnpatch = callable<
@@ -49,11 +82,15 @@ export const getGameStatus = callable<
dll_name?: string | null;
target_dir?: string | null;
patched_at?: string | null;
+ optiscaler_version?: string | null;
+ fsr4_variant?: string | null;
+ fsr4_variant_label?: string | null;
+ fsr4_upscaler_sha256?: string | null;
}
>("get_game_status");
export const patchGame = callable<
- [appid: string, dll_name: string, current_launch_options: string],
+ [appid: string, dll_name: string, current_launch_options: string, fsr4_variant: string],
{
status: string;
message?: string;
@@ -63,6 +100,10 @@ export const patchGame = callable<
target_dir?: string;
launch_options?: string;
original_launch_options?: string;
+ optiscaler_version?: string | null;
+ fsr4_variant?: string;
+ fsr4_variant_label?: string;
+ fsr4_upscaler_sha256?: string;
}
>("patch_game");
diff --git a/src/components/ClipboardCommands.tsx b/src/components/ClipboardCommands.tsx
index e1f6ef9..124423e 100644
--- a/src/components/ClipboardCommands.tsx
+++ b/src/components/ClipboardCommands.tsx
@@ -3,9 +3,16 @@ import { SmartClipboardButton } from "./SmartClipboardButton";
interface ClipboardCommandsProps {
pathExists: boolean | null;
dllName: string;
+ manualModeEnabled?: boolean;
+ showLaunchOptions?: boolean;
}
-export function ClipboardCommands({ pathExists, dllName }: ClipboardCommandsProps) {
+export function ClipboardCommands({
+ pathExists,
+ dllName,
+ manualModeEnabled = false,
+ showLaunchOptions = true,
+}: ClipboardCommandsProps) {
if (pathExists !== true) return null;
const launchCmd =
@@ -14,9 +21,25 @@ export function ClipboardCommands({ pathExists, dllName }: ClipboardCommandsProp
: `WINEDLLOVERRIDES=${dllName.replace(".dll", "")}=n,b SteamDeck=0 %command%`;
return (
- <SmartClipboardButton
- command={launchCmd}
- buttonText="Copy launch options"
- />
+ <>
+ {showLaunchOptions ? (
+ <SmartClipboardButton
+ command={launchCmd}
+ buttonText="Copy launch options"
+ />
+ ) : null}
+ {manualModeEnabled ? (
+ <>
+ <SmartClipboardButton
+ command="~/fgmod/fgmod %command%"
+ buttonText="Copy Patch Command"
+ />
+ <SmartClipboardButton
+ command="~/fgmod/fgmod-uninstaller.sh %command%"
+ buttonText="Copy Unpatch Command"
+ />
+ </>
+ ) : null}
+ </>
);
}
diff --git a/src/components/CustomPathOverride.tsx b/src/components/CustomPathOverride.tsx
index 4effc6c..af47735 100644
--- a/src/components/CustomPathOverride.tsx
+++ b/src/components/CustomPathOverride.tsx
@@ -37,6 +37,7 @@ interface ManualPatchControlsProps {
isAvailable: boolean;
onManualModeChange?: (enabled: boolean) => void;
dllName: string;
+ fsr4Variant: string;
}
interface PickerState {
@@ -57,7 +58,7 @@ const formatResultMessage = (result: ApiResponse | null) => {
return result.message || result.output || "Operation failed.";
};
-export const ManualPatchControls = ({ isAvailable, onManualModeChange, dllName }: ManualPatchControlsProps) => {
+export const ManualPatchControls = ({ isAvailable, onManualModeChange, dllName, fsr4Variant }: ManualPatchControlsProps) => {
const [isEnabled, setEnabled] = useState(false);
const [defaults, setDefaults] = useState<PathDefaults>(INITIAL_DEFAULTS);
const [pickerState, setPickerState] = useState<PickerState>(INITIAL_PICKER_STATE);
@@ -166,7 +167,7 @@ export const ManualPatchControls = ({ isAvailable, onManualModeChange, dllName }
try {
const response =
action === "patch"
- ? await runManualPatch(selectedPath, dllName)
+ ? await runManualPatch(selectedPath, dllName, fsr4Variant)
: await runManualUnpatch(selectedPath);
setOperationResult(response ?? { status: "error", message: "No response from backend." });
} catch (err) {
@@ -178,7 +179,7 @@ export const ManualPatchControls = ({ isAvailable, onManualModeChange, dllName }
setBusy(false);
}
},
- [selectedPath, dllName]
+ [selectedPath, dllName, fsr4Variant]
);
const handleToggle = (value: boolean) => {
diff --git a/src/components/InstalledGamesSection.tsx b/src/components/InstalledGamesSection.tsx
index 04d653b..e0e2677 100644
--- a/src/components/InstalledGamesSection.tsx
+++ b/src/components/InstalledGamesSection.tsx
@@ -48,7 +48,7 @@ export function InstalledGamesSection() {
strCancelButtonText="Cancel"
onOK={async () => {
try {
- await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod %COMMAND%');
+ await SteamClient.Apps.SetAppLaunchOptions(Number(selectedGame.appid), '~/fgmod/fgmod %COMMAND%');
setResult(`Frame generation enabled for ${selectedGame.name}. Launch the game, enable DLSS in graphics settings, then press Insert to access OptiScaler options.`);
} catch (error) {
logError('handlePatchClick: ' + String(error));
@@ -63,7 +63,7 @@ export function InstalledGamesSection() {
if (!selectedGame) return;
try {
- await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod-uninstaller.sh %COMMAND%');
+ await SteamClient.Apps.SetAppLaunchOptions(Number(selectedGame.appid), '~/fgmod/fgmod-uninstaller.sh %COMMAND%');
setResult(`Frame generation will be disabled on next launch of ${selectedGame.name}.`);
} catch (error) {
logError('handleUnpatchClick: ' + String(error));
diff --git a/src/components/OptiScalerControls.tsx b/src/components/OptiScalerControls.tsx
index f88e8f9..2167fcd 100644
--- a/src/components/OptiScalerControls.tsx
+++ b/src/components/OptiScalerControls.tsx
@@ -1,9 +1,9 @@
import { useState, useEffect } from "react";
-import { DropdownItem, PanelSection, PanelSectionRow } from "@decky/ui";
-import { runInstallFGMod, runUninstallFGMod } from "../api";
+import { DropdownItem, Field, PanelSection, PanelSectionRow, ToggleField } from "@decky/ui";
+import { runInstallFGMod, runUninstallFGMod, setDefaultFsr4Variant } from "../api";
import { OperationResult } from "./ResultDisplay";
import { createAutoCleanupTimer } from "../utils";
-import { TIMEOUTS, PROXY_DLL_OPTIONS, DEFAULT_PROXY_DLL } from "../utils/constants";
+import { TIMEOUTS, PROXY_DLL_OPTIONS, DEFAULT_PROXY_DLL, FSR4_VARIANT_OPTIONS, DEFAULT_FSR4_VARIANT } from "../utils/constants";
import { InstallationStatus } from "./InstallationStatus";
import { OptiScalerHeader } from "./OptiScalerHeader";
import { ClipboardCommands } from "./ClipboardCommands";
@@ -13,18 +13,31 @@ import { UninstallButton } from "./UninstallButton";
import { ManualPatchControls } from "./CustomPathOverride";
import { SteamGamePatcher } from "./SteamGamePatcher";
+interface FgmodInfo {
+ exists: boolean;
+ version?: string | null;
+ selected_fsr4_variant?: string | null;
+ selected_fsr4_variant_label?: string | null;
+ install_manifest_present?: boolean;
+}
+
interface OptiScalerControlsProps {
pathExists: boolean | null;
setPathExists: (exists: boolean | null) => void;
+ fgmodInfo?: FgmodInfo | null;
}
-export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerControlsProps) {
+export function OptiScalerControls({ pathExists, setPathExists, fgmodInfo }: OptiScalerControlsProps) {
const [installing, setInstalling] = useState(false);
const [uninstalling, setUninstalling] = useState(false);
const [installResult, setInstallResult] = useState<OperationResult | null>(null);
const [uninstallResult, setUninstallResult] = useState<OperationResult | null>(null);
- const [manualModeEnabled, setManualModeEnabled] = useState(false);
+ const [advancedModeEnabled, setAdvancedModeEnabled] = useState(false);
+ const [manualClipboardModeEnabled, setManualClipboardModeEnabled] = useState(false);
const [dllName, setDllName] = useState<string>(DEFAULT_PROXY_DLL);
+ const [fsr4Variant, setFsr4Variant] = useState<string>(DEFAULT_FSR4_VARIANT);
+ const [fsr4VariantTouched, setFsr4VariantTouched] = useState(false);
+ const [switchingVariant, setSwitchingVariant] = useState(false);
useEffect(() => {
if (installResult) {
return createAutoCleanupTimer(() => setInstallResult(null), TIMEOUTS.resultDisplay);
@@ -39,10 +52,17 @@ export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerCont
return () => {}; // Ensure a cleanup function is always returned
}, [uninstallResult]);
+ useEffect(() => {
+ const installedVariant = fgmodInfo?.selected_fsr4_variant;
+ if (!fsr4VariantTouched && installedVariant && FSR4_VARIANT_OPTIONS.some((option) => option.value === installedVariant)) {
+ setFsr4Variant(installedVariant);
+ }
+ }, [fgmodInfo?.selected_fsr4_variant, fsr4VariantTouched]);
+
const handleInstallClick = async () => {
try {
setInstalling(true);
- const result = await runInstallFGMod();
+ const result = await runInstallFGMod(fsr4Variant);
setInstallResult(result);
if (result.status === "success") {
setPathExists(true);
@@ -69,6 +89,31 @@ export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerCont
}
};
+ const handleFsr4VariantChange = async (nextVariant: string) => {
+ const previousVariant = fsr4Variant;
+ setFsr4Variant(nextVariant);
+ setFsr4VariantTouched(true);
+
+ if (pathExists !== true) return;
+
+ try {
+ setSwitchingVariant(true);
+ const result = await setDefaultFsr4Variant(nextVariant);
+ if (result.status !== "success") {
+ throw new Error(result.message || result.output || "Failed to switch default FSR4 runtime.");
+ }
+ setFsr4Variant(result.selected_default_variant || nextVariant);
+ setFsr4VariantTouched(false);
+ } catch (error) {
+ console.error(error);
+ setFsr4Variant(previousVariant);
+ } finally {
+ setSwitchingVariant(false);
+ }
+ };
+
+ const installedVariantLabel = fgmodInfo?.selected_fsr4_variant_label || FSR4_VARIANT_OPTIONS.find((option) => option.value === fsr4Variant)?.label;
+
return (
<PanelSection>
<InstallationStatus
@@ -79,6 +124,28 @@ export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerCont
<OptiScalerHeader pathExists={pathExists} />
+ <PanelSectionRow>
+ <DropdownItem
+ label="Default FSR4 runtime"
+ description={FSR4_VARIANT_OPTIONS.find((option) => option.value === fsr4Variant)?.hint}
+ menuLabel="Default FSR4 runtime"
+ selectedOption={fsr4Variant}
+ rgOptions={FSR4_VARIANT_OPTIONS.map((option) => ({ data: option.value, label: option.label }))}
+ disabled={installing || uninstalling || switchingVariant}
+ onChange={(option) => {
+ void handleFsr4VariantChange(String(option.data));
+ }}
+ />
+ </PanelSectionRow>
+
+ {pathExists === true && fgmodInfo?.version && installedVariantLabel && (
+ <PanelSectionRow>
+ <Field label="Installed bundle" description={`OptiScaler ${fgmodInfo.version}`}>
+ {installedVariantLabel}
+ </Field>
+ </PanelSectionRow>
+ )}
+
{pathExists === true && (
<PanelSectionRow>
<DropdownItem
@@ -93,18 +160,39 @@ export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerCont
)}
{pathExists === true && (
- <SteamGamePatcher dllName={dllName} />
+ <SteamGamePatcher dllName={dllName} fsr4Variant={fsr4Variant} />
)}
<ClipboardCommands pathExists={pathExists} dllName={dllName} />
+ {pathExists === true && (
+ <PanelSectionRow>
+ <ToggleField
+ label="Manual Mode"
+ description="Show wrapper command clipboard buttons for patching and unpatching through ~/fgmod scripts."
+ checked={manualClipboardModeEnabled}
+ onChange={setManualClipboardModeEnabled}
+ />
+ </PanelSectionRow>
+ )}
+
+ {pathExists === true && manualClipboardModeEnabled ? (
+ <ClipboardCommands
+ pathExists={pathExists}
+ dllName={dllName}
+ manualModeEnabled
+ showLaunchOptions={false}
+ />
+ ) : null}
+
<ManualPatchControls
isAvailable={pathExists === true}
- onManualModeChange={setManualModeEnabled}
+ onManualModeChange={setAdvancedModeEnabled}
dllName={dllName}
+ fsr4Variant={fsr4Variant}
/>
- {!manualModeEnabled && (
+ {!advancedModeEnabled && (
<InstructionCard pathExists={pathExists} />
)}
<OptiScalerWiki pathExists={pathExists} />
diff --git a/src/components/SteamGamePatcher.tsx b/src/components/SteamGamePatcher.tsx
index b17ed48..2d3b0fa 100644
--- a/src/components/SteamGamePatcher.tsx
+++ b/src/components/SteamGamePatcher.tsx
@@ -50,6 +50,10 @@ type GameStatus = {
dll_name?: string | null;
target_dir?: string | null;
patched_at?: string | null;
+ optiscaler_version?: string | null;
+ fsr4_variant?: string | null;
+ fsr4_variant_label?: string | null;
+ fsr4_upscaler_sha256?: string | null;
};
// ─── Module-level state persistence ──────────────────────────────────────────
@@ -60,9 +64,10 @@ let lastSelectedAppId = "";
interface SteamGamePatcherProps {
dllName: string;
+ fsr4Variant: string;
}
-export function SteamGamePatcher({ dllName }: SteamGamePatcherProps) {
+export function SteamGamePatcher({ dllName, fsr4Variant }: SteamGamePatcherProps) {
const [games, setGames] = useState<GameEntry[]>([]);
const [gamesLoading, setGamesLoading] = useState(true);
const [selectedAppId, setSelectedAppId] = useState<string>(() => lastSelectedAppId);
@@ -165,7 +170,7 @@ export function SteamGamePatcher({ dllName }: SteamGamePatcherProps) {
} catch {
// non-fatal: proceed without current launch options
}
- const result = await patchGame(selectedAppId, dllName, currentLaunchOptions);
+ const result = await patchGame(selectedAppId, dllName, currentLaunchOptions, fsr4Variant);
if (result.status !== "success") throw new Error(result.message || "Patch failed.");
setAppLaunchOptions(Number(selectedAppId), result.launch_options || "");
const msg = result.message || `Patched ${selectedGame.name}.`;
@@ -179,7 +184,7 @@ export function SteamGamePatcher({ dllName }: SteamGamePatcherProps) {
} finally {
setBusyAction(null);
}
- }, [busyAction, dllName, loadStatus, selectedAppId, selectedGame]);
+ }, [busyAction, dllName, fsr4Variant, loadStatus, selectedAppId, selectedGame]);
const handleUnpatch = useCallback(async () => {
if (!selectedGame || !selectedAppId || busyAction) return;
@@ -258,6 +263,16 @@ export function SteamGamePatcher({ dllName }: SteamGamePatcherProps) {
</PanelSectionRow>
<PanelSectionRow>
+ <Field {...focusableFieldProps} label="FSR4 runtime">
+ {gameStatus?.patched
+ ? (gameStatus?.fsr4_variant_label || "Unknown")
+ : (fsr4Variant === "rdna4-native"
+ ? "Will patch with Native bundle / RDNA4"
+ : "Will patch with Steam Deck / RDNA2-3 optimized")}
+ </Field>
+ </PanelSectionRow>
+
+ <PanelSectionRow>
<ButtonItem layout="below" disabled={!canPatch} onClick={handlePatch}>
{patchButtonLabel}
</ButtonItem>
diff --git a/src/index.tsx b/src/index.tsx
index fb9635d..4a9a9f6 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -7,8 +7,17 @@ import { checkFGModPath } from "./api";
import { safeAsyncOperation } from "./utils";
import { TIMEOUTS } from "./utils/constants";
+type FgmodInfo = {
+ exists: boolean;
+ version?: string | null;
+ selected_fsr4_variant?: string | null;
+ selected_fsr4_variant_label?: string | null;
+ install_manifest_present?: boolean;
+};
+
function MainContent() {
const [pathExists, setPathExists] = useState<boolean | null>(null);
+ const [fgmodInfo, setFgmodInfo] = useState<FgmodInfo | null>(null);
useEffect(() => {
const checkPath = async () => {
@@ -16,7 +25,10 @@ function MainContent() {
async () => await checkFGModPath(),
'MainContent -> checkPath'
);
- if (result) setPathExists(result.exists);
+ if (result) {
+ setFgmodInfo(result);
+ setPathExists(result.exists);
+ }
};
checkPath(); // Initial check
@@ -29,6 +41,7 @@ function MainContent() {
<OptiScalerControls
pathExists={pathExists}
setPathExists={setPathExists}
+ fgmodInfo={fgmodInfo}
/>
{pathExists === true ? (
<>
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 7fa6970..8444240 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -59,6 +59,22 @@ export const PROXY_DLL_OPTIONS = [
export type ProxyDllValue = typeof PROXY_DLL_OPTIONS[number]["value"];
export const DEFAULT_PROXY_DLL: ProxyDllValue = "dxgi.dll";
+export const FSR4_VARIANT_OPTIONS = [
+ {
+ value: "rdna23-int8",
+ label: "Steam Deck / RDNA2-3 optimized",
+ hint: "Uses the bundled FSR4 INT8 4.0.2c override. Recommended for Steam Deck and other non-RDNA4 systems.",
+ },
+ {
+ value: "rdna4-native",
+ label: "Native bundle / RDNA4",
+ hint: "Uses the amd_fidelityfx_upscaler_dx12.dll that ships inside the OptiScaler 0.9.2a bundle.",
+ },
+] as const;
+
+export type Fsr4VariantValue = typeof FSR4_VARIANT_OPTIONS[number]["value"];
+export const DEFAULT_FSR4_VARIANT: Fsr4VariantValue = "rdna23-int8";
+
// Common timeout values
export const TIMEOUTS = {
resultDisplay: 5000, // 5 seconds
@@ -76,5 +92,5 @@ export const MESSAGES = {
installSuccess: "OptiScaler mod setup successfully!",
uninstallSuccess: "OptiScaler mod removed successfully.",
instructionTitle: "How to Use:",
- instructionText: "Click 'Copy Patch Command' or 'Copy Unpatch Command', then go to your game's properties, and paste the command into the Launch Options field.\n\nIn-game: Enable DLSS in graphics settings to unlock FSR 3.1/XeSS 2.0 in DirectX12 Games.\n\nFor extended OptiScaler options, assign a back button to a keyboard's 'Insert' key."
+ instructionText: "Use 'Copy launch options' for the standard direct launch-options method. If you want the wrapper commands instead, enable Manual Mode to reveal 'Copy Patch Command' and 'Copy Unpatch Command'.\n\nIn-game: Enable DLSS in graphics settings to unlock FSR 3.1/XeSS 2.0 in DirectX12 Games.\n\nFor extended OptiScaler options, assign a back button to a keyboard's 'Insert' key."
};