[bugfix-742] launch bs version directly with steam shortcut

This commit is contained in:
silentrald
2025-01-16 00:24:59 +08:00
parent 350dff37cf
commit dc3ae1971d
6 changed files with 122 additions and 57 deletions
+2 -1
View File
@@ -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
@@ -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 || {}) };
@@ -185,7 +185,7 @@ export class BSLauncherService {
* @returns {Promise<string>} Path of the icon
*/
private async createShortcutIco(color: Color): Promise<string>{
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({
@@ -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);
@@ -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 = {
+66 -13
View File
@@ -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<string> {
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<string, string>,
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<SteamShortcutData> {
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
)
};
}
}