From 5d4b79c04101b574598d4934cb3dc8b160f634aa Mon Sep 17 00:00:00 2001 From: silentrald Date: Tue, 21 Jan 2025 01:04:12 +0800 Subject: [PATCH 1/5] [feat] added a way to parse env strings --- src/__tests__/unit/env.test.ts | 61 +++++++++++++++++ src/main/helpers/env.helpers.ts | 117 +++++++++++++++++++++++++++++++- 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/unit/env.test.ts diff --git a/src/__tests__/unit/env.test.ts b/src/__tests__/unit/env.test.ts new file mode 100644 index 00000000..fc6c80d2 --- /dev/null +++ b/src/__tests__/unit/env.test.ts @@ -0,0 +1,61 @@ +import { parseEnvString } from "main/helpers/env.helpers"; + +describe("Test parseEnvString", () => { + + it("Empty", () => { + const envVars = parseEnvString(""); + expect(envVars).toEqual({}); + }); + + it("Single test; no quotes", () => { + const envString = "HELLO=World!"; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + HELLO: "World!", + }); + }); + + it("Single test; single quotes", () => { + const envString = "SINGLE_QOUTE='Single quote with spaces'"; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + SINGLE_QOUTE: "Single quote with spaces", + }); + }); + + it("Single test; double quotes", () => { + const envString = 'DOUBLE_QOUTE="Some random quote."'; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + DOUBLE_QOUTE: "Some random quote.", + }); + }); + + it("Single test; empty value", () => { + const envString = "EMPTY="; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + EMPTY: "", + }); + }); + + it("Multiple test; combined", () => { + const envString = `HELLO=World! DOUBLE_QUOTE="Two Words" SINGLE_QUOTE='' EMPTY=` + const envVars = parseEnvString(envString); + expect(envVars).toEqual(expect.objectContaining({ + HELLO: "World!", + DOUBLE_QUOTE: "Two Words", + SINGLE_QUOTE: "", + EMPTY: "" + })); + }); + + it("Key with numbers and lower case", () => { + const envString = "H3ll0=world"; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + H3ll0: "world", + }); + }); + +}); diff --git a/src/main/helpers/env.helpers.ts b/src/main/helpers/env.helpers.ts index 8eaffea7..677fa2eb 100644 --- a/src/main/helpers/env.helpers.ts +++ b/src/main/helpers/env.helpers.ts @@ -1,3 +1,4 @@ +import { CustomError } from "shared/models/exceptions/custom-error.class"; import { ProviderPlatform } from "shared/models/provider-platform.enum"; export function execOnOs(executions: { [key in ProviderPlatform]?: () => T }, noError = false): T { @@ -10,4 +11,118 @@ export function execOnOs(executions: { [key in ProviderPlatform]?: () => T }, } return undefined; -} \ No newline at end of file +} + +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 { + const envVars: Record = {}; + + 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}`, + "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", + "env.parse" + ); +} From c6ff848bfa6f347a87d3b094d891e0596b6eee2d Mon Sep 17 00:00:00 2001 From: silentrald Date: Tue, 21 Jan 2025 01:36:38 +0800 Subject: [PATCH 2/5] [feat] inject env vars to launch options with steam format %command% --- assets/jsons/translations/en.json | 5 +++++ src/main/helpers/env.helpers.ts | 4 ++-- src/main/services/linux.service.ts | 20 ++++++++++++++++++++ src/renderer/services/bs-launcher.service.ts | 7 ++++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 8b769c7d..557137b8 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -28,6 +28,11 @@ "pin": "Pin", "unpin": "Unpin" }, + "generic": { + "env": { + "parse": "Could not properly parse the env var string." + } + }, "title-bar": { "outdated": "outdated" }, diff --git a/src/main/helpers/env.helpers.ts b/src/main/helpers/env.helpers.ts index 677fa2eb..bb4dd8c6 100644 --- a/src/main/helpers/env.helpers.ts +++ b/src/main/helpers/env.helpers.ts @@ -107,7 +107,7 @@ export function parseEnvString(envString: string): Record { if (state === EnvParserState.ERROR) { throw new CustomError( `parseEnvString failed: invalid character at position ${pos}`, - "env.parse" + "generic.env.parse" ); } } @@ -123,6 +123,6 @@ export function parseEnvString(envString: string): Record { throw new CustomError( "parseEnvString failed: invalid ending state", - "env.parse" + "generic.env.parse" ); } diff --git a/src/main/services/linux.service.ts b/src/main/services/linux.service.ts index 5f12896a..e956148c 100644 --- a/src/main/services/linux.service.ts +++ b/src/main/services/linux.service.ts @@ -10,6 +10,7 @@ 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; @@ -24,6 +25,7 @@ export class LinuxService { private readonly installLocationService: InstallationLocationService; private readonly staticConfig: StaticConfigurationService; + private readonly COMMAND_FORMAT = "%command%"; private nixOS: boolean | undefined; private constructor() { @@ -112,6 +114,24 @@ 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; } diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 86da6d59..3594d568 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -61,7 +61,12 @@ export class BSLauncherService { this.notificationService.notifySuccess({title: `notifications.bs-launch.success.titles.${event.type}`, desc: `notifications.bs-launch.success.msg.${event.type}`}); }, error: (err: CustomError) => { - if(!err?.code || !Object.values(BSLaunchError).includes(err.code as BSLaunchError)){ + if (err?.code?.startsWith("generic.")) { + this.notificationService.notifyError({ + title: "notifications.bs-launch.errors.titles.UNKNOWN_ERROR", + desc: err.code, + }); + } else if(!err?.code || !Object.values(BSLaunchError).includes(err.code as BSLaunchError)){ this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.UNKNOWN_ERROR", desc: "notifications.bs-launch.errors.msg.UNKNOWN_ERROR"}); } else { this.notificationService.notifyError({title: `notifications.bs-launch.errors.titles.${err.code}`, desc: `notifications.bs-launch.errors.msg.${err.code}`, duration: sToMs(9)}) From 889e792b3c20b58271ab1dacc4adfddcd7677b04 Mon Sep 17 00:00:00 2001 From: silentrald Date: Fri, 24 Jan 2025 20:37:46 +0800 Subject: [PATCH 3/5] [feat] added translations for env parser error --- assets/jsons/translations/de.json | 5 +++++ assets/jsons/translations/es.json | 5 +++++ assets/jsons/translations/fr.json | 5 +++++ assets/jsons/translations/it.json | 5 +++++ assets/jsons/translations/ja.json | 5 +++++ assets/jsons/translations/ko.json | 5 +++++ assets/jsons/translations/ru.json | 5 +++++ assets/jsons/translations/zh-tw.json | 5 +++++ assets/jsons/translations/zh.json | 5 +++++ 9 files changed, 45 insertions(+) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index c7e7a533..3a27b870 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -28,6 +28,11 @@ "pin": "Anheften", "unpin": "Lösen" }, + "generic": { + "env": { + "parse": "Konnte den Umgebungsvariablen-String nicht richtig parsen." + } + }, "title-bar": { "outdated": "veraltet" }, diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 61cba828..c5ee39ad 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -28,6 +28,11 @@ "pin": "Fijar", "unpin": "Desfijar" }, + "generic": { + "env": { + "parse": "No se pudo analizar correctamente la cadena de la variable de entorno." + } + }, "title-bar": { "outdated": "obsoleto" }, diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 09ff7267..e4aeb2bf 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -28,6 +28,11 @@ "pin": "Épingler", "unpin": "Désépingler" }, + "generic": { + "env": { + "parse": "Impossible d'analyser correctement la chaîne de variable d'environnement." + } + }, "title-bar": { "outdated": "obsolète" }, diff --git a/assets/jsons/translations/it.json b/assets/jsons/translations/it.json index 6be54279..cec4cfbc 100644 --- a/assets/jsons/translations/it.json +++ b/assets/jsons/translations/it.json @@ -28,6 +28,11 @@ "pin": "Fissa", "unpin": "Rimuovi Fissaggio" }, + "generic": { + "env": { + "parse": "Impossibile analizzare correttamente la stringa della variabile d'ambiente." + } + }, "title-bar": { "outdated": "obsoleta" }, diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 356fb81e..465c468a 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -28,6 +28,11 @@ "pin": "固定", "unpin": "固定解除" }, + "generic": { + "env": { + "parse": "環境変数の文字列を正しく解析できませんでした。" + } + }, "title-bar": { "outdated": "時代遅れ" }, diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index d95476be..beddd618 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -28,6 +28,11 @@ "pin": "고정", "unpin": "고정 해제" }, + "generic": { + "env": { + "parse": "환경 변수 문자열을 제대로 파싱할 수 없습니다." + } + }, "title-bar": { "outdated": "구식" }, diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index d583e90e..43038711 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -28,6 +28,11 @@ "pin": "Закрепить", "unpin": "Открепить" }, + "generic": { + "env": { + "parse": "Не удалось правильно разобрать строку переменной окружения." + } + }, "title-bar": { "outdated": "устаревший" }, diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index bec5592d..87c42b76 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -28,6 +28,11 @@ "pin": "固定", "unpin": "取消固定" }, + "generic": { + "env": { + "parse": "無法正確解析環境變數字串。" + } + }, "title-bar": { "outdated": "過時" }, diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 93434081..9afbde0f 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -28,6 +28,11 @@ "pin": "固定", "unpin": "取消固定" }, + "generic": { + "env": { + "parse": "无法正确解析环境变量字符串。" + } + }, "title-bar": { "outdated": "过时" }, From 1c2321b43820ca122b3c6329414c9f2f450d9fdd Mon Sep 17 00:00:00 2001 From: silentrald Date: Tue, 28 Jan 2025 13:41:16 +0800 Subject: [PATCH 4/5] [feat] support %command% env args to windows and oculus * refactored additionalArgs to command and change its type to string instead of string array --- assets/jsons/translations/de.json | 2 +- assets/jsons/translations/en.json | 2 +- assets/jsons/translations/es.json | 2 +- assets/jsons/translations/fr.json | 2 +- assets/jsons/translations/it.json | 2 +- assets/jsons/translations/ja.json | 2 +- assets/jsons/translations/ko.json | 2 +- assets/jsons/translations/ru.json | 2 +- assets/jsons/translations/zh-tw.json | 2 +- assets/jsons/translations/zh.json | 2 +- .../bs-launcher/abstract-launcher.service.ts | 35 +++++++++++++++++-- .../bs-launcher/bs-launcher.service.ts | 10 ++---- .../bs-launcher/oculus-launcher.service.ts | 16 +++++---- .../bs-launcher/steam-launcher.service.ts | 4 ++- src/main/services/linux.service.ts | 20 ----------- ...create-launch-shortcut-modal.component.tsx | 12 +++---- .../slides/launch/launch-slide.component.tsx | 16 ++++----- src/renderer/services/bs-launcher.service.ts | 12 ++----- .../bs-launch/launch-option.interface.ts | 2 +- 19 files changed, 74 insertions(+), 73 deletions(-) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 3a27b870..b5b70e28 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -56,7 +56,7 @@ "outdated-tippy": "Diese Version ist veraltet, und einige Mods oder Funktionen funktionieren möglicherweise nicht wie erwartet. Es wird empfohlen, die empfohlene Version ({recommendedVersion}) von Beat Saber zu verwenden, um die neuesten Funktionen und Fehlerbehebungen zu genießen.", "advanced-launch": { "button": "Startoptionen", - "placeholder": "Weitere Argumente, bspw: --revert; --nowait" + "placeholder": "Startoptionen, bspw: KEY=VALUE %command% fpfc" }, "skipsteam": "Steam überspringen", "skipsteam-description": "Verhindert, dass Steam automatisch mit Beat Saber geöffnet wird. Aktivieren Sie dies, wenn Sie eine andere VR-Laufzeit wie WiVRn oder Monado verwenden, mit der SteamVR interferieren könnte.", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 557137b8..f1f0daf4 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -56,7 +56,7 @@ "outdated-tippy": "This version is outdated, and some mods or features may no longer work as expected. Prefer using the recommended version ({recommendedVersion}) of Beat Saber to enjoy the latest features and bugfixes.", "advanced-launch": { "button": "Launch options", - "placeholder": "Additional arguments ex: --revert; --nowait" + "placeholder": "Launch options ex: KEY=VALUE %command% fpfc" }, "skipsteam": "Skip Steam", "skipsteam-description": "Stops Steam from opening automatically with Beat Saber, enable if you are using a different VR runtime like WiVRn or Monado that SteamVR may interfere with.", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index c5ee39ad..3b902458 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -56,7 +56,7 @@ "outdated-tippy": "Esta versión está desactualizada, y algunos mods o funciones pueden no funcionar como se espera. Es mejor usar la versión recomendada ({recommendedVersion}) de Beat Saber para disfrutar de las últimas características y correcciones.", "advanced-launch": { "button": "Opciones de lanzamiento", - "placeholder": "Argumentos adicionales ej: --revert; --no-wait" + "placeholder": "Opciones de lanzamiento ej: KEY=VALUE %command% fpfc" }, "skipsteam": "Saltar Steam", "skipsteam-description": "Evita que Steam se abra automáticamente con Beat Saber, habilita esto si estás usando un runtime de VR diferente como WiVRn o Monado que SteamVR podría interferir.", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index e4aeb2bf..2f1fe96b 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -56,7 +56,7 @@ "outdated-tippy": "Cette version est obsolète et certains mods ou fonctionnalités peuvent ne plus fonctionner comme prévu. Préférez utiliser la version recommandée ({recommendedVersion}) de Beat Saber pour profiter des dernières fonctionnalités et correctifs.", "advanced-launch": { "button": "Options de lancement", - "placeholder": "Arguments supplémentaires ex: --revert; --nowait" + "placeholder": "Options de lancement ex: KEY=VALUE %command% fpfc" }, "skipsteam": "Ignorer Steam", "skipsteam-description": "Empêche Steam de s'ouvrir automatiquement avec Beat Saber, activez-le si vous utilisez un autre runtime VR comme WiVRn ou Monado avec lequel SteamVR pourrait interférer.", diff --git a/assets/jsons/translations/it.json b/assets/jsons/translations/it.json index cec4cfbc..bc497b23 100644 --- a/assets/jsons/translations/it.json +++ b/assets/jsons/translations/it.json @@ -56,7 +56,7 @@ "outdated-tippy": "Questa versione è obsoleta, e alcune mod o funzioni non potrebbero più funzionare correttamente. Consigliamo di usare la versione raccomandata ({recommendedVersion}) di Beat Saber per godere delle ultime funzioni e bugfix.", "advanced-launch": { "button": "Opzioni di Lancio", - "placeholder": "Argomenti aggiuntivi es: --revert; --nowait" + "placeholder": "Opzioni di Lancio es: KEY=VALUE %command% fpfc" }, "skipsteam": "Salta Steam", "skipsteam-description": "Ferma Steam da aprirsi automaticamente con Beat Saber, abilitalo se stai usando un VR runtime differente come WiVRn o Monado con cui SteamVR potrebbe interferire.", diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 465c468a..fed708d3 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -56,7 +56,7 @@ "outdated-tippy": "このバージョンは古いため、一部のMODや機能が期待通りに動作しない可能性があります。最新の機能やバグ修正を楽しむには、推奨バージョン ({recommendedVersion}) のBeat Saberを使用することをお勧めします。", "advanced-launch": { "button": "起動オプション", - "placeholder": "追加引数 例:--revert; --nowait" + "placeholder": "起動オプション 例:KEY=VALUE %command% fpfc" }, "skipsteam": "Steamをスキップ", "skipsteam-description": "Beat Saberと一緒にSteamが自動的に開くのを防ぎます。SteamVRが干渉する可能性のあるWiVRnやMonadoなど、別のVRランタイムを使用している場合は有効にしてください。", diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index beddd618..e44135c8 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -56,7 +56,7 @@ "outdated-tippy": "이 버전은 오래되어 일부 모드나 기능이 예상대로 작동하지 않을 수 있습니다. 최신 기능과 버그 수정을 사용하려면 권장 버전({recommendedVersion})의 Beat Saber를 사용하는 것이 좋습니다.", "advanced-launch": { "button": "실행 옵션", - "placeholder": "추가 인수 예: --revert; --nowait" + "placeholder": "실행 옵션 예: KEY=VALUE %command% fpfc" }, "skipsteam": "Steam 실행 건너뛰기", "skipsteam-description": "Beat Saber와 함께 Steam이 자동으로 열리는 것을 방지합니다. SteamVR이 간섭할 수 있는 WiVRn 또는 Monado와 같은 다른 VR 런타임을 사용하는 경우 활성화하세요.", diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 43038711..03a88032 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -56,7 +56,7 @@ "outdated-tippy": "Эта версия устарела, и некоторые моды или функции могут работать не так, как ожидалось. Рекомендуется использовать рекомендуемую версию ({recommendedVersion}) Beat Saber, чтобы воспользоваться последними функциями и исправлениями ошибок.", "advanced-launch": { "button": "Параметры запуска", - "placeholder": "Параметры запуска, например: --revert; --nowait" + "placeholder": "Параметры запуска, например: KEY=VALUE %command% fpfc" }, "skipsteam": "Пропустить Steam", "skipsteam-description": "Предотвращает автоматическое открытие Steam с Beat Saber, включите, если вы используете другую VR-среду, такую как WiVRn или Monado, с которой SteamVR может мешать.", diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 87c42b76..924889da 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -56,7 +56,7 @@ "outdated-tippy": "此版本已過時,某些模組或功能可能無法按預期運作。建議使用推薦版本的 Beat Saber ({recommendedVersion}),以享受最新功能和修復。", "advanced-launch": { "button": "啟動選項", - "placeholder": "額外啟動參數,例如: --revert; --nowait" + "placeholder": "啟動選項,例如: KEY=VALUE %command% fpfc" }, "skipsteam": "跳過 Steam", "skipsteam-description": "防止 Steam 與 Beat Saber 自動打開,如果您使用的是其他 VR 執行時,如 WiVRn 或 Monado,SteamVR 可能會干擾,請啟用此選項。", diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 9afbde0f..5ca0ef8e 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -56,7 +56,7 @@ "outdated-tippy": "此版本已过时,某些模组或功能可能无法按预期运行。建议使用推荐版本的 Beat Saber ({recommendedVersion}),以享受最新功能和修复。", "advanced-launch": { "button": "启动选项", - "placeholder": "额外启动参数,例如: --revert; --nowait" + "placeholder": "启动选项,例如: KEY=VALUE %command% fpfc" }, "skipsteam": "跳过 Steam", "skipsteam-description": "防止 Steam 与 Beat Saber 自动打开,如果您使用的是其他 VR 运行时,如 WiVRn 或 Monado,SteamVR 可能会干扰,请启用此选项。", diff --git a/src/main/services/bs-launcher/abstract-launcher.service.ts b/src/main/services/bs-launcher/abstract-launcher.service.ts index b61d23b9..0399e3f1 100644 --- a/src/main/services/bs-launcher/abstract-launcher.service.ts +++ b/src/main/services/bs-launcher/abstract-launcher.service.ts @@ -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 + ) { + 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 = { diff --git a/src/main/services/bs-launcher/bs-launcher.service.ts b/src/main/services/bs-launcher/bs-launcher.service.ts index b03dd0ef..66e4e9e5 100644 --- a/src/main/services/bs-launcher/bs-launcher.service.ts +++ b/src/main/services/bs-launcher/bs-launcher.service.ts @@ -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; diff --git a/src/main/services/bs-launcher/oculus-launcher.service.ts b/src/main/services/bs-launcher/oculus-launcher.service.ts index d92ccef9..6a02c652 100644 --- a/src/main/services/bs-launcher/oculus-launcher.service.ts +++ b/src/main/services/bs-launcher/oculus-launcher.service.ts @@ -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(); private constructor() { super(); this.oculus = OculusService.getInstance(); - this.util = UtilsService.getInstance(); } public launch(launchOptions: LaunchOption): Observable { @@ -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 = {}; + 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); diff --git a/src/main/services/bs-launcher/steam-launcher.service.ts b/src/main/services/bs-launcher/steam-launcher.service.ts index ab8277d6..8b46ff42 100644 --- a/src/main/services/bs-launcher/steam-launcher.service.ts +++ b/src/main/services/bs-launcher/steam-launcher.service.ts @@ -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 }; diff --git a/src/main/services/linux.service.ts b/src/main/services/linux.service.ts index e956148c..5f12896a 100644 --- a/src/main/services/linux.service.ts +++ b/src/main/services/linux.service.ts @@ -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; } diff --git a/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx index 0ec7966e..014c5142 100644 --- a/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx @@ -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)} /> diff --git a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx index 3f933c89..1eb75078 100644 --- a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx @@ -38,7 +38,7 @@ export function LaunchSlide({ version }: Props) { const versions = useService(BSVersionManagerService); const [advancedLaunch, setAdvancedLaunch] = useState(false); - const [additionalArgsString, setAdditionalArgsString] = useState(configService.get("additionnal-args") || ""); + const [command, setCommand] = useState(configService.get("additionnal-args") || ""); const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$); const [activeLaunchMods, setActiveLaunchMods] = useState(configService.get("launch-mods") ?? []); const [pinnedLaunchMods, setPinnedLaunchMods] = useState(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) {
- + { @@ -207,7 +205,7 @@ export function LaunchSlide({ version }: Props) { />
- +
("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("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0), + command: this.config.get("additionnal-args") || "", } } diff --git a/src/shared/models/bs-launch/launch-option.interface.ts b/src/shared/models/bs-launch/launch-option.interface.ts index 6199fdde..d255e67a 100644 --- a/src/shared/models/bs-launch/launch-option.interface.ts +++ b/src/shared/models/bs-launch/launch-option.interface.ts @@ -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 } From b94fe0a053118188d51c9c4d4dadceb7c93dcba3 Mon Sep 17 00:00:00 2001 From: silentrald Date: Tue, 28 Jan 2025 20:58:24 +0800 Subject: [PATCH 5/5] [feat] follow launch command string even if its hidden --- .../create-launch-shortcut-modal.component.tsx | 8 +------- .../slides/launch/launch-slide.component.tsx | 6 +++--- src/renderer/services/bs-launcher.service.ts | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx index 014c5142..5c44584d 100644 --- a/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx @@ -31,13 +31,7 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean, }, [data]); const completeModal = () => { - - if(advanced) { - launchOption.command = command.trim(); - } else { - launchOption.command = ""; - } - + launchOption.command = command.trim(); resolver({exitCode: ModalExitCode.COMPLETED, data: { launchOption, steamShortcut }}); } diff --git a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx index 1eb75078..472e2086 100644 --- a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx @@ -38,7 +38,7 @@ export function LaunchSlide({ version }: Props) { const versions = useService(BSVersionManagerService); const [advancedLaunch, setAdvancedLaunch] = useState(false); - const [command, setCommand] = useState(configService.get("additionnal-args") || ""); + const [command, setCommand] = useState(configService.get("launch-command") || ""); const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$); const [activeLaunchMods, setActiveLaunchMods] = useState(configService.get("launch-mods") ?? []); const [pinnedLaunchMods, setPinnedLaunchMods] = useState(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []); @@ -46,7 +46,7 @@ export function LaunchSlide({ version }: Props) { const versionRunning = useObservable(() => bsLauncherService.versionRunning$); useEffect(() => { - configService.set("additionnal-args", command); + configService.set("launch-command", command); }, [command]); useEffect(() => { @@ -149,7 +149,7 @@ export function LaunchSlide({ version }: Props) { const launch$ = bsLauncherService.launch({ version, launchMods: activeLaunchMods, - command: advancedLaunch ? command : "", + command, }); return lastValueFrom(launch$).catch(() => {}); diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 357399b5..8c630c95 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -40,7 +40,7 @@ export class BSLauncherService { return { version, launchMods: this.config.get("launch-mods") ?? [], - command: this.config.get("additionnal-args") || "", + command: this.config.get("launch-command") || "", } }