[feat-578] store protonFolder in static config

* changed protonPath to protonFolder
* log stdout for running bsipa executable
This commit is contained in:
silentrald
2024-09-22 13:07:02 +08:00
parent 4a91fa04e7
commit 0398a957ad
17 changed files with 189 additions and 52 deletions
+11 -1
View File
@@ -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!",
+10 -5
View File
@@ -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",
+11 -1
View File
@@ -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!",
+11 -1
View File
@@ -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!",
+11 -1
View File
@@ -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をカスタマイズできる追加コンテンツ!",
+11 -1
View File
@@ -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!",
+11 -1
View File
@@ -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 的附加內容!",
+11 -1
View File
@@ -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 的附加内容!",
+2 -2
View File
@@ -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)));
});
@@ -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
@@ -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<Mod> {
@@ -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<boolean>(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(
@@ -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<K extends StaticConfigKeys>(key: K, value: StaticConfigKeyValues[K]): void {
public async set<K extends StaticConfigKeys>(key: K, value: StaticConfigKeyValues[K]): Promise<void> {
// 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<K extends StaticConfigKeys>(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<K extends StaticConfigKeys> = {
export type StaticConfigSetIpcRequest<K extends StaticConfigKeys> = {
request: { key: K, value: StaticConfigKeyValues[K] };
response: void;
}
};
@@ -66,7 +66,6 @@ export function LaunchSlide({ version }: Props) {
desktop: desktopMode,
debug: debugMode,
additionalArgs: advancedLaunch ? additionalArgs : [],
protonPath: bsLauncherService.getProtonPath(),
});
return lastValueFrom(launch$).catch(() => {});
+27 -14
View File
@@ -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() {
</div>
</SettingContainer>
<SettingContainer os="linux" title="pages.settings.proton-path.title" description="pages.settings.proton-path.description">
<SettingContainer os="linux" title="pages.settings.proton-folder.title" description="pages.settings.proton-folder.description">
<div className="relative flex items-center justify-between w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 rounded-md pl-2 py-1">
<span className="block text-ellipsis overflow-hidden min-w-0 whitespace-nowrap" title={protonPath}>
{protonPath}
<span className="block text-ellipsis overflow-hidden min-w-0 whitespace-nowrap" title={protonFolder}>
{protonFolder}
</span>
<BsmButton onClick={setDefaultProtonPath} className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md" text="pages.settings.proton-path.choose-file" withBar={false} />
<BsmButton onClick={setDefaultProtonFolder} className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md" text="pages.settings.proton-folder.choose-folder" withBar={false} />
</div>
</SettingContainer>
@@ -23,8 +23,6 @@ export class BSLauncherService {
public readonly versionRunning$: BehaviorSubject<BSVersion> = 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<string>(this.PROTON_PATH_KEY);
}
private notRewindBackupOculus(): boolean{
return this.config.get<boolean>("not-rewind-backup-oculus");
}
@@ -93,7 +83,6 @@ export class BSLauncherService {
}
public doLaunch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
launchOptions.protonPath = this.getProtonPath();
return this.ipcService.sendV2("bs-launch.launch", launchOptions);
}
@@ -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",
}
@@ -7,5 +7,4 @@ export interface LaunchOption {
debug?: boolean,
additionalArgs?: string[],
admin?: boolean,
protonPath?: string,
}