From 0398a957ad96f34f81da9aff482a5a9d3252ec37 Mon Sep 17 00:00:00 2001 From: silentrald Date: Sun, 22 Sep 2024 13:07:02 +0800 Subject: [PATCH] [feat-578] store protonFolder in static config * changed protonPath to protonFolder * log stdout for running bsipa executable --- assets/jsons/translations/de.json | 12 +++++- assets/jsons/translations/en.json | 15 ++++--- assets/jsons/translations/es.json | 12 +++++- assets/jsons/translations/fr.json | 12 +++++- assets/jsons/translations/ja.json | 12 +++++- assets/jsons/translations/ru.json | 12 +++++- assets/jsons/translations/zh-tw.json | 12 +++++- assets/jsons/translations/zh.json | 12 +++++- src/main/ipcs/static-configuration.ipcs.ts | 4 +- .../bs-launcher/steam-launcher.service.ts | 18 ++++++-- .../services/mods/bs-mods-manager.service.ts | 31 ++++++++++++-- .../services/static-configuration.service.ts | 34 +++++++++++++-- .../slides/launch/launch-slide.component.tsx | 1 - .../pages/settings-page.component.tsx | 41 ++++++++++++------- src/renderer/services/bs-launcher.service.ts | 11 ----- .../models/bs-launch/launch-event.model.ts | 1 + .../bs-launch/launch-option.interface.ts | 1 - 17 files changed, 189 insertions(+), 52 deletions(-) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index d280acd0..80f204e4 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -15,7 +15,8 @@ "apply": "Übernehmen", "copy": "Kopieren", "copied": "Kopiert!", - "confirm": "Bestätigen" + "confirm": "Bestätigen", + "unknown": "Ein unbekannter Fehler ist aufgetreten ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "Version hinzufügen", @@ -160,6 +161,15 @@ "description": "Ändern Sie den Ordner, der alle von BSManager heruntergeladenen Inhalte enthalten wird.", "choose-folder": "Ordner wählen" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "Zusätzlicher Inhalt", "description": "Mit zusätzlichen Inhalten kannst du Beat Saber anpassen!", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 6bf60e2f..670f3d5f 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -15,7 +15,8 @@ "apply": "Apply", "copy": "Copy", "copied": "Copied!", - "confirm": "Confirm" + "confirm": "Confirm", + "unknown": "An unknown error occurred ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "Add a version", @@ -160,10 +161,14 @@ "description": "Change the folder that will contain all the content downloaded by BSManager.", "choose-folder": "Choose folder" }, - "proton-path": { - "title": "Proton path", - "description": "Change the path to the Proton binary", - "choose-file": "Choose file" + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } }, "additional-content": { "title": "Additional content", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 0ea57cbb..e631221f 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -15,7 +15,8 @@ "apply": "Aplicar", "copy": "Copiar", "copied": "¡Copiado!", - "confirm": "Confirmar" + "confirm": "Confirmar", + "unknown": "Se ha producido un error desconocido ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "Agregar una versión", @@ -160,6 +161,15 @@ "description": "Cambiar la carpeta que contendrá todo el contenido descargado por BSManager.", "choose-folder": "Elige la carpeta" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "Contenido adicional", "description": "¡Contenido adicional que te permite personalizar Beat Saber!", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 0695a01a..eaa48089 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -15,7 +15,8 @@ "apply": "Appliquer", "copy": "Copier", "copied": "Copié !", - "confirm": "Confirmer" + "confirm": "Confirmer", + "unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "Ajouter une version", @@ -160,6 +161,15 @@ "description": "Changer le dossier qui contiendra tout le contenu téléchargé par BSManager.", "choose-folder": "Choisir un dossier" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "Contenus additionnels", "description": "Les contenus additionnels te permettent de personaliser Beat Saber!", diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index eb259009..bd57898f 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -15,7 +15,8 @@ "apply": "適用", "copy": "コピー", "copied": "コピー済み!", - "confirm": "確認" + "confirm": "確認", + "unknown": "不明なエラーが発生しました (´・ω・`)" }, "nav-bar": { "add-version": "バージョンを追加", @@ -160,6 +161,15 @@ "description": "BSManager によってダウンロードされたすべてのコンテンツを含むフォルダを変更します。", "choose-folder": "フォルダーを選択" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "追加コンテンツ", "description": "Beat Saberをカスタマイズできる追加コンテンツ!", diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 7e4c1a13..33909402 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -15,7 +15,8 @@ "apply": "Применить", "copy": "Скопировать", "copied": "Скопировано!", - "confirm": "Подтвердить" + "confirm": "Подтвердить", + "unknown": "Неизвестная ошибка ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "Добавить версию игры", @@ -160,6 +161,15 @@ "description": "Изменить папку, которая будет содержать весь контент, загруженный BSManager.", "choose-folder": "Изменить папку" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "Дополнительный контент", "description": "Дополнительный контент для кастомизации Beat Saber!", diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 598bd6f1..60e975ad 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -15,7 +15,8 @@ "apply": "應用", "copy": "複製", "copied": "已複製!", - "confirm": "確認" + "confirm": "確認", + "unknown": "發生了一個錯誤 ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "新增版本", @@ -160,6 +161,15 @@ "description": "更改將包含 BSManager 下載的所有內容的文件夾。", "choose-folder": "選擇文件夾" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "附加內容", "description": "允許你自訂 BeatSaber 的附加內容!", diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 3e0f263b..0469ed39 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -15,7 +15,8 @@ "apply": "应用", "copy": "复制", "copied": "已复制!", - "confirm": "确认" + "confirm": "确认", + "Unknown": "发生了一个错误 ¯\\_(ツ)_/¯" }, "nav-bar": { "add-version": "添加版本", @@ -160,6 +161,15 @@ "description": "更改将包含 BSManager 下载的所有内容的文件夹。", "choose-folder": "选择文件夹" }, + "proton-folder": { + "title": "Proton folder", + "description": "Change the folder to the Proton path. (eg. Proton - Experimental)", + "choose-folder": "Choose folder", + "errors": { + "title": "Change Proton folder failed", + "invalid-folder": "Invalid Proton folder path" + } + }, "additional-content": { "title": "附加内容", "description": "允许你自定义 BeatSaber 的附加内容!", diff --git a/src/main/ipcs/static-configuration.ipcs.ts b/src/main/ipcs/static-configuration.ipcs.ts index 586b04e5..d3dc7b93 100644 --- a/src/main/ipcs/static-configuration.ipcs.ts +++ b/src/main/ipcs/static-configuration.ipcs.ts @@ -1,4 +1,4 @@ -import { of } from "rxjs"; +import { from, of } from "rxjs"; import { IpcService } from "../services/ipc.service"; import { StaticConfigurationService } from "../services/static-configuration.service"; @@ -10,5 +10,5 @@ ipc.on("static-configuration.get", (args, reply) => { }); ipc.on("static-configuration.set", (args, reply) => { - reply(of(staticConfig.set(args.key, args.value))); + reply(from(staticConfig.set(args.key, args.value))); }); diff --git a/src/main/services/bs-launcher/steam-launcher.service.ts b/src/main/services/bs-launcher/steam-launcher.service.ts index 0e414087..e2a42a27 100644 --- a/src/main/services/bs-launcher/steam-launcher.service.ts +++ b/src/main/services/bs-launcher/steam-launcher.service.ts @@ -1,7 +1,7 @@ import { Observable } from "rxjs"; import { BSLaunchError, BSLaunchEvent, BSLaunchEventData, BSLaunchWarning, LaunchOption } from "../../../shared/models/bs-launch"; import { StoreLauncherInterface } from "./store-launcher.interface"; -import { pathExists, rename } from "fs-extra"; +import { pathExists, pathExistsSync, rename } from "fs-extra"; import { SteamService } from "../steam.service"; import path from "path"; import { BS_APP_ID, BS_EXECUTABLE, STEAMVR_APP_ID } from "../../constants"; @@ -11,6 +11,7 @@ import { CustomError } from "../../../shared/models/exceptions/custom-error.clas import { UtilsService } from "../utils.service"; import { exec } from "child_process"; import fs from 'fs'; +import { StaticConfigurationService } from "../static-configuration.service"; export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{ @@ -23,11 +24,13 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto return SteamLauncherService.instance; } + private readonly staticConfig: StaticConfigurationService; private readonly steam: SteamService; private readonly util: UtilsService; private constructor(){ super(); + this.staticConfig = StaticConfigurationService.getInstance(); this.steam = SteamService.getInstance(); this.util = UtilsService.getInstance(); } @@ -128,9 +131,16 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto `${exePath}`, ...launchArgs, ]; - exePath = launchOptions.protonPath; - if (!exePath) { - throw CustomError.fromError(new Error("Proton path not set"), BSLaunchError.PROTON_NOT_SET); + + if (!this.staticConfig.has("proton-folder")) { + throw CustomError.fromError(new Error("Proton folder not set"), BSLaunchError.PROTON_NOT_SET); + } + exePath = path.join(this.staticConfig.get("proton-folder"), "proton"); + if (!pathExistsSync(exePath)) { + throw CustomError.fromError( + new Error("Could not locate proton binary"), + BSLaunchError.PROTON_NOT_FOUND + ); } // Setup Proton environment variables diff --git a/src/main/services/mods/bs-mods-manager.service.ts b/src/main/services/mods/bs-mods-manager.service.ts index dd574c67..f91e967e 100644 --- a/src/main/services/mods/bs-mods-manager.service.ts +++ b/src/main/services/mods/bs-mods-manager.service.ts @@ -17,6 +17,7 @@ import { sToMs } from "../../../shared/helpers/time.helpers"; import { ensureDir, pathExistsSync } from "fs-extra"; import { CustomError } from "shared/models/exceptions/custom-error.class"; import { popElement } from "shared/helpers/array.helpers"; +import { StaticConfigurationService } from "../static-configuration.service"; export class BsModsManagerService { private static instance: BsModsManagerService; @@ -24,6 +25,7 @@ export class BsModsManagerService { private readonly beatModsApi: BeatModsApiService; private readonly bsLocalService: BSLocalVersionService; private readonly requestService: RequestService; + private readonly staticConfig: StaticConfigurationService; private manifestMatches: Mod[]; @@ -38,6 +40,7 @@ export class BsModsManagerService { this.beatModsApi = BeatModsApiService.getInstance(); this.bsLocalService = BSLocalVersionService.getInstance(); this.requestService = RequestService.getInstance(); + this.staticConfig = StaticConfigurationService.getInstance(); } private async getModFromHash(hash: string): Promise { @@ -140,9 +143,28 @@ export class BsModsManagerService { return false; } + // Just use the wine binary within the proton folder so no additional wine installation is needed + let winePath: string = ""; + if (process.platform === "linux") { + if (!this.staticConfig.has("proton-folder")) { + log.error("Proton folder not setup"); + return false; + } + + winePath = path.join( + this.staticConfig.get("proton-folder"), + "files", "bin", "wine" + ); + + if (!pathExistsSync(winePath)) { + log.error("Wine binary not found"); + return false; + } + } + return new Promise(resolve => { - const cmd = process.platform === 'linux' - ? `screen -dmS "BSIPA" dotnet "${ipaPath}" ${args.join(" ")}` // Must run through screen, otherwise BSIPA tries to move console cursor and crashes. + const cmd = process.platform === "linux" + ? `"${winePath}" "${ipaPath}" ${args.join(" ")}` : `"${ipaPath}" ${args.join(" ")}`; log.info("START IPA PROCESS", cmd); @@ -153,6 +175,9 @@ export class BsModsManagerService { resolve(false) }, sToMs(30)); + processIPA.stdout.on("data", data => { + log.info("IPA process stdout", data.toString()); + }); processIPA.stderr.on("data", data => { log.error("IPA process stderr", data.toString()); }) @@ -195,7 +220,7 @@ export class BsModsManagerService { } const crypto = require("crypto"); - const files = await zip.files; + const { files } = zip; const checkedEntries = ( await Promise.all( diff --git a/src/main/services/static-configuration.service.ts b/src/main/services/static-configuration.service.ts index 87f6f2f1..112aa891 100644 --- a/src/main/services/static-configuration.service.ts +++ b/src/main/services/static-configuration.service.ts @@ -1,6 +1,9 @@ import ElectronStore from "electron-store"; +import { pathExistsSync } from "fs-extra"; +import path from "path"; import { Observable, Subject } from "rxjs"; import { BSVersion } from "shared/bs-version.interface"; +import { CustomError } from "shared/models/exceptions/custom-error.class"; export class StaticConfigurationService { private static instance: StaticConfigurationService; @@ -34,7 +37,17 @@ export class StaticConfigurationService { cb(this.get(key)); } - public set(key: K, value: StaticConfigKeyValues[K]): void { + public async set(key: K, value: StaticConfigKeyValues[K]): Promise { + // Validate the setters + switch (key) { + case "proton-folder": + this.validateProtonFolder(value as string); + break; + + default: + break; + } + this.store.set(key, value); if (this.watchers[key]) { @@ -42,6 +55,16 @@ export class StaticConfigurationService { } } + // Setters with validation + + private validateProtonFolder(protonFolder: string): void { + const protonPath = path.join(protonFolder, "proton"); + const winePath = path.join(protonFolder, "files", "bin", "wine"); + if (!pathExistsSync(protonPath) || !pathExistsSync(winePath)) { + throw new CustomError("Invalid proton folder path", "invalid-folder"); + } + } + public delete(key: K): void { this.store.delete(key); } @@ -60,12 +83,15 @@ export class StaticConfigurationService { } export interface StaticConfigKeyValues { - "versions": BSVersion[]; "installation-folder": string; "song-details-cache-etag": string; "disable-hadware-acceleration": boolean; "use-symlinks": boolean; -} + + // Linux Specific static configs + "versions": BSVersion[]; + "proton-folder": string; +}; export type StaticConfigKeys = keyof StaticConfigKeyValues; @@ -77,5 +103,5 @@ export type StaticConfigGetIpcRequestResponse = { export type StaticConfigSetIpcRequest = { request: { key: K, value: StaticConfigKeyValues[K] }; response: void; -} +}; 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 46418feb..3f64b672 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 @@ -66,7 +66,6 @@ export function LaunchSlide({ version }: Props) { desktop: desktopMode, debug: debugMode, additionalArgs: advancedLaunch ? additionalArgs : [], - protonPath: bsLauncherService.getProtonPath(), }); return lastValueFrom(launch$).catch(() => {}); diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index 18e88002..e797fefd 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -43,7 +43,6 @@ import { AutoUpdaterService } from "renderer/services/auto-updater.service"; import BeatWaitingImg from "../../../assets/images/apngs/beat-waiting.png"; import BeatConflict from "../../../assets/images/apngs/beat-conflict.png"; import { logRenderError } from "renderer"; -import { BSLauncherService } from "renderer/services/bs-launcher.service"; import { SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component"; import { BasicModal } from "renderer/components/modal/basic-modal.component"; import { StaticConfigurationService } from "renderer/services/static-configuration.service"; @@ -57,7 +56,6 @@ export function SettingsPage() { const ipcService = useService(IpcService); const modalService = useService(ModalService); const bsDownloader = useService(BsDownloaderService); - const bsLauncher = useService(BSLauncherService); const steamDownloader = useService(SteamDownloaderService); const progressBarService = useService(ProgressBarService); const notificationService = useService(NotificationService); @@ -94,7 +92,7 @@ export function SettingsPage() { const downloadStore = useObservable(() => bsDownloader.defaultStore$); const [installationFolder, setInstallationFolder] = useState(null); - const [protonPath, setProtonPath] = useState(bsLauncher.getProtonPath()); + const [protonFolder, setProtonFolder] = useState(""); const [showSupporters, setShowSupporters] = useState(false); const [mapDeepLinksEnabled, setMapDeepLinksEnabled] = useState(false); const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false); @@ -116,6 +114,7 @@ export function SettingsPage() { staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true)); staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks)); + staticConfig.get("proton-folder").then(setProtonFolder); }, []); const allDeepLinkEnabled = mapDeepLinksEnabled && playlistsDeepLinkEnabled && modelsDeepLinkEnabled; @@ -180,18 +179,32 @@ export function SettingsPage() { clearTimeout(timeoutId); }; - const setDefaultProtonPath = () => { + const setDefaultProtonFolder = async () => { if (!progressBarService.require()) { return; } - lastValueFrom(ipcService.sendV2("choose-file")).then(res => { - if (!res.canceled && res.filePaths?.length) { - const protonPath = res.filePaths[0]; - setProtonPath(protonPath); - bsLauncher.setProtonPath(protonPath); + try { + const pathResponse = await lastValueFrom(ipcService.sendV2("choose-folder")); + if ( + pathResponse.canceled + || !pathResponse.filePaths + || pathResponse.filePaths.length === 0 + ) { + return; } - }); + + const folder = pathResponse.filePaths[0]; + await staticConfig.set("proton-folder", folder); + setProtonFolder(folder); + } catch (error: any) { + notificationService.notifyError({ + title: "pages.settings.proton-folder.errors.title", + desc: ["invalid-folder"].includes(error?.code) + ? `pages.settings.proton-folder.errors.${error.code}` + : "misc.unknown", + }); + } }; const setDefaultInstallationFolder = () => { @@ -394,12 +407,12 @@ export function SettingsPage() { - +
- - {protonPath} + + {protonFolder} - +
diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 248870b9..8bc89ec8 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -23,8 +23,6 @@ export class BSLauncherService { public readonly versionRunning$: BehaviorSubject = new BehaviorSubject(null); - private readonly PROTON_PATH_KEY = "protonPath"; - public static getInstance(){ if(!BSLauncherService.instance){ BSLauncherService.instance = new BSLauncherService(); } return BSLauncherService.instance; @@ -38,14 +36,6 @@ export class BSLauncherService { this.modals = ModalService.getInstance(); } - public setProtonPath(protonPath: string|undefined): void { - this.config.set(this.PROTON_PATH_KEY, protonPath); - } - - public getProtonPath(): string|undefined { - return this.config.get(this.PROTON_PATH_KEY); - } - private notRewindBackupOculus(): boolean{ return this.config.get("not-rewind-backup-oculus"); } @@ -93,7 +83,6 @@ export class BSLauncherService { } public doLaunch(launchOptions: LaunchOption): Observable{ - launchOptions.protonPath = this.getProtonPath(); return this.ipcService.sendV2("bs-launch.launch", launchOptions); } diff --git a/src/shared/models/bs-launch/launch-event.model.ts b/src/shared/models/bs-launch/launch-event.model.ts index 29663132..61eb80a4 100644 --- a/src/shared/models/bs-launch/launch-event.model.ts +++ b/src/shared/models/bs-launch/launch-event.model.ts @@ -15,6 +15,7 @@ export enum BSLaunchError{ BS_EXIT_ERROR = "EXIT", OCULUS_LIB_NOT_FOUND = "OCULUS_LIB_NOT_FOUND", PROTON_NOT_SET = "PROTON_NOT_SET", + PROTON_NOT_FOUND = "PROTON_NOT_FOUND", UNKNOWN_ERROR = "UNKNOWN_ERROR", } diff --git a/src/shared/models/bs-launch/launch-option.interface.ts b/src/shared/models/bs-launch/launch-option.interface.ts index 3731143b..21056c19 100644 --- a/src/shared/models/bs-launch/launch-option.interface.ts +++ b/src/shared/models/bs-launch/launch-option.interface.ts @@ -7,5 +7,4 @@ export interface LaunchOption { debug?: boolean, additionalArgs?: string[], admin?: boolean, - protonPath?: string, }