diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/ClipboardCommands.tsx | 33 | ||||
| -rw-r--r-- | src/components/CustomPathOverride.tsx | 7 | ||||
| -rw-r--r-- | src/components/InstalledGamesSection.tsx | 4 | ||||
| -rw-r--r-- | src/components/OptiScalerControls.tsx | 106 | ||||
| -rw-r--r-- | src/components/SteamGamePatcher.tsx | 21 |
5 files changed, 149 insertions, 22 deletions
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> |
