Skip to content

Commit 03a1a0b

Browse files
committed
fix(library-guard): move backup section outside advanced gate; fix non-steam not-in-library
- Move Backup & Restore section to always-visible end of normal settings (was gated behind advancedEnabled toggle) - Remove advancedEnabled guard from local-data screenshot automation handler - Fix non-Steam games (Heroic/GOG/etc.) incorrectly showing Not in library: - gamePageBadge was replacing shortcut CRC32 appId with resolved Steam store ID before navigating, causing ConfigureTab to lose isShortcut context - Pass original appId so ConfigureTab's own resolution flow runs correctly - Add NON_STEAM_ID_THRESHOLD to isInLibrary check as belt-and-suspenders guard - Export NON_STEAM_ID_THRESHOLD from steamApps.ts for reuse
1 parent 51dce3b commit 03a1a0b

4 files changed

Lines changed: 106 additions & 101 deletions

File tree

src/components/tabs/ConfigureTab.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { VoteTotals } from '../../lib/cache';
1414
import { getSetting, setSetting } from '../../lib/settings';
1515
import type { CdnReport, ScoredReport, SystemInfo, GpuVendor } from '../../types';
1616
import { logFrontendEvent } from '../../lib/logger';
17-
import { getLaunchOptionsFromDetails, getSteamAppDetails, getSteamAppOverview, isSteamShortcutApp } from '../../lib/steamApps';
17+
import { getLaunchOptionsFromDetails, getSteamAppDetails, getSteamAppOverview, isSteamShortcutApp, NON_STEAM_ID_THRESHOLD } from '../../lib/steamApps';
1818
import { getGameSource } from '../../lib/gameSource';
1919
import { checkProtonVersionAvailability, getProtonGeManagerState, installProtonGe } from '../../lib/compatTools';
2020
import {
@@ -518,7 +518,20 @@ function ConfigureTabContent({ appId, appName, sysInfo }: Props) {
518518
const isShortcut = appId ? isSteamShortcutApp(appId) : false;
519519
// Non-shortcut apps not found in the local library overview are store-only views;
520520
// applying launch options requires the game to be in the library.
521-
const isInLibrary = isShortcut || (appId ? getSteamAppOverview(appId) !== null : true);
521+
const isInLibrary = isShortcut || (appId !== null && appId !== undefined && appId >= NON_STEAM_ID_THRESHOLD) || (appId ? (
522+
getSteamAppOverview(appId) !== null ||
523+
(() => {
524+
try {
525+
const collection = (globalThis as any).collectionStore?.allAppsCollection;
526+
const allApps = Array.isArray(collection?.allApps)
527+
? collection.allApps
528+
: collection?.apps && Symbol.iterator in collection.apps
529+
? Array.from(collection.apps)
530+
: [];
531+
return allApps.some((app: any) => Number(app?.appid) === appId);
532+
} catch { return false; }
533+
})()
534+
) : true);
522535
// When appName prop is empty for a non-Steam shortcut, try collectionStore
523536
const effectiveAppName = (appName || (appId && isShortcut ? lookupAppNameFromCollection(appId) : '')) ?? '';
524537
// null = resolution pending, 'none' = tried but no Steam match, number = resolved ID

src/components/tabs/GeneralSettingsTab.tsx

Lines changed: 87 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -484,13 +484,8 @@ const [cefDebuggingEnabled, setCefDebuggingEnabledLocal] = useState(false);
484484
}), []);
485485

486486
useEffect(() => registerScreenshotAutomationHandler('settings/local-data', async () => {
487-
if (!advancedEnabled) {
488-
setAdvancedEnabled(true);
489-
setSetting(ADVANCED_SETTINGS_KEY, true);
490-
await new Promise((resolve) => window.setTimeout(resolve, 150));
491-
}
492487
localDataSectionRef.current?.scrollIntoView({ block: 'center' });
493-
}), [advancedEnabled]);
488+
}), []);
494489

495490
useEffect(() => registerScreenshotAutomationHandler('settings/account-linking', async () => {
496491
setPluginLinkStatus({
@@ -997,6 +992,92 @@ const [cefDebuggingEnabled, setCefDebuggingEnabledLocal] = useState(false);
997992
</div>
998993
</div>
999994

995+
{/* backup & restore -- always visible, at end of normal settings */}
996+
<div ref={localDataSectionRef} style={sectionStyle()}>
997+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12, marginBottom: 4, marginRight: 8 }}>
998+
<div style={{ fontSize: 15, fontWeight: 700, color: '#eef7ff' }}>
999+
{extras.localDataSection()}
1000+
</div>
1001+
{backupStatusMessage && (
1002+
<div
1003+
style={{
1004+
fontSize: 10,
1005+
color:
1006+
backupStatusTone === 'success'
1007+
? '#9dc4e8'
1008+
: backupStatusTone === 'error'
1009+
? '#f3b3b3'
1010+
: '#7a9bb5',
1011+
textAlign: 'right',
1012+
maxWidth: 360,
1013+
lineHeight: 1.35,
1014+
}}
1015+
>
1016+
{backupStatusMessage}
1017+
</div>
1018+
)}
1019+
</div>
1020+
<div style={{ fontSize: 11, color: '#7a9bb5', margin: '0 8px 10px' }}>
1021+
{extras.localDataSectionDescription()}
1022+
</div>
1023+
<div
1024+
style={{
1025+
margin: '0 8px 12px',
1026+
padding: '10px 12px',
1027+
borderRadius: 8,
1028+
border: '1px solid rgba(255,255,255,0.08)',
1029+
background: 'rgba(255,255,255,0.03)',
1030+
}}
1031+
>
1032+
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10, marginBottom: 4 }}>
1033+
<div style={{ fontSize: 11, fontWeight: 700, color: '#eef7ff', minWidth: 0, flex: 1 }}>
1034+
{extras.anonymousClientId()}
1035+
</div>
1036+
<div style={{ width: 76, flexShrink: 0, overflow: 'hidden', borderRadius: 8 }}>
1037+
<DialogButton
1038+
onClick={() => void handleCopyAnonymousClientId()}
1039+
disabled={!anonymousClientId}
1040+
style={{
1041+
minWidth: 0,
1042+
width: '100%',
1043+
padding: '4px 8px',
1044+
fontSize: 10,
1045+
lineHeight: 1.1,
1046+
}}
1047+
>
1048+
{extras.copyAnonymousClientId()}
1049+
</DialogButton>
1050+
</div>
1051+
</div>
1052+
<div style={{ fontSize: 10, color: '#7a9bb5', marginBottom: 6, lineHeight: 1.4 }}>
1053+
{extras.anonymousClientIdDescription()}
1054+
</div>
1055+
<div style={{ fontSize: 11, fontFamily: 'monospace', color: '#9dc4e8', wordBreak: 'break-all' }}>
1056+
{anonymousClientId ?? extras.anonymousClientIdLoading()}
1057+
</div>
1058+
</div>
1059+
<div style={{ ...focusClipRowStyle(), paddingBottom: 8 }}>
1060+
<DialogButton onClick={() => void handleExportLocalData()} disabled={backupBusy}>
1061+
<div style={{ fontSize: 12, fontWeight: 600 }}>
1062+
{extras.backupLocalData()}
1063+
</div>
1064+
<div style={{ fontSize: 11, color: '#7a9bb5' }}>
1065+
{extras.backupLocalDataDescription()}
1066+
</div>
1067+
</DialogButton>
1068+
</div>
1069+
<div style={focusClipRowStyle()}>
1070+
<DialogButton onClick={() => void handleImportLocalData()} disabled={backupBusy}>
1071+
<div style={{ fontSize: 12, fontWeight: 600 }}>
1072+
{extras.importLocalData()}
1073+
</div>
1074+
<div style={{ fontSize: 11, color: '#7a9bb5' }}>
1075+
{extras.importLocalDataDescription()}
1076+
</div>
1077+
</DialogButton>
1078+
</div>
1079+
</div>
1080+
10001081
{/* advanced settings toggle */}
10011082
<div style={sectionStyle()}>
10021083
<div ref={advancedSettingsRowRef} style={focusClipRowStyle()}>
@@ -1080,93 +1161,6 @@ const [cefDebuggingEnabled, setCefDebuggingEnabledLocal] = useState(false);
10801161
</div>
10811162
)}
10821163

1083-
{advancedEnabled && (
1084-
<div ref={localDataSectionRef} style={sectionStyle()}>
1085-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12, marginBottom: 4, marginRight: 8 }}>
1086-
<div style={{ fontSize: 15, fontWeight: 700, color: '#eef7ff' }}>
1087-
{extras.localDataSection()}
1088-
</div>
1089-
{backupStatusMessage && (
1090-
<div
1091-
style={{
1092-
fontSize: 10,
1093-
color:
1094-
backupStatusTone === 'success'
1095-
? '#9dc4e8'
1096-
: backupStatusTone === 'error'
1097-
? '#f3b3b3'
1098-
: '#7a9bb5',
1099-
textAlign: 'right',
1100-
maxWidth: 360,
1101-
lineHeight: 1.35,
1102-
}}
1103-
>
1104-
{backupStatusMessage}
1105-
</div>
1106-
)}
1107-
</div>
1108-
<div style={{ fontSize: 11, color: '#7a9bb5', margin: '0 8px 10px' }}>
1109-
{extras.localDataSectionDescription()}
1110-
</div>
1111-
<div
1112-
style={{
1113-
margin: '0 8px 12px',
1114-
padding: '10px 12px',
1115-
borderRadius: 8,
1116-
border: '1px solid rgba(255,255,255,0.08)',
1117-
background: 'rgba(255,255,255,0.03)',
1118-
}}
1119-
>
1120-
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10, marginBottom: 4 }}>
1121-
<div style={{ fontSize: 11, fontWeight: 700, color: '#eef7ff', minWidth: 0, flex: 1 }}>
1122-
{extras.anonymousClientId()}
1123-
</div>
1124-
<div style={{ width: 76, flexShrink: 0, overflow: 'hidden', borderRadius: 8 }}>
1125-
<DialogButton
1126-
onClick={() => void handleCopyAnonymousClientId()}
1127-
disabled={!anonymousClientId}
1128-
style={{
1129-
minWidth: 0,
1130-
width: '100%',
1131-
padding: '4px 8px',
1132-
fontSize: 10,
1133-
lineHeight: 1.1,
1134-
}}
1135-
>
1136-
{extras.copyAnonymousClientId()}
1137-
</DialogButton>
1138-
</div>
1139-
</div>
1140-
<div style={{ fontSize: 10, color: '#7a9bb5', marginBottom: 6, lineHeight: 1.4 }}>
1141-
{extras.anonymousClientIdDescription()}
1142-
</div>
1143-
<div style={{ fontSize: 11, fontFamily: 'monospace', color: '#9dc4e8', wordBreak: 'break-all' }}>
1144-
{anonymousClientId ?? extras.anonymousClientIdLoading()}
1145-
</div>
1146-
</div>
1147-
<div style={{ ...focusClipRowStyle(), paddingBottom: 8 }}>
1148-
<DialogButton onClick={() => void handleExportLocalData()} disabled={backupBusy}>
1149-
<div style={{ fontSize: 12, fontWeight: 600 }}>
1150-
{extras.backupLocalData()}
1151-
</div>
1152-
<div style={{ fontSize: 11, color: '#7a9bb5' }}>
1153-
{extras.backupLocalDataDescription()}
1154-
</div>
1155-
</DialogButton>
1156-
</div>
1157-
<div style={focusClipRowStyle()}>
1158-
<DialogButton onClick={() => void handleImportLocalData()} disabled={backupBusy}>
1159-
<div style={{ fontSize: 12, fontWeight: 600 }}>
1160-
{extras.importLocalData()}
1161-
</div>
1162-
<div style={{ fontSize: 11, color: '#7a9bb5' }}>
1163-
{extras.importLocalDataDescription()}
1164-
</div>
1165-
</DialogButton>
1166-
</div>
1167-
</div>
1168-
)}
1169-
11701164
{/* advanced section: performance metrics */}
11711165
{advancedEnabled && (
11721166
<div style={sectionStyle()}>

src/lib/steamApps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function getSteamAppOverview(appId: number): any | null {
1616

1717
// Non-Steam shortcut IDs are CRC32-based with the high bit set, always >= 2^31.
1818
// Real Steam app IDs are currently in the low millions.
19-
const NON_STEAM_ID_THRESHOLD = 2_000_000_000;
19+
export const NON_STEAM_ID_THRESHOLD = 2_000_000_000;
2020

2121
export function isSteamShortcutApp(appId: number | null | undefined): boolean {
2222
if (!appId) return false;

src/patches/gamePageBadge.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,10 @@ function BadgeIcon({ appId }: { appId: number }) {
182182
const navigate = () => {
183183
const appName =
184184
(globalThis as any).SteamClient?.Apps?.GetAppOverviewByAppID?.(appId)?.display_name ?? '';
185-
// For non-Steam games with a title match, use the matched Steam app ID for reports
186-
const effectiveAppId = isNonSteam && sourceInfo?.steam_app_id_match
187-
? parseInt(sourceInfo.steam_app_id_match, 10)
188-
: appId;
185+
// Always pass the original appId so ConfigureTab can detect isShortcut correctly.
186+
// ConfigureTab resolves non-Steam shortcuts to their Steam store ID internally.
189187
rememberReturnPath(globalThis.location?.pathname);
190-
dispatchNavigate({ tab: 'manage-game', appId: effectiveAppId, appName });
188+
dispatchNavigate({ tab: 'manage-game', appId: appId, appName });
191189
try {
192190
Navigation.Navigate('/proton-pulse');
193191
} catch {

0 commit comments

Comments
 (0)