[feature-274] Can now launch downgraded version from Oculus

This commit is contained in:
MathieuG-P
2023-11-03 15:11:35 +01:00
parent f45698121f
commit 9b7fa23016
17 changed files with 404 additions and 183 deletions
+1
View File
@@ -3,6 +3,7 @@ import path from "path";
export const BS_EXECUTABLE = "Beat Saber.exe";
export const OCULUS_BS_DIR = "hyperbolic-magnetism-beat-saber";
export const OCULUS_BS_BACKUP_DIR = `${OCULUS_BS_DIR}.bsmbak`;
export const BS_APP_ID = "620980";
export const BS_DEPOT = "620981";
export const APP_NAME = "BSManager";
+13
View File
@@ -0,0 +1,13 @@
import log from "electron-log";
import psList from "ps-list";
export async function taskRunning(task: string): Promise<boolean> {
try {
const processes = await psList();
return processes.some(process => process.name?.includes(task) || process.cmd?.includes(task));
}
catch(error){
log.error(error);
return false;
}
}
+4 -3
View File
@@ -1,8 +1,9 @@
import { LaunchOption } from "shared/models/bs-launch";
import { BSLauncherService } from "../services/bs-launcher.service"
import { BSLauncherService } from "../services/bs-launcher/bs-launcher.service"
import { IpcService } from '../services/ipc.service';
import { from } from "rxjs";
import { SteamLauncherService } from "../services/bs-launcher/steam-launcher.service";
const ipc = IpcService.getInstance();
@@ -18,6 +19,6 @@ ipc.on<LaunchOption>("create-launch-shortcut", (req, reply) => {
ipc.on<void>("bs-launch.restore-steamvr", (_, reply) => {
const bsLauncher = BSLauncherService.getInstance();
reply(from(bsLauncher.restoreSteamVR()));
const steamLauncher = SteamLauncherService.getInstance();
reply(from(steamLauncher.restoreSteamVR()));
});
+5 -2
View File
@@ -17,9 +17,11 @@ import { LocalMapsManagerService } from "./services/additional-content/local-map
import { LocalPlaylistsManagerService } from "./services/additional-content/local-playlists-manager.service";
import { LocalModelsManagerService } from "./services/additional-content/local-models-manager.service";
import { APP_NAME } from "./constants";
import { BSLauncherService } from "./services/bs-launcher.service";
import { BSLauncherService } from "./services/bs-launcher/bs-launcher.service";
import { IpcRequest } from "shared/models/ipc";
import { LivShortcut } from "./services/liv/liv-shortcut.service";
import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.service";
import { OculusLauncherService } from "./services/bs-launcher/oculus-launcher.service";
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
@@ -102,7 +104,8 @@ if (!gotTheLock) {
DeepLinkService.getInstance().dispatchLinkOpened(deepLink);
}
BSLauncherService.getInstance().restoreSteamVR();
SteamLauncherService.getInstance().restoreSteamVR();
OculusLauncherService.getInstance().deleteBsSymlinks().catch(log.error)
// Log renderer errors
ipcMain.on("log-error", (_, args: IpcRequest<unknown>) => {
@@ -0,0 +1,60 @@
import { LaunchOption } from "shared/models/bs-launch";
import { BSLocalVersionService } from "../bs-local-version.service";
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process";
import path from "path";
export abstract class AbstractLauncherService {
protected readonly localVersions = BSLocalVersionService.getInstance();
constructor(){
this.localVersions = BSLocalVersionService.getInstance();
}
protected buildBsLaunchArgs(launchOptions: LaunchOption): string[]{
const launchArgs = ["--no-yeet"];
if (launchOptions.oculus) {
launchArgs.push("-vrmode");
launchArgs.push("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());
}
protected launchBSProcess(bsExePath: string, args: string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams {
const spawnOptions: SpawnOptionsWithoutStdio = { detached: true, cwd: path.dirname(bsExePath), ...(options || {}) };
if(args.includes("--verbose")){
spawnOptions.windowsVerbatimArguments = true;
}
return spawn(bsExePath, args, spawnOptions);
}
protected launchBs(bsExePath: string, args: string[], options?: SpawnOptionsWithoutStdio): Promise<number> {
return new Promise<number>((resolve, reject) => {
const bsProcess = this.launchBSProcess(bsExePath, args, options);
bsProcess.on("error", reject);
bsProcess.on("exit", resolve);
setTimeout(() => {
bsProcess.removeAllListeners("error");
bsProcess.removeAllListeners("exit");
resolve(-1);
}, 30_000);
});
}
}
@@ -1,36 +1,36 @@
import path from "path";
import { LaunchOption, BSLaunchEvent, BSLaunchEventData, BSLaunchWarning, BSLaunchErrorData, BSLaunchError } from "../../shared/models/bs-launch";
import { UtilsService } from "./utils.service";
import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID, IMAGE_CACHE_PATH } from "../constants";
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process";
import { SteamService } from "./steam.service";
import { BSLocalVersionService } from "./bs-local-version.service";
import { pathExist } from "../helpers/fs.helpers";
import { rename } from "fs/promises";
import { LaunchOption, BSLaunchEventData } from "../../../shared/models/bs-launch";
import { IMAGE_CACHE_PATH } from "../../constants";
import { BSLocalVersionService } from "../bs-local-version.service";
import log from "electron-log";
import { Observable, of } from "rxjs";
import { BsmProtocolService } from "./bsm-protocol.service";
import { Observable, of, throwError } from "rxjs";
import { BsmProtocolService } from "../bsm-protocol.service";
import { app, shell} from "electron";
import Color from "color";
import { ensureDir, writeFile } from "fs-extra";
import toIco from "to-ico";
import { objectFromEntries } from "../../shared/helpers/object.helpers";
import { WindowManagerService } from "./window-manager.service";
import { IpcService } from "./ipc.service";
import { BSVersionLibService } from "./bs-version-lib.service";
import { execOnOs } from "../helpers/env.helpers";
import { objectFromEntries } from "../../../shared/helpers/object.helpers";
import { WindowManagerService } from "../window-manager.service";
import { IpcService } from "../ipc.service";
import { BSVersionLibService } from "../bs-version-lib.service";
import { execOnOs } from "../../helpers/env.helpers";
import sharp from "sharp";
import { StoreLauncherInterface } from "./store-launcher.interface";
import { SteamLauncherService } from "./steam-launcher.service";
import { OculusLauncherService } from "./oculus-launcher.service";
import { BSVersion } from "shared/bs-version.interface";
import { BsStore } from "../../../shared/models/bs-store.enum";
export class BSLauncherService {
private static instance: BSLauncherService;
private readonly utilsService: UtilsService;
private readonly steamService: SteamService;
private readonly localVersionService: BSLocalVersionService;
private readonly bsmProtocolService: BsmProtocolService;
private readonly windows: WindowManagerService;
private readonly ipc: IpcService;
private readonly remoteVersion: BSVersionLibService;
private readonly steamLauncher: SteamLauncherService;
private readonly oculusLauncher: OculusLauncherService;
public static getInstance(): BSLauncherService {
if (!BSLauncherService.instance) {
@@ -40,13 +40,13 @@ export class BSLauncherService {
}
private constructor() {
this.utilsService = UtilsService.getInstance();
this.steamService = SteamService.getInstance();
this.localVersionService = BSLocalVersionService.getInstance();
this.bsmProtocolService = BsmProtocolService.getInstance();
this.windows = WindowManagerService.getInstance();
this.ipc = IpcService.getInstance();
this.remoteVersion = BSVersionLibService.getInstance();
this.steamLauncher = SteamLauncherService.getInstance();
this.oculusLauncher = OculusLauncherService.getInstance();
this.bsmProtocolService.on("launch", link => {
log.info("Launch from bsm protocol", link.toString());
@@ -54,128 +54,23 @@ export class BSLauncherService {
});
}
private getSteamVRPath(): Promise<string> {
return this.steamService.getGameFolder(STEAMVR_APP_ID, "SteamVR");
}
private async backupSteamVR(): Promise<void> {
const steamVrFolder = await this.getSteamVRPath();
if (!(await pathExist(steamVrFolder))) {
return;
}
return rename(steamVrFolder, `${steamVrFolder}.bak`).catch(log.error);
}
public async restoreSteamVR(): Promise<void> {
const steamVrFolder = await this.getSteamVRPath();
const steamVrBackup = `${steamVrFolder}.bak`;
if (!(await pathExist(steamVrBackup))) {
return;
}
return rename(steamVrBackup, steamVrFolder).catch(log.error);
}
public async isBsRunning(): Promise<boolean> {
return this.utilsService.taskRunning(BS_EXECUTABLE);
}
private buildBsLaunchArgs(launchOptions: LaunchOption): string[]{
const launchArgs = ["--no-yeet"];
if (launchOptions.oculus) {
launchArgs.push("-vrmode");
launchArgs.push("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): ChildProcessWithoutNullStreams{
const spawnOptions: SpawnOptionsWithoutStdio = { detached: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} };
if(debug){
spawnOptions.windowsVerbatimArguments = true;
}
return spawn(bsExePath, args, spawnOptions);
private getStoreLauncherFromVersion(version: BSVersion): StoreLauncherInterface {
if(version.steam){ return this.steamLauncher; }
if(version.oculus){ return this.oculusLauncher; }
if(version.metadata?.store === BsStore.STEAM){ return this.steamLauncher; }
if(version.metadata?.store === BsStore.OCULUS){ return this.oculusLauncher; }
return null;
}
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
return new Observable<BSLaunchEventData>(obs => {(async () => {
const launcher = this.getStoreLauncherFromVersion(launchOptions.version);
if(launchOptions.skipAlreadyRunning !== true && await this.isBsRunning()){
return obs.error({type: BSLaunchError.BS_ALREADY_RUNNING} as BSLaunchErrorData);
}
if(!launcher){
return throwError(() => new Error("Unable to get launcher for the provided version"));
}
const bsFolderPath = await this.localVersionService.getInstalledVersionPath(launchOptions.version);
const exePath = path.join(bsFolderPath, BS_EXECUTABLE);
if(!(await pathExist(exePath))){
return obs.error({type: BSLaunchError.BS_NOT_FOUND} as BSLaunchErrorData);
}
// Open Steam if not running
if(!launchOptions.version.oculus && !(await this.steamService.steamRunning())){
obs.next({type: BSLaunchEvent.STEAM_LAUNCHING});
await this.steamService.openSteam().then(() => {
obs.next({type: BSLaunchEvent.STEAM_LAUNCHED});
}).catch(e => {
log.error(e);
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
});
}
// Backup SteamVR when desktop mode is enabled
if(!launchOptions.version.oculus && launchOptions.desktop){
await this.backupSteamVR().catch(() => {
return this.restoreSteamVR();
});
} else if(!launchOptions.version.oculus){
await this.restoreSteamVR();
}
const launchArgs = this.buildBsLaunchArgs(launchOptions);
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
await new Promise<number>((resolve, reject) => {
const bsProcess = this.launchBSProcess(exePath, launchArgs, launchOptions.debug);
bsProcess.on("error", reject);
bsProcess.on("exit", resolve);
setTimeout(() => {
bsProcess.removeAllListeners("error");
bsProcess.removeAllListeners("exit");
resolve(-1);
}, 30_000);
}).then(exitCode => {
log.info("BS process exit code", exitCode);
}).catch(err => {
obs.error({type: BSLaunchError.BS_EXIT_ERROR, data: err} as BSLaunchErrorData);
}).finally(() => {
this.restoreSteamVR().catch(log.error);
});
})().then(() => {
obs.complete();
}).catch(err => {
obs.error({type: BSLaunchError.UNKNOWN_ERROR, data: err} as BSLaunchErrorData);
})});
return launcher.launch(launchOptions);
}
public shortcutLinkToShortcutParams(shortcutLink: string|URL): ShortcutParams{
@@ -0,0 +1,133 @@
import { Observable, ReplaySubject, catchError, lastValueFrom, of, take, timeout } from "rxjs";
import { StoreLauncherInterface } from "./store-launcher.interface";
import { BSLaunchError, BSLaunchErrorData, BSLaunchEventData, LaunchOption } from "../../../shared/models/bs-launch";
import { OculusService } from "../oculus.service";
import { BS_EXECUTABLE, OCULUS_BS_BACKUP_DIR, OCULUS_BS_DIR } from "../../constants";
import path from "path";
import log from "electron-log";
import { sToMs } from "../../../shared/helpers/time.helpers";
import { pathExists, readdir, rename, stat, symlink, unlink } from "fs-extra";
import { AbstractLauncherService } from "./abstract-launcher.service";
import { taskRunning } from "../../helpers/os.helpers";
export class OculusLauncherService extends AbstractLauncherService implements StoreLauncherInterface {
public static instance: OculusLauncherService;
public static getInstance(): OculusLauncherService {
if (!OculusLauncherService.instance) {
OculusLauncherService.instance = new OculusLauncherService();
}
return OculusLauncherService.instance;
}
private readonly oculus: OculusService;
private readonly oculusLib$ = new ReplaySubject<string>();
private constructor() {
super();
this.oculus = OculusService.getInstance();
this.oculus.tryGetGameFolder([OCULUS_BS_DIR, OCULUS_BS_BACKUP_DIR]).then(async dirPath => {
if(dirPath){ return this.oculusLib$.next( path.join(dirPath, "..") ); }
const defaultLib = ((await this.oculus.getOculusLibs()) || []).find(lib => lib.isDefault);
if(defaultLib?.path){ return this.oculusLib$.next(path.join(defaultLib.path, "Software")); }
this.oculusLib$.next(null);
}).catch(err => {
log.error("Error while getting Oculus libs", err);
this.oculusLib$.next(null);
});
}
public async deleteBsSymlinks(): Promise<void> {
const oculusLibPath = await lastValueFrom(this.oculusLib$.pipe(take(1), timeout(sToMs(30)), catchError(() => of(null))));
if(!oculusLibPath || !(await pathExists(oculusLibPath))){
throw new Error("Oculus library not found, deleteBsSymlinks");
}
const libContents = await readdir(oculusLibPath, { withFileTypes: true});
const symlinks = libContents.filter(dirent => dirent.isSymbolicLink());
const dirSymlinks = (await Promise.all(symlinks.map(async symlink => {
const symlinkPath = path.join(oculusLibPath, symlink.name);
const symlinkStats = await stat(symlinkPath);
if(!symlinkStats.isDirectory()){ return null; }
return symlink;
}))).filter(Boolean);
const bsSymlinks = dirSymlinks.filter(dirent => dirent.name.startsWith(OCULUS_BS_DIR));
// get only symlinks created by BSM (with metadata.config)
const bsmSymlinks = (await Promise.all(bsSymlinks.map(async symlink => {
const symlinkPath = path.join(oculusLibPath, symlink.name);
const symlinkContents = await readdir(symlinkPath);
if(!symlinkContents.includes("metadata.config")){
return null;
}
return symlink;
}))).filter(Boolean);
await Promise.all(bsmSymlinks.map(symlink => {
return unlink(path.join(oculusLibPath, symlink.name));
}));
}
private async backupOriginalBeatSaber(): Promise<void>{
const bsFolder = await this.oculus.getGameFolder(OCULUS_BS_DIR);
if(!bsFolder){ return; }
const backupPath = path.join(bsFolder, "..", OCULUS_BS_BACKUP_DIR);
return rename(bsFolder, backupPath);
}
private async restoreOriginalBeatSaber(): Promise<void>{
const bsFolderBackupPath = await this.oculus.getGameFolder(OCULUS_BS_BACKUP_DIR);
if(!(await pathExists(bsFolderBackupPath))){ return; }
const originalPath = path.join(bsFolderBackupPath, "..", OCULUS_BS_DIR);
return rename(bsFolderBackupPath, originalPath);
}
// TODO : Convert all errors to CustomError
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData> {
return new Observable<BSLaunchEventData>(obs => {
(async () => {
// Cannot start multiple instances of Beat Saber with Oculus
const bsRunning = await taskRunning(BS_EXECUTABLE);
if(bsRunning){
throw ({type: BSLaunchError.BS_ALREADY_RUNNING, data: bsRunning}) as BSLaunchErrorData;
}
const oculusLib = await lastValueFrom(this.oculusLib$.pipe(take(1), timeout(sToMs(30)), catchError(() => of(null))));
if(!oculusLib){
throw new Error("No Oculus library found");
}
// Remove previously symlinks created by BSM
await this.deleteBsSymlinks().catch(log.error);
// Backup original Beat Saber folder
await this.backupOriginalBeatSaber();
// Create symlink in the oculus library from the BSM BS version
const symlinkTarget = await this.localVersions.getInstalledVersionPath(launchOptions.version);
const symlinkPath = path.join(oculusLib, OCULUS_BS_DIR);
await symlink(symlinkTarget, symlinkPath, "junction");
// Launch Beat Saber
const exePath = path.join(symlinkPath, "Beat Saber.exe");
return this.launchBs(exePath, this.buildBsLaunchArgs(launchOptions)).catch(err => {
throw ({type: BSLaunchError.BS_EXIT_ERROR, data: err}) as BSLaunchErrorData;
});
})().catch(err => {
obs.error(err);
}).finally(() => {
obs.complete();
})
});
}
}
@@ -0,0 +1,108 @@
import { Observable } from "rxjs";
import { BSLaunchError, BSLaunchErrorData, BSLaunchEvent, BSLaunchEventData, BSLaunchWarning, LaunchOption } from "../../../shared/models/bs-launch";
import { StoreLauncherInterface } from "./store-launcher.interface";
import { pathExists, rename } from "fs-extra";
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";
export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{
private static instance: SteamLauncherService;
public static getInstance(): SteamLauncherService{
if(!SteamLauncherService.instance){
SteamLauncherService.instance = new SteamLauncherService();
}
return SteamLauncherService.instance;
}
private readonly steam: SteamService;
private constructor(){
super();
this.steam = SteamService.getInstance();
}
private getSteamVRPath(): Promise<string> {
return this.steam.getGameFolder(STEAMVR_APP_ID, "SteamVR");
}
private async backupSteamVR(): Promise<void> {
const steamVrFolder = await this.getSteamVRPath();
if (!(await pathExists(steamVrFolder))) {
return;
}
return rename(steamVrFolder, `${steamVrFolder}.bak`).catch(err => {
log.error("Error while create backup of SteamVR", err);
});
}
public async restoreSteamVR(): Promise<void> {
const steamVrFolder = await this.getSteamVRPath();
const steamVrBackup = `${steamVrFolder}.bak`;
if (!(await pathExists(steamVrBackup))) {
return;
}
return rename(steamVrBackup, steamVrFolder).catch(err => {
log.error("Error while restoring SteamVR", err);
});
}
// TODO : Convert all errors to CustomError
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
return new Observable<BSLaunchEventData>(obs => {(async () => {
const bsFolderPath = await this.localVersions.getInstalledVersionPath(launchOptions.version);
const exePath = path.join(bsFolderPath, BS_EXECUTABLE);
if(!(await pathExists(exePath))){
return obs.error({type: BSLaunchError.BS_NOT_FOUND} as BSLaunchErrorData);
}
// Open Steam if not running
if(!launchOptions.version.oculus && !(await this.steam.steamRunning())){
obs.next({type: BSLaunchEvent.STEAM_LAUNCHING});
await this.steam.openSteam().then(() => {
obs.next({type: BSLaunchEvent.STEAM_LAUNCHED});
}).catch(e => {
log.error(e);
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
});
}
// Backup SteamVR when desktop mode is enabled
if(!launchOptions.version.oculus && launchOptions.desktop){
await this.backupSteamVR().catch(() => {
return this.restoreSteamVR();
});
} else if(!launchOptions.version.oculus){
await this.restoreSteamVR();
}
const launchArgs = this.buildBsLaunchArgs(launchOptions);
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
this.launchBs(exePath, launchArgs, { env: {...process.env, "SteamAppId": BS_APP_ID} }).then(exitCode => {
log.info("BS process exit code", exitCode);
}).catch(err => {
obs.error({type: BSLaunchError.BS_EXIT_ERROR, data: err} as BSLaunchErrorData);
}).finally(() => {
this.restoreSteamVR().catch(log.error);
});
})().then(() => {
obs.complete();
}).catch(err => {
obs.error({type: BSLaunchError.UNKNOWN_ERROR, data: err} as BSLaunchErrorData);
})});
}
}
@@ -0,0 +1,6 @@
import { Observable } from "rxjs";
import { BSLaunchEventData, LaunchOption } from "shared/models/bs-launch";
export interface StoreLauncherInterface {
launch(launchOptions: LaunchOption): Observable<BSLaunchEventData>;
}
@@ -2,7 +2,7 @@ import { BSVersionLibService } from "./bs-version-lib.service";
import { BSVersion, BSVersionMetadata } from "shared/bs-version.interface";
import { InstallationLocationService } from "./installation-location.service";
import { SteamService } from "./steam.service";
import { BS_APP_ID, OCULUS_BS_DIR } from "../constants";
import { BS_APP_ID, OCULUS_BS_BACKUP_DIR, OCULUS_BS_DIR } from "../constants";
import path from "path";
import { ConfigurationService } from "./configuration.service";
import { lstat, rename } from "fs/promises";
@@ -235,7 +235,7 @@ export class BSLocalVersionService {
}
private async getOculusVersion(): Promise<BSVersion> {
const oculusBsFolder = await this.oculusService.getGameFolder(OCULUS_BS_DIR);
const oculusBsFolder = await this.oculusService.tryGetGameFolder([OCULUS_BS_DIR, OCULUS_BS_BACKUP_DIR]);
if (!oculusBsFolder) {
return null;
@@ -1,4 +1,4 @@
import { BSLauncherService } from "../bs-launcher.service";
import { BSLauncherService } from "../bs-launcher/bs-launcher.service";
import { LivEntry, LivService } from "./liv.service";
import { LaunchOption } from "shared/models/bs-launch";
import { app } from "electron";
@@ -43,8 +43,7 @@ export class LivShortcut {
versions.forEach(version => this.createLivShortcut({
...(clearedLaunchOptions?.find(launchOpt => launchOpt.version.ino === version.ino) ?? {}),
version,
skipAlreadyRunning: true
version
}));
})().catch(err => log.error("Error while creating LIV shortcuts", err));
});
+33 -21
View File
@@ -1,4 +1,3 @@
import { UtilsService } from "./utils.service";
import regedit from "regedit";
import path from "path";
import { pathExist } from "../helpers/fs.helpers";
@@ -7,9 +6,7 @@ import log from "electron-log";
export class OculusService {
private static instance: OculusService;
private readonly utils: UtilsService;
private oculusPaths: string[];
private oculusLibraries: OculusLibrary[];
public static getInstance(): OculusService {
if (!OculusService.instance) {
@@ -18,22 +15,16 @@ export class OculusService {
return OculusService.instance;
}
private constructor() {
this.utils = UtilsService.getInstance();
}
private constructor() {}
public async oculusRunning(): Promise<boolean> {
return this.utils.taskRunning("OculusClient.exe");
}
public async getOculusLibsPath(): Promise<string[]> {
public async getOculusLibs(): Promise<OculusLibrary[]> {
if (process.platform !== "win32") {
log.info("Oculus library auto-detection not supported on non-windows platforms");
return null;
}
if (this.oculusPaths) {
return this.oculusPaths;
if (this.oculusLibraries) {
return this.oculusLibraries;
}
const oculusLibsRegKey = "HKCU\\SOFTWARE\\Oculus VR, LLC\\Oculus\\Libraries";
@@ -44,7 +35,9 @@ export class OculusService {
return null;
}
const libsPath = (
const defaultLibraryId = libsRegData.values.DefaultLibrary.value as string;
const libsPath: OculusLibrary[] = (
await Promise.all(
libsRegData.keys.map(async key => {
const originalPath = (await regedit.promisified.list([`${oculusLibsRegKey}\\${key}`]))[`${oculusLibsRegKey}\\${key}`];
@@ -52,18 +45,18 @@ export class OculusService {
return null;
}
return originalPath.values.OriginalPath.value as string;
return { id: key, path: originalPath.values.OriginalPath.value, isDefault: defaultLibraryId === key } as OculusLibrary
}, [])
)
).filter(path => !!path);
).filter(Boolean);
this.oculusPaths = libsPath;
this.oculusLibraries = libsPath;
return libsPath;
}
public async getGameFolder(gameFolder: string): Promise<string> {
const libsFolders = await this.getOculusLibsPath();
const libsFolders = await this.getOculusLibs();
if (!libsFolders) {
return null;
@@ -71,8 +64,8 @@ export class OculusService {
const rootLibDir = "Software";
for (const lib of libsFolders) {
const gameFullPath = path.join(lib, rootLibDir, gameFolder);
for (const { path: libPath } of libsFolders) {
const gameFullPath = path.join(libPath, rootLibDir, gameFolder);
if (await pathExist(gameFullPath)) {
return gameFullPath;
}
@@ -80,4 +73,23 @@ export class OculusService {
return null;
}
/**
* Return the first game folder found in the list
* @param {string[]} gameFolders
*/
public async tryGetGameFolder(gameFolders: string[]): Promise<string> {
for(const gameFolder of gameFolders){
const fullPath = await this.getGameFolder(gameFolder);
if(fullPath){ return fullPath; }
}
return null;
}
}
export interface OculusLibrary {
id: string;
path: string;
isDefault?: boolean;
}
+2 -1
View File
@@ -6,6 +6,7 @@ import { readFile } from "fs/promises";
import { pathExist } from "../helpers/fs.helpers";
import log from "electron-log";
import { app, shell } from "electron";
import { taskRunning } from "../helpers/os.helpers";
export class SteamService {
@@ -33,7 +34,7 @@ export class SteamService {
}
public async steamRunning(): Promise<boolean>{
const steamProcessRunning = await this.utils.taskRunning("steam");
const steamProcessRunning = await taskRunning("steam");
if(process.platform === "linux") { return steamProcessRunning; }
return steamProcessRunning && !!(await this.getActiveUser());
-12
View File
@@ -2,7 +2,6 @@ import path from "path";
import { app, BrowserWindow } from "electron";
import { IpcResponse } from "shared/models/ipc";
import log from "electron-log";
import psList from "ps-list";
// TODO : REFACTOR
@@ -37,17 +36,6 @@ export class UtilsService {
return path.join(app.getPath("temp"), app.getName());
}
public async taskRunning(task: string): Promise<boolean> {
try {
const processes = await psList();
return processes.some(process => process.name?.includes(task) || process.cmd?.includes(task));
}
catch(error){
log.error(error);
return null;
}
}
public ipcSend<T = unknown>(channel: string, response: IpcResponse<T>): void {
try {
BrowserWindow.getAllWindows().forEach(window => window?.webContents?.send(channel, response));
-2
View File
@@ -43,8 +43,6 @@ export default function ShortcutLaunch() {
useOnUpdate(() => {
if(!launchOptions) { return; }
launchOptions.skipAlreadyRunning = true;
const sub = bsLauncher.doLaunch(launchOptions).subscribe({
next: event => {
setStatus(event.type);
+4
View File
@@ -20,3 +20,7 @@ export function hourToS(hours: number): number {
export function msToS(milliseconds: number): number {
return milliseconds / 1000;
}
export function sToMs(seconds: number): number {
return seconds * 1000;
}
@@ -5,6 +5,5 @@ export interface LaunchOption {
oculus?: boolean,
desktop?: boolean,
debug?: boolean,
additionalArgs?: string[],
skipAlreadyRunning?: boolean
additionalArgs?: string[]
}