From a9ca5336a1fd37993bd5df0fc79480610af45d51 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 3 Nov 2023 12:54:47 +0000 Subject: [PATCH 1/5] [FEAT] Support Custom User Agent for browser apps --- public/locales/en/gamepage.json | 6 ++- src/backend/storeManagers/sideload/library.ts | 6 ++- .../storeManagers/storeManagerCommon/games.ts | 28 +++++++++--- src/common/types.ts | 1 + .../InstallModal/SideloadDialog/index.tsx | 43 +++++++++++++------ 5 files changed, 61 insertions(+), 23 deletions(-) diff --git a/public/locales/en/gamepage.json b/public/locales/en/gamepage.json index 1ca9b8392c..5f6bf23323 100644 --- a/public/locales/en/gamepage.json +++ b/public/locales/en/gamepage.json @@ -183,12 +183,14 @@ "broser": "BrowserURL", "exe": "Select Executable", "image": "App Image", - "title": "Game/App Title" + "title": "Game/App Title", + "useragent": "Custom User Agent" }, "placeholder": { "image": "Paste an Image URL here", "title": "Add a title to your Game/App", - "url": "Paste the Game URL here" + "url": "Paste the Game URL here", + "useragent": "Write a custom user agent here to be used on this browser app/game" } }, "specs": { diff --git a/src/backend/storeManagers/sideload/library.ts b/src/backend/storeManagers/sideload/library.ts index 3bc69a1b4a..a29daf99bc 100644 --- a/src/backend/storeManagers/sideload/library.ts +++ b/src/backend/storeManagers/sideload/library.ts @@ -19,7 +19,8 @@ export function addNewApp({ art_square, browserUrl, is_installed = true, - description + description, + customUserAgent }: GameInfo): void { const game: GameInfo = { runner: 'sideload', @@ -36,7 +37,8 @@ export function addNewApp({ art_square, canRunOffline: !browserUrl, browserUrl, - description + description, + customUserAgent } if (isMac && executable?.endsWith('.app')) { diff --git a/src/backend/storeManagers/storeManagerCommon/games.ts b/src/backend/storeManagers/storeManagerCommon/games.ts index f115027575..a2263bf7fb 100644 --- a/src/backend/storeManagers/storeManagerCommon/games.ts +++ b/src/backend/storeManagers/storeManagerCommon/games.ts @@ -36,10 +36,17 @@ export function logFileLocation(appName: string) { return join(gamesConfigPath, `${appName}-lastPlay.log`) } -const openNewBrowserGameWindow = async ( - browserUrl: string, +type BrowserGameOptions = { + browserUrl: string abortId: string -): Promise => { + customUserAgent?: string +} + +const openNewBrowserGameWindow = async ({ + browserUrl, + abortId, + customUserAgent +}: BrowserGameOptions): Promise => { const hostname = new URL(browserUrl).hostname return new Promise((res) => { @@ -60,9 +67,12 @@ const openNewBrowserGameWindow = async ( { role: 'toggleDevTools' } ]) ) + + const defaultUserAgent = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' + browserGame.webContents.userAgent = customUserAgent ?? defaultUserAgent + browserGame.menuBarVisible = false - browserGame.webContents.userAgent = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36' browserGame.loadURL(browserUrl) browserGame.on('ready-to-show', () => browserGame.show()) @@ -113,7 +123,7 @@ export async function launchGame( install: { executable } } = gameInfo - const { browserUrl } = gameInfo + const { browserUrl, customUserAgent } = gameInfo const gameSettingsOverrides = await GameConfig.get(appName).getSettings() if ( @@ -124,7 +134,11 @@ export async function launchGame( } if (browserUrl) { - return openNewBrowserGameWindow(browserUrl, appName) + return openNewBrowserGameWindow({ + browserUrl, + abortId: appName, + customUserAgent + }) } const gameSettings = await getAppSettings(appName) diff --git a/src/common/types.ts b/src/common/types.ts index 2529a3f977..f646b7eea5 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -135,6 +135,7 @@ export interface GameInfo { //used for store release versions. if remote !== local, then update version?: string dlcList?: GameMetadataInner[] + customUserAgent?: string } export interface GameSettings { diff --git a/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx b/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx index 75a45230a7..1ad9255812 100644 --- a/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx +++ b/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx @@ -52,6 +52,7 @@ export default function SideloadDialog({ ) const [selectedExe, setSelectedExe] = useState('') const [gameUrl, setGameUrl] = useState('') + const [customUserAgent, setCustomUserAgent] = useState('') const [imageUrl, setImageUrl] = useState('') const [searching, setSearching] = useState(false) const [app_name, setApp_name] = useState(appName ?? '') @@ -81,7 +82,8 @@ export default function SideloadDialog({ art_square, install: { executable, platform }, title, - browserUrl + browserUrl, + customUserAgent } = info if (executable && platform) { @@ -92,6 +94,10 @@ export default function SideloadDialog({ setGameUrl(browserUrl) } + if (customUserAgent) { + setCustomUserAgent(customUserAgent) + } + setTitle(title) setImageUrl(art_cover ? art_cover : art_square) }) @@ -159,7 +165,8 @@ export default function SideloadDialog({ is_installed: true, art_square: imageUrl ? imageUrl : fallbackImage, canRunOffline: true, - browserUrl: gameUrl + browserUrl: gameUrl, + customUserAgent }) const gameSettings = await getGameSettings(app_name, 'sideload') if (!gameSettings) { @@ -322,16 +329,28 @@ export default function SideloadDialog({ /> )} {!showSideloadExe && ( - handleGameUrl(e.target.value)} - htmlId="sideload-game-url" - value={gameUrl} - /> + <> + handleGameUrl(e.target.value)} + htmlId="sideload-game-url" + value={gameUrl} + /> + setCustomUserAgent(e.target.value)} + htmlId="sideload-user-agent" + value={customUserAgent} + /> + )} From 701bcdc4c6faa8c4cca9b09f375b3d5d1d4d6dbb Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 3 Nov 2023 13:21:15 +0000 Subject: [PATCH 2/5] feat: add fullscreen toggle to browser apps --- src/backend/storeManagers/sideload/library.ts | 6 +++-- .../storeManagers/storeManagerCommon/games.ts | 11 +++++--- src/common/types.ts | 1 + .../InstallModal/SideloadDialog/index.tsx | 25 ++++++++++++++++--- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/backend/storeManagers/sideload/library.ts b/src/backend/storeManagers/sideload/library.ts index a29daf99bc..a6b2fb6569 100644 --- a/src/backend/storeManagers/sideload/library.ts +++ b/src/backend/storeManagers/sideload/library.ts @@ -20,7 +20,8 @@ export function addNewApp({ browserUrl, is_installed = true, description, - customUserAgent + customUserAgent, + launchFullScreen }: GameInfo): void { const game: GameInfo = { runner: 'sideload', @@ -38,7 +39,8 @@ export function addNewApp({ canRunOffline: !browserUrl, browserUrl, description, - customUserAgent + customUserAgent, + launchFullScreen } if (isMac && executable?.endsWith('.app')) { diff --git a/src/backend/storeManagers/storeManagerCommon/games.ts b/src/backend/storeManagers/storeManagerCommon/games.ts index a2263bf7fb..ef6de159f4 100644 --- a/src/backend/storeManagers/storeManagerCommon/games.ts +++ b/src/backend/storeManagers/storeManagerCommon/games.ts @@ -40,19 +40,21 @@ type BrowserGameOptions = { browserUrl: string abortId: string customUserAgent?: string + launchFullScreen?: boolean } const openNewBrowserGameWindow = async ({ browserUrl, abortId, - customUserAgent + customUserAgent, + launchFullScreen }: BrowserGameOptions): Promise => { const hostname = new URL(browserUrl).hostname return new Promise((res) => { const browserGame = new BrowserWindow({ icon: icon, - fullscreen: true, + fullscreen: launchFullScreen ?? false, autoHideMenuBar: true, webPreferences: { partition: `persist:${hostname}` @@ -123,7 +125,7 @@ export async function launchGame( install: { executable } } = gameInfo - const { browserUrl, customUserAgent } = gameInfo + const { browserUrl, customUserAgent, launchFullScreen } = gameInfo const gameSettingsOverrides = await GameConfig.get(appName).getSettings() if ( @@ -137,7 +139,8 @@ export async function launchGame( return openNewBrowserGameWindow({ browserUrl, abortId: appName, - customUserAgent + customUserAgent, + launchFullScreen }) } diff --git a/src/common/types.ts b/src/common/types.ts index f646b7eea5..a2562db319 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -136,6 +136,7 @@ export interface GameInfo { version?: string dlcList?: GameMetadataInner[] customUserAgent?: string + launchFullScreen?: boolean } export interface GameSettings { diff --git a/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx b/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx index 1ad9255812..05af90f7d2 100644 --- a/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx +++ b/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx @@ -6,7 +6,8 @@ import { InstallPlatform, WineInstallation, GameInfo } from 'common/types' import { CachedImage, TextInputField, - PathSelectionBox + PathSelectionBox, + ToggleSwitch } from 'frontend/components/UI' import { DialogContent, DialogFooter } from 'frontend/components/UI/Dialog' import { @@ -53,6 +54,7 @@ export default function SideloadDialog({ const [selectedExe, setSelectedExe] = useState('') const [gameUrl, setGameUrl] = useState('') const [customUserAgent, setCustomUserAgent] = useState('') + const [launchFullScreen, setLaunchFullScreen] = useState(false) const [imageUrl, setImageUrl] = useState('') const [searching, setSearching] = useState(false) const [app_name, setApp_name] = useState(appName ?? '') @@ -83,7 +85,8 @@ export default function SideloadDialog({ install: { executable, platform }, title, browserUrl, - customUserAgent + customUserAgent, + launchFullScreen } = info if (executable && platform) { @@ -98,6 +101,11 @@ export default function SideloadDialog({ setCustomUserAgent(customUserAgent) } + console.log(launchFullScreen) + if (launchFullScreen !== undefined) { + setLaunchFullScreen(launchFullScreen) + } + setTitle(title) setImageUrl(art_cover ? art_cover : art_square) }) @@ -166,7 +174,8 @@ export default function SideloadDialog({ art_square: imageUrl ? imageUrl : fallbackImage, canRunOffline: true, browserUrl: gameUrl, - customUserAgent + customUserAgent, + launchFullScreen }) const gameSettings = await getGameSettings(app_name, 'sideload') if (!gameSettings) { @@ -183,6 +192,7 @@ export default function SideloadDialog({ }) await refreshLibrary({ + library: 'sideload', runInBackground: true, checkForUpdates: true }) @@ -350,6 +360,15 @@ export default function SideloadDialog({ htmlId="sideload-user-agent" value={customUserAgent} /> + setLaunchFullScreen(!launchFullScreen)} + title={t( + 'sideload.info.fullscreen', + 'Launch Fullscreen (F11 to exit' + )} + /> )} From e431e60f354d175e56b9f8c3ff1c4fb2e9266863 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 3 Nov 2023 13:21:41 +0000 Subject: [PATCH 3/5] fix: missing client hints --- src/backend/main.ts | 13 ++++++++++++- src/backend/preload.ts | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/backend/main.ts b/src/backend/main.ts index d0463eb0cd..83b58bb28d 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -19,7 +19,8 @@ import { protocol, screen, clipboard, - components + components, + session } from 'electron' import 'backend/updater' import { autoUpdater } from 'electron-updater' @@ -298,6 +299,16 @@ if (!gotTheLock) { initOnlineMonitor() initImagesCache() + // Add User-Agent Client hints to behave like Windows + if (process.argv.includes('--spoof-windows')) { + session.defaultSession.webRequest.onBeforeSendHeaders( + (details, callback) => { + details.requestHeaders['sec-ch-ua-platform'] = 'Windows' + callback({ cancel: false, requestHeaders: details.requestHeaders }) + } + ) + } + if (!process.env.CI) { await components.whenReady().catch((e) => { logError([ diff --git a/src/backend/preload.ts b/src/backend/preload.ts index 4fd5870f45..5edfd40e82 100644 --- a/src/backend/preload.ts +++ b/src/backend/preload.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { contextBridge } from 'electron' import api from './api' @@ -6,3 +8,19 @@ contextBridge.exposeInMainWorld( 'isSteamDeckGameMode', process.env.XDG_CURRENT_DESKTOP === 'gamescope' ) + +if (navigator.userAgent.includes('Windows')) { + Object.defineProperty(navigator, 'platform', { + get: function () { + return 'Win32' + }, + set: function (a) {} + }) + + Object.defineProperty(navigator, 'userAgentData', { + get: function () { + return null + }, + set: function (a) {} + }) +} From fff0130892df256702a24b022e253f3b9b1de2b9 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 3 Nov 2023 13:22:20 +0000 Subject: [PATCH 4/5] i18n: updated keys --- public/locales/en/gamepage.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/locales/en/gamepage.json b/public/locales/en/gamepage.json index 5f6bf23323..be1169e943 100644 --- a/public/locales/en/gamepage.json +++ b/public/locales/en/gamepage.json @@ -182,6 +182,7 @@ "info": { "broser": "BrowserURL", "exe": "Select Executable", + "fullscreen": "Launch Fullscreen (F11 to exit", "image": "App Image", "title": "Game/App Title", "useragent": "Custom User Agent" From 21d03a1cb34564615d144c266469a884e08b2d9e Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 3 Nov 2023 13:27:20 +0000 Subject: [PATCH 5/5] fix: typo --- public/locales/en/gamepage.json | 2 +- .../Library/components/InstallModal/SideloadDialog/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/locales/en/gamepage.json b/public/locales/en/gamepage.json index be1169e943..06d56f21d8 100644 --- a/public/locales/en/gamepage.json +++ b/public/locales/en/gamepage.json @@ -182,7 +182,7 @@ "info": { "broser": "BrowserURL", "exe": "Select Executable", - "fullscreen": "Launch Fullscreen (F11 to exit", + "fullscreen": "Launch Fullscreen (F11 to exit)", "image": "App Image", "title": "Game/App Title", "useragent": "Custom User Agent" diff --git a/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx b/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx index 05af90f7d2..8824b4035d 100644 --- a/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx +++ b/src/frontend/screens/Library/components/InstallModal/SideloadDialog/index.tsx @@ -366,7 +366,7 @@ export default function SideloadDialog({ handleChange={() => setLaunchFullScreen(!launchFullScreen)} title={t( 'sideload.info.fullscreen', - 'Launch Fullscreen (F11 to exit' + 'Launch Fullscreen (F11 to exit)' )} />