diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2026-05-19 14:26:39 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2026-05-19 14:26:39 -0400 |
| commit | 19e5aefa2f41ebd4389bf27136adacf3e75d3502 (patch) | |
| tree | c7f2228aa45fd4325a68de6fe6f32864f383791c /src | |
| parent | 9e12c11b6189972ea04de454dad5c8554efe657a (diff) | |
| download | Decky-Framegen-feat/asset-manifest-fsr4-variants.tar.gz Decky-Framegen-feat/asset-manifest-fsr4-variants.zip | |
feat: add fsr4 runtime manifestsfeat/asset-manifest-fsr4-variants
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/index.ts | 53 | ||||
| -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 | ||||
| -rw-r--r-- | src/index.tsx | 15 | ||||
| -rw-r--r-- | src/utils/constants.ts | 18 |
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." }; |
