Merge branch 'master' into feat/702-respect-system-proxy-windows

This commit is contained in:
Gold John King
2024-12-24 19:52:46 +08:00
21 changed files with 168 additions and 69 deletions
+4 -2
View File
@@ -26,7 +26,8 @@ body:
id: replication
attributes:
label: Steps to Reproduce
description: Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include code to reproduce, if relevant.
description: Provide a link to a live example, or an unambiguous set of steps to reproduce this bug.
Include code to reproduce, if relevant.
placeholder: |
1. Go to '...'
2. Click on '....'
@@ -50,5 +51,6 @@ body:
id: additional-context
attributes:
label: Additional Context
description: Any other context that you may share about the issue. You may add your log files here.
description: Any other context that you may share about the issue such as logs.
To get the logs, follow these steps `Open BSManager -> Go to settings -> Scroll down -> Open Logs`.
+1 -1
View File
@@ -1,4 +1,4 @@
lank_issues_enabled: true
blank_issues_enabled: false
contact_links:
- name: Discord Support
url: https://discord.gg/uSqbHVpKdV
+5 -1
View File
@@ -56,7 +56,9 @@
"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.",
"map-editor": "Karteneditor",
"map-editor-description": "Starte den offiziellen Beat Saber-Karteneditor anstelle des Spiels."
"map-editor-description": "Starte den offiziellen Beat Saber-Karteneditor anstelle des Spiels.",
"proton-logs": "Proton-Logs",
"proton-logs-description": "Aktiviert die Aufzeichnung von Proton-Logs für diese Beat Saber-Installation auf \"{versionPath}\"."
},
"maps": {
"search-bar": {
@@ -327,6 +329,8 @@
"msg": {
"401": "Steam scheint uns Beat Saber nicht herunterladen zu lassen 😢",
"404": "Steam-Server können nicht kontaktiert werden.",
"ExeNotFoundWindows": "\"DepotDownloader.exe\" fehlt. Bitte überprüfen Sie, ob die ausführbare Datei von Ihrer Antivirensoftware quarantäneiert wurde.",
"ExeNotFoundLinux": "Die ausführbare Datei \"DepotDownloader\" fehlt.",
"Password": "Passwort ist ungültig.",
"InvalidCredentials": "Ungültige Anmeldedaten, nicht genehmigte Verbindung oder zu viele Anmeldeversuche.",
"NoManifest": "Es wurde kein Manifest gefunden.",
+5 -1
View File
@@ -56,7 +56,9 @@
"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.",
"map-editor": "Map Editor",
"map-editor-description": "Start the official Beat Saber map editor instead of the game."
"map-editor-description": "Start the official Beat Saber map editor instead of the game.",
"proton-logs": "Proton Logs",
"proton-logs-description": "Enables the recording of proton logs for this Beat Saber install to \"{versionPath}\"."
},
"maps": {
"search-bar": {
@@ -339,6 +341,8 @@
"msg": {
"401": "Steam doesn't seem to want to let us download Beat Saber 😢",
"404": "Unable to contact Steam servers.",
"ExeNotFoundWindows": "\"DepotDownloader.exe\" is missing. Please check if the executable is quarantined by your anti-virus.",
"ExeNotFoundLinux": "\"DepotDownloader\" executable is missing.",
"Password": "Password is invalid.",
"InvalidCredentials": "Invalid login credentials, unapproved connection, or too many login attempts.",
"NoManifest": "No manifest was found",
+10 -6
View File
@@ -52,7 +52,13 @@
"advanced-launch": {
"button": "Opciones de lanzamiento",
"placeholder": "Argumentos adicionales ej: --revert; --no-wait"
}
},
"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.",
"map-editor": "Editor de mapas",
"map-editor-description": "Inicia el editor oficial de mapas de Beat Saber en lugar del juego.",
"proton-logs": "Registros de Proton",
"proton-logs-description": "Habilita el registro de los logs de Proton para esta instalación de Beat Saber en \"{versionPath}\"."
},
"maps": {
"search-bar": {
@@ -62,11 +68,7 @@
"export-maps": "Exportar mapas",
"delete-maps": "Borrar mapas",
"delete-duplicate-maps": "Borrar duplicados"
},
"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.",
"map-editor": "Editor de mapas",
"map-editor-description": "Inicia el editor oficial de mapas de Beat Saber en lugar del juego."
}
},
"tabs": {
"maps": {
@@ -327,6 +329,8 @@
"msg": {
"401": "Parece que Steam no quiere dejarnos descargar Beat Saber 😢",
"404": "No se puede contactar con los servidores de Steam",
"ExeNotFoundWindows": "Falta \"DepotDownloader.exe\". Por favor, verifique si el ejecutable está en cuarentena por su antivirus.",
"ExeNotFoundLinux": "Falta el ejecutable de \"DepotDownloader\".",
"Password": "La contraseña es inválida.",
"InvalidCredentials": "Credenciales de inicio de sesión inválidas, conexión no aprobada o demasiados intentos de inicio de sesión.",
"NoManifest": "No se ha encontrado el manifiesto",
+5 -1
View File
@@ -56,7 +56,9 @@
"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.",
"map-editor": "Éditeur de map",
"map-editor-description": "Lance l'éditeur officiel de map de Beat Saber au lieu du jeu."
"map-editor-description": "Lance l'éditeur officiel de map de Beat Saber au lieu du jeu.",
"proton-logs": "Journaux de Proton",
"proton-logs-description": "Active l'enregistrement des journaux Proton pour cette installation de Beat Saber sur \"{versionPath}\"."
},
"maps": {
"search-bar": {
@@ -327,6 +329,8 @@
"msg": {
"401": "Steam ne semble pas vouloir nous laisser télécharger Beat Saber 😢",
"404": "Impossible de contacter les serveurs de Steam.",
"ExeNotFoundWindows": "\"DepotDownloader.exe\" est manquant. Veuillez vérifier si l'exécutable est mis en quarantaine par votre antivirus.",
"ExeNotFoundLinux": "L'exécutable \"DepotDownloader\" est manquanto.",
"Password": "Le mot de passe est invalide.",
"InvalidCredentials": "Identifiants de connexion invalides, connexion non approuvée, ou trop de tentatives de connexion.",
"NoManifest": "Aucun manifest n'a été trouvé.",
+7 -1
View File
@@ -54,7 +54,11 @@
"placeholder": "追加引数 例:--revert; --nowait"
},
"skipsteam": "Steamをスキップ",
"skipsteam-description": "Beat Saberと一緒にSteamが自動的に開くのを防ぎます。SteamVRが干渉する可能性のあるWiVRnやMonadoなど、別のVRランタイムを使用している場合は有効にしてください。"
"skipsteam-description": "Beat Saberと一緒にSteamが自動的に開くのを防ぎます。SteamVRが干渉する可能性のあるWiVRnやMonadoなど、別のVRランタイムを使用している場合は有効にしてください。",
"map-editor": "マップエディタ",
"map-editor-description": "ゲームの代わりに公式のBeat Saberマップエディタを起動する。",
"proton-logs": "プロトンログ",
"proton-logs-description": "このBeat Saberインストールのプロトンログの記録を「{versionPath}」に有効にします。"
},
"maps": {
"search-bar": {
@@ -325,6 +329,8 @@
"msg": {
"401": "SteamがBeat Saberのダウンロードを許可してくれないようだ。😢",
"404": "Steamサーバーに接続できません。",
"ExeNotFoundWindows": "「DepotDownloader.exe」が見つかりません。実行ファイルがアンチウイルスソフトによって隔離されていないか確認してください。",
"ExeNotFoundLinux": "「DepotDownloader」の実行ファイルが見つかりません。",
"Password": "パスワードが不正です。",
"InvalidCredentials": "無効なログイン認証情報、許可されていない接続、またはログイン試行回数が多すぎます。",
"NoManifest": "マニフェストは見つかりませんでした",
+5 -1
View File
@@ -56,7 +56,9 @@
"skipsteam": "Steam 실행 건너뛰기",
"skipsteam-description": "Beat Saber와 함께 Steam이 자동으로 열리는 것을 방지합니다. SteamVR이 간섭할 수 있는 WiVRn 또는 Monado와 같은 다른 VR 런타임을 사용하는 경우 활성화하세요.",
"map-editor": "맵 편집기",
"map-editor-description": "게임 대신 공식 Beat Saber 맵 편집기를 실행해."
"map-editor-description": "게임 대신 공식 Beat Saber 맵 편집기를 실행해.",
"proton-logs": "프로톤 로그",
"proton-logs-description": "이 Beat Saber 설치에 대한 프로톤 로그 기록을 「{versionPath}」로 활성화합니다."
},
"maps": {
"search-bar": {
@@ -327,6 +329,8 @@
"msg": {
"401": "Steam이 Beat Saber 다운로드를 허용하지 않는 것 같습니다 😢",
"404": "Steam 서버에 연결할 수 없습니다.",
"ExeNotFoundWindows": "「DepotDownloader.exe」가 없습니다. 실행 파일이 바이러스 백신 프로그램에 의해 격리되었는지 확인하십시오.",
"ExeNotFoundLinux": "「DepotDownloader」 실행 파일이 없습니다.",
"Password": "비밀번호가 올바르지 않습니다.",
"InvalidCredentials": "유효하지 않은 로그인 자격 증명, 허가되지 않은 연결 또는 너무 많은 로그인 시도입니다.",
"NoManifest": "매니페스트를 찾을 수 없습니다",
+5 -1
View File
@@ -56,7 +56,9 @@
"skipsteam": "Пропустить Steam",
"skipsteam-description": "Предотвращает автоматическое открытие Steam с Beat Saber, включите, если вы используете другую VR-среду, такую как WiVRn или Monado, с которой SteamVR может мешать.",
"map-editor": "Редактор карт",
"map-editor-description": "Запусти официальный редактор карт Beat Saber вместо игры."
"map-editor-description": "Запусти официальный редактор карт Beat Saber вместо игры.",
"proton-logs": "Протонные логи",
"proton-logs-description": "Включает запись протонных логов для этой установки Beat Saber в \"{versionPath}\"."
},
"maps": {
"search-bar": {
@@ -327,6 +329,8 @@
"msg": {
"401": "Похоже Steam не хочет, чтобы мы скачали Beat Saber 😢",
"404": "Нет связи с серверами Steam.",
"ExeNotFoundWindows": "Отсутствует \"DepotDownloader.exe\". Пожалуйста, проверьте, не находится ли исполняемый файл в карантине вашего антивируса.",
"ExeNotFoundLinux": "Отсутствует исполняемый файл \"DepotDownloader\".",
"Password": "Неверный пароль.",
"InvalidCredentials": "Неверные учетные данные для входа, неразрешенное соединение или слишком много попыток входа.",
"NoManifest": "Файл манифеста не найден",
+5 -1
View File
@@ -56,7 +56,9 @@
"skipsteam": "跳過 Steam",
"skipsteam-description": "防止 Steam 與 Beat Saber 自動打開,如果您使用的是其他 VR 執行時,如 WiVRn 或 Monado,SteamVR 可能會干擾,請啟用此選項。",
"map-editor": "地圖編輯器",
"map-editor-description": "啟動官方的 Beat Saber 地圖編輯器,而不是遊戲。"
"map-editor-description": "啟動官方的 Beat Saber 地圖編輯器,而不是遊戲。",
"proton-logs": "Proton紀錄",
"proton-logs-description": "啟用此 Beat Saber 安裝的質子紀錄,路徑為\"{versionPath}\"。"
},
"maps": {
"search-bar": {
@@ -327,6 +329,8 @@
"msg": {
"401": "Steam 似乎不想讓我們下載 Beat Saber😢",
"404": "無法聯繫 Steam 伺服器。",
"ExeNotFoundWindows": "\"DepotDownloader.exe\" 缺失。請檢查執行檔是否被您的防毒軟體隔離。",
"ExeNotFoundLinux": "\"DepotDownloader\" 的可執行檔缺失。",
"Password": "密碼無效",
"InvalidCredentials": "登錄憑據無效、連接未經批准或登錄嘗試次數過多。",
"NoManifest": "未找到清單",
+5 -1
View File
@@ -56,7 +56,9 @@
"skipsteam": "跳过 Steam",
"skipsteam-description": "防止 Steam 与 Beat Saber 自动打开,如果您使用的是其他 VR 运行时,如 WiVRn 或 Monado,SteamVR 可能会干扰,请启用此选项。",
"map-editor": "地图编辑器",
"map-editor-description": "启动官方的 Beat Saber 地图编辑器,而不是游戏。"
"map-editor-description": "启动官方的 Beat Saber 地图编辑器,而不是游戏。",
"proton-logs": "Proton日志",
"proton-logs-description": "启用此 Beat Saber 安装的质子日志,路径为\"{versionPath}\"。"
},
"maps": {
"search-bar": {
@@ -339,6 +341,8 @@
"msg": {
"401": "Steam 似乎不想让我们下载 Beat Saber😢",
"404": "无法联系 Steam 服务器。",
"ExeNotFoundWindows": "\"DepotDownloader.exe\" 缺失。请检查执行文件是否被您的防病毒软件隔离。",
"ExeNotFoundLinux": "\"DepotDownloader\" 的可执行文件缺失。",
"Password": "密码无效",
"InvalidCredentials": "登录凭据无效、连接未经批准或登录尝试次数过多。",
"NoManifest": "未找到清单",
+30 -12
View File
@@ -1,30 +1,48 @@
import path from "path";
import fs from "fs";
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process";
import { Observable, ReplaySubject, Subscriber, filter, map, share } from "rxjs";
import { DepotDownloaderArgsOptions, DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventType, DepotDownloaderEventTypes, DepotDownloaderInfoEvent, DepotDownloaderWarningEvent } from "../../shared/models/bs-version-download/depot-downloader.model";
import { UtilsService } from 'main/services/utils.service';
import { CustomError } from "shared/models/exceptions/custom-error.class";
export class DepotDownloader {
private static readonly EXE_PATH = path.join(
UtilsService.getInstance().getAssetsScriptsPath(),
process.platform === "win32" ? "DepotDownloader.exe" : "DepotDownloader"
);
private process: ChildProcessWithoutNullStreams;
private processOut$: Observable<string>;
private subscriber: Subscriber<string>;
public constructor(
options: {
command: string, args?: string[], options?: SpawnOptionsWithoutStdio, echoStartData?: unknown
},
args?: string[], options?: SpawnOptionsWithoutStdio, echoStartData?: unknown
},
logger?: Logger
){
this.processOut$ = new Observable<string>(subscriber => {
) {
if (!fs.existsSync(DepotDownloader.EXE_PATH)) {
throw new CustomError(
"DepotDownloader executable not found",
process.platform === "win32"
? DepotDownloaderErrorEvent.ExeNotFoundWindows
: DepotDownloaderErrorEvent.ExeNotFoundLinux
);
}
this.processOut$ = new Observable<string>(subscriber => {
this.subscriber = subscriber;
this.process = spawn(options.command, options.args ?? [], options.options);
this.process = spawn(DepotDownloader.EXE_PATH, options.args ?? [], options.options);
subscriber.next(`[Info]|[Start]|${JSON.stringify(options.echoStartData) ?? ""}`);
this.process.stdout.on("data", data => {
const stringData: string = data.toString();
if(!stringData.includes(DepotDownloaderInfoEvent.Progress) && !stringData.includes(DepotDownloaderInfoEvent.Validated)){
logger?.info("DepotDownloader stdout:", stringData);
}
@@ -32,7 +50,7 @@ export class DepotDownloader {
const lines: string[] = stringData.split("\n");
lines.forEach(line => subscriber.next(line));
});
this.process.on("error", error => subscriber.error(error));
this.process.stderr.on("error", error => subscriber.error(error));
this.process.on("exit", () => subscriber.complete());
@@ -74,7 +92,7 @@ export class DepotDownloader {
data: splitedLine[2],
}
}),
}),
filter(Boolean));
}
@@ -93,7 +111,7 @@ export class DepotDownloader {
const args: string[] = [];
for(const [key, value] of Object.entries(depotDownloaderArgs)){
if(value === true){
args.push(`-${key}`);
}
@@ -102,7 +120,7 @@ export class DepotDownloader {
args.push(`${value}`);
}
}
return args;
}
@@ -112,4 +130,4 @@ interface Logger {
info: (...args: unknown[]) => void,
warn: (...args: unknown[]) => void,
error: (...args: unknown[]) => void,
}
}
@@ -131,6 +131,7 @@ export class BSLauncherService {
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)){ res.debug = "true"; }
if(launchOptions.additionalArgs){ res.additionalArgs = launchOptions.additionalArgs; }
if(launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM)){ res.skipSteam = "true"; }
if(launchOptions.launchMods?.includes(LaunchMods.PROTON_LOGS)){ res.protonLogs = "true"; }
return res;
}
@@ -248,6 +249,7 @@ type ShortcutParams = {
debug?: string;
additionalArgs?: string[];
skipSteam?: string;
protonLogs?: string;
version: string;
versionName?: string;
versionIno?: string;
@@ -1,7 +1,6 @@
import { BS_APP_ID, BS_DEPOT } from "../../constants";
import path from "path";
import { BSVersion } from "shared/bs-version.interface";
import { UtilsService } from "../utils.service";
import log from "electron-log";
import { InstallationLocationService } from "../installation-location.service";
import { BSLocalVersionService } from "../bs-local-version.service";
@@ -17,14 +16,12 @@ import { CustomError } from "shared/models/exceptions/custom-error.class";
export class BsSteamDownloaderService {
private static instance: BsSteamDownloaderService;
private readonly utils: UtilsService;
private readonly installLocationService: InstallationLocationService;
private readonly localVersionService: BSLocalVersionService;
private depotDownloader: DepotDownloader;
private constructor() {
this.utils = UtilsService.getInstance();
this.installLocationService = InstallationLocationService.getInstance();
this.localVersionService = BSLocalVersionService.getInstance();
@@ -40,10 +37,6 @@ export class BsSteamDownloaderService {
return BsSteamDownloaderService.instance;
}
private getDepotDownloaderExePath(): string {
return path.join(this.utils.getAssetsScriptsPath(), process.platform === 'linux' ? "DepotDownloader" : "DepotDownloader.exe");
}
private async buildDepotDownloaderInstance(downloadInfos: DownloadSteamInfo, qr?: boolean): Promise<{depotDownloader: DepotDownloader, depotDownloaderOptions: DepotDownloaderArgsOptions, version: BSVersion}> {
const versionPath = await this.localVersionService.getVersionPath(downloadInfos.bsVersion);
@@ -68,11 +61,9 @@ export class BsSteamDownloaderService {
await ensureDir(this.installLocationService.versionsDirectory());
const exePath = this.getDepotDownloaderExePath();
const args = DepotDownloader.buildArgs(depotDownloaderOptions);
const depotDownloader = new DepotDownloader({
command: exePath,
args,
options: { cwd: this.installLocationService.versionsDirectory() },
echoStartData: downloadVersion
@@ -104,11 +95,21 @@ export class BsSteamDownloaderService {
finalize(() => this.localVersionService.initVersionMetadata(version, { store: BsStore.STEAM }))
).subscribe(sub);
}).catch(err => sub.error({
type: DepotDownloaderEventType.Error,
subType: DepotDownloaderErrorEvent.Unknown,
data: err
} as DepotDownloaderEvent));
}).catch(err => {
if (err instanceof CustomError
&& Object.values(DepotDownloaderErrorEvent).includes(
err.code as DepotDownloaderErrorEvent
)
) {
return sub.error(err);
}
return sub.error({
type: DepotDownloaderEventType.Error,
subType: DepotDownloaderErrorEvent.Unknown,
data: err
} as DepotDownloaderEvent)
});
return () => {
depotDownloaderBuildPromise.then(({ depotDownloader }) => depotDownloader.stop());
+9 -4
View File
@@ -6,6 +6,7 @@ import { StaticConfigurationService } from "./static-configuration.service";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { BSLaunchError, LaunchOption } from "shared/models/bs-launch";
import { bsmExec } from "main/helpers/os.helpers";
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
export class LinuxService {
private static instance: LinuxService;
@@ -78,11 +79,15 @@ export class LinuxService {
"STEAM_COMPAT_CLIENT_INSTALL_PATH": steamPath,
"STEAM_COMPAT_APP_ID": BS_APP_ID,
// Run game in steam environment; fixes #585 for unicode song titles
"SteamEnv": "1",
// Uncomment these to create a proton log file in the Beat Saber install directory.
// "PROTON_LOG": 1,
// "PROTON_LOG_DIR": bsFolderPath,
"SteamEnv": 1,
});
if (launchOptions.launchMods?.includes(LaunchMods.PROTON_LOGS)) {
Object.assign(env, {
"PROTON_LOG": 1,
"PROTON_LOG_DIR": path.join(bsFolderPath, "Logs"),
});
}
}
public verifyProtonPath(protonFolder: string = ""): boolean {
@@ -2,7 +2,7 @@ import Tippy from "@tippyjs/react";
import { GlowEffect } from "renderer/components/shared/glow-effect.component";
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
type Props = {
onClick: (active: boolean) => void;
@@ -13,7 +13,7 @@ type Props = {
};
export function LaunchModToogle({ onClick, active, text, icon: Icon, infoText }: Props) {
const t = useTranslation();
const { text: t } = useTranslationV2();
return (
<button className={`shrink-0 relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!active && "shadow-md shadow-black"}`} onClick={() => onClick(!active)}>
@@ -22,7 +22,7 @@ export function LaunchModToogle({ onClick, active, text, icon: Icon, infoText }:
{Icon && <Icon className="h-7 shrink-0 text-gray-800 dark:text-white" />}
<span className="w-fit min-w-fit text-lg font-bold uppercase tracking-wide italic ">{t(text)}</span>
{infoText && (
<Tippy content={t(infoText)} placement="top" theme="default" delay={[400, 0]}>
<Tippy content={t(infoText)} className="break-words" placement="top" theme="default" delay={[200, 0]}>
<div className="h-[25px] w-[25px] shrink-0 p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
<BsmIcon className="w-full h-full" icon="info" />
</div>
@@ -52,7 +52,7 @@ export function LaunchModItem({ id, icon: Icon, label, description, active, visi
const { text: t } = useTranslationV2();
return (
<Tippy theme="default" placement="top" content={description}>
<Tippy theme="default" className="break-words" placement="top" content={description}>
<button id={id} className={cn("grow rounded-md bg-theme-1 relative flex justify-center items-center h-10 py-1 px-3", visible === false && "hidden")} onClick={e => { e.preventDefault(); e.stopPropagation(); onChange?.(!active) }}>
<BsmCheckbox className="h-4 aspect-square z-[1] relative mr-1.5" checked={active} onChange={onChange}/>
{Icon && <Icon className="h-full w-fit py-0.5 mr-1.5"/>}
@@ -62,7 +62,22 @@ export function LaunchSlide({ version }: Props) {
}
}, [activeLaunchMods]);
const toggleActiveLaunchMod = (checked: boolean, launchMod: LaunchMod) => checked
? setActiveLaunchMods(prev => [...prev, launchMod])
: setActiveLaunchMods(prev => prev.filter(mod => mod !== launchMod));
const togglePinnedLaunchMod = (pinned: boolean, launchMod: LaunchMod) => pinned
? setPinnedLaunchMods(prev => [...prev, launchMod])
: setPinnedLaunchMods(prev => prev.filter(mod => mod !== launchMod));
useEffect(() => {
let protonLogsPath: string[] = [];
if (window.electron.platform === "linux") {
protonLogsPath = version.steam
? [version.path, "Logs"]
: ["BSInstances", version.name, "Logs"];
}
setLaunchModItems(() => [
{
id: LaunchMods.OCULUS,
@@ -72,8 +87,8 @@ export function LaunchSlide({ version }: Props) {
active: activeLaunchMods.includes(LaunchMods.OCULUS),
visible: !(version.metadata?.store === BsStore.OCULUS),
pinned: pinnedLaunchMods.includes(LaunchMods.OCULUS),
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.OCULUS]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.OCULUS)),
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.OCULUS]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.OCULUS)),
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.OCULUS),
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.OCULUS),
},
{
id: LaunchMods.FPFC,
@@ -82,8 +97,8 @@ export function LaunchSlide({ version }: Props) {
description: t("pages.version-viewer.launch-mods.desktop-description"),
active: activeLaunchMods.includes(LaunchMods.FPFC),
pinned: pinnedLaunchMods.includes(LaunchMods.FPFC),
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.FPFC]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.FPFC)),
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.FPFC]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.FPFC)),
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.FPFC),
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.FPFC),
},
{
id: LaunchMods.DEBUG,
@@ -92,8 +107,8 @@ export function LaunchSlide({ version }: Props) {
description: t("pages.version-viewer.launch-mods.debug-description"),
active: activeLaunchMods.includes(LaunchMods.DEBUG),
pinned: pinnedLaunchMods.includes(LaunchMods.DEBUG),
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.DEBUG]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.DEBUG)),
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.DEBUG]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.DEBUG)),
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.DEBUG),
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.DEBUG),
},
{
id: LaunchMods.SKIP_STEAM,
@@ -101,8 +116,8 @@ export function LaunchSlide({ version }: Props) {
description: t("pages.version-viewer.launch-mods.skipsteam-description"),
active: activeLaunchMods.includes(LaunchMods.SKIP_STEAM),
pinned: pinnedLaunchMods.includes(LaunchMods.SKIP_STEAM),
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.SKIP_STEAM]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.SKIP_STEAM)),
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.SKIP_STEAM]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.SKIP_STEAM)),
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.SKIP_STEAM),
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.SKIP_STEAM),
},
{
id: LaunchMods.EDITOR,
@@ -112,14 +127,25 @@ export function LaunchSlide({ version }: Props) {
active: activeLaunchMods.includes(LaunchMods.EDITOR),
pinned: pinnedLaunchMods.includes(LaunchMods.EDITOR),
visible: !safeLt(version.BSVersion, "1.23.0"),
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.EDITOR]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.EDITOR)),
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.EDITOR]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.EDITOR)),
}
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.EDITOR),
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.EDITOR),
},
{
id: LaunchMods.PROTON_LOGS,
label: t("pages.version-viewer.launch-mods.proton-logs"),
description: t("pages.version-viewer.launch-mods.proton-logs-description", {
versionPath: `${window.electron.path.join(...protonLogsPath)}/`
}),
active: activeLaunchMods.includes(LaunchMods.PROTON_LOGS),
pinned: pinnedLaunchMods.includes(LaunchMods.PROTON_LOGS),
visible: window.electron.platform === "linux",
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.PROTON_LOGS),
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.PROTON_LOGS),
},
]);
}, [activeLaunchMods, pinnedLaunchMods, version]);
const launch = () => {
const launch = async () => {
const additionalArgs = additionalArgsString?.split(";").map(arg => arg.trim()).filter(arg => arg.length > 0);
const launch$ = bsLauncherService.launch({
@@ -193,5 +219,3 @@ export function LaunchSlide({ version }: Props) {
</div>
);
}
+8 -1
View File
@@ -5,6 +5,7 @@ import { ConfigurationService } from "./configuration.service";
import { getProperty } from "dot-prop";
import { i18n } from "dateformat";
import { createElement, Fragment } from "react";
import { logRenderError } from "renderer";
export class I18nService {
private static instance: I18nService;
@@ -79,7 +80,13 @@ export class I18nService {
public translate(translationKey: string, args?: Record<string, string>): string {
let translated = this.cache.get(translationKey);
if (!translated) {
translated = getProperty(this.dictionary, translationKey) ?? translationKey;
try {
translated = getProperty(this.dictionary, translationKey) ?? translationKey;
} catch (error) {
// Invalid translationKey like strings containing "[]"
logRenderError(`Could not get property of ${translationKey}`, error);
translated = translationKey;
}
this.cache.set(translationKey, translated);
}
@@ -6,6 +6,7 @@ export const LaunchMods = {
DEBUG: "debug",
SKIP_STEAM: "skip_steam",
EDITOR: "editor",
PROTON_LOGS: "proton_logs",
} as const;
export type LaunchMod = typeof LaunchMods[keyof typeof LaunchMods];
@@ -2,7 +2,6 @@ export enum DepotDownloaderEventType {
Error = "Error",
Warning = "Warning",
Info = "Info",
}
export interface DepotDownloaderEvent<T = unknown> {
@@ -27,6 +26,8 @@ export enum DepotDownloaderInfoEvent {
}
export enum DepotDownloaderErrorEvent {
ExeNotFoundWindows = "ExeNotFoundWindows",
ExeNotFoundLinux = "ExeNotFoundLinux",
Password = "Password",
InvalidCredentials = "InvalidCredentials",
NoManifest = "NoManifest",
@@ -66,4 +67,4 @@ export interface DepotDownloaderArgsOptions {
dir: string,
validate?: boolean,
qr?: boolean,
}
}