mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge pull request #749 from silentrald/bugfix/linux-env-parser
[feat] inject env vars to launch options with steam format %command%
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { ProviderPlatform } from "shared/models/provider-platform.enum";
|
||||
|
||||
export function execOnOs<T>(executions: { [key in ProviderPlatform]?: () => T }, noError = false): T {
|
||||
@@ -10,4 +11,118 @@ export function execOnOs<T>(executions: { [key in ProviderPlatform]?: () => T },
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
enum EnvParserState {
|
||||
NAME_START,
|
||||
NAME,
|
||||
VALUE_START,
|
||||
VALUE,
|
||||
QUOTE_VALUE,
|
||||
DQUOTE_VALUE,
|
||||
SPACE,
|
||||
ERROR,
|
||||
};
|
||||
|
||||
const isAlphaCharacter = (c: string) =>
|
||||
(c >= "a" && c <= "z") || (c >= "A" && c <= "Z");
|
||||
const isNumber = (c: string) => c >= "0" && c <= "9";
|
||||
|
||||
export function parseEnvString(envString: string): Record<string, string> {
|
||||
const envVars: Record<string, string> = {};
|
||||
|
||||
let state: EnvParserState = EnvParserState.NAME_START;
|
||||
let index = 0;
|
||||
let newName = "";
|
||||
for (let pos = 0; pos < envString.length; ++pos) {
|
||||
const c = envString[pos];
|
||||
|
||||
switch (state) {
|
||||
case EnvParserState.NAME_START:
|
||||
if (isAlphaCharacter(c) || c === "_") {
|
||||
state = EnvParserState.NAME;
|
||||
index = pos;
|
||||
} else if (c !== " ") {
|
||||
state = EnvParserState.ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.NAME:
|
||||
if (c === "=") {
|
||||
state = EnvParserState.VALUE_START;
|
||||
newName = envString.substring(index, pos);
|
||||
index = pos + 1;
|
||||
} else if (!isAlphaCharacter(c) && !isNumber(c) && c !== "_") {
|
||||
state = EnvParserState.ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.VALUE_START:
|
||||
if (c === "'") {
|
||||
++index;
|
||||
state = EnvParserState.QUOTE_VALUE;
|
||||
} else if (c === '"') {
|
||||
++index;
|
||||
state = EnvParserState.DQUOTE_VALUE;
|
||||
} else if (c === " ") {
|
||||
state = EnvParserState.NAME_START;
|
||||
envVars[newName] = "";
|
||||
} else {
|
||||
state = EnvParserState.VALUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.VALUE:
|
||||
if (c === " ") {
|
||||
state = EnvParserState.NAME_START;
|
||||
envVars[newName] = envString.substring(index, pos);
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.QUOTE_VALUE:
|
||||
if (c === "'") {
|
||||
state = EnvParserState.SPACE;
|
||||
envVars[newName] = envString.substring(index, pos);
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.DQUOTE_VALUE:
|
||||
if (c === '"') {
|
||||
state = EnvParserState.SPACE;
|
||||
envVars[newName] = envString.substring(index, pos);
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.SPACE:
|
||||
if (c === " ") {
|
||||
state = EnvParserState.NAME_START;
|
||||
} else {
|
||||
state = EnvParserState.ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
if (state === EnvParserState.ERROR) {
|
||||
throw new CustomError(
|
||||
`parseEnvString failed: invalid character at position ${pos}`,
|
||||
"generic.env.parse"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state === EnvParserState.VALUE_START || state === EnvParserState.VALUE) {
|
||||
envVars[newName] = envString.substring(index);
|
||||
return envVars;
|
||||
}
|
||||
|
||||
if (state === EnvParserState.NAME_START || state === EnvParserState.SPACE) {
|
||||
return envVars;
|
||||
}
|
||||
|
||||
throw new CustomError(
|
||||
"parseEnvString failed: invalid ending state",
|
||||
"generic.env.parse"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { LinuxService } from "../linux.service";
|
||||
import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers";
|
||||
import { IS_FLATPAK } from "main/constants";
|
||||
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { parseEnvString } from "main/helpers/env.helpers";
|
||||
|
||||
export function buildBsLaunchArgs(launchOptions: LaunchOption): string[] {
|
||||
const launchArgs = [];
|
||||
@@ -29,8 +30,8 @@ export function buildBsLaunchArgs(launchOptions: LaunchOption): string[] {
|
||||
launchArgs.push("editor");
|
||||
}
|
||||
|
||||
if (launchOptions.additionalArgs) {
|
||||
launchArgs.push(...launchOptions.additionalArgs);
|
||||
if (launchOptions.command) {
|
||||
launchArgs.push(launchOptions.command);
|
||||
}
|
||||
|
||||
return Array.from(new Set(launchArgs).values());
|
||||
@@ -46,6 +47,8 @@ export abstract class AbstractLauncherService {
|
||||
this.localVersions = BSLocalVersionService.getInstance();
|
||||
}
|
||||
|
||||
private readonly COMMAND_FORMAT = "%command%";
|
||||
|
||||
protected launchBSProcess(bsExePath: string, args: string[], options?: SpawnBsProcessOptions): ChildProcessWithoutNullStreams {
|
||||
|
||||
const spawnOptions: SpawnOptionsWithoutStdio = { detached: true, cwd: path.dirname(bsExePath), ...(options || {}) };
|
||||
@@ -116,6 +119,34 @@ export abstract class AbstractLauncherService {
|
||||
|
||||
return { process, exit };
|
||||
}
|
||||
|
||||
protected injectAdditionalArgsEnvs(
|
||||
launchOptions: LaunchOption,
|
||||
env: Record<string, string>
|
||||
) {
|
||||
if (!launchOptions.command) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { command } = launchOptions;
|
||||
const index = command.indexOf(this.COMMAND_FORMAT);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const envString = command.substring(0, index);
|
||||
log.info("Parsing env string ", `"${envString}"`)
|
||||
for (const [ key, value ] of Object.entries(parseEnvString(envString))) {
|
||||
if (key in env) {
|
||||
log.warn("Ignoring", `${key}=${value}`, "already set env launch command");
|
||||
} else {
|
||||
log.info("Injecting", `${key}="${value}"`, "to the env launch command");
|
||||
}
|
||||
}
|
||||
|
||||
launchOptions.command = command.substring(index + this.COMMAND_FORMAT.length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type SpawnBsProcessOptions = {
|
||||
|
||||
@@ -95,10 +95,6 @@ export class BSLauncherService {
|
||||
|
||||
const params = objectFromEntries(shortcutLink.searchParams.entries()) as ShortcutParams;
|
||||
|
||||
if(typeof params.additionalArgs === "string"){
|
||||
params.additionalArgs = [params.additionalArgs];
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -123,7 +119,7 @@ export class BSLauncherService {
|
||||
oculus: params.versionOculus === "true",
|
||||
ino: +params.versionIno
|
||||
},
|
||||
additionalArgs: params.additionalArgs,
|
||||
command: params.command,
|
||||
launchMods,
|
||||
};
|
||||
|
||||
@@ -141,7 +137,7 @@ export class BSLauncherService {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)){ res.oculusMode = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){ res.desktopMode = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)){ res.debug = "true"; }
|
||||
if(launchOptions.additionalArgs){ res.additionalArgs = launchOptions.additionalArgs; }
|
||||
if(launchOptions.command){ res.command = launchOptions.command; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM)){ res.skipSteam = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.PROTON_LOGS)){ res.protonLogs = "true"; }
|
||||
|
||||
@@ -287,7 +283,7 @@ type ShortcutParams = {
|
||||
oculusMode?: string;
|
||||
desktopMode?: string;
|
||||
debug?: string;
|
||||
additionalArgs?: string[];
|
||||
command?: string;
|
||||
skipSteam?: string;
|
||||
protonLogs?: string;
|
||||
version: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Observable, ReplaySubject } from "rxjs";
|
||||
import { Observable } from "rxjs";
|
||||
import { StoreLauncherInterface } from "./store-launcher.interface";
|
||||
import { BSLaunchError, BSLaunchEvent, BSLaunchEventData, LaunchOption } from "../../../shared/models/bs-launch";
|
||||
import { OculusService } from "../oculus.service";
|
||||
@@ -9,7 +9,6 @@ import { pathExists } from "fs-extra";
|
||||
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";
|
||||
|
||||
export class OculusLauncherService extends AbstractLauncherService implements StoreLauncherInterface {
|
||||
|
||||
@@ -23,14 +22,10 @@ export class OculusLauncherService extends AbstractLauncherService implements St
|
||||
}
|
||||
|
||||
private readonly oculus: OculusService;
|
||||
private readonly util: UtilsService;
|
||||
|
||||
private readonly oculusLib$ = new ReplaySubject<string>();
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.oculus = OculusService.getInstance();
|
||||
this.util = UtilsService.getInstance();
|
||||
}
|
||||
|
||||
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData> {
|
||||
@@ -54,10 +49,17 @@ export class OculusLauncherService extends AbstractLauncherService implements St
|
||||
// Make sure Oculus is running
|
||||
await this.oculus.startOculus().catch(err => log.error("Error while starting Oculus", err));
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
this.injectAdditionalArgsEnvs(launchOptions, env);
|
||||
|
||||
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
|
||||
|
||||
// Launch Beat Saber
|
||||
const process = this.launchBs(exePath, buildBsLaunchArgs(launchOptions));
|
||||
const process = this.launchBs(
|
||||
exePath,
|
||||
buildBsLaunchArgs(launchOptions),
|
||||
{ env }
|
||||
);
|
||||
|
||||
return process.exit.catch(err => {
|
||||
throw CustomError.fromError(err, BSLaunchError.BS_EXIT_ERROR);
|
||||
|
||||
@@ -102,7 +102,6 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
await this.restoreSteamVR().catch(log.error);
|
||||
}
|
||||
|
||||
const launchArgs = buildBsLaunchArgs(launchOptions);
|
||||
const steamPath = await this.steam.getSteamPath();
|
||||
|
||||
const env = {
|
||||
@@ -122,6 +121,9 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
Object.assign(env, linuxSetup.env);
|
||||
}
|
||||
|
||||
this.injectAdditionalArgsEnvs(launchOptions, env);
|
||||
const launchArgs = buildBsLaunchArgs(launchOptions);
|
||||
|
||||
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
|
||||
|
||||
const spawnOpts = { env, cwd: bsFolderPath };
|
||||
|
||||
Reference in New Issue
Block a user