From dc3ae1971d38b5d1e66c60219b8d5937d39f37fb Mon Sep 17 00:00:00 2001 From: silentrald Date: Thu, 16 Jan 2025 00:24:59 +0800 Subject: [PATCH] [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 + ) + }; + } + }