From 96fb2ed8c7c955b6bbef3b2d692b10155fd176fb Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 15 Jan 2025 19:58:03 +0800 Subject: [PATCH 1/7] [bugfix-738] fixed issue when creating Beat Saber shortcuts --- electron-builder.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/electron-builder.config.js b/electron-builder.config.js index c692c4dc..94858c8f 100644 --- a/electron-builder.config.js +++ b/electron-builder.config.js @@ -65,6 +65,7 @@ const config = { // Audio output "--socket=pulseaudio", // Read/write home directory access + "--filesystem=~/Desktop:rw", // allow writing shortcuts to desktop "--filesystem=~/BSManager:create", // Default BSManager installation folder "--filesystem=~/.steam/steam/steamapps:ro", // for the libraryfolders.vdf "--filesystem=~/.steam/steam/steamapps/common:create", // Steam game folder From a47616086ca2a40491105e32273ea19705caa224 Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 15 Jan 2025 19:58:36 +0800 Subject: [PATCH 2/7] [bugfix] fixed issue when proton logs with flatpak --- src/main/services/bs-launcher/abstract-launcher.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/services/bs-launcher/abstract-launcher.service.ts b/src/main/services/bs-launcher/abstract-launcher.service.ts index cf4ee365..6bd98495 100644 --- a/src/main/services/bs-launcher/abstract-launcher.service.ts +++ b/src/main/services/bs-launcher/abstract-launcher.service.ts @@ -70,6 +70,8 @@ export abstract class AbstractLauncherService { "STEAM_COMPAT_CLIENT_INSTALL_PATH", "STEAM_COMPAT_APP_ID", "SteamEnv", + "PROTON_LOG", + "PROTON_LOG_DIR", ], }, }); From 350dff37cf37acac71faf7fe6abba690b7bd0e89 Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 15 Jan 2025 23:25:40 +0800 Subject: [PATCH 3/7] [bugfix] refactored bs launch + launch bs version directly with shortcut --- .../bs-launcher/abstract-launcher.service.ts | 11 +- .../bs-launcher/bs-launcher.service.ts | 39 ++---- .../bs-launcher/steam-launcher.service.ts | 12 +- src/main/services/linux.service.ts | 111 +++++++++++++----- src/main/services/steam.service.ts | 2 +- 5 files changed, 108 insertions(+), 67 deletions(-) diff --git a/src/main/services/bs-launcher/abstract-launcher.service.ts b/src/main/services/bs-launcher/abstract-launcher.service.ts index 6bd98495..ba48b163 100644 --- a/src/main/services/bs-launcher/abstract-launcher.service.ts +++ b/src/main/services/bs-launcher/abstract-launcher.service.ts @@ -46,7 +46,7 @@ export abstract class AbstractLauncherService { return Array.from(new Set(launchArgs).values()); } - protected launchBSProcess(bsExePath: string, args: string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams { + protected launchBSProcess(bsExePath: string, args: string[], options?: SpawnBsProcessOptions): ChildProcessWithoutNullStreams { const spawnOptions: SpawnOptionsWithoutStdio = { detached: true, cwd: path.dirname(bsExePath), ...(options || {}) }; @@ -57,7 +57,7 @@ export abstract class AbstractLauncherService { spawnOptions.shell = true; // For windows to spawn properly return bsmSpawn(`"${bsExePath}"`, { args, options: spawnOptions, log: BsmShellLog.Command, - linux: { prefix: this.linux.getProtonPrefix() }, + linux: { prefix: options?.protonPrefix || "" }, flatpak: { host: IS_FLATPAK, env: [ @@ -80,7 +80,7 @@ export abstract class AbstractLauncherService { protected launchBs(bsExePath: string, args: string[], options?: SpawnBsProcessOptions): {process: ChildProcessWithoutNullStreams, exit: Promise} { const process = this.launchBSProcess(bsExePath, args, options); - let timoutId: NodeJS.Timeout; + let timeoutId: NodeJS.Timeout; const exit = new Promise((resolve, reject) => { // Don't remove, useful for debugging! @@ -103,7 +103,7 @@ export abstract class AbstractLauncherService { const unrefAfter = options?.unrefAfter ?? sToMs(10); - timoutId = setTimeout(() => { + timeoutId = setTimeout(() => { log.error("BS process unref after timeout", unrefAfter); process.unref(); process.removeAllListeners(); @@ -111,7 +111,7 @@ export abstract class AbstractLauncherService { }, unrefAfter); }).finally(() => { - clearTimeout(timoutId); + clearTimeout(timeoutId); }); return { process, exit }; @@ -119,5 +119,6 @@ export abstract class AbstractLauncherService { } export type SpawnBsProcessOptions = { + protonPrefix?: string; unrefAfter?: number; } & SpawnOptionsWithoutStdio; diff --git a/src/main/services/bs-launcher/bs-launcher.service.ts b/src/main/services/bs-launcher/bs-launcher.service.ts index 6ebe7ac7..ac8cd546 100644 --- a/src/main/services/bs-launcher/bs-launcher.service.ts +++ b/src/main/services/bs-launcher/bs-launcher.service.ts @@ -24,6 +24,7 @@ import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.int import { StaticConfigurationService } from "../static-configuration.service"; import { SteamService } from "../steam.service"; import { tryit } from "shared/helpers/error.helpers"; +import { LinuxService } from "../linux.service"; export class BSLauncherService { private static instance: BSLauncherService; @@ -37,6 +38,7 @@ export class BSLauncherService { private readonly steam: SteamService; private readonly oculusLauncher: OculusLauncherService; private readonly staticConfig: StaticConfigurationService; + private readonly linux: LinuxService; public static getInstance(): BSLauncherService { if (!BSLauncherService.instance) { @@ -55,6 +57,7 @@ export class BSLauncherService { this.oculusLauncher = OculusLauncherService.getInstance(); this.staticConfig = StaticConfigurationService.getInstance(); this.steam = SteamService.getInstance(); + this.linux = LinuxService.getInstance(); this.bsmProtocolService.on("launch", link => { log.info("Launch from bsm protocol", link.toString()); @@ -230,12 +233,13 @@ export class BSLauncherService { description: [shortcutName, launchOptions.version.color].join(" "), // <= Need color in description to help windows know that the shortcut is different }) ), - linux: async () => ( - createDesktopUrlShortcut(path.join(app.getPath("desktop"), `${shortcutName}.desktop`), { - name: shortcutName, - url: shortcutUrl, - icon: await this.createShortcutPng(shortcutIconColor), - }) + linux: async () => this.linux.createDesktopShortcut( + path.join(app.getPath("desktop"), `${shortcutName}.desktop`), + shortcutName, + await this.createShortcutPng(shortcutIconColor), + launchOptions, + await this.steam.getSteamPath(), + await this.localVersionService.getVersionPath(launchOptions.version) ) }) @@ -282,26 +286,3 @@ type ShortcutParams = { versionOculus?: string; } -/** - * Create .desktop file for url shortcut (only for linux) - * @param {string} shortcutPath - * @param options - * @returns - */ -function createDesktopUrlShortcut(shortcutPath: string, options?: { - url: string - name: string, - icon: string -}): Promise { - const { url, name, icon } = options || {}; - - const data = [ - "[Desktop Entry]", - "Type=Link", - `Name=${name}`, - `Icon=${icon}`, - `URL=${url}` - ].join("\n"); - - return writeFile(shortcutPath, data).then(() => true); -} diff --git a/src/main/services/bs-launcher/steam-launcher.service.ts b/src/main/services/bs-launcher/steam-launcher.service.ts index d3d25b31..96eb3822 100644 --- a/src/main/services/bs-launcher/steam-launcher.service.ts +++ b/src/main/services/bs-launcher/steam-launcher.service.ts @@ -112,9 +112,14 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto "SteamGameId": BS_APP_ID, }; + let protonPrefix = ""; // Linux setup if (process.platform === "linux") { - await this.linux.setupLaunch(launchOptions, steamPath, bsFolderPath, env); + const linuxSetup = await this.linux.setupLaunch( + launchOptions, steamPath, bsFolderPath + ); + protonPrefix = linuxSetup.protonPrefix; + Object.assign(env, linuxSetup.env); } obs.next({type: BSLaunchEvent.BS_LAUNCHING}); @@ -122,7 +127,10 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto const spawnOpts = { env, cwd: bsFolderPath }; const launchPromise = !launchOptions.admin ? ( - this.launchBs(bsExePath, launchArgs, spawnOpts).exit + this.launchBs(bsExePath, launchArgs, { + ...spawnOpts, + protonPrefix + }).exit ) : ( new Promise(resolve => { const adminProcess = exec(`"${this.getStartBsAsAdminExePath()}" "${bsExePath}" ${launchArgs.join(" ")} --log-path "${path.join(app.getPath("logs"), "bs-admin-start.log")}"`, spawnOpts); diff --git a/src/main/services/linux.service.ts b/src/main/services/linux.service.ts index 8db15e8d..b3ef82af 100644 --- a/src/main/services/linux.service.ts +++ b/src/main/services/linux.service.ts @@ -1,7 +1,7 @@ import fs from "fs-extra"; import log from "electron-log"; import path from "path"; -import { BS_APP_ID, IS_FLATPAK, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants"; +import { BS_APP_ID, BS_EXECUTABLE, IS_FLATPAK, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants"; import { InstallationLocationService } from "./installation-location.service"; import { StaticConfigurationService } from "./static-configuration.service"; import { CustomError } from "shared/models/exceptions/custom-error.class"; @@ -21,7 +21,6 @@ export class LinuxService { private readonly installLocationService: InstallationLocationService; private readonly staticConfig: StaticConfigurationService; - private protonPrefix = ""; private nixOS: boolean | undefined; @@ -40,24 +39,16 @@ export class LinuxService { public async setupLaunch( launchOptions: LaunchOption, steamPath: string, - bsFolderPath: string, - env: Record - ) { + bsFolderPath: string + ): Promise<{ + protonPrefix: string; + env: Record; + }> { if (launchOptions.admin) { log.warn("Launching as admin is not supported on Linux! Starting the game as a normal user."); launchOptions.admin = false; } - // Create the compat data path if it doesn't exist. - // If the user never ran Beat Saber through steam before - // using bsmanager, it won't exist, and proton will fail - // to launch the game. - const compatDataPath = this.getCompatDataPath(); - if (!fs.existsSync(compatDataPath)) { - log.info(`Proton compat data path not found at '${compatDataPath}', creating directory`); - fs.mkdirSync(compatDataPath); - } - if (!this.staticConfig.has("proton-folder")) { throw CustomError.fromError( new Error("Proton folder not set"), @@ -75,27 +66,46 @@ export class LinuxService { ); } - this.protonPrefix = await this.isNixOS() - ? `steam-run "${protonPath}" run` - : `"${protonPath}" run`; + return { + protonPrefix: await this.isNixOS() + ? `steam-run "${protonPath}" run` + : `"${protonPath}" run`, + env: await this.prepareEnvVariables(launchOptions, steamPath, bsFolderPath) + }; + } + + private async prepareEnvVariables( + launchOptions: LaunchOption, + steamPath: string, + bsFolderPath: string + ): Promise> { + // Create the compat data path if it doesn't exist. + // If the user never ran Beat Saber through steam before + // using bsmanager, it won't exist, and proton will fail + // to launch the game. + const compatDataPath = this.getCompatDataPath(); + if (!fs.existsSync(compatDataPath)) { + log.info(`Proton compat data path not found at '${compatDataPath}', creating directory`); + fs.mkdirSync(compatDataPath); + } // Setup Proton environment variables - Object.assign(env, { + const envVars: Record = { "WINEDLLOVERRIDES": "winhttp=n,b", // Required for mods to work "STEAM_COMPAT_DATA_PATH": compatDataPath, "STEAM_COMPAT_INSTALL_PATH": bsFolderPath, "STEAM_COMPAT_CLIENT_INSTALL_PATH": steamPath, "STEAM_COMPAT_APP_ID": BS_APP_ID, // Run game in steam environment; fixes #585 for unicode song titles - "SteamEnv": 1, - }); + "SteamEnv": "1", + }; if (launchOptions.launchMods?.includes(LaunchMods.PROTON_LOGS)) { - Object.assign(env, { - "PROTON_LOG": 1, - "PROTON_LOG_DIR": path.join(bsFolderPath, "Logs"), - }); + envVars.PROTON_LOG = "1"; + envVars.PROTON_LOG_DIR = path.join(bsFolderPath, "Logs"); } + + return envVars; } public verifyProtonPath(protonFolder: string = ""): boolean { @@ -134,11 +144,6 @@ export class LinuxService { ? path.join(compatDataPath, "pfx") : ""; } - public getProtonPrefix(): string { - // Set in setupLaunch - return this.protonPrefix; - } - // === NixOS Specific === // public async isNixOS(): Promise { @@ -159,4 +164,50 @@ export class LinuxService { return this.nixOS; } + + // === Shortcuts === // + + public async createDesktopShortcut( + shortcutPath: string, + name: string, + icon: string, + launchOptions: LaunchOption, + steamPath: string, + bsFolderPath: string + ): Promise { + try { + const { + protonPrefix, env + } = await this.setupLaunch(launchOptions, steamPath, bsFolderPath); + + Object.assign(env, { + "SteamAppId": BS_APP_ID, + "SteamOverlayGameId": BS_APP_ID, + "SteamGameId": BS_APP_ID, + }); + + const envString = Object.entries(env) + .map(([ key, value ]) => `${key}="${value}"`) + .join(" "); + const command = `${envString} ${protonPrefix} "${ + path.join(bsFolderPath, BS_EXECUTABLE) + }"`; + + const desktopEntry = [ + "[Desktop Entry]", + "Type=Application", + `Name=${name}`, + `Icon=${icon}`, + `Path=${bsFolderPath}`, + `Exec=${command}` + ].join("\n"); + + await fs.writeFile(shortcutPath, desktopEntry); + log.info("Created shorcut at ", `"${shortcutPath}/${name}"`); + return true; + } catch (error) { + log.error("Could not create shortcut", error); + return false; + } + } } diff --git a/src/main/services/steam.service.ts b/src/main/services/steam.service.ts index 9298f8c8..54414ff5 100644 --- a/src/main/services/steam.service.ts +++ b/src/main/services/steam.service.ts @@ -211,7 +211,7 @@ export class SteamService { for (const userId of userIds) { const shortcuts = await this.getShortcuts(userId).catch(e => { log.warn("Error while reading shortcuts", e); - return []; + return [] as SteamShortcut[]; }); shortcuts.push(new SteamShortcut(shortcutData)); From dc3ae1971d38b5d1e66c60219b8d5937d39f37fb Mon Sep 17 00:00:00 2001 From: silentrald Date: Thu, 16 Jan 2025 00:24:59 +0800 Subject: [PATCH 4/7] [bugfix-742] launch bs version directly with steam shortcut --- electron-builder.config.js | 3 +- .../bs-launcher/abstract-launcher.service.ts | 54 ++++++------- .../bs-launcher/bs-launcher.service.ts | 35 +++++--- .../bs-launcher/oculus-launcher.service.ts | 4 +- .../bs-launcher/steam-launcher.service.ts | 4 +- src/main/services/linux.service.ts | 79 ++++++++++++++++--- 6 files changed, 122 insertions(+), 57 deletions(-) diff --git a/electron-builder.config.js b/electron-builder.config.js index 94858c8f..8c76afb9 100644 --- a/electron-builder.config.js +++ b/electron-builder.config.js @@ -65,11 +65,12 @@ const config = { // Audio output "--socket=pulseaudio", // Read/write home directory access - "--filesystem=~/Desktop:rw", // allow writing shortcuts to desktop "--filesystem=~/BSManager:create", // Default BSManager installation folder "--filesystem=~/.steam/steam/steamapps:ro", // for the libraryfolders.vdf "--filesystem=~/.steam/steam/steamapps/common:create", // Steam game folder "--filesystem=~/.steam/steam/steamapps/common/Beat Saber:create", // For installing mods/maps to original Beat Saber version + "--filesystem=~/Desktop", // allow writing shortcuts to desktop + "--filesystem=~/.steam/steam/userdata", // allow writing steam shortcuts // Allow BSManager to create the compat folder if it does not exist "--filesystem=~/.steam/steam/steamapps/compatdata/620980:create", // Allow communication with network diff --git a/src/main/services/bs-launcher/abstract-launcher.service.ts b/src/main/services/bs-launcher/abstract-launcher.service.ts index ba48b163..b61d23b9 100644 --- a/src/main/services/bs-launcher/abstract-launcher.service.ts +++ b/src/main/services/bs-launcher/abstract-launcher.service.ts @@ -9,6 +9,33 @@ import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers"; import { IS_FLATPAK } from "main/constants"; import { LaunchMods } from "shared/models/bs-launch/launch-option.interface"; +export function buildBsLaunchArgs(launchOptions: LaunchOption): string[] { + const launchArgs = []; + + if(!launchOptions.version.steam && !launchOptions.version.oculus){ + launchArgs.push("--no-yeet") + } + if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)) { + launchArgs.push("-vrmode"); + launchArgs.push("oculus"); + } + if(launchOptions.launchMods?.includes(LaunchMods.FPFC)) { + launchArgs.push("fpfc"); + } + if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)) { + launchArgs.push("--verbose"); + } + if(launchOptions.launchMods?.includes(LaunchMods.EDITOR)) { + launchArgs.push("editor"); + } + + if (launchOptions.additionalArgs) { + launchArgs.push(...launchOptions.additionalArgs); + } + + return Array.from(new Set(launchArgs).values()); +} + export abstract class AbstractLauncherService { protected readonly linux = LinuxService.getInstance(); @@ -19,33 +46,6 @@ export abstract class AbstractLauncherService { this.localVersions = BSLocalVersionService.getInstance(); } - protected buildBsLaunchArgs(launchOptions: LaunchOption): string[]{ - const launchArgs = []; - - if(!launchOptions.version.steam && !launchOptions.version.oculus){ - launchArgs.push("--no-yeet") - } - if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)) { - launchArgs.push("-vrmode"); - launchArgs.push("oculus"); - } - if(launchOptions.launchMods?.includes(LaunchMods.FPFC)) { - launchArgs.push("fpfc"); - } - if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)) { - launchArgs.push("--verbose"); - } - if(launchOptions.launchMods?.includes(LaunchMods.EDITOR)) { - launchArgs.push("editor"); - } - - if (launchOptions.additionalArgs) { - launchArgs.push(...launchOptions.additionalArgs); - } - - return Array.from(new Set(launchArgs).values()); - } - protected launchBSProcess(bsExePath: string, args: string[], options?: SpawnBsProcessOptions): ChildProcessWithoutNullStreams { const spawnOptions: SpawnOptionsWithoutStdio = { detached: true, cwd: path.dirname(bsExePath), ...(options || {}) }; diff --git a/src/main/services/bs-launcher/bs-launcher.service.ts b/src/main/services/bs-launcher/bs-launcher.service.ts index ac8cd546..b03dd0ef 100644 --- a/src/main/services/bs-launcher/bs-launcher.service.ts +++ b/src/main/services/bs-launcher/bs-launcher.service.ts @@ -185,7 +185,7 @@ export class BSLauncherService { * @returns {Promise} Path of the icon */ private async createShortcutIco(color: Color): Promise{ - const pngBuffer = await this.createShortcutPngBuffer(color); + const pngBuffer = this.createShortcutPngBuffer(color); const icoBuffer = await toIco([pngBuffer]); await ensureDir(IMAGE_CACHE_PATH); @@ -211,17 +211,28 @@ export class BSLauncherService { if(steamShortcut){ const userId = await tryit(() => this.steam.getActiveUser()); const exePath = app.getPath("exe"); - return this.steam.createShortcut({ - AppName: shortcutName, - Exe: exePath, - StartDir: path.dirname(exePath), - LaunchOptions: this.createLaunchLink(launchOptions), - icon: await this.createShortcutPng(shortcutIconColor), - OpenVR: "\u0001" - }, userId.result).then(() => true).catch(e => { - log.error(e); - return false; - }); + const icon = await this.createShortcutPng(shortcutIconColor) + return this.steam.createShortcut( + process.platform === "win32" + ? { + AppName: shortcutName, + Exe: exePath, + StartDir: path.dirname(exePath), + LaunchOptions: this.createLaunchLink(launchOptions), + OpenVR: "\u0001", icon, + } + : await this.linux.getSteamShortcutData( + shortcutName, icon, launchOptions, + await this.steam.getSteamPath(), + await this.localVersionService.getVersionPath(launchOptions.version) + ), + userId.result + ) + .then(() => true) + .catch(e => { + log.error(e); + return false; + }); } return execOnOs({ diff --git a/src/main/services/bs-launcher/oculus-launcher.service.ts b/src/main/services/bs-launcher/oculus-launcher.service.ts index d60ffe3a..d92ccef9 100644 --- a/src/main/services/bs-launcher/oculus-launcher.service.ts +++ b/src/main/services/bs-launcher/oculus-launcher.service.ts @@ -6,7 +6,7 @@ import { BS_EXECUTABLE } from "../../constants"; import path from "path"; import log from "electron-log"; import { pathExists } from "fs-extra"; -import { AbstractLauncherService } from "./abstract-launcher.service"; +import { AbstractLauncherService, buildBsLaunchArgs } from "./abstract-launcher.service"; import { isProcessRunning } from "../../helpers/os.helpers"; import { CustomError } from "../../../shared/models/exceptions/custom-error.class"; import { UtilsService } from "../utils.service"; @@ -57,7 +57,7 @@ export class OculusLauncherService extends AbstractLauncherService implements St obs.next({type: BSLaunchEvent.BS_LAUNCHING}); // Launch Beat Saber - const process = this.launchBs(exePath, this.buildBsLaunchArgs(launchOptions)); + const process = this.launchBs(exePath, buildBsLaunchArgs(launchOptions)); return process.exit.catch(err => { throw CustomError.fromError(err, BSLaunchError.BS_EXIT_ERROR); diff --git a/src/main/services/bs-launcher/steam-launcher.service.ts b/src/main/services/bs-launcher/steam-launcher.service.ts index 96eb3822..ab8277d6 100644 --- a/src/main/services/bs-launcher/steam-launcher.service.ts +++ b/src/main/services/bs-launcher/steam-launcher.service.ts @@ -6,7 +6,7 @@ import { SteamService } from "../steam.service"; import path from "path"; import { BS_APP_ID, BS_EXECUTABLE, STEAMVR_APP_ID } from "../../constants"; import log from "electron-log"; -import { AbstractLauncherService } from "./abstract-launcher.service"; +import { AbstractLauncherService, buildBsLaunchArgs } from "./abstract-launcher.service"; import { CustomError } from "../../../shared/models/exceptions/custom-error.class"; import { UtilsService } from "../utils.service"; import { exec } from "child_process"; @@ -102,7 +102,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto await this.restoreSteamVR().catch(log.error); } - const launchArgs = this.buildBsLaunchArgs(launchOptions); + const launchArgs = buildBsLaunchArgs(launchOptions); const steamPath = await this.steam.getSteamPath(); const env = { diff --git a/src/main/services/linux.service.ts b/src/main/services/linux.service.ts index b3ef82af..b3ead173 100644 --- a/src/main/services/linux.service.ts +++ b/src/main/services/linux.service.ts @@ -8,6 +8,8 @@ import { CustomError } from "shared/models/exceptions/custom-error.class"; import { BSLaunchError, LaunchOption } from "shared/models/bs-launch"; import { BsmShellLog, bsmExec } from "main/helpers/os.helpers"; import { LaunchMods } from "shared/models/bs-launch/launch-option.interface"; +import { SteamShortcutData } from "shared/models/steam/shortcut.model"; +import { buildBsLaunchArgs } from "./bs-launcher/abstract-launcher.service"; export class LinuxService { private static instance: LinuxService; @@ -49,6 +51,16 @@ export class LinuxService { launchOptions.admin = false; } + const protonPath = await this.getProtonPath(); + return { + protonPrefix: await this.isNixOS() + ? `steam-run "${protonPath}" run` + : `"${protonPath}" run`, + env: await this.buildEnvVariables(launchOptions, steamPath, bsFolderPath) + }; + } + + private async getProtonPath(): Promise { if (!this.staticConfig.has("proton-folder")) { throw CustomError.fromError( new Error("Proton folder not set"), @@ -66,15 +78,10 @@ export class LinuxService { ); } - return { - protonPrefix: await this.isNixOS() - ? `steam-run "${protonPath}" run` - : `"${protonPath}" run`, - env: await this.prepareEnvVariables(launchOptions, steamPath, bsFolderPath) - }; + return protonPath; } - private async prepareEnvVariables( + private async buildEnvVariables( launchOptions: LaunchOption, steamPath: string, bsFolderPath: string @@ -167,6 +174,20 @@ export class LinuxService { // === Shortcuts === // + private getCommand( + protonPrefix: string, + bsFolderPath: string, + env: Record, + launchOptions: LaunchOption + ): string { + const envString = Object.entries(env) + .map(([ key, value ]) => `${key}="${value}"`) + .join(" "); + const bsExe = path.join(bsFolderPath, BS_EXECUTABLE); + const args = buildBsLaunchArgs(launchOptions).join(" "); + return `${envString} ${protonPrefix} "${bsExe}" ${args}`; + } + public async createDesktopShortcut( shortcutPath: string, name: string, @@ -186,12 +207,10 @@ export class LinuxService { "SteamGameId": BS_APP_ID, }); - const envString = Object.entries(env) - .map(([ key, value ]) => `${key}="${value}"`) - .join(" "); - const command = `${envString} ${protonPrefix} "${ - path.join(bsFolderPath, BS_EXECUTABLE) - }"`; + const command = this.getCommand( + protonPrefix, bsFolderPath, + env, launchOptions + ); const desktopEntry = [ "[Desktop Entry]", @@ -210,4 +229,38 @@ export class LinuxService { return false; } } + + public async getSteamShortcutData( + shortcutName: string, + icon: string, + launchOptions: LaunchOption, + steamPath: string, + bsFolderPath: string + ): Promise { + const env = await this.buildEnvVariables( + launchOptions, steamPath, bsFolderPath + ); + Object.assign(env, { + "SteamAppId": BS_APP_ID, + "SteamOverlayGameId": BS_APP_ID, + "SteamGameId": BS_APP_ID, + }); + + const protonPrefix = await this.isNixOS() + ? "steam-run %command% run" + : "%command% run"; + + return { + AppName: shortcutName, + Exe: await this.getProtonPath(), + StartDir: bsFolderPath, + icon, + OpenVR: "\x01", + LaunchOptions: this.getCommand( + protonPrefix, bsFolderPath, + env, launchOptions + ) + }; + } + } From 0c3a5b9e75cd5f79978155844ef72de758c9a0f3 Mon Sep 17 00:00:00 2001 From: silentrald Date: Thu, 16 Jan 2025 23:42:10 +0800 Subject: [PATCH 5/7] [bugfix] use static config for bs-version writing/reading for linux --- build/after-install.sh | 4 ---- docs/wiki/Home.md | 1 - .../[Linux]-Permission-Denied-on-bs-version.json.md | 13 ------------- docs/wiki/_Sidebar.md | 1 - src/main/services/bs-version-lib.service.ts | 13 ++----------- 5 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 docs/wiki/Troubleshoots/Installation-Problems/Linux/[Linux]-Permission-Denied-on-bs-version.json/[Linux]-Permission-Denied-on-bs-version.json.md diff --git a/build/after-install.sh b/build/after-install.sh index a9e1aedd..f2ec23df 100644 --- a/build/after-install.sh +++ b/build/after-install.sh @@ -1,6 +1,2 @@ -# Add write permissions to anybody so that this can be sync with the -# github's bs-versions.json when starting bsmanager -/usr/bin/chmod +002 /opt/BSManager/resources/assets/jsons/bs-versions.json - # https://github.com/electron/electron/issues/42510 /usr/bin/chmod 4755 /opt/BSManager/chrome-sandbox diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md index a0fbf9a7..737d8fce 100644 --- a/docs/wiki/Home.md +++ b/docs/wiki/Home.md @@ -49,7 +49,6 @@ Guidance on fixing errors during setup or version updates - __🐧Linux__: - [Missing Icons in Game]([Linux]-Missing-Icons-in-Game) - - [Permission Denied on "bs-versions.json"]([Linux]-Permission-Denied-on-bs-version.json) - [[Deb] The SUID Sandbox Helper Binary Was Found]([Linux]-[deb]-The-SUID-Sandbox-Helper-Binary-Was-Found) - [[Flatpak] Steam Beat Saber Version Not Showing / Proton Not Detected]([Linux]-[Flatpak]-Steam-Beat-Saber-Version-Not-Showing-Proton-Not-Detected) - [[Flatpak] Changing Installation Folder]([Linux]-[Flatpak]-Changing-Installation-Folder) diff --git a/docs/wiki/Troubleshoots/Installation-Problems/Linux/[Linux]-Permission-Denied-on-bs-version.json/[Linux]-Permission-Denied-on-bs-version.json.md b/docs/wiki/Troubleshoots/Installation-Problems/Linux/[Linux]-Permission-Denied-on-bs-version.json/[Linux]-Permission-Denied-on-bs-version.json.md deleted file mode 100644 index 81e9af5d..00000000 --- a/docs/wiki/Troubleshoots/Installation-Problems/Linux/[Linux]-Permission-Denied-on-bs-version.json/[Linux]-Permission-Denied-on-bs-version.json.md +++ /dev/null @@ -1,13 +0,0 @@ -
-Unhandled Exception UnhandledRejection Error: EACCES: permission denied, open '/opt/BSManager/resources/assets/jsons/bs-versions.json'
-
- -To fix this issue, the current user must have write permissions to the "bs-versions.json". To correct the permissions do command below: - -```bash -chmod +002 /opt/BSManager/resources/assets/jsons/bs-versions.json - -# or - -chown $(whoami) /opt/BSManager/resources/assets/jsons/bs-versions.json -``` diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md index d114fbc6..1c5eddab 100644 --- a/docs/wiki/_Sidebar.md +++ b/docs/wiki/_Sidebar.md @@ -71,7 +71,6 @@ 🐧 Linux
  • Missing Icons in Game
  • -
  • Permission Denied on "bs-versions.json"
  • [Deb] The SUID Sandbox Helper Binary Was Found
  • [Flatpak] Steam Beat Saber Version Not Showing / Proton Not Detected
  • [Flatpak] Changing Installation Folder
  • diff --git a/src/main/services/bs-version-lib.service.ts b/src/main/services/bs-version-lib.service.ts index 70c16e9c..b90582e1 100644 --- a/src/main/services/bs-version-lib.service.ts +++ b/src/main/services/bs-version-lib.service.ts @@ -5,8 +5,6 @@ import { BSVersion } from "shared/bs-version.interface"; import { RequestService } from "./request.service"; import { readJSON } from "fs-extra"; import { allSettled } from "../../shared/helpers/promise.helpers"; -import { LinuxService } from "./linux.service"; -import { IS_FLATPAK } from "main/constants"; import { StaticConfigurationService } from "./static-configuration.service"; export class BSVersionLibService { @@ -15,7 +13,6 @@ export class BSVersionLibService { private static instance: BSVersionLibService; - private readonly linuxService: LinuxService; private readonly utilsService: UtilsService; private readonly requestService: RequestService; private readonly configService: StaticConfigurationService; @@ -23,7 +20,6 @@ export class BSVersionLibService { private bsVersions: BSVersion[]; private constructor() { - this.linuxService = LinuxService.getInstance(); this.utilsService = UtilsService.getInstance(); this.requestService = RequestService.getInstance(); this.configService = StaticConfigurationService.getInstance(); @@ -40,15 +36,10 @@ export class BSVersionLibService { return this.requestService.getJSON(this.REMOTE_BS_VERSIONS_URL).then(res => res.data); } - private async shouldLoadFromConfig(): Promise { - // Some special cases of readonly memory installations - return process.platform === "linux" && (IS_FLATPAK || this.linuxService.isNixOS()); - } - private async getLocalVersions(): Promise { const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE); - if (!(await this.shouldLoadFromConfig())) { + if (process.platform !== "linux") { return readJSON(localVersionsPath); } @@ -60,7 +51,7 @@ export class BSVersionLibService { } private async updateLocalVersions(versions: BSVersion[]): Promise { - if (await this.shouldLoadFromConfig()) { + if (process.platform === "linux") { this.configService.set("versions", versions); return; } From 773b54ca0866aa63b1352f250559eebada766fea Mon Sep 17 00:00:00 2001 From: silentrald Date: Fri, 17 Jan 2025 07:35:12 +0800 Subject: [PATCH 6/7] [bugfix] recursively create the compatdata folder --- src/main/services/linux.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/services/linux.service.ts b/src/main/services/linux.service.ts index b3ead173..5f12896a 100644 --- a/src/main/services/linux.service.ts +++ b/src/main/services/linux.service.ts @@ -93,7 +93,7 @@ export class LinuxService { const compatDataPath = this.getCompatDataPath(); if (!fs.existsSync(compatDataPath)) { log.info(`Proton compat data path not found at '${compatDataPath}', creating directory`); - fs.mkdirSync(compatDataPath); + await fs.ensureDir(compatDataPath); } // Setup Proton environment variables From effe0608e1e03ddc1dcee723df0713f423bf744d Mon Sep 17 00:00:00 2001 From: silentrald Date: Sat, 18 Jan 2025 22:25:02 +0800 Subject: [PATCH 7/7] [bugfix] fix light theming pin icon color --- .../slides/launch/launch-options-panel.component.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx index bfea54ca..7ef2b418 100644 --- a/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx +++ b/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx @@ -60,7 +60,9 @@ export function LaunchModItem({ id, icon: Icon, label, description, active, visi {onPinChange && ( )}