[feature] Rework of the BS launching system to prepare for shortcuts

This commit is contained in:
MathieuG-P
2023-06-26 23:25:53 +02:00
parent f9674df2ce
commit b7e287c86a
12 changed files with 249 additions and 78 deletions
+17 -9
View File
@@ -1,17 +1,25 @@
import { ipcMain } from 'electron';
import { UtilsService } from '../services/utils.service'
import { LauchOption } from "shared/models/bs-launch";
import { LaunchOption } from "shared/models/bs-launch";
import { BSLauncherService } from "../services/bs-launcher.service"
import { IpcRequest } from 'shared/models/ipc';
import { BsmException } from 'shared/models/bsm-exception.model';
import { IpcService } from '../services/ipc.service';
ipcMain.on('bs-launch.launch', (event, request: IpcRequest<LauchOption>) => {
const launcherService = BSLauncherService.getInstance();
const utilsService = UtilsService.getInstance();
// ipcMain.on('bs-launch.launch', (event, request: IpcRequest<LauchOption>) => {
// const launcherService = BSLauncherService.getInstance();
// const utilsService = UtilsService.getInstance();
launcherService.launch(request.args).then(res => {
utilsService.ipcSend(request.responceChannel, {success: true, data: res});
}).catch((err: BsmException) => {
utilsService.ipcSend(request.responceChannel, {success: false, error: err});
})
// launcherService.launch(request.args).then(res => {
// utilsService.ipcSend(request.responceChannel, {success: true, data: res});
// }).catch((err: BsmException) => {
// utilsService.ipcSend(request.responceChannel, {success: false, error: err});
// })
// });
const ipc = IpcService.getInstance();
ipc.on('bs-launch.launch', async (req: IpcRequest<LaunchOption>, reply) => {
const bsLauncher = BSLauncherService.getInstance();
reply(bsLauncher.launchV2(req.args));
});
+97 -4
View File
@@ -1,15 +1,15 @@
import path from "path";
import { LaunchResult, LauchOption } from "shared/models/bs-launch";
import { LaunchResult, LaunchOption, BSLaunchEvent, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEventType } from "../../shared/models/bs-launch";
import { UtilsService } from "./utils.service";
import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID } from "../constants";
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process";
import { SteamService } from "./steam.service";
import { BSLocalVersionService } from "./bs-local-version.service";
import { OculusService } from "./oculus.service";
import { pathExist } from "../helpers/fs.helpers";
import { rename } from "fs/promises";
import log from "electron-log";
import { timer } from "rxjs";
import { Observable, lastValueFrom, timer } from "rxjs";
import { NotificationService } from "./notification.service";
import { NotificationType } from "../../shared/models/notification/notification.model";
@@ -60,7 +60,7 @@ export class BSLauncherService{
}
// TODO : Rework with shortcuts implementation
public async launch(launchOptions: LauchOption): Promise<LaunchResult>{
public async launch(launchOptions: LaunchOption): Promise<LaunchResult>{
if(this.isBsRunning() === true){ return "BS_ALREADY_RUNNING" }
if(launchOptions.version.oculus && this.oculusService.oculusRunning() === false){ return "OCULUS_NOT_RUNNING" }
@@ -121,4 +121,97 @@ export class BSLauncherService{
return "LAUNCHED";
}
private buildBsLaunchArgs(launchOptions: LaunchOption){
let launchArgs = [];
if(!launchOptions.version.steam && !launchOptions.version.oculus){ launchArgs.push("--no-yeet"); }
if(launchOptions.oculus){ launchArgs.push("-vrmode oculus"); }
if(launchOptions.desktop){ launchArgs.push("fpfc"); }
if(launchOptions.debug){ launchArgs.push("--verbose"); }
if(launchOptions.additionalArgs){ launchArgs.push(...launchOptions.additionalArgs); }
return Array.from(new Set(launchArgs).values());
}
private launchBSProcess(bsExePath: string, args: string[], debug = false): Promise<void>{
if(this.bsProcess?.connected){
return Promise.reject("Beat Saber process already running");
}
const spawnOptions: SpawnOptionsWithoutStdio = { shell: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} };
if(debug){
spawnOptions.detached = true;
spawnOptions.windowsVerbatimArguments = true;
}
this.bsProcess = spawn(`\"${bsExePath}\"`, args, spawnOptions);
return new Promise((resolve, reject) => {
this.bsProcess.on('error', e => { log.error(e); reject(e); });
this.bsProcess.once('exit', code => {
if(code !== 0){
log.error(`Beat Saber process exited with code ${code}`);
}
resolve();
});
});
}
public launchV2(launchOptions: LaunchOption): Observable<BSLaunchEvent>{
return new Observable<BSLaunchEvent>(obs => {(async () => {
if(this.isBsRunning()){
return obs.error({type: BSLaunchErrorType.BS_ALREADY_RUNNING} as BSLaunchErrorEvent);
}
if(launchOptions.version.oculus && this.oculusService.oculusRunning() === false){
return obs.error({type: BSLaunchErrorType.OCULUS_NOT_RUNNING} as BSLaunchErrorEvent);
}
const bsFolderPath = await this.localVersionService.getVersionPath(launchOptions.version);
const exePath = path.join(bsFolderPath, BS_EXECUTABLE);
if(!(await pathExist(exePath))){
return obs.error({type: BSLaunchErrorType.BS_NOT_FOUND} as BSLaunchErrorEvent);
}
// Open Steam if not running
if(!launchOptions.version.oculus && !(await this.steamService.steamRunning())){
obs.next({type: BSLaunchEventType.STEAM_LAUNCHING});
await this.steamService.openSteam().catch(log.error);
}
// Backup SteamVR when desktop mode is enabled
if(!launchOptions.version.oculus && launchOptions.desktop){
await this.backupSteamVR().catch(e => {
log.error("ERR_BACKUP_STEAM_VR", e);
this.restoreSteamVR();
});
await lastValueFrom(timer(2_000));
} else if(!launchOptions.version.oculus){
await this.restoreSteamVR();
}
const launchArgs = this.buildBsLaunchArgs(launchOptions);
obs.next({type: BSLaunchEventType.BS_LAUNCHING});
await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(() => {
obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR} as BSLaunchErrorEvent);
}).finally(() => {
if(!launchOptions.desktop || launchOptions.version.oculus){ return; }
this.restoreSteamVR().catch(e => log.error("ERR_RESTORE_STEAM_VR", e));
});
})().then(() => {
obs.complete()
}).catch(err => {
obs.error({type: BSLaunchErrorType.UNKNOWN_ERROR, data: err} as BSLaunchErrorEvent);
})});
}
}
+43 -41
View File
@@ -9,21 +9,21 @@ import log from "electron-log";
export class SteamService{
private static instance: SteamService;
private static instance: SteamService;
private readonly utils: UtilsService = UtilsService.getInstance();
private readonly utils: UtilsService = UtilsService.getInstance();
private steamPath: string = '';
private steamPath: string = '';
private constructor(){
const vbsDirectory = path.join(this.utils.getAssetsScriptsPath(), "node-regedit", "vbs");
regedit.setExternalVBSLocation(vbsDirectory);
}
private constructor(){
const vbsDirectory = path.join(this.utils.getAssetsScriptsPath(), "node-regedit", "vbs");
regedit.setExternalVBSLocation(vbsDirectory);
}
public static getInstance(){
if(!SteamService.instance){ SteamService.instance = new SteamService(); }
return SteamService.instance;
}
public static getInstance(){
if(!SteamService.instance){ SteamService.instance = new SteamService(); }
return SteamService.instance;
}
public async getActiveUser(): Promise<number>{
const res = await regedit.promisified.list(["HKCU\\Software\\Valve\\Steam\\ActiveProcess"]);
@@ -40,47 +40,49 @@ export class SteamService{
.catch(e => {log.error(e); throw e})
}
public async getSteamPath(): Promise<string>{
public async getSteamPath(): Promise<string>{
if(!!this.steamPath){ return this.steamPath; }
if(!!this.steamPath){ return this.steamPath; }
const [win32Res, win64Res] = await Promise.all([
regedit.promisified.list(['HKLM\\SOFTWARE\\Valve\\Steam']),
regedit.promisified.list(['HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam'])
]);
const [win32Res, win64Res] = await Promise.all([
regedit.promisified.list(['HKLM\\SOFTWARE\\Valve\\Steam']),
regedit.promisified.list(['HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam'])
]);
const [win32, win64] = [win32Res["HKLM\\SOFTWARE\\Valve\\Steam"], win64Res["HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"]];
const [win32, win64] = [win32Res["HKLM\\SOFTWARE\\Valve\\Steam"], win64Res["HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"]];
let res = '';
if(win64.exists && win64?.values?.InstallPath?.value){ res = win64.values.InstallPath.value as string; }
else if(win32.exists && win32?.values?.InstallPath?.value){ res = win32.values.InstallPath.value as string; }
this.steamPath = res;
return this.steamPath;
}
let res = '';
if(win64.exists && win64?.values?.InstallPath?.value){ res = win64.values.InstallPath.value as string; }
else if(win32.exists && win32?.values?.InstallPath?.value){ res = win32.values.InstallPath.value as string; }
this.steamPath = res;
return this.steamPath;
}
public async getGameFolder(gameId: string, gameFolder?: string): Promise<string>{
try{
const steamPath = await this.getSteamPath();
public async getGameFolder(gameId: string, gameFolder?: string): Promise<string>{
try{
const steamPath = await this.getSteamPath();
let libraryFolders: any = path.join(steamPath, 'steamapps', 'libraryfolders.vdf');
let libraryFolders: any = path.join(steamPath, 'steamapps', 'libraryfolders.vdf');
if(!(await pathExist(libraryFolders))){ return null; }
libraryFolders = parse(await readFile(libraryFolders, {encoding: 'utf-8'}));
if(!(await pathExist(libraryFolders))){ return null; }
libraryFolders = parse(await readFile(libraryFolders, {encoding: 'utf-8'}));
if(!libraryFolders.libraryfolders){ return null; }
libraryFolders = libraryFolders.libraryfolders
if(!libraryFolders.libraryfolders){ return null; }
libraryFolders = libraryFolders.libraryfolders
for(const libKey in Object.keys(libraryFolders)){
if(!libraryFolders[libKey] || !libraryFolders[libKey]["apps"]){ continue; }
if(libraryFolders[libKey]["apps"][gameId] != null){ return path.join(libraryFolders[libKey]["path"], "steamapps", "common", gameFolder); };
for(const libKey in Object.keys(libraryFolders)){
if(!libraryFolders[libKey] || !libraryFolders[libKey]["apps"]){ continue; }
if(libraryFolders[libKey]["apps"][gameId] != null){ return path.join(libraryFolders[libKey]["path"], "steamapps", "common", gameFolder); };
}
return null;
}
catch(e){
log.error(e);
return null;
}
return null;
}
catch(e){
log.error(e);
return null;
}
}
public openSteam(): Promise<void>{
const process = spawn("start", ["steam://open/games"], {shell: true});