Launch Beat Saber with Proton on Linux

This commit is contained in:
Pierce Thompson
2024-03-27 01:53:49 -04:00
committed by Pierce Thompson
parent 12a8a49ccb
commit 9b8f1a7c42
7 changed files with 67 additions and 7 deletions
+3 -1
View File
@@ -381,6 +381,7 @@
"OCULUS_NOT_RUNNING": "Oculus is not running",
"BS_ALREADY_RUNNING": "Beat Saber already running",
"EXE_NOT_FINDED": "Missing files",
"PROTON_NOT_SET": "Proton path not set",
"EXIT": "Abrupt stop",
"OCULUS_LIB_NOT_FOUND": "Oculus library not found"
},
@@ -391,7 +392,8 @@
"BS_ALREADY_RUNNING": "Close BeatSaber before launching it again.",
"EXE_NOT_FINDED": "Some files seem to be missing, try to verify the files.",
"EXIT": "BeatSaber stopped abruptly, tries to check the files.",
"OCULUS_LIB_NOT_FOUND": "Check that the Oculus application is properly installed, and that the libraries are correctly defined in Oculus."
"OCULUS_LIB_NOT_FOUND": "Check that the Oculus application is properly installed, and that the libraries are correctly defined in Oculus.",
"PROTON_NOT_SET": "Set the Proton path in settings"
},
"actions": {
"STEAM_NOT_RUNNING": "Launch Steam"
@@ -53,6 +53,13 @@ export abstract class AbstractLauncherService {
let timoutId: NodeJS.Timeout;
const exit = new Promise<number>((resolve, reject) => {
// Don't remove, useful for debugging!
// process.stdout.on("data", (data) => {
// log.info(`BS stdout: ${data}`);
// });
// process.stderr.on("data", (data) => {
// log.error(`BS stderr: ${data}`);
// });
process.on("error", (err) => {
log.error(`Error while launching BS`, err);
@@ -11,6 +11,7 @@ import { CustomError } from "../../../shared/models/exceptions/custom-error.clas
import isElevated from "is-elevated";
import { UtilsService } from "../utils.service";
import { exec } from "child_process";
import fs from 'fs';
export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{
@@ -75,7 +76,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
return new Observable<BSLaunchEventData>(obs => {(async () => {
const bsFolderPath = await this.localVersions.getInstalledVersionPath(launchOptions.version);
const exePath = path.join(bsFolderPath, BS_EXECUTABLE);
let exePath = path.join(bsFolderPath, BS_EXECUTABLE);
if(!(await pathExists(exePath))){
throw CustomError.fromError(new Error(`Path not exist : ${exePath}`), BSLaunchError.BS_NOT_FOUND);
@@ -102,15 +103,61 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
await this.restoreSteamVR().catch(log.error);
}
const launchArgs = this.buildBsLaunchArgs(launchOptions);
let launchArgs = this.buildBsLaunchArgs(launchOptions);
const steamPath = await this.steam.getSteamPath();
const env = {
...process.env,
"SteamAppId": BS_APP_ID,
"SteamOverlayGameId": BS_APP_ID,
"SteamGameId": BS_APP_ID,
};
// Linux setup
if (process.platform === "linux") {
// 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 = `${steamPath}/steamapps/compatdata/${BS_APP_ID}`;
if (!fs.existsSync(compatDataPath)) {
log.info(`Proton compat data path not found at '${compatDataPath}', creating directory`);
fs.mkdirSync(compatDataPath);
}
// proton run BeatSaber.exe
launchArgs = [
"run",
`${exePath}`,
...launchArgs,
];
exePath = launchOptions.protonPath;
if (!exePath) {
throw CustomError.fromError(new Error("Proton path not set"), BSLaunchError.PROTON_NOT_SET);
}
// Setup Proton environment variables
Object.assign(env, {
"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,
// Uncomment these to create a proton log file in the Beat Saber install directory.
// "PROTON_LOG": 1,
// "PROTON_LOG_DIR": bsFolderPath,
});
}
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
const spawnOpts = { env, cwd: bsFolderPath };
const launchPromise = !launchOptions.admin ? (
this.launchBs(exePath, launchArgs, { env: {...process.env, "SteamAppId": BS_APP_ID} }).exit
this.launchBs(exePath, launchArgs, spawnOpts).exit
) : (
new Promise<number>(resolve => {
const adminProcess = exec(`"${this.getStartBsAsAdminExePath()}" "${exePath}" ${launchArgs.join(" ")}`, { env: {...process.env, "SteamAppId": BS_APP_ID} });
const adminProcess = exec(`"${this.getStartBsAsAdminExePath()}" "${exePath}" ${launchArgs.join(" ")}`, spawnOpts);
adminProcess.on("error", err => {
log.error("Error while starting BS as Admin", err);
resolve(-1)
@@ -68,7 +68,8 @@ export function LaunchSlide({ version }: Props) {
oculus: version.oculus ? false : oculusMode,
desktop: desktopMode,
debug: debugMode,
additionalArgs
additionalArgs,
protonPath: bsLauncherService.getProtonPath(),
});
return lastValueFrom(launch$).catch(() => {});
@@ -93,6 +93,7 @@ export class BSLauncherService {
}
public doLaunch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
launchOptions.protonPath = this.getProtonPath();
return this.ipcService.sendV2("bs-launch.launch", launchOptions);
}
@@ -14,6 +14,7 @@ export enum BSLaunchError{
OCULUS_NOT_RUNNING = "OCULUS_NOT_RUNNING",
BS_EXIT_ERROR = "EXIT",
OCULUS_LIB_NOT_FOUND = "OCULUS_LIB_NOT_FOUND",
PROTON_NOT_SET = "PROTON_NOT_SET",
UNKNOWN_ERROR = "UNKNOWN_ERROR",
}
@@ -27,4 +28,4 @@ export enum BSLaunchWarning{
UNABLE_TO_LAUNCH_STEAM = "UNABLE_TO_LAUNCH_STEAM",
}
export type BSLaunchEventType = BSLaunchEvent | BSLaunchWarning;
export type BSLaunchEventType = BSLaunchEvent | BSLaunchWarning;
@@ -7,4 +7,5 @@ export interface LaunchOption {
debug?: boolean,
additionalArgs?: string[],
admin?: boolean,
protonPath?: string,
}