From b7e287c86ad3421009b770a490f49a3b8d6d5d1d Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Mon, 26 Jun 2023 23:25:53 +0200 Subject: [PATCH 1/9] [feature] Rework of the BS launching system to prepare for shortcuts --- assets/jsons/translations/de.json | 9 +- assets/jsons/translations/en.json | 9 +- assets/jsons/translations/es.json | 9 +- assets/jsons/translations/fr.json | 9 +- src/main/ipcs/bs-launcher-ipcs.ts | 26 +++-- src/main/services/bs-launcher.service.ts | 101 +++++++++++++++++- src/main/services/steam.service.ts | 84 ++++++++------- .../slides/launch/launch-slide.component.tsx | 4 +- src/renderer/services/bs-launcher.service.ts | 47 ++++++-- src/shared/models/bs-launch/index.ts | 5 +- .../models/bs-launch/launch-event.model.ts | 22 ++++ .../bs-launch/launch-option.interface.ts | 2 +- 12 files changed, 249 insertions(+), 78 deletions(-) create mode 100644 src/shared/models/bs-launch/launch-event.model.ts diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 066256e4..a53e1a86 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -335,12 +335,17 @@ "bs-launch": { "success": { "titles": { - "launching": "Starte...🚀" + "BS_LAUNCHING": "Starte...🚀", + "STEAM_LAUNCHING": "Steam startet!" + }, + "msg": { + "BS_LAUNCHING": "TODO", + "STEAM_LAUNCHING": "Beat Saber wird automatisch nach Steam gestartet." } }, "errors": { "titles": { - "UNABLE_TO_LAUNCH": "Start nicht möglich", + "UNKNOWN_ERROR": "Start nicht möglich", "STEAM_NOT_RUNNING": "Steam läuft nicht", "OCULUS_NOT_RUNNING": "Oculus läuft nicht", "BS_ALREADY_RUNNING": "BeatSaber läuft bereits", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 87ead7b0..8c069767 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -335,12 +335,17 @@ "bs-launch": { "success": { "titles": { - "launching": "Launching...🚀" + "BS_LAUNCHING": "Launching...🚀", + "STEAM_LAUNCHING": "Steam is launching!" + }, + "msg": { + "BS_LAUNCHING": "TODO", + "STEAM_LAUNCHING": "Beat Saber will automatically launch after Steam." } }, "errors": { "titles": { - "UNABLE_TO_LAUNCH": "Unable to launch", + "UNKNOWN_ERROR": "Unable to launch", "STEAM_NOT_RUNNING": "Steam is not running", "OCULUS_NOT_RUNNING": "Oculus is not running", "BS_ALREADY_RUNNING": "BeatSaber already running", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 55a6dadf..90d23ea0 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -334,12 +334,17 @@ "bs-launch": { "success": { "titles": { - "launching": "Lanzamiento...🚀" + "BS_LAUNCHING": "Lanzamiento...🚀", + "STEAM_LAUNCHING": "¡Steam se está iniciando!" + }, + "msg": { + "BS_LAUNCHING": "TODO", + "STEAM_LAUNCHING": "Beat Saber se lanzará automáticamente después de Steam." } }, "errors": { "titles": { - "UNABLE_TO_LAUNCH": "No se puede lanzar", + "UNKNOWN_ERROR": "No se puede lanzar", "STEAM_NOT_RUNNING": "Steam no funciona", "OCULUS_NOT_RUNNING": "Oculus no funciona", "BS_ALREADY_RUNNING": "BeatSaber ya está en marcha", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 8e7d4305..c5644dcf 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -334,12 +334,17 @@ "bs-launch": { "success": { "titles": { - "launching": "Lancement...🚀" + "BS_LAUNCHING": "Lancement...🚀", + "STEAM_LAUNCHING": "Steam se lance !" + }, + "msg": { + "BS_LAUNCHING": "TODO", + "STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam" } }, "errors": { "titles": { - "UNABLE_TO_LAUNCH": "Lancement impossible", + "UNKNOWN_ERROR": "Lancement impossible", "STEAM_NOT_RUNNING": "Steam n'est pas lancé", "OCULUS_NOT_RUNNING": "Oculus n'est pas lancé", "BS_ALREADY_RUNNING": "Beat Saber est déjà lancé", diff --git a/src/main/ipcs/bs-launcher-ipcs.ts b/src/main/ipcs/bs-launcher-ipcs.ts index 53ff080a..5efddc67 100644 --- a/src/main/ipcs/bs-launcher-ipcs.ts +++ b/src/main/ipcs/bs-launcher-ipcs.ts @@ -1,17 +1,25 @@ import { ipcMain } from 'electron'; import { UtilsService } from '../services/utils.service' -import { LauchOption } from "shared/models/bs-launch"; +import { LaunchOption } from "shared/models/bs-launch"; import { BSLauncherService } from "../services/bs-launcher.service" import { IpcRequest } from 'shared/models/ipc'; import { BsmException } from 'shared/models/bsm-exception.model'; +import { IpcService } from '../services/ipc.service'; -ipcMain.on('bs-launch.launch', (event, request: IpcRequest) => { - const launcherService = BSLauncherService.getInstance(); - const utilsService = UtilsService.getInstance(); +// ipcMain.on('bs-launch.launch', (event, request: IpcRequest) => { +// const launcherService = BSLauncherService.getInstance(); +// const utilsService = UtilsService.getInstance(); - launcherService.launch(request.args).then(res => { - utilsService.ipcSend(request.responceChannel, {success: true, data: res}); - }).catch((err: BsmException) => { - utilsService.ipcSend(request.responceChannel, {success: false, error: err}); - }) +// launcherService.launch(request.args).then(res => { +// utilsService.ipcSend(request.responceChannel, {success: true, data: res}); +// }).catch((err: BsmException) => { +// utilsService.ipcSend(request.responceChannel, {success: false, error: err}); +// }) +// }); + +const ipc = IpcService.getInstance(); + +ipc.on('bs-launch.launch', async (req: IpcRequest, reply) => { + const bsLauncher = BSLauncherService.getInstance(); + reply(bsLauncher.launchV2(req.args)); }); diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index 02eae74d..e41b3662 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -1,15 +1,15 @@ import path from "path"; -import { LaunchResult, LauchOption } from "shared/models/bs-launch"; +import { LaunchResult, LaunchOption, BSLaunchEvent, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEventType } from "../../shared/models/bs-launch"; import { UtilsService } from "./utils.service"; import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID } from "../constants"; -import { ChildProcessWithoutNullStreams, spawn } from "child_process"; +import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process"; import { SteamService } from "./steam.service"; import { BSLocalVersionService } from "./bs-local-version.service"; import { OculusService } from "./oculus.service"; import { pathExist } from "../helpers/fs.helpers"; import { rename } from "fs/promises"; import log from "electron-log"; -import { timer } from "rxjs"; +import { Observable, lastValueFrom, timer } from "rxjs"; import { NotificationService } from "./notification.service"; import { NotificationType } from "../../shared/models/notification/notification.model"; @@ -60,7 +60,7 @@ export class BSLauncherService{ } // TODO : Rework with shortcuts implementation - public async launch(launchOptions: LauchOption): Promise{ + public async launch(launchOptions: LaunchOption): Promise{ if(this.isBsRunning() === true){ return "BS_ALREADY_RUNNING" } if(launchOptions.version.oculus && this.oculusService.oculusRunning() === false){ return "OCULUS_NOT_RUNNING" } @@ -121,4 +121,97 @@ export class BSLauncherService{ return "LAUNCHED"; } + private buildBsLaunchArgs(launchOptions: LaunchOption){ + let launchArgs = []; + + if(!launchOptions.version.steam && !launchOptions.version.oculus){ launchArgs.push("--no-yeet"); } + if(launchOptions.oculus){ launchArgs.push("-vrmode oculus"); } + if(launchOptions.desktop){ launchArgs.push("fpfc"); } + if(launchOptions.debug){ launchArgs.push("--verbose"); } + if(launchOptions.additionalArgs){ launchArgs.push(...launchOptions.additionalArgs); } + + return Array.from(new Set(launchArgs).values()); + } + + private launchBSProcess(bsExePath: string, args: string[], debug = false): Promise{ + + if(this.bsProcess?.connected){ + return Promise.reject("Beat Saber process already running"); + } + + const spawnOptions: SpawnOptionsWithoutStdio = { shell: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} }; + + if(debug){ + spawnOptions.detached = true; + spawnOptions.windowsVerbatimArguments = true; + } + + this.bsProcess = spawn(`\"${bsExePath}\"`, args, spawnOptions); + + return new Promise((resolve, reject) => { + this.bsProcess.on('error', e => { log.error(e); reject(e); }); + this.bsProcess.once('exit', code => { + if(code !== 0){ + log.error(`Beat Saber process exited with code ${code}`); + } + resolve(); + }); + }); + + } + + public launchV2(launchOptions: LaunchOption): Observable{ + return new Observable(obs => {(async () => { + + if(this.isBsRunning()){ + return obs.error({type: BSLaunchErrorType.BS_ALREADY_RUNNING} as BSLaunchErrorEvent); + } + + if(launchOptions.version.oculus && this.oculusService.oculusRunning() === false){ + return obs.error({type: BSLaunchErrorType.OCULUS_NOT_RUNNING} as BSLaunchErrorEvent); + } + + const bsFolderPath = await this.localVersionService.getVersionPath(launchOptions.version); + const exePath = path.join(bsFolderPath, BS_EXECUTABLE); + + if(!(await pathExist(exePath))){ + return obs.error({type: BSLaunchErrorType.BS_NOT_FOUND} as BSLaunchErrorEvent); + } + + // Open Steam if not running + if(!launchOptions.version.oculus && !(await this.steamService.steamRunning())){ + obs.next({type: BSLaunchEventType.STEAM_LAUNCHING}); + await this.steamService.openSteam().catch(log.error); + } + + // Backup SteamVR when desktop mode is enabled + if(!launchOptions.version.oculus && launchOptions.desktop){ + await this.backupSteamVR().catch(e => { + log.error("ERR_BACKUP_STEAM_VR", e); + this.restoreSteamVR(); + }); + await lastValueFrom(timer(2_000)); + } else if(!launchOptions.version.oculus){ + await this.restoreSteamVR(); + } + + const launchArgs = this.buildBsLaunchArgs(launchOptions); + + obs.next({type: BSLaunchEventType.BS_LAUNCHING}); + + await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(() => { + obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR} as BSLaunchErrorEvent); + }).finally(() => { + if(!launchOptions.desktop || launchOptions.version.oculus){ return; } + this.restoreSteamVR().catch(e => log.error("ERR_RESTORE_STEAM_VR", e)); + }); + + })().then(() => { + obs.complete() + }).catch(err => { + obs.error({type: BSLaunchErrorType.UNKNOWN_ERROR, data: err} as BSLaunchErrorEvent); + })}); + } + + } \ No newline at end of file diff --git a/src/main/services/steam.service.ts b/src/main/services/steam.service.ts index c7e4ce38..19c3f3ef 100644 --- a/src/main/services/steam.service.ts +++ b/src/main/services/steam.service.ts @@ -9,21 +9,21 @@ import log from "electron-log"; export class SteamService{ - private static instance: SteamService; + private static instance: SteamService; - private readonly utils: UtilsService = UtilsService.getInstance(); + private readonly utils: UtilsService = UtilsService.getInstance(); - private steamPath: string = ''; + private steamPath: string = ''; - private constructor(){ - const vbsDirectory = path.join(this.utils.getAssetsScriptsPath(), "node-regedit", "vbs"); - regedit.setExternalVBSLocation(vbsDirectory); - } + private constructor(){ + const vbsDirectory = path.join(this.utils.getAssetsScriptsPath(), "node-regedit", "vbs"); + regedit.setExternalVBSLocation(vbsDirectory); + } - public static getInstance(){ - if(!SteamService.instance){ SteamService.instance = new SteamService(); } - return SteamService.instance; - } + public static getInstance(){ + if(!SteamService.instance){ SteamService.instance = new SteamService(); } + return SteamService.instance; + } public async getActiveUser(): Promise{ const res = await regedit.promisified.list(["HKCU\\Software\\Valve\\Steam\\ActiveProcess"]); @@ -40,47 +40,49 @@ export class SteamService{ .catch(e => {log.error(e); throw e}) } - public async getSteamPath(): Promise{ + public async getSteamPath(): Promise{ - if(!!this.steamPath){ return this.steamPath; } + if(!!this.steamPath){ return this.steamPath; } - const [win32Res, win64Res] = await Promise.all([ - regedit.promisified.list(['HKLM\\SOFTWARE\\Valve\\Steam']), - regedit.promisified.list(['HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam']) - ]); + const [win32Res, win64Res] = await Promise.all([ + regedit.promisified.list(['HKLM\\SOFTWARE\\Valve\\Steam']), + regedit.promisified.list(['HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam']) + ]); - const [win32, win64] = [win32Res["HKLM\\SOFTWARE\\Valve\\Steam"], win64Res["HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"]]; + const [win32, win64] = [win32Res["HKLM\\SOFTWARE\\Valve\\Steam"], win64Res["HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"]]; - let res = ''; - if(win64.exists && win64?.values?.InstallPath?.value){ res = win64.values.InstallPath.value as string; } - else if(win32.exists && win32?.values?.InstallPath?.value){ res = win32.values.InstallPath.value as string; } - this.steamPath = res; - return this.steamPath; - } + let res = ''; + + if(win64.exists && win64?.values?.InstallPath?.value){ res = win64.values.InstallPath.value as string; } + else if(win32.exists && win32?.values?.InstallPath?.value){ res = win32.values.InstallPath.value as string; } + + this.steamPath = res; + return this.steamPath; + } - public async getGameFolder(gameId: string, gameFolder?: string): Promise{ - try{ - const steamPath = await this.getSteamPath(); + public async getGameFolder(gameId: string, gameFolder?: string): Promise{ + try{ + const steamPath = await this.getSteamPath(); - let libraryFolders: any = path.join(steamPath, 'steamapps', 'libraryfolders.vdf'); + let libraryFolders: any = path.join(steamPath, 'steamapps', 'libraryfolders.vdf'); - if(!(await pathExist(libraryFolders))){ return null; } - libraryFolders = parse(await readFile(libraryFolders, {encoding: 'utf-8'})); + if(!(await pathExist(libraryFolders))){ return null; } + libraryFolders = parse(await readFile(libraryFolders, {encoding: 'utf-8'})); - if(!libraryFolders.libraryfolders){ return null; } - libraryFolders = libraryFolders.libraryfolders + if(!libraryFolders.libraryfolders){ return null; } + libraryFolders = libraryFolders.libraryfolders - for(const libKey in Object.keys(libraryFolders)){ - if(!libraryFolders[libKey] || !libraryFolders[libKey]["apps"]){ continue; } - if(libraryFolders[libKey]["apps"][gameId] != null){ return path.join(libraryFolders[libKey]["path"], "steamapps", "common", gameFolder); }; + for(const libKey in Object.keys(libraryFolders)){ + if(!libraryFolders[libKey] || !libraryFolders[libKey]["apps"]){ continue; } + if(libraryFolders[libKey]["apps"][gameId] != null){ return path.join(libraryFolders[libKey]["path"], "steamapps", "common", gameFolder); }; + } + return null; + } + catch(e){ + log.error(e); + return null; } - return null; } - catch(e){ - log.error(e); - return null; - } - } public openSteam(): Promise{ const process = spawn("start", ["steam://open/games"], {shell: true}); 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 18cbb5ab..60716c8e 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 @@ -25,7 +25,7 @@ export function LaunchSlide({version}: Props) { const [advancedLaunch, setAdvancedLaunch] = useState(false); const [additionalArgsString, setAdditionalArgsString] = useState(configService.get("additionnal-args") || ""); - const launchState = useObservable(bsLauncherService.launchState$); + const versionRunning = useObservable(bsLauncherService.versionRunning$); useEffect(() => { configService.set("additionnal-args", additionalArgsString); @@ -69,7 +69,7 @@ export function LaunchSlide({version}: Props) {
- +
) diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 78523108..9e399a49 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -1,10 +1,11 @@ -import { LauchOption, LaunchResult } from "shared/models/bs-launch"; +import { LaunchOption, LaunchResult } from "shared/models/bs-launch"; import { BSVersion } from 'shared/bs-version.interface'; import { IpcService } from "./ipc.service"; import { NotificationService } from "./notification.service"; import { BsDownloaderService } from "./bs-downloader.service"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, Observable } from "rxjs"; import { NotificationResult } from "shared/models/notification/notification.model"; +import { BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEvent } from "../../shared/models/bs-launch"; export class BSLauncherService{ @@ -14,7 +15,7 @@ export class BSLauncherService{ private readonly notificationService: NotificationService; private readonly bsDownloaderService: BsDownloaderService; - public readonly launchState$: BehaviorSubject = new BehaviorSubject(null); + public readonly versionRunning$: BehaviorSubject = new BehaviorSubject(null); public static getInstance(){ if(!BSLauncherService.instance){ BSLauncherService.instance = new BSLauncherService(); } @@ -25,13 +26,13 @@ export class BSLauncherService{ this.ipcService = IpcService.getInstance(); this.notificationService = NotificationService.getInstance(); this.bsDownloaderService = BsDownloaderService.getInstance(); - this.listenBsExit(); } + // TODO REMOVE private listenBsExit(): void{ this.ipcService.watch("bs-launch.exit").subscribe(res => { - const version = this.launchState$.value; - this.launchState$.next(null); + const version = this.versionRunning$.value; + this.versionRunning$.next(null); if(res.success){ return; } this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.EXIT", desc: "notifications.bs-launch.errors.msg.EXIT", actions: [{id: "0", title: "misc.verify"}]}).then(res => { if(res === "0"){ this.bsDownloaderService.download(version, true); } @@ -41,15 +42,15 @@ export class BSLauncherService{ // TODO : Rework with shortcuts implementation - public launch(version: BSVersion, oculus: boolean, desktop: boolean, debug: boolean, additionalArgs?: string[]): Promise{ - const lauchOption: LauchOption = {debug, oculus, desktop, version, additionalArgs}; - if(this.launchState$.value){ return this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.BS_ALREADY_RUNNING"}); } - this.launchState$.next(version); + public launch_old(version: BSVersion, oculus: boolean, desktop: boolean, debug: boolean, additionalArgs?: string[]): Promise{ + const lauchOption: LaunchOption = {debug, oculus, desktop, version, additionalArgs}; + if(this.versionRunning$.value){ return this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.BS_ALREADY_RUNNING"}); } + this.versionRunning$.next(version); return this.ipcService.send("bs-launch.launch", {args: lauchOption}).then(res => { if(res.data === "LAUNCHED"){ return this.notificationService.notifySuccess({title: "notifications.bs-launch.success.titles.launching"}); } - this.launchState$.next(null); + this.versionRunning$.next(null); if(!res.success){ return this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.UNABLE_TO_LAUNCH", desc: res.error.title}); } @@ -66,6 +67,30 @@ export class BSLauncherService{ }); } + public launch(version: BSVersion, oculus: boolean, desktop: boolean, debug: boolean, additionalArgs?: string[]): Observable { + const launchState$ = this.ipcService.sendV2("bs-launch.launch", {args: {debug, oculus, desktop, version, additionalArgs}}); + + this.versionRunning$.next(version); + + launchState$.subscribe({ + next: event => { + this.notificationService.notifySuccess({title: `notifications.bs-launch.success.titles.${event.type}`, desc: `notifications.bs-launch.success.msg.${event.type}`}); + }, + error: (err: BSLaunchErrorEvent) => { + if(err.type === BSLaunchErrorType.UNKNOWN_ERROR || !Object.values(BSLaunchErrorType).includes(err.type)){ + this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.UNABLE_TO_LAUNCH"}); + } else { + this.notificationService.notifyError({title: `notifications.bs-launch.errors.titles.${err.type}`, desc: `notifications.bs-launch.errors.msg.${err.type}`}) + } + }, + complete: () => { + this.versionRunning$.next(null); + } + }) + + return launchState$; + } + } export enum LaunchMods{ diff --git a/src/shared/models/bs-launch/index.ts b/src/shared/models/bs-launch/index.ts index aec201cf..0e46b4a5 100644 --- a/src/shared/models/bs-launch/index.ts +++ b/src/shared/models/bs-launch/index.ts @@ -1,2 +1,3 @@ -export { LauchOption } from "./launch-option.interface" -export { LaunchResult } from "./launch-result.interface" \ No newline at end of file +export { LaunchOption } from "./launch-option.interface" +export { LaunchResult } from "./launch-result.interface" +export { BSLaunchErrorEvent, BSLaunchEvent, BSLaunchErrorType, BSLaunchEventType } from "./launch-event.model" \ No newline at end of file diff --git a/src/shared/models/bs-launch/launch-event.model.ts b/src/shared/models/bs-launch/launch-event.model.ts new file mode 100644 index 00000000..9f2d7858 --- /dev/null +++ b/src/shared/models/bs-launch/launch-event.model.ts @@ -0,0 +1,22 @@ +export interface BSLaunchEvent{ + type: BSLaunchEventType; + data?: unknown; +} + +export interface BSLaunchErrorEvent{ + type: BSLaunchErrorType; + data?: unknown; +} + +export enum BSLaunchErrorType{ + BS_NOT_FOUND = "EXE_NOT_FINDED", + BS_ALREADY_RUNNING = "BS_ALREADY_RUNNING", + OCULUS_NOT_RUNNING = "OCULUS_NOT_RUNNING", + BS_EXIT_ERROR = "EXIT", + UNKNOWN_ERROR = "UNKNOWN_ERROR", +} + +export enum BSLaunchEventType{ + STEAM_LAUNCHING = "STEAM_LAUNCHING", + BS_LAUNCHING = "BS_LAUNCHING", +} \ No newline at end of file diff --git a/src/shared/models/bs-launch/launch-option.interface.ts b/src/shared/models/bs-launch/launch-option.interface.ts index cad8fecf..e816b9b4 100644 --- a/src/shared/models/bs-launch/launch-option.interface.ts +++ b/src/shared/models/bs-launch/launch-option.interface.ts @@ -1,6 +1,6 @@ import { BSVersion } from "shared/bs-version.interface"; -export interface LauchOption { +export interface LaunchOption { version: BSVersion, oculus: boolean, desktop: boolean, From 1661a61babfa8788b7a6a5d8468fe67cd02b60ac Mon Sep 17 00:00:00 2001 From: MathieuG-P Date: Tue, 27 Jun 2023 11:38:19 +0200 Subject: [PATCH 2/9] [feature] Add translation for "BS_LAUNCHING" event + add TODO --- assets/jsons/translations/de.json | 2 +- assets/jsons/translations/en.json | 2 +- assets/jsons/translations/es.json | 2 +- assets/jsons/translations/fr.json | 2 +- src/main/services/bs-launcher.service.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index a53e1a86..aa5b528a 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -339,7 +339,7 @@ "STEAM_LAUNCHING": "Steam startet!" }, "msg": { - "BS_LAUNCHING": "TODO", + "BS_LAUNCHING": "Vergiss nicht, dich aufzuwärmen 😉", "STEAM_LAUNCHING": "Beat Saber wird automatisch nach Steam gestartet." } }, diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 8c069767..a20e6bde 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -339,7 +339,7 @@ "STEAM_LAUNCHING": "Steam is launching!" }, "msg": { - "BS_LAUNCHING": "TODO", + "BS_LAUNCHING": "Don't forget to warm up 😉", "STEAM_LAUNCHING": "Beat Saber will automatically launch after Steam." } }, diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 90d23ea0..acecb60d 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -338,7 +338,7 @@ "STEAM_LAUNCHING": "¡Steam se está iniciando!" }, "msg": { - "BS_LAUNCHING": "TODO", + "BS_LAUNCHING": "No olvides calentarte 😉", "STEAM_LAUNCHING": "Beat Saber se lanzará automáticamente después de Steam." } }, diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index c5644dcf..57a32aab 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -338,7 +338,7 @@ "STEAM_LAUNCHING": "Steam se lance !" }, "msg": { - "BS_LAUNCHING": "TODO", + "BS_LAUNCHING": "N'oublie pas de t'échauffer 😉", "STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam" } }, diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index e41b3662..8423c0ce 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -197,7 +197,7 @@ export class BSLauncherService{ const launchArgs = this.buildBsLaunchArgs(launchOptions); - obs.next({type: BSLaunchEventType.BS_LAUNCHING}); + obs.next({type: BSLaunchEventType.BS_LAUNCHING}); // Add BS_FORCE_LAUNCH when steam not succeded to launch await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(() => { obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR} as BSLaunchErrorEvent); From 943118538dd87e13f4038879fc4b42c4a9c11c04 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Fri, 30 Jun 2023 21:00:02 +0200 Subject: [PATCH 3/9] [feature] Can now launch a bs version from cmd + fix some bugs + some code cleaning --- assets/jsons/bs-versions.json | 8 ++ assets/jsons/translations/de.json | 1 + assets/jsons/translations/en.json | 1 + assets/jsons/translations/es.json | 1 + assets/jsons/translations/fr.json | 3 +- package-lock.json | 31 +---- package.json | 1 - release/app/package-lock.json | 37 +++++- release/app/package.json | 3 +- src/main/ipcs/bs-launcher-ipcs.ts | 17 +-- src/main/main.ts | 7 +- src/main/services/bs-launcher.service.ts | 106 ++++----------- src/main/services/bs-local-version.service.ts | 123 ++++++++++++------ src/main/services/bsm-protocol.service.ts | 41 ++++++ src/main/services/deep-link.service.ts | 2 +- src/main/services/steam.service.ts | 69 +++++----- src/main/services/utils.service.ts | 3 +- src/renderer/services/bs-launcher.service.ts | 4 +- src/shared/bs-version.interface.ts | 1 + 19 files changed, 253 insertions(+), 206 deletions(-) create mode 100644 src/main/services/bsm-protocol.service.ts diff --git a/assets/jsons/bs-versions.json b/assets/jsons/bs-versions.json index 0a6cc9d8..54e32ece 100644 --- a/assets/jsons/bs-versions.json +++ b/assets/jsons/bs-versions.json @@ -557,5 +557,13 @@ "ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/6d78e092c54f411b36f2c0577d62eb1b25f91aa3.png", "ReleaseDate": "1686668364", "year": "2023" + }, + { + "BSVersion": "1.31.0", + "BSManifest": "676582354127623831", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3674423070127121445", + "ReleaseImg": "https://clan.cloudflare.steamstatic.com/images/32055887/28993aecc56a75c52295fa07620f58cb0e4b09e8.png", + "ReleaseDate": "1688050646", + "year": "2023" } ] \ No newline at end of file diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index aa5b528a..51601285 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -353,6 +353,7 @@ "EXIT": "Abrupter Stop" }, "msg": { + "UNKNOWN_ERROR": "Ein unbekannter Fehler ist aufgetreten.", "STEAM_NOT_RUNNING": "Steam muss laufen, um BeatSaber zu starten.", "OCULUS_NOT_RUNNING": "Oculus muss ausgeführt werden, um BeatSaber zu starten.", "BS_ALREADY_RUNNING": "Schließe BeatSaber, bevor Du es erneut startest.", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index a20e6bde..f44c6fd9 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -353,6 +353,7 @@ "EXIT": "Abrupt stop" }, "msg": { + "UNKNOWN_ERROR": "An unknown error has occurred.", "STEAM_NOT_RUNNING": "Steam must be running to launch BeatSaber.", "OCULUS_NOT_RUNNING": "Oculus must be running to launch BeatSaber.", "BS_ALREADY_RUNNING": "Close BeatSaber before launching it again.", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index acecb60d..fc89e9e6 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -352,6 +352,7 @@ "EXIT": "Parada abrupta" }, "msg": { + "UNKNOWN_ERROR": "Se ha producido un error desconocido.", "STEAM_NOT_RUNNING": "Para iniciar BeatSaber es necesario que Steam esté en funcionamiento.", "OCULUS_NOT_RUNNING": "Oculus debe estar funcionando para lanzar BeatSaber.", "BS_ALREADY_RUNNING": "Cierra BeatSaber antes de volver a lanzarlo.", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 57a32aab..196bd088 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -339,7 +339,7 @@ }, "msg": { "BS_LAUNCHING": "N'oublie pas de t'échauffer 😉", - "STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam" + "STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam." } }, "errors": { @@ -352,6 +352,7 @@ "EXIT": "Arrêt brutal" }, "msg": { + "UNKNOWN_ERROR": "Une erreur inconnue s'est produite.", "STEAM_NOT_RUNNING": "Steam doit être en cours d'exécution pour lancer Beat Saber.", "OCULUS_NOT_RUNNING": "Oculus doit être en cours d'exécution pour lancer Beat Saber.", "BS_ALREADY_RUNNING": "Ferme Beat Saber avant de le lancer à nouveau.", diff --git a/package-lock.json b/package-lock.json index a0c6aadd..08fdd7b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "md5-file": "^5.0.0", "node-fetch": "^2.6.7", "node-stream-zip": "^1.15.0", - "ps-list": "^7.2.0", "react": "^18.2.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", @@ -81,7 +80,7 @@ "css-loader": "^6.7.1", "css-minimizer-webpack-plugin": "^4.1.0", "detect-port": "^1.3.0", - "electron": "^25.2.0", + "electron": "^24.3.1", "electron-builder": "^24.4.0", "electron-devtools-installer": "^3.2.0", "electron-notarize": "^1.2.1", @@ -6561,9 +6560,9 @@ } }, "node_modules/electron": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.2.0.tgz", - "integrity": "sha512-I/rhcW2sV2fyiveVSBr2N7v5ZiCtdGY0UiNCDZgk2fpSC+irQjbeh7JT2b4vWmJ2ogOXBjqesrN9XszTIG6DHg==", + "version": "24.6.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-24.6.1.tgz", + "integrity": "sha512-SXbRtatpvBqtOwlOiEbaI47Repspz5a/qoaNZ+GB3DLhi9Z0r28t14pu+a9nXgX5tXj8lOfN8D9wmwVYdo4TAg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -14861,17 +14860,6 @@ "node": ">= 0.10" } }, - "node_modules/ps-list": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", - "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ps-scrollbar-tailwind": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/ps-scrollbar-tailwind/-/ps-scrollbar-tailwind-0.0.1.tgz", @@ -23797,9 +23785,9 @@ } }, "electron": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.2.0.tgz", - "integrity": "sha512-I/rhcW2sV2fyiveVSBr2N7v5ZiCtdGY0UiNCDZgk2fpSC+irQjbeh7JT2b4vWmJ2ogOXBjqesrN9XszTIG6DHg==", + "version": "24.6.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-24.6.1.tgz", + "integrity": "sha512-SXbRtatpvBqtOwlOiEbaI47Repspz5a/qoaNZ+GB3DLhi9Z0r28t14pu+a9nXgX5tXj8lOfN8D9wmwVYdo4TAg==", "dev": true, "requires": { "@electron/get": "^2.0.0", @@ -30001,11 +29989,6 @@ } } }, - "ps-list": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", - "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==" - }, "ps-scrollbar-tailwind": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/ps-scrollbar-tailwind/-/ps-scrollbar-tailwind-0.0.1.tgz", diff --git a/package.json b/package.json index 32f00d0c..0c413580 100644 --- a/package.json +++ b/package.json @@ -248,7 +248,6 @@ "md5-file": "^5.0.0", "node-fetch": "^2.6.7", "node-stream-zip": "^1.15.0", - "ps-list": "^7.2.0", "react": "^18.2.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", diff --git a/release/app/package-lock.json b/release/app/package-lock.json index b4d6d537..25aeb7b7 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,13 +1,46 @@ { "name": "bs-manager", - "version": "1.1.1", - "lockfileVersion": 1, + "version": "1.2.0", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "bs-manager", + "version": "1.2.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "ctrlc-windows": "^2.1.0", + "ps-list": "^7.2.0" + } + }, + "node_modules/ctrlc-windows": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz", + "integrity": "sha512-OrX5KI+K+2NMN91QIhYZdW7VDO2YsSdTZW494pA7Nvw/wBdU2hz+MGP006bR978zOTrG6Q8EIeJvLJmLqc6MsQ==" + }, + "node_modules/ps-list": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", + "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "ctrlc-windows": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz", "integrity": "sha512-OrX5KI+K+2NMN91QIhYZdW7VDO2YsSdTZW494pA7Nvw/wBdU2hz+MGP006bR978zOTrG6Q8EIeJvLJmLqc6MsQ==" + }, + "ps-list": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", + "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==" } } } diff --git a/release/app/package.json b/release/app/package.json index a4eeceae..c63ec728 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -14,7 +14,8 @@ "postinstall": "npm run electron-rebuild && npm run link-modules" }, "dependencies": { - "ctrlc-windows": "^2.1.0" + "ctrlc-windows": "^2.1.0", + "ps-list": "^7.2.0" }, "license": "MIT" } diff --git a/src/main/ipcs/bs-launcher-ipcs.ts b/src/main/ipcs/bs-launcher-ipcs.ts index 5efddc67..c9225649 100644 --- a/src/main/ipcs/bs-launcher-ipcs.ts +++ b/src/main/ipcs/bs-launcher-ipcs.ts @@ -1,25 +1,12 @@ -import { ipcMain } from 'electron'; -import { UtilsService } from '../services/utils.service' + import { LaunchOption } from "shared/models/bs-launch"; import { BSLauncherService } from "../services/bs-launcher.service" import { IpcRequest } from 'shared/models/ipc'; -import { BsmException } from 'shared/models/bsm-exception.model'; import { IpcService } from '../services/ipc.service'; -// ipcMain.on('bs-launch.launch', (event, request: IpcRequest) => { -// const launcherService = BSLauncherService.getInstance(); -// const utilsService = UtilsService.getInstance(); - -// launcherService.launch(request.args).then(res => { -// utilsService.ipcSend(request.responceChannel, {success: true, data: res}); -// }).catch((err: BsmException) => { -// utilsService.ipcSend(request.responceChannel, {success: false, error: err}); -// }) -// }); - const ipc = IpcService.getInstance(); ipc.on('bs-launch.launch', async (req: IpcRequest, reply) => { const bsLauncher = BSLauncherService.getInstance(); - reply(bsLauncher.launchV2(req.args)); + reply(bsLauncher.launch(req.args)); }); diff --git a/src/main/main.ts b/src/main/main.ts index 1e5c20d6..e601c10d 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -58,7 +58,8 @@ const initServicesMustBeInitialized = () => { LocalMapsManagerService.getInstance(); LocalPlaylistsManagerService.getInstance(); LocalModelsManagerService.getInstance(); - // Model + + BSLauncherService.getInstance(); } const gotTheLock = app.requestSingleInstanceLock(); @@ -79,10 +80,12 @@ else{ }); app.whenReady().then(() => { - + app.setAppUserModelId(APP_NAME); initServicesMustBeInitialized(); + + //DeepLinkService.getInstance().dispatchLinkOpened("bsmanager://launch/?launchOptions=%7B%22debug%22%3Afalse%2C%22oculus%22%3Atrue%2C%22desktop%22%3Atrue%2C%22version%22%3A%7B%22BSVersion%22%3A%221.29.0%22%2C%22BSManifest%22%3A%223341527958186345367%22%2C%22ReleaseURL%22%3A%22https%3A%2F%2Fsteamcommunity.com%2Fgames%2F620980%2Fannouncements%2Fdetail%2F6169409105082976092%22%2C%22ReleaseImg%22%3A%22https%3A%2F%2Fcdn.cloudflare.steamstatic.com%2Fsteamcommunity%2Fpublic%2Fimages%2Fclans%2F%2F32055887%2F255fd49cd96042b97089f754609be291293e783f.png%22%2C%22ReleaseDate%22%3A%221680188760%22%2C%22year%22%3A%222023%22%2C%22name%22%3A%221.29.0+%281%29%22%7D%7D"); const deepLink = process.argv.find(arg => DeepLinkService.getInstance().isDeepLink(arg)); diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index 469b40cf..5211b572 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -1,5 +1,5 @@ import path from "path"; -import { LaunchResult, LaunchOption, BSLaunchEvent, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEventType } from "../../shared/models/bs-launch"; +import { LaunchOption, BSLaunchEvent, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEventType } from "../../shared/models/bs-launch"; import { UtilsService } from "./utils.service"; import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID } from "../constants"; import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process"; @@ -10,8 +10,8 @@ import { pathExist } from "../helpers/fs.helpers"; import { rename } from "fs/promises"; import log from "electron-log"; import { Observable, lastValueFrom, timer } from "rxjs"; -import { NotificationService } from "./notification.service"; -import { NotificationType } from "../../shared/models/notification/notification.model"; +import { BsmProtocolService } from "./bsm-protocol.service"; +import { URL } from "url"; export class BSLauncherService{ @@ -21,7 +21,7 @@ export class BSLauncherService{ private readonly steamService: SteamService; private readonly oculusService: OculusService; private readonly localVersionService: BSLocalVersionService; - private readonly notification: NotificationService; + private readonly bsmProtocolService: BsmProtocolService; private bsProcess: ChildProcessWithoutNullStreams; @@ -35,7 +35,13 @@ export class BSLauncherService{ this.steamService = SteamService.getInstance(); this.oculusService = OculusService.getInstance(); this.localVersionService = BSLocalVersionService.getInstance(); - this.notification = NotificationService.getInstance(); + this.bsmProtocolService = BsmProtocolService.getInstance(); + + this.bsmProtocolService.on("launch", link => { + log.info("Launch from bsm protocol", link.toString()); + if(!link.searchParams.has("launchOptions")){ return; } + this.launch(JSON.parse(link.searchParams.get("launchOptions"))).subscribe(); + }); } private getSteamVRPath(): Promise{ @@ -56,69 +62,7 @@ export class BSLauncherService{ } public async isBsRunning(): Promise { - return this.bsProcess?.connected || await this.utilsService.taskRunning(BS_EXECUTABLE) === true; - } - - // TODO : Rework with shortcuts implementation - public async launch(launchOptions: LaunchOption): Promise{ - if(await this.isBsRunning() === true){ return "BS_ALREADY_RUNNING" } - if(launchOptions.version.oculus && await this.oculusService.oculusRunning() === false){ return "OCULUS_NOT_RUNNING" } - - const steamRunning = await this.steamService.steamRunning().catch(() => true); // True if error (error not not means that steam is not running) - if(!launchOptions.version.oculus && !steamRunning){ - - this.notification.notifyRenderer({ - title: "notifications.steam.steam-launching.title", - desc: "notifications.steam.steam-launching.description", - type: NotificationType.SUCCESS, - }); - - await this.steamService.openSteam().catch(log.error); - } - - const cwd = await this.localVersionService.getVersionPath(launchOptions.version); - const exePath = path.join(cwd, BS_EXECUTABLE); - - if(!(await pathExist(exePath))){ return "EXE_NOT_FINDED"; } - - let launchArgs = []; - - if(!launchOptions.version.steam && !launchOptions.version.oculus){ launchArgs.push("--no-yeet"); } - if(launchOptions.oculus){ launchArgs.push("-vrmode oculus"); } - if(launchOptions.desktop){ launchArgs.push("fpfc"); } - if(launchOptions.debug){ launchArgs.push("--verbose"); } - if(launchOptions.additionalArgs){ launchArgs.push(...launchOptions.additionalArgs); } - - launchArgs = Array.from(new Set(launchArgs).values()); - - let restoreTimout: NodeJS.Timeout; - - if(launchArgs.includes("fpfc")){ - await this.backupSteamVR().catch(() => { - this.restoreSteamVR(); - }).finally(() => { - restoreTimout = setTimeout(() => this.restoreSteamVR(), 35_000); - }); - await timer(2_000).toPromise(); - } - else{ - await this.restoreSteamVR(); - } - - if(launchOptions.debug){ - this.bsProcess = spawn(`\"${exePath}\"`, launchArgs, {shell: true, cwd, env: {...process.env, "SteamAppId": BS_APP_ID}, detached: true, windowsVerbatimArguments: true }); - } - else{ - this.bsProcess = spawn(`\"${exePath}\"`, launchArgs, {shell: true, cwd, env: {...process.env, "SteamAppId": BS_APP_ID} }); - } - - this.bsProcess.on('error', err => log.error(err)); - this.bsProcess.on('exit', code => { - this.restoreSteamVR().finally(() => clearTimeout(restoreTimout)); - this.utilsService.ipcSend("bs-launch.exit", {data: code, success: code === 0}) - }); - - return "LAUNCHED"; + return this.utilsService.taskRunning(BS_EXECUTABLE); } private buildBsLaunchArgs(launchOptions: LaunchOption){ @@ -160,18 +104,23 @@ export class BSLauncherService{ } - public launchV2(launchOptions: LaunchOption): Observable{ + public launch(launchOptions: LaunchOption): Observable{ + + // const t = new URL("bsmanager://launch"); + // t.searchParams.set("launchOptions", JSON.stringify(launchOptions)); + // console.log(t.toString()); + return new Observable(obs => {(async () => { - if(this.isBsRunning()){ + if(await this.isBsRunning()){ return obs.error({type: BSLaunchErrorType.BS_ALREADY_RUNNING} as BSLaunchErrorEvent); } - if(launchOptions.version.oculus && (await this.oculusService.oculusRunning()) === false){ + if(launchOptions.version.oculus && (await this.oculusService.oculusRunning())){ return obs.error({type: BSLaunchErrorType.OCULUS_NOT_RUNNING} as BSLaunchErrorEvent); } - const bsFolderPath = await this.localVersionService.getVersionPath(launchOptions.version); + const bsFolderPath = await this.localVersionService.getInstalledVersionPath(launchOptions.version); const exePath = path.join(bsFolderPath, BS_EXECUTABLE); if(!(await pathExist(exePath))){ @@ -187,8 +136,7 @@ export class BSLauncherService{ // Backup SteamVR when desktop mode is enabled if(!launchOptions.version.oculus && launchOptions.desktop){ await this.backupSteamVR().catch(e => { - log.error("ERR_BACKUP_STEAM_VR", e); - this.restoreSteamVR(); + return this.restoreSteamVR(); }); await lastValueFrom(timer(2_000)); } else if(!launchOptions.version.oculus){ @@ -197,17 +145,17 @@ export class BSLauncherService{ const launchArgs = this.buildBsLaunchArgs(launchOptions); - obs.next({type: BSLaunchEventType.BS_LAUNCHING}); // Add BS_FORCE_LAUNCH when steam not succeded to launch + obs.next({type: BSLaunchEventType.BS_LAUNCHING}); - await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(() => { - obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR} as BSLaunchErrorEvent); + await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(err => { + obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR, data: err} as BSLaunchErrorEvent); }).finally(() => { if(!launchOptions.desktop || launchOptions.version.oculus){ return; } - this.restoreSteamVR().catch(e => log.error("ERR_RESTORE_STEAM_VR", e)); + this.restoreSteamVR(); }); })().then(() => { - obs.complete() + obs.complete(); }).catch(err => { obs.error({type: BSLaunchErrorType.UNKNOWN_ERROR, data: err} as BSLaunchErrorEvent); })}); diff --git a/src/main/services/bs-local-version.service.ts b/src/main/services/bs-local-version.service.ts index 1f8c05a1..612217d8 100644 --- a/src/main/services/bs-local-version.service.ts +++ b/src/main/services/bs-local-version.service.ts @@ -6,7 +6,7 @@ import { BS_APP_ID, OCULUS_BS_DIR } from "../constants"; import path from "path"; import { createReadStream } from "fs"; import { ConfigurationService } from "./configuration.service"; -import { rename } from "fs/promises"; +import { lstat, rename } from "fs/promises"; import { BsmException } from "shared/models/bsm-exception.model"; import log from "electron-log"; import { OculusService } from "./oculus.service"; @@ -44,36 +44,41 @@ export class BSLocalVersionService{ - public async getVersionOfBSFolder(bsPath: string): Promise{ - const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers'); - if(!(await pathExist(versionFilePath))){ return null; } - const versionsAvailable = await this.remoteVersionService.getAvailableVersions(); - return new Promise(resolve => { - const stream = createReadStream(versionFilePath); - let findVersion: PartialBSVersion = null; - stream.on('data', (line) => { - line = line.toString(); - for(const version of versionsAvailable){ - if(line.includes(version.BSVersion)){ - findVersion = {BSVersion: version.BSVersion}; + public async getVersionOfBSFolder(bsPath: string): Promise{ + const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers'); + + if(!(await pathExist(versionFilePath))){ return null; } + + const versionsAvailable = await this.remoteVersionService.getAvailableVersions(); + + return new Promise(async resolve => { + const stream = createReadStream(versionFilePath); + + stream.on('data', async line => { + + line = line.toString(); + + for(const version of versionsAvailable){ + if(!line.includes(version.BSVersion)){ continue; } + + const findVersion: PartialBSVersion = {BSVersion: version.BSVersion}; + if(findVersion.BSVersion !== path.basename(bsPath)){ findVersion.name = path.basename(bsPath); } + + const folderStats = await lstat(bsPath); + if(folderStats.ino){ + findVersion.ino = folderStats.ino; + } + stream.close(); stream.destroy(); - break; + + return resolve(findVersion); } - } - }); - stream.on('end', () => { - if(findVersion){ resolve(findVersion) } - resolve(findVersion); - }); - stream.on('close', () => { - if(findVersion){ resolve(findVersion) } - resolve(findVersion); - }) - }) + }); + }); } private setCustomVersions(versions: BSVersion[]): void{ @@ -93,14 +98,42 @@ export class BSLocalVersionService{ this.setCustomVersions(customVersions.filter(v => (v.name !== version.name || v.BSVersion !== version.BSVersion || v.color !== version.color))); } - public async getVersionPath(version: BSVersion): Promise{ - if(version.steam){ return this.steamService.getGameFolder(BS_APP_ID, "Beat Saber") } - if(version.oculus){ return this.oculusService.getGameFolder(OCULUS_BS_DIR); } - return path.join( - this.installLocationService.versionsDirectory, - this.getVersionFolder(version) - ); - } + + /** + * Return path of a version even if it's not installed. + * @param {BSVersion} version + * @returns {Promise} + */ + public async getVersionPath(version: BSVersion): Promise{ + if(version.steam){ return this.steamService.getGameFolder(BS_APP_ID, "Beat Saber") } + if(version.oculus){ return this.oculusService.getGameFolder(OCULUS_BS_DIR); } + + return path.join( + this.installLocationService.versionsDirectory, + this.getVersionFolder(version) + ); + } + + /** + * Return path of an installed version. Returns null if not found. + * @param {BSVersion} version + * @returns {Promise} + */ + public async getInstalledVersionPath(version: BSVersion): Promise{ + const versionPath = await this.getVersionPath(version); + if(await pathExist(versionPath)){ return versionPath; } + + const versionFolders = await getFoldersInFolder(this.installLocationService.versionsDirectory); + + for(const folder of versionFolders){ + const stats = await lstat(folder); + if(stats.ino === version.ino){ + return folder; + } + } + + return null; + } private removeSpecialChar(seq: string): string{ return sanitize(seq); @@ -131,6 +164,7 @@ export class BSLocalVersionService{ const oculusBsFolder = await this.oculusService.getGameFolder(OCULUS_BS_DIR); if(!oculusBsFolder){ return null; } const oculusBsVersion = await this.getVersionOfBSFolder(oculusBsFolder); + if(!oculusBsVersion){ return null; } const version = await this.remoteVersionService.getVersionDetails(oculusBsVersion.BSVersion); if(!version){ return null; } @@ -154,9 +188,13 @@ export class BSLocalVersionService{ for(const f of folderInInstallation){ log.info("try get version from folder", f); const rawVersion = await this.getVersionOfBSFolder(f); + if(!rawVersion){ continue; } + const bsVersion = {...await this.remoteVersionService.getVersionDetails(rawVersion.BSVersion)}; + if(!bsVersion){ continue; } + bsVersion.name = path.basename(f) !== bsVersion.BSVersion ? path.basename(f) : undefined; const customVersion = this.getCustomVersions().find(custom => custom.BSVersion === bsVersion.BSVersion && custom.name === bsVersion.name); @@ -167,20 +205,21 @@ export class BSLocalVersionService{ versions.push(bsVersion); }; + this.setCustomVersions(versions.filter(v => !!v.color)); return versions; - } + } - public async deleteVersion(version: BSVersion): Promise{ - if(version.steam || version.oculus){ return false; } - const versionFolder = await this.getVersionPath(version); - if(!(await pathExist(versionFolder))){ return true; } + public async deleteVersion(version: BSVersion): Promise{ + if(version.steam || version.oculus){ return false; } + const versionFolder = await this.getVersionPath(version); + if(!(await pathExist(versionFolder))){ return true; } - return deleteFolder(versionFolder) - .then(() => { return true; }) - .catch(() => { return false; }) - } + return deleteFolder(versionFolder) + .then(() => { return true; }) + .catch(() => { return false; }) + } public async editVersion(version: BSVersion, name: string, color: string): Promise{ if(version.steam || version.oculus){ throw {title: "CantEditSteam", message: "CantEditSteam"} as BsmException; } diff --git a/src/main/services/bsm-protocol.service.ts b/src/main/services/bsm-protocol.service.ts new file mode 100644 index 00000000..ec68f57e --- /dev/null +++ b/src/main/services/bsm-protocol.service.ts @@ -0,0 +1,41 @@ +import { Subject, Subscription, filter } from "rxjs"; +import { DeepLinkService } from "./deep-link.service"; +import { URL } from "url"; +import { isValidUrl } from "../../shared/helpers/url.helpers"; + +export class BsmProtocolService { + + private static instance: BsmProtocolService; + + public static getInstance(): BsmProtocolService{ + if(!BsmProtocolService.instance){ BsmProtocolService.instance = new BsmProtocolService(); } + return BsmProtocolService.instance; + } + + private readonly BSM_PROTOCOL = "bsmanager"; + + private readonly deepLink: DeepLinkService; + + private linkeReceived$ = new Subject(); + + private constructor(){ + this.deepLink = DeepLinkService.getInstance(); + + this.registerBsmProtocol(); + + this.deepLink.addLinkOpenedListener(this.BSM_PROTOCOL, link => { + if(!isValidUrl(link)){ return; } + this.linkeReceived$.next(new URL(link)); + }); + } + + private registerBsmProtocol(): boolean{ + if(this.deepLink.isDeepLinkRegistred(this.BSM_PROTOCOL)){ return true; } + return this.deepLink.registerDeepLink(this.BSM_PROTOCOL); + } + + public on(host: string, listener: (link: URL) => void): Subscription { + return this.linkeReceived$.pipe(filter(link => link.host === host)).subscribe(listener); + } + +} \ No newline at end of file diff --git a/src/main/services/deep-link.service.ts b/src/main/services/deep-link.service.ts index 095f9e35..b1d696c6 100644 --- a/src/main/services/deep-link.service.ts +++ b/src/main/services/deep-link.service.ts @@ -69,7 +69,7 @@ export class DeepLinkService { public dispatchLinkOpened(link: string){ - log.info("DEISPATCH", link); + log.info("DISPATCH", link); const url = new URL(link); diff --git a/src/main/services/steam.service.ts b/src/main/services/steam.service.ts index 2287309c..5fb81f90 100644 --- a/src/main/services/steam.service.ts +++ b/src/main/services/steam.service.ts @@ -27,47 +27,48 @@ export class SteamService{ return SteamService.instance; } - public async getActiveUser(): Promise{ - const res = await regedit.promisified.list(["HKCU\\Software\\Valve\\Steam\\ActiveProcess"]); - const keys = res?.["HKCU\\Software\\Valve\\Steam\\ActiveProcess"]; - if(!keys?.exists){ throw "Key \"HKCU\\Software\\Valve\\Steam\\ActiveProcess\" not exist"; } - return (keys.values?.ActiveUser.value || undefined) as number; - } - - public async steamRunning(): Promise{ - if (process.platform === 'win32') { - return !!(await this.getActiveUser()); + public async getActiveUser(): Promise{ + const res = await regedit.promisified.list(["HKCU\\Software\\Valve\\Steam\\ActiveProcess"]); + const keys = res?.["HKCU\\Software\\Valve\\Steam\\ActiveProcess"]; + if(!keys?.exists){ throw "Key \"HKCU\\Software\\Valve\\Steam\\ActiveProcess\" not exist"; } + return (keys.values?.ActiveUser.value || undefined) as number; + } + + public async steamRunning(): Promise{ + const steamProcessRunning = await this.utils.taskRunning("steam"); + if(process.platform === "linux") { return steamProcessRunning; } + + return steamProcessRunning && !!(await this.getActiveUser()); } - return await psList() - .then(processes => !!processes.find(process => process.cmd.includes('steam'))) - .catch(e => {log.error(e); throw e}) - } public async getSteamPath(): Promise{ - if(this.steamPath){ return this.steamPath; } + if(this.steamPath){ return this.steamPath; } - switch (process.platform) { - case "linux": - this.steamPath = path.join(app.getPath('home'), '.steam', "steam"); - return this.steamPath; - case "win32": - const [win32Res, win64Res] = await Promise.all([ - regedit.promisified.list(['HKLM\\SOFTWARE\\Valve\\Steam']), - regedit.promisified.list(['HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam']) - ]); + switch (process.platform) { + case "linux": + this.steamPath = path.join(app.getPath('home'), '.steam', "steam"); + return this.steamPath; + case "win32": + const [win32Res, win64Res] = await Promise.all([ + regedit.promisified.list(['HKLM\\SOFTWARE\\Valve\\Steam']), + regedit.promisified.list(['HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam']) + ]); - const [win32, win64] = [win32Res["HKLM\\SOFTWARE\\Valve\\Steam"], win64Res["HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"]]; + const [win32, win64] = [win32Res["HKLM\\SOFTWARE\\Valve\\Steam"], win64Res["HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam"]]; - let res = ''; - if(win64.exists && win64?.values?.InstallPath?.value){ res = win64.values.InstallPath.value as string; } - else if(win32.exists && win32?.values?.InstallPath?.value){ res = win32.values.InstallPath.value as string; } - this.steamPath = res; - return res; - default: - return null; + if(win64.exists && win64?.values?.InstallPath?.value){ + this.steamPath = win64.values.InstallPath.value as string; + } + else if (win32.exists && win32?.values?.InstallPath?.value){ + this.steamPath = win32.values.InstallPath.value as string; + } + + return this.steamPath; + default: + return null; + } } - } public async getGameFolder(gameId: string, gameFolder?: string): Promise{ try{ @@ -105,7 +106,7 @@ export class SteamService{ if(!steamRunning){ return; } clearInterval(interval); resolve(); - }, 3000); + }, 4000); // If steam is not running after 60 seconds, reject setTimeout(() => { diff --git a/src/main/services/utils.service.ts b/src/main/services/utils.service.ts index 6975903e..152301dd 100644 --- a/src/main/services/utils.service.ts +++ b/src/main/services/utils.service.ts @@ -1,4 +1,3 @@ -import { spawnSync } from "child_process"; import path from "path"; import { app, BrowserWindow } from "electron"; import { IpcResponse } from "shared/models/ipc"; @@ -37,7 +36,7 @@ export class UtilsService{ public async taskRunning(task: string): Promise { try{ const processes = await psList(); - return !!processes.find(process => process.cmd.includes(task)); + return processes.some(process => process.name?.includes(task) || process.cmd?.includes(task)); } catch(error){ log.error(error); diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 9e399a49..4c9401bb 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -77,8 +77,8 @@ export class BSLauncherService{ this.notificationService.notifySuccess({title: `notifications.bs-launch.success.titles.${event.type}`, desc: `notifications.bs-launch.success.msg.${event.type}`}); }, error: (err: BSLaunchErrorEvent) => { - if(err.type === BSLaunchErrorType.UNKNOWN_ERROR || !Object.values(BSLaunchErrorType).includes(err.type)){ - this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.UNABLE_TO_LAUNCH"}); + if(!Object.values(BSLaunchErrorType).includes(err.type)){ + 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.type}`, desc: `notifications.bs-launch.errors.msg.${err.type}`}) } diff --git a/src/shared/bs-version.interface.ts b/src/shared/bs-version.interface.ts index a76b342d..e9a45b56 100644 --- a/src/shared/bs-version.interface.ts +++ b/src/shared/bs-version.interface.ts @@ -1,6 +1,7 @@ export interface PartialBSVersion { BSVersion: string, name?: string + ino?: number, } export interface BSVersion extends PartialBSVersion { From 215586a41b5faedd10d9daee67c5c15d7df6da71 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:49:08 +0200 Subject: [PATCH 4/9] [feature] nothing --- assets/jsons/bs-versions.json | 1136 ++++++++++++++++----------------- src/main/main.ts | 2 - 2 files changed, 568 insertions(+), 570 deletions(-) diff --git a/assets/jsons/bs-versions.json b/assets/jsons/bs-versions.json index 2b2a62b6..54e32ece 100644 --- a/assets/jsons/bs-versions.json +++ b/assets/jsons/bs-versions.json @@ -1,569 +1,569 @@ [ - { - "BSVersion": "0.10.1", - "BSManifest": "6316038906315325420", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1662268711472777795", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/a6d2b9ce074e71458499defdb5ef83bc4b9b56bb.png", - "ReleaseDate": "1525201860", - "year": "2018" - }, - { - "BSVersion": "0.10.2", - "BSManifest": "2542095265882143144", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3199123443061240805", - "ReleaseDate": "1526065860", - "year": "2018" - }, - { - "BSVersion": "0.10.2p1", - "BSManifest": "5611588554149133260", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1660018815112331498", - "ReleaseDate": "1526843460", - "year": "2018" - }, - { - "BSVersion": "0.11.0b1", - "BSManifest": "8892605455517437772", - "ReleaseDate": "1530472260", - "year": "2018" - }, - { - "BSVersion": "0.11.0", - "BSManifest": "8700049030626148111", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1675787121569907303", - "ReleaseDate": "1532027460", - "year": "2018" - }, - { - "BSVersion": "0.11.1", - "BSManifest": "6574193224879562324", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1675787121577641471", - "ReleaseDate": "1532113860", - "year": "2018" - }, - { - "BSVersion": "0.11.2", - "BSManifest": "2707973953401625222", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1700559455389069835", - "ReleaseDate": "1534792260", - "year": "2018" - }, - { - "BSVersion": "0.12.0", - "BSManifest": "6094599000655593822", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708449632708343984", - "ReleaseDate": "1543349460", - "year": "2018" - }, - { - "BSVersion": "0.12.0p1", - "BSManifest": "2068421223689664394", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708450266986928520", - "ReleaseDate": "1543435860", - "year": "2018" - }, - { - "BSVersion": "0.12.1", - "BSManifest": "2472041066434647526", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3142848113551335636", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/9b4b1890f39a682c6f4a06f12a56965b7aee0b6c.jpg", - "ReleaseDate": "1544818260", - "year": "2018" - }, - { - "BSVersion": "0.12.2", - "BSManifest": "5325635033564462932", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708452169977638962", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/82e2ce43df0e59337a79341c54ac2e5e12a5e7bc.jpg", - "ReleaseDate": "1545423060", - "year": "2018" - }, - { - "BSVersion": "0.13.0", - "BSManifest": "3102409495238838111", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1809790777736470809", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/8f2128973e6d221b4f00aedb5194417eadad9920.jpg", - "ReleaseDate": "1552594260", - "year": "2019" - }, - { - "BSVersion": "0.13.0p1", - "BSManifest": "6827433614670733798", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1809790964563689188", - "ReleaseDate": "1553026260", - "year": "2019" - }, - { - "BSVersion": "0.13.1", - "BSManifest": "6033025349617217666", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1735482645687974657", - "ReleaseDate": "1554145860", - "year": "2019" - }, - { - "BSVersion": "0.13.2", - "BSManifest": "6839388023573913446", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1779393376014546213", - "ReleaseDate": "1554405060", - "year": "2019" - }, - { - "BSVersion": "1.0.0", - "BSManifest": "152937782137361764", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2655347409592352590", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/023ab2b6a2f811e2beea327ae3875da643101694.jpg", - "ReleaseDate": "1558465860", - "year": "2019" - }, - { - "BSVersion": "1.0.1", - "BSManifest": "7950322551526208347", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1617268229025488499", - "ReleaseDate": "1558984260", - "year": "2019" - }, - { - "BSVersion": "1.1.0", - "BSManifest": "1400454104881094752", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1606010497768610092", - "ReleaseDate": "1560193860", - "year": "2019" - }, - { - "BSVersion": "1.1.0p1", - "BSManifest": "1041583928494277430", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1607139567641962696", - "ReleaseDate": "1563217860", - "year": "2019" - }, - { - "BSVersion": "1.2.0", - "BSManifest": "3820905673516362176", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3266717385279679846", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/cd96e7e784bb8761ba8e8609285baa5e1aec0d85.jpg", - "ReleaseDate": "1564081860", - "year": "2019" - }, - { - "BSVersion": "1.3.0", - "BSManifest": "2440312204809283162", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1602640407719691235", - "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/7aec534fb70fafa582addd6cd52998ccc31adf57.jpg", - "ReleaseDate": "1567105860", - "year": "2019" - }, - { - "BSVersion": "1.4.0", - "BSManifest": "3532596684905902618", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1595888090489865366", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/2998e47ebbc57552718bb95e233a3a43d598630f.png", - "ReleaseDate": "1567105860", - "year": "2019" - }, - { - "BSVersion": "1.4.2", - "BSManifest": "1199049250928380207", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3172148783714663931", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/b35d93fe817cea38c92c266579395c1fcd275af2.jpg", - "ReleaseDate": "1570821060", - "year": "2019" - }, - { - "BSVersion": "1.5.0", - "BSManifest": "2831333980042022356", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1604898551795379829", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/56dbb4a14b764ccea1de93625225b83de1636aee_400x225.jpg", - "ReleaseDate": "1573157460", - "year": "2019" - }, - { - "BSVersion": "1.6.0", - "BSManifest": "1869974316274529288", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3462636667924814182", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/99a5ad752c76859e8697d75a140500195969fb2d.jpg", - "ReleaseDate": "1576267860", - "year": "2019" - }, - { - "BSVersion": "1.6.1", - "BSManifest": "6122319670026856947", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1702855647517326143", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/5e54a041cc52674f03a4d7c5406f2bb42d1f379b_400x225.jpg", - "ReleaseDate": "1578427860", - "year": "2020" - }, - { - "BSVersion": "1.6.2", - "BSManifest": "4932559146183937357", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1702857555888635029", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/5e54a041cc52674f03a4d7c5406f2bb42d1f379b_400x225.jpg", - "ReleaseDate": "1578600660", - "year": "2020" - }, - { - "BSVersion": "1.7.0", - "BSManifest": "3516084911940449222", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708488968117071534", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/ddf8c11e64231bf86ff18f5bb7d81f7dec7bc655_400x225.png", - "ReleaseDate": "1580328660", - "year": "2020" - }, - { - "BSVersion": "1.8.0", - "BSManifest": "3177969677109016846", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2080040995048021450", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/ef301c5813a7f029156da357a997f17f80564a17_400x225.png", - "ReleaseDate": "1585253460", - "year": "2020" - }, - { - "BSVersion": "1.9.0", - "BSManifest": "7885463693258878294", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2194884053816491058", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/8b365e3fdd9e8494987ea92543114404f20db891.png", - "ReleaseDate": "1586459460", - "year": "2020" - }, - { - "BSVersion": "1.9.1", - "BSManifest": "6222769774084748916", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2196011856188103369", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/144427845aef04dbe4aa9dee62818f2d637e1f42_400x225.png", - "ReleaseDate": "1588705860", - "year": "2020" - }, - { - "BSVersion": "1.10.0", - "BSManifest": "6711131863503994755", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2179125258536373088", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/3a13e1c04c4f91a16c53fdb3a7662bcae60f48fa.png", - "ReleaseDate": "1590433860", - "year": "2020" - }, - { - "BSVersion": "1.11.0", - "BSManifest": "1919603726987963829", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3961432516746159538", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/afba23a3fcc80b6d2c40d2acd9ab9a51be9d3eee.png", - "ReleaseDate": "1597691460", - "year": "2020" - }, - { - "BSVersion": "1.11.1", - "BSManifest": "3268824881806146387", - "ReleaseDate": "1597950660", - "year": "2020" - }, - { - "BSVersion": "1.12.1", - "BSManifest": "2928416283534881313", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2889580787311737971", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/cb8f24404f10a177e0ff60473f9293c8fb9b5496.png", - "ReleaseDate": "1602616260", - "year": "2020" - }, - { - "BSVersion": "1.12.2", - "BSManifest": "543439039654962432", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5096345334644252275", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/56d1d6b2a2687530a5db221241cc4d7e4feefb4d.png", - "ReleaseDate": "1603134660", - "year": "2020" - }, - { - "BSVersion": "1.13.0", - "BSManifest": "4635119747389290346", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2903094755791760339", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/4ae946eda7c0bc9421c2afb7b33e965103924cb9.png", - "ReleaseDate": "1605211860", - "year": "2020" - }, - { - "BSVersion": "1.13.2", - "BSManifest": "8571679771389514488", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3025824186763919109", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/70f13d02f95c7fadae92305474ae16ce1b99162e.jpg", - "ReleaseDate": "1611259860", - "year": "2021" - }, - { - "BSVersion": "1.13.4", - "BSManifest": "1257277263145069282", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3020198491530803202", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/78643cb3c68591da1e69550300a6452390c3e797.png", - "ReleaseDate": "1615320660", - "year": "2021" - }, - { - "BSVersion": "1.13.5", - "BSManifest": "7007516983116400336", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3021325027071201045", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/2611baaea1f0e49be4f611fed4a3cb5d155c8eb5.png", - "ReleaseDate": "1615579860", - "year": "2021" - }, - { - "BSVersion": "1.14.0", - "BSManifest": "9218225910501819399", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3012318462417082617", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/51750f9885b9fe51df5820ba0e7f36c9b2154d22.png", - "ReleaseDate": "1616098260", - "year": "2021" - }, - { - "BSVersion": "1.15.0", - "BSManifest": "7624554893344753887", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3041595663499101523", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/e8de2954a115e1961bdbfa3abab546d43dd653a4.png", - "ReleaseDate": "1619723460", - "year": "2021" - }, - { - "BSVersion": "1.16.0", - "BSManifest": "3667184295685865706", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3001065803744427929", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/0ad20c1cf3d4e78aa8f28fd136f89f3683df2fb6.png", - "ReleaseDate": "1622142660", - "year": "2021" - }, - { - "BSVersion": "1.16.1", - "BSManifest": "9201874499606445062", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3001065803748822093", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/522ca68fe0c4576f57189afdbb8107226cdcc819.png", - "ReleaseDate": "1622229060", - "year": "2021" - }, - { - "BSVersion": "1.16.2", - "BSManifest": "3692829915208062825", - "ReleaseURL": "http://steamcommunity.com/games/620980/announcements/detail/3024710972645830369", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/7e81895079a84f1b85ddcee330aec7e5626fd2c6.png", - "ReleaseDate": "1623352260", - "year": "2021" - }, - { - "BSVersion": "1.16.3", - "BSManifest": "6392596175313869009", - "ReleaseURL": "http://steamcommunity.com/games/620980/announcements/detail/5293402556388135977", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/34340f96abfa0203931ca312302ee0c19dc336c2.png", - "ReleaseDate": "1626808260", - "year": "2021" - }, - { - "BSVersion": "1.16.4", - "BSManifest": "8820433629543698585", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2965042081090310028", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/22b48a8e92eed7c4e471692124724a2f3ebc3f18.png", - "ReleaseDate": "1627326660", - "year": "2021" - }, - { - "BSVersion": "1.17.0", - "BSManifest": "7826684224434229804", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2945905042782366475", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/a789a030122442308376887039c244222b92c768.png", - "ReleaseDate": "1630437060", - "year": "2021" - }, - { - "BSVersion": "1.17.1", - "BSManifest": "4668547658954826996", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2947032120578243378", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/51561161026afcf3482bcbe2ea62a6d56e03a8d0.png", - "ReleaseDate": "1631214660", - "year": "2021" - }, - { - "BSVersion": "1.18.0", - "BSManifest": "5599254819160454367", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5570379635768010184", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/89a693714b3ee32d84d05bf62caca6f651a1dbec.png", - "ReleaseDate": "1632251460", - "year": "2021" - }, - { - "BSVersion": "1.18.1", - "BSManifest": "8961661233382948062", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2994322456166449763", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/a3a4511beadf811a56742fc234724ad14ab34749.png", - "ReleaseDate": "1633547460", - "year": "2021" - }, - { - "BSVersion": "1.18.2", - "BSManifest": "6835596583028648427", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3026974832179662032", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/71994e941f3d2a8fbbe335fc717c136b5dc9f823.png", - "ReleaseDate": "1635189060", - "year": "2021" - }, - { - "BSVersion": "1.18.3", - "BSManifest": "6558821762131072991", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3067508483705433949", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/7d79335af5e8ca560198e6a850e339bd7b430d9f.png", - "ReleaseDate": "1636056660", - "year": "2021" - }, - { - "BSVersion": "1.19.0", - "BSManifest": "8948172000430595334", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3105792342251200877", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/537cf297a6f90b8bbe7baae92b6885f8fbf98d8c.png", - "ReleaseDate": "1639167060", - "year": "2021" - }, - { - "BSVersion": "1.19.1", - "BSManifest": "1175216641760077721", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5771929045825669047", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/908d3ebdf273ca6573c3c95898be22ff052ea014.png", - "ReleaseDate": "1644869460", - "year": "2022" - }, - { - "BSVersion": "1.20.0", - "BSManifest": "4202491068770948196", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3096792662638975765", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/8dfcc668bc78674d7c1d010cee2a8153e0c0c2aa.png", - "ReleaseDate": "1646770260", - "year": "2022" - }, - { - "BSVersion": "1.21.0", - "BSManifest": "3648558848715327622", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3180111792066790947", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/d854a4cc83b282acc191227a7759b4f5e6c6b085.png", - "ReleaseDate": "1648753860", - "year": "2022" - }, - { - "BSVersion": "1.22.0", - "BSManifest": "4038188869828689801", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3181240878804806326", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/8486ec6eb3b6e28eaac59d414dd20bd18eed36d8.png", - "ReleaseDate": "1651777860", - "year": "2022" - }, - { - "BSVersion": "1.22.1", - "BSManifest": "3771211058784167267", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3185745740381652029", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/bc9fea661f7f18616b6b2feb267cc3af0ea679c5.png", - "ReleaseDate": "1652987460", - "year": "2022" - }, - { - "BSVersion": "1.23.0", - "BSManifest": "8051970117665729770", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3346751320551809488", - "ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/1623b6e0b864fdad8be08275c3bb126868eafe9b.png", - "ReleaseDate": "1655218980", - "year": "2022" - }, - { - "BSVersion": "1.24.0", - "BSManifest": "649695825989344999", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4719226562388423388", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/82c29d674bc57596102a6b815f66e956f217e658.png", - "ReleaseDate": "1657810920", - "year": "2022" - }, - { - "BSVersion": "1.24.1", - "BSManifest": "3141359577983993890", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4719226562388423388", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/82c29d674bc57596102a6b815f66e956f217e658.png", - "ReleaseDate": "1661864214", - "year": "2022" - }, - { - "BSVersion": "1.25.0", - "BSManifest": "4529582051007915041", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3312985013538143172", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/54ac649877eca25a2a6449662337e2c63c51bbcf.png", - "ReleaseDate": "1665068529", - "year": "2022" - }, - { - "BSVersion": "1.25.1", - "BSManifest": "7372469314660649150", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5346361068670142187", - "ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/52cbd15b5e226f509e051565f6e4935eb0e44e2c.png", - "ReleaseDate": "1665673269", - "year": "2022" - }, - { - "BSVersion": "1.26.0", - "BSManifest": "3634450655207470002", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3414318646334948892", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/d18ad9d7f45a5cc2a467d75f0bdf9d32b9141b14.png", - "ReleaseDate": "1667923560", - "year": "2022" - }, - { - "BSVersion": "1.27.0", - "BSManifest": "3485516174915301618", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3629368753553708497", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/d9ed8771498be7fc83721aaaaa5b15a08fbeadb1.png", - "ReleaseDate": "1670943003", - "year": "2022" - }, - { - "BSVersion": "1.28.0", - "BSManifest": "7463243155526905423", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3666530358620442392", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/8f978bbf292810004bca12523e76be8a7eec8c4e.png", - "ReleaseDate": "1677593822", - "year": "2023" - }, - { - "BSVersion": "1.29.0", - "BSManifest": "3341527958186345367", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105082976092", - "ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/255fd49cd96042b97089f754609be291293e783f.png", - "ReleaseDate": "1680188760", - "year": "2023" - }, - { - "BSVersion": "1.29.1", - "BSManifest": "886973241045584398", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105101272202", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/c328e407367e9914abaf92f609501877ee5abb63.png", - "ReleaseDate": "1680623885", - "year": "2023" - }, - { - "BSVersion": "1.29.4", - "BSManifest": "6291266771922375922", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3708194993422325380", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/9c3d476c03e4a2ac72ca5df016b7c94d242b41cb.png", - "ReleaseDate": "1683644991", - "year": "2023" - }, - { - "BSVersion": "1.30.0", - "BSManifest": "7304715857122259127", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3725085394628677636", - "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/cd56688ad716b544af14a61b2fbace9088478887.png", - "ReleaseDate": "1684965390", - "year": "2023" - }, - { - "BSVersion": "1.30.2", - "BSManifest": "8042804988990059618", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3655280868312846230", - "ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/6d78e092c54f411b36f2c0577d62eb1b25f91aa3.png", - "ReleaseDate": "1686668364", - "year": "2023" - }, - { - "BSVersion": "1.31.0", - "BSManifest": "676582354127623831", - "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3674423070127121445", - "ReleaseImg": "https://clan.cloudflare.steamstatic.com/images/32055887/28993aecc56a75c52295fa07620f58cb0e4b09e8.png", - "ReleaseDate": "1688050646", - "year": "2023" - } -] + { + "BSVersion": "0.10.1", + "BSManifest": "6316038906315325420", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1662268711472777795", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/a6d2b9ce074e71458499defdb5ef83bc4b9b56bb.png", + "ReleaseDate": "1525201860", + "year": "2018" + }, + { + "BSVersion": "0.10.2", + "BSManifest": "2542095265882143144", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3199123443061240805", + "ReleaseDate": "1526065860", + "year": "2018" + }, + { + "BSVersion": "0.10.2p1", + "BSManifest": "5611588554149133260", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1660018815112331498", + "ReleaseDate": "1526843460", + "year": "2018" + }, + { + "BSVersion": "0.11.0b1", + "BSManifest": "8892605455517437772", + "ReleaseDate": "1530472260", + "year": "2018" + }, + { + "BSVersion": "0.11.0", + "BSManifest": "8700049030626148111", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1675787121569907303", + "ReleaseDate": "1532027460", + "year": "2018" + }, + { + "BSVersion": "0.11.1", + "BSManifest": "6574193224879562324", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1675787121577641471", + "ReleaseDate": "1532113860", + "year": "2018" + }, + { + "BSVersion": "0.11.2", + "BSManifest": "2707973953401625222", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1700559455389069835", + "ReleaseDate": "1534792260", + "year": "2018" + }, + { + "BSVersion": "0.12.0", + "BSManifest": "6094599000655593822", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708449632708343984", + "ReleaseDate": "1543349460", + "year": "2018" + }, + { + "BSVersion": "0.12.0p1", + "BSManifest": "2068421223689664394", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708450266986928520", + "ReleaseDate": "1543435860", + "year": "2018" + }, + { + "BSVersion": "0.12.1", + "BSManifest": "2472041066434647526", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3142848113551335636", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/9b4b1890f39a682c6f4a06f12a56965b7aee0b6c.jpg", + "ReleaseDate": "1544818260", + "year": "2018" + }, + { + "BSVersion": "0.12.2", + "BSManifest": "5325635033564462932", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708452169977638962", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/82e2ce43df0e59337a79341c54ac2e5e12a5e7bc.jpg", + "ReleaseDate": "1545423060", + "year": "2018" + }, + { + "BSVersion": "0.13.0", + "BSManifest": "3102409495238838111", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1809790777736470809", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/8f2128973e6d221b4f00aedb5194417eadad9920.jpg", + "ReleaseDate": "1552594260", + "year": "2019" + }, + { + "BSVersion": "0.13.0p1", + "BSManifest": "6827433614670733798", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1809790964563689188", + "ReleaseDate": "1553026260", + "year": "2019" + }, + { + "BSVersion": "0.13.1", + "BSManifest": "6033025349617217666", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1735482645687974657", + "ReleaseDate": "1554145860", + "year": "2019" + }, + { + "BSVersion": "0.13.2", + "BSManifest": "6839388023573913446", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1779393376014546213", + "ReleaseDate": "1554405060", + "year": "2019" + }, + { + "BSVersion": "1.0.0", + "BSManifest": "152937782137361764", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2655347409592352590", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/023ab2b6a2f811e2beea327ae3875da643101694.jpg", + "ReleaseDate": "1558465860", + "year": "2019" + }, + { + "BSVersion": "1.0.1", + "BSManifest": "7950322551526208347", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1617268229025488499", + "ReleaseDate": "1558984260", + "year": "2019" + }, + { + "BSVersion": "1.1.0", + "BSManifest": "1400454104881094752", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1606010497768610092", + "ReleaseDate": "1560193860", + "year": "2019" + }, + { + "BSVersion": "1.1.0p1", + "BSManifest": "1041583928494277430", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1607139567641962696", + "ReleaseDate": "1563217860", + "year": "2019" + }, + { + "BSVersion": "1.2.0", + "BSManifest": "3820905673516362176", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3266717385279679846", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/cd96e7e784bb8761ba8e8609285baa5e1aec0d85.jpg", + "ReleaseDate": "1564081860", + "year": "2019" + }, + { + "BSVersion": "1.3.0", + "BSManifest": "2440312204809283162", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1602640407719691235", + "ReleaseImg": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/clans/32055887/7aec534fb70fafa582addd6cd52998ccc31adf57.jpg", + "ReleaseDate": "1567105860", + "year": "2019" + }, + { + "BSVersion": "1.4.0", + "BSManifest": "3532596684905902618", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1595888090489865366", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/2998e47ebbc57552718bb95e233a3a43d598630f.png", + "ReleaseDate": "1567105860", + "year": "2019" + }, + { + "BSVersion": "1.4.2", + "BSManifest": "1199049250928380207", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3172148783714663931", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/b35d93fe817cea38c92c266579395c1fcd275af2.jpg", + "ReleaseDate": "1570821060", + "year": "2019" + }, + { + "BSVersion": "1.5.0", + "BSManifest": "2831333980042022356", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1604898551795379829", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/56dbb4a14b764ccea1de93625225b83de1636aee_400x225.jpg", + "ReleaseDate": "1573157460", + "year": "2019" + }, + { + "BSVersion": "1.6.0", + "BSManifest": "1869974316274529288", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3462636667924814182", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/99a5ad752c76859e8697d75a140500195969fb2d.jpg", + "ReleaseDate": "1576267860", + "year": "2019" + }, + { + "BSVersion": "1.6.1", + "BSManifest": "6122319670026856947", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1702855647517326143", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/5e54a041cc52674f03a4d7c5406f2bb42d1f379b_400x225.jpg", + "ReleaseDate": "1578427860", + "year": "2020" + }, + { + "BSVersion": "1.6.2", + "BSManifest": "4932559146183937357", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1702857555888635029", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/5e54a041cc52674f03a4d7c5406f2bb42d1f379b_400x225.jpg", + "ReleaseDate": "1578600660", + "year": "2020" + }, + { + "BSVersion": "1.7.0", + "BSManifest": "3516084911940449222", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/1708488968117071534", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/ddf8c11e64231bf86ff18f5bb7d81f7dec7bc655_400x225.png", + "ReleaseDate": "1580328660", + "year": "2020" + }, + { + "BSVersion": "1.8.0", + "BSManifest": "3177969677109016846", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2080040995048021450", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/ef301c5813a7f029156da357a997f17f80564a17_400x225.png", + "ReleaseDate": "1585253460", + "year": "2020" + }, + { + "BSVersion": "1.9.0", + "BSManifest": "7885463693258878294", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2194884053816491058", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/8b365e3fdd9e8494987ea92543114404f20db891.png", + "ReleaseDate": "1586459460", + "year": "2020" + }, + { + "BSVersion": "1.9.1", + "BSManifest": "6222769774084748916", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2196011856188103369", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/144427845aef04dbe4aa9dee62818f2d637e1f42_400x225.png", + "ReleaseDate": "1588705860", + "year": "2020" + }, + { + "BSVersion": "1.10.0", + "BSManifest": "6711131863503994755", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2179125258536373088", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/3a13e1c04c4f91a16c53fdb3a7662bcae60f48fa.png", + "ReleaseDate": "1590433860", + "year": "2020" + }, + { + "BSVersion": "1.11.0", + "BSManifest": "1919603726987963829", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3961432516746159538", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/afba23a3fcc80b6d2c40d2acd9ab9a51be9d3eee.png", + "ReleaseDate": "1597691460", + "year": "2020" + }, + { + "BSVersion": "1.11.1", + "BSManifest": "3268824881806146387", + "ReleaseDate": "1597950660", + "year": "2020" + }, + { + "BSVersion": "1.12.1", + "BSManifest": "2928416283534881313", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2889580787311737971", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/cb8f24404f10a177e0ff60473f9293c8fb9b5496.png", + "ReleaseDate": "1602616260", + "year": "2020" + }, + { + "BSVersion": "1.12.2", + "BSManifest": "543439039654962432", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5096345334644252275", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/56d1d6b2a2687530a5db221241cc4d7e4feefb4d.png", + "ReleaseDate": "1603134660", + "year": "2020" + }, + { + "BSVersion": "1.13.0", + "BSManifest": "4635119747389290346", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2903094755791760339", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/4ae946eda7c0bc9421c2afb7b33e965103924cb9.png", + "ReleaseDate": "1605211860", + "year": "2020" + }, + { + "BSVersion": "1.13.2", + "BSManifest": "8571679771389514488", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3025824186763919109", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/70f13d02f95c7fadae92305474ae16ce1b99162e.jpg", + "ReleaseDate": "1611259860", + "year": "2021" + }, + { + "BSVersion": "1.13.4", + "BSManifest": "1257277263145069282", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3020198491530803202", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/78643cb3c68591da1e69550300a6452390c3e797.png", + "ReleaseDate": "1615320660", + "year": "2021" + }, + { + "BSVersion": "1.13.5", + "BSManifest": "7007516983116400336", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3021325027071201045", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/2611baaea1f0e49be4f611fed4a3cb5d155c8eb5.png", + "ReleaseDate": "1615579860", + "year": "2021" + }, + { + "BSVersion": "1.14.0", + "BSManifest": "9218225910501819399", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3012318462417082617", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/51750f9885b9fe51df5820ba0e7f36c9b2154d22.png", + "ReleaseDate": "1616098260", + "year": "2021" + }, + { + "BSVersion": "1.15.0", + "BSManifest": "7624554893344753887", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3041595663499101523", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/e8de2954a115e1961bdbfa3abab546d43dd653a4.png", + "ReleaseDate": "1619723460", + "year": "2021" + }, + { + "BSVersion": "1.16.0", + "BSManifest": "3667184295685865706", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3001065803744427929", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/0ad20c1cf3d4e78aa8f28fd136f89f3683df2fb6.png", + "ReleaseDate": "1622142660", + "year": "2021" + }, + { + "BSVersion": "1.16.1", + "BSManifest": "9201874499606445062", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3001065803748822093", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/522ca68fe0c4576f57189afdbb8107226cdcc819.png", + "ReleaseDate": "1622229060", + "year": "2021" + }, + { + "BSVersion": "1.16.2", + "BSManifest": "3692829915208062825", + "ReleaseURL": "http://steamcommunity.com/games/620980/announcements/detail/3024710972645830369", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/7e81895079a84f1b85ddcee330aec7e5626fd2c6.png", + "ReleaseDate": "1623352260", + "year": "2021" + }, + { + "BSVersion": "1.16.3", + "BSManifest": "6392596175313869009", + "ReleaseURL": "http://steamcommunity.com/games/620980/announcements/detail/5293402556388135977", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/34340f96abfa0203931ca312302ee0c19dc336c2.png", + "ReleaseDate": "1626808260", + "year": "2021" + }, + { + "BSVersion": "1.16.4", + "BSManifest": "8820433629543698585", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2965042081090310028", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/22b48a8e92eed7c4e471692124724a2f3ebc3f18.png", + "ReleaseDate": "1627326660", + "year": "2021" + }, + { + "BSVersion": "1.17.0", + "BSManifest": "7826684224434229804", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2945905042782366475", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/a789a030122442308376887039c244222b92c768.png", + "ReleaseDate": "1630437060", + "year": "2021" + }, + { + "BSVersion": "1.17.1", + "BSManifest": "4668547658954826996", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2947032120578243378", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/51561161026afcf3482bcbe2ea62a6d56e03a8d0.png", + "ReleaseDate": "1631214660", + "year": "2021" + }, + { + "BSVersion": "1.18.0", + "BSManifest": "5599254819160454367", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5570379635768010184", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/89a693714b3ee32d84d05bf62caca6f651a1dbec.png", + "ReleaseDate": "1632251460", + "year": "2021" + }, + { + "BSVersion": "1.18.1", + "BSManifest": "8961661233382948062", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/2994322456166449763", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/a3a4511beadf811a56742fc234724ad14ab34749.png", + "ReleaseDate": "1633547460", + "year": "2021" + }, + { + "BSVersion": "1.18.2", + "BSManifest": "6835596583028648427", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3026974832179662032", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/71994e941f3d2a8fbbe335fc717c136b5dc9f823.png", + "ReleaseDate": "1635189060", + "year": "2021" + }, + { + "BSVersion": "1.18.3", + "BSManifest": "6558821762131072991", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3067508483705433949", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/7d79335af5e8ca560198e6a850e339bd7b430d9f.png", + "ReleaseDate": "1636056660", + "year": "2021" + }, + { + "BSVersion": "1.19.0", + "BSManifest": "8948172000430595334", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3105792342251200877", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/537cf297a6f90b8bbe7baae92b6885f8fbf98d8c.png", + "ReleaseDate": "1639167060", + "year": "2021" + }, + { + "BSVersion": "1.19.1", + "BSManifest": "1175216641760077721", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5771929045825669047", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/908d3ebdf273ca6573c3c95898be22ff052ea014.png", + "ReleaseDate": "1644869460", + "year": "2022" + }, + { + "BSVersion": "1.20.0", + "BSManifest": "4202491068770948196", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3096792662638975765", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/8dfcc668bc78674d7c1d010cee2a8153e0c0c2aa.png", + "ReleaseDate": "1646770260", + "year": "2022" + }, + { + "BSVersion": "1.21.0", + "BSManifest": "3648558848715327622", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3180111792066790947", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/d854a4cc83b282acc191227a7759b4f5e6c6b085.png", + "ReleaseDate": "1648753860", + "year": "2022" + }, + { + "BSVersion": "1.22.0", + "BSManifest": "4038188869828689801", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3181240878804806326", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/8486ec6eb3b6e28eaac59d414dd20bd18eed36d8.png", + "ReleaseDate": "1651777860", + "year": "2022" + }, + { + "BSVersion": "1.22.1", + "BSManifest": "3771211058784167267", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3185745740381652029", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/bc9fea661f7f18616b6b2feb267cc3af0ea679c5.png", + "ReleaseDate": "1652987460", + "year": "2022" + }, + { + "BSVersion": "1.23.0", + "BSManifest": "8051970117665729770", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3346751320551809488", + "ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/1623b6e0b864fdad8be08275c3bb126868eafe9b.png", + "ReleaseDate": "1655218980", + "year": "2022" + }, + { + "BSVersion": "1.24.0", + "BSManifest": "649695825989344999", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4719226562388423388", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/82c29d674bc57596102a6b815f66e956f217e658.png", + "ReleaseDate": "1657810920", + "year": "2022" + }, + { + "BSVersion": "1.24.1", + "BSManifest": "3141359577983993890", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4719226562388423388", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/82c29d674bc57596102a6b815f66e956f217e658.png", + "ReleaseDate": "1661864214", + "year": "2022" + }, + { + "BSVersion": "1.25.0", + "BSManifest": "4529582051007915041", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3312985013538143172", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/54ac649877eca25a2a6449662337e2c63c51bbcf.png", + "ReleaseDate": "1665068529", + "year": "2022" + }, + { + "BSVersion": "1.25.1", + "BSManifest": "7372469314660649150", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/5346361068670142187", + "ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/52cbd15b5e226f509e051565f6e4935eb0e44e2c.png", + "ReleaseDate": "1665673269", + "year": "2022" + }, + { + "BSVersion": "1.26.0", + "BSManifest": "3634450655207470002", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3414318646334948892", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/d18ad9d7f45a5cc2a467d75f0bdf9d32b9141b14.png", + "ReleaseDate": "1667923560", + "year": "2022" + }, + { + "BSVersion": "1.27.0", + "BSManifest": "3485516174915301618", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3629368753553708497", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/d9ed8771498be7fc83721aaaaa5b15a08fbeadb1.png", + "ReleaseDate": "1670943003", + "year": "2022" + }, + { + "BSVersion": "1.28.0", + "BSManifest": "7463243155526905423", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3666530358620442392", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/8f978bbf292810004bca12523e76be8a7eec8c4e.png", + "ReleaseDate": "1677593822", + "year": "2023" + }, + { + "BSVersion": "1.29.0", + "BSManifest": "3341527958186345367", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105082976092", + "ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/255fd49cd96042b97089f754609be291293e783f.png", + "ReleaseDate": "1680188760", + "year": "2023" + }, + { + "BSVersion": "1.29.1", + "BSManifest": "886973241045584398", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105101272202", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/c328e407367e9914abaf92f609501877ee5abb63.png", + "ReleaseDate": "1680623885", + "year": "2023" + }, + { + "BSVersion": "1.29.4", + "BSManifest": "6291266771922375922", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3708194993422325380", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/9c3d476c03e4a2ac72ca5df016b7c94d242b41cb.png", + "ReleaseDate": "1683644991", + "year": "2023" + }, + { + "BSVersion": "1.30.0", + "BSManifest": "7304715857122259127", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3725085394628677636", + "ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/cd56688ad716b544af14a61b2fbace9088478887.png", + "ReleaseDate": "1684965390", + "year": "2023" + }, + { + "BSVersion": "1.30.2", + "BSManifest": "8042804988990059618", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3655280868312846230", + "ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/6d78e092c54f411b36f2c0577d62eb1b25f91aa3.png", + "ReleaseDate": "1686668364", + "year": "2023" + }, + { + "BSVersion": "1.31.0", + "BSManifest": "676582354127623831", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3674423070127121445", + "ReleaseImg": "https://clan.cloudflare.steamstatic.com/images/32055887/28993aecc56a75c52295fa07620f58cb0e4b09e8.png", + "ReleaseDate": "1688050646", + "year": "2023" + } +] \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 3ee6f93d..0a77a075 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,5 +1,3 @@ -/* eslint global-require: off, no-console: off, promise/always-return: off */ - /** * This module executes inside of electron's main process. You can start * electron renderer process from here and communicate with the other processes From 8a7ffdc26487159fd29a7521acff23d57de75646 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Mon, 10 Jul 2023 01:36:37 +0200 Subject: [PATCH 5/9] [feature] prepare shortcut creation --- src/main/main.ts | 19 ++++++++ src/main/services/bs-launcher.service.ts | 47 +++++++++++++++++++ src/main/services/bs-local-version.service.ts | 6 +-- src/main/services/bsm-protocol.service.ts | 10 +++- src/shared/helpers/object.helpers.ts | 13 +++++ src/shared/helpers/url.helpers.ts | 27 +++++++++++ 6 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/shared/helpers/object.helpers.ts diff --git a/src/main/main.ts b/src/main/main.ts index 0a77a075..02dcf132 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -109,6 +109,25 @@ if (!gotTheLock) { ipcMain.on("log-error", (event, args: IpcRequest) => { log.error(args?.args); }); + + // TODO : remove this + BSLauncherService.getInstance().createLaunchShortcut({ + debug: false, + oculus: true, + desktop: true, + version: { + BSVersion: '1.29.1', + BSManifest: '886973241045584398', + ReleaseURL: 'https://steamcommunity.com/games/620980/announcements/detail/6169409105101272202', + ReleaseImg: 'https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/c328e407367e9914abaf92f609501877ee5abb63.png', + ReleaseDate: '1680623885', + year: '2023', + name: 'Hi Twitter', + ino: 2533274792493431, + color: '#6545ff af' + }, + additionalArgs: [ '--nowait', '-omgargs' ] + }); }).catch(log.error); } diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index 04593b84..71c6d246 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -11,6 +11,8 @@ import { rename } from "fs/promises"; import log from "electron-log"; import { Observable, lastValueFrom, timer } from "rxjs"; import { BsmProtocolService } from "./bsm-protocol.service"; +import { app, shell } from "electron"; +import { objectFromEntries } from "../../shared/helpers/object.helpers"; export class BSLauncherService { private static instance: BSLauncherService; @@ -124,6 +126,8 @@ export class BSLauncherService { // t.searchParams.set("launchOptions", JSON.stringify(launchOptions)); // console.log(t.toString()); + console.log(launchOptions); + return new Observable(obs => {(async () => { if(await this.isBsRunning()){ @@ -174,5 +178,48 @@ export class BSLauncherService { obs.error({type: BSLaunchErrorType.UNKNOWN_ERROR, data: err} as BSLaunchErrorEvent); })}); } + + private launchOptionToShortcutParams(launchOptions: LaunchOption): ShortcutParams{ + const res: ShortcutParams = { version: launchOptions.version.BSVersion }; + + if(launchOptions.version.name){ res.versionName = launchOptions.version.name; } + if(launchOptions.version.steam){ res.versionSteam = `${launchOptions.version.steam}`; } + if(launchOptions.version.oculus){ res.versionOculus = `${launchOptions.version.oculus}`; } + if(launchOptions.version.ino){ res.versionIno = `${launchOptions.version.ino}`; } + + if(launchOptions.oculus){ res.oculusMode = "true"; } + if(launchOptions.desktop){ res.desktopMode = "true"; } + if(launchOptions.debug){ res.debug = "true"; } + if(launchOptions.additionalArgs){ res.additionalArgs = launchOptions.additionalArgs; } + + return res; + } + + public async createLaunchShortcut(launchOptions: LaunchOption): Promise{ + const shortcutParams = this.launchOptionToShortcutParams(launchOptions); + const shortcutUrl = this.bsmProtocolService.buildLink("launch", shortcutParams); + + console.log(objectFromEntries(shortcutUrl.searchParams.entries())); + + // shell.writeShortcutLink(path.join(app.getPath("desktop"), "test.lnk"), "create", { + // target: shortcutUrl.toString(), + // description: "test allo allo", + + // }); + + + } } + +type ShortcutParams = { + oculusMode?: string; + desktopMode?: string; + debug?: string; + additionalArgs?: string[]; + version: string; + versionName?: string; + versionIno?: string; + versionSteam?: string; + versionOculus?: string; +} diff --git a/src/main/services/bs-local-version.service.ts b/src/main/services/bs-local-version.service.ts index 73c687c4..a33b421a 100644 --- a/src/main/services/bs-local-version.service.ts +++ b/src/main/services/bs-local-version.service.ts @@ -221,11 +221,11 @@ export class BSLocalVersionService { if(!rawVersion){ continue; } - const bsVersion = {...await this.remoteVersionService.getVersionDetails(rawVersion.BSVersion)}; + const vertionDetails = await this.remoteVersionService.getVersionDetails(rawVersion.BSVersion); - if(!bsVersion){ continue; } + if(!vertionDetails){ continue; } - bsVersion.name = path.basename(f) !== bsVersion.BSVersion ? path.basename(f) : undefined; + const bsVersion: BSVersion = {...vertionDetails, ...rawVersion}; const customVersion = this.getCustomVersions().find(custom => custom.BSVersion === bsVersion.BSVersion && custom.name === bsVersion.name); diff --git a/src/main/services/bsm-protocol.service.ts b/src/main/services/bsm-protocol.service.ts index ec68f57e..4e2d1c67 100644 --- a/src/main/services/bsm-protocol.service.ts +++ b/src/main/services/bsm-protocol.service.ts @@ -1,7 +1,7 @@ import { Subject, Subscription, filter } from "rxjs"; import { DeepLinkService } from "./deep-link.service"; import { URL } from "url"; -import { isValidUrl } from "../../shared/helpers/url.helpers"; +import { buildUrl, isValidUrl } from "../../shared/helpers/url.helpers"; export class BsmProtocolService { @@ -38,4 +38,12 @@ export class BsmProtocolService { return this.linkeReceived$.pipe(filter(link => link.host === host)).subscribe(listener); } + public buildLink(host: string, params?: Record): URL { + return buildUrl({ + protocol: this.BSM_PROTOCOL, + host, + search: params + }); + } + } \ No newline at end of file diff --git a/src/shared/helpers/object.helpers.ts b/src/shared/helpers/object.helpers.ts new file mode 100644 index 00000000..5b7f272f --- /dev/null +++ b/src/shared/helpers/object.helpers.ts @@ -0,0 +1,13 @@ +export function objectFromEntries(entries: Iterable): Record { + const temp: Record = {}; + + for (const [key, value] of entries) { + if (temp[key]) { + temp[key] = Array.isArray(temp[key]) ? [...(temp[key] as T[]), value] : [(temp[key] as T), value]; + } else { + temp[key] = value; + } + } + + return temp; +} \ No newline at end of file diff --git a/src/shared/helpers/url.helpers.ts b/src/shared/helpers/url.helpers.ts index bdd2ea21..4f1e21a9 100644 --- a/src/shared/helpers/url.helpers.ts +++ b/src/shared/helpers/url.helpers.ts @@ -6,3 +6,30 @@ export function isValidUrl(url: string): boolean { return false; } } + +export function buildUrl({ + protocol = "https", + host = "about:blank", + path = "", + search = {}, + hash = "" + }: { + protocol?: string, + host?: string, + path?: string, + search?: Record, + hash?: string + }): URL { + const url = new URL(`${protocol}://${host}${path}`); + url.hash = hash; + + for (const [key, value] of Object.entries(search)) { + if (Array.isArray(value)) { + value.forEach(v => url.searchParams.append(key, v)); + } else { + url.searchParams.append(key, value); + } + } + + return url; + } From 2f3b5bf610eaa334540142ec5771accceb1f764d Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:18:23 +0200 Subject: [PATCH 6/9] [feature] can now launch beat saber from shortcut --- .erb/configs/webpack.config.renderer.dev.ts | 1 + .erb/configs/webpack.config.renderer.prod.ts | 1 + assets/jsons/translations/de.json | 14 + assets/jsons/translations/en.json | 14 + assets/jsons/translations/es.json | 14 + assets/jsons/translations/fr.json | 14 + package-lock.json | 1512 ++++++++++++++--- package.json | 4 +- release/app/package-lock.json | 316 +++- release/app/package.json | 1 + src/main/constants.ts | 6 + src/main/helpers/env.helpers.ts | 13 + src/main/ipcs/window-manager-ipcs.ts | 8 + src/main/main.ts | 8 +- src/main/services/bs-installer.service.ts | 12 +- src/main/services/bs-launcher.service.ts | 185 +- src/main/services/bs-local-version.service.ts | 101 +- src/main/services/bsm-protocol.service.ts | 10 +- src/main/services/window-manager.service.ts | 12 + .../bs-version-item.component.tsx | 7 +- .../svgs/icons/bs-note-fill.component.tsx | 2 +- .../title-bar/title-bar.component.tsx | 6 +- .../slides/launch/launch-slide.component.tsx | 9 +- src/renderer/index.tsx | 5 + src/renderer/services/bs-launcher.service.ts | 30 +- .../services/window-manager.service.ts | 6 + src/renderer/shortcut-launch.ejs | 14 + src/renderer/windows/ShortcutLaunch.tsx | 115 ++ src/shared/models/bs-launch/index.ts | 2 +- .../models/bs-launch/launch-event.model.ts | 19 +- .../bs-launch/launch-option.interface.ts | 3 +- .../models/window-manager/app-window.model.ts | 2 +- 32 files changed, 2119 insertions(+), 347 deletions(-) create mode 100644 src/main/helpers/env.helpers.ts create mode 100644 src/renderer/shortcut-launch.ejs create mode 100644 src/renderer/windows/ShortcutLaunch.tsx diff --git a/.erb/configs/webpack.config.renderer.dev.ts b/.erb/configs/webpack.config.renderer.dev.ts index 1ae950d2..229cf712 100644 --- a/.erb/configs/webpack.config.renderer.dev.ts +++ b/.erb/configs/webpack.config.renderer.dev.ts @@ -152,6 +152,7 @@ const configuration: webpack.Configuration = { new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map.html")), new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-playlist.html")), new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-model.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("shortcut-launch.html")), ], node: { diff --git a/.erb/configs/webpack.config.renderer.prod.ts b/.erb/configs/webpack.config.renderer.prod.ts index 54a8d33a..df4c6dc2 100644 --- a/.erb/configs/webpack.config.renderer.prod.ts +++ b/.erb/configs/webpack.config.renderer.prod.ts @@ -146,6 +146,7 @@ const configuration: webpack.Configuration = { new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map.html")), new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-playlist.html")), new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-model.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("shortcut-launch.html")), ], }; diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 0577f3e3..be559f32 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -861,6 +861,20 @@ "checking": "Prüfe auf Updates", "downloading": "Lade Updates Herunter" }, + "bs-shortcut-launch": { + "beat-saber-launching": "Start von Beat Saber", + "launching": "Starten", + "open-bsmanager": "BSManager öffnen", + "status-text": { + "init": "Initialisierung...", + "success": { + "BS_LAUNCHING": "Start von Beat Saber...", + "STEAM_LAUNCHING": "Start von Steam...", + "STEAM_LAUNCHED": "Steam erfolgreich gestartet!", + "UNABLE_TO_LAUNCH_STEAM": "Steam kann nicht gestartet werden, erzwungener Start von Beat Saber..." + } + } + }, "dateformat": { "dayNames": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], "monthNames": ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"], diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index ca401da1..db781be8 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -861,6 +861,20 @@ "checking": "Checking for updates", "downloading": "Downloading updates" }, + "bs-shortcut-launch": { + "beat-saber-launching": "Launching Beat Saber", + "launching": "Launching", + "open-bsmanager": "Open BSManager", + "status-text": { + "init": "Initializing...", + "success": { + "BS_LAUNCHING": "Launching Beat Saber...", + "STEAM_LAUNCHING": "Launching Steam...", + "STEAM_LAUNCHED": "Steam successfully launched!", + "UNABLE_TO_LAUNCH_STEAM": "Unable to launch Steam, forcing Beat Saber launch..." + } + } + }, "dateformat": { "dayNames": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], "monthNames": ["Jan", "Feb", "Mar", "Apr", "May", "June", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index ea6d5d04..4c59ad74 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -860,6 +860,20 @@ "checking": "Comprobar las actualizaciones", "downloading": "Descargar actualizaciones" }, + "bs-shortcut-launch": { + "beat-saber-launching": "Iniciando Beat Saber", + "launching": "Iniciando", + "open-bsmanager": "Abrir BSManager", + "status-text": { + "init": "Inicializando...", + "success": { + "BS_LAUNCHING": "Iniciando Beat Saber...", + "STEAM_LAUNCHING": "Iniciando Steam...", + "STEAM_LAUNCHED": "¡Steam iniciado exitosamente!", + "UNABLE_TO_LAUNCH_STEAM": "Incapaz de iniciar Steam, forzando el inicio de Beat Saber..." + } + } + }, "dateformat": { "dayNames": ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"], "monthNames": ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic", "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"], diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index e6ce2882..c2ed1461 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -860,6 +860,20 @@ "checking": "Vérification des mises à jour", "downloading": "Téléchargement des mises à jour" }, + "bs-shortcut-launch": { + "beat-saber-launching": "Lancement de Beat Saber", + "launching": "Lancement", + "open-bsmanager": "Ouvrir BSManager", + "status-text": { + "init": "Initialisation...", + "success": { + "BS_LAUNCHING": "Lancement de Beat Saber...", + "STEAM_LAUNCHING": "Lancement de Steam...", + "STEAM_LAUNCHED": "Steam lancé avec succès !", + "UNABLE_TO_LAUNCH_STEAM": "Impossible de lancer Steam, lancement forcé de Beat Saber..." + } + } + }, "dateformat": { "dayNames": ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"], "monthNames": ["Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Juil", "Aou", "Sept", "Oct", "Nov", "Déc", "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"], diff --git a/package-lock.json b/package-lock.json index 52eb9257..68ce129f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,10 +45,12 @@ "tailwind-scrollbar-hide": "^1.1.7", "tailwindcss-scoped-groups": "^2.0.0", "tippy.js": "^6.3.7", + "to-ico": "^1.1.5", "use-double-click": "^1.0.5", "use-fit-text": "^2.4.0" }, "devDependencies": { + "@electron/rebuild": "^3.2.13", "@pmmmwh/react-refresh-webpack-plugin": "0.5.5", "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", "@testing-library/jest-dom": "^5.16.5", @@ -67,6 +69,7 @@ "@types/react-window": "^1.8.5", "@types/recursive-readdir": "^2.2.1", "@types/terser-webpack-plugin": "^5.0.4", + "@types/to-ico": "^1.1.1", "@types/use-double-click": "^1.0.1", "@types/webpack-bundle-analyzer": "^4.4.2", "@types/webpack-env": "^1.18.0", @@ -85,7 +88,6 @@ "electron-builder": "^24.4.0", "electron-devtools-installer": "^3.2.0", "electron-notarize": "^1.2.1", - "electron-rebuild": "^3.2.9", "electronmon": "^2.0.2", "eslint": "^8.22.0", "eslint-config-airbnb-base": "^15.0.0", @@ -2906,6 +2908,15 @@ "@types/jest": "*" } }, + "node_modules/@types/to-ico": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/to-ico/-/to-ico-1.1.1.tgz", + "integrity": "sha512-WQbdkUB4RjE2rrM9rNqm5OutcxWWZ3J9olDtYA+zDxiPobD7o/RHC+GlGOM5rL1ASP/AoudkDLKw5DbFV3DO8w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/use-double-click": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/use-double-click/-/use-double-click-1.0.1.tgz", @@ -3627,7 +3638,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4023,12 +4033,26 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "optional": true, "engines": { "node": ">=0.8" } @@ -4074,8 +4098,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -4139,6 +4162,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "node_modules/axe-core": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", @@ -4301,6 +4337,14 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -4310,6 +4354,14 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha512-uw4ra6Cv483Op/ebM0GBKKfxZlSmn6NgFRby5L3yGTlunLj53KQgndDlqy2WVFOwgvurocApYkSud0aO+mvrpQ==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -4343,6 +4395,11 @@ "bluebird": "^3.5.5" } }, + "node_modules/bmp-js": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.1.tgz", + "integrity": "sha512-OS74Rlt0Aynu2mTPmY9RZOUOXlqWecFIILFXr70vv16/xCZnFxvri9IKkF1IGxQ8r9dOE62qGNpKxXx8Lko8bg==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -4535,6 +4592,20 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -4555,6 +4626,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4804,6 +4880,11 @@ } ] }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5139,7 +5220,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5927,6 +6007,17 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -6146,7 +6237,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -6407,6 +6497,11 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -6539,6 +6634,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6561,9 +6665,9 @@ } }, "node_modules/electron": { - "version": "24.6.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-24.6.1.tgz", - "integrity": "sha512-SXbRtatpvBqtOwlOiEbaI47Repspz5a/qoaNZ+GB3DLhi9Z0r28t14pu+a9nXgX5tXj8lOfN8D9wmwVYdo4TAg==", + "version": "24.6.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-24.6.2.tgz", + "integrity": "sha512-HC0lE1u9GYfj82Tt60Y/T+ELTdBKlvfPwLCzB6mWsyqYvrb2mtBy5fBSNtlfT0MWZOMh6J2of/AEYaLitniNCQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -6726,49 +6830,6 @@ "node": ">=12" } }, - "node_modules/electron-rebuild": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-3.2.9.tgz", - "integrity": "sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw==", - "deprecated": "Please use @electron/rebuild moving forward. There is no API change, just a package name change", - "dev": true, - "dependencies": { - "@malept/cross-spawn-promise": "^2.0.0", - "chalk": "^4.0.0", - "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "fs-extra": "^10.0.0", - "got": "^11.7.0", - "lzma-native": "^8.0.5", - "node-abi": "^3.0.0", - "node-api-version": "^0.1.4", - "node-gyp": "^9.0.0", - "ora": "^5.1.0", - "semver": "^7.3.5", - "tar": "^6.0.5", - "yargs": "^17.0.1" - }, - "bin": { - "electron-rebuild": "lib/src/cli.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/electron-rebuild/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-store": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.1.0.tgz", @@ -7073,6 +7134,11 @@ "dev": true, "optional": true }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -8500,6 +8566,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -8641,6 +8712,11 @@ } ] }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -8700,8 +8776,7 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -8788,6 +8863,14 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -8927,6 +9010,14 @@ "is-callable": "^1.1.3" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -9177,6 +9268,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9213,6 +9312,15 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -9362,6 +9470,27 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -9718,6 +9847,20 @@ } } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -9886,6 +10029,17 @@ "node": ">= 4" } }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -10193,6 +10347,11 @@ "node": ">=8" } }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, "node_modules/is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -10448,8 +10607,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -10541,6 +10699,11 @@ "node": ">=0.10.0" } }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -11974,6 +12137,67 @@ "node": ">=10" } }, + "node_modules/jimp": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz", + "integrity": "sha512-9HT7DA279xkTlry2oG30s6AtOUglNiY2UdyYpj0yNI4/NBv8PmdNC0gcldgMU4HqvbUlrM3+v+6GaHnTkH23JQ==", + "dependencies": { + "bignumber.js": "^2.1.0", + "bmp-js": "0.0.3", + "es6-promise": "^3.0.2", + "exif-parser": "^0.1.9", + "file-type": "^3.1.0", + "jpeg-js": "^0.2.0", + "load-bmfont": "^1.2.3", + "mime": "^1.3.4", + "mkdirp": "0.5.1", + "pixelmatch": "^4.0.0", + "pngjs": "^3.0.0", + "read-chunk": "^1.0.1", + "request": "^2.65.0", + "stream-to-buffer": "^0.1.0", + "tinycolor2": "^1.1.2", + "url-regex": "^3.0.0" + } + }, + "node_modules/jimp/node_modules/bmp-js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.3.tgz", + "integrity": "sha512-epsm3Z92j5xwek9p97pVw3KbsNc0F4QnbYh+N93SpbJYuHFQQ/UAh6K+bKFGyLePH3Hudtl/Sa95Quqp0gX8IQ==" + }, + "node_modules/jimp/node_modules/jpeg-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", + "integrity": "sha512-Ni9PffhJtYtdD7VwxH6V2MnievekGfUefosGCHadog0/jAevRu6HPjYeMHbUemn0IPE8d4wGa8UsOGsX+iKy2g==" + }, + "node_modules/jimp/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jimp/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==" + }, + "node_modules/jimp/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/jiti": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", @@ -11982,6 +12206,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jpeg-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.1.2.tgz", + "integrity": "sha512-CiRVjMKBUp6VYtGwzRjrdnro41yMwKGOWdP9ATXqmixdz2n7KHNwdqthTYSSbOKj/Ha79Gct1sA8ZQpse55TYA==" + }, "node_modules/js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -12004,6 +12233,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, "node_modules/jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -12110,11 +12344,15 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-schema-typed": { "version": "7.0.3", @@ -12130,9 +12368,7 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "optional": true + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "node_modules/json5": { "version": "2.2.3", @@ -12157,6 +12393,46 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jsprim/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/jsprim/node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/jsprim/node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -12446,6 +12722,40 @@ "node": ">=8" } }, + "node_modules/load-bmfont": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/load-bmfont/node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/load-bmfont/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -12659,24 +12969,6 @@ "lz-string": "bin/bin.js" } }, - "node_modules/lzma-native": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz", - "integrity": "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^3.1.0", - "node-gyp-build": "^4.2.1", - "readable-stream": "^3.6.0" - }, - "bin": { - "lzmajs": "bin/lzmajs" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -12884,7 +13176,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -12893,7 +13184,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -12917,6 +13207,14 @@ "node": ">=4" } }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -13235,12 +13533,6 @@ "node": ">=10" } }, - "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, "node_modules/node-api-version": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.1.4.tgz", @@ -13330,17 +13622,6 @@ "node": "^12.22 || ^14.13 || >=16" } }, - "node_modules/node-gyp-build": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", - "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -13453,6 +13734,14 @@ "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", "dev": true }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -13868,6 +14157,30 @@ "node": ">=6" } }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -13886,6 +14199,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-png": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-1.1.2.tgz", + "integrity": "sha512-Ge6gDV9T5zhkWHmjvnNiyhPTCIoY7W+FC7qWPtuL2lIGZAFxxqTRG/ouEXsH9qkw+HzYiPEU/tFcxOCEDTP1Yw==", + "dependencies": { + "pngjs": "^3.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -13962,6 +14286,16 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -13990,6 +14324,25 @@ "node": ">=0.10" } }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -13998,6 +14351,17 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -14080,6 +14444,14 @@ "node": ">=6" } }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", @@ -14782,6 +15154,14 @@ "node": ">= 0.8" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -14990,8 +15370,7 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/public-ip": { "version": "4.0.4", @@ -15467,6 +15846,14 @@ "node": ">=0.10.0" } }, + "node_modules/read-chunk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz", + "integrity": "sha512-5NLTTdX45dKFtG8CX5pKmvS9V5u9wBE+gkklN7xhDuhq3pA2I4O7ALfKxosCMcLHOhkxj6GNacZhfXtp5nlCdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -15660,6 +16047,70 @@ "strip-ansi": "^6.0.1" } }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -15683,6 +16134,34 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/resize-img": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resize-img/-/resize-img-1.1.2.tgz", + "integrity": "sha512-/4nKUmuNPuM6gYTWad136ica81baOVjpesgv8FGaIvP0KWcbCWahOWBKaM4tFoM+aVcSA+qQDg28pcnIzFRpJw==", + "dependencies": { + "bmp-js": "0.0.1", + "file-type": "^3.8.0", + "get-stream": "^2.0.0", + "jimp": "^0.2.21", + "jpeg-js": "^0.1.1", + "parse-png": "^1.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resize-img/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -15892,8 +16371,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sanitize-filename": { "version": "1.6.3", @@ -16547,6 +17025,30 @@ "dev": true, "optional": true }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ssri": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", @@ -16616,6 +17118,25 @@ "resolved": "https://registry.npmjs.org/stream-slicer/-/stream-slicer-0.0.6.tgz", "integrity": "sha512-QsY0LbweYE5L+e+iBQgtkM5WUIf7+kCMA/m2VULv8rEEDDnlDPsPvOHH4nli6uaZOKQEt64u65h0l/eeZo7lCw==" }, + "node_modules/stream-to": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz", + "integrity": "sha512-Kg1BSDTwgGiVMtTCJNlo7kk/xzL33ZuZveEBRt6rXw+f1WLK/8kmz2NVCT/Qnv0JkV85JOHcLhD82mnXsR3kPw==", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/stream-to-buffer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz", + "integrity": "sha512-Da4WoKaZyu3nf+bIdIifh7IPkFjARBnBK+pYqn0EUJqksjV9afojjaCCHUemH30Jmu7T2qcKvlZm2ykN38uzaw==", + "dependencies": { + "stream-to": "~0.2.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -17244,6 +17765,11 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -17288,6 +17814,21 @@ "node": ">=4" } }, + "node_modules/to-ico": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/to-ico/-/to-ico-1.1.5.tgz", + "integrity": "sha512-5kIh7m7bkIlqIESEZkL8gAMMzucXKfPe3hX2FoDY5HEAfD9OJU+Qh9b6Enp74w0qRcxVT5ejss66PHKqc3AVkg==", + "dependencies": { + "arrify": "^1.0.1", + "buffer-alloc": "^1.1.0", + "image-size": "^0.5.0", + "parse-png": "^1.0.0", + "resize-img": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -17604,6 +18145,22 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -17863,6 +18420,25 @@ "node": ">=4" } }, + "node_modules/url-regex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", + "integrity": "sha512-dQ9cJzMou5OKr6ZzfvwJkCq3rC72PNXhqz0v3EIhF4a3Np+ujr100AhUx2cKx5ei3iymoJpJrPB3sVSEMdqAeg==", + "dependencies": { + "ip-regex": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/url-regex/node_modules/ip-regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz", + "integrity": "sha512-HjpCHTuxbR/6jWJroc/VN+npo5j0T4Vv2TAI5qdEHQx7hsL767MeccGFSsLtF694EiZKTSEqgoeU6DtGFCcuqQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/use-double-click": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/use-double-click/-/use-double-click-1.0.5.tgz", @@ -17912,6 +18488,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -18647,12 +19232,48 @@ } } }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -21066,6 +21687,15 @@ "@types/jest": "*" } }, + "@types/to-ico": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/to-ico/-/to-ico-1.1.1.tgz", + "integrity": "sha512-WQbdkUB4RjE2rrM9rNqm5OutcxWWZ3J9olDtYA+zDxiPobD7o/RHC+GlGOM5rL1ASP/AoudkDLKw5DbFV3DO8w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/use-double-click": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/use-double-click/-/use-double-click-1.0.1.tgz", @@ -21605,7 +22235,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -21922,12 +22551,23 @@ "get-intrinsic": "^1.1.3" } }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==" + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "optional": true + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "ast-metadata-inferer": { "version": "0.7.0", @@ -21964,8 +22604,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "at-least-node": { "version": "1.0.0", @@ -21998,6 +22637,16 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" + }, + "aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "axe-core": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", @@ -22121,12 +22770,25 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "requires": { + "tweetnacl": "^0.14.3" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha512-uw4ra6Cv483Op/ebM0GBKKfxZlSmn6NgFRby5L3yGTlunLj53KQgndDlqy2WVFOwgvurocApYkSud0aO+mvrpQ==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -22157,6 +22819,11 @@ "bluebird": "^3.5.5" } }, + "bmp-js": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.1.tgz", + "integrity": "sha512-OS74Rlt0Aynu2mTPmY9RZOUOXlqWecFIILFXr70vv16/xCZnFxvri9IKkF1IGxQ8r9dOE62qGNpKxXx8Lko8bg==" + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -22303,6 +22970,20 @@ "ieee754": "^1.1.13" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -22314,6 +22995,11 @@ "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", "dev": true }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -22511,6 +23197,11 @@ "integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -22754,7 +23445,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -23324,6 +24014,14 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "requires": { + "assert-plus": "^1.0.0" + } + }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -23480,8 +24178,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "delegates": { "version": "1.0.0", @@ -23683,6 +24380,11 @@ "entities": "^2.0.0" } }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -23781,6 +24483,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -23797,9 +24508,9 @@ } }, "electron": { - "version": "24.6.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-24.6.1.tgz", - "integrity": "sha512-SXbRtatpvBqtOwlOiEbaI47Repspz5a/qoaNZ+GB3DLhi9Z0r28t14pu+a9nXgX5tXj8lOfN8D9wmwVYdo4TAg==", + "version": "24.6.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-24.6.2.tgz", + "integrity": "sha512-HC0lE1u9GYfj82Tt60Y/T+ELTdBKlvfPwLCzB6mWsyqYvrb2mtBy5fBSNtlfT0MWZOMh6J2of/AEYaLitniNCQ==", "dev": true, "requires": { "@electron/get": "^2.0.0", @@ -23946,41 +24657,6 @@ } } }, - "electron-rebuild": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-3.2.9.tgz", - "integrity": "sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw==", - "dev": true, - "requires": { - "@malept/cross-spawn-promise": "^2.0.0", - "chalk": "^4.0.0", - "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "fs-extra": "^10.0.0", - "got": "^11.7.0", - "lzma-native": "^8.0.5", - "node-abi": "^3.0.0", - "node-api-version": "^0.1.4", - "node-gyp": "^9.0.0", - "ora": "^5.1.0", - "semver": "^7.3.5", - "tar": "^6.0.5", - "yargs": "^17.0.1" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - } - } - }, "electron-store": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.1.0.tgz", @@ -24228,6 +24904,11 @@ "dev": true, "optional": true }, + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -25217,6 +25898,11 @@ } } }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -25333,6 +26019,11 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -25378,8 +26069,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -25447,6 +26137,11 @@ "schema-utils": "^3.0.0" } }, + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==" + }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -25561,6 +26256,11 @@ "is-callable": "^1.1.3" } }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -25732,6 +26432,14 @@ "get-intrinsic": "^1.1.1" } }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -25759,6 +26467,15 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -25868,6 +26585,20 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -26142,6 +26873,16 @@ "micromatch": "^4.0.2" } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -26260,6 +27001,11 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==" + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -26479,6 +27225,11 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, "is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -26647,8 +27398,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "is-unicode-supported": { "version": "0.1.0", @@ -26713,6 +27463,11 @@ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, "istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -27921,11 +28676,69 @@ } } }, + "jimp": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz", + "integrity": "sha512-9HT7DA279xkTlry2oG30s6AtOUglNiY2UdyYpj0yNI4/NBv8PmdNC0gcldgMU4HqvbUlrM3+v+6GaHnTkH23JQ==", + "requires": { + "bignumber.js": "^2.1.0", + "bmp-js": "0.0.3", + "es6-promise": "^3.0.2", + "exif-parser": "^0.1.9", + "file-type": "^3.1.0", + "jpeg-js": "^0.2.0", + "load-bmfont": "^1.2.3", + "mime": "^1.3.4", + "mkdirp": "0.5.1", + "pixelmatch": "^4.0.0", + "pngjs": "^3.0.0", + "read-chunk": "^1.0.1", + "request": "^2.65.0", + "stream-to-buffer": "^0.1.0", + "tinycolor2": "^1.1.2", + "url-regex": "^3.0.0" + }, + "dependencies": { + "bmp-js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.3.tgz", + "integrity": "sha512-epsm3Z92j5xwek9p97pVw3KbsNc0F4QnbYh+N93SpbJYuHFQQ/UAh6K+bKFGyLePH3Hudtl/Sa95Quqp0gX8IQ==" + }, + "jpeg-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", + "integrity": "sha512-Ni9PffhJtYtdD7VwxH6V2MnievekGfUefosGCHadog0/jAevRu6HPjYeMHbUemn0IPE8d4wGa8UsOGsX+iKy2g==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "requires": { + "minimist": "0.0.8" + } + } + } + }, "jiti": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==" }, + "jpeg-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.1.2.tgz", + "integrity": "sha512-CiRVjMKBUp6VYtGwzRjrdnro41yMwKGOWdP9ATXqmixdz2n7KHNwdqthTYSSbOKj/Ha79Gct1sA8ZQpse55TYA==" + }, "js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -27945,6 +28758,11 @@ "argparse": "^2.0.1" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, "jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -28027,11 +28845,15 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-schema-typed": { "version": "7.0.3", @@ -28047,9 +28869,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "optional": true + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "json5": { "version": "2.2.3", @@ -28066,6 +28886,39 @@ "universalify": "^2.0.0" } }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } + }, "jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -28298,6 +29151,33 @@ } } }, + "load-bmfont": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "requires": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + } + } + }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -28471,17 +29351,6 @@ "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", "dev": true }, - "lzma-native": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz", - "integrity": "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==", - "dev": true, - "requires": { - "node-addon-api": "^3.1.0", - "node-gyp-build": "^4.2.1", - "readable-stream": "^3.6.0" - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -28643,14 +29512,12 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "requires": { "mime-db": "1.52.0" } @@ -28665,6 +29532,14 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "requires": { + "dom-walk": "^0.1.0" + } + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -28899,12 +29774,6 @@ "semver": "^7.3.5" } }, - "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, "node-api-version": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.1.4.tgz", @@ -28976,12 +29845,6 @@ "which": "^2.0.2" } }, - "node-gyp-build": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", - "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", - "dev": true - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -29060,6 +29923,11 @@ "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", "dev": true }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -29353,6 +30221,30 @@ "callsites": "^3.0.0" } }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "requires": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -29365,6 +30257,14 @@ "lines-and-columns": "^1.1.6" } }, + "parse-png": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-1.1.2.tgz", + "integrity": "sha512-Ge6gDV9T5zhkWHmjvnNiyhPTCIoY7W+FC7qWPtuL2lIGZAFxxqTRG/ouEXsH9qkw+HzYiPEU/tFcxOCEDTP1Yw==", + "requires": { + "pngjs": "^3.2.0" + } + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -29426,6 +30326,16 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -29442,11 +30352,32 @@ "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==", "dev": true }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "requires": { + "pinkie": "^2.0.0" + } + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "requires": { + "pngjs": "^3.0.0" + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -29510,6 +30441,11 @@ "xmlbuilder": "^15.1.1" } }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, "postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", @@ -29935,6 +30871,11 @@ "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -30096,8 +31037,7 @@ "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "public-ip": { "version": "4.0.4", @@ -30449,6 +31389,11 @@ } } }, + "read-chunk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz", + "integrity": "sha512-5NLTTdX45dKFtG8CX5pKmvS9V5u9wBE+gkklN7xhDuhq3pA2I4O7ALfKxosCMcLHOhkxj6GNacZhfXtp5nlCdg==" + }, "read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -30610,6 +31555,59 @@ "strip-ansi": "^6.0.1" } }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -30627,6 +31625,30 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "resize-img": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resize-img/-/resize-img-1.1.2.tgz", + "integrity": "sha512-/4nKUmuNPuM6gYTWad136ica81baOVjpesgv8FGaIvP0KWcbCWahOWBKaM4tFoM+aVcSA+qQDg28pcnIzFRpJw==", + "requires": { + "bmp-js": "0.0.1", + "file-type": "^3.8.0", + "get-stream": "^2.0.0", + "jimp": "^0.2.21", + "jpeg-js": "^0.1.1", + "parse-png": "^1.1.1" + }, + "dependencies": { + "get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + } + } + }, "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -30779,8 +31801,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-filename": { "version": "1.6.3", @@ -31292,6 +32313,22 @@ "dev": true, "optional": true }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, "ssri": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", @@ -31347,6 +32384,19 @@ "resolved": "https://registry.npmjs.org/stream-slicer/-/stream-slicer-0.0.6.tgz", "integrity": "sha512-QsY0LbweYE5L+e+iBQgtkM5WUIf7+kCMA/m2VULv8rEEDDnlDPsPvOHH4nli6uaZOKQEt64u65h0l/eeZo7lCw==" }, + "stream-to": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz", + "integrity": "sha512-Kg1BSDTwgGiVMtTCJNlo7kk/xzL33ZuZveEBRt6rXw+f1WLK/8kmz2NVCT/Qnv0JkV85JOHcLhD82mnXsR3kPw==" + }, + "stream-to-buffer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz", + "integrity": "sha512-Da4WoKaZyu3nf+bIdIifh7IPkFjARBnBK+pYqn0EUJqksjV9afojjaCCHUemH30Jmu7T2qcKvlZm2ykN38uzaw==", + "requires": { + "stream-to": "~0.2.0" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -31813,6 +32863,11 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -31851,6 +32906,18 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, + "to-ico": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/to-ico/-/to-ico-1.1.5.tgz", + "integrity": "sha512-5kIh7m7bkIlqIESEZkL8gAMMzucXKfPe3hX2FoDY5HEAfD9OJU+Qh9b6Enp74w0qRcxVT5ejss66PHKqc3AVkg==", + "requires": { + "arrify": "^1.0.1", + "buffer-alloc": "^1.1.0", + "image-size": "^0.5.0", + "parse-png": "^1.0.0", + "resize-img": "^1.1.0" + } + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -32076,6 +33143,19 @@ } } }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -32258,6 +33338,21 @@ "prepend-http": "^2.0.0" } }, + "url-regex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", + "integrity": "sha512-dQ9cJzMou5OKr6ZzfvwJkCq3rC72PNXhqz0v3EIhF4a3Np+ujr100AhUx2cKx5ei3iymoJpJrPB3sVSEMdqAeg==", + "requires": { + "ip-regex": "^1.0.1" + }, + "dependencies": { + "ip-regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz", + "integrity": "sha512-HjpCHTuxbR/6jWJroc/VN+npo5j0T4Vv2TAI5qdEHQx7hsL767MeccGFSsLtF694EiZKTSEqgoeU6DtGFCcuqQ==" + } + } + }, "use-double-click": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/use-double-click/-/use-double-click-1.0.5.tgz", @@ -32294,6 +33389,11 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -32829,12 +33929,44 @@ "dev": true, "requires": {} }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "dependencies": { + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } + }, "xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/package.json b/package.json index 94ad5323..833e7ae8 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ ] }, "devDependencies": { + "@electron/rebuild": "^3.2.13", "@pmmmwh/react-refresh-webpack-plugin": "0.5.5", "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", "@testing-library/jest-dom": "^5.16.5", @@ -164,6 +165,7 @@ "@types/react-window": "^1.8.5", "@types/recursive-readdir": "^2.2.1", "@types/terser-webpack-plugin": "^5.0.4", + "@types/to-ico": "^1.1.1", "@types/use-double-click": "^1.0.1", "@types/webpack-bundle-analyzer": "^4.4.2", "@types/webpack-env": "^1.18.0", @@ -182,7 +184,6 @@ "electron-builder": "^24.4.0", "electron-devtools-installer": "^3.2.0", "electron-notarize": "^1.2.1", - "electron-rebuild": "^3.2.9", "electronmon": "^2.0.2", "eslint": "^8.22.0", "eslint-config-airbnb-base": "^15.0.0", @@ -266,6 +267,7 @@ "tailwind-scrollbar-hide": "^1.1.7", "tailwindcss-scoped-groups": "^2.0.0", "tippy.js": "^6.3.7", + "to-ico": "^1.1.5", "use-double-click": "^1.0.5", "use-fit-text": "^2.4.0" }, diff --git a/release/app/package-lock.json b/release/app/package-lock.json index dc08246c..0f68e5de 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -10,10 +10,213 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@resvg/resvg-js": "^2.4.1", "ctrlc-windows": "^2.1.0", "ps-list": "^7.2.0" } }, + "node_modules/@resvg/resvg-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz", + "integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.4.1", + "@resvg/resvg-js-android-arm64": "2.4.1", + "@resvg/resvg-js-darwin-arm64": "2.4.1", + "@resvg/resvg-js-darwin-x64": "2.4.1", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1", + "@resvg/resvg-js-linux-arm64-gnu": "2.4.1", + "@resvg/resvg-js-linux-arm64-musl": "2.4.1", + "@resvg/resvg-js-linux-x64-gnu": "2.4.1", + "@resvg/resvg-js-linux-x64-musl": "2.4.1", + "@resvg/resvg-js-win32-arm64-msvc": "2.4.1", + "@resvg/resvg-js-win32-ia32-msvc": "2.4.1", + "@resvg/resvg-js-win32-x64-msvc": "2.4.1" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz", + "integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz", + "integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz", + "integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz", + "integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz", + "integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz", + "integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz", + "integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz", + "integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz", + "integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/ctrlc-windows": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz", @@ -31,16 +234,107 @@ } } }, - "dependencies": { - "ctrlc-windows": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz", - "integrity": "sha512-OrX5KI+K+2NMN91QIhYZdW7VDO2YsSdTZW494pA7Nvw/wBdU2hz+MGP006bR978zOTrG6Q8EIeJvLJmLqc6MsQ==" - }, - "ps-list": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", - "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==" - } + "dependencies": { + "@resvg/resvg-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz", + "integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==", + "requires": { + "@resvg/resvg-js-android-arm-eabi": "2.4.1", + "@resvg/resvg-js-android-arm64": "2.4.1", + "@resvg/resvg-js-darwin-arm64": "2.4.1", + "@resvg/resvg-js-darwin-x64": "2.4.1", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1", + "@resvg/resvg-js-linux-arm64-gnu": "2.4.1", + "@resvg/resvg-js-linux-arm64-musl": "2.4.1", + "@resvg/resvg-js-linux-x64-gnu": "2.4.1", + "@resvg/resvg-js-linux-x64-musl": "2.4.1", + "@resvg/resvg-js-win32-arm64-msvc": "2.4.1", + "@resvg/resvg-js-win32-ia32-msvc": "2.4.1", + "@resvg/resvg-js-win32-x64-msvc": "2.4.1" + } + }, + "@resvg/resvg-js-android-arm-eabi": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz", + "integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==", + "optional": true + }, + "@resvg/resvg-js-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz", + "integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==", + "optional": true + }, + "@resvg/resvg-js-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==", + "optional": true + }, + "@resvg/resvg-js-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz", + "integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==", + "optional": true + }, + "@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz", + "integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==", + "optional": true + }, + "@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz", + "integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==", + "optional": true + }, + "@resvg/resvg-js-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==", + "optional": true + }, + "@resvg/resvg-js-linux-x64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz", + "integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==", + "optional": true + }, + "@resvg/resvg-js-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==", + "optional": true + }, + "@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz", + "integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==", + "optional": true + }, + "@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz", + "integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==", + "optional": true + }, + "@resvg/resvg-js-win32-x64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz", + "integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==", + "optional": true + }, + "ctrlc-windows": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz", + "integrity": "sha512-OrX5KI+K+2NMN91QIhYZdW7VDO2YsSdTZW494pA7Nvw/wBdU2hz+MGP006bR978zOTrG6Q8EIeJvLJmLqc6MsQ==" + }, + "ps-list": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz", + "integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ==" } + } } diff --git a/release/app/package.json b/release/app/package.json index c63ec728..8cbd8872 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -14,6 +14,7 @@ "postinstall": "npm run electron-rebuild && npm run link-modules" }, "dependencies": { + "@resvg/resvg-js": "^2.4.1", "ctrlc-windows": "^2.1.0", "ps-list": "^7.2.0" }, diff --git a/src/main/constants.ts b/src/main/constants.ts index 4d345b19..83b22a7c 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -1,3 +1,6 @@ +import { app } from "electron"; +import path from "path"; + export const BS_EXECUTABLE = "Beat Saber.exe"; export const OCULUS_BS_DIR = "hyperbolic-magnetism-beat-saber"; export const BS_APP_ID = "620980"; @@ -5,3 +8,6 @@ export const BS_DEPOT = "620981"; export const APP_NAME = "BSManager"; export const STEAMVR_APP_ID = "250820"; + +export const IMAGE_CACHE_FOLDER = "imagescache"; +export const IMAGE_CACHE_PATH = path.join(path.dirname(app.getPath("exe")), IMAGE_CACHE_FOLDER); diff --git a/src/main/helpers/env.helpers.ts b/src/main/helpers/env.helpers.ts new file mode 100644 index 00000000..8eaffea7 --- /dev/null +++ b/src/main/helpers/env.helpers.ts @@ -0,0 +1,13 @@ +import { ProviderPlatform } from "shared/models/provider-platform.enum"; + +export function execOnOs(executions: { [key in ProviderPlatform]?: () => T }, noError = false): T { + if(executions[process.platform as ProviderPlatform]) { + return executions[process.platform as ProviderPlatform](); + } + + if(!noError) { + throw new Error(`No execution found for platform ${process.platform}`); + } + + return undefined; +} \ No newline at end of file diff --git a/src/main/ipcs/window-manager-ipcs.ts b/src/main/ipcs/window-manager-ipcs.ts index eb12c047..64f1a806 100644 --- a/src/main/ipcs/window-manager-ipcs.ts +++ b/src/main/ipcs/window-manager-ipcs.ts @@ -3,8 +3,11 @@ import { WindowManagerService } from "../services/window-manager.service"; import { IpcRequest } from "shared/models/ipc"; import { AppWindow } from "shared/models/window-manager/app-window.model"; import { BSLauncherService } from "../services/bs-launcher.service"; +import { IpcService } from "../services/ipc.service"; +import { from } from "rxjs"; const launcher = BSLauncherService.getInstance(); +const ipc = IpcService.getInstance(); ipcMain.on("open-window-then-close-all", async (event, request: IpcRequest) => { const windowManager = WindowManagerService.getInstance(); @@ -25,3 +28,8 @@ ipcMain.on("close-windows", async (event, request: IpcRequest) => { const windowManager = WindowManagerService.getInstance(); windowManager.close(...request.args); }); + +ipc.on("open-window-or-focus", (req, reply) => { + const windowManager = WindowManagerService.getInstance(); + reply(from(windowManager.openWindowOrFocus(req.args))); +}); diff --git a/src/main/main.ts b/src/main/main.ts index 02dcf132..761f8a0c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -88,7 +88,11 @@ if (!gotTheLock) { initServicesMustBeInitialized(); - // DeepLinkService.getInstance().dispatchLinkOpened("bsmanager://launch/?launchOptions=%7B%22debug%22%3Afalse%2C%22oculus%22%3Atrue%2C%22desktop%22%3Atrue%2C%22version%22%3A%7B%22BSVersion%22%3A%221.29.0%22%2C%22BSManifest%22%3A%223341527958186345367%22%2C%22ReleaseURL%22%3A%22https%3A%2F%2Fsteamcommunity.com%2Fgames%2F620980%2Fannouncements%2Fdetail%2F6169409105082976092%22%2C%22ReleaseImg%22%3A%22https%3A%2F%2Fcdn.cloudflare.steamstatic.com%2Fsteamcommunity%2Fpublic%2Fimages%2Fclans%2F%2F32055887%2F255fd49cd96042b97089f754609be291293e783f.png%22%2C%22ReleaseDate%22%3A%221680188760%22%2C%22year%22%3A%222023%22%2C%22name%22%3A%221.29.0+%281%29%22%7D%7D"); + // TODO : to remove + setTimeout(() => { + // DeepLinkService.getInstance().dispatchLinkOpened("bsmanager://launch?version=1.29.1&versionName=Hi+Twitter&versionIno=2533274792493431&oculusMode=true&desktopMode=true&additionalArgs=--nowait&additionalArgs=-omgargs"); + }, 3000); + const deepLink = process.argv.find(arg => DeepLinkService.getInstance().isDeepLink(arg)); @@ -124,7 +128,7 @@ if (!gotTheLock) { year: '2023', name: 'Hi Twitter', ino: 2533274792493431, - color: '#6545ff af' + color: '#6545ff' }, additionalArgs: [ '--nowait', '-omgargs' ] }); diff --git a/src/main/services/bs-installer.service.ts b/src/main/services/bs-installer.service.ts index 2d2a58d4..1ab5e588 100644 --- a/src/main/services/bs-installer.service.ts +++ b/src/main/services/bs-installer.service.ts @@ -1,6 +1,6 @@ import { BS_APP_ID, BS_DEPOT } from "../constants"; import path from "path"; -import { BSVersion, PartialBSVersion } from "shared/bs-version.interface"; +import { BSVersion } from "shared/bs-version.interface"; import { UtilsService } from "./utils.service"; import { ChildProcessWithoutNullStreams, spawn, spawnSync } from "child_process"; import log from "electron-log"; @@ -194,18 +194,18 @@ export class BSInstallerService { return destPath; } - public async importVersion(path: string): Promise { - const rawBsVersion = await this.localVersionService.getVersionOfBSFolder(path); + public async importVersion(path: string): Promise { + const version = await this.localVersionService.getVersionOfBSFolder(path); - if (!rawBsVersion) { + if (!version) { throw new Error("NOT_BS_FOLDER"); } - const destPath = await this.getPathNotAleardyExist(await this.localVersionService.getVersionPath(rawBsVersion)); + const destPath = await this.getPathNotAleardyExist(await this.localVersionService.getVersionPath(version)); await copy(path, destPath, { dereference: true }); - return rawBsVersion; + return version; } } diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index 71c6d246..cea0f096 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -1,7 +1,7 @@ import path from "path"; -import { LaunchOption, BSLaunchEvent, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEventType } from "../../shared/models/bs-launch"; +import { LaunchOption, BSLaunchEvent, BSLaunchEventData, BSLaunchWarning, BSLaunchErrorData, BSLaunchError } from "../../shared/models/bs-launch"; import { UtilsService } from "./utils.service"; -import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID } from "../constants"; +import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID, IMAGE_CACHE_PATH } from "../constants"; import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process"; import { SteamService } from "./steam.service"; import { BSLocalVersionService } from "./bs-local-version.service"; @@ -9,10 +9,17 @@ import { OculusService } from "./oculus.service"; import { pathExist } from "../helpers/fs.helpers"; import { rename } from "fs/promises"; import log from "electron-log"; -import { Observable, lastValueFrom, timer } from "rxjs"; +import { Observable, lastValueFrom, of, timer } from "rxjs"; import { BsmProtocolService } from "./bsm-protocol.service"; -import { app, shell } from "electron"; +import { app} from "electron"; +import { Resvg } from "@resvg/resvg-js"; +import Color from "color"; +import { ensureDir, writeFile } from "fs-extra"; +import toIco from "to-ico"; import { objectFromEntries } from "../../shared/helpers/object.helpers"; +import { WindowManagerService } from "./window-manager.service"; +import { IpcService } from "./ipc.service"; +import { BSVersionLibService } from "./bs-version-lib.service"; export class BSLauncherService { private static instance: BSLauncherService; @@ -22,8 +29,9 @@ export class BSLauncherService { private readonly oculusService: OculusService; private readonly localVersionService: BSLocalVersionService; private readonly bsmProtocolService: BsmProtocolService; - - private bsProcess: ChildProcessWithoutNullStreams; + private readonly windows: WindowManagerService; + private readonly ipc: IpcService; + private readonly remoteVersion: BSVersionLibService; public static getInstance(): BSLauncherService { if (!BSLauncherService.instance) { @@ -38,11 +46,14 @@ export class BSLauncherService { this.oculusService = OculusService.getInstance(); this.localVersionService = BSLocalVersionService.getInstance(); this.bsmProtocolService = BsmProtocolService.getInstance(); + this.windows = WindowManagerService.getInstance(); + this.ipc = IpcService.getInstance(); + this.remoteVersion = BSVersionLibService.getInstance(); this.bsmProtocolService.on("launch", link => { log.info("Launch from bsm protocol", link.toString()); - if(!link.searchParams.has("launchOptions")){ return; } - this.launch(JSON.parse(link.searchParams.get("launchOptions"))).subscribe(); + const shortcutParams = objectFromEntries(link.searchParams.entries()) as ShortcutParams; + this.openShortcutLaunchWindow(shortcutParams); }); } @@ -93,11 +104,7 @@ export class BSLauncherService { return Array.from(new Set(launchArgs).values()); } - private launchBSProcess(bsExePath: string, args: string[], debug = false): Promise{ - - if(this.bsProcess?.connected){ - return Promise.reject("Beat Saber process already running"); - } + private launchBSProcess(bsExePath: string, args: string[], debug = false): ChildProcessWithoutNullStreams{ const spawnOptions: SpawnOptionsWithoutStdio = { shell: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} }; @@ -106,54 +113,44 @@ export class BSLauncherService { spawnOptions.windowsVerbatimArguments = true; } - this.bsProcess = spawn(`\"${bsExePath}\"`, args, spawnOptions); - - return new Promise((resolve, reject) => { - this.bsProcess.on('error', e => { log.error(e); reject(e); }); - this.bsProcess.once('exit', code => { - if(code !== 0){ - log.error(`Beat Saber process exited with code ${code}`); - } - resolve(); - }); - }); + return spawn(`\"${bsExePath}\"`, args, spawnOptions); } - public launch(launchOptions: LaunchOption): Observable{ + public launch(launchOptions: LaunchOption): Observable{ - // const t = new URL("bsmanager://launch"); - // t.searchParams.set("launchOptions", JSON.stringify(launchOptions)); - // console.log(t.toString()); + return new Observable(obs => {(async () => { - console.log(launchOptions); - - return new Observable(obs => {(async () => { - - if(await this.isBsRunning()){ - return obs.error({type: BSLaunchErrorType.BS_ALREADY_RUNNING} as BSLaunchErrorEvent); + if(launchOptions.skipAlreadyRunning !== true && await this.isBsRunning()){ + return obs.error({type: BSLaunchError.BS_ALREADY_RUNNING} as BSLaunchErrorData); } if(launchOptions.version.oculus && (await this.oculusService.oculusRunning())){ - return obs.error({type: BSLaunchErrorType.OCULUS_NOT_RUNNING} as BSLaunchErrorEvent); + return obs.error({type: BSLaunchError.OCULUS_NOT_RUNNING} as BSLaunchErrorData); } const bsFolderPath = await this.localVersionService.getInstalledVersionPath(launchOptions.version); const exePath = path.join(bsFolderPath, BS_EXECUTABLE); if(!(await pathExist(exePath))){ - return obs.error({type: BSLaunchErrorType.BS_NOT_FOUND} as BSLaunchErrorEvent); + return obs.error({type: BSLaunchError.BS_NOT_FOUND} as BSLaunchErrorData); } // Open Steam if not running if(!launchOptions.version.oculus && !(await this.steamService.steamRunning())){ - obs.next({type: BSLaunchEventType.STEAM_LAUNCHING}); - await this.steamService.openSteam().catch(log.error); + obs.next({type: BSLaunchEvent.STEAM_LAUNCHING}); + + await this.steamService.openSteam().then(() => { + obs.next({type: BSLaunchEvent.STEAM_LAUNCHED}); + }).catch(e => { + log.error(e); + obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM}); + }); } // Backup SteamVR when desktop mode is enabled if(!launchOptions.version.oculus && launchOptions.desktop){ - await this.backupSteamVR().catch(e => { + await this.backupSteamVR().catch(() => { return this.restoreSteamVR(); }); await lastValueFrom(timer(2_000)); @@ -163,22 +160,52 @@ export class BSLauncherService { const launchArgs = this.buildBsLaunchArgs(launchOptions); - obs.next({type: BSLaunchEventType.BS_LAUNCHING}); + obs.next({type: BSLaunchEvent.BS_LAUNCHING}); - await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(err => { - obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR, data: err} as BSLaunchErrorEvent); - }).finally(() => { - if(!launchOptions.desktop || launchOptions.version.oculus){ return; } - this.restoreSteamVR(); + await new Promise((resolve, reject) => { + const bsProcess = this.launchBSProcess(exePath, launchArgs, launchOptions.debug); + + bsProcess.on("error", reject); + bsProcess.on("exit", resolve); + + setTimeout(() => { + bsProcess.removeAllListeners("error"); + bsProcess.removeAllListeners("exit"); + resolve(-1); + }, 4000); + + }).then(exitCode => { + log.info("BS process exit code", exitCode); + }).catch(err => { + log.error(err); + obs.error({type: BSLaunchError.BS_EXIT_ERROR, data: err} as BSLaunchErrorData); }); })().then(() => { obs.complete(); }).catch(err => { - obs.error({type: BSLaunchErrorType.UNKNOWN_ERROR, data: err} as BSLaunchErrorEvent); + obs.error({type: BSLaunchError.UNKNOWN_ERROR, data: err} as BSLaunchErrorData); })}); } + private shortcutParamsToLaunchOption(params: ShortcutParams): LaunchOption{ + const res: LaunchOption = { + version: { + BSVersion: params.version, + name: params.versionName, + steam: params.versionSteam === "true", + oculus: params.versionOculus === "true", + ino: +params.versionIno + }, + oculus: params.oculusMode === "true", + desktop: params.desktopMode === "true", + debug: params.debug === "true", + additionalArgs: params.additionalArgs + }; + + return res; + } + private launchOptionToShortcutParams(launchOptions: LaunchOption): ShortcutParams{ const res: ShortcutParams = { version: launchOptions.version.BSVersion }; @@ -195,19 +222,73 @@ export class BSLauncherService { return res; } + /** + * Create .ico file for the shortcut with the given color + * @param {Color} color + * @returns {Promise} Path of the icon + */ + private async createShortcutIco(color: Color): Promise{ + + const svgIcon = ` + + + + + `; + + const pngBuffer = new Resvg(svgIcon, { + fitTo: { mode: "width", value: 256 } + }).render().asPng(); + + const icoBuffer = await toIco([pngBuffer]); + + await ensureDir(IMAGE_CACHE_PATH); + + const iconPath = path.join(IMAGE_CACHE_PATH, `launch_shortcut_${color.hex()}.ico`); + + await writeFile(iconPath, icoBuffer); + + return iconPath; + } + + private createInternetShortcut(options: { url: string, output: string, iconFile?: string, iconIndex?: number }): Promise{ + + const data = [ + "[InternetShortcut]", + `URL=${options.url}`, + options.iconFile ? `IconFile=${options.iconFile}` : null, + `IconIndex=${options.iconIndex ?? 0}` + ].join("\r\n"); + + return writeFile(options.output, data); + } + public async createLaunchShortcut(launchOptions: LaunchOption): Promise{ const shortcutParams = this.launchOptionToShortcutParams(launchOptions); const shortcutUrl = this.bsmProtocolService.buildLink("launch", shortcutParams); - console.log(objectFromEntries(shortcutUrl.searchParams.entries())); + const shortcutName = ["Beat Saber", launchOptions.version.BSVersion, launchOptions.version.name].join(" ") - // shell.writeShortcutLink(path.join(app.getPath("desktop"), "test.lnk"), "create", { - // target: shortcutUrl.toString(), - // description: "test allo allo", - - // }); + return this.createInternetShortcut({ + output: path.join(app.getPath("desktop"), `${shortcutName}.url`), + url: shortcutUrl.toString(), + iconFile: await this.createShortcutIco(new Color(launchOptions.version.color, "hex")), + }); + } + private async openShortcutLaunchWindow(launchOptions: ShortcutParams): Promise{ + const launchOption = this.shortcutParamsToLaunchOption(launchOptions); + + launchOption.version = await this.localVersionService.getVersionOfBSFolder(await this.localVersionService.getInstalledVersionPath(launchOption.version)); + + launchOption.version = {...(await this.remoteVersion.getVersionDetails(launchOption.version.BSVersion)), ...launchOption.version}; + + this.ipc.once("shortcut-launch-options", (_data, reply) => { + reply(of(launchOption)); + }); + + this.windows.openWindow("shortcut-launch.html"); } } diff --git a/src/main/services/bs-local-version.service.ts b/src/main/services/bs-local-version.service.ts index a33b421a..2b96da09 100644 --- a/src/main/services/bs-local-version.service.ts +++ b/src/main/services/bs-local-version.service.ts @@ -1,5 +1,5 @@ import { BSVersionLibService } from "./bs-version-lib.service"; -import { BSVersion, PartialBSVersion } from "shared/bs-version.interface"; +import { BSVersion } from "shared/bs-version.interface"; import { InstallationLocationService } from "./installation-location.service"; import { SteamService } from "./steam.service"; import { BS_APP_ID, OCULUS_BS_DIR } from "../constants"; @@ -45,50 +45,55 @@ export class BSLocalVersionService { - public async getVersionOfBSFolder(bsPath: string): Promise{ + public async getVersionOfBSFolder(bsPath: string): Promise{ const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers'); if(!(await pathExist(versionFilePath))){ return null; } - const versionsAvailable = await this.remoteVersionService.getAvailableVersions(); + const versionsDict = await this.remoteVersionService.getAvailableVersions(); - return new Promise(async (resolve, reject) => { + const folderVersion = await new Promise(async (resolve, reject) => { const stream = createReadStream(versionFilePath); - stream.on('data', async line => { + stream.on('data', line => { line = line.toString(); - for(const version of versionsAvailable){ - if(!line.includes(version.BSVersion)){ continue; } - - const findVersion: PartialBSVersion = {BSVersion: version.BSVersion}; - - if(findVersion.BSVersion !== path.basename(bsPath)){ - findVersion.name = path.basename(bsPath); - } + for(const bsVersion of versionsDict){ - const folderStats = await lstat(bsPath); - if(folderStats.ino){ - findVersion.ino = folderStats.ino; - } + if(!line.includes(bsVersion.BSVersion)){ continue; } + + resolve({...bsVersion}); stream.close(); stream.destroy(); - return resolve(findVersion); + return; } }); - stream.on("close", () => { - resolve(null); - }); - - stream.on("error", e => { - reject(e); - }); - + stream.on("close", () => { resolve(null); }); + stream.on("error", e => { log.error(e); reject(e); }); }); + + if(!folderVersion){ return null; } + + if(folderVersion.BSVersion !== path.basename(bsPath)){ + folderVersion.name = path.basename(bsPath); + } + + const folderStats = await lstat(bsPath); + if(folderStats.ino){ + folderVersion.ino = folderStats.ino; + } + + const customVersion = this.getCustomVersions().find(customVersion => { + return customVersion.BSVersion === folderVersion.BSVersion && customVersion.name === folderVersion.name; + }); + + folderVersion.color = customVersion?.color; + + return folderVersion; } private setCustomVersions(versions: BSVersion[]): void{ @@ -165,35 +170,38 @@ export class BSLocalVersionService { private async getSteamVersion(): Promise { const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber"); + if (!steamBsFolder || !(await pathExist(steamBsFolder))) { return null; } + const steamBsVersion = await this.getVersionOfBSFolder(steamBsFolder); - if (!steamBsVersion) { + if(!steamBsVersion){ return null; } - const version = await this.remoteVersionService.getVersionDetails(steamBsVersion.BSVersion); - if (!version) { - return null; - } - return { ...version, steam: true }; + + steamBsVersion.name = undefined; + steamBsVersion.steam = true; + + return steamBsVersion; } private async getOculusVersion(): Promise { const oculusBsFolder = await this.oculusService.getGameFolder(OCULUS_BS_DIR); + if (!oculusBsFolder) { return null; } + const oculusBsVersion = await this.getVersionOfBSFolder(oculusBsFolder); if(!oculusBsVersion){ return null; } - - const version = await this.remoteVersionService.getVersionDetails(oculusBsVersion.BSVersion); - if (!version) { - return null; - } - return { ...version, oculus: true }; + + oculusBsVersion.name = undefined; + oculusBsVersion.oculus = true; + + return oculusBsVersion; } public async getInstalledVersions(): Promise { @@ -217,23 +225,12 @@ export class BSLocalVersionService { for (const f of folderInInstallation) { log.info("try get version from folder", f); - const rawVersion = await this.getVersionOfBSFolder(f); - if(!rawVersion){ continue; } + const version = await this.getVersionOfBSFolder(f); - const vertionDetails = await this.remoteVersionService.getVersionDetails(rawVersion.BSVersion); + if(!version){ continue; } - if(!vertionDetails){ continue; } - - const bsVersion: BSVersion = {...vertionDetails, ...rawVersion}; - - const customVersion = this.getCustomVersions().find(custom => custom.BSVersion === bsVersion.BSVersion && custom.name === bsVersion.name); - - if (customVersion) { - bsVersion.color = customVersion.color; - } - - versions.push(bsVersion); + versions.push(version); }; this.setCustomVersions(versions.filter(v => !!v.color)); diff --git a/src/main/services/bsm-protocol.service.ts b/src/main/services/bsm-protocol.service.ts index 4e2d1c67..9bf4438a 100644 --- a/src/main/services/bsm-protocol.service.ts +++ b/src/main/services/bsm-protocol.service.ts @@ -39,11 +39,11 @@ export class BsmProtocolService { } public buildLink(host: string, params?: Record): URL { - return buildUrl({ - protocol: this.BSM_PROTOCOL, - host, - search: params - }); + return buildUrl({ + protocol: this.BSM_PROTOCOL, + host, + search: params + }); } } \ No newline at end of file diff --git a/src/main/services/window-manager.service.ts b/src/main/services/window-manager.service.ts index f2152ddc..8fb41807 100644 --- a/src/main/services/window-manager.service.ts +++ b/src/main/services/window-manager.service.ts @@ -18,6 +18,7 @@ export class WindowManagerService { "oneclick-download-map.html": { width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false }, "oneclick-download-playlist.html": { width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false }, "oneclick-download-model.html": { width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false }, + "shortcut-launch.html": { width: 600, height: 300, minWidth: 600, minHeight: 300, resizable: false }, }; private readonly baseWindowOption: BrowserWindowConstructorOptions = { @@ -89,4 +90,15 @@ export class WindowManagerService { public getAppWindowFromWebContents(sender: Electron.WebContents): AppWindow { return Array.from(this.windows.entries()).find(([, value]) => value.webContents.id === sender.id)[0]; } + + public openWindowOrFocus(window: AppWindow): Promise { + const win = this.getWindow(window); + + if (win) { + win.focus(); + return Promise.resolve(); + } + + return this.openWindow(window).then(() => {}); + } } diff --git a/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx b/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx index 5dba7724..04d81b7d 100644 --- a/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx +++ b/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx @@ -32,7 +32,12 @@ export function BsVersionItem(props: { version: BSVersion }) { }; const handleDoubleClick = () => { - launcherService.launch(state, !!configService.get(LaunchMods.OCULUS_MOD), !!configService.get(LaunchMods.DESKTOP_MOD), !!configService.get(LaunchMods.DEBUG_MOD)); + launcherService.launch({ + version: state, + oculus: !!configService.get(LaunchMods.OCULUS_MOD), + desktop: !!configService.get(LaunchMods.DESKTOP_MOD), + debug: !!configService.get(LaunchMods.DEBUG_MOD), + }); }; const cancel = () => { diff --git a/src/renderer/components/svgs/icons/bs-note-fill.component.tsx b/src/renderer/components/svgs/icons/bs-note-fill.component.tsx index 36ed1b65..3c998257 100644 --- a/src/renderer/components/svgs/icons/bs-note-fill.component.tsx +++ b/src/renderer/components/svgs/icons/bs-note-fill.component.tsx @@ -2,7 +2,7 @@ import { CSSProperties } from "react"; export function BsNoteFill(props: { className?: string; style?: CSSProperties }) { return ( - + diff --git a/src/renderer/components/title-bar/title-bar.component.tsx b/src/renderer/components/title-bar/title-bar.component.tsx index 728cf454..db722a4d 100644 --- a/src/renderer/components/title-bar/title-bar.component.tsx +++ b/src/renderer/components/title-bar/title-bar.component.tsx @@ -116,7 +116,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin ); } - if (template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html" || template === "oneclick-download-model.html") { + if (template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html" || template === "oneclick-download-model.html" || "shortcut-launch.html") { return (
@@ -127,9 +127,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
- - {" "} - +
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 01b25ad5..5b38cd9a 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 @@ -50,7 +50,14 @@ export function LaunchSlide({ version }: Props) { .map(arg => arg.trim()) .filter(arg => arg.length > 0) : undefined; - bsLauncherService.launch(version, version.oculus ? false : oculusMode, desktopMode, debugMode, additionalArgs); + + bsLauncherService.launch({ + version, + oculus: version.oculus ? false : oculusMode, + desktop: desktopMode, + debug: debugMode, + additionalArgs + }) }; return ( diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 5f39fb37..16ce237a 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -8,6 +8,7 @@ const launcherContainer = document.getElementById("launcher"); const oneclickDownloadMapContainer = document.getElementById("oneclick-download-map"); const oneclickDownloadPlaylistContainer = document.getElementById("oneclick-download-playlist"); const oneclickDownloadModelContainer = document.getElementById("oneclick-download-model"); +const shortcutLaunchContainer = document.getElementById("shortcut-launch"); const ipc = IpcService.getInstance(); @@ -31,6 +32,10 @@ if (launcherContainer) { import("./windows/OneClick/OneClickDownloadModel").then(reactWindow => { createRoot(oneclickDownloadModelContainer).render(); }); +} else if (shortcutLaunchContainer) { + import("./windows/ShortcutLaunch").then(reactWindow => { + createRoot(shortcutLaunchContainer).render(); + }); } else { const root = document.getElementById("root"); import("./windows/App").then(reactWindow => { diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 56bec9e6..40f00861 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -1,9 +1,9 @@ -import { LaunchOption, LaunchResult, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEvent } from "shared/models/bs-launch"; +import { LaunchOption, LaunchResult, BSLaunchEvent, BSLaunchWarning, BSLaunchEventData, BSLaunchErrorData, BSLaunchError } from "shared/models/bs-launch"; import { BSVersion } from 'shared/bs-version.interface'; import { IpcService } from "./ipc.service"; import { NotificationService } from "./notification.service"; import { BsDownloaderService } from "./bs-downloader.service"; -import { BehaviorSubject, Observable } from "rxjs"; +import { BehaviorSubject, Observable, filter } from "rxjs"; import { NotificationResult } from "shared/models/notification/notification.model"; export class BSLauncherService { @@ -66,26 +66,32 @@ export class BSLauncherService { }); } - public launch(version: BSVersion, oculus: boolean, desktop: boolean, debug: boolean, additionalArgs?: string[]): Observable { - const launchState$ = this.ipcService.sendV2("bs-launch.launch", {args: {debug, oculus, desktop, version, additionalArgs}}); + public doLaunch(launchOptions: LaunchOption): Observable{ + return this.ipcService.sendV2("bs-launch.launch", {args: launchOptions}); + } - this.versionRunning$.next(version); + public launch(launchOptions: LaunchOption): Observable { + const launchState$ = this.doLaunch(launchOptions); - launchState$.subscribe({ + this.versionRunning$.next(launchOptions.version); + + launchState$.pipe(filter(event => { + const eventToFilter = [...Object.values(BSLaunchWarning), BSLaunchEvent.STEAM_LAUNCHED] + return !eventToFilter.includes(event.type); + })).subscribe({ next: event => { this.notificationService.notifySuccess({title: `notifications.bs-launch.success.titles.${event.type}`, desc: `notifications.bs-launch.success.msg.${event.type}`}); }, - error: (err: BSLaunchErrorEvent) => { - if(!Object.values(BSLaunchErrorType).includes(err.type)){ + error: (err: BSLaunchErrorData) => { + if(!Object.values(BSLaunchError).includes(err.type)){ 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.type}`, desc: `notifications.bs-launch.errors.msg.${err.type}`}) } - }, - complete: () => { - this.versionRunning$.next(null); } - }) + }).add(() => { + this.versionRunning$.next(null); + }); return launchState$; } diff --git a/src/renderer/services/window-manager.service.ts b/src/renderer/services/window-manager.service.ts index b8ef66d2..c3b452aa 100644 --- a/src/renderer/services/window-manager.service.ts +++ b/src/renderer/services/window-manager.service.ts @@ -1,5 +1,6 @@ import { AppWindow } from "shared/models/window-manager/app-window.model"; import { IpcService } from "./ipc.service"; +import { lastValueFrom } from "rxjs"; export class WindowManagerService { private static instance: WindowManagerService; @@ -28,4 +29,9 @@ export class WindowManagerService { public close(...win: AppWindow[]) { this.ipcService.sendLazy("close-windows", { args: win }); } + + public openWindowOrFocus(window: AppWindow): Promise { + return lastValueFrom(this.ipcService.sendV2("open-window-or-focus", { args: window })); + } + } diff --git a/src/renderer/shortcut-launch.ejs b/src/renderer/shortcut-launch.ejs new file mode 100644 index 00000000..a22eeca2 --- /dev/null +++ b/src/renderer/shortcut-launch.ejs @@ -0,0 +1,14 @@ + + + + + + + + +
+ + \ No newline at end of file diff --git a/src/renderer/windows/ShortcutLaunch.tsx b/src/renderer/windows/ShortcutLaunch.tsx new file mode 100644 index 00000000..13d4d8fd --- /dev/null +++ b/src/renderer/windows/ShortcutLaunch.tsx @@ -0,0 +1,115 @@ +import TitleBar from "renderer/components/title-bar/title-bar.component"; +import { useService } from "renderer/hooks/use-service.hook"; +import { ThemeService } from "renderer/services/theme.service"; +import { useEffect, useState } from "react" +import { WindowManagerService } from "renderer/services/window-manager.service"; +import { IpcService } from "renderer/services/ipc.service"; +import { take } from "rxjs"; +import { BSLauncherService } from "renderer/services/bs-launcher.service"; +import { BsmImage } from "renderer/components/shared/bsm-image.component"; +import defaultImage from "../../../assets/images/default-version-img.jpg"; +import { useObservable } from "renderer/hooks/use-observable.hook"; +import { useOnUpdate } from "renderer/hooks/use-on-update.hook"; +import { BsNoteFill } from "renderer/components/svgs/icons/bs-note-fill.component"; +import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; +import { BsmButton } from "renderer/components/shared/bsm-button.component"; +import { motion } from "framer-motion" +import { BSLaunchError, BSLaunchEventType, LaunchOption } from "shared/models/bs-launch"; +import { NotificationService } from "renderer/services/notification.service"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; + +export default function ShortcutLaunch() { + + + const themeService = useService(ThemeService); + const windows = useService(WindowManagerService); + const ipc = useService(IpcService); + const bsLauncher = useService(BSLauncherService); + const notification = useService(NotificationService); + + const t = useTranslation(); + const color = useThemeColor("second-color"); + const launchOptions = useObservable(ipc.sendV2("shortcut-launch-options").pipe(take(1)), null) + const [rotation, setRotation] = useState(0); + const [status, setStatus] = useState(); + + useEffect(() => { + const sub = themeService.theme$.subscribe(() => { + if (themeService.isDark || (themeService.isOS && window.matchMedia("(prefers-color-scheme: dark)").matches)) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } + }); + + const interval = setInterval(() => { + setRotation(rotation => rotation + 45 * 3); + }, 1500); + + return () => { + sub.unsubscribe(); + clearInterval(interval); + } + }, []); + + useOnUpdate(() => { + if(!launchOptions) { return; } + + launchOptions.skipAlreadyRunning = true; + + const sub = bsLauncher.doLaunch(launchOptions).subscribe({ + next: event => { + setStatus(event.type); + }, + error: err => { + if(!Object.values(BSLaunchError).includes(err.type)){ + notification.notifySystem({title: t("bs-launch.errors.titles.UNKNOWN_ERROR"), body: t("bs-launch.errors.msg.UNKNOWN_ERROR")}); + } else { + notification.notifySystem({title: t(`bs-launch.errors.titles.${err.type}`), body: t(`bs-launch.errors.msg.${err.type}`)}) + } + } + }); + + sub.add(() => { + windows.close("shortcut-launch.html"); + }); + + return () => sub.unsubscribe(); + }, [launchOptions]); + + return ( +
+ +
+ +
+
+ + + + +
+
+
+

{t("bs-shortcut-launch.beat-saber-launching")}

+

{[launchOptions?.version?.BSVersion, launchOptions?.version?.name].join(" ")}

+
+ {t("bs-shortcut-launch.launching")} + {( + status ? t(`bs-shortcut-launch.status-text.success.${status}`) : t("bs-shortcut-launch.status-text.init") + )} + +
+ windows.openWindowOrFocus("index.html")}/> +
+
+
+
+
+ ) +} diff --git a/src/shared/models/bs-launch/index.ts b/src/shared/models/bs-launch/index.ts index 0711c277..60d451b8 100644 --- a/src/shared/models/bs-launch/index.ts +++ b/src/shared/models/bs-launch/index.ts @@ -1,3 +1,3 @@ export { LaunchOption } from "./launch-option.interface" export { LaunchResult } from "./launch-result.interface" -export { BSLaunchErrorEvent, BSLaunchEvent, BSLaunchErrorType, BSLaunchEventType } from "./launch-event.model" +export { BSLaunchError, BSLaunchErrorData, BSLaunchEvent, BSLaunchEventData, BSLaunchEventType, BSLaunchWarning } from "./launch-event.model" diff --git a/src/shared/models/bs-launch/launch-event.model.ts b/src/shared/models/bs-launch/launch-event.model.ts index 9f2d7858..51c3d9c2 100644 --- a/src/shared/models/bs-launch/launch-event.model.ts +++ b/src/shared/models/bs-launch/launch-event.model.ts @@ -1,14 +1,14 @@ -export interface BSLaunchEvent{ +export interface BSLaunchEventData{ type: BSLaunchEventType; data?: unknown; } -export interface BSLaunchErrorEvent{ - type: BSLaunchErrorType; +export interface BSLaunchErrorData{ + type: BSLaunchError; data?: unknown; } -export enum BSLaunchErrorType{ +export enum BSLaunchError{ BS_NOT_FOUND = "EXE_NOT_FINDED", BS_ALREADY_RUNNING = "BS_ALREADY_RUNNING", OCULUS_NOT_RUNNING = "OCULUS_NOT_RUNNING", @@ -16,7 +16,14 @@ export enum BSLaunchErrorType{ UNKNOWN_ERROR = "UNKNOWN_ERROR", } -export enum BSLaunchEventType{ +export enum BSLaunchEvent{ STEAM_LAUNCHING = "STEAM_LAUNCHING", + STEAM_LAUNCHED = "STEAM_LAUNCHED", BS_LAUNCHING = "BS_LAUNCHING", -} \ No newline at end of file +} + +export enum BSLaunchWarning{ + UNABLE_TO_LAUNCH_STEAM = "UNABLE_TO_LAUNCH_STEAM", +} + +export type BSLaunchEventType = BSLaunchEvent | BSLaunchWarning; \ No newline at end of file diff --git a/src/shared/models/bs-launch/launch-option.interface.ts b/src/shared/models/bs-launch/launch-option.interface.ts index 1b02c9db..66feaa8a 100644 --- a/src/shared/models/bs-launch/launch-option.interface.ts +++ b/src/shared/models/bs-launch/launch-option.interface.ts @@ -5,5 +5,6 @@ export interface LaunchOption { oculus: boolean, desktop: boolean, debug: boolean, - additionalArgs?: string[] + additionalArgs?: string[], + skipAlreadyRunning?: boolean } diff --git a/src/shared/models/window-manager/app-window.model.ts b/src/shared/models/window-manager/app-window.model.ts index d18020eb..9e6cacda 100644 --- a/src/shared/models/window-manager/app-window.model.ts +++ b/src/shared/models/window-manager/app-window.model.ts @@ -1 +1 @@ -export type AppWindow = "index.html" | "launcher.html" | "oneclick-download-map.html" | "oneclick-download-playlist.html" | "oneclick-download-model.html"; +export type AppWindow = "index.html" | "launcher.html" | "oneclick-download-map.html" | "oneclick-download-playlist.html" | "oneclick-download-model.html" | "shortcut-launch.html"; From 0acb70cf7e6234f71c4c43c97416de1b9b24be35 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sun, 16 Jul 2023 04:00:16 +0200 Subject: [PATCH 7/9] [feature] can now create launch shortcut --- assets/jsons/translations/de.json | 17 +++ assets/jsons/translations/en.json | 17 +++ assets/jsons/translations/es.json | 17 +++ assets/jsons/translations/fr.json | 17 +++ src/main/constants.ts | 2 +- src/main/ipcs/bs-launcher-ipcs.ts | 9 +- src/main/main.ts | 25 ---- src/main/services/bs-launcher.service.ts | 131 ++++++++++++------ src/main/services/bs-local-version.service.ts | 95 ++++++------- src/main/services/notification.service.ts | 13 -- ...create-launch-shortcut-modal.component.tsx | 96 +++++++++++++ .../components/svgs/bsm-icon.component.tsx | 7 +- .../svgs/icons/shortcut-icon.component.tsx | 9 ++ .../pages/version-viewer.component.tsx | 34 ++++- .../services/bs-downloader.service.ts | 2 +- src/renderer/services/bs-launcher.service.ts | 59 +++----- src/renderer/services/notification.service.ts | 5 - src/renderer/services/theme.service.ts | 8 +- src/renderer/windows/ShortcutLaunch.tsx | 4 +- .../bs-launch/launch-option.interface.ts | 6 +- 20 files changed, 384 insertions(+), 189 deletions(-) create mode 100644 src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx create mode 100644 src/renderer/components/svgs/icons/shortcut-icon.component.tsx diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index be559f32..a0a87635 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -97,6 +97,7 @@ "clone": "Klonen", "edit": "Bearbeiten", "uninstall": "Entfernen", + "create-shortcut": "Verknüpfung erstellen", "shared-folders": "Geteilte Ordner" } }, @@ -451,6 +452,15 @@ "msg": "Das Freigeben des Ordners „UserData“ kann zu Fehlern führen. Bei Problemen trenne den Ordner, um die Sicherung wiederherzustellen" } } + }, + "create-launch-shortcut": { + "success": { + "title": "Verknüpfung erstellt", + "msg": "Die Verknüpfung wurde auf dem Desktop erstellt." + }, + "error": { + "msg": "Beim Erstellen der Verknüpfung ist ein Fehler aufgetreten." + } } }, "modals": { @@ -611,6 +621,13 @@ "unlink-folder": "Ordner Verknüpfung Aufheben", "link-all": "Alle verlinken" } + }, + "create-launch-shortcut": { + "title": "Verknüpfung erstellen", + "desc": "Das Erstellen einer Verknüpfung ermöglicht es Ihnen, Beat Saber mit den ausgewählten Optionen zu starten, ohne durch BSManager zu gehen.", + "launch-options": "Startoptionen", + "advanced-launch": "Erweiterte Start", + "valid-btn": "Verknüpfung erstellen" } }, "maps": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index db781be8..49481eb4 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -97,6 +97,7 @@ "clone": "Clone", "edit": "Edit", "uninstall": "Uninstall", + "create-shortcut": "Create a shortcut", "shared-folders": "Shared Folders" } }, @@ -451,6 +452,15 @@ "msg": "Sharing the 'UserData' folder can generate errors, in case of problems unlink the folder to restore the backup" } } + }, + "create-launch-shortcut": { + "success": { + "title": "Shortcut created", + "msg": "The shortcut has been created on the desktop." + }, + "error": { + "msg": "An error occurred while creating the shortcut." + } } }, "modals": { @@ -611,6 +621,13 @@ "unlink-folder": "Unlink Folder", "link-all": "Link all" } + }, + "create-launch-shortcut": { + "title": "Create a shortcut", + "desc": "Creating a shortcut will allow you to start Beat Saber with the chosen options without going through BSManager.", + "launch-options": "Launch options", + "advanced-launch": "Advanced launch", + "valid-btn": "Create the shortcut" } }, "maps": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 4c59ad74..2aea8d24 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -96,6 +96,7 @@ "clone": "Clonar", "edit": "Editar", "uninstall": "Desinstalar", + "create-shortcut": "Crear un atajo", "shared-folders": "Carpetas Compartidas" } }, @@ -450,6 +451,15 @@ "msg": "Compartir la carpeta 'UserData' puede generar errores, en caso de problemas desvincular la carpeta para restaurar la copia de seguridad" } } + }, + "create-launch-shortcut": { + "success": { + "title": "Acceso directo creado", + "msg": "El acceso directo se ha creado en el escritorio." + }, + "error": { + "msg": "Se produjo un error al crear el acceso directo." + } } }, "modals": { @@ -610,6 +620,13 @@ "unlink-folder": "Desenlazar Carpeta", "link-all": "Enlazar todo" } + }, + "create-launch-shortcut": { + "title": "Crear un atajo", + "desc": "Crear un atajo te permitirá iniciar Beat Saber con las opciones seleccionadas sin pasar por BSManager.", + "launch-options": "Opciones de lanzamiento", + "advanced-launch": "Lanzamiento avanzado", + "valid-btn": "Crear el atajo" } }, "maps": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index c2ed1461..5e8100fd 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -96,6 +96,7 @@ "clone": "Cloner", "edit": "Editer", "uninstall": "Désinstaller", + "create-shortcut": "Créer un raccourci", "shared-folders": "Dossiers Partagés" } }, @@ -450,6 +451,15 @@ "msg": "Le partage du dossier 'UserData' peut générer des erreurs, en cas de soucis déliez le dossier pour restaurer la sauvegarde" } } + }, + "create-launch-shortcut": { + "success": { + "title": "Raccourci créé", + "msg": "Le raccourci a été créé sur le bureau." + }, + "error": { + "msg": "Une erreur s'est produite lors de la création du raccourci." + } } }, "modals": { @@ -610,6 +620,13 @@ "unlink-folder": "Délier le dossier", "link-all": "Tout lier" } + }, + "create-launch-shortcut": { + "title": "Créer un raccourci", + "desc": "Créer un raccourci te permettra de démarrer Beat Saber avec les options choisi sans passé par BSManager.", + "launch-options": "Options de lancement", + "advanced-launch": "Lancement avancé", + "valid-btn": "Créer le raccourci" } }, "maps": { diff --git a/src/main/constants.ts b/src/main/constants.ts index 83b22a7c..2392c70b 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -10,4 +10,4 @@ export const APP_NAME = "BSManager"; export const STEAMVR_APP_ID = "250820"; export const IMAGE_CACHE_FOLDER = "imagescache"; -export const IMAGE_CACHE_PATH = path.join(path.dirname(app.getPath("exe")), IMAGE_CACHE_FOLDER); +export const IMAGE_CACHE_PATH = path.join(app.getPath("userData"), IMAGE_CACHE_FOLDER); diff --git a/src/main/ipcs/bs-launcher-ipcs.ts b/src/main/ipcs/bs-launcher-ipcs.ts index c9225649..1a272778 100644 --- a/src/main/ipcs/bs-launcher-ipcs.ts +++ b/src/main/ipcs/bs-launcher-ipcs.ts @@ -1,12 +1,17 @@ import { LaunchOption } from "shared/models/bs-launch"; import { BSLauncherService } from "../services/bs-launcher.service" -import { IpcRequest } from 'shared/models/ipc'; import { IpcService } from '../services/ipc.service'; +import { from } from "rxjs"; const ipc = IpcService.getInstance(); -ipc.on('bs-launch.launch', async (req: IpcRequest, reply) => { +ipc.on('bs-launch.launch', (req, reply) => { const bsLauncher = BSLauncherService.getInstance(); reply(bsLauncher.launch(req.args)); }); + +ipc.on("create-launch-shortcut", (req, reply) => { + const bsLauncher = BSLauncherService.getInstance(); + reply(from(bsLauncher.createLaunchShortcut(req.args))); +}); diff --git a/src/main/main.ts b/src/main/main.ts index 761f8a0c..ea405e39 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -87,12 +87,6 @@ if (!gotTheLock) { app.setAppUserModelId(APP_NAME); initServicesMustBeInitialized(); - - // TODO : to remove - setTimeout(() => { - // DeepLinkService.getInstance().dispatchLinkOpened("bsmanager://launch?version=1.29.1&versionName=Hi+Twitter&versionIno=2533274792493431&oculusMode=true&desktopMode=true&additionalArgs=--nowait&additionalArgs=-omgargs"); - }, 3000); - const deepLink = process.argv.find(arg => DeepLinkService.getInstance().isDeepLink(arg)); @@ -113,25 +107,6 @@ if (!gotTheLock) { ipcMain.on("log-error", (event, args: IpcRequest) => { log.error(args?.args); }); - - // TODO : remove this - BSLauncherService.getInstance().createLaunchShortcut({ - debug: false, - oculus: true, - desktop: true, - version: { - BSVersion: '1.29.1', - BSManifest: '886973241045584398', - ReleaseURL: 'https://steamcommunity.com/games/620980/announcements/detail/6169409105101272202', - ReleaseImg: 'https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/c328e407367e9914abaf92f609501877ee5abb63.png', - ReleaseDate: '1680623885', - year: '2023', - name: 'Hi Twitter', - ino: 2533274792493431, - color: '#6545ff' - }, - additionalArgs: [ '--nowait', '-omgargs' ] - }); }).catch(log.error); } diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index cea0f096..7724418f 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -11,7 +11,7 @@ import { rename } from "fs/promises"; import log from "electron-log"; import { Observable, lastValueFrom, of, timer } from "rxjs"; import { BsmProtocolService } from "./bsm-protocol.service"; -import { app} from "electron"; +import { app, shell} from "electron"; import { Resvg } from "@resvg/resvg-js"; import Color from "color"; import { ensureDir, writeFile } from "fs-extra"; @@ -20,13 +20,13 @@ import { objectFromEntries } from "../../shared/helpers/object.helpers"; import { WindowManagerService } from "./window-manager.service"; import { IpcService } from "./ipc.service"; import { BSVersionLibService } from "./bs-version-lib.service"; +import { execOnOs } from "../helpers/env.helpers"; export class BSLauncherService { private static instance: BSLauncherService; private readonly utilsService: UtilsService; private readonly steamService: SteamService; - private readonly oculusService: OculusService; private readonly localVersionService: BSLocalVersionService; private readonly bsmProtocolService: BsmProtocolService; private readonly windows: WindowManagerService; @@ -43,7 +43,6 @@ export class BSLauncherService { private constructor() { this.utilsService = UtilsService.getInstance(); this.steamService = SteamService.getInstance(); - this.oculusService = OculusService.getInstance(); this.localVersionService = BSLocalVersionService.getInstance(); this.bsmProtocolService = BsmProtocolService.getInstance(); this.windows = WindowManagerService.getInstance(); @@ -82,14 +81,12 @@ export class BSLauncherService { return this.utilsService.taskRunning(BS_EXECUTABLE); } - private buildBsLaunchArgs(launchOptions: LaunchOption){ + private buildBsLaunchArgs(launchOptions: LaunchOption): string[]{ const launchArgs = []; - if (!launchOptions.version.steam && !launchOptions.version.oculus) { - launchArgs.push("--no-yeet"); - } if (launchOptions.oculus) { - launchArgs.push("-vrmode oculus"); + launchArgs.push("-vrmode"); + launchArgs.push("oculus"); } if (launchOptions.desktop) { launchArgs.push("fpfc"); @@ -106,14 +103,13 @@ export class BSLauncherService { private launchBSProcess(bsExePath: string, args: string[], debug = false): ChildProcessWithoutNullStreams{ - const spawnOptions: SpawnOptionsWithoutStdio = { shell: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} }; + const spawnOptions: SpawnOptionsWithoutStdio = { detached: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} }; if(debug){ - spawnOptions.detached = true; spawnOptions.windowsVerbatimArguments = true; } - return spawn(`\"${bsExePath}\"`, args, spawnOptions); + return spawn(bsExePath, args, spawnOptions); } @@ -125,10 +121,6 @@ export class BSLauncherService { return obs.error({type: BSLaunchError.BS_ALREADY_RUNNING} as BSLaunchErrorData); } - if(launchOptions.version.oculus && (await this.oculusService.oculusRunning())){ - return obs.error({type: BSLaunchError.OCULUS_NOT_RUNNING} as BSLaunchErrorData); - } - const bsFolderPath = await this.localVersionService.getInstalledVersionPath(launchOptions.version); const exePath = path.join(bsFolderPath, BS_EXECUTABLE); @@ -222,13 +214,8 @@ export class BSLauncherService { return res; } - /** - * Create .ico file for the shortcut with the given color - * @param {Color} color - * @returns {Promise} Path of the icon - */ - private async createShortcutIco(color: Color): Promise{ - + private createShortcutPngBuffer(color: Color): Buffer{ + const svgIcon = ` @@ -236,10 +223,36 @@ export class BSLauncherService { `; - const pngBuffer = new Resvg(svgIcon, { + return new Resvg(svgIcon, { fitTo: { mode: "width", value: 256 } }).render().asPng(); + } + + /** + * Create .png file for the shortcut with the given color + * @param {Color} color + * @returns {Promise} Path of the icon + */ + private async createShortcutPng(color: Color): Promise{ + const pngBuffer = this.createShortcutPngBuffer(color); + + await ensureDir(IMAGE_CACHE_PATH); + + const iconPath = path.join(IMAGE_CACHE_PATH, `launch_shortcut_${color.hex()}.png`); + + await writeFile(iconPath, pngBuffer); + + return iconPath; + } + + /** + * Create .ico file for the shortcut with the given color + * @param {Color} color + * @returns {Promise} Path of the icon + */ + private async createShortcutIco(color: Color): Promise{ + const pngBuffer = this.createShortcutPngBuffer(color); const icoBuffer = await toIco([pngBuffer]); await ensureDir(IMAGE_CACHE_PATH); @@ -251,36 +264,42 @@ export class BSLauncherService { return iconPath; } - private createInternetShortcut(options: { url: string, output: string, iconFile?: string, iconIndex?: number }): Promise{ - - const data = [ - "[InternetShortcut]", - `URL=${options.url}`, - options.iconFile ? `IconFile=${options.iconFile}` : null, - `IconIndex=${options.iconIndex ?? 0}` - ].join("\r\n"); - - return writeFile(options.output, data); - } - - public async createLaunchShortcut(launchOptions: LaunchOption): Promise{ + public async createLaunchShortcut(launchOptions: LaunchOption): Promise{ const shortcutParams = this.launchOptionToShortcutParams(launchOptions); - const shortcutUrl = this.bsmProtocolService.buildLink("launch", shortcutParams); + const shortcutUrl = this.bsmProtocolService.buildLink("launch", shortcutParams).toString(); - const shortcutName = ["Beat Saber", launchOptions.version.BSVersion, launchOptions.version.name].join(" ") + const shortcutName = ["Beat Saber", launchOptions.version.BSVersion, launchOptions.version.name].join(" "); + const shortcutIconColor = new Color(launchOptions.version.color, "hex"); - return this.createInternetShortcut({ - output: path.join(app.getPath("desktop"), `${shortcutName}.url`), - url: shortcutUrl.toString(), - iconFile: await this.createShortcutIco(new Color(launchOptions.version.color, "hex")), - }); + return execOnOs({ + win32: async () => ( + shell.writeShortcutLink(path.join(app.getPath("desktop"), `${shortcutName}.lnk`), { + target: shortcutUrl, + icon: await this.createShortcutIco(shortcutIconColor), + iconIndex: 0, + description: [shortcutName, launchOptions.version.color].join(" "), // <= Need color in description to help windows know that the shortcut is different + }) + ), + linux: async () => ( + createDesktopUrlShortcut(path.join(app.getPath("desktop"), `${shortcutName}.desktop`), { + name: shortcutName, + url: shortcutUrl, + icon: await this.createShortcutPng(shortcutIconColor), + }) + ) + }) } private async openShortcutLaunchWindow(launchOptions: ShortcutParams): Promise{ const launchOption = this.shortcutParamsToLaunchOption(launchOptions); - launchOption.version = await this.localVersionService.getVersionOfBSFolder(await this.localVersionService.getInstalledVersionPath(launchOption.version)); + const bsPath = await this.localVersionService.getInstalledVersionPath(launchOption.version); + + launchOption.version = await this.localVersionService.getVersionOfBSFolder(bsPath, { + steam: launchOption.version.steam, + oculus: launchOption.version.oculus, + }); launchOption.version = {...(await this.remoteVersion.getVersionDetails(launchOption.version.BSVersion)), ...launchOption.version}; @@ -304,3 +323,27 @@ type ShortcutParams = { versionSteam?: string; versionOculus?: string; } + +/** + * Create .desktop file for url shortcut (only for linux) + * @param {string} shortcutPath + * @param options + * @returns + */ +function createDesktopUrlShortcut(shortcutPath: string, options?: { + url: string + name: string, + icon: string +}): Promise { + const { url, name, icon } = options || {}; + + const data = [ + "[Desktop Entry]", + "Type=Link", + `Name=${name}`, + `Icon=${icon}`, + `URL=${url}` + ].join("\n"); + + return writeFile(shortcutPath, data).then(() => true); +} diff --git a/src/main/services/bs-local-version.service.ts b/src/main/services/bs-local-version.service.ts index 2b96da09..6d13ab08 100644 --- a/src/main/services/bs-local-version.service.ts +++ b/src/main/services/bs-local-version.service.ts @@ -4,7 +4,6 @@ import { InstallationLocationService } from "./installation-location.service"; import { SteamService } from "./steam.service"; import { BS_APP_ID, OCULUS_BS_DIR } from "../constants"; import path from "path"; -import { createReadStream } from "fs"; import { ConfigurationService } from "./configuration.service"; import { lstat, rename } from "fs/promises"; import { BsmException } from "shared/models/bsm-exception.model"; @@ -14,6 +13,8 @@ import { DownloadLinkType } from "shared/models/mods"; import sanitize from "sanitize-filename"; import { copyDirectoryWithJunctions, deleteFolder, getFoldersInFolder, pathExist } from "../helpers/fs.helpers"; import { FolderLinkerService } from "./folder-linker.service"; +import { ReadStream, createReadStream } from "fs-extra"; +import readline from "readline"; export class BSLocalVersionService { private static instance: BSLocalVersionService; @@ -43,41 +44,55 @@ export class BSLocalVersionService { this.linker = FolderLinkerService.getInstance(); } - + private async getVersionFromGlobalGameManagerFile(versionFilePath: string): Promise { - public async getVersionOfBSFolder(bsPath: string): Promise{ - const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers'); - if(!(await pathExist(versionFilePath))){ return null; } - + const versionsDict = await this.remoteVersionService.getAvailableVersions(); - - const folderVersion = await new Promise(async (resolve, reject) => { - const stream = createReadStream(versionFilePath); - stream.on('data', line => { - - line = line.toString(); + let stream: ReadStream; - for(const bsVersion of versionsDict){ - - if(!line.includes(bsVersion.BSVersion)){ continue; } - - resolve({...bsVersion}); - - stream.close(); - stream.destroy(); - - return; - } + try{ + stream = createReadStream(versionFilePath); + const rl = readline.createInterface({ + input: stream, + crlfDelay: Infinity }); - stream.on("close", () => { resolve(null); }); - stream.on("error", e => { log.error(e); reject(e); }); - }); + for await (const line of rl) { + for (const bsVersion of versionsDict) { + if (line.includes(bsVersion.BSVersion)) { + stream.close(); + return {...bsVersion}; + } + } + } + + } catch(e) { + log.error(e); + } finally { + stream?.close(); + } + + return null; + } + + public async getVersionOfBSFolder( + bsPath: string, + options?: { + steam?: boolean; + oculus?: boolean; + } + ): Promise{ + const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers'); + const folderVersion = await this.getVersionFromGlobalGameManagerFile(versionFilePath); if(!folderVersion){ return null; } + if(options?.steam || options?.oculus){ + return {...folderVersion, ...options}; + } + if(folderVersion.BSVersion !== path.basename(bsPath)){ folderVersion.name = path.basename(bsPath); } @@ -150,10 +165,6 @@ export class BSLocalVersionService { return null; } - private removeSpecialChar(seq: string): string{ - return sanitize(seq); - } - public getVersionFolder(version: BSVersion): string{ return version.name ?? version.BSVersion; } @@ -175,16 +186,7 @@ export class BSLocalVersionService { return null; } - const steamBsVersion = await this.getVersionOfBSFolder(steamBsFolder); - - if(!steamBsVersion){ - return null; - } - - steamBsVersion.name = undefined; - steamBsVersion.steam = true; - - return steamBsVersion; + return this.getVersionOfBSFolder(steamBsFolder, { steam: true }); } private async getOculusVersion(): Promise { @@ -194,14 +196,7 @@ export class BSLocalVersionService { return null; } - const oculusBsVersion = await this.getVersionOfBSFolder(oculusBsFolder); - - if(!oculusBsVersion){ return null; } - - oculusBsVersion.name = undefined; - oculusBsVersion.oculus = true; - - return oculusBsVersion; + return this.getVersionOfBSFolder(oculusBsFolder, { oculus: true }); } public async getInstalledVersions(): Promise { @@ -253,7 +248,7 @@ export class BSLocalVersionService { const oldPath = await this.getVersionPath(version); const editedVersion: BSVersion = version.BSVersion === name ? {...version, name: undefined, color} - : {...version, name: this.removeSpecialChar(name), color}; + : {...version, name: sanitize(name), color}; const newPath = await this.getVersionPath(editedVersion); if(oldPath === newPath){ @@ -278,7 +273,7 @@ export class BSLocalVersionService { const originPath = await this.getVersionPath(version); const cloneVersion: BSVersion = version.BSVersion === name ? {...version, name: undefined, color, steam: false, oculus: false} - : {...version, name: this.removeSpecialChar(name), color, steam: false, oculus: false}; + : {...version, name: sanitize(name), color, steam: false, oculus: false}; const newPath = await this.getVersionPath(cloneVersion); if(originPath === newPath){ diff --git a/src/main/services/notification.service.ts b/src/main/services/notification.service.ts index 5ea92cbc..e64f4e51 100644 --- a/src/main/services/notification.service.ts +++ b/src/main/services/notification.service.ts @@ -1,8 +1,6 @@ import { SystemNotificationOptions } from "shared/models/notification/system-notification.model"; import { UtilsService } from "./utils.service"; import { Notification } from "electron"; -import { Notification as NotificationRenderer } from "../../shared/models/notification/notification.model"; -import { IpcService } from "./ipc.service"; export class NotificationService { private static instance: NotificationService; @@ -17,24 +15,13 @@ export class NotificationService { private readonly APP_ICON: string; private readonly utils: UtilsService; - private readonly ipc: IpcService; private constructor() { this.utils = UtilsService.getInstance(); - this.ipc = IpcService.getInstance(); this.APP_ICON = this.utils.getAssetsPath("favicon.ico"); } public notify(options: SystemNotificationOptions) { new Notification({ ...options, icon: this.APP_ICON }).show(); } - - // TODO : Make actions work - public notifyRenderer(notification: Omit) { - try { - this.ipc.send("show-notification", "index.html", notification); - } catch (e) { - console.error(e); - } - } } 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 new file mode 100644 index 00000000..18f5fc4c --- /dev/null +++ b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx @@ -0,0 +1,96 @@ +import { ModalComponent, ModalExitCode } from "renderer/services/modale.service" +import { BSVersion } from "shared/bs-version.interface" +import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; +import { BsmButton } from "renderer/components/shared/bsm-button.component"; +import { LaunchOption } from "shared/models/bs-launch"; +import { useService } from "renderer/hooks/use-service.hook"; +import { BSLauncherService } from "renderer/services/bs-launcher.service"; +import { useState } from "react"; +import { BsNoteFill } from "renderer/components/svgs/icons/bs-note-fill.component"; +import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; +import { ChevronTopIcon } from "renderer/components/svgs/icons/chevron-top-icon.component"; +import Tippy from "@tippyjs/react"; + +export const CreateLaunchShortcutModal: ModalComponent = ({resolver, data}) => { + + const bsLauncher = useService(BSLauncherService); + + const t = useTranslation(); + const color = useThemeColor("second-color"); + + const [launchOptions, setLaunchOptions] = useState(bsLauncher.getLaunchOptions(data)); + const [advanced, setAdvanced] = useState(!!launchOptions.additionalArgs?.length); + const [additionalArgsString, setAdditionalArgsString] = useState(launchOptions.additionalArgs?.join("; ") ?? ""); + + const completeModal = () => { + + if(advanced) { + launchOptions.additionalArgs = additionalArgsString.split(";").map(arg => arg.trim()).filter(arg => arg.length); + } else { + launchOptions.additionalArgs = undefined; + } + + resolver({exitCode: ModalExitCode.COMPLETED, data: launchOptions}); + } + + return ( +
e.preventDefault()}> +

{t("modals.create-launch-shortcut.title")}

+

{t("modals.create-launch-shortcut.desc")}

+
+ +
+ Beat Saber + {[data.BSVersion, data.name].join(" ")} +
+
+

{t("modals.create-launch-shortcut.launch-options")}

+
+ {data.oculus !== true && ( +
+ setLaunchOptions({...launchOptions, oculus: e})} /> + + {t("pages.version-viewer.launch-mods.oculus")} + +
+ )} +
+ setLaunchOptions({...launchOptions, desktop: e})} /> + + {t("pages.version-viewer.launch-mods.desktop")} + +
+
+ setLaunchOptions({...launchOptions, debug: e})} /> + + {t("pages.version-viewer.launch-mods.debug")} + +
+
+
+
setAdvanced(prev => !prev)}> + {t("modals.create-launch-shortcut.advanced-launch")} + +
+
+
+
+ setAdditionalArgsString(e.target.value)} + /> +
+
+
+
+
+ resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" /> + +
+
+ ) +} diff --git a/src/renderer/components/svgs/bsm-icon.component.tsx b/src/renderer/components/svgs/bsm-icon.component.tsx index a954abab..7ba0ae47 100644 --- a/src/renderer/components/svgs/bsm-icon.component.tsx +++ b/src/renderer/components/svgs/bsm-icon.component.tsx @@ -59,8 +59,9 @@ import { ModelTypePlatformIcon } from "./icons/model-type-platform-icon.componen import { ModelTypeBloqIcon } from "./icons/model-type-bloq-icon.component"; import { ChevronTopIcon } from "./icons/chevron-top-icon.component"; import { EyeCrossIcon } from "./icons/eye-cross-icon.component"; +import { ShortcutIcon } from "./icons/shortcut-icon.component"; -export type BsmIconType = BsvMapCharacteristic | MSModelType | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag"); +export type BsmIconType = BsvMapCharacteristic | MSModelType | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag"); export const BsmIcon = memo(({ className, icon, style }: { className?: string; icon: BsmIconType; style?: CSSProperties }) => { // TODO : Very ugly very messy, need to find a better way to do this @@ -229,6 +230,10 @@ export const BsmIcon = memo(({ className, icon, style }: { className?: string; i return ; } + if (icon === "shortcut") { + return ; + } + if (icon === MSModelType.Avatar) { return ; } diff --git a/src/renderer/components/svgs/icons/shortcut-icon.component.tsx b/src/renderer/components/svgs/icons/shortcut-icon.component.tsx new file mode 100644 index 00000000..13e4598e --- /dev/null +++ b/src/renderer/components/svgs/icons/shortcut-icon.component.tsx @@ -0,0 +1,9 @@ +import { CSSProperties } from "react"; + +export function ShortcutIcon(props: { className?: string; style?: CSSProperties }) { + return ( + + + + ) +} diff --git a/src/renderer/pages/version-viewer.component.tsx b/src/renderer/pages/version-viewer.component.tsx index cab3dbe0..6d4d92dd 100644 --- a/src/renderer/pages/version-viewer.component.tsx +++ b/src/renderer/pages/version-viewer.component.tsx @@ -16,13 +16,21 @@ import { UninstallModal } from "renderer/components/modal/modal-types/uninstall- import { MapsPlaylistsPanel } from "renderer/components/maps-mangement-components/maps-playlists-panel.component"; import { ShareFoldersModal } from "renderer/components/modal/modal-types/share-folders-modal.component"; import { ModelsPanel } from "renderer/components/models-management/models-panel.component"; +import { useService } from "renderer/hooks/use-service.hook"; +import { BSLauncherService } from "renderer/services/bs-launcher.service"; +import { CreateLaunchShortcutModal } from "renderer/components/modal/modal-types/create-launch-shortcut-modal.component"; +import { lastValueFrom } from "rxjs"; +import { NotificationService } from "renderer/services/notification.service"; export function VersionViewer() { + const bsUninstallerService = BSUninstallerService.getInstance(); const bsVersionManagerService = BSVersionManagerService.getInstance(); const modalService = ModalService.getInsance(); const bsDownloaderService = BsDownloaderService.getInstance(); const ipcService = IpcService.getInstance(); + const bsLauncher = useService(BSLauncherService); + const notification = useService(NotificationService); const { state } = useLocation() as { state: BSVersion }; const navigate = useNavigate(); @@ -74,6 +82,23 @@ export function VersionViewer() { modalService.openModal(ShareFoldersModal, state); }; + const createLaunchShortcut = async () => { + const { exitCode, data } = await modalService.openModal(CreateLaunchShortcutModal, state); + if(exitCode !== ModalExitCode.COMPLETED){ return; } + + lastValueFrom(bsLauncher.createLaunchShortcut(data)).then(() => { + notification.notifySuccess({ + title: "notifications.create-launch-shortcut.success.title", + desc: "notifications.create-launch-shortcut.success.msg" + }); + }).catch(() => { + notification.notifyError({ + title: "notifications.types.error", + desc: "notifications.create-launch-shortcut.error.msg" + }); + }); + } + return ( <> @@ -90,7 +115,14 @@ export function VersionViewer() {
- + ); } diff --git a/src/renderer/services/bs-downloader.service.ts b/src/renderer/services/bs-downloader.service.ts index 667dfa30..bd4bd379 100644 --- a/src/renderer/services/bs-downloader.service.ts +++ b/src/renderer/services/bs-downloader.service.ts @@ -136,7 +136,7 @@ export class BsDownloaderService { } public async download(bsVersion: BSVersion, isVerification?: boolean, isFirstCall = true): Promise> { - // TODO : to remake cause we don't need recursion anymore + // TODO : to remake cause we don't need recursion anymore (will be rework with qr code) if (isFirstCall && !this.progressBarService.require()) { return { success: false }; diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 40f00861..492e5d5d 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -3,8 +3,10 @@ import { BSVersion } from 'shared/bs-version.interface'; import { IpcService } from "./ipc.service"; import { NotificationService } from "./notification.service"; import { BsDownloaderService } from "./bs-downloader.service"; -import { BehaviorSubject, Observable, filter } from "rxjs"; +import { BehaviorSubject, Observable, filter, of } from "rxjs"; import { NotificationResult } from "shared/models/notification/notification.model"; +import { ConfigurationService } from "./configuration.service"; +import { ThemeService } from "./theme.service"; export class BSLauncherService { private static instance: BSLauncherService; @@ -12,6 +14,8 @@ export class BSLauncherService { private readonly ipcService: IpcService; private readonly notificationService: NotificationService; private readonly bsDownloaderService: BsDownloaderService; + private readonly config: ConfigurationService; + private readonly theme: ThemeService; public readonly versionRunning$: BehaviorSubject = new BehaviorSubject(null); @@ -24,48 +28,20 @@ export class BSLauncherService { this.ipcService = IpcService.getInstance(); this.notificationService = NotificationService.getInstance(); this.bsDownloaderService = BsDownloaderService.getInstance(); + this.config = ConfigurationService.getInstance(); + this.theme = ThemeService.getInstance(); } - // TODO REMOVE - private listenBsExit(): void{ - this.ipcService.watch("bs-launch.exit").subscribe(res => { - const version = this.versionRunning$.value; - this.versionRunning$.next(null); - if(res.success){ return; } - this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.EXIT", desc: "notifications.bs-launch.errors.msg.EXIT", actions: [{id: "0", title: "misc.verify"}]}).then(res => { - if(res === "0"){ this.bsDownloaderService.download(version, true); } - }); - }); + public getLaunchOptions(version: BSVersion): LaunchOption{ + return { + version, + oculus: this.config.get(LaunchMods.OCULUS_MOD), + desktop: this.config.get(LaunchMods.DESKTOP_MOD), + debug: this.config.get(LaunchMods.DEBUG_MOD), + additionalArgs: (this.config.get("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0) + } } - // TODO : Rework with shortcuts implementation - public launch_old(version: BSVersion, oculus: boolean, desktop: boolean, debug: boolean, additionalArgs?: string[]): Promise{ - const lauchOption: LaunchOption = {debug, oculus, desktop, version, additionalArgs}; - if(this.versionRunning$.value){ return this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.BS_ALREADY_RUNNING"}); } - this.versionRunning$.next(version); - return this.ipcService.send("bs-launch.launch", {args: lauchOption}).then(res => { - - if(res.data === "LAUNCHED"){ return this.notificationService.notifySuccess({title: "notifications.bs-launch.success.titles.launching"}); } - - this.versionRunning$.next(null); - if(!res.success){ - return this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.UNABLE_TO_LAUNCH", desc: res.error.title}); - } - if (res.data === "EXE_NOT_FINDED") { - return this.notificationService.notifyError({ title: "notifications.bs-launch.errors.titles.EXE_NOT_FINDED", desc: "notifications.bs-launch.errors.msg.EXE_NOT_FINDED", actions: [{ id: "0", title: "misc.verify" }] }).then(res => { - if (res === "0") { - this.bsDownloaderService.download(version, true); - } - return res; - }); - } - if (res.data) { - return this.notificationService.notifyError({ title: `notifications.bs-launch.errors.titles.${res.data}` }); - } - return this.notificationService.notifyError({title: res.data || res.error.title}); - }); - } - public doLaunch(launchOptions: LaunchOption): Observable{ return this.ipcService.sendV2("bs-launch.launch", {args: launchOptions}); } @@ -96,6 +72,11 @@ export class BSLauncherService { return launchState$; } + public createLaunchShortcut(launchOptions: LaunchOption): Observable{ + const options: LaunchOption = {...launchOptions, version: {...launchOptions.version, color: launchOptions.version.color || this.theme.getBsmColors()[1]}}; + return this.ipcService.sendV2("create-launch-shortcut", {args: options}); + } + } export enum LaunchMods { diff --git a/src/renderer/services/notification.service.ts b/src/renderer/services/notification.service.ts index dfd4a150..c1183172 100644 --- a/src/renderer/services/notification.service.ts +++ b/src/renderer/services/notification.service.ts @@ -20,11 +20,6 @@ export class NotificationService { private constructor() { this.ipc = IpcService.getInstance(); this.notifications$ = new BehaviorSubject([]); - - // TODO : Make actions work and adapt with "watch" remork - this.ipc.watch("show-notification").subscribe(notification => { - this.notify(notification as unknown as Notification); - }); } public notify(notification: Notification): Promise { diff --git a/src/renderer/services/theme.service.ts b/src/renderer/services/theme.service.ts index d605dd68..2b2ceb5a 100644 --- a/src/renderer/services/theme.service.ts +++ b/src/renderer/services/theme.service.ts @@ -1,5 +1,5 @@ import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config"; -import { BehaviorSubject } from "rxjs"; +import { Observable } from "rxjs"; import { ConfigurationService } from "./configuration.service"; export class ThemeService { @@ -7,7 +7,7 @@ export class ThemeService { private readonly configService: ConfigurationService; - public readonly theme$: BehaviorSubject; + public readonly theme$: Observable; public static getInstance(): ThemeService { if (!ThemeService.instance) { @@ -29,6 +29,10 @@ export class ThemeService { return this.configService.get("theme" as DefaultConfigKey); } + public getBsmColors(): [string, string]{ + return [this.configService.get("first-color" as DefaultConfigKey), this.configService.get("second-color" as DefaultConfigKey)]; + } + public get isLight() { return this.configService.get("theme" as DefaultConfigKey) === ("light" as ThemeConfig); } diff --git a/src/renderer/windows/ShortcutLaunch.tsx b/src/renderer/windows/ShortcutLaunch.tsx index 13d4d8fd..c1058f34 100644 --- a/src/renderer/windows/ShortcutLaunch.tsx +++ b/src/renderer/windows/ShortcutLaunch.tsx @@ -63,9 +63,9 @@ export default function ShortcutLaunch() { }, error: err => { if(!Object.values(BSLaunchError).includes(err.type)){ - notification.notifySystem({title: t("bs-launch.errors.titles.UNKNOWN_ERROR"), body: t("bs-launch.errors.msg.UNKNOWN_ERROR")}); + notification.notifySystem({title: t("notifications.bs-launch.errors.titles.UNKNOWN_ERROR"), body: t("notifications.bs-launch.errors.msg.UNKNOWN_ERROR")}); } else { - notification.notifySystem({title: t(`bs-launch.errors.titles.${err.type}`), body: t(`bs-launch.errors.msg.${err.type}`)}) + notification.notifySystem({title: t(`notifications.bs-launch.errors.titles.${err.type}`), body: t(`notifications.bs-launch.errors.msg.${err.type}`)}) } } }); diff --git a/src/shared/models/bs-launch/launch-option.interface.ts b/src/shared/models/bs-launch/launch-option.interface.ts index 66feaa8a..ae3739a3 100644 --- a/src/shared/models/bs-launch/launch-option.interface.ts +++ b/src/shared/models/bs-launch/launch-option.interface.ts @@ -2,9 +2,9 @@ import { BSVersion } from "shared/bs-version.interface"; export interface LaunchOption { version: BSVersion, - oculus: boolean, - desktop: boolean, - debug: boolean, + oculus?: boolean, + desktop?: boolean, + debug?: boolean, additionalArgs?: string[], skipAlreadyRunning?: boolean } From c010ca8fe6599b9b432087b235fc5349f91d5f12 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sun, 16 Jul 2023 04:18:42 +0200 Subject: [PATCH 8/9] [feature] remove useless imports --- src/main/services/bs-launcher.service.ts | 1 - src/renderer/services/bs-launcher.service.ts | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index 7724418f..ae496b21 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -5,7 +5,6 @@ import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID, IMAGE_CACHE_PATH } from "../c import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process"; import { SteamService } from "./steam.service"; import { BSLocalVersionService } from "./bs-local-version.service"; -import { OculusService } from "./oculus.service"; import { pathExist } from "../helpers/fs.helpers"; import { rename } from "fs/promises"; import log from "electron-log"; diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 492e5d5d..b254015c 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -1,10 +1,9 @@ -import { LaunchOption, LaunchResult, BSLaunchEvent, BSLaunchWarning, BSLaunchEventData, BSLaunchErrorData, BSLaunchError } from "shared/models/bs-launch"; +import { LaunchOption, BSLaunchEvent, BSLaunchWarning, BSLaunchEventData, BSLaunchErrorData, BSLaunchError } from "shared/models/bs-launch"; import { BSVersion } from 'shared/bs-version.interface'; import { IpcService } from "./ipc.service"; import { NotificationService } from "./notification.service"; import { BsDownloaderService } from "./bs-downloader.service"; -import { BehaviorSubject, Observable, filter, of } from "rxjs"; -import { NotificationResult } from "shared/models/notification/notification.model"; +import { BehaviorSubject, Observable, filter } from "rxjs"; import { ConfigurationService } from "./configuration.service"; import { ThemeService } from "./theme.service"; @@ -13,7 +12,6 @@ export class BSLauncherService { private readonly ipcService: IpcService; private readonly notificationService: NotificationService; - private readonly bsDownloaderService: BsDownloaderService; private readonly config: ConfigurationService; private readonly theme: ThemeService; @@ -27,7 +25,6 @@ export class BSLauncherService { private constructor() { this.ipcService = IpcService.getInstance(); this.notificationService = NotificationService.getInstance(); - this.bsDownloaderService = BsDownloaderService.getInstance(); this.config = ConfigurationService.getInstance(); this.theme = ThemeService.getInstance(); } From a4e7816d7b35616854c8b94f66a9e4379141074c Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sun, 16 Jul 2023 04:21:13 +0200 Subject: [PATCH 9/9] [feature] remove useless import --- src/renderer/services/bs-launcher.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index b254015c..c1832eb2 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -2,7 +2,6 @@ import { LaunchOption, BSLaunchEvent, BSLaunchWarning, BSLaunchEventData, BSLaun import { BSVersion } from 'shared/bs-version.interface'; import { IpcService } from "./ipc.service"; import { NotificationService } from "./notification.service"; -import { BsDownloaderService } from "./bs-downloader.service"; import { BehaviorSubject, Observable, filter } from "rxjs"; import { ConfigurationService } from "./configuration.service"; import { ThemeService } from "./theme.service";