[feat] support %command% env args to windows and oculus

* refactored additionalArgs to command and change its type to string instead of string array
This commit is contained in:
silentrald
2025-01-28 13:41:16 +08:00
parent 889e792b3c
commit 1c2321b438
19 changed files with 74 additions and 73 deletions
@@ -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 };
-20
View File
@@ -10,7 +10,6 @@ import { BsmShellLog, bsmExec } from "main/helpers/os.helpers";
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
import { SteamShortcutData } from "shared/models/steam/shortcut.model";
import { buildBsLaunchArgs } from "./bs-launcher/abstract-launcher.service";
import { parseEnvString } from "main/helpers/env.helpers";
export class LinuxService {
private static instance: LinuxService;
@@ -25,7 +24,6 @@ export class LinuxService {
private readonly installLocationService: InstallationLocationService;
private readonly staticConfig: StaticConfigurationService;
private readonly COMMAND_FORMAT = "%command%";
private nixOS: boolean | undefined;
private constructor() {
@@ -114,24 +112,6 @@ export class LinuxService {
envVars.PROTON_LOG_DIR = path.join(bsFolderPath, "Logs");
}
if (launchOptions.additionalArgs) {
const additionalArgs = launchOptions.additionalArgs.join(" ");
const index = additionalArgs.indexOf(this.COMMAND_FORMAT);
if (index > -1) {
const envString = additionalArgs.substring(0, index);
log.info("Parsing env string ", `"${envString}"`)
for (const [ key, value ] of Object.entries(parseEnvString(envString))) {
if (key in envVars) {
log.warn("Ignoring", `${key}=${value}`, "already set env launch command");
} else {
log.info("Injecting", `${key}="${value}"`, "to the env launch command");
}
}
}
launchOptions.additionalArgs = [ additionalArgs.substring(index + this.COMMAND_FORMAT.length) ];
}
return envVars;
}
@@ -22,8 +22,8 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
const color = useThemeColor("second-color");
const [launchOption, setLaunchOptions] = useState(bsLauncher.getLaunchOptions(data));
const [advanced, setAdvanced] = useState(!!launchOption.additionalArgs?.length);
const [additionalArgsString, setAdditionalArgsString] = useState(launchOption.additionalArgs?.join("; ") ?? "");
const [advanced, setAdvanced] = useState(!!launchOption.command?.length);
const [command, setCommand] = useState(launchOption.command || "");
const [steamShortcut, setSteamShortcut] = useState(false);
const isSteamVersion = useMemo(() => {
@@ -33,9 +33,9 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
const completeModal = () => {
if(advanced) {
launchOption.additionalArgs = additionalArgsString.split(";").map(arg => arg.trim()).filter(arg => arg.length);
launchOption.command = command.trim();
} else {
launchOption.additionalArgs = undefined;
launchOption.command = "";
}
resolver({exitCode: ModalExitCode.COMPLETED, data: { launchOption, steamShortcut }});
@@ -95,8 +95,8 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
type="text"
className="w-full rounded-md text-center outline-none bg-light-main-color-3 dark:bg-main-color-3"
placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")}
value={additionalArgsString}
onChange={e => setAdditionalArgsString(e.target.value)}
value={command}
onChange={e => setCommand(e.target.value)}
/>
</div>
</div>
@@ -38,7 +38,7 @@ export function LaunchSlide({ version }: Props) {
const versions = useService(BSVersionManagerService);
const [advancedLaunch, setAdvancedLaunch] = useState(false);
const [additionalArgsString, setAdditionalArgsString] = useState<string>(configService.get<string>("additionnal-args") || "");
const [command, setCommand] = useState<string>(configService.get<string>("additionnal-args") || "");
const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$);
const [activeLaunchMods, setActiveLaunchMods] = useState<LaunchMod[]>(configService.get("launch-mods") ?? []);
const [pinnedLaunchMods, setPinnedLaunchMods] = useState<LaunchMod[]>(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []);
@@ -46,8 +46,8 @@ export function LaunchSlide({ version }: Props) {
const versionRunning = useObservable(() => bsLauncherService.versionRunning$);
useEffect(() => {
configService.set("additionnal-args", additionalArgsString);
}, [additionalArgsString]);
configService.set("additionnal-args", command);
}, [command]);
useEffect(() => {
configService.set("pinned-launch-mods", pinnedLaunchMods);
@@ -146,12 +146,10 @@ export function LaunchSlide({ version }: Props) {
}, [activeLaunchMods, pinnedLaunchMods, version]);
const launch = async () => {
const additionalArgs = additionalArgsString?.split(";").map(arg => arg.trim()).filter(arg => arg.length > 0);
const launch$ = bsLauncherService.launch({
version,
launchMods: activeLaunchMods,
additionalArgs: advancedLaunch ? additionalArgs : [],
command: advancedLaunch ? command : "",
});
return lastValueFrom(launch$).catch(() => {});
@@ -195,9 +193,9 @@ export function LaunchSlide({ version }: Props) {
</div>
<div className="mt-4 flex flex-col items-center justify-center gap-3">
<div className="relative">
<GlowEffect className="!rounded-full" visible={!!(activeLaunchMods?.length || additionalArgsString)}/>
<GlowEffect className="!rounded-full" visible={!!(activeLaunchMods?.length || command)}/>
<BsmButton
className={cn("rounded-full w-fit text-lg py-1 px-7 bg-theme-2 text-gray-800 dark:text-white", (advancedLaunch && additionalArgsString) ? "" : "shadow-md shadow-black")}
className={cn("rounded-full w-fit text-lg py-1 px-7 bg-theme-2 text-gray-800 dark:text-white", (advancedLaunch && command) ? "" : "shadow-md shadow-black")}
text="pages.version-viewer.launch-mods.advanced-launch.button"
withBar={false}
onClick={e => {
@@ -207,7 +205,7 @@ export function LaunchSlide({ version }: Props) {
/>
</div>
</div>
<LaunchOptionsPanel open={advancedLaunch} launchMods={launchModItems} launchArgs={additionalArgsString} className="w-full max-w-3xl mt-3" onLaunchArgsChange={setAdditionalArgsString}/>
<LaunchOptionsPanel open={advancedLaunch} launchMods={launchModItems} launchArgs={command} className="w-full max-w-3xl mt-3" onLaunchArgsChange={setCommand}/>
<div className='grow flex justify-center items-center p-2'>
<BsmButton
onClick={launch}
+2 -10
View File
@@ -36,19 +36,11 @@ export class BSLauncherService {
this.modals = ModalService.getInstance();
}
private notRewindBackupOculus(): boolean{
return this.config.get<boolean>("not-rewind-backup-oculus");
}
private setNotRewindBackupOculus(value: boolean): void{
this.config.set("not-rewind-backup-oculus", value);
}
public getLaunchOptions(version: BSVersion): LaunchOption{
public getLaunchOptions(version: BSVersion): LaunchOption {
return {
version,
launchMods: this.config.get("launch-mods") ?? [],
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0),
command: this.config.get<string>("additionnal-args") || "",
}
}
@@ -14,6 +14,6 @@ export type LaunchMod = typeof LaunchMods[keyof typeof LaunchMods];
export interface LaunchOption {
version: BSVersion,
launchMods?: LaunchMod[],
additionalArgs?: string[],
command?: string,
admin?: boolean
}