diff --git a/.gitignore b/.gitignore index eb0592e9..ac8094b0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ out *.log* .env .vite -ludusavi/ +ludusavi/** +!ludusavi/config.yaml hydra-python-rpc/ .python-version diff --git a/index.ts b/index.ts new file mode 100644 index 00000000..b7cc9340 --- /dev/null +++ b/index.ts @@ -0,0 +1,10 @@ +import vdf from "vdf-parser"; +import fs from "node:fs"; + +const vdfData = fs.readFileSync( + "/home/chubby/.local/share/Steam/userdata/1126196664/config/localconfig.vdf", + "utf-8" +); +const data = vdf.parse(vdfData); + +console.log(data); diff --git a/ludusavi/config.yaml b/ludusavi/config.yaml new file mode 100644 index 00000000..9b718c85 --- /dev/null +++ b/ludusavi/config.yaml @@ -0,0 +1,6 @@ +manifest: + enable: false + secondary: + - url: https://cdn.losbroxas.org/manifest.yaml + enable: true +customGames: [] diff --git a/package.json b/package.json index 4d7b5cd4..18d28cd0 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "jsonwebtoken": "^9.0.2", "lodash-es": "^4.17.21", "parse-torrent": "^11.0.17", - "piscina": "^4.7.0", "rc-virtual-list": "^3.16.1", "react-hook-form": "^7.53.0", "react-i18next": "^14.1.0", diff --git a/src/main/constants.ts b/src/main/constants.ts index 8bc2332a..ae4703df 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -2,8 +2,6 @@ import { app } from "electron"; import path from "node:path"; import { SystemPath } from "./services/system-path"; -export const LUDUSAVI_MANIFEST_URL = "https://cdn.losbroxas.org/manifest.yaml"; - export const defaultDownloadsPath = SystemPath.getPath("downloads"); export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index 364e253a..2d716c18 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -1,74 +1,73 @@ -import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services"; +import { CloudSync, HydraApi, logger, WindowManager } from "@main/services"; import fs from "node:fs"; import * as tar from "tar"; import { registerEvent } from "../register-event"; import axios from "axios"; -import os from "node:os"; import path from "node:path"; import { backupsPath } from "@main/constants"; -import type { GameShop } from "@types"; +import type { GameShop, LudusaviBackupMapping } from "@types"; import YAML from "yaml"; import { normalizePath } from "@main/helpers"; import { SystemPath } from "@main/services/system-path"; +import { gamesSublevel, levelKeys } from "@main/level"; -export interface LudusaviBackup { - files: { - [key: string]: { - hash: string; - size: number; - }; - }; -} +export const transformLudusaviBackupPathIntoWindowsPath = ( + backupPath: string, + winePrefixPath?: string | null +) => { + return backupPath.replace(winePrefixPath ?? "", "").replace("drive_c", "C:"); +}; -const replaceLudusaviBackupWithCurrentUser = ( +const restoreLudusaviBackup = ( backupPath: string, title: string, - homeDir: string + homeDir: string, + winePrefixPath?: string | null ) => { const gameBackupPath = path.join(backupPath, title); const mappingYamlPath = path.join(gameBackupPath, "mapping.yaml"); const data = fs.readFileSync(mappingYamlPath, "utf8"); const manifest = YAML.parse(data) as { - backups: LudusaviBackup[]; + backups: LudusaviBackupMapping[]; drives: Record; }; - const currentHomeDir = normalizePath(SystemPath.getPath("home")); + const { userProfilePath, publicProfilePath } = + CloudSync.getProfilePaths(winePrefixPath); - /* Renaming logic */ - if (os.platform() === "win32") { - const mappedHomeDir = path.join( - gameBackupPath, - path.join("drive-C", homeDir.replace("C:", "")) - ); - - if (fs.existsSync(mappedHomeDir)) { - fs.renameSync( - mappedHomeDir, - path.join(gameBackupPath, "drive-C", currentHomeDir.replace("C:", "")) + manifest.backups.forEach((backup) => { + Object.keys(backup.files).forEach((key) => { + const sourcePathWithDrives = Object.entries(manifest.drives).reduce( + (prev, [driveKey, driveValue]) => { + return prev.replace(driveValue, driveKey); + }, + key ); - } - } - const backups = manifest.backups.map((backup: LudusaviBackup) => { - const files = Object.entries(backup.files).reduce((prev, [key, value]) => { - const updatedKey = key.replace(homeDir, currentHomeDir); + const sourcePath = path.join(gameBackupPath, sourcePathWithDrives); - return { - ...prev, - [updatedKey]: value, - }; - }, {}); + logger.info(`Source path: ${sourcePath}`); - return { - ...backup, - files, - }; + const destinationPath = transformLudusaviBackupPathIntoWindowsPath( + key, + null + ) + .replace(homeDir, userProfilePath) + .replace("C:/Users/Public", publicProfilePath); + + logger.info(`Moving ${sourcePath} to ${destinationPath}`); + + fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); + + if (fs.existsSync(destinationPath)) { + fs.unlinkSync(destinationPath); + } + + fs.renameSync(sourcePath, destinationPath); + }); }); - - fs.writeFileSync(mappingYamlPath, YAML.stringify({ ...manifest, backups })); }; const downloadGameArtifact = async ( @@ -78,6 +77,8 @@ const downloadGameArtifact = async ( gameArtifactId: string ) => { try { + const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); + const { downloadUrl, objectKey, homeDir } = await HydraApi.post<{ downloadUrl: string; objectKey: string; @@ -109,34 +110,33 @@ const downloadGameArtifact = async ( response.data.pipe(writer); writer.on("error", (err) => { - logger.error("Failed to write zip", err); + logger.error("Failed to write tar file", err); throw err; }); fs.mkdirSync(backupPath, { recursive: true }); - writer.on("close", () => { - tar - .x({ - file: zipLocation, - cwd: backupPath, - }) - .then(async () => { - replaceLudusaviBackupWithCurrentUser( - backupPath, - objectId, - normalizePath(homeDir) - ); + writer.on("close", async () => { + await tar.x({ + file: zipLocation, + cwd: backupPath, + }); - Ludusavi.restoreBackup(backupPath).then(() => { - WindowManager.mainWindow?.webContents.send( - `on-backup-download-complete-${objectId}-${shop}`, - true - ); - }); - }); + restoreLudusaviBackup( + backupPath, + objectId, + normalizePath(homeDir), + game?.winePrefixPath + ); + + WindowManager.mainWindow?.webContents.send( + `on-backup-download-complete-${objectId}-${shop}`, + true + ); }); } catch (err) { + logger.error("Failed to download game artifact", err); + WindowManager.mainWindow?.webContents.send( `on-backup-download-complete-${objectId}-${shop}`, false diff --git a/src/main/events/library/select-game-wine-prefix.ts b/src/main/events/library/select-game-wine-prefix.ts index c085dbad..ddb0f575 100644 --- a/src/main/events/library/select-game-wine-prefix.ts +++ b/src/main/events/library/select-game-wine-prefix.ts @@ -1,5 +1,6 @@ import { registerEvent } from "../register-event"; import { levelKeys, gamesSublevel } from "@main/level"; +import { Wine } from "@main/services"; import type { GameShop } from "@types"; const selectGameWinePrefix = async ( @@ -8,6 +9,10 @@ const selectGameWinePrefix = async ( objectId: string, winePrefixPath: string | null ) => { + if (winePrefixPath && !Wine.validatePrefix(winePrefixPath)) { + throw new Error("Invalid wine prefix path"); + } + const gameKey = levelKeys.game(shop, objectId); const game = await gamesSublevel.get(gameKey); diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index 163fb23a..695bc95f 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -32,3 +32,5 @@ export const isPortableVersion = () => { export const normalizePath = (str: string) => path.posix.normalize(str).replace(/\\/g, "/"); + +export * from "./reg-parser"; diff --git a/src/main/helpers/reg-parser.ts b/src/main/helpers/reg-parser.ts new file mode 100644 index 00000000..6c08b87e --- /dev/null +++ b/src/main/helpers/reg-parser.ts @@ -0,0 +1,58 @@ +type RegValue = string | number | null; + +interface RegEntry { + path: string; + timestamp?: string; + values: Record; +} + +export function parseRegFile(content: string): RegEntry[] { + const lines = content.split(/\r?\n/); + const entries: RegEntry[] = []; + + let currentPath: string | null = null; + let currentEntry: RegEntry | null = null; + + for (const rawLine of lines) { + const line = rawLine.trim(); + if (!line || line.startsWith(";") || line.startsWith(";;")) continue; + + if (line.startsWith("#")) { + const match = line.match(/^#time=(\w+)/); + if (match && currentEntry) { + currentEntry.timestamp = match[1]; + } + continue; + } + + if (line.startsWith("[")) { + const match = line.match(/^\[(.+?)\](?:\s+\d+)?/); + if (match) { + if (currentEntry) entries.push(currentEntry); + currentPath = match[1]; + currentEntry = { path: currentPath, values: {} }; + } + } else if (currentEntry) { + const kvMatch = line.match(/^"?(.*?)"?=(.*)$/); + if (kvMatch) { + const [, key, rawValue] = kvMatch; + let value: RegValue; + + if (rawValue === '""') { + value = ""; + } else if (rawValue.startsWith("dword:")) { + value = parseInt(rawValue.slice(6), 16); + } else if (rawValue.startsWith('"') && rawValue.endsWith('"')) { + value = rawValue.slice(1, -1); + } else { + value = rawValue; + } + + currentEntry.values[key || "@"] = value; + } + } + } + + if (currentEntry) entries.push(currentEntry); + return entries; +} diff --git a/src/main/index.ts b/src/main/index.ts index ebe59b36..3b223299 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -23,7 +23,9 @@ autoUpdater.logger = logger; const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) app.quit(); -app.commandLine.appendSwitch("--no-sandbox"); +if (process.platform !== "linux") { + app.commandLine.appendSwitch("--no-sandbox"); +} i18n.init({ resources, diff --git a/src/main/main.ts b/src/main/main.ts index 0669d6f2..b0e142fc 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -11,10 +11,10 @@ import { RealDebridClient, Aria2, DownloadManager, - Ludusavi, HydraApi, uploadGamesBatch, startMainLoop, + Ludusavi, } from "@main/services"; export const loadState = async () => { @@ -39,7 +39,7 @@ export const loadState = async () => { TorBoxClient.authorize(userPreferences.torBoxApiToken); } - Ludusavi.addManifestToLudusaviConfig(); + Ludusavi.copyConfigFileToUserData(); await HydraApi.setupApi().then(() => { uploadGamesBatch(); diff --git a/src/main/services/cloud-sync.ts b/src/main/services/cloud-sync.ts index 77b4ac65..1195abe8 100644 --- a/src/main/services/cloud-sync.ts +++ b/src/main/services/cloud-sync.ts @@ -7,7 +7,7 @@ import os from "node:os"; import type { GameShop, User } from "@types"; import { backupsPath } from "@main/constants"; import { HydraApi } from "./hydra-api"; -import { normalizePath } from "@main/helpers"; +import { normalizePath, parseRegFile } from "@main/helpers"; import { logger } from "./logger"; import { WindowManager } from "./window-manager"; import axios from "axios"; @@ -17,6 +17,53 @@ import i18next, { t } from "i18next"; import { SystemPath } from "./system-path"; export class CloudSync { + public static getProfilePaths(winePrefixPath?: string | null) { + const currentHomeDir = normalizePath(SystemPath.getPath("home")); + + if (process.platform === "linux") { + if (!winePrefixPath) { + throw new Error("Wine prefix path is required"); + } + + const userReg = fs.readFileSync( + path.join(winePrefixPath, "user.reg"), + "utf8" + ); + + const entries = parseRegFile(userReg); + const volatileEnvironment = entries.find( + (entry) => entry.path === "Volatile Environment" + ); + + if (!volatileEnvironment) { + throw new Error("Volatile environment not found in user.reg"); + } + + const { values } = volatileEnvironment; + const userProfile = String(values["USERPROFILE"]); + + if (userProfile) { + return { + userProfilePath: path.join( + winePrefixPath, + normalizePath(userProfile.replace("C:", "drive_c")) + ), + publicProfilePath: path.join( + winePrefixPath, + "drive_c", + "users", + "Public" + ), + }; + } + } + + return { + userProfilePath: currentHomeDir, + publicProfilePath: path.join("C:", "Users", "Public"), + }; + } + public static getBackupLabel(automatic: boolean) { const language = i18next.language; @@ -102,7 +149,9 @@ export class CloudSync { shop, objectId, hostname: os.hostname(), - homeDir: normalizePath(SystemPath.getPath("home")), + winePrefixPath: game?.winePrefixPath ?? null, + homeDir: this.getProfilePaths(game?.winePrefixPath ?? null) + .userProfilePath, downloadOptionTitle, platform: os.platform(), label, diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 3d1ab69e..11d97810 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -15,3 +15,4 @@ export * from "./aria2"; export * from "./ws"; export * from "./system-path"; export * from "./library-sync"; +export * from "./wine"; diff --git a/src/main/services/ludusavi.ts b/src/main/services/ludusavi.ts index 81dae6bd..cc1bd8b3 100644 --- a/src/main/services/ludusavi.ts +++ b/src/main/services/ludusavi.ts @@ -1,70 +1,83 @@ import type { GameShop, LudusaviBackup, LudusaviConfig } from "@types"; -import Piscina from "piscina"; import { app } from "electron"; import fs from "node:fs"; import path from "node:path"; import YAML from "yaml"; - -import ludusaviWorkerPath from "../workers/ludusavi.worker?modulePath"; -import { LUDUSAVI_MANIFEST_URL } from "@main/constants"; +import cp from "node:child_process"; import { SystemPath } from "./system-path"; export class Ludusavi { - private static ludusaviPath = path.join( - SystemPath.getPath("appData"), - "ludusavi" - ); - private static ludusaviConfigPath = path.join( - this.ludusaviPath, + private static ludusaviPath = app.isPackaged + ? path.join(process.resourcesPath, "ludusavi") + : path.join(__dirname, "..", "..", "ludusavi"); + + private static binaryPath = path.join(this.ludusaviPath, "ludusavi"); + private static configPath = path.join( + SystemPath.getPath("userData"), "config.yaml" ); - private static binaryPath = app.isPackaged - ? path.join(process.resourcesPath, "ludusavi", "ludusavi") - : path.join(__dirname, "..", "..", "ludusavi", "ludusavi"); - - private static worker = new Piscina({ - filename: ludusaviWorkerPath, - workerData: { - binaryPath: this.binaryPath, - }, - maxThreads: 1, - }); - - static async getConfig() { - if (!fs.existsSync(this.ludusaviConfigPath)) { - await this.worker.run(undefined, { name: "generateConfig" }); - } + public static async getConfig() { const config = YAML.parse( - fs.readFileSync(this.ludusaviConfigPath, "utf-8") + fs.readFileSync(this.configPath, "utf-8") ) as LudusaviConfig; return config; } - static async backupGame( - _shop: GameShop, - objectId: string, - backupPath: string, - winePrefix?: string | null - ): Promise { - return this.worker.run( - { title: objectId, backupPath, winePrefix }, - { name: "backupGame" } - ); + public static async copyConfigFileToUserData() { + fs.cpSync(path.join(this.ludusaviPath, "config.yaml"), this.configPath); } - static async getBackupPreview( + public static async backupGame( + _shop: GameShop, + objectId: string, + backupPath?: string | null, + winePrefix?: string | null, + preview?: boolean + ): Promise { + return new Promise((resolve, reject) => { + const args = [ + "--config", + this.ludusaviPath, + "backup", + objectId, + "--api", + "--force", + ]; + + if (preview) args.push("--preview"); + if (backupPath) args.push("--path", backupPath); + if (winePrefix) args.push("--wine-prefix", winePrefix); + + cp.execFile( + this.binaryPath, + args, + (err: cp.ExecFileException | null, stdout: string) => { + if (err) { + return reject(err); + } + + return resolve(JSON.parse(stdout) as LudusaviBackup); + } + ); + }); + } + + public static async getBackupPreview( _shop: GameShop, objectId: string, winePrefix?: string | null ): Promise { const config = await this.getConfig(); - const backupData = await this.worker.run( - { title: objectId, winePrefix, preview: true }, - { name: "backupGame" } + const backupData = await this.backupGame( + _shop, + objectId, + null, + winePrefix, + true ); const customGame = config.customGames.find( @@ -77,19 +90,6 @@ export class Ludusavi { }; } - static async restoreBackup(backupPath: string) { - return this.worker.run(backupPath, { name: "restoreBackup" }); - } - - static async addManifestToLudusaviConfig() { - const config = await this.getConfig(); - - config.manifest.enable = false; - config.manifest.secondary = [{ url: LUDUSAVI_MANIFEST_URL, enable: true }]; - - fs.writeFileSync(this.ludusaviConfigPath, YAML.stringify(config)); - } - static async addCustomGame(title: string, savePath: string | null) { const config = await this.getConfig(); const filteredGames = config.customGames.filter( @@ -105,6 +105,10 @@ export class Ludusavi { } config.customGames = filteredGames; - fs.writeFileSync(this.ludusaviConfigPath, YAML.stringify(config)); + + fs.writeFileSync( + path.join(this.ludusaviPath, "config.yaml"), + YAML.stringify(config) + ); } } diff --git a/src/main/services/wine.ts b/src/main/services/wine.ts new file mode 100644 index 00000000..8a860ab2 --- /dev/null +++ b/src/main/services/wine.ts @@ -0,0 +1,23 @@ +import fs from "node:fs"; +import path from "node:path"; + +export class Wine { + public static validatePrefix(winePrefixPath: string) { + const requiredFiles = [ + "system.reg", + "user.reg", + "userdef.reg", + "dosdevices", + "drive_c", + ]; + + for (const file of requiredFiles) { + const filePath = path.join(winePrefixPath, file); + if (!fs.existsSync(filePath)) { + return false; + } + } + + return true; + } +} diff --git a/src/main/workers/ludusavi.worker.ts b/src/main/workers/ludusavi.worker.ts deleted file mode 100644 index dbe16968..00000000 --- a/src/main/workers/ludusavi.worker.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { LudusaviBackup } from "@types"; -import cp from "node:child_process"; - -import { workerData } from "node:worker_threads"; - -const { binaryPath } = workerData; - -export const backupGame = ({ - title, - backupPath, - preview = false, - winePrefix, -}: { - title: string; - backupPath: string; - preview?: boolean; - winePrefix?: string; -}) => { - return new Promise((resolve, reject) => { - const args = ["backup", title, "--api", "--force"]; - - if (preview) args.push("--preview"); - if (backupPath) args.push("--path", backupPath); - if (winePrefix) args.push("--wine-prefix", winePrefix); - - cp.execFile( - binaryPath, - args, - (err: cp.ExecFileException | null, stdout: string) => { - if (err) { - return reject(err); - } - - return resolve(JSON.parse(stdout) as LudusaviBackup); - } - ); - }); -}; - -export const restoreBackup = (backupPath: string) => { - const result = cp.execFileSync(binaryPath, [ - "restore", - "--path", - backupPath, - "--api", - "--force", - ]); - - return JSON.parse(result.toString("utf-8")) as LudusaviBackup; -}; - -export const generateConfig = () => { - const result = cp.execFileSync(binaryPath, ["schema", "config"]); - - return JSON.parse(result.toString("utf-8")) as LudusaviBackup; -}; diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 6973f5e7..21d11b33 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -160,7 +160,6 @@ export function GameDetailsContextProvider({ setShopDetails((prev) => { if (!prev) return null; - console.log("assets", assets); return { ...prev, assets, diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 343f3d5d..7267c1fc 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -147,12 +147,16 @@ export function GameOptionsModal({ }); if (filePaths && filePaths.length > 0) { - await window.electron.selectGameWinePrefix( - game.shop, - game.objectId, - filePaths[0] - ); - await updateGame(); + try { + await window.electron.selectGameWinePrefix( + game.shop, + game.objectId, + filePaths[0] + ); + await updateGame(); + } catch (error) { + showErrorToast(t("invalid_wine_prefix_path")); + } } }; diff --git a/src/types/ludusavi.types.ts b/src/types/ludusavi.types.ts index 8432b9f6..64bb90f5 100644 --- a/src/types/ludusavi.types.ts +++ b/src/types/ludusavi.types.ts @@ -40,3 +40,12 @@ export interface LudusaviConfig { registry: []; }[]; } + +export interface LudusaviBackupMapping { + files: { + [key: string]: { + hash: string; + size: number; + }; + }; +} diff --git a/yarn.lock b/yarn.lock index e6300878..5535b804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1850,108 +1850,6 @@ dependencies: "@monaco-editor/loader" "^1.4.0" -"@napi-rs/nice-android-arm-eabi@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936" - integrity sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w== - -"@napi-rs/nice-android-arm64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz#32fc32e9649bd759d2a39ad745e95766f6759d2f" - integrity sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA== - -"@napi-rs/nice-darwin-arm64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz#d3c44c51b94b25a82d45803e2255891e833e787b" - integrity sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA== - -"@napi-rs/nice-darwin-x64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz#f1b1365a8370c6a6957e90085a9b4873d0e6a957" - integrity sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ== - -"@napi-rs/nice-freebsd-x64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz#4280f081efbe0b46c5165fdaea8b286e55a8f89e" - integrity sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw== - -"@napi-rs/nice-linux-arm-gnueabihf@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz#07aec23a9467ed35eb7602af5e63d42c5d7bd473" - integrity sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q== - -"@napi-rs/nice-linux-arm64-gnu@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz#038a77134cc6df3c48059d5a5e199d6f50fb9a90" - integrity sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA== - -"@napi-rs/nice-linux-arm64-musl@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz#715d0906582ba0cff025109f42e5b84ea68c2bcc" - integrity sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw== - -"@napi-rs/nice-linux-ppc64-gnu@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz#ac1c8f781c67b0559fa7a1cd4ae3ca2299dc3d06" - integrity sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q== - -"@napi-rs/nice-linux-riscv64-gnu@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz#b0a430549acfd3920ffd28ce544e2fe17833d263" - integrity sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig== - -"@napi-rs/nice-linux-s390x-gnu@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz#5b95caf411ad72a965885217db378c4d09733e97" - integrity sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg== - -"@napi-rs/nice-linux-x64-gnu@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz#a98cdef517549f8c17a83f0236a69418a90e77b7" - integrity sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA== - -"@napi-rs/nice-linux-x64-musl@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz#5e26843eafa940138aed437c870cca751c8a8957" - integrity sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ== - -"@napi-rs/nice-win32-arm64-msvc@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz#bd62617d02f04aa30ab1e9081363856715f84cd8" - integrity sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg== - -"@napi-rs/nice-win32-ia32-msvc@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz#b8b7aad552a24836027473d9b9f16edaeabecf18" - integrity sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw== - -"@napi-rs/nice-win32-x64-msvc@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz#37d8718b8f722f49067713e9f1e85540c9a3dd09" - integrity sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg== - -"@napi-rs/nice@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@napi-rs/nice/-/nice-1.0.1.tgz#483d3ff31e5661829a1efb4825591a135c3bfa7d" - integrity sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ== - optionalDependencies: - "@napi-rs/nice-android-arm-eabi" "1.0.1" - "@napi-rs/nice-android-arm64" "1.0.1" - "@napi-rs/nice-darwin-arm64" "1.0.1" - "@napi-rs/nice-darwin-x64" "1.0.1" - "@napi-rs/nice-freebsd-x64" "1.0.1" - "@napi-rs/nice-linux-arm-gnueabihf" "1.0.1" - "@napi-rs/nice-linux-arm64-gnu" "1.0.1" - "@napi-rs/nice-linux-arm64-musl" "1.0.1" - "@napi-rs/nice-linux-ppc64-gnu" "1.0.1" - "@napi-rs/nice-linux-riscv64-gnu" "1.0.1" - "@napi-rs/nice-linux-s390x-gnu" "1.0.1" - "@napi-rs/nice-linux-x64-gnu" "1.0.1" - "@napi-rs/nice-linux-x64-musl" "1.0.1" - "@napi-rs/nice-win32-arm64-msvc" "1.0.1" - "@napi-rs/nice-win32-ia32-msvc" "1.0.1" - "@napi-rs/nice-win32-x64-msvc" "1.0.1" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -7722,13 +7620,6 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -piscina@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/piscina/-/piscina-4.7.0.tgz#68936fc77128db00541366531330138e366dc851" - integrity sha512-b8hvkpp9zS0zsfa939b/jXbe64Z2gZv0Ha7FYPNUiDIB1y2AtxcOZdfP8xN8HFjUaqQiT9gRlfjAsoL8vdJ1Iw== - optionalDependencies: - "@napi-rs/nice" "^1.0.1" - plist@3.1.0, plist@^3.0.4, plist@^3.0.5, plist@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9"