mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[fix] properly parse launch option string
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@
|
||||
"start:main": "concurrently -k \"cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --watch --config ./.erb/configs/webpack.config.main.dev.ts\" \"electronmon .\"",
|
||||
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
|
||||
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
|
||||
"test": "jest",
|
||||
"test": "jest ./src/__tests__/**/*.test.ts",
|
||||
"test:unit": "jest ./src/__tests__/unit",
|
||||
"publish": "npm run build && electron-builder -c.win.certificateSha1=206941d969c4fa8a0e04d9427def361e13b02fd0 --config electron-builder.config.js --publish always --win --x64",
|
||||
"publish:linux": "npm run build && electron-builder --config electron-builder.config.js --publish never --linux --x64",
|
||||
|
||||
@@ -65,4 +65,27 @@ describe("Test parseEnvString", () => {
|
||||
expect(command).toEqual("");
|
||||
});
|
||||
|
||||
it("Simple command", () => {
|
||||
const { env, command } = parseEnvString("some-command");
|
||||
expect(env).toEqual({});
|
||||
expect(command).toBe("some-command");
|
||||
});
|
||||
|
||||
it("Env with command", () => {
|
||||
const { env, command } = parseEnvString("SAMPLE=value some-command");
|
||||
expect(env).toEqual(expect.objectContaining({
|
||||
SAMPLE: "value"
|
||||
}));
|
||||
expect(command).toBe("some-command");
|
||||
});
|
||||
|
||||
it("Complex with %command%", () => {
|
||||
const envString = "KEY=value gamescope -h 720 -H 1440 -S integer -- %command% ";
|
||||
const { env, command } = parseEnvString(envString);
|
||||
expect(env).toEqual(expect.objectContaining({
|
||||
KEY: "value"
|
||||
}));
|
||||
expect(command).toBe("gamescope -h 720 -H 1440 -S integer -- %command%");
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { parseLaunchOptions } from "main/helpers/launchOptions.helper";
|
||||
|
||||
const SAMPLE_EXE = "Beat Saber.exe";
|
||||
const WRAPPED_EXE = `"${SAMPLE_EXE}"`
|
||||
|
||||
describe("Test parseLaunchOptions", () => {
|
||||
|
||||
it("Empty", () => {
|
||||
const {
|
||||
env, cmdlet, args
|
||||
} = parseLaunchOptions("", { beatSaberExe: SAMPLE_EXE });
|
||||
expect(env).toEqual({});
|
||||
expect(cmdlet).toBe(WRAPPED_EXE);
|
||||
expect(args).toBe("");
|
||||
});
|
||||
|
||||
it("Envs", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
`HELLO=World! DOUBLE_QUOTE="Two Words" SINGLE_QUOTE='' EMPTY=`,
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual(expect.objectContaining({
|
||||
HELLO: "World!",
|
||||
DOUBLE_QUOTE: "Two Words",
|
||||
SINGLE_QUOTE: "",
|
||||
EMPTY: ""
|
||||
}));
|
||||
expect(cmdlet).toEqual(WRAPPED_EXE);
|
||||
expect(args).toEqual("");
|
||||
});
|
||||
|
||||
it("Env with %command%", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
`TEST=TEST %command%`,
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual(expect.objectContaining({
|
||||
TEST: "TEST",
|
||||
}));
|
||||
expect(cmdlet).toEqual(WRAPPED_EXE);
|
||||
expect(args).toEqual("");
|
||||
});
|
||||
|
||||
it("Envs with arguments", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
`HELLO=World! DOUBLE_QUOTE="Two Words" SINGLE_QUOTE='' EMPTY= %command% --vr-mode`,
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual(expect.objectContaining({
|
||||
HELLO: "World!",
|
||||
DOUBLE_QUOTE: "Two Words",
|
||||
SINGLE_QUOTE: "",
|
||||
EMPTY: ""
|
||||
}));
|
||||
expect(cmdlet).toEqual(WRAPPED_EXE);
|
||||
expect(args).toEqual("--vr-mode");
|
||||
});
|
||||
|
||||
it("Linux Command 1", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
"gamemoderun %command%",
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual({});
|
||||
expect(cmdlet).toBe("gamemoderun");
|
||||
expect(args).toBe(WRAPPED_EXE);
|
||||
});
|
||||
|
||||
it("Linux Command 2", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
"mangohud %command%",
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual({});
|
||||
expect(cmdlet).toBe("mangohud");
|
||||
expect(args).toBe(WRAPPED_EXE);
|
||||
});
|
||||
|
||||
it("Linux Command 3", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
"gamescope -h 720 -H 1440 -S integer -- %command%",
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual({});
|
||||
expect(cmdlet).toBe("gamescope");
|
||||
expect(args).toBe(`-h 720 -H 1440 -S integer -- ${WRAPPED_EXE}`);
|
||||
});
|
||||
|
||||
it("Complex Linux Command", () => {
|
||||
const { env, cmdlet, args } = parseLaunchOptions(
|
||||
"WINEPREFIX=some-path HELLO=World gamescope -h 720 -H 1440 -S integer -- %command% --debug",
|
||||
{ beatSaberExe: SAMPLE_EXE }
|
||||
);
|
||||
expect(env).toEqual(expect.objectContaining({
|
||||
WINEPREFIX: "some-path",
|
||||
HELLO: "World",
|
||||
}));
|
||||
expect(cmdlet).toBe("gamescope");
|
||||
expect(args).toBe(`-h 720 -H 1440 -S integer -- ${WRAPPED_EXE} --debug`);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -21,6 +21,7 @@ enum EnvParserState {
|
||||
QUOTE_VALUE,
|
||||
DQUOTE_VALUE,
|
||||
SPACE,
|
||||
EXIT,
|
||||
ERROR,
|
||||
};
|
||||
|
||||
@@ -51,13 +52,13 @@ export function parseEnvString(envString: string): {
|
||||
|
||||
switch (state) {
|
||||
case EnvParserState.NAME_START:
|
||||
index = pos;
|
||||
if (isAlphaCharacter(c) || c === "_") {
|
||||
state = EnvParserState.NAME;
|
||||
index = pos;
|
||||
} else if (c !== " ") {
|
||||
state = EnvParserState.ERROR;
|
||||
state = EnvParserState.EXIT;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case EnvParserState.NAME:
|
||||
if (c === "=") {
|
||||
@@ -65,58 +66,65 @@ export function parseEnvString(envString: string): {
|
||||
newName = envString.substring(index, pos);
|
||||
index = pos + 1;
|
||||
} else if (!isAlphaCharacter(c) && !isNumber(c) && c !== "_") {
|
||||
state = EnvParserState.ERROR;
|
||||
state = EnvParserState.EXIT;
|
||||
}
|
||||
break;
|
||||
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;
|
||||
++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;
|
||||
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;
|
||||
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;
|
||||
state = EnvParserState.SPACE;
|
||||
envVars[newName] = envString.substring(index, pos);
|
||||
}
|
||||
break;
|
||||
|
||||
case EnvParserState.SPACE:
|
||||
if (c === " ") {
|
||||
state = EnvParserState.NAME_START;
|
||||
} else {
|
||||
state = EnvParserState.ERROR;
|
||||
}
|
||||
break;
|
||||
state = EnvParserState.NAME_START;
|
||||
} else {
|
||||
state = EnvParserState.ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
// TODO: Change to an early exit instead
|
||||
// Early exit
|
||||
if (state === EnvParserState.EXIT) {
|
||||
return {
|
||||
env: envVars,
|
||||
command: envString.substring(index).trim()
|
||||
};
|
||||
}
|
||||
|
||||
if (state === EnvParserState.ERROR) {
|
||||
throw new CustomError(
|
||||
`parseEnvString failed: invalid character at position ${pos}`,
|
||||
@@ -131,14 +139,11 @@ export function parseEnvString(envString: string): {
|
||||
}
|
||||
|
||||
if (state === EnvParserState.NAME_START || state === EnvParserState.SPACE) {
|
||||
return {
|
||||
env: envVars,
|
||||
command: envString.substring(index + 1, envString.length)
|
||||
};
|
||||
return { env: envVars, command: "" };
|
||||
}
|
||||
|
||||
throw new CustomError(
|
||||
"parseEnvString failed: invalid ending state",
|
||||
"generic.env.parse"
|
||||
);
|
||||
return {
|
||||
env: envVars,
|
||||
command: envString.substring(index + 1).trim(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,28 +18,37 @@ export function parseLaunchOptions(launchOption: string, options: {
|
||||
cmdlet: string;
|
||||
args: string;
|
||||
} {
|
||||
const wrappedExe = `"${options.beatSaberExe}"`;
|
||||
if (!launchOption) {
|
||||
return { env: {}, cmdlet: "", args: "" };
|
||||
return { env: {}, cmdlet: wrappedExe, args: "" };
|
||||
}
|
||||
|
||||
// Get the env variables first
|
||||
const {
|
||||
env, command
|
||||
} = parseEnvString(launchOption);
|
||||
const parsed = parseEnvString(launchOption);
|
||||
const { env } = parsed;
|
||||
|
||||
// Replace the %command%
|
||||
if (options.beatSaberExe) {
|
||||
launchOption.replace("%command%", `"${options.beatSaberExe}"`);
|
||||
// If launch options only contains env strings
|
||||
if (!parsed.command) {
|
||||
return { env, cmdlet: wrappedExe, args: "" };
|
||||
}
|
||||
|
||||
const command = parsed.command.replace("%command%", wrappedExe);
|
||||
|
||||
// Offset if it starts with a " or '
|
||||
let offset = 0;
|
||||
if (command.startsWith('"')) {
|
||||
offset = command.indexOf('"', 1);
|
||||
} else if (command.startsWith("'")) {
|
||||
offset = command.indexOf("'", 1);
|
||||
}
|
||||
|
||||
// First word/token is the cmdlet, the rest are the arguments
|
||||
const index = command.indexOf(" ");
|
||||
const index = command.indexOf(" ", offset);
|
||||
if (index === -1) {
|
||||
return { env, cmdlet: command, args: "" }
|
||||
return { env, cmdlet: command.trim(), args: "" };
|
||||
}
|
||||
|
||||
return {
|
||||
env, cmdlet: command.substring(index),
|
||||
env, cmdlet: command.substring(0, index),
|
||||
args: command.substring(index + 1, command.length).trim(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ export abstract class AbstractLauncherService {
|
||||
}
|
||||
|
||||
protected launchBeatSaberProcess(options: LaunchBeatSaberOptions): ChildProcessWithoutNullStreams {
|
||||
|
||||
const spawnOptions: SpawnOptionsWithoutStdio = {
|
||||
detached: true,
|
||||
cwd: options.beatSaberFolderPath,
|
||||
env: options.env,
|
||||
};
|
||||
|
||||
if(options.args && options.args.includes("--verbose")){
|
||||
@@ -57,8 +57,7 @@ export abstract class AbstractLauncherService {
|
||||
}
|
||||
|
||||
spawnOptions.shell = true; // For windows to spawn properly
|
||||
// TODO: bsExePath can be another executable here
|
||||
return bsmSpawn(`"${options.cmdlet}"`, {
|
||||
return bsmSpawn(options.cmdlet, {
|
||||
args: options.args, options: spawnOptions, log: BsmShellLog.Command,
|
||||
linux: { prefix: options.protonPrefix ?? "" },
|
||||
flatpak: {
|
||||
@@ -126,7 +125,7 @@ export abstract class AbstractLauncherService {
|
||||
newEnv: Record<string, string>
|
||||
): Record<string, string> {
|
||||
const env = { ...originalEnv };
|
||||
for (const [ key, value ] of Object.values(newEnv)) {
|
||||
for (const [ key, value ] of Object.entries(newEnv)) {
|
||||
log.info(
|
||||
key in env ? "Overriding" : "Injecting",
|
||||
`${key}="${value}"`,
|
||||
|
||||
@@ -169,7 +169,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
cmdlet, args
|
||||
} = parseLaunchOptions(launchOptions.command, {
|
||||
beatSaberExe: bsExePath
|
||||
})
|
||||
});
|
||||
env = this.mergeEnvVariables(env, parsedEnv);
|
||||
|
||||
const launchArgs = buildBsLaunchArgs(launchOptions);
|
||||
@@ -181,7 +181,9 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
const launchPromise = !launchOptions.admin ? (
|
||||
this.launchBeatSaber({
|
||||
env, cmdlet,
|
||||
args: [ args, ...launchArgs ],
|
||||
args: args
|
||||
? [ args, ...launchArgs ]
|
||||
: launchArgs,
|
||||
beatSaberFolderPath: bsFolderPath,
|
||||
protonPrefix
|
||||
}).exit
|
||||
|
||||
Reference in New Issue
Block a user