1
0
Fork 0

1.4.1 并且不至于1.4.1

Browse Source
This commit is contained in:
yunyun 2026-06-02 15:51:05 +08:00
parent 0f8d799384
commit 231dcf0a4f
25 changed files with 15805 additions and 14 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
node_modules/
dist/
build/
out/
.env
.env.local
.DS_Store
*.log
start.bat
push.bat
GoodPlanCraftLauncher/

View File

@ -1,29 +1,22 @@
# GoodPlanCraftLaunCher (GPCL)
# GoodPlanCraftLauncher (GPCL)
简洁高效的 Minecraft 启动器。
代码优先挂
https://git.caellab.com/yunyun/GoodPlanCraftLaunCher
这个可能很久都没更新了
## 制作方法
使用[Electron](https://www.electronjs.org/zh/docs/latest/)进行制作。
使用 [Node.js](https://nodejs.org) + [Electron](https://www.electronjs.org/zh/docs/latest/) 进行制作。
## 安装
### 从源码构建
```bash
git clone https://github.com/yunyun-3782/GoodPlanCraftLaunCher.git
git clone https://git.caellab.com/yunyun/GoodPlanCraftLauncher.git
```
### 预编译版本
前往 [Releases](https://github.com/yunyun-3782/GoodPlanCraftLaunCher/releases/) 下载对可执行文件。
前往 [Releases](https://git.caellab.com/yunyun/GoodPlanCraftLauncher/releases) 下载对可执行文件。
## 使用
@ -49,14 +42,17 @@ npm run build
## 贡献
欢迎提交 不同意见,同时欢迎开发 衍生 版本。
欢迎提交 [不同意见](https://f.caellab.com/t/problem) 或 [Issue](https://git.caellab.com/yunyun/GoodPlanCraftLauncher/issues) ,同时欢迎开发 衍生 版本。
## 许可证
本项目采用 [CaelLab BY-SA Code License](https://www.caellab.com/license/bysa-code) 授权。
具体可查看 [LICENSE](https://github.com/yunyun-3782/GoodPlanCraftLaunCher/blob/main/LICENSE) 。
具体可查看 [LICENSE](https://git.caellab.com/yunyun/GoodPlanCraftLauncher/src/branch/main/LICENSE) 。
---
很高兴能帮助到你祝你在MC中玩的愉快
---
**GoodPlanCraftLaunCher** — 让 Minecraft 启动更简单。
**GoodPlanCraftLauncher** — 让 Minecraft 启动更简单。

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

3408
main.js Normal file
View File

@ -0,0 +1,3408 @@
/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
* Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
*/
const { app, BrowserWindow, ipcMain, net, dialog, Menu, shell } = require('electron');
const path = require('path');
const { spawn, spawnSync } = require('child_process');
const fs = require('fs');
const os = require('os');
const { tmpdir } = require('os');
const { pipeline } = require('stream/promises');
process.env.TZ = 'Asia/Shanghai';
app.commandLine.appendSwitch('js-flags', '--compile-hints-always');
app.commandLine.appendSwitch('disk-cache-size', '134217728');
function formatTimestamp() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const ms = String(now.getMilliseconds()).padStart(3, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
}
function checkDebuggerAttached() {
try {
if (process.execArgv.some(arg => arg.includes('--inspect') || arg.includes('--debug-brk'))) {
return true;
}
if (process.env.ELECTRON_ENABLE_STACK_DUMPING) {
return true;
}
} catch (e) {}
return false;
}
let isDeveloperMode = false;
let BASE_DIR;
let GAME_DIR;
let CACHE_DIR;
let LOG_DIR;
let LOG_FILE;
let APP_VERSION;
let CONFIG_DIR;
const DEFAULT_SETTINGS = {
game: {
memory: '2',
playerName: 'GPCL_Player',
javaPath: '',
jvmArgs: '',
windowMode: 'windowed'
},
appearance: {
theme: 'light',
scale: '110'
},
download: {
maxConcurrent: 64,
javaMirror: 'tsinghua',
customJavaMirror: ''
},
advanced: {
autoCheckUpdate: true,
preventMultipleLaunch: true,
autoClearLogs: true,
logRetentionValue: 7,
logRetentionUnit: 'day',
playStartupAnimation: true,
developerMode: false
}
};
function parseIni(text) {
const result = {};
let currentSection = null;
const lines = text.split(/\r?\n/);
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('#')) {
continue;
}
const sectionMatch = trimmed.match(/^\[(.+)\]$/);
if (sectionMatch) {
currentSection = sectionMatch[1].toLowerCase();
if (!result[currentSection]) {
result[currentSection] = {};
}
continue;
}
const keyValueMatch = trimmed.match(/^([^=]+)=(.*)$/);
if (keyValueMatch && currentSection) {
const key = keyValueMatch[1].trim();
let value = keyValueMatch[2].trim();
if (value.toLowerCase() === 'true') {
value = true;
} else if (value.toLowerCase() === 'false') {
value = false;
} else {
const num = Number(value);
if (!isNaN(num)) {
value = num;
}
}
result[currentSection][key] = value;
}
}
return result;
}
function stringifyIni(obj) {
let result = '';
for (const [section, sectionData] of Object.entries(obj)) {
result += `[${section}]\n`;
for (const [key, value] of Object.entries(sectionData)) {
result += `${key}=${value}\n`;
}
result += '\n';
}
return result.trim();
}
function mergeWithDefaults(settings) {
const merged = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
for (const [section, sectionData] of Object.entries(settings)) {
if (merged[section]) {
for (const [key, value] of Object.entries(sectionData)) {
if (key in merged[section]) {
merged[section][key] = value;
}
}
}
}
return merged;
}
function loadSettings() {
try {
const configPath = path.join(BASE_DIR, 'gpcl', 'config', 'setting.ini');
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, 'utf8');
const parsed = parseIni(content);
const merged = mergeWithDefaults(parsed);
writeLog('配置已加载');
return merged;
}
} catch (e) {
writeLog(`加载配置失败: ${e.message}`);
}
writeLog('使用默认配置');
return JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
}
function saveSettings(settings) {
try {
const configDir = path.join(BASE_DIR, 'gpcl', 'config');
const configPath = path.join(configDir, 'setting.ini');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
const content = stringifyIni(settings);
fs.writeFileSync(configPath, content, 'utf8');
writeLog('配置已保存');
return true;
} catch (e) {
writeLog(`保存配置失败: ${e.message}`);
return false;
}
}
function initPaths() {
BASE_DIR = app.getPath('userData');
GAME_DIR = path.join(BASE_DIR, '.minecraft');
CACHE_DIR = path.join(BASE_DIR, 'gpcl', 'cache');
LOG_DIR = path.join(BASE_DIR, 'gpcl', 'log');
CONFIG_DIR = path.join(BASE_DIR, 'gpcl', 'config');
const startupTime = new Date().toISOString().replace(/:/g, '-').replace('.', '_');
LOG_FILE = path.join(LOG_DIR, `${startupTime}.log`);
const requiredDirs = [
path.join(BASE_DIR, 'gpcl', 'config'),
path.join(BASE_DIR, 'gpcl', 'users'),
LOG_DIR,
CACHE_DIR
];
for (const dir of requiredDirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
}
function cleanupExpiredLogs() {
try {
const settings = loadSettings();
const autoClearLogs = settings.advanced?.autoClearLogs;
if (!autoClearLogs) {
writeLog('[日志清理] 自动清理日志已禁用');
return;
}
const retentionValue = settings.advanced?.logRetentionValue || 7;
const retentionUnit = settings.advanced?.logRetentionUnit || 'day';
// 计算过期时间
const now = Date.now();
let expireTime;
switch (retentionUnit) {
case 'hour':
expireTime = now - retentionValue * 60 * 60 * 1000;
break;
case 'day':
expireTime = now - retentionValue * 24 * 60 * 60 * 1000;
break;
case 'month':
expireTime = now - retentionValue * 30 * 24 * 60 * 60 * 1000;
break;
case 'year':
expireTime = now - retentionValue * 365 * 24 * 60 * 60 * 1000;
break;
default:
expireTime = now - retentionValue * 24 * 60 * 60 * 1000;
}
if (!fs.existsSync(LOG_DIR)) {
writeLog('[日志清理] 日志目录不存在');
return;
}
const files = fs.readdirSync(LOG_DIR);
let cleanedCount = 0;
for (const file of files) {
if (!file.endsWith('.log')) continue;
const filePath = path.join(LOG_DIR, file);
try {
const stat = fs.statSync(filePath);
if (stat.mtime.getTime() < expireTime) {
fs.unlinkSync(filePath);
cleanedCount++;
}
} catch (e) {
writeLog(`[日志清理] 清理文件失败: ${file} - ${e.message}`);
}
}
if (cleanedCount > 0) {
writeLog(`[日志清理] 已清理 ${cleanedCount} 个过期日志文件`);
} else {
writeLog('[日志清理] 没有需要清理的过期日志');
}
} catch (e) {
writeLog(`[日志清理] 清理过程出错: ${e.message}`);
}
}
let mainWindow;
APP_VERSION = require('./package.json').version;
function writeLog(message) {
const timestamp = formatTimestamp();
const logLine = `[${timestamp}] ${message}\n`;
try {
if (LOG_FILE) {
fs.appendFileSync(LOG_FILE, logLine, 'utf8');
}
console.log(`[${timestamp}] LOG:`, message);
} catch (e) {
console.error(`[${formatTimestamp()}] 写入日志失败:`, e);
}
}
let isDownloading = false;
let currentDownloadVersion = null;
let shouldCancelDownload = false;
let currentDownloadType = null; // 'game' or 'java'
let currentDownloadPath = null; // 下载的文件或文件夹路径
let javaDownloadResolve = null; // Java下载对话框的Promise resolve
let javaDownloadReject = null; // Java下载对话框的Promise reject
let isJavaDownloading = false; // Java下载中标志
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 700,
minWidth: 800,
minHeight: 600,
maxWidth: 1920,
maxHeight: 1080,
frame: false,
title: 'GoodPlanCraftLauncher',
icon: path.join(__dirname, 'gpcl', 'logo.ico'),
backgroundColor: '#0d0d15',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
devTools: true
}
});
try { Menu.setApplicationMenu(null); } catch (e) {}
mainWindow.setMenuBarVisibility(false);
mainWindow.setAutoHideMenuBar(true);
mainWindow.loadFile(path.join(__dirname, 'renderer', 'splash.html'));
// 关闭窗口前拦截
mainWindow.on('close', (event) => {
if (isDownloading || isJavaDownloading) {
event.preventDefault();
// 发送询问到渲染进程
mainWindow.webContents.send('confirm-close-while-downloading');
}
});
mainWindow.webContents.on('before-input-event', (event, input) => {
if (isDeveloperMode) return;
const key = input.key && input.key.toLowerCase();
const ctrlOrCmd = input.control || input.meta;
if ((ctrlOrCmd && key === 'r') || input.key === 'F5') event.preventDefault();
if ((input.control && input.shift && key === 'i') || input.key === 'F12' || (input.meta && input.alt && key === 'i')) event.preventDefault();
});
mainWindow.webContents.on('context-menu', (e) => {
if (!isDeveloperMode) e.preventDefault();
});
}
app.on('web-contents-created', (event, contents) => {
try {
contents.on('context-menu', (e) => {
if (!isDeveloperMode) e.preventDefault();
});
contents.on('before-input-event', (event, input) => {
if (isDeveloperMode) return;
const key = input.key && input.key.toLowerCase();
const ctrlOrCmd = input.control || input.meta;
if ((ctrlOrCmd && key === 'r') || input.key === 'F5') event.preventDefault();
if ((input.control && input.shift && key === 'i') || input.key === 'F12' || (input.meta && input.alt && key === 'i')) event.preventDefault();
});
} catch (e) {}
});
// 单实例锁:当检测到第二个实例时,凸显已有窗口
app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
function createGPCLShortcut() {
try {
const startMenuDir = path.join(app.getPath('appData'), 'Microsoft', 'Windows', 'Start Menu', 'Programs');
const shortcutPath = path.join(startMenuDir, 'GPCL.lnk');
if (fs.existsSync(shortcutPath)) {
return;
}
if (app.isPackaged) {
const exePath = process.execPath;
shell.writeShortcutLink(shortcutPath, 'create', {
target: exePath,
description: 'GoodPlanCraftLauncher - Minecraft 启动器',
icon: exePath,
iconIndex: 0
});
writeLog('[快捷方式] 已创建 GPCL 快捷方式');
}
} catch (e) {
writeLog(`[快捷方式] 创建 GPCL 快捷方式失败: ${e.message}`);
}
}
app.whenReady().then(() => {
// 检测是否是 electron-builder 的测试运行
const isTesting = process.argv.includes('--squirrel-install') ||
process.argv.includes('--squirrel-obsolete') ||
process.argv.includes('--squirrel-updated') ||
process.argv.includes('--testing');
if (isTesting) {
console.log('[启动] 检测到构建测试模式,跳过窗口创建');
app.quit();
return;
}
// 提前初始化 BASE_DIR 以读取配置文件
BASE_DIR = app.getPath('userData');
// 检查是否启用防止多次启动
let preventMultipleLaunch = true;
try {
const configPath = path.join(BASE_DIR, 'gpcl', 'config', 'setting.ini');
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, 'utf8');
const parsed = parseIni(content);
if (parsed.advanced) {
if (parsed.advanced.preventMultipleLaunch === false || parsed.advanced.preventmultiplelaunch === false) {
preventMultipleLaunch = false;
}
}
}
} catch (e) {}
if (preventMultipleLaunch) {
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
writeLog('检测到已有实例运行,退出');
app.quit();
return;
}
}
initPaths();
cleanupExpiredLogs();
try {
const devConfigPath = path.join(BASE_DIR, 'gpcl', 'config', 'setting.ini');
if (fs.existsSync(devConfigPath)) {
const devContent = fs.readFileSync(devConfigPath, 'utf8');
const devParsed = parseIni(devContent);
if (devParsed.advanced && devParsed.advanced.developerMode === true) {
isDeveloperMode = true;
writeLog('[开发者模式] 启动时已启用');
}
}
} catch (e) {}
createGPCLShortcut();
writeLog(`GPCL v${APP_VERSION} 启动`);
console.log('日志文件路径:', LOG_FILE);
createWindow();
});
app.on('window-all-closed', () => app.quit());
ipcMain.handle('show-message-dialog', async (_, options) => {
if (!mainWindow) return { success: false };
const result = await dialog.showMessageBox(mainWindow, {
type: options.type || 'info',
title: options.title || '提示',
message: options.message || '',
detail: options.detail || '',
buttons: options.buttons || ['确定'],
defaultId: options.defaultId || 0,
cancelId: options.cancelId || 0,
noLink: true
});
return { success: true, response: result.response };
});
// Java版本 → 官方runtime目录名映射
const JAVA_RUNTIME_MAP = {
"8": "jre-legacy",
"16": "java-runtime-beta",
"17": "java-runtime-gamma",
"21": "java-runtime-delta",
"25": "java-runtime-epsilon"
};
// Java版本 → 下载URL的key映射
const JAVA_DOWNLOAD_KEYS = {
"8": "jre-legacy",
"17": "java-runtime-gamma",
"21": "java-runtime-delta",
"25": "java-runtime-epsilon"
};
// 可选择的Java版本列表
const JAVA_VERSIONS_AVAILABLE = ["8", "17", "21", "25"];
// 获取Java运行时目录官方目录结构使用绝对路径
function getJavaRuntimeDir(javaVersion) {
const runtimeName = JAVA_RUNTIME_MAP[javaVersion] || `jre${javaVersion}`;
// 使用官方Minecraft运行时目录C:\Users\用户名\AppData\Roaming\.minecraft\runtime
return path.join(os.homedir(), 'AppData', 'Roaming', '.minecraft', 'runtime', runtimeName);
}
// 在目录中查找java.exe自动拼接\bin\使用java.exe以便捕获错误输出
function findJavaExecutable(runtimeDir) {
// 优先使用 bin\java.exe可以捕获控制台输出用于调试
const javaPath = path.join(runtimeDir, 'bin', 'java.exe');
if (fs.existsSync(javaPath)) {
try {
fs.accessSync(javaPath, fs.constants.R_OK);
return javaPath;
} catch (e) {
return null;
}
}
// 兜底:递归查找
return findJavaExecutableInDir(runtimeDir);
}
// 检查指定版本的Java是否已安装
async function checkJavaInstalled(javaVersion) {
const runtimeDir = getJavaRuntimeDir(javaVersion);
if (fs.existsSync(runtimeDir)) {
const exe = findJavaExecutable(runtimeDir);
if (exe) {
return { installed: true, javaPath: exe };
}
}
return { installed: false, javaPath: null };
}
// 获取指定Minecraft版本所需的Java版本
function getJavaVersionForMCVersion(versionId) {
// Minecraft 1.16及以下: Java 8
// Minecraft 1.17: Java 16
// Minecraft 1.18-1.20: Java 17
// Minecraft 1.21+: Java 21
const parts = versionId.split('.');
let major = parseInt(parts[0], 10);
let minor = parseInt(parts[1], 10);
// 如果版本号是 "26" 这种没有点的格式
if (isNaN(major)) major = 1;
// 处理像 "26" 这种格式(没有 minor
if (isNaN(minor)) {
// 26 及以上使用 Java 25
if (major >= 26) return '25';
// 21-25 使用 Java 21
if (major >= 21) return '21';
// 否则默认 Java 17
return '17';
}
// 处理 1.x.x 的旧版本
if (major === 1) {
if (minor < 17) return '8';
if (minor === 17) return '16';
if (minor <= 20) return '17';
return '21';
}
// 处理 2.x.x 和更大的版本21, 24, 26, ...
// 26 及以上使用 Java 25
if (major >= 26) return '25';
// 21-25 使用 Java 21
if (major >= 21) return '21';
return '17';
}
// 获取版本数据中指定的Java版本
function getJavaVersionFromVersionData(versionData) {
if (versionData?.javaVersion?.majorVersion) {
return String(versionData.javaVersion.majorVersion);
}
if (versionData?.javaVersion?.component) {
const m = versionData.javaVersion.component.match(/\d+/);
if (m) return m[0];
}
return getJavaVersionForMCVersion(versionData.id);
}
// 查找本地Java运行时只查找不下载
async function ensureJavaRuntime(version) {
const javaVersion = typeof version === 'string' ? version : String(version);
const runtimeDir = getJavaRuntimeDir(javaVersion);
writeLog(`[Java] 查找 Java ${javaVersion} 运行时: ${runtimeDir}`);
if (fs.existsSync(runtimeDir)) {
const exe = findJavaExecutable(runtimeDir);
if (exe) {
writeLog(`[Java] 找到 Java ${javaVersion}: ${exe}`);
return exe;
}
}
writeLog(`[Java] 未找到 Java ${javaVersion} 运行时`);
return null;
}
// 完整的Java运行时保证包括下载和安装
async function ensureJavaRuntimeWithInstall(version, mainWindow) {
const javaVersion = typeof version === 'string' ? version : String(version);
// 先检查是否已安装
const installed = await checkJavaInstalled(javaVersion);
if (installed.installed && installed.javaPath) {
writeLog(`[Java] Java ${javaVersion} 已安装: ${installed.javaPath}`);
return { success: true, javaPath: installed.javaPath };
}
// 如果未安装,触发下载流程
writeLog(`[Java] Java ${javaVersion} 未安装,开始下载流程`);
return null;
}
// 用户确认关闭并清理
ipcMain.handle('confirm-close-download', async () => {
if (isDownloading && currentDownloadVersion) {
shouldCancelDownload = true;
try {
// 清理游戏版本下载
const versionDir = currentDownloadPath || path.join(GAME_DIR, 'versions', currentDownloadVersion);
const cancelFilePath = path.join(versionDir, 'CancelDownload.txt');
const cancelContent = `GoodPlanCraftLauncher ${APP_VERSION}\n取消时间:${formatTimestamp()}\n\n正确的关闭\n您在下载完成前正确地关闭了GPCL。`;
if (fs.existsSync(versionDir)) {
fs.writeFileSync(cancelFilePath, cancelContent, 'utf8');
const files = fs.readdirSync(versionDir);
for (const file of files) {
const filePath = path.join(versionDir, file);
if (file !== 'CancelDownload.txt') {
if (fs.statSync(filePath).isDirectory()) {
fs.rmSync(filePath, { recursive: true, force: true });
} else {
fs.unlinkSync(filePath);
}
}
}
}
writeLog(`已清理游戏版本下载目录: ${versionDir}`);
} catch (e) {
console.error('清理下载失败:', e);
writeLog(`清理下载失败: ${e.message}`);
}
isDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
}
if (mainWindow) {
mainWindow.destroy();
}
return { success: true };
});
// 取消关闭
ipcMain.handle('cancel-close', () => {
return { success: true };
});
// 取消启动游戏
let launchCancelFlag = false;
ipcMain.handle('cancel-launch', () => {
launchCancelFlag = true;
writeLog('[启动] 用户取消启动');
return { success: true };
});
// 检查更新使用Node.js网络请求避免CORS问题
const https = require('https');
ipcMain.handle('check-for-updates', async () => {
return new Promise((resolve) => {
const url = 'https://gamets.caellab.com/gpcl/data.json';
writeLog('[更新] 开始检查更新...');
https.get(url, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const json = JSON.parse(data);
writeLog('[更新] 获取到数据: ' + data);
resolve({ success: true, data: json });
} catch (e) {
writeLog('[更新] JSON解析失败: ' + e.message);
resolve({ success: false, error: 'JSON解析失败: ' + e.message });
}
});
}).on('error', (e) => {
writeLog('[更新] 请求失败: ' + e.message);
resolve({ success: false, error: e.message });
});
});
});
// 获取当前版本号从package.json读取
ipcMain.handle('get-app-version', () => {
try {
const packagePath = path.join(__dirname, 'package.json');
if (fs.existsSync(packagePath)) {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
return pkg.version || '1.0.0';
}
} catch (e) {
writeLog('[版本] 读取package.json失败: ' + e.message);
}
return '1.0.0';
});
// 打开外部链接
ipcMain.handle('open-external', (_, url) => {
try {
shell.openExternal(url);
return { success: true };
} catch (e) {
writeLog('[外部链接] 打开失败: ' + e.message);
return { success: false, error: e.message };
}
});
// IPC: 卸载Java运行时
ipcMain.handle('uninstall-java', async (_, javaVersion) => {
try {
const runtimeDir = getJavaRuntimeDir(javaVersion);
writeLog(`[Java] 开始卸载 Java ${javaVersion},目录: ${runtimeDir}`);
if (!fs.existsSync(runtimeDir)) {
return { success: false, error: `Java ${javaVersion} 未安装` };
}
fs.rmSync(runtimeDir, { recursive: true, force: true });
writeLog(`[Java] Java ${javaVersion} 已卸载,目录已删除: ${runtimeDir}`);
if (mainWindow) {
mainWindow.webContents.send('java-uninstalled', { javaVersion });
}
return { success: true };
} catch (e) {
writeLog(`[Java] 卸载 Java ${javaVersion} 失败: ${e.message}`);
return { success: false, error: e.message };
}
});
// 获取Java镜像源设置
ipcMain.handle('get-java-mirror-settings', () => {
try {
const settingsPath = path.join(GAME_DIR, 'settings', 'java_mirror.json');
if (fs.existsSync(settingsPath)) {
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
}
return {
mirror: 'tsinghua',
customUrl: ''
};
} catch (e) {
return {
mirror: 'tsinghua',
customUrl: ''
};
}
});
// 获取Java版本对应的下载URL
ipcMain.handle('get-java-download-url', async (_, javaVersion) => {
try {
const settingsPath = path.join(GAME_DIR, 'settings', 'java_mirror.json');
let settings = {
mirror: 'tsinghua',
customUrl: ''
};
if (fs.existsSync(settingsPath)) {
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
}
// 根据镜像源生成URL
const url = getJavaDownloadUrl(javaVersion, settings.mirror, settings.customUrl);
writeLog(`[Java] 获取下载URL: Java ${javaVersion} - ${url}`);
return { success: true, url };
} catch (e) {
writeLog(`[Java] 获取下载URL失败: ${e.message}`);
return { success: false, error: e.message };
}
});
// 根据镜像源生成下载URL
function getJavaDownloadUrl(javaVersion, mirror, customUrl) {
// 清华大学镜像
const tsinghuaMirrors = {
"8": "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/8/jre/x64/windows/OpenJDK8U-jre_x64_windows_hotspot_8u492b09.zip",
"17": "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/17/jre/x64/windows/OpenJDK17U-jre_x64_windows_hotspot_17.0.19_10.zip",
"21": "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/21/jre/x64/windows/OpenJDK21U-jre_x64_windows_hotspot_21.0.11_10.zip",
"25": "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/25/jre/x64/windows/OpenJDK25U-jre_x64_windows_hotspot_25.0.3_9.zip"
};
// 如果使用清华大学镜像
if (mirror === 'tsinghua') {
return tsinghuaMirrors[javaVersion] || tsinghuaMirrors["17"];
}
// 如果使用自定义镜像
if (mirror === 'custom' && customUrl) {
// 替换{v}变量
let url = customUrl;
// Java版本号8, 17, 21等
url = url.replace(/\{v\}/gi, javaVersion);
// 完整版本号的映射
const versionMap = {
"8": "8u492b09",
"17": "17.0.19_10",
"21": "21.0.11_10",
"25": "25.0.3_9"
};
url = url.replace(/\{version\}/gi, versionMap[javaVersion] || javaVersion);
return url;
}
// 默认使用清华大学镜像
return tsinghuaMirrors[javaVersion] || tsinghuaMirrors["17"];
}
// 保存Java镜像源设置
function saveJavaMirrorSettings(mirror, customUrl) {
try {
const settingsDir = path.join(GAME_DIR, 'settings');
if (!fs.existsSync(settingsDir)) {
fs.mkdirSync(settingsDir, { recursive: true });
}
const settingsPath = path.join(settingsDir, 'java_mirror.json');
fs.writeFileSync(settingsPath, JSON.stringify({
mirror,
customUrl
}, null, 2));
writeLog(`[Java] 镜像源设置已保存: ${mirror}`);
return true;
} catch (e) {
writeLog(`[Java] 保存镜像源设置失败: ${e.message}`);
return false;
}
}
// 取消下载(只清理文件,不关闭窗口)
ipcMain.handle('cancel-download-only', async () => {
writeLog('[取消下载] 开始取消下载流程');
// 设置取消标志
shouldCancelDownload = true;
// 清空下载队列
const pendingCount = downloadQueue.length;
downloadQueue = [];
writeLog(`[取消下载] 已清空 ${pendingCount} 个待处理下载任务`);
// 等待当前活跃的下载完成最多等待2秒
const waitStart = Date.now();
while (activeDownloads > 0 && (Date.now() - waitStart) < 2000) {
await new Promise(r => setTimeout(r, 100));
}
writeLog(`[取消下载] 活跃下载数: ${activeDownloads}`);
if ((isDownloading && currentDownloadVersion) || isJavaDownloading) {
try {
if (currentDownloadType === 'game' && currentDownloadPath) {
// 清理游戏版本下载
const versionDir = currentDownloadPath;
if (fs.existsSync(versionDir)) {
const files = fs.readdirSync(versionDir);
for (const file of files) {
const filePath = path.join(versionDir, file);
try {
if (fs.statSync(filePath).isDirectory()) {
fs.rmSync(filePath, { recursive: true, force: true });
} else {
fs.unlinkSync(filePath);
}
} catch (e) {
writeLog(`清理文件失败: ${filePath} - ${e.message}`);
}
}
}
writeLog(`[取消下载] 已清理游戏版本下载目录: ${versionDir}`);
} else if (currentDownloadType === 'java' && currentDownloadPath) {
// 清理Java下载
const javaFile = currentDownloadPath;
if (fs.existsSync(javaFile)) {
try {
fs.unlinkSync(javaFile);
writeLog(`[取消下载] 已清理Java下载文件: ${javaFile}`);
} catch (e) {
writeLog(`清理Java文件失败: ${e.message}`);
}
}
}
} catch (e) {
console.error('清理下载失败:', e);
writeLog(`清理下载失败: ${e.message}`);
}
// 重置所有下载状态
isDownloading = false;
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
activeDownloads = 0;
totalDownloadFiles = 0;
completedDownloadFiles = 0;
}
// 通知渲染进程下载已取消
for (const w of BrowserWindow.getAllWindows()) {
w.webContents.send('download-cancelled');
}
writeLog('[取消下载] 取消下载流程完成');
return { success: true };
});
// 显示Java版本选择对话框
ipcMain.handle('show-java-install-dialog', async (_, options) => {
try {
const { mcVersion, recommendedJava } = options;
// 构建版本选择列表,按推荐程度排序
const versionPriority = {
[recommendedJava]: 0,
"17": 1,
"21": 2,
"25": 3,
"8": 4
};
const sortedVersions = [...JAVA_VERSIONS_AVAILABLE].sort((a, b) => {
return (versionPriority[a] || 99) - (versionPriority[b] || 99);
});
const result = await new Promise((resolve, reject) => {
javaDownloadResolve = resolve;
javaDownloadReject = reject;
// 发送消息给渲染进程显示对话框
if (mainWindow) {
mainWindow.webContents.send('show-java-install-dialog', {
mcVersion,
recommendedJava,
availableVersions: sortedVersions
});
} else {
reject(new Error('窗口未初始化'));
}
});
return result;
} catch (e) {
writeLog(`Java安装对话框错误: ${e.message}`);
return { success: false, cancelled: true };
}
});
// 用户在对话框中选择Java版本
ipcMain.handle('select-java-version', async (_, data) => {
const { selectedVersion, cancelled } = data;
if (cancelled) {
if (javaDownloadResolve) {
javaDownloadResolve({ success: false, cancelled: true });
javaDownloadResolve = null;
javaDownloadReject = null;
}
return { success: false, cancelled: true };
}
if (selectedVersion && javaDownloadResolve) {
javaDownloadResolve({ success: true, selectedVersion });
javaDownloadResolve = null;
javaDownloadReject = null;
return { success: true, selectedVersion };
}
return { success: false, cancelled: true };
});
// 下载并安装Java运行时
async function downloadAndInstallJava(javaVersion) {
const runtimeDir = getJavaRuntimeDir(javaVersion);
writeLog(`[Java] 开始安装 Java ${javaVersion} 运行时`);
try {
// 检查是否已存在
if (fs.existsSync(runtimeDir)) {
const exe = findJavaExecutable(runtimeDir);
if (exe) {
writeLog(`[Java] Java ${javaVersion} 已存在,无需下载`);
return { success: true, javaPath: exe };
}
}
// 获取Java运行时的下载URL支持自定义镜像源
const settingsPath = path.join(GAME_DIR, 'settings', 'java_mirror.json');
let settings = { mirror: 'tsinghua', customUrl: '' };
try {
if (fs.existsSync(settingsPath)) {
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
}
} catch (e) {}
const downloadUrl = getJavaDownloadUrl(javaVersion, settings.mirror, settings.customUrl);
writeLog(`[Java] 使用镜像源: ${settings.mirror}, URL: ${downloadUrl}`);
// 使用Electron webContents下载更可靠
const result = await downloadJavaWithWebContents(javaVersion, downloadUrl);
return result;
} catch (e) {
writeLog(`[Java] Java ${javaVersion} 安装失败: ${e.message}`);
// 清理失败的安装
if (fs.existsSync(runtimeDir)) {
try {
fs.rmSync(runtimeDir, { recursive: true, force: true });
} catch (e2) {}
}
return { success: false, error: e.message };
}
}
// 使用Electron webContents下载Java推荐方法
async function downloadJavaWithWebContents(javaVersion, downloadUrl) {
const runtimeName = JAVA_RUNTIME_MAP[javaVersion];
const runtimeDir = getJavaRuntimeDir(javaVersion);
const tempDir = path.join(CACHE_DIR, 'java_temp');
const tempFile = path.join(tempDir, `java-${javaVersion}.zip`);
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
writeLog(`[Java] 使用Electron下载 Java ${javaVersion}: ${downloadUrl}`);
// 设置下载路径,以便取消时能正确删除
currentDownloadVersion = `java-${javaVersion}`;
currentDownloadType = 'java';
currentDownloadPath = tempFile;
shouldCancelDownload = false;
isJavaDownloading = true;
return new Promise((resolve, reject) => {
const win = BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0];
if (!win) {
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
reject(new Error('没有可用的窗口'));
return;
}
// 使用webContents.downloadURL
win.webContents.downloadURL(downloadUrl, true);
// 监听下载开始
win.webContents.session.once('will-download', (event, item) => {
// 设置下载路径
item.setSavePath(tempFile);
const totalBytes = item.getTotalBytes();
writeLog(`[Java] 开始下载,总大小: ${totalBytes} bytes`);
// 监听进度
item.on('updated', (event, state) => {
if (state === 'progressing') {
const received = item.getReceivedBytes();
const percent = totalBytes > 0 ? Math.floor((received / totalBytes) * 100) : 0;
for (const w of BrowserWindow.getAllWindows()) {
w.webContents.send('download-progress', {
label: `Java ${javaVersion}`,
percent: percent,
bytesDownloaded: received,
current: completedDownloadFiles,
total: totalDownloadFiles
});
}
if (shouldCancelDownload) {
item.cancel();
writeLog(`[Java] 下载已取消`);
}
}
});
// 监听完成
item.on('done', (event, state) => {
if (state === 'cancelled') {
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
reject(new Error('下载已取消'));
return;
}
if (state === 'interrupted') {
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
reject(new Error('下载中断'));
return;
}
if (state === 'completed') {
writeLog(`[Java] 下载完成,开始解压...`);
// 验证文件
const fileStats = fs.statSync(tempFile);
const fileSize = fileStats.size;
if (fileSize < 1024) {
reject(new Error(`下载失败:文件太小 (${fileSize} bytes)`));
return;
}
// 检查ZIP头
const header = Buffer.alloc(4);
const fd = fs.openSync(tempFile, 'r');
fs.readSync(fd, header, 0, 4, 0);
fs.closeSync(fd);
if (header.toString('hex') !== '504b0304') {
reject(new Error('下载失败文件不是有效的ZIP格式'));
return;
}
writeLog(`[Java] 文件验证通过,大小: ${fileSize} bytes`);
// 解压
const parentDir = path.dirname(runtimeDir);
if (!fs.existsSync(parentDir)) {
fs.mkdirSync(parentDir, { recursive: true });
}
if (fs.existsSync(runtimeDir)) {
fs.rmSync(runtimeDir, { recursive: true, force: true });
}
try {
const AdmZip = require('adm-zip');
const zip = new AdmZip(tempFile);
zip.extractAllTo(parentDir, false);
writeLog(`[Java] 解压完成`);
} catch (zipError) {
reject(new Error(`解压失败: ${zipError.message}`));
return;
}
// 重命名目录
const entries = fs.readdirSync(parentDir);
let extractedDir = null;
for (const entry of entries) {
const entryPath = path.join(parentDir, entry);
if (fs.statSync(entryPath).isDirectory()) {
if (entry.includes('jdk') || entry.includes('jre')) {
const binPath = path.join(entryPath, 'bin', 'java.exe');
if (fs.existsSync(binPath)) {
extractedDir = entryPath;
writeLog(`[Java] 找到目录: ${entry}`);
break;
}
}
}
}
if (extractedDir && extractedDir !== runtimeDir) {
if (fs.existsSync(runtimeDir)) {
fs.rmSync(runtimeDir, { recursive: true, force: true });
}
fs.renameSync(extractedDir, runtimeDir);
writeLog(`[Java] 已重命名为: ${runtimeName}`);
}
// 清理临时文件
try {
fs.unlinkSync(tempFile);
} catch (e) {}
// 验证安装
const javaPath = findJavaExecutable(runtimeDir);
if (!javaPath) {
reject(new Error('Java运行时安装失败未找到java.exe'));
return;
}
writeLog(`[Java] Java ${javaVersion} 安装成功: ${javaPath}`);
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
resolve({ success: true, javaPath });
} else {
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
reject(new Error(`下载失败: ${state}`));
}
});
});
// 超时处理10分钟
setTimeout(() => {
if (isJavaDownloading) {
writeLog(`[Java] 下载超时`);
isJavaDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
reject(new Error('下载超时'));
}
}, 600000);
}).catch((e) => {
writeLog(`[Java] Java ${javaVersion} 安装失败: ${e.message}`);
if (fs.existsSync(runtimeDir)) {
try {
fs.rmSync(runtimeDir, { recursive: true, force: true });
} catch (e2) {}
}
return { success: false, error: e.message };
});
}
// 从Minecraft元数据获取Java运行时的下载信息
async function getJavaRuntimeDownloadInfo(javaVersion) {
try {
const runtimeKey = JAVA_DOWNLOAD_KEYS[javaVersion];
if (!runtimeKey) {
writeLog(`[Java] 不支持的Java版本: ${javaVersion}`);
return null;
}
writeLog(`[Java] 正在查找 Java ${javaVersion} (${runtimeKey}) 的下载信息...`);
// 使用清华大学开源软件镜像站TUNA的Adoptium镜像
const runtimeInfo = {
"8": {
id: "jre-legacy",
version: "8",
url: "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/8/jre/x64/windows/OpenJDK8U-jre_x64_windows_hotspot_8u492b09.zip"
},
"17": {
id: "java-runtime-gamma",
version: "17",
url: "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/17/jre/x64/windows/OpenJDK17U-jre_x64_windows_hotspot_17.0.19_10.zip"
},
"21": {
id: "java-runtime-delta",
version: "21",
url: "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/21/jre/x64/windows/OpenJDK21U-jre_x64_windows_hotspot_21.0.11_10.zip"
},
"25": {
id: "java-runtime-epsilon",
version: "25",
url: "https://mirrors.tuna.tsinghua.edu.cn/Adoptium/25/jre/x64/windows/OpenJDK25U-jre_x64_windows_hotspot_25.0.3_9.zip"
}
};
const javaInfo = runtimeInfo[javaVersion];
if (javaInfo) {
writeLog(`[Java] ✓ 找到 Java ${javaVersion} 下载信息`);
writeLog(`[Java] URL: ${javaInfo.url}`);
return {
url: javaInfo.url,
name: javaInfo.id,
sha1: null,
size: null
};
}
writeLog(`[Java] ✗ 无法找到 Java ${javaVersion} 的下载信息`);
return null;
} catch (e) {
writeLog(`[Java] 获取Java运行时下载信息失败: ${e.message}`);
return null;
}
}
// 获取MC版本推荐的Java版本
function getRecommendedJavaVersion(mcVersion) {
// Minecraft 1.16.5及以下: Java 8
// Minecraft 1.17: Java 16/17
// Minecraft 1.18-1.20.4: Java 17
// Minecraft 1.20.5+: Java 21
// Minecraft 1.21+: Java 21/25
// Minecraft 26+: Java 21
const parts = mcVersion.split('.');
let major = parseInt(parts[0], 10);
let minor = parseInt(parts[1], 10);
let patch = parts.length > 2 ? parseInt(parts[2], 10) : 0;
// 如果版本号是 "26" 这种没有点的格式
if (isNaN(major)) major = 1;
// 处理像 "26" 这种格式(只有 major 没有 minor
if (isNaN(minor)) {
// 26 及以上使用 Java 25
if (major >= 26) return '25';
// 21-25 使用 Java 21
if (major >= 21) return '21';
return '17';
}
// 处理 1.x.x 的旧版本
if (major === 1) {
if (minor < 17) return '8';
if (minor === 17) return '17';
if (minor <= 20) return '17';
if (minor === 21 && patch <= 4) return '21';
if (minor === 21 && patch > 4) return '21';
if (minor >= 22) return '21';
return '17';
}
// 处理 2.x.x 和更大的版本21, 24, 26, ...
// 26 及以上使用 Java 25
if (major >= 26) return '25';
// 21-25 使用 Java 21
if (major >= 21) return '21';
return '17';
}
function findJavaExecutableInDir(rootDir) {
const stack = [rootDir];
while (stack.length > 0) {
const current = stack.pop();
if (!fs.existsSync(current)) continue;
const javaw = path.join(current, 'bin', 'javaw.exe');
const java = path.join(current, 'bin', 'java.exe');
if (fs.existsSync(javaw)) return javaw;
if (fs.existsSync(java)) return java;
try {
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const e of entries) {
if (e.isDirectory()) stack.push(path.join(current, e.name));
}
} catch (e) {}
}
return null;
}
async function fetchText(url) {
return new Promise((resolve, reject) => {
let finished = false;
const fallbackToHttps = (reason) => {
if (finished) return;
try {
const https = require('https');
const req2 = https.get(url, { timeout: 20000 }, res2 => {
let body = '';
res2.on('data', d => body += d);
res2.on('end', () => { finished = true; resolve(body); });
res2.on('error', err2 => { if (!finished) { finished = true; reject(err2); } });
});
req2.on('timeout', () => { req2.destroy(); if (!finished) { finished = true; reject('超时'); } });
req2.on('error', err2 => { if (!finished) { finished = true; reject(err2); } });
} catch (e2) {
if (!finished) { finished = true; reject(e2); }
}
};
try {
const req = net.request(url);
const to = setTimeout(() => { try { req.abort(); } catch {} ; fallbackToHttps('timeout'); }, 20000);
req.on('response', res => {
clearTimeout(to);
let body = '';
res.on('data', d => body += d);
res.on('end', () => { if (!finished) { finished = true; resolve(body); } });
res.on('error', err => { if (!finished) fallbackToHttps(err); });
});
req.on('error', err => { if (!finished) fallbackToHttps(err); });
req.end();
} catch (e) {
fallbackToHttps(e);
}
});
}
let MAX_CONCURRENT = 128;
let downloadQueue = [];
let activeDownloads = 0;
let totalDownloadFiles = 0;
let completedDownloadFiles = 0;
let currentDownloadSpeed = 0;
async function downloadWithPool(url, dest, label) {
return new Promise((resolve, reject) => {
downloadQueue.push({ url, dest, label, resolve, reject });
processDownloadQueue();
});
}
async function processDownloadQueue() {
while (downloadQueue.length > 0 && activeDownloads < MAX_CONCURRENT && !shouldCancelDownload) {
const task = downloadQueue.shift();
activeDownloads++;
downloadFileInternal(task.url, task.dest, task.label)
.then(() => {
task.resolve();
})
.catch(err => {
task.reject(err);
})
.finally(() => {
activeDownloads--;
completedDownloadFiles++;
processDownloadQueue();
});
}
// 如果取消下载,拒绝队列中所有剩余任务
if (shouldCancelDownload) {
while (downloadQueue.length > 0) {
const task = downloadQueue.shift();
task.reject(new Error('下载已取消'));
}
// 通知渲染进程下载已取消
for (const w of BrowserWindow.getAllWindows()) {
w.webContents.send('download-cancelled');
}
}
}
async function downloadFile(url, dest, label) {
if (fs.existsSync(dest)) {
const stat = fs.statSync(dest);
if (stat.size > 1024) {
for (const w of BrowserWindow.getAllWindows()) {
w.webContents.send('download-progress', {
label,
percent: 100,
current: completedDownloadFiles,
total: totalDownloadFiles
});
}
return;
}
}
await downloadWithPool(url, dest, label);
}
async function downloadFileInternal(url, dest, label) {
return new Promise((resolve, reject) => {
const dir = path.dirname(dest);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
// 确保使用二进制模式写入
const file = fs.createWriteStream(dest, { encoding: null });
let loaded = 0;
let total = 0;
const fallbackToHttps = (reason) => {
writeLog(`[下载] ${label} HTTP请求失败: ${reason}尝试HTTPS`);
try {
const https = require('https');
const options = {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
};
const req2 = https.get(url, options, res2 => {
// 处理重定向
if (res2.statusCode >= 300 && res2.statusCode < 400 && res2.headers.location) {
writeLog(`[下载] ${label} 重定向到: ${res2.headers.location}`);
file.close();
// 递归处理重定向
downloadFileInternal(res2.headers.location, dest, label).then(resolve).catch(reject);
return;
}
total = parseInt(res2.headers['content-length'] || '0', 10);
// 检查内容类型
const contentType = res2.headers['content-type'] || '';
if (!contentType.includes('zip') && !contentType.includes('octet-stream')) {
writeLog(`[下载] ${label} 警告: 内容类型是 ${contentType}可能不是ZIP文件`);
}
writeLog(`[下载] ${label} 开始下载,大小: ${total} bytes`);
res2.on('data', d => {
if (shouldCancelDownload) {
req2.destroy();
file.destroy();
reject(new Error('下载已取消'));
return;
}
loaded += d.length;
file.write(d);
const p = total > 0 ? Math.floor((loaded / total) * 100) : 0;
for (const w of BrowserWindow.getAllWindows()) {
w.webContents.send('download-progress', {
label,
percent: p,
bytesDownloaded: loaded,
current: completedDownloadFiles,
total: totalDownloadFiles
});
}
});
res2.on('end', () => {
file.end();
writeLog(`[下载] ${label} 下载完成,实际大小: ${loaded} bytes`);
resolve();
});
res2.on('error', err2 => {
file.destroy();
reject(err2);
});
});
req2.on('timeout', () => { req2.destroy(); reject('超时'); });
req2.on('error', err2 => {
file.destroy();
reject(err2);
});
} catch (e2) {
file.destroy();
reject(e2);
}
};
try {
const req = net.request(url);
const to = setTimeout(() => { try { req.abort(); } catch {} ; fallbackToHttps('timeout'); }, 60000);
// 设置User-Agent头
req.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
req.on('response', res => {
clearTimeout(to);
// 处理重定向
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
writeLog(`[下载] ${label} 重定向到: ${res.headers.location}`);
file.close();
// 递归处理重定向
downloadFileInternal(res.headers.location, dest, label).then(resolve).catch(reject);
return;
}
total = parseInt(res.headers['content-length'] || '0', 10);
// 检查内容类型
const contentType = res.headers['content-type'] || '';
if (!contentType.includes('zip') && !contentType.includes('octet-stream')) {
writeLog(`[下载] ${label} 警告: 内容类型是 ${contentType}可能不是ZIP文件`);
}
writeLog(`[下载] ${label} 开始下载,大小: ${total} bytes`);
res.on('data', d => {
if (shouldCancelDownload) {
try { req.abort(); } catch {}
file.destroy();
reject(new Error('下载已取消'));
return;
}
loaded += d.length;
file.write(d);
const p = total > 0 ? Math.floor((loaded / total) * 100) : 0;
for (const w of BrowserWindow.getAllWindows()) {
w.webContents.send('download-progress', {
label,
percent: p,
bytesDownloaded: loaded,
current: completedDownloadFiles,
total: totalDownloadFiles
});
}
});
res.on('end', () => {
file.end();
writeLog(`[下载] ${label} 下载完成,实际大小: ${loaded} bytes`);
resolve();
});
res.on('error', err => {
file.destroy();
fallbackToHttps(err);
});
});
req.on('error', err => {
file.destroy();
fallbackToHttps(err);
});
req.end();
} catch (e) {
file.destroy();
fallbackToHttps(e);
}
});
}
// 带超时的下载包装器,超时后会抛出错误让调用方尝试下一个源
async function downloadFileInternalWithTimeout(url, dest, label, timeoutMs) {
return new Promise((resolve, reject) => {
let settled = false;
const timer = setTimeout(() => {
if (!settled) {
settled = true;
reject(new Error(`连接超时 (${timeoutMs / 1000}秒)`));
}
}, timeoutMs);
downloadFileInternal(url, dest, label)
.then(result => {
if (!settled) {
settled = true;
clearTimeout(timer);
resolve(result);
}
})
.catch(err => {
if (!settled) {
settled = true;
clearTimeout(timer);
reject(err);
}
});
});
}
async function installVersion(versionId, maxConcurrent = 20, downloadPath = null) {
const targetDir = downloadPath || path.join(GAME_DIR, 'versions', versionId);
const versionDir = targetDir;
const cancelFilePath = path.join(versionDir, 'CancelDownload.txt');
if (fs.existsSync(cancelFilePath)) {
try { fs.unlinkSync(cancelFilePath); } catch (e) {}
}
// 重置取消标志
shouldCancelDownload = false;
isDownloading = true;
currentDownloadVersion = versionId;
currentDownloadType = 'game';
currentDownloadPath = targetDir;
writeLog(`开始下载:${versionId}${targetDir}`);
try {
MAX_CONCURRENT = maxConcurrent;
downloadQueue = [];
activeDownloads = 0;
totalDownloadFiles = 0;
completedDownloadFiles = 0;
const versionDir = path.join(GAME_DIR, 'versions', versionId);
if (!fs.existsSync(versionDir)) fs.mkdirSync(versionDir, { recursive: true });
const versionJsonPath = path.join(versionDir, versionId + '.json');
const versionJarPath = path.join(versionDir, versionId + '.jar');
// 获取版本清单
const manifest = JSON.parse(await fetchText('https://piston-meta.mojang.com/mc/game/version_manifest_v2.json'));
const vInfo = manifest.versions.find(v => v.id === versionId);
if (!vInfo) throw new Error('版本不存在: ' + versionId);
// 下载版本JSON
if (!fs.existsSync(versionJsonPath)) {
const vData = await fetchText(vInfo.url);
fs.writeFileSync(versionJsonPath, vData);
}
const versionData = JSON.parse(fs.readFileSync(versionJsonPath, 'utf8'));
// 下载客户端JAR
if (!fs.existsSync(versionJarPath)) {
await downloadFile(versionData.downloads.client.url, versionJarPath, '客户端');
}
// 检查是否取消
if (shouldCancelDownload) {
writeLog(`下载取消:${versionId}`);
throw new Error('下载已取消');
}
// 下载资源索引
const assetIndexPath = path.join(GAME_DIR, 'assets', 'indexes', versionData.assetIndex.id + '.json');
if (!fs.existsSync(assetIndexPath)) {
await downloadFile(versionData.assetIndex.url, assetIndexPath, '资源索引');
}
// 检查是否取消
if (shouldCancelDownload) {
writeLog(`下载取消:${versionId}`);
throw new Error('下载已取消');
}
// 等待资源索引下载完成并读取
let assetIndexData;
for (let i = 0; i < 10; i++) {
try {
assetIndexData = JSON.parse(fs.readFileSync(assetIndexPath, 'utf8'));
break;
} catch (e) {
await new Promise(r => setTimeout(r, 500));
}
}
if (!assetIndexData) throw new Error('无法读取资源索引');
// 收集所有需要下载的文件
const toDownload = [];
// 资源文件
for (const [name, obj] of Object.entries(assetIndexData.objects)) {
const hash = obj.hash;
const prefix = hash.substring(0, 2);
const assetPath = path.join(GAME_DIR, 'assets', 'objects', prefix, hash);
if (!fs.existsSync(assetPath)) {
toDownload.push({
url: `https://resources.download.minecraft.net/${prefix}/${hash}`,
dest: assetPath,
label: `资源: ${name}`
});
}
}
// 库文件
for (const lib of versionData.libraries) {
if (lib.downloads?.artifact) {
const libPath = path.join(GAME_DIR, 'libraries', lib.downloads.artifact.path);
if (!fs.existsSync(libPath)) {
toDownload.push({
url: lib.downloads.artifact.url,
dest: libPath,
label: `库: ${lib.name}`
});
}
}
if (lib.downloads?.classifiers) {
for (const [cls, info] of Object.entries(lib.downloads.classifiers)) {
if (cls.includes('natives-windows')) {
const libPath = path.join(GAME_DIR, 'libraries', info.path);
if (!fs.existsSync(libPath)) {
toDownload.push({
url: info.url,
dest: libPath,
label: `原生库: ${lib.name}`
});
}
}
}
}
}
totalDownloadFiles = toDownload.length;
completedDownloadFiles = 0;
// 并发下载所有文件
const downloadPromises = toDownload.map(item => downloadFile(item.url, item.dest, item.label));
await Promise.all(downloadPromises);
// 解压原生库
const nativesDir = path.join(GAME_DIR, 'versions', versionId, 'natives');
if (!fs.existsSync(nativesDir)) fs.mkdirSync(nativesDir, { recursive: true });
for (const lib of versionData.libraries) {
if (lib.downloads?.classifiers) {
for (const [cls, info] of Object.entries(lib.downloads.classifiers)) {
if (cls.includes('natives-windows')) {
const libPath = path.join(GAME_DIR, 'libraries', info.path);
if (fs.existsSync(libPath)) {
const AdmZip = require('adm-zip');
const zip = new AdmZip(libPath);
zip.extractAllTo(nativesDir, true);
}
}
}
}
}
if (shouldCancelDownload) {
writeLog(`下载取消:${versionId}`);
throw new Error('下载已取消');
}
writeLog(`下载完成:${versionId}`);
// 更新版本最后启动时间
updateVersionLastPlayed(versionId, targetDir);
return { success: true };
} catch (e) {
writeLog(`下载失败:${versionId} - ${e.message}`);
return { success: false, error: e.message };
} finally {
isDownloading = false;
currentDownloadVersion = null;
currentDownloadType = null;
currentDownloadPath = null;
shouldCancelDownload = false;
downloadQueue = [];
activeDownloads = 0;
totalDownloadFiles = 0;
completedDownloadFiles = 0;
}
}
// 更新版本的最后启动时间
function updateVersionLastPlayed(versionId, targetDir) {
try {
const versionDir = targetDir || path.join(GAME_DIR, 'versions', versionId);
const gpclDir = path.join(versionDir, 'gpcl');
const configPath = path.join(gpclDir, 'config.json');
let settings = {};
// 读取现有配置
if (fs.existsSync(configPath)) {
try {
settings = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
settings = {};
}
}
// 更新最后启动时间
settings.lastPlayed = new Date().toISOString();
// 确保目录存在
if (!fs.existsSync(gpclDir)) {
fs.mkdirSync(gpclDir, { recursive: true });
}
// 保存配置
fs.writeFileSync(configPath, JSON.stringify(settings, null, 2), 'utf8');
writeLog(`[版本] ${versionId} 最后启动时间已更新: ${settings.lastPlayed}`);
} catch (e) {
writeLog(`[版本] 更新 ${versionId} 最后启动时间失败: ${e.message}`);
}
}
ipcMain.handle('scan-versions', async (_, gameDir, silent = false) => {
const targetDir = gameDir ? path.resolve(gameDir) : GAME_DIR;
const versionsDir = path.join(targetDir, 'versions');
if (!fs.existsSync(versionsDir)) {
if (!silent) writeLog('扫描本地版本未找到versions目录');
return [];
}
const entries = fs.readdirSync(versionsDir, { withFileTypes: true });
const versions = [];
const skippedVersions = [];
for (const entry of entries) {
if (entry.isDirectory()) {
const versionDir = path.join(versionsDir, entry.name);
try {
const files = fs.readdirSync(versionDir);
// 检查是否只有一个 CancelDownload.txt 文件
if (files.length === 1 && files[0] === 'CancelDownload.txt') {
skippedVersions.push(entry.name);
if (!silent) writeLog(`扫描本地版本:跳过空目录 "${entry.name}"(仅包含 CancelDownload.txt`);
continue;
}
// 查找所有 .json 文件
const jsonFiles = files.filter(f => f.endsWith('.json'));
// 如果没有 .json 文件,跳过
if (jsonFiles.length === 0) {
skippedVersions.push(entry.name);
if (!silent) writeLog(`扫描本地版本:跳过 "${entry.name}"(无 .json 文件)`);
continue;
}
// 收集所有版本(包括原版和带模组加载器的版本)
// 原版: 1.21.1 -> versionId = "1.21.1"
// Forge: 1.21.1-forge -> versionId = "1.21.1-forge"
// Fabric: 1.21.1-fabric -> versionId = "1.21.1-fabric"
const baseVersionId = entry.name; // 文件夹名
const mcVersion = baseVersionId; // MC 版本号
// 总是添加原版
if (jsonFiles.includes(`${baseVersionId}.json`)) {
versions.push(baseVersionId);
}
// 检查是否有模组加载器版本
for (const jsonFile of jsonFiles) {
const jsonVersionId = jsonFile.replace('.json', '');
// 如果不是原版,且不在列表中,添加它
if (jsonVersionId !== baseVersionId && !versions.includes(jsonVersionId)) {
versions.push(jsonVersionId);
}
}
} catch (e) {
writeLog(`扫描本地版本:读取目录 "${entry.name}" 失败 - ${e.message}`);
}
}
}
if (skippedVersions.length > 0 && !silent) {
writeLog(`扫描本地版本:跳过 ${skippedVersions.length} 个无效目录 - [${skippedVersions.join(', ')}]`);
}
if (!silent) writeLog(`扫描本地版本:共 ${versions.length} 个版本 - [${versions.join(', ')}]`);
return versions;
});
ipcMain.handle('get-version-manifest', async () => {
try {
if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
const cacheFile = path.join(CACHE_DIR, 'version_manifest.json');
let data;
let fromCache = false;
if (fs.existsSync(cacheFile)) {
const stat = fs.statSync(cacheFile);
const age = Date.now() - stat.mtimeMs;
if (age < 10 * 60 * 1000) {
data = fs.readFileSync(cacheFile, 'utf8');
fromCache = true;
}
}
if (!data) {
try {
data = await fetchText('https://piston-meta.mojang.com/mc/game/version_manifest_v2.json');
fs.writeFileSync(cacheFile, data);
writeLog('远程拉取版本列表:成功');
} catch (e) {
writeLog(`远程拉取版本列表:失败 - ${e.message}`);
return [];
}
} else {
writeLog(`读取本地缓存版本列表:成功`);
}
const parsed = JSON.parse(data);
// 返回所有类型的版本,包括正式版、快照版、旧测试版
writeLog(`版本列表:共 ${parsed.versions.length} 个版本`);
return parsed.versions;
} catch (e) {
writeLog(`版本列表解析失败:${e.message}`);
return [];
}
});
ipcMain.handle('download-version', (event, versionId, maxConcurrent, downloadPath) => installVersion(versionId, maxConcurrent, downloadPath));
ipcMain.handle('download-with-modloader', (event, versionId, loaderType, loaderVersion, maxConcurrent) =>
installVersionWithModLoader(versionId, loaderType, loaderVersion, maxConcurrent));
ipcMain.handle('get-version-config', (event, versionId) => readVersionConfig(versionId));
ipcMain.handle('get-version-display-name', (event, versionId) => generateVersionDisplayName(versionId));
ipcMain.handle('get-launch-display-name', (event, versionId) => generateLaunchDisplayName(versionId));
ipcMain.handle('delete-version', async (_, versionId) => {
try {
const versionDir = path.join(GAME_DIR, 'versions', versionId);
if (!fs.existsSync(versionDir)) {
return { success: false, error: '版本不存在' };
}
writeLog(`[删除版本] 开始删除版本: ${versionId}`);
fs.rmSync(versionDir, { recursive: true, force: true });
writeLog(`[删除版本] 版本 ${versionId} 删除成功`);
return { success: true };
} catch (e) {
writeLog(`[删除版本] 删除版本失败: ${e.message}`);
return { success: false, error: e.message };
}
});
ipcMain.handle('get-version-settings', async (_, versionId) => {
try {
const versionDir = path.join(GAME_DIR, 'versions', versionId);
const gpclDir = path.join(versionDir, 'gpcl');
const configPath = path.join(gpclDir, 'config.json');
if (!fs.existsSync(configPath)) {
return { success: true, settings: null };
}
const content = fs.readFileSync(configPath, 'utf8');
const settings = JSON.parse(content);
return { success: true, settings };
} catch (e) {
writeLog(`[版本设置] 读取版本 ${versionId} 设置失败: ${e.message}`);
return { success: false, error: e.message, settings: null };
}
});
ipcMain.handle('save-version-settings', async (_, versionId, settings) => {
try {
const versionDir = path.join(GAME_DIR, 'versions', versionId);
const gpclDir = path.join(versionDir, 'gpcl');
if (!fs.existsSync(gpclDir)) {
fs.mkdirSync(gpclDir, { recursive: true });
}
const configPath = path.join(gpclDir, 'config.json');
// 保存设置
fs.writeFileSync(configPath, JSON.stringify(settings, null, 2), 'utf8');
writeLog(`[版本设置] 版本 ${versionId} 设置已保存`);
return { success: true };
} catch (e) {
writeLog(`[版本设置] 保存版本 ${versionId} 设置失败: ${e.message}`);
return { success: false, error: e.message };
}
});
ipcMain.handle('get-game-dir', () => {
return GAME_DIR;
});
ipcMain.handle('get-player-name', () => {
try {
const playerFile = path.join(app.getPath('userData'), 'gpcl', 'player.json');
if (fs.existsSync(playerFile)) {
const data = JSON.parse(fs.readFileSync(playerFile, 'utf8'));
return data.name || 'GPCL_Player';
}
} catch (e) {
console.error('读取玩家名失败:', e);
}
return 'GPCL_Player';
});
ipcMain.handle('save-player-name', (_, name) => {
try {
const gpclDir = path.join(app.getPath('userData'), 'gpcl');
if (!fs.existsSync(gpclDir)) fs.mkdirSync(gpclDir, { recursive: true });
const playerFile = path.join(gpclDir, 'player.json');
fs.writeFileSync(playerFile, JSON.stringify({ name: name || 'GPCL_Player' }));
return { success: true };
} catch (e) {
console.error('保存玩家名失败:', e);
return { success: false, error: e.message };
}
});
ipcMain.handle('window-minimize', () => {
if (mainWindow) mainWindow.minimize();
return { success: true };
});
ipcMain.handle('window-focus', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
mainWindow.focus();
}
return { success: true };
});
ipcMain.handle('window-close', () => {
if (mainWindow) mainWindow.close();
return { success: true };
});
ipcMain.handle('select-directory', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory']
});
if (result.canceled || result.filePaths.length === 0) {
return null;
}
return result.filePaths[0];
});
// 检查指定Minecraft版本所需的Java是否已安装
ipcMain.handle('check-java', async (_, versionOrJavaVersion) => {
try {
// 判断是Minecraft版本号还是Java版本号
const isMcVersion = versionOrJavaVersion && versionOrJavaVersion.match(/^\d+\.\d+/);
if (isMcVersion) {
// Minecraft版本检查该版本需要的Java
const versionId = versionOrJavaVersion;
const jsonPath = path.join(GAME_DIR, 'versions', versionId, versionId + '.json');
if (!fs.existsSync(jsonPath)) {
return { success: false, error: '版本未下载' };
}
const versionData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
const javaVersion = getJavaVersionFromVersionData(versionData);
const result = await checkJavaInstalled(javaVersion);
return {
success: true,
installed: result.installed,
javaVersion: javaVersion,
javaPath: result.javaPath
};
} else {
// Java版本直接检查该版本是否安装
const javaVersion = versionOrJavaVersion;
const result = await checkJavaInstalled(javaVersion);
return {
success: true,
installed: result.installed,
javaVersion: javaVersion,
javaPath: result.javaPath
};
}
} catch (e) {
writeLog(`检查Java失败${e.message}`);
return { success: false, error: e.message };
}
});
// 解析 Minecraft 启动参数模板
function parseMCArguments(template, vars) {
let result = template;
for (const [key, value] of Object.entries(vars)) {
result = result.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value);
}
return result;
}
// 智能分割参数(处理引号内的空格)
function splitArgs(str) {
const args = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < str.length; i++) {
const c = str[i];
if (c === '"') {
inQuotes = !inQuotes;
} else if (c === ' ' && !inQuotes) {
if (current.length > 0) {
args.push(current);
current = '';
}
} else {
current += c;
}
}
if (current.length > 0) {
args.push(current);
}
return args;
}
// 检查库规则是否允许当前平台
function checkLibraryRules(rules) {
if (!rules || rules.length === 0) return true;
let allowed = false;
for (const rule of rules) {
let match = true;
if (rule.os) {
const platform = os.platform();
if (rule.os.name === 'windows' && platform !== 'win32') match = false;
if (rule.os.name === 'linux' && platform !== 'linux') match = false;
if (rule.os.name === 'osx' && platform !== 'darwin') match = false;
}
if (match) {
allowed = (rule.action === 'allow');
}
}
return allowed;
}
// 构建 Minecraft 启动参数
function buildLaunchArgs(vd, gameDir, versionId, username, windowMode, memoryMB, serverIp) {
const cp = [];
// 优先使用 downloads.client 的 jar 路径
const clientJar = path.join(gameDir, 'versions', versionId, `${versionId}.jar`);
if (fs.existsSync(clientJar)) {
cp.push(clientJar);
}
// 添加库(带平台规则过滤)
let missingLibs = 0;
for (const lib of vd.libraries || []) {
if (!checkLibraryRules(lib.rules)) continue;
// 普通 artifact
if (lib.downloads?.artifact?.path) {
const libPath = path.join(gameDir, 'libraries', lib.downloads.artifact.path);
if (fs.existsSync(libPath)) {
cp.push(libPath);
} else {
missingLibs++;
writeLog(`[启动] 库文件缺失: ${lib.downloads.artifact.path}`);
}
}
// NativesWindows 平台)
if (lib.downloads?.classifiers?.['natives-windows']?.path) {
const nativePath = path.join(gameDir, 'libraries', lib.downloads.classifiers['natives-windows'].path);
if (fs.existsSync(nativePath)) {
cp.push(nativePath);
} else {
missingLibs++;
writeLog(`[启动] Native库缺失: ${lib.downloads.classifiers['natives-windows'].path}`);
}
}
}
if (missingLibs > 0) {
writeLog(`[启动] 警告: ${missingLibs} 个库文件缺失`);
}
const nativesDir = path.join(gameDir, 'versions', versionId, 'natives');
const assetsDir = path.join(gameDir, 'assets');
const jvmArgs = [
`-Djava.library.path=${nativesDir}`,
'-cp', cp.join(';'),
];
// 添加内存参数
if (memoryMB) {
jvmArgs.unshift(`-Xmx${memoryMB}M`);
writeLog(`[启动] 已设置最大内存: ${memoryMB}M`);
} else {
writeLog(`[启动] 未设置内存限制`);
}
const vars = {
auth_player_name: username,
version_name: versionId,
game_directory: gameDir,
assets_root: assetsDir,
assets_index_name: vd.assetIndex?.id || vd.assets || versionId,
auth_uuid: '00000000000000000000000000000000',
auth_access_token: 'offline',
auth_session: 'offline',
user_type: 'legacy',
user_properties: '{}',
version_type: 'GPCL'
};
let gameArgs = [];
// 新版格式 (1.13+)
if (vd.arguments?.game) {
for (const arg of vd.arguments.game) {
if (typeof arg === 'string') {
gameArgs.push(parseMCArguments(arg, vars));
}
// 忽略条件参数(简化处理)
}
}
// 旧版格式 (1.12.2 及以下)
else if (vd.minecraftArguments) {
const parsed = parseMCArguments(vd.minecraftArguments, vars);
gameArgs = splitArgs(parsed);
}
// 兜底
else {
gameArgs = [
'--username', username,
'--version', versionId,
'--gameDir', gameDir,
'--assetsDir', assetsDir,
'--assetIndex', vd.assetIndex?.id || vd.assets || versionId,
'--accessToken', 'offline',
'--userType', 'legacy'
];
}
// 根据窗口模式添加参数
if (windowMode === 'fullscreen') {
gameArgs.push('--fullscreen');
writeLog(`[启动] 已添加全屏参数: --fullscreen`);
} else if (windowMode === 'borderless') {
// 无边框窗口模式:使用全屏但无边框的分辨率
gameArgs.push('--fullscreen');
gameArgs.push('--width');
gameArgs.push('1920');
gameArgs.push('--height');
gameArgs.push('1080');
writeLog(`[启动] 已添加无边框窗口参数: --fullscreen --width 1920 --height 1080`);
} else if (windowMode === 'minimized') {
// 最小化模式,不添加特殊参数,但会在游戏启动后自动最小化
writeLog(`[启动] 已设置最小化模式`);
}
// 如果有服务器IP添加服务器连接参数
if (serverIp && serverIp.trim()) {
const ipParts = serverIp.trim().split(':');
const serverAddress = ipParts[0];
const serverPort = ipParts[1] || '25565';
gameArgs.push('--server');
gameArgs.push(serverAddress);
gameArgs.push('--port');
gameArgs.push(serverPort);
writeLog(`[启动] 已添加服务器连接参数: --server ${serverAddress} --port ${serverPort}`);
}
return { jvmArgs, gameArgs, mainClass: vd.mainClass };
}
ipcMain.handle('launch-minecraft', async (_, opt) => {
try {
// 重置取消标志
launchCancelFlag = false;
const { versionId, username, gameDir: rawGameDir, windowMode } = opt;
// 使用传入的路径或默认的 GAME_DIR
const gameDir = rawGameDir ? path.resolve(rawGameDir) : GAME_DIR;
// 窗口模式fullscreen, windowed, borderless
const mode = windowMode || 'windowed';
writeLog(`启动游戏:玩家 ${username},版本 ${versionId},目录: ${gameDir},窗口模式: ${mode}`);
// 检查是否已取消
if (launchCancelFlag) {
writeLog('[启动] 用户已取消启动');
return { success: false, error: '启动已取消', cancelled: true };
}
const jsonPath = path.join(gameDir, 'versions', versionId, `${versionId}.json`);
if (!fs.existsSync(jsonPath)) {
writeLog(`启动游戏失败:版本 ${versionId} 未下载`);
return { success: false, error: '请先下载游戏' };
}
const vd = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
const javaVersion = getJavaVersionFromVersionData(vd);
// 检查是否已取消
if (launchCancelFlag) {
writeLog('[启动] 用户已取消启动');
return { success: false, error: '启动已取消', cancelled: true };
}
// 加载设置
const settings = loadSettings();
let memoryMB = null;
let actualWindowMode = mode;
let serverIp = null;
// 首先检查是否有版本特定设置
try {
const versionConfigPath = path.join(gameDir, 'versions', versionId, 'gpcl', 'config.json');
if (fs.existsSync(versionConfigPath)) {
const versionSettings = JSON.parse(fs.readFileSync(versionConfigPath, 'utf8'));
// 处理内存设置
if (versionSettings.memory && versionSettings.memory !== 'global') {
const memoryVal = parseFloat(versionSettings.memory);
if (!isNaN(memoryVal)) {
if (versionSettings.memory === '0.5') {
memoryMB = 512; // 0.5GB = 512MB
} else {
memoryMB = Math.floor(memoryVal * 1024);
}
writeLog(`[启动] 使用版本内存设置: ${memoryMB}M`);
}
}
// 处理窗口模式设置
if (versionSettings.windowMode && versionSettings.windowMode !== 'global') {
actualWindowMode = versionSettings.windowMode;
writeLog(`[启动] 使用版本窗口模式: ${actualWindowMode}`);
}
// 处理启动模式和服务器IP
if (versionSettings.startupMode === 'join' && versionSettings.serverIp) {
serverIp = versionSettings.serverIp;
writeLog(`[启动] 使用版本启动模式: 加入服务器 ${serverIp}`);
}
}
} catch (e) {
writeLog(`[启动] 读取版本设置失败: ${e.message}`);
}
// 如果没有版本特定设置,使用全局设置
if (memoryMB === null && settings.game?.memory) {
const globalMemory = parseFloat(settings.game.memory);
if (!isNaN(globalMemory)) {
memoryMB = Math.floor(globalMemory * 1024);
writeLog(`[启动] 使用全局内存设置: ${memoryMB}M`);
}
}
// 检查是否已取消
if (launchCancelFlag) {
writeLog('[启动] 用户已取消启动');
return { success: false, error: '启动已取消', cancelled: true };
}
// 检查Java是否已安装
let java = await ensureJavaRuntime(javaVersion);
// 检查是否已取消在Java检查后
if (launchCancelFlag) {
writeLog('[启动] 用户已取消启动');
return { success: false, error: '启动已取消', cancelled: true };
}
// 如果Java未安装自动下载
if (!java) {
writeLog(`[Java] Java ${javaVersion} 未安装,自动开始下载`);
// 发送通知给渲染进程
if (mainWindow) {
mainWindow.webContents.send('java-auto-download-start', {
javaVersion: javaVersion,
mcVersion: versionId
});
}
// 自动下载并安装Java期间检查取消标志
const installResult = await downloadAndInstallJava(javaVersion);
// 检查是否已取消在Java下载后
if (launchCancelFlag) {
writeLog('[启动] 用户已取消启动Java下载完成后');
return { success: false, error: '启动已取消', cancelled: true };
}
if (installResult.success) {
java = installResult.javaPath;
writeLog(`[Java] Java ${javaVersion} 安装成功: ${java}`);
// 发送安装完成通知
if (mainWindow) {
mainWindow.webContents.send('java-auto-download-complete', {
javaVersion: javaVersion,
javaPath: java
});
}
} else {
writeLog(`[Java] Java ${javaVersion} 安装失败: ${installResult.error}`);
return {
success: false,
error: `Java ${javaVersion} 安装失败: ${installResult.error}`
};
}
}
// 最终检查是否已取消(在启动游戏前)
if (launchCancelFlag) {
writeLog('[启动] 用户已取消启动(即将启动游戏前)');
return { success: false, error: '启动已取消', cancelled: true };
}
const { jvmArgs, gameArgs, mainClass } = buildLaunchArgs(vd, gameDir, versionId, username, actualWindowMode, memoryMB, serverIp);
const args = [...jvmArgs, mainClass, ...gameArgs];
writeLog(`[启动] Java: ${java}`);
writeLog(`[启动] 主类: ${mainClass}`);
writeLog(`[启动] 窗口模式: ${actualWindowMode}`);
writeLog(`[启动] 参数数: ${args.length}`);
writeLog(`[启动] JVM参数: ${JSON.stringify(jvmArgs)}`);
writeLog(`[启动] 游戏参数: ${JSON.stringify(gameArgs.slice(0, 10))}...`);
writeLog(`[启动] Classpath条目数: ${jvmArgs[2].split(';').length}`);
writeLog(`[启动] Classpath总长度: ${jvmArgs[2].length} 字符`);
let proc;
for (let i = 0; i < 3; i++) {
try {
proc = spawn(java, args, { cwd: gameDir, detached: false, windowsHide: false });
writeLog(`启动游戏成功PID ${proc.pid}`);
break;
} catch (e) {
if (e.code === 'EBUSY' && i < 2) {
await new Promise(r => setTimeout(r, 1500));
continue;
}
writeLog(`启动游戏失败:${e.message}`);
return { success: false, error: e.message };
}
}
// 检测游戏窗口创建成功
await detectGameWindow(proc.pid);
// 捕获输出用于调试
let stdoutData = '';
let stderrData = '';
proc.stdout?.on('data', d => {
const text = d.toString();
stdoutData += text;
mainWindow.webContents.send('game-log', text);
// 检测Minecraft窗口创建成功的标志
if (text.includes('LWJGL Version') || text.includes('OpenGL version') || text.includes('Window')) {
writeLog(`[启动] 检测到窗口初始化日志`);
if (mainWindow) {
mainWindow.webContents.send('game-window-created', { pid: proc.pid });
}
}
});
proc.stderr?.on('data', d => {
const text = d.toString();
stderrData += text;
mainWindow.webContents.send('game-log', text);
// 检测Minecraft窗口创建成功的标志
if (text.includes('LWJGL Version') || text.includes('OpenGL version') || text.includes('Window')) {
writeLog(`[启动] 检测到窗口初始化日志`);
if (mainWindow) {
mainWindow.webContents.send('game-window-created', { pid: proc.pid });
}
}
});
proc.on('error', (err) => {
writeLog(`[启动] 进程错误: ${err.message}`);
});
proc.on('exit', (code) => {
writeLog(`[启动] 进程退出,代码: ${code}`);
if (code !== 0 && stderrData) {
writeLog(`[启动] 错误输出: ${stderrData.slice(0, 1000)}`);
}
});
return { success: true, pid: proc.pid };
} catch (e) {
writeLog(`启动游戏异常:${e.message}`);
return { success: false, error: e.message };
}
});
// 检测游戏窗口创建成功
async function detectGameWindow(pid) {
writeLog(`[启动] 开始检测游戏窗口PID: ${pid}`);
return new Promise((resolve) => {
<<<<<<< Updated upstream
// 设置超时最多等待5秒比原来快3秒
const timeout = setTimeout(() => {
writeLog(`[启动] 游戏窗口检测超时,继续启动流程`);
if (mainWindow) {
mainWindow.webContents.send('game-window-created', { pid: pid, timeout: true });
}
resolve();
}, 5000);
// 使用更快速的检测方式
let checkCount = 0;
const maxChecks = 50; // 最多检查50次5秒
let detected = false;
// 首先立即发送一次,不等待检测完成
writeLog(`[启动] 快速发送创建事件(立即检测)`);
setTimeout(() => {
if (!detected) {
detected = true;
if (mainWindow) {
mainWindow.webContents.send('game-window-created', { pid: pid, fast: true });
}
}
}, 1500); // 1.5秒后直接认为启动成功
const checkInterval = setInterval(() => {
checkCount++;
if (detected) {
clearInterval(checkInterval);
clearTimeout(timeout);
resolve();
return;
=======
let resolved = false;
const done = () => {
if (resolved) return;
resolved = true;
resolve();
};
const cancelCheck = setInterval(() => {
if (launchCancelFlag) {
writeLog(`[启动] 窗口检测期间检测到取消标志,立即退出`);
clearInterval(cancelCheck);
try { detector.kill(); } catch {}
done();
}
}, 100);
const timeout = setTimeout(() => {
writeLog(`[启动] 游戏窗口检测超时8秒继续启动流程`);
clearInterval(cancelCheck);
try { detector.kill(); } catch {}
if (mainWindow && !launchCancelFlag) {
mainWindow.webContents.send('game-window-created', { pid, timeout: true });
>>>>>>> Stashed changes
}
done();
}, 8000);
const script = `
$deadline = (Get-Date).AddSeconds(7)
while ((Get-Date) -lt $deadline) {
try {
$p = Get-Process -Id ${pid} -ErrorAction Stop
if ($p.MainWindowHandle -ne 0) { Write-Output "FOUND"; exit 0 }
} catch { Write-Output "GONE"; exit 1 }
Start-Sleep -Milliseconds 150
}
Write-Output "TIMEOUT"; exit 2
`;
const detector = spawn('powershell', ['-NoProfile', '-Command', script], { windowsHide: true });
let output = '';
detector.stdout.on('data', d => { output += d.toString(); });
detector.stderr.on('data', d => { output += d.toString(); });
detector.on('close', (code) => {
clearInterval(cancelCheck);
clearTimeout(timeout);
if (launchCancelFlag) { done(); return; }
if (output.includes('FOUND')) {
writeLog(`[启动] 检测到游戏窗口MainWindowHandle != 0`);
} else if (output.includes('GONE')) {
writeLog(`[启动] 进程已退出`);
} else {
writeLog(`[启动] 窗口检测结束code=${code}output=${output.trim()}`);
}
if (mainWindow) {
mainWindow.webContents.send('game-window-created', { pid, windowDetected: output.includes('FOUND') });
}
done();
});
detector.on('error', (err) => {
clearInterval(cancelCheck);
clearTimeout(timeout);
writeLog(`[启动] 窗口检测器启动失败: ${err.message}`);
if (mainWindow && !launchCancelFlag) {
mainWindow.webContents.send('game-window-created', { pid, error: true });
}
done();
});
});
}
// IPC: 下载并安装Java运行时
ipcMain.handle('install-java', async (_, javaVersion) => {
try {
writeLog(`[Java] 开始安装 Java ${javaVersion}`);
// 发送下载开始消息
if (mainWindow) {
mainWindow.webContents.send('java-download-started', { javaVersion });
}
const result = await downloadAndInstallJava(javaVersion);
if (result.success) {
writeLog(`[Java] Java ${javaVersion} 安装成功: ${result.javaPath}`);
if (mainWindow) {
mainWindow.webContents.send('java-download-completed', {
javaVersion,
javaPath: result.javaPath
});
}
return result;
} else {
writeLog(`[Java] Java ${javaVersion} 安装失败: ${result.error}`);
if (mainWindow) {
mainWindow.webContents.send('java-download-failed', {
javaVersion,
error: result.error
});
}
return result;
}
} catch (e) {
writeLog(`[Java] 安装Java异常: ${e.message}`);
if (mainWindow) {
mainWindow.webContents.send('java-download-failed', {
javaVersion,
error: e.message
});
}
return { success: false, error: e.message };
}
});
let currentSettings = null;
ipcMain.handle('get-settings', () => {
if (!currentSettings) {
currentSettings = loadSettings();
}
return currentSettings;
});
ipcMain.handle('save-settings', (_, settings) => {
currentSettings = mergeWithDefaults(settings);
const success = saveSettings(currentSettings);
return { success, settings: currentSettings };
});
ipcMain.handle('reset-settings', () => {
currentSettings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
const success = saveSettings(currentSettings);
return { success, settings: currentSettings };
});
ipcMain.handle('restart-app', () => {
writeLog('[重启] 正在重启应用...');
app.relaunch();
app.exit();
});
ipcMain.handle('set-developer-mode', (_, enabled) => {
isDeveloperMode = !!enabled;
writeLog('[开发者模式] ' + (isDeveloperMode ? '已启用' : '已禁用'));
return { success: true };
});
let activeAnimWindow = null;
function cleanupAnimTempDir(dir) {
try {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
} catch (e) {}
}
ipcMain.handle('play-startup-animation', async () => {
if (activeAnimWindow && !activeAnimWindow.isDestroyed()) {
try { activeAnimWindow.close(); } catch (e) {}
activeAnimWindow = null;
}
writeLog('[启动动画] 开始播放启动动画');
const iconDir = path.join(__dirname, 'static', 'icon');
const tempAnimDir = path.join(app.getPath('temp'), 'gpcl-anim-' + Date.now());
try { fs.mkdirSync(tempAnimDir, { recursive: true }); } catch (e) {}
var imageNames = ['caellab.png', 'gamets.png', 'gamets.ico', 'GPCL.png'];
for (var _i = 0; _i < imageNames.length; _i++) {
var src = path.join(iconDir, imageNames[_i]);
if (fs.existsSync(src)) {
try { fs.copyFileSync(src, path.join(tempAnimDir, imageNames[_i])); } catch (e) {}
}
}
var hasCaelab = fs.existsSync(path.join(tempAnimDir, 'caellab.png'));
var hasGamets = fs.existsSync(path.join(tempAnimDir, 'gamets.png')) || fs.existsSync(path.join(tempAnimDir, 'gamets.ico'));
var hasGpcl = fs.existsSync(path.join(tempAnimDir, 'GPCL.png'));
var gametsFile = fs.existsSync(path.join(tempAnimDir, 'gamets.png')) ? 'gamets.png' : 'gamets.ico';
writeLog('[启动动画] 资源: caellab=' + hasCaelab + ' gamets=' + hasGamets + ' gpcl=' + hasGpcl);
var ANIM_DURATION = 30;
var htmlContent = '<!DOCTYPE html>\n<html><head><meta charset="utf-8"><style>\n' +
'*{margin:0;padding:0;box-sizing:border-box}\n' +
'html,body{width:100%;height:100%;overflow:hidden;background:#0a0a0a}\n' +
'canvas{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1}\n' +
'.scene{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;z-index:10;opacity:0}\n' +
'#welcome-txt{text-align:center;font-family:"Microsoft YaHei","Segoe UI",sans-serif}\n' +
'#welcome-txt h1{font-size:56px;font-weight:700;color:#fff;letter-spacing:3px;text-shadow:0 0 25px rgba(91,135,49,0.6),0 0 50px rgba(91,135,49,0.2);margin-bottom:16px}\n' +
'#welcome-txt p{font-size:20px;color:rgba(180,180,180,0.7);letter-spacing:5px}\n' +
'.logo-row{display:flex;flex-direction:row;align-items:center;justify-content:center}\n' +
'#logo-caellab img{max-height:130px;max-width:300px;object-fit:contain;filter:drop-shadow(0 0 18px rgba(91,135,49,0.35))}\n' +
'#logo-both .logo-row{gap:50px}\n' +
'#logo-both img{max-height:110px;max-width:240px;object-fit:contain}\n' +
'#logo-both .caellab-img{filter:drop-shadow(0 0 18px rgba(91,135,49,0.35))}\n' +
'#logo-both .gamets-img{filter:drop-shadow(0 0 14px rgba(255,255,255,0.25))}\n' +
'#logo-gpcl img{max-height:170px;max-width:340px;object-fit:contain;filter:drop-shadow(0 0 25px rgba(91,135,49,0.4))}\n' +
'.logo-text{text-align:center;font-family:"Microsoft YaHei","Segoe UI",sans-serif;margin-top:18px}\n' +
'.logo-text-bottom{position:absolute;bottom:40px;left:0;width:100%;margin-top:0}\n' +
'.logo-title{font-size:20px;color:rgba(200,200,200,0.85);letter-spacing:4px;margin-bottom:6px}\n' +
'.footer-link{font-size:14px;color:rgba(91,135,49,0.7);text-decoration:underline;pointer-events:auto;cursor:pointer}\n' +
'.copyright{font-size:13px;color:rgba(160,160,160,0.6);letter-spacing:1px;margin-bottom:6px}\n' +
'.license-text{font-size:12px;color:rgba(140,140,140,0.5);margin:0}\n' +
'.license-text .footer-link{font-size:12px}\n' +
'.disclaimer{font-size:12px;color:rgba(120,120,120,0.45);max-width:600px;line-height:1.6;margin:40px auto 0;text-align:center}\n' +
'#sweep-line{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:25;opacity:0}\n' +
'#sweep-line .band{position:absolute;top:0;height:100%;width:100px;background:linear-gradient(90deg,transparent,rgba(91,135,49,0.1),rgba(127,255,0,0.12),rgba(91,135,49,0.1),transparent)}\n' +
'#fade{position:fixed;top:0;left:0;width:100%;height:100%;background:#0a0a0a;opacity:0;pointer-events:none;z-index:30;transition:opacity 1.5s ease-in}\n' +
'#fade.show{opacity:1}\n' +
'#flash{position:fixed;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;pointer-events:none;z-index:28}\n' +
'</style></head><body>\n' +
'<canvas id="c"></canvas>\n' +
'<div class="scene" id="welcome"><div id="welcome-txt"><h1>Minecraft\u6B22\u8FCE\u4F60\uFF01</h1><p>By GPCL And AllMinecraftPlayer</p></div></div>\n' +
'<div class="scene" id="logo-caellab"><div class="logo-row"><img src="caellab.png" alt="CaelLab"></div><div class="logo-text"><div class="logo-title">\u865A\u821F\u5B9E\u9A8C\u5BA4</div><a href="https://www.caellab.com" target="_blank" class="footer-link">caellab.com</a></div></div>\n' +
'<div class="scene" id="logo-both"><div class="logo-row"><img class="caellab-img" src="caellab.png" alt="CaelLab"><img class="gamets-img" src="' + gametsFile + '" alt="GameTS"></div><div class="logo-text logo-text-bottom"><div class="copyright">Copyright \u00A9 2026 Yunyun(\u4E91\u4E91) By \u865A\u821F\u5B9E\u9A8C\u5BA4(CaelLab) / CaelLabGameTS</div><p class="license-text">Licensed under <a href="https://www.caellab.com/license/bysa-code" target="_blank" class="footer-link">CaelLab BY-SA Code License</a>. Open Source on <a href="https://git.caellab.com/yunyun/GoodPlanCraftLauncher" target="_blank" class="footer-link">GitCaelLab</a>.</p></div></div>\n' +
'<div class="scene" id="logo-gpcl"><img src="GPCL.png" alt="GPCL"><div class="logo-text logo-text-bottom"><div class="disclaimer">GoodPlanCraftLauncher \u4E0E Mojang Studios \u53CA Microsoft\u3001Xbox \u65E0\u5B98\u65B9\u5173\u8054\u3002Minecraft \u4E3A Mojang Studios \u5546\u6807\u3002</div></div></div>\n' +
'<div id="sweep-line"><div class="band" id="sweep-band"></div></div>\n' +
'<div id="flash"></div><div id="fade"></div>\n' +
'<script>\n' +
'(function(){\n' +
'var canvas=document.getElementById("c"),ctx=canvas.getContext("2d");\n' +
'var W,H,dpr=window.devicePixelRatio||1;\n' +
'function resize(){W=window.innerWidth;H=window.innerHeight;canvas.width=W*dpr;canvas.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0)}\n' +
'resize();window.addEventListener("resize",resize);\n' +
'var startTime=performance.now();\n' +
'var DUR=' + ANIM_DURATION + ';\n' +
'function rand(a,b){return a+Math.random()*(b-a)}\n' +
'function easeOut(t){return 1-Math.pow(1-t,3)}\n' +
'function easeInOut(t){return t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2}\n' +
'function clamp(v,a,b){return v<a?a:v>b?b:v}\n' +
'function lerp(a,b,t){return a+(b-a)*t}\n' +
'var BS=24;\n' +
'var MC_COLORS=["#8B6B3D","#6B6B6B","#5B8731","#3E6B1F","#6B4226","#5A5A5A","#7B5B2D","#4A4A4A"];\n' +
'var terrCols=Math.ceil(W/BS)+1;\n' +
'var terrH=[];\n' +
'for(var i=0;i<terrCols;i++){terrH.push(2+~~rand(0,4))}\n' +
'var parts=[];\n' +
'for(var i=0;i<160;i++){\n' +
' var a=rand(0,6.283),sp=rand(0.12,0.6);\n' +
' parts.push({x:rand(0,W),y:rand(0,H),vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,\n' +
' sz:rand(8,18),c:MC_COLORS[~~rand(0,MC_COLORS.length)],a:0,ta:rand(.08,.25),\n' +
' rot:rand(0,6.28),rs:rand(-.012,.012)});\n' +
'}\n' +
'var star={active:false,x:0,y:0,sx:0,sy:0,tx:0,ty:0,t:0,dur:1.6,trail:[]};\n' +
'var sparkles=[];\n' +
'function addSparkle(x,y){for(var i=0;i<6;i++){var a=rand(0,6.28),sp=rand(0.5,2);\n' +
' sparkles.push({x:x,y:y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,a:1,sz:rand(3,6)})}}\n' +
'var shockwaves=[];\n' +
'function addShock(x,y,c,mr,d){shockwaves.push({x:x,y:y,r:0,mr:mr,c:c,a:.35,d:d,t:0})}\n' +
'var phaseShown={};\n' +
'function showScene(id,o,d){document.title="show:"+id+":"+o+":"+d}\n' +
'function hideScene(id,d){document.title="hide:"+id+":"+d}\n' +
'var waitingForDismiss=false;\n' +
'window.dismissAnimation=function(){\n' +
' var f=document.getElementById("fade");f.style.transition="opacity 1.2s ease-in";f.classList.add("show");\n' +
' setTimeout(function(){document.title="ended"},1300);\n' +
'};\n' +
'function drawBlock(x,y,sz,color,alpha){\n' +
' ctx.save();ctx.globalAlpha=alpha;\n' +
' ctx.fillStyle=color;ctx.fillRect(x,y,sz,sz);\n' +
' ctx.fillStyle="rgba(255,255,255,0.09)";ctx.fillRect(x,y,sz,1);ctx.fillRect(x,y,1,sz);\n' +
' ctx.fillStyle="rgba(0,0,0,0.18)";ctx.fillRect(x,y+sz-1,sz,1);ctx.fillRect(x+sz-1,y,1,sz);\n' +
' ctx.restore();\n' +
'}\n' +
'function drawTerrain(t){\n' +
' var a=clamp(t*.3,0,.2);\n' +
' for(var c=0;c<terrCols;c++){\n' +
' for(var r=0;r<terrH[c];r++){\n' +
' var y=H-(r+1)*BS;var x=c*BS;\n' +
' var col=r===0?"#3E6B1F":r===1?"#5B4B2D":"#3D3D3D";\n' +
' drawBlock(x,y,BS,col,a);\n' +
' }\n' +
' }\n' +
'}\n' +
'function drawGrid(t){\n' +
' var a=clamp(t*.3,0,.018);\n' +
' ctx.save();ctx.strokeStyle="rgba(255,255,255,"+a+")";ctx.lineWidth=0.5;\n' +
' for(var x=0;x<W;x+=BS){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke()}\n' +
' for(var y=0;y<H;y+=BS){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke()}\n' +
' ctx.restore();\n' +
'}\n' +
'function frame(now){\n' +
' var t=(now-startTime)/1000;\n' +
' ctx.clearRect(0,0,W,H);\n' +
' ctx.fillStyle="rgba(10,10,10,"+clamp(t*.5,0,1)+")";ctx.fillRect(0,0,W,H);\n' +
' drawGrid(t);\n' +
' drawTerrain(t);\n' +
' if(t>1&&t<8){\n' +
' var gp=clamp((t-1)*.6,0,.3);var r=100+gp*120;\n' +
' var g=ctx.createRadialGradient(W/2,H/2,0,W/2,H/2,r);\n' +
' g.addColorStop(0,"rgba(91,135,49,"+(.06*gp)+")");g.addColorStop(1,"rgba(62,107,31,0)");\n' +
' ctx.fillStyle=g;ctx.fillRect(0,0,W,H);\n' +
' }\n' +
' if(t>22&&t<28){\n' +
' var gp2=clamp((t-22)*.5,0,.25);var r2=120+gp2*130;\n' +
' var g2=ctx.createRadialGradient(W/2,H/2,0,W/2,H/2,r2);\n' +
' g2.addColorStop(0,"rgba(91,135,49,"+(.06*gp2)+")");g2.addColorStop(1,"rgba(62,107,31,0)");\n' +
' ctx.fillStyle=g2;ctx.fillRect(0,0,W,H);\n' +
' }\n' +
' for(var i=0;i<parts.length;i++){var p=parts[i];\n' +
' p.a=clamp(p.a+.003,0,p.ta);p.x+=p.vx;p.y+=p.vy;p.rot+=p.rs;\n' +
' p.vx*=.9995;p.vy*=.9995;\n' +
' if(p.x<-30)p.x=W+30;if(p.x>W+30)p.x=-30;\n' +
' if(p.y<-30)p.y=H+30;if(p.y>H+30)p.y=-30;\n' +
' ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);\n' +
' drawBlock(-p.sz/2,-p.sz/2,p.sz,p.c,p.a);ctx.restore();\n' +
' }\n' +
' if(star.active){\n' +
' var st=clamp((t-star.t)/star.dur,0,1);var e=easeInOut(st);\n' +
' star.x=lerp(star.sx,star.tx,e);star.y=lerp(star.sy,star.ty,e);\n' +
' star.trail.push({x:star.x,y:star.y,a:1});if(star.trail.length>35)star.trail.shift();\n' +
' for(var j=0;j<star.trail.length;j++){var tr=star.trail[j];tr.a*=.93;\n' +
' ctx.save();ctx.globalAlpha=tr.a*.5;ctx.fillStyle="#7FFF00";\n' +
' ctx.fillRect(tr.x-2,tr.y-2,4,4);ctx.restore();}\n' +
' ctx.save();ctx.fillStyle="#7FFF00";ctx.shadowColor="#7FFF00";ctx.shadowBlur=15;\n' +
' ctx.fillRect(star.x-5,star.y-5,10,10);\n' +
' ctx.shadowBlur=30;ctx.fillStyle="rgba(127,255,0,0.12)";\n' +
' ctx.fillRect(star.x-14,star.y-14,28,28);ctx.restore();\n' +
' if(Math.random()>.6)addSparkle(star.x+rand(-8,8),star.y+rand(-8,8));\n' +
' if(st>=1)star.active=false;\n' +
' }\n' +
' for(var s=sparkles.length-1;s>=0;s--){var sp=sparkles[s];\n' +
' sp.x+=sp.vx;sp.y+=sp.vy;sp.a*=.95;sp.vx*=.97;sp.vy*=.97;\n' +
' if(sp.a<.01){sparkles.splice(s,1);continue}\n' +
' ctx.save();ctx.globalAlpha=sp.a;ctx.fillStyle="#7FFF00";\n' +
' ctx.fillRect(sp.x-sp.sz/2,sp.y-sp.sz/2,sp.sz,sp.sz);ctx.restore();\n' +
' }\n' +
' for(var s=shockwaves.length-1;s>=0;s--){var sw=shockwaves[s];\n' +
' sw.t+=1/60;var pct=sw.t/sw.d;if(pct>=1){shockwaves.splice(s,1);continue}\n' +
' sw.r=sw.mr*easeOut(pct);sw.a=.35*(1-pct);\n' +
' ctx.save();ctx.strokeStyle=sw.c;ctx.globalAlpha=sw.a;ctx.lineWidth=2+2*(1-pct);\n' +
' ctx.beginPath();ctx.arc(sw.x,sw.y,sw.r,0,6.28);ctx.stroke();ctx.restore();\n' +
' }\n' +
' if(t>1.5&&!phaseShown.w){phaseShown.w=true;showScene("welcome",1,1)}\n' +
' if(t>7&&!phaseShown.hidew){phaseShown.hidew=true;hideScene("welcome",1)}\n' +
' if(t>8.5&&!phaseShown.star){\n' +
' phaseShown.star=true;\n' +
' star.active=true;star.sx=-50;star.sy=H*.55;star.tx=W/2;star.ty=H/2;star.t=t;star.trail=[];\n' +
' }\n' +
' if(t>10.5&&!phaseShown.ca){phaseShown.ca=true;addShock(W/2,H/2,"rgba(91,135,49,0.3)",400,1.2);showScene("logo-caellab",1,1.2)}\n' +
' if(t>14.5&&!phaseShown.hideca){phaseShown.hideca=true;hideScene("logo-caellab",.8)}\n' +
' if(t>15.5&&!phaseShown.both){phaseShown.both=true;showScene("logo-both",1,1.2)}\n' +
' if(t>20.5&&!phaseShown.hideboth){phaseShown.hideboth=true;hideScene("logo-both",1)}\n' +
' if(t>22&&!phaseShown.gpcl){phaseShown.gpcl=true;addShock(W/2,H/2,"rgba(127,255,0,0.12)",350,1);showScene("logo-gpcl",1,1.5)}\n' +
' if(t>26&&!phaseShown.sweep){\n' +
' phaseShown.sweep=true;\n' +
' var sl=document.getElementById("sweep-line");sl.style.opacity="1";\n' +
' var band=document.getElementById("sweep-band");var swStart=performance.now();\n' +
' function sweepFrame(swNow){var swT=(swNow-swStart)/1500;\n' +
' if(swT>1){sl.style.opacity="0";return}\n' +
' band.style.left=(-200+(W+400)*easeInOut(swT))+"px";\n' +
' requestAnimationFrame(sweepFrame)}\n' +
' requestAnimationFrame(sweepFrame);\n' +
' }\n' +
' if(t>27.5&&!phaseShown.launch){phaseShown.launch=true;document.title="launch"}\n' +
' if(t>28&&!phaseShown.hgpcl){phaseShown.hgpcl=true;hideScene("logo-gpcl",1)}\n' +
' if(t>29.5&&!phaseShown.fadeblk){phaseShown.fadeblk=true;document.getElementById("fade").classList.add("show")}\n' +
' if(t>DUR&&!waitingForDismiss){waitingForDismiss=true;document.title="waiting"}\n' +
' requestAnimationFrame(frame);\n' +
'}\n' +
'document.title="meta:"+DUR;\n' +
'requestAnimationFrame(frame);\n' +
'})();\n' +
'<\/script></body></html>';
var tempHtmlPath = path.join(tempAnimDir, 'index.html');
try { fs.writeFileSync(tempHtmlPath, htmlContent, 'utf8'); } catch (e) {
writeLog('[启动动画] 写入临时文件失败: ' + e.message);
cleanupAnimTempDir(tempAnimDir);
return { success: false, error: '写入临时文件失败', duration: 0 };
}
return new Promise(function(resolve) {
var animWindow = new BrowserWindow({
fullscreen: true,
frame: false,
skipTaskbar: true,
backgroundColor: '#000000',
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
});
activeAnimWindow = animWindow;
animWindow.setAlwaysOnTop(true, 'screen-saver');
animWindow.webContents.setWindowOpenHandler(function(details) {
shell.openExternal(details.url);
return { action: 'deny' };
});
animWindow.loadFile(tempHtmlPath);
var resolved = false;
animWindow.on('page-title-updated', function(e, title) {
e.preventDefault();
if (title.startsWith('show:')) {
var parts = title.split(':');
var sceneId = parts[1];
var opacity = parts[2];
var dur = parts[3];
animWindow.webContents.executeJavaScript(
'(function(){var el=document.getElementById("' + sceneId + '");' +
'if(el){el.style.transition="opacity ' + dur + 's ease-out";' +
'el.style.opacity="' + opacity + '"}})()'
);
return;
}
if (title.startsWith('hide:')) {
var parts = title.split(':');
var sceneId = parts[1];
var dur = parts[2];
animWindow.webContents.executeJavaScript(
'(function(){var el=document.getElementById("' + sceneId + '");' +
'if(el){el.style.transition="opacity ' + dur + 's ease-in";' +
'el.style.opacity="0"}})()'
);
return;
}
if (title === 'launch' && !resolved) {
resolved = true;
writeLog('[启动动画] 动画即将结束提前启动MC');
resolve({ success: true, duration: ANIM_DURATION });
return;
}
if (title === 'waiting') {
writeLog('[启动动画] 进入黑屏等待阶段');
return;
}
if (title === 'ended') {
writeLog('[启动动画] 动画退场完毕');
try { animWindow.close(); } catch (e) {}
return;
}
});
animWindow.on('closed', function() {
if (activeAnimWindow === animWindow) activeAnimWindow = null;
cleanupAnimTempDir(tempAnimDir);
if (!resolved) {
resolved = true;
resolve({ success: false, error: '动画窗口已关闭', duration: 0 });
}
});
setTimeout(function() {
if (!resolved) {
resolved = true;
writeLog('[启动动画] 动画加载超时');
resolve({ success: false, error: '动画加载超时', duration: 0 });
try { if (!animWindow.isDestroyed()) animWindow.close(); } catch (e) {}
}
}, 35000);
});
});
ipcMain.handle('dismiss-startup-animation', function() {
if (activeAnimWindow && !activeAnimWindow.isDestroyed()) {
writeLog('[启动动画] MC窗口就绪执行退场');
try {
activeAnimWindow.webContents.executeJavaScript(
'if(typeof dismissAnimation==="function")dismissAnimation()'
);
} catch (e) {}
}
});
ipcMain.handle('read-json-file', async (_, subPath, fileName) => {
try {
const dir = path.join(BASE_DIR, 'gpcl', subPath);
const filePath = path.join(dir, fileName);
if (!fs.existsSync(filePath)) {
return null;
}
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
} catch (e) {
writeLog(`读取文件失败: ${e.message}`);
return null;
}
});
ipcMain.handle('write-json-file', async (_, subPath, fileName, data) => {
try {
const dir = path.join(BASE_DIR, 'gpcl', subPath);
const filePath = path.join(dir, fileName);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
return true;
} catch (e) {
writeLog(`写入文件失败: ${e.message}`);
return false;
}
});
// 获取 Forge 版本列表
ipcMain.handle('get-forge-versions', async (_, mcVersion) => {
try {
writeLog(`[Forge] 获取版本列表: ${mcVersion}`);
const versions = await fetchForgeVersions(mcVersion);
return { success: true, versions };
} catch (e) {
writeLog(`[Forge] 获取版本列表失败: ${e.message}`);
return { success: false, error: e.message, versions: [] };
}
});
// 从 Forge Maven 获取版本列表
async function fetchForgeVersions(mcVersion) {
return new Promise((resolve, reject) => {
const https = require('https');
const url = `https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json`;
https.get(url, { timeout: 10000 }, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const metadata = JSON.parse(data);
const versions = [];
// 查找对应 MC 版本的 Forge 版本
const versionArray = metadata[mcVersion];
if (versionArray && Array.isArray(versionArray)) {
versionArray.forEach((fullVersion) => {
const parts = fullVersion.split('-');
const forgeVersion = parts.slice(1).join('-');
versions.push({
id: fullVersion,
name: `Forge ${forgeVersion}`,
version: forgeVersion,
mcVersion: mcVersion
});
});
}
// 按版本号排序(最新的在前)
versions.sort((a, b) => compareVersions(b.version, a.version));
// 只返回前 10 个版本
const topVersions = versions.slice(0, 10);
// 标记推荐版本(第一个)
if (topVersions.length > 0) {
topVersions[0].name += ' (推荐)';
}
writeLog(`[Forge] 找到 ${topVersions.length} 个版本`);
resolve(topVersions);
} catch (e) {
writeLog(`[Forge] 解析错误: ${e.message}`);
reject(new Error('解析 Forge 元数据失败'));
}
});
}).on('error', (e) => {
reject(new Error(`请求 Forge 元数据失败: ${e.message}`));
}).on('timeout', () => {
reject(new Error('请求 Forge 元数据超时'));
});
});
}
// 版本号比较函数
function compareVersions(v1, v2) {
const parts1 = v1.split(/[.-]/).map(p => parseInt(p, 10) || 0);
const parts2 = v2.split(/[.-]/).map(p => parseInt(p, 10) || 0);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const a = parts1[i] || 0;
const b = parts2[i] || 0;
if (a > b) return 1;
if (a < b) return -1;
}
return 0;
}
async function installVersionWithModLoader(versionId, loaderType, loaderVersion, maxConcurrent) {
try {
writeLog(`[模组加载器] 开始下载: ${loaderType} ${loaderVersion} for MC ${versionId}`);
// 1. 先下载原版游戏
writeLog(`[模组加载器] 下载原版 MC ${versionId}`);
await installVersion(versionId, maxConcurrent);
if (shouldCancelDownload) {
return { success: false, error: '下载已取消' };
}
// 2. 根据模组加载器类型进行安装
let finalVersionId;
if (loaderType === 'forge') {
finalVersionId = await installForge(versionId, loaderVersion);
} else {
return { success: false, error: `不支持的模组加载器: ${loaderType}` };
}
writeLog(`[模组加载器] 安装完成: ${finalVersionId}`);
// 更新版本最后启动时间
updateVersionLastPlayed(finalVersionId);
return { success: true, finalVersionId };
} catch (e) {
writeLog(`[模组加载器] 安装失败: ${e.message}`);
return { success: false, error: e.message };
}
}
// 安装 Forge直接从 Forge Maven 下载完整的版本 JSON
async function installForge(mcVersion, forgeVersion) {
writeLog(`[Forge] 安装: MC ${mcVersion}, Forge ${forgeVersion}`);
const versionDir = path.join(GAME_DIR, 'versions', mcVersion);
if (!fs.existsSync(versionDir)) {
throw new Error(`原版版本 ${mcVersion} 不存在`);
}
// 构造完整的 Forge 版本 ID如 1.20.1-47.2.0
const fullForgeVersionId = `${mcVersion}-${forgeVersion}`;
// Forge 在 Maven 上提供了完整的版本 JSON含 mainClass、libraries、inheritsFrom 等)
const forgeJsonUrl = `https://maven.minecraftforge.net/net/minecraftforge/forge/${fullForgeVersionId}/forge-${fullForgeVersionId}.json`;
writeLog(`[Forge] 下载版本 JSON: ${forgeJsonUrl}`);
let forgeJsonData;
try {
forgeJsonData = await fetchText(forgeJsonUrl);
} catch (e) {
throw new Error(`无法下载 Forge 版本 JSON: ${e.message}`);
}
let forgeVersionData;
try {
forgeVersionData = JSON.parse(forgeJsonData);
} catch (e) {
const snippet = forgeJsonData.substring(0, 100);
writeLog(`[Forge] JSON 解析失败,响应内容: ${snippet}`);
throw new Error(`Forge 版本 JSON 解析失败,服务器可能返回了错误页面`);
}
const forgeJsonId = `${mcVersion}-forge`;
forgeVersionData.id = forgeJsonId;
const forgeJsonPath = path.join(versionDir, `${forgeJsonId}.json`);
fs.writeFileSync(forgeJsonPath, JSON.stringify(forgeVersionData, null, 2));
const gpclDir = path.join(versionDir, 'gpcl');
if (!fs.existsSync(gpclDir)) fs.mkdirSync(gpclDir, { recursive: true });
const configPath = path.join(gpclDir, 'config.ini');
const configContent = `[Version]
Minecraft=${mcVersion}
Forge=${forgeVersion}
`;
fs.writeFileSync(configPath, configContent, 'utf8');
writeLog(`[Forge] 安装完成: ${forgeJsonId}`);
return forgeJsonId;
}
// 读取 config.ini 配置文件
function readVersionConfig(versionId) {
const versionDir = path.join(GAME_DIR, 'versions', versionId);
const configPath = path.join(versionDir, 'gpcl', 'config.ini');
if (!fs.existsSync(configPath)) {
return null;
}
try {
const content = fs.readFileSync(configPath, 'utf8');
const config = {
Minecraft: '',
Forge: ''
};
const lines = content.split('\n');
let inVersionSection = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed === '[Version]') {
inVersionSection = true;
continue;
}
if (!inVersionSection) continue;
const parts = trimmed.split('=');
if (parts.length === 2) {
const key = parts[0].trim();
const value = parts[1].trim();
if (config.hasOwnProperty(key)) {
config[key] = value;
}
}
}
return config;
} catch (e) {
writeLog(`[Config] 读取配置失败: ${e.message}`);
return null;
}
}
// 生成版本显示名称
function generateVersionDisplayName(versionId) {
const config = readVersionConfig(versionId);
if (!config) {
return versionId;
}
const parts = [];
parts.push(config.Minecraft || versionId);
if (config.Forge) {
parts.push(`Forge${config.Forge}`);
}
return parts.join(' ');
}
// 生成启动页显示名称
function generateLaunchDisplayName(versionId) {
const config = readVersionConfig(versionId);
if (!config) {
return versionId;
}
const parts = [];
parts.push(config.Minecraft || versionId);
if (config.Forge) {
parts.push(`Forge${config.Forge}`);
}
return parts.join(' ');
}
// 安装 Forge
async function installForge(mcVersion, forgeVersionId) {
writeLog(`[Forge] 安装: ${forgeVersionId}`);
const versionDir = path.join(GAME_DIR, 'versions', mcVersion);
if (!fs.existsSync(versionDir)) {
throw new Error(`原版版本 ${mcVersion} 不存在`);
}
// 创建 gpcl 目录
const gpclDir = path.join(versionDir, 'gpcl');
if (!fs.existsSync(gpclDir)) fs.mkdirSync(gpclDir, { recursive: true });
// 读取或创建 config.ini
const configPath = path.join(gpclDir, 'config.ini');
let config = readVersionConfig(mcVersion) || {
Minecraft: mcVersion,
Forge: '',
Fabric: '',
OptiFine: ''
};
config.Minecraft = mcVersion;
config.Forge = forgeVersionId;
// 写入 config.ini
const configContent = `[Version]
Minecraft=${config.Minecraft}
Forge=${config.Forge}
Fabric=${config.Fabric || ''}
OptiFine=${config.OptiFine || ''}
`;
fs.writeFileSync(configPath, configContent, 'utf8');
// 更新 version.json
const jsonPath = path.join(versionDir, `${mcVersion}.json`);
const versionData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
versionData.mainClass = 'net.minecraftforge.userdev.LaunchTesting';
versionData.libraries = versionData.libraries || [];
const hasForgeLib = versionData.libraries.some(lib =>
lib.name && lib.name.startsWith('net.minecraftforge:forge:')
);
if (!hasForgeLib) {
versionData.libraries.push({
name: `net.minecraftforge:forge:${forgeVersionId}`,
downloads: {
artifact: {
url: `https://maven.minecraftforge.net/net/minecraftforge/forge/${forgeVersionId}/forge-${forgeVersionId}.jar`,
path: `net/minecraftforge/forge/${forgeVersionId}/forge-${forgeVersionId}.jar`,
sha1: '',
size: 0
}
}
});
}
fs.writeFileSync(jsonPath, JSON.stringify(versionData, null, 2));
writeLog(`[Forge] 安装完成: ${mcVersion} + Forge ${forgeVersionId}`);
return mcVersion;
}

21
obfuscator.json Normal file
View File

@ -0,0 +1,21 @@
{
"compact": true,
"controlFlowFlattening": false,
"controlFlowFlatteningThreshold": 0,
"deadCodeInjection": false,
"deadCodeInjectionThreshold": 0,
"debugProtection": false,
"debugProtectionInterval": 0,
"disableConsoleOutput": false,
"identifierNamesGenerator": "mangled",
"rotateStringArray": true,
"selfDefending": false,
"stringArray": true,
"stringArrayEncoding": ["rc4"],
"stringArrayThreshold": 0.75,
"transformObjectKeys": false,
"unicodeEscapeSequence": false,
"renameGlobals": false,
"shuffleStringArray": true,
"splitStrings": false
}

4709
package-lock.json generated Normal file
View File

@ -0,0 +1,4709 @@
{
"name": "gpcl",
"version": "1.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gpcl",
"version": "1.4.1",
"license": "ISC",
"dependencies": {
"adm-zip": "^0.5.17",
"unzip-stream": "^0.3.4"
},
"devDependencies": {
"electron": "^42.1.0",
"electron-builder": "^26.8.1",
"javascript-obfuscator": "^5.4.3"
}
},
"node_modules/@develar/schema-utils": {
"version": "2.6.5",
"resolved": "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
"integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.0",
"ajv-keywords": "^3.4.1"
},
"engines": {
"node": ">= 8.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/@electron/asar": {
"version": "3.4.1",
"resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz",
"integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "^5.0.0",
"glob": "^7.1.6",
"minimatch": "^3.0.4"
},
"bin": {
"asar": "bin/asar.js"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/@electron/asar/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/@electron/asar/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@electron/asar/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@electron/fuses": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@electron/fuses/-/fuses-1.8.0.tgz",
"integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.1.1",
"fs-extra": "^9.0.1",
"minimist": "^1.2.5"
},
"bin": {
"electron-fuses": "dist/bin.js"
}
},
"node_modules/@electron/fuses/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@electron/get": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/@electron/get/-/get-5.0.0.tgz",
"integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"env-paths": "^3.0.0",
"graceful-fs": "^4.2.11",
"progress": "^2.0.3",
"semver": "^7.6.3",
"sumchecker": "^3.0.1"
},
"engines": {
"node": ">=22.12.0"
},
"optionalDependencies": {
"undici": "^7.24.4"
}
},
"node_modules/@electron/notarize": {
"version": "2.5.0",
"resolved": "https://registry.npmmirror.com/@electron/notarize/-/notarize-2.5.0.tgz",
"integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1",
"promise-retry": "^2.0.1"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@electron/notarize/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@electron/osx-sign": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/@electron/osx-sign/-/osx-sign-1.3.3.tgz",
"integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"compare-version": "^0.1.2",
"debug": "^4.3.4",
"fs-extra": "^10.0.0",
"isbinaryfile": "^4.0.8",
"minimist": "^1.2.6",
"plist": "^3.0.5"
},
"bin": {
"electron-osx-flat": "bin/electron-osx-flat.js",
"electron-osx-sign": "bin/electron-osx-sign.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@electron/osx-sign/node_modules/isbinaryfile": {
"version": "4.0.10",
"resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
"integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8.0.0"
},
"funding": {
"url": "https://github.com/sponsors/gjtorikian/"
}
},
"node_modules/@electron/rebuild": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/@electron/rebuild/-/rebuild-4.0.4.tgz",
"integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.1.1",
"node-abi": "^4.2.0",
"node-api-version": "^0.2.1",
"node-gyp": "^12.2.0",
"read-binary-file-arch": "^1.0.6"
},
"bin": {
"electron-rebuild": "lib/cli.js"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/@electron/universal": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/@electron/universal/-/universal-2.0.3.tgz",
"integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@electron/asar": "^3.3.1",
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.3.1",
"dir-compare": "^4.2.0",
"fs-extra": "^11.1.1",
"minimatch": "^9.0.3",
"plist": "^3.1.0"
},
"engines": {
"node": ">=16.4"
}
},
"node_modules/@electron/universal/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/@electron/universal/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@electron/universal/node_modules/fs-extra": {
"version": "11.3.5",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.5.tgz",
"integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/@electron/universal/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@electron/windows-sign": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
"integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"dependencies": {
"cross-dirname": "^0.1.0",
"debug": "^4.3.4",
"fs-extra": "^11.1.1",
"minimist": "^1.2.8",
"postject": "^1.0.0-alpha.6"
},
"bin": {
"electron-windows-sign": "bin/electron-windows-sign.js"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
"version": "11.3.5",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.5.tgz",
"integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/@inversifyjs/common": {
"version": "1.5.2",
"resolved": "https://registry.npmmirror.com/@inversifyjs/common/-/common-1.5.2.tgz",
"integrity": "sha512-WlzR9xGadABS9gtgZQ+luoZ8V6qm4Ii6RQfcfC9Ho2SOlE6ZuemFo7PKJvKI0ikm8cmKbU8hw5UK6E4qovH21w==",
"dev": true,
"license": "MIT"
},
"node_modules/@inversifyjs/container": {
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/@inversifyjs/container/-/container-1.15.0.tgz",
"integrity": "sha512-U2xYsPrJTz5za2TExi5lg8qOWf8TEVBpN+pQM7B8BVA2rajtbRE9A66SLRHk8c1eGXmg+0K4Hdki6tWAsSQBUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@inversifyjs/common": "1.5.2",
"@inversifyjs/core": "9.2.0",
"@inversifyjs/plugin": "0.2.0",
"@inversifyjs/reflect-metadata-utils": "1.4.1"
},
"peerDependencies": {
"reflect-metadata": "~0.2.2"
}
},
"node_modules/@inversifyjs/core": {
"version": "9.2.0",
"resolved": "https://registry.npmmirror.com/@inversifyjs/core/-/core-9.2.0.tgz",
"integrity": "sha512-Nm7BR6KmpgshIHpVQWuEDehqRVb6GBm8LFEuhc2s4kSZWrArZ15RmXQzROLk4m+hkj4kMXgvMm5Qbopot/D6Sg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@inversifyjs/common": "1.5.2",
"@inversifyjs/prototype-utils": "0.1.3",
"@inversifyjs/reflect-metadata-utils": "1.4.1"
}
},
"node_modules/@inversifyjs/plugin": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/@inversifyjs/plugin/-/plugin-0.2.0.tgz",
"integrity": "sha512-R/JAdkTSD819pV1zi0HP54mWHyX+H2m8SxldXRgPQarS3ySV4KPyRdosWcfB8Se0JJZWZLHYiUNiS6JvMWSPjw==",
"dev": true,
"license": "MIT"
},
"node_modules/@inversifyjs/prototype-utils": {
"version": "0.1.3",
"resolved": "https://registry.npmmirror.com/@inversifyjs/prototype-utils/-/prototype-utils-0.1.3.tgz",
"integrity": "sha512-EzRamZzNgE9Sn3QtZ8NncNa2lpPMZfspqbK6BWFguWnOpK8ymp2TUuH46ruFHZhrHKnknPd7fG22ZV7iF517TQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@inversifyjs/common": "1.5.2"
}
},
"node_modules/@inversifyjs/reflect-metadata-utils": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/@inversifyjs/reflect-metadata-utils/-/reflect-metadata-utils-1.4.1.tgz",
"integrity": "sha512-Cp77C4d2wLaHXiUB7iH6Cxb7i1lD/YDuTIHLTDzKINqGSz0DCSoL/Dg2wVkW/6Qx03r/yQMLJ+32Agl32N2X8g==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"reflect-metadata": "~0.2.2"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@javascript-obfuscator/escodegen": {
"version": "2.4.1",
"resolved": "https://registry.npmmirror.com/@javascript-obfuscator/escodegen/-/escodegen-2.4.1.tgz",
"integrity": "sha512-YrEJJDr4cb+pIQKWzHFoDlDkQzatcrNB6OhAD6iTSwiKwzZUMVdobwbOuLpF4EiLxUj0qP28Xl1saTHYzIPCLg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@javascript-obfuscator/estraverse": "^5.3.0",
"esprima": "^4.0.1",
"esutils": "^2.0.2",
"optionator": "^0.8.1"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/@javascript-obfuscator/estraverse": {
"version": "5.4.0",
"resolved": "https://registry.npmmirror.com/@javascript-obfuscator/estraverse/-/estraverse-5.4.0.tgz",
"integrity": "sha512-CZFX7UZVN9VopGbjTx4UXaXsi9ewoM1buL0kY7j1ftYdSs7p2spv9opxFjHlQ/QGTgh4UqufYqJJ0WKLml7b6w==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/@malept/cross-spawn-promise": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
"integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/malept"
},
{
"type": "tidelift",
"url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
}
],
"license": "Apache-2.0",
"dependencies": {
"cross-spawn": "^7.0.1"
},
"engines": {
"node": ">= 12.13.0"
}
},
"node_modules/@malept/flatpak-bundler": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz",
"integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.0",
"lodash": "^4.17.15",
"tmp-promise": "^3.0.2"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@malept/flatpak-bundler/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"dev": true,
"license": "MIT",
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "^3.1.4",
"@types/node": "*",
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz",
"integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/http-cache-semantics": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
"integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-3.0.5.tgz",
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.12.4",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.12.4.tgz",
"integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/plist": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/@types/plist/-/plist-3.0.5.tgz",
"integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*",
"xmlbuilder": ">=11.0.1"
}
},
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz",
"integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/validator": {
"version": "13.15.10",
"resolved": "https://registry.npmmirror.com/@types/validator/-/validator-13.15.10.tgz",
"integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/verror": {
"version": "1.10.11",
"resolved": "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz",
"integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz",
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@vercel/blob": {
"version": "2.4.0",
"resolved": "https://registry.npmmirror.com/@vercel/blob/-/blob-2.4.0.tgz",
"integrity": "sha512-ncQ8CRb6XoEAYJwjOTRGpACRT6h/AeY+/33gLyeVxG5BIes27OPm1jmqreF+JHjcTmGhClTP+kBpmyLfbV0xew==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"async-retry": "^1.3.4",
"is-buffer": "^2.0.5",
"is-node-process": "^1.2.0",
"throttleit": "^2.1.0",
"undici": "^6.23.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@vercel/blob/node_modules/undici": {
"version": "6.25.0",
"resolved": "https://registry.npmmirror.com/undici/-/undici-6.25.0.tgz",
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.13",
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/7zip-bin": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/7zip-bin/-/7zip-bin-5.2.0.tgz",
"integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
"dev": true,
"license": "MIT"
},
"node_modules/abbrev": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-4.0.0.tgz",
"integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-attributes": {
"version": "1.9.5",
"resolved": "https://registry.npmmirror.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/adm-zip": {
"version": "0.5.17",
"resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.17.tgz",
"integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==",
"license": "MIT",
"engines": {
"node": ">=12.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz",
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/app-builder-bin": {
"version": "5.0.0-alpha.12",
"resolved": "https://registry.npmmirror.com/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz",
"integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==",
"dev": true,
"license": "MIT"
},
"node_modules/app-builder-lib": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/app-builder-lib/-/app-builder-lib-26.8.1.tgz",
"integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@develar/schema-utils": "~2.6.5",
"@electron/asar": "3.4.1",
"@electron/fuses": "^1.8.0",
"@electron/get": "^3.0.0",
"@electron/notarize": "2.5.0",
"@electron/osx-sign": "1.3.4",
"@electron/rebuild": "^4.0.3",
"@electron/universal": "2.0.3",
"@malept/flatpak-bundler": "^0.4.0",
"@types/fs-extra": "9.0.13",
"async-exit-hook": "^2.0.1",
"builder-util": "26.8.1",
"builder-util-runtime": "9.5.1",
"chromium-pickle-js": "^0.2.0",
"ci-info": "4.3.1",
"debug": "^4.3.4",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"ejs": "^3.1.8",
"electron-publish": "26.8.1",
"fs-extra": "^10.1.0",
"hosted-git-info": "^4.1.0",
"isbinaryfile": "^5.0.0",
"jiti": "^2.4.2",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"lazy-val": "^1.0.5",
"minimatch": "^10.0.3",
"plist": "3.1.0",
"proper-lockfile": "^4.1.2",
"resedit": "^1.7.0",
"semver": "~7.7.3",
"tar": "^7.5.7",
"temp-file": "^3.4.0",
"tiny-async-pool": "1.3.0",
"which": "^5.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"dmg-builder": "26.8.1",
"electron-builder-squirrel-windows": "26.8.1"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/@electron/get/-/get-3.1.0.tgz",
"integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"env-paths": "^2.2.0",
"fs-extra": "^8.1.0",
"got": "^11.8.5",
"progress": "^2.0.3",
"semver": "^6.2.0",
"sumchecker": "^3.0.1"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"global-agent": "^3.0.0"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/app-builder-lib/node_modules/ci-info": {
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.3.1.tgz",
"integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/app-builder-lib/node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/app-builder-lib/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/app-builder-lib/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/app-builder-lib/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/array-differ": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/array-differ/-/array-differ-3.0.0.tgz",
"integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/arrify": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/assert": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/assert/-/assert-2.1.0.tgz",
"integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
"is-nan": "^1.3.2",
"object-is": "^1.1.5",
"object.assign": "^4.1.4",
"util": "^0.12.5"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.8"
}
},
"node_modules/astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz",
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"dev": true,
"license": "MIT"
},
"node_modules/async-exit-hook": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz",
"integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/async-retry": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/async-retry/-/async-retry-1.3.4.tgz",
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"retry": "0.13.1"
}
},
"node_modules/async-retry/node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmmirror.com/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/binary": {
"version": "0.3.0",
"resolved": "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz",
"integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
"license": "MIT",
"dependencies": {
"buffers": "~0.1.1",
"chainsaw": "~0.1.0"
},
"engines": {
"node": "*"
}
},
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"optional": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/buffers": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz",
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
"engines": {
"node": ">=0.2.0"
}
},
"node_modules/builder-util": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/builder-util/-/builder-util-26.8.1.tgz",
"integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/debug": "^4.1.6",
"7zip-bin": "~5.2.0",
"app-builder-bin": "5.0.0-alpha.12",
"builder-util-runtime": "9.5.1",
"chalk": "^4.1.2",
"cross-spawn": "^7.0.6",
"debug": "^4.3.4",
"fs-extra": "^10.1.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"js-yaml": "^4.1.0",
"sanitize-filename": "^1.6.3",
"source-map-support": "^0.5.19",
"stat-mode": "^1.0.0",
"temp-file": "^3.4.0",
"tiny-async-pool": "1.3.0"
}
},
"node_modules/builder-util-runtime": {
"version": "9.5.1",
"resolved": "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.4",
"resolved": "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz",
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"dev": true,
"license": "MIT",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.9.tgz",
"integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"get-intrinsic": "^1.3.0",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chainsaw": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz",
"integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
"license": "MIT/X11",
"dependencies": {
"traverse": ">=0.3.0 <0.4"
},
"engines": {
"node": "*"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chance": {
"version": "1.1.13",
"resolved": "https://registry.npmmirror.com/chance/-/chance-1.1.13.tgz",
"integrity": "sha512-V6lQCljcLznE7tUYUM9EOAnnKXbctE6j/rdQkYOHIWbfGQbrzTsAXNW9CdU5XCo4ArXQCj/rb6HgxPlmGJcaUg==",
"dev": true,
"license": "MIT"
},
"node_modules/char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/chromium-pickle-js": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
"integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==",
"dev": true,
"license": "MIT"
},
"node_modules/ci-info": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-4.4.0.tgz",
"integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/class-validator": {
"version": "0.14.3",
"resolved": "https://registry.npmmirror.com/class-validator/-/class-validator-0.14.3.tgz",
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/validator": "^13.15.3",
"libphonenumber-js": "^1.11.1",
"validator": "^13.15.20"
}
},
"node_modules/cli-truncate": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-2.1.0.tgz",
"integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"slice-ansi": "^3.0.0",
"string-width": "^4.2.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz",
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-response": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz",
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/compare-version": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz",
"integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/crc": {
"version": "3.8.0",
"resolved": "https://registry.npmmirror.com/crc/-/crc-3.8.0.tgz",
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"buffer": "^5.1.0"
}
},
"node_modules/cross-dirname": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/cross-dirname/-/cross-dirname-0.1.0.tgz",
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn/node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/cross-spawn/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
"license": "MIT"
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/dir-compare": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/dir-compare/-/dir-compare-4.2.0.tgz",
"integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimatch": "^3.0.5",
"p-limit": "^3.1.0 "
}
},
"node_modules/dir-compare/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/dir-compare/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/dir-compare/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/dmg-builder": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-26.8.1.tgz",
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"app-builder-lib": "26.8.1",
"builder-util": "26.8.1",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.2",
"js-yaml": "^4.1.0"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
}
},
"node_modules/dmg-license": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/dmg-license/-/dmg-license-1.0.11.tgz",
"integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"dependencies": {
"@types/plist": "^3.0.1",
"@types/verror": "^1.10.3",
"ajv": "^6.10.0",
"crc": "^3.8.0",
"iconv-corefoundation": "^1.1.7",
"plist": "^3.0.4",
"smart-buffer": "^4.0.2",
"verror": "^1.10.0"
},
"bin": {
"dmg-license": "bin/dmg-license.js"
},
"engines": {
"node": ">=8"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-expand": {
"version": "11.0.7",
"resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
"integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"dotenv": "^16.4.5"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/electron": {
"version": "42.1.0",
"resolved": "https://registry.npmmirror.com/electron/-/electron-42.1.0.tgz",
"integrity": "sha512-0szNwC/0dWtkvNce5j3ThiuL0TxBNrZN/BZhdOiGwbLreiD/+u3MGpkct4hA5Ycagb8MXjpEr5/oosi+FwuKRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@electron/get": "^5.0.0",
"@types/node": "^24.9.0",
"extract-zip": "^2.0.1"
},
"bin": {
"electron": "cli.js",
"install-electron": "install.js"
},
"engines": {
"node": ">= 22.12.0"
}
},
"node_modules/electron-builder": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/electron-builder/-/electron-builder-26.8.1.tgz",
"integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"app-builder-lib": "26.8.1",
"builder-util": "26.8.1",
"builder-util-runtime": "9.5.1",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
"dmg-builder": "26.8.1",
"fs-extra": "^10.1.0",
"lazy-val": "^1.0.5",
"simple-update-notifier": "2.0.0",
"yargs": "^17.6.2"
},
"bin": {
"electron-builder": "cli.js",
"install-app-deps": "install-app-deps.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/electron-builder-squirrel-windows": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz",
"integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "26.8.1",
"builder-util": "26.8.1",
"electron-winstaller": "5.4.0"
}
},
"node_modules/electron-publish": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.8.1.tgz",
"integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/fs-extra": "^9.0.11",
"builder-util": "26.8.1",
"builder-util-runtime": "9.5.1",
"chalk": "^4.1.2",
"form-data": "^4.0.5",
"fs-extra": "^10.1.0",
"lazy-val": "^1.0.5",
"mime": "^2.5.2"
}
},
"node_modules/electron-winstaller": {
"version": "5.4.0",
"resolved": "https://registry.npmmirror.com/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
"integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@electron/asar": "^3.2.1",
"debug": "^4.1.1",
"fs-extra": "^7.0.1",
"lodash": "^4.17.21",
"temp": "^0.9.0"
},
"engines": {
"node": ">=8.0.0"
},
"optionalDependencies": {
"@electron/windows-sign": "^1.1.2"
}
},
"node_modules/electron-winstaller/node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/electron-winstaller/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"license": "MIT",
"peer": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-winstaller/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/env-paths": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-3.0.0.tgz",
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"dev": true,
"license": "MIT"
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es6-error": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz",
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/exponential-backoff": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/extsprintf": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz",
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
"dev": true,
"engines": [
"node >=0.6.0"
],
"license": "MIT",
"optional": true
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT"
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/filelist": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.6.tgz",
"integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/filelist/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true,
"license": "ISC"
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz",
"integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/global-agent": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
"matcher": "^3.0.0",
"roarr": "^2.15.3",
"semver": "^7.3.2",
"serialize-error": "^7.0.1"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"define-properties": "^1.2.1",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": {
"version": "11.8.6",
"resolved": "https://registry.npmmirror.com/got/-/got-11.8.6.tgz",
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz",
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
"integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
"dev": true,
"license": "ISC",
"dependencies": {
"lru-cache": "^6.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/http-cache-semantics": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iconv-corefoundation": {
"version": "1.1.7",
"resolved": "https://registry.npmmirror.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
"integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"dependencies": {
"cli-truncate": "^2.1.0",
"node-addon-api": "^1.6.3"
},
"engines": {
"node": "^8.11.2 || >=10"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/inversify": {
"version": "7.11.0",
"resolved": "https://registry.npmmirror.com/inversify/-/inversify-7.11.0.tgz",
"integrity": "sha512-yZDprSSr8TyVeMGI/AOV4ws6gwjX22hj9Z8/oHAVpJORY6WRFTcUzhnZtibBUHEw2U8ArvHcR+i863DplQ3Cwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@inversifyjs/common": "1.5.2",
"@inversifyjs/container": "1.15.0",
"@inversifyjs/core": "9.2.0"
}
},
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-buffer": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-generator-function": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz",
"integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.4",
"generator-function": "^2.0.0",
"get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/is-nan/-/is-nan-1.3.2.tgz",
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-node-process": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/is-node-process/-/is-node-process-1.2.0.tgz",
"integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==",
"dev": true,
"license": "MIT"
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-safe-filename": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/is-safe-filename/-/is-safe-filename-0.1.1.tgz",
"integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isbinaryfile": {
"version": "5.0.7",
"resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
"integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"url": "https://github.com/sponsors/gjtorikian/"
}
},
"node_modules/isexe": {
"version": "3.1.5",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz",
"integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.4.tgz",
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.6",
"filelist": "^1.0.4",
"picocolors": "^1.1.1"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/javascript-obfuscator": {
"version": "5.4.3",
"resolved": "https://registry.npmmirror.com/javascript-obfuscator/-/javascript-obfuscator-5.4.3.tgz",
"integrity": "sha512-G5WUgh84tJa5jtk49w+nQCxQpXIvp65xhLyaFeTAdQeYqWji+SXo89k96UvYIDqojyWZEWSDKRmaWKK6bBn/2A==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@javascript-obfuscator/escodegen": "2.4.1",
"@javascript-obfuscator/estraverse": "5.4.0",
"@vercel/blob": ">=0.23.0",
"acorn": "8.15.0",
"acorn-import-attributes": "^1.9.5",
"assert": "2.1.0",
"chalk": "4.1.2",
"chance": "1.1.13",
"class-validator": "0.14.3",
"commander": "12.1.0",
"env-paths": "4.0.0",
"eslint-scope": "8.4.0",
"eslint-visitor-keys": "4.2.1",
"fast-deep-equal": "3.1.3",
"inversify": "7.11.0",
"js-string-escape": "1.0.1",
"md5": "2.3.0",
"multimatch": "5.0.0",
"process": "0.11.10",
"reflect-metadata": "0.2.2",
"string-template": "1.0.0",
"stringz": "2.1.0",
"tslib": "2.8.1"
},
"bin": {
"javascript-obfuscator": "bin/javascript-obfuscator"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/javascript-obfuscator/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/javascript-obfuscator/node_modules/env-paths": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-4.0.0.tgz",
"integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-safe-filename": "^0.1.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jiti": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-string-escape": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/js-string-escape/-/js-string-escape-1.0.1.tgz",
"integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/jsonfile": {
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/lazy-val": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/levn": {
"version": "0.3.0",
"resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz",
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
"dev": true,
"license": "MIT",
"dependencies": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/libphonenumber-js": {
"version": "1.13.3",
"resolved": "https://registry.npmmirror.com/libphonenumber-js/-/libphonenumber-js-1.13.3.tgz",
"integrity": "sha512-xMkdAMqcyG7iN2WZZmGIfWbYxW4orRkny+0/AXIbwL0xll2zkDX0Vzo/BXFa6+7mh2UvJl9MbcTtHk0YXkFtBA==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/md5/node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true,
"license": "MIT"
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"dev": true,
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/multimatch": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/multimatch/-/multimatch-5.0.0.tgz",
"integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/minimatch": "^3.0.3",
"array-differ": "^3.0.0",
"array-union": "^2.1.0",
"arrify": "^2.0.1",
"minimatch": "^3.0.4"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/multimatch/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/multimatch/node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/multimatch/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/node-abi": {
"version": "4.31.0",
"resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-4.31.0.tgz",
"integrity": "sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.6.3"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/node-addon-api": {
"version": "1.7.2",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-1.7.2.tgz",
"integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/node-api-version": {
"version": "0.2.1",
"resolved": "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.2.1.tgz",
"integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
}
},
"node_modules/node-gyp": {
"version": "12.3.0",
"resolved": "https://registry.npmmirror.com/node-gyp/-/node-gyp-12.3.0.tgz",
"integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.0",
"exponential-backoff": "^3.1.1",
"graceful-fs": "^4.2.6",
"nopt": "^9.0.0",
"proc-log": "^6.0.0",
"semver": "^7.3.5",
"tar": "^7.5.4",
"tinyglobby": "^0.2.12",
"undici": "^6.25.0",
"which": "^6.0.0"
},
"bin": {
"node-gyp": "bin/node-gyp.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/node-gyp/node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/node-gyp/node_modules/isexe": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz",
"integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=20"
}
},
"node_modules/node-gyp/node_modules/undici": {
"version": "6.25.0",
"resolved": "https://registry.npmmirror.com/undici/-/undici-6.25.0.tgz",
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/node-gyp/node_modules/which": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/which/-/which-6.0.1.tgz",
"integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^4.0.0"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/nopt": {
"version": "9.0.0",
"resolved": "https://registry.npmmirror.com/nopt/-/nopt-9.0.0.tgz",
"integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
"dev": true,
"license": "ISC",
"dependencies": {
"abbrev": "^4.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/object.assign": {
"version": "4.1.7",
"resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz",
"integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"define-properties": "^1.2.1",
"es-object-atoms": "^1.0.0",
"has-symbols": "^1.1.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz",
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.6",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
"word-wrap": "~1.2.3"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/pe-library": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/pe-library/-/pe-library-0.4.1.tgz",
"integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12",
"npm": ">=6"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jet2jet"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz",
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
},
"engines": {
"node": ">=10.4.0"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/postject": {
"version": "1.0.0-alpha.6",
"resolved": "https://registry.npmmirror.com/postject/-/postject-1.0.0-alpha.6.tgz",
"integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"commander": "^9.4.0"
},
"bin": {
"postject": "dist/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/postject/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
"dev": true,
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/proc-log": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-6.1.0.tgz",
"integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/proper-lockfile": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"retry": "^0.12.0",
"signal-exit": "^3.0.2"
}
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"dev": true,
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/read-binary-file-arch": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
"integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"bin": {
"read-binary-file-arch": "cli.js"
}
},
"node_modules/reflect-metadata": {
"version": "0.2.2",
"resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resedit": {
"version": "1.7.2",
"resolved": "https://registry.npmmirror.com/resedit/-/resedit-1.7.2.tgz",
"integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pe-library": "^0.4.1"
},
"engines": {
"node": ">=12",
"npm": ">=6"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jet2jet"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"dev": true,
"license": "MIT"
},
"node_modules/responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lowercase-keys": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/roarr": {
"version": "2.15.4",
"resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz",
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
"globalthis": "^1.0.1",
"json-stringify-safe": "^5.0.1",
"semver-compare": "^1.0.0",
"sprintf-js": "^1.1.2"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/safe-regex-test": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"is-regex": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT"
},
"node_modules/sanitize-filename": {
"version": "1.6.4",
"resolved": "https://registry.npmmirror.com/sanitize-filename/-/sanitize-filename-1.6.4.tgz",
"integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==",
"dev": true,
"license": "WTFPL OR ISC",
"dependencies": {
"truncate-utf8-bytes": "^1.0.0"
}
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/semver": {
"version": "7.8.0",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/serialize-error": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.13.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true,
"license": "ISC"
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/slice-ansi": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz",
"integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
"is-fullwidth-code-point": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/stat-mode": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz",
"integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/string-template": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/string-template/-/string-template-1.0.0.tgz",
"integrity": "sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg==",
"dev": true,
"license": "MIT"
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/stringz": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/stringz/-/stringz-2.1.0.tgz",
"integrity": "sha512-KlywLT+MZ+v0IRepfMxRtnSvDCMc3nR1qqCs3m/qIbSOWkNZYT8XHQA31rS3TnKp0c5xjZu3M4GY/2aRKSi/6A==",
"dev": true,
"license": "MIT",
"dependencies": {
"char-regex": "^1.0.2"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/sumchecker": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz",
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.1.0"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tar": {
"version": "7.5.15",
"resolved": "https://registry.npmmirror.com/tar/-/tar-7.5.15.tgz",
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/temp": {
"version": "0.9.4",
"resolved": "https://registry.npmmirror.com/temp/-/temp-0.9.4.tgz",
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "~2.6.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/temp-file": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/temp-file/-/temp-file-3.4.0.tgz",
"integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==",
"dev": true,
"license": "MIT",
"dependencies": {
"async-exit-hook": "^2.0.1",
"fs-extra": "^10.0.0"
}
},
"node_modules/throttleit": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/throttleit/-/throttleit-2.1.0.tgz",
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tiny-async-pool": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
"integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^5.5.0"
}
},
"node_modules/tiny-async-pool/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tmp": {
"version": "0.2.5",
"resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
}
},
"node_modules/tmp-promise": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/tmp-promise/-/tmp-promise-3.0.3.tgz",
"integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"tmp": "^0.2.0"
}
},
"node_modules/traverse": {
"version": "0.3.9",
"resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz",
"integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
"license": "MIT/X11",
"engines": {
"node": "*"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
"integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
"dev": true,
"license": "WTFPL",
"dependencies": {
"utf8-byte-length": "^1.0.1"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"prelude-ls": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "0.13.1",
"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici": {
"version": "7.25.0",
"resolved": "https://registry.npmmirror.com/undici/-/undici-7.25.0.tgz",
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unzip-stream": {
"version": "0.3.4",
"resolved": "https://registry.npmmirror.com/unzip-stream/-/unzip-stream-0.3.4.tgz",
"integrity": "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw==",
"license": "MIT",
"dependencies": {
"binary": "^0.3.0",
"mkdirp": "^0.5.1"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
"dev": true,
"license": "(WTFPL OR MIT)"
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmmirror.com/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/validator": {
"version": "13.15.35",
"resolved": "https://registry.npmmirror.com/validator/-/validator-13.15.35.tgz",
"integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz",
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/which": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/which/-/which-5.0.0.tgz",
"integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/which-typed-array": {
"version": "1.1.20",
"resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz",
"integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
"dev": true,
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
"call-bound": "^1.0.4",
"for-each": "^0.3.5",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"license": "ISC"
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}

68
package.json Normal file
View File

@ -0,0 +1,68 @@
{
"name": "gpcl",
"version": "1.4.1",
"description": "GoodPlanCraftLauncher",
"main": "main.js",
"scripts": {
"start": "electron .",
"clean": "if (Test-Path build) { Remove-Item -Recurse -Force build }",
"copy-files": "node scripts/copy.js",
"copy-static": "node -e \"const p=require('./package.json');console.log(JSON.stringify({name:p.name,version:p.version,main:p.main,type:p.type}))\" > build/app/package.json",
"obfuscate": "javascript-obfuscator build/app --config obfuscator.json --exclude node_modules",
"build": "npm run copy-files && npm run obfuscate && npm run copy-static && electron-builder --publish=never",
"start:dev": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"devDependencies": {
"electron": "^42.1.0",
"electron-builder": "^26.8.1",
"javascript-obfuscator": "^5.4.3"
},
"dependencies": {
"adm-zip": "^0.5.17",
"unzip-stream": "^0.3.4"
},
"build": {
"appId": "com.caellab.gamets.gpcl",
"productName": "GoodPlanCraftLauncher",
"icon": "gpcl-icon.ico",
"directories": {
"app": "build/app",
"output": "dist"
},
"asar": true,
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": false,
"installerIcon": "icon.ico",
"uninstallerIcon": "icon.ico",
"installerHeaderIcon": "icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "GoodPlanCraftLauncher",
"artifactName": "GPCL-${version}-Setup.${ext}",
"deleteAppDataOnUninstall": false
},
"files": [
"**/*",
"!.minecraft/**"
],
"asarUnpack": [
"node_modules/adm-zip/**/*",
"node_modules/unzip-stream/**/*"
]
}
}

189
preload.js Normal file
View File

@ -0,0 +1,189 @@
/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
* Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
*/
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('gpcl', {
// 获取游戏目录路径
getGameDir: () => ipcRenderer.invoke('get-game-dir'),
// 扫描本地已安装版本
scanVersions: (gameDir, silent) => ipcRenderer.invoke('scan-versions', gameDir, silent),
// 启动游戏
launch: (options) => ipcRenderer.invoke('launch-minecraft', options),
// 获取远程版本列表
getVersionManifest: () => ipcRenderer.invoke('get-version-manifest'),
// 下载指定版本
downloadVersion: (versionId, maxConcurrent) => ipcRenderer.invoke('download-version', versionId, maxConcurrent),
// 监听游戏日志
onGameLog: (callback) => {
ipcRenderer.on('game-log', (event, text) => callback(text));
},
// 监听游戏关闭
onGameClosed: (callback) => {
ipcRenderer.on('game-closed', (event, code) => callback(code));
},
// 监听启动错误
onGameError: (callback) => {
ipcRenderer.on('game-error', (event, message) => callback(message));
},
// 监听下载进度
onDownloadProgress: (callback) => {
ipcRenderer.on('download-progress', (event, data) => callback(data));
},
// 监听关闭确认询问
onConfirmCloseWhileDownloading: (callback) => {
ipcRenderer.on('confirm-close-while-downloading', () => callback());
},
// 读取玩家名
getPlayerName: () => ipcRenderer.invoke('get-player-name'),
// 保存玩家名
savePlayerName: (name) => ipcRenderer.invoke('save-player-name', name),
// 显示消息对话框
showMessageDialog: (options) => ipcRenderer.invoke('show-message-dialog', options),
// 确认关闭并清理下载
confirmCloseDownload: () => ipcRenderer.invoke('confirm-close-download'),
// 取消下载(只清理,不关闭窗口)
cancelDownloadOnly: () => ipcRenderer.invoke('cancel-download-only'),
// 获取Java镜像源设置
getJavaMirrorSettings: () => ipcRenderer.invoke('get-java-mirror-settings'),
// 获取Java版本对应的下载URL
getJavaDownloadUrl: (javaVersion) => ipcRenderer.invoke('get-java-download-url', javaVersion),
// 取消关闭
cancelClose: () => ipcRenderer.invoke('cancel-close'),
// 检查Java是否已安装
checkJava: (versionId) => ipcRenderer.invoke('check-java', versionId),
// 显示Java安装对话框
showJavaInstallDialog: (callback) => {
ipcRenderer.on('show-java-install-dialog', (event, data) => callback(data));
},
// 选择Java版本
selectJavaVersion: (data) => ipcRenderer.invoke('select-java-version', data),
// 安装Java
installJava: (javaVersion) => ipcRenderer.invoke('install-java', javaVersion),
// 卸载Java
uninstallJava: (javaVersion) => ipcRenderer.invoke('uninstall-java', javaVersion),
// 监听Java安装对话框
onJavaInstallDialog: (callback) => {
ipcRenderer.on('show-java-install-dialog', (event, data) => callback(data));
},
// 监听Java下载完成
onJavaDownloadCompleted: (callback) => {
ipcRenderer.on('java-download-completed', (event, data) => callback(data));
},
// 监听Java下载失败
onJavaDownloadFailed: (callback) => {
ipcRenderer.on('java-download-failed', (event, data) => callback(data));
},
// 监听游戏窗口创建成功
onGameWindowCreated: (callback) => {
ipcRenderer.on('game-window-created', (event, data) => callback(data));
},
// 移除所有监听器(避免重复绑定)
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
focusWindow: () => ipcRenderer.invoke('window-focus'),
closeWindow: () => ipcRenderer.invoke('window-close'),
deleteVersion: (versionId) => ipcRenderer.invoke('delete-version', versionId),
getVersionSettings: (versionId) => ipcRenderer.invoke('get-version-settings', versionId),
saveVersionSettings: (versionId, settings) => ipcRenderer.invoke('save-version-settings', versionId, settings),
selectDirectory: () => ipcRenderer.invoke('select-directory'),
cancelLaunch: () => ipcRenderer.invoke('cancel-launch'),
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
openExternal: (url) => ipcRenderer.invoke('open-external', url),
// 获取设置
getSettings: () => ipcRenderer.invoke('get-settings'),
// 保存设置
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
// 重置设置
resetSettings: () => ipcRenderer.invoke('reset-settings'),
// 播放启动动画
playStartupAnimation: () => ipcRenderer.invoke('play-startup-animation'),
// 关闭启动动画
dismissStartupAnimation: () => ipcRenderer.invoke('dismiss-startup-animation'),
// 开发者模式
setDeveloperMode: (enabled) => ipcRenderer.invoke('set-developer-mode', enabled),
// 重启应用
restartApp: () => ipcRenderer.invoke('restart-app'),
// 文件读写
readJsonFile: (subPath, fileName) => ipcRenderer.invoke('read-json-file', subPath, fileName),
writeJsonFile: (subPath, fileName, data) => ipcRenderer.invoke('write-json-file', subPath, fileName, data),
// 模组加载器版本获取
getForgeVersions: (mcVersion) => ipcRenderer.invoke('get-forge-versions', mcVersion),
getFabricVersions: (mcVersion) => ipcRenderer.invoke('get-fabric-versions', mcVersion),
getOptiFineVersions: (mcVersion) => ipcRenderer.invoke('get-optifine-versions', mcVersion),
// 下载带模组加载器的版本
downloadWithModLoader: (versionId, loaderType, loaderVersion, maxConcurrent) =>
ipcRenderer.invoke('download-with-modloader', versionId, loaderType, loaderVersion, maxConcurrent),
// 读取版本配置
getVersionConfig: (versionId) => ipcRenderer.invoke('get-version-config', versionId),
getVersionDisplayName: (versionId) => ipcRenderer.invoke('get-version-display-name', versionId),
getLaunchDisplayName: (versionId) => ipcRenderer.invoke('get-launch-display-name', versionId),
removeAllListeners: () => {
ipcRenderer.removeAllListeners('game-log');
ipcRenderer.removeAllListeners('game-closed');
ipcRenderer.removeAllListeners('game-error');
ipcRenderer.removeAllListeners('download-progress');
ipcRenderer.removeAllListeners('confirm-close-while-downloading');
ipcRenderer.removeAllListeners('show-java-install-dialog');
ipcRenderer.removeAllListeners('java-download-completed');
ipcRenderer.removeAllListeners('java-download-failed');
ipcRenderer.removeAllListeners('game-window-created');
}
});
// 屏蔽开发者工具快捷键(生产环境)
window.addEventListener('keydown', e => {
const key = (e.key || '').toLowerCase();
if (key === 'f12' ||
(e.ctrlKey && e.shiftKey && key === 'i') ||
(e.metaKey && e.altKey && key === 'i')) {
e.preventDefault();
}
}, { capture: true });
window.addEventListener('contextmenu', e => {
e.preventDefault();
}, { capture: true });

885
renderer/index.html Normal file
View File

@ -0,0 +1,885 @@
<!--
CaelLab BY-SA Code License
Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GoodPlanCraftLauncher</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="app-layout">
<div class="titlebar">
<div class="titlebar-left">
<div class="title">GPCL</div>
</div>
<div class="titlebar-center">
<nav class="menu-bar">
<ul class="menu">
<li id="menu-launch" class="menu-item"><span class="menu-icon"></span>启动</li>
<li id="menu-download" class="menu-item"><span class="menu-icon"></span>下载</li>
<li id="menu-settings" class="menu-item"><span class="menu-icon"></span>设置</li>
</ul>
</nav>
</div>
<div class="window-controls">
<button id="minimize-btn" class="min-btn" aria-label="最小化"></button>
<button id="close-btn" class="close-btn" aria-label="关闭"></button>
</div>
</div>
<main class="main-content">
<div class="container">
<div class="header">
</div>
<div id="launch-panel" class="launch-panel">
<div class="launch-sidebar">
<div class="auth-tabs">
<button id="auth-microsoft" class="auth-tab auth-tab-disabled" disabled>
<span class="auth-label">🔷 正版</span>
</button>
<button id="auth-offline" class="auth-tab auth-tab-active">
<span class="auth-label">👤 离线</span>
</button>
</div>
<div class="skin-container">
<div class="skin-wrapper">
<img src="skin/Alex.png" alt="Skin" class="skin-image">
</div>
<div class="skin-reflection"></div>
</div>
<div class="username-input-group">
<label for="username" class="username-label"></label>
<input type="text" id="username" placeholder="输入用户名" value="GPCL_Player" class="username-input">
</div>
<button id="launch-btn" class="launch-button">
<span class="launch-text">▶ 开始游戏</span>
</button>
<div id="selected-version-display" class="selected-version-display hidden">
<span class="version-display-label">将启动:</span>
<span id="selected-version-text" class="version-display-text">-</span>
</div>
<div class="launch-footer">
<button id="version-settings" class="footer-btn">
<span class="footer-text">版本设置</span>
</button>
<button id="version-select" class="footer-btn">
<span class="footer-text">版本选择</span>
</button>
</div>
</div>
<div class="launch-content">
<div id="version-settings-panel" class="version-settings-panel hidden">
<div class="version-settings-header">
<button class="version-settings-back" id="version-settings-back-btn">
<span></span>
</button>
<h3 class="version-settings-title" id="version-settings-title">版本设置</h3>
</div>
<div class="version-settings-body">
<div class="version-settings-section">
<h4 class="section-title">版本操作</h4>
<div class="setting-item">
<div class="setting-info">
<div class="setting-label">删除版本</div>
<div class="setting-desc">删除此版本的全部文件</div>
</div>
<label class="toggle-switch">
<input type="checkbox" id="version-delete-enable">
<span class="slider"></span>
</label>
</div>
</div>
<div class="version-settings-section">
<h4 class="section-title">游戏设置</h4>
<p class="section-desc">由全局设置托管表示使用全局设置</p>
<div class="setting-item">
<div class="setting-info">
<div class="setting-label">游戏最大内存</div>
<div class="setting-desc">设置游戏分配的最大内存GB</div>
</div>
<div class="custom-select" data-select-id="version-memory">
<div class="custom-select-trigger">
<span class="custom-select-value">由全局设置托管</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option selected" data-value="global">由全局设置托管</div>
<div class="custom-select-option" data-value="0.5">512 MB</div>
<div class="custom-select-option" data-value="1">1 GB</div>
<div class="custom-select-option" data-value="2">2 GB</div>
<div class="custom-select-option" data-value="4">4 GB</div>
<div class="custom-select-option" data-value="6">6 GB</div>
<div class="custom-select-option" data-value="8">8 GB</div>
<div class="custom-select-option" data-value="16">16 GB</div>
</div>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<div class="setting-label">窗口模式</div>
<div class="setting-desc">设置游戏启动时的窗口模式</div>
</div>
<div class="custom-select" data-select-id="version-window-mode">
<div class="custom-select-trigger">
<span class="custom-select-value">由全局设置托管</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option selected" data-value="global">由全局设置托管</div>
<div class="custom-select-option" data-value="fullscreen">全屏</div>
<div class="custom-select-option" data-value="windowed">窗口</div>
<div class="custom-select-option" data-value="borderless">无边框窗口</div>
<div class="custom-select-option" data-value="minimized">最小化</div>
</div>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<div class="setting-label">默认启动状态</div>
<div class="setting-desc">设置开始游戏按钮的默认状态</div>
</div>
<div class="custom-select" data-select-id="version-startup-mode">
<div class="custom-select-trigger">
<span class="custom-select-value">主界面(常规)</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option selected" data-value="default">主界面(常规)</div>
<div class="custom-select-option" data-value="join">加入服务器</div>
</div>
</div>
</div>
<div class="setting-item" id="version-server-ip-container">
<div class="setting-info">
<div class="setting-label">服务器IP/域名</div>
<div class="setting-desc">默认要加入的服务器地址</div>
</div>
<input type="text" id="version-server-ip" class="setting-input" placeholder="caellab.com:25565">
</div>
</div>
</div>
</div>
<div id="version-select-panel" class="version-select-panel hidden">
<div class="version-panel-header">
<h3 class="version-panel-title">版本选择</h3>
<div id="version-count" class="version-count">加载中...</div>
</div>
<div id="launch-version-list" class="version-list-container">
<div class="version-list-loading">正在扫描本地版本...</div>
</div>
<div id="selected-version-info" class="selected-version-info hidden">
<div class="selected-version-label">已选择:</div>
<div id="selected-version-name" class="selected-version-name">-</div>
</div>
</div>
<div id="status" class="status">准备就绪</div>
</div>
</div>
<div id="download-page" class="download-page hidden">
<div class="download-header">
<h3>下载</h3>
<button id="refresh-btn-page" class="refresh-btn hidden">刷新版本列表</button>
</div>
<div id="download-panel" class="download-status-panel hidden">
<div class="download-stats">
<div class="chart-container">
<canvas id="speedChart"></canvas>
</div>
<div class="progress-section">
<div class="speed-info">
<div class="speed-item">
<span class="speed-label">当前</span>
<span id="current-speed" class="speed-value">0.00 Mbps</span>
</div>
<div class="speed-item">
<span class="speed-label">峰值</span>
<span id="peak-speed" class="speed-value">0.00 Mbps</span>
</div>
</div>
<div class="progress-bar-wrapper">
<div class="progress-bar-bg">
<div id="progress-fill" class="progress-bar-fill"></div>
</div>
<div id="progress-text" class="progress-text">0%</div>
</div>
<div id="download-filename" class="download-filename">文件名</div>
<button id="cancel-download-btn" class="cancel-download-btn">取消下载</button>
</div>
</div>
</div>
<div id="download-status" class="status-message"></div>
<div id="version-list-section" class="version-list-section">
<div class="version-list-header">
<h4 class="section-title">游戏版本</h4>
<span id="version-count-label" class="version-count-label">加载中...</span>
</div>
<div id="version-list-container" class="version-list-container">
<div class="version-list-loading">正在加载版本列表...</div>
</div>
</div>
<div id="version-detail-page" class="version-detail-page hidden">
<button id="detail-back-btn" class="detail-back-btn">← 返回列表</button>
<div class="detail-content">
<div class="detail-header">
<div class="detail-version-id" id="detail-version-id">-</div>
<div class="detail-version-type" id="detail-version-type">-</div>
</div>
<div class="detail-info-grid">
<div class="detail-info-item">
<span class="detail-info-label">版本类型</span>
<span class="detail-info-value" id="detail-type-label">-</span>
</div>
<div class="detail-info-item">
<span class="detail-info-label">发布时间</span>
<span class="detail-info-value" id="detail-release-time">-</span>
</div>
</div>
<div class="detail-options" id="mod-loader-options">
<div class="detail-option-card" id="forge-option" data-loader="forge">
<div class="detail-option-header">
<div class="detail-option-left">
<span class="detail-option-icon">⚙️</span>
<div class="detail-option-text">
<span class="detail-option-name">Forge</span>
<span class="detail-option-desc">Forge 模组加载器</span>
</div>
</div>
<div class="detail-option-right">
<span class="detail-option-badge" id="forge-badge">检测中...</span>
<span class="detail-option-arrow"></span>
</div>
</div>
<div class="detail-option-content hidden" id="forge-content">
<div class="detail-option-loading" id="forge-loading">正在检测可用版本...</div>
<div class="detail-option-versions hidden" id="forge-versions"></div>
<div class="detail-option-error hidden" id="forge-error">该版本暂不支持 Forge</div>
</div>
</div>
<div class="detail-option-card" id="fabric-option" data-loader="fabric">
<div class="detail-option-header">
<div class="detail-option-left">
<span class="detail-option-icon">🧵</span>
<div class="detail-option-text">
<span class="detail-option-name">Fabric</span>
<span class="detail-option-desc">Fabric 模组加载器</span>
</div>
</div>
<div class="detail-option-right">
<span class="detail-option-badge" id="fabric-badge">检测中...</span>
<span class="detail-option-arrow"></span>
</div>
</div>
<div class="detail-option-content hidden" id="fabric-content">
<div class="detail-option-loading" id="fabric-loading">正在检测可用版本...</div>
<div class="detail-option-versions hidden" id="fabric-versions"></div>
<div class="detail-option-error hidden" id="fabric-error">该版本暂不支持 Fabric</div>
</div>
</div>
<div class="detail-option-card" id="optifine-option" data-loader="optifine">
<div class="detail-option-header">
<div class="detail-option-left">
<span class="detail-option-icon">👁️</span>
<div class="detail-option-text">
<span class="detail-option-name">OptiFine</span>
<span class="detail-option-desc">OptiFine 高清修复</span>
</div>
</div>
<div class="detail-option-right">
<span class="detail-option-badge" id="optifine-badge">检测中...</span>
<span class="detail-option-arrow"></span>
</div>
</div>
<div class="detail-option-content hidden" id="optifine-content">
<div class="detail-option-loading" id="optifine-loading">正在检测可用版本...</div>
<div class="detail-option-versions hidden" id="optifine-versions"></div>
<div class="detail-option-error hidden" id="optifine-error">该版本暂不支持 OptiFine</div>
</div>
</div>
</div>
<button id="detail-download-btn" class="detail-download-btn">下载</button>
</div>
</div>
<div id="java-download-section" class="java-download-section">
<h4 class="section-title">Java 运行时</h4>
<p class="section-desc">下载并安装 Minecraft 所需的 Java 运行时环境</p>
<div id="java-install-status" class="java-install-status"></div>
<div class="java-version-selector">
<div class="java-version-grid" id="java-version-grid">
<div class="java-version-card" data-version="8">
<div class="java-version-header">
<span class="java-version-num">Java 8</span>
<span class="java-version-badge" id="java-8-status"></span>
</div>
<div class="java-version-info">
<div class="java-info-row">
<span class="info-label">适用版本:</span>
<span class="info-value">Minecraft 1.7.10 - 1.16.5</span>
</div>
</div>
<button class="java-install-btn" data-version="8">安装</button>
</div>
<div class="java-version-card" data-version="17">
<div class="java-version-header">
<span class="java-version-num">Java 17</span>
<span class="java-version-badge" id="java-17-status">推荐</span>
</div>
<div class="java-version-info">
<div class="java-info-row">
<span class="info-label">适用版本:</span>
<span class="info-value">Minecraft 1.17 - 1.20.4</span>
</div>
</div>
<button class="java-install-btn" data-version="17">安装</button>
</div>
<div class="java-version-card" data-version="21">
<div class="java-version-header">
<span class="java-version-num">Java 21</span>
<span class="java-version-badge" id="java-21-status"></span>
</div>
<div class="java-version-info">
<div class="java-info-row">
<span class="info-label">适用版本:</span>
<span class="info-value">Minecraft 1.20.5 - 1.21+</span>
</div>
</div>
<button class="java-install-btn" data-version="21">安装</button>
</div>
<div class="java-version-card" data-version="25">
<div class="java-version-header">
<span class="java-version-num">Java 25</span>
<span class="java-version-badge" id="java-25-status"></span>
</div>
<div class="java-version-info">
<div class="java-info-row">
<span class="info-label">适用版本:</span>
<span class="info-value">Minecraft 1.21+ (最新)</span>
</div>
</div>
<button class="java-install-btn" data-version="25">安装</button>
</div>
</div>
</div>
</div>
</div>
<div id="settings-page" class="settings-page hidden">
<div class="settings-sidebar" id="settings-sidebar">
<ul class="settings-sidebar-menu">
<li id="settings-tab-game" class="settings-sidebar-item active" data-tab="game">
<span class="sidebar-icon">🎮</span>
<span class="sidebar-label">游戏</span>
</li>
<li id="settings-tab-compatibility" class="settings-sidebar-item" data-tab="compatibility">
<span class="sidebar-icon">🔧</span>
<span class="sidebar-label">兼容性</span>
</li>
<li id="settings-tab-appearance" class="settings-sidebar-item" data-tab="appearance">
<span class="sidebar-icon">🎨</span>
<span class="sidebar-label">外观</span>
</li>
<li id="settings-tab-download" class="settings-sidebar-item" data-tab="download">
<span class="sidebar-icon"></span>
<span class="sidebar-label">下载</span>
</li>
<li id="settings-tab-advanced" class="settings-sidebar-item" data-tab="advanced">
<span class="sidebar-icon">⚙️</span>
<span class="sidebar-label">高级</span>
</li>
<li id="settings-tab-about" class="settings-sidebar-item" data-tab="about">
<span class="sidebar-icon"></span>
<span class="sidebar-label">关于</span>
</li>
</ul>
</div>
<div class="settings-content">
<!-- 游戏设置 -->
<div id="settings-content-game" class="settings-content-panel active">
<div class="settings-page-inner">
<div class="settings-group">
<h2 class="settings-group-title">游戏设置</h2>
<p class="settings-group-desc">配置游戏运行相关参数</p>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">游戏内存</div>
<div class="setting-desc">设置游戏分配的最大内存GB</div>
</div>
<div class="setting-control">
<div class="custom-select" data-select-id="settings-memory">
<div class="custom-select-trigger">
<span class="custom-select-value">2 GB</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option" data-value="0.5">512 MB</div>
<div class="custom-select-option" data-value="1">1 GB</div>
<div class="custom-select-option selected" data-value="2">2 GB</div>
<div class="custom-select-option" data-value="4">4 GB</div>
<div class="custom-select-option" data-value="6">6 GB</div>
<div class="custom-select-option" data-value="8">8 GB</div>
<div class="custom-select-option" data-value="16">16 GB</div>
</div>
</div>
</div>
</div>
</div>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">窗口模式</div>
<div class="setting-desc">设置游戏启动时的窗口模式</div>
</div>
<div class="setting-control">
<div class="custom-select" data-select-id="settings-window-mode">
<div class="custom-select-trigger">
<span class="custom-select-value">窗口</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option" data-value="fullscreen">全屏</div>
<div class="custom-select-option selected" data-value="windowed">窗口</div>
<div class="custom-select-option" data-value="borderless">无边框窗口</div>
<div class="custom-select-option" data-value="minimized">最小化</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 兼容性设置 -->
<div id="settings-content-compatibility" class="settings-content-panel hidden">
<div class="settings-page-inner">
<div class="settings-group">
<h2 class="settings-group-title">兼容性设置</h2>
<p class="settings-group-desc">解决启动器兼容性问题</p>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">恢复默认设置</div>
<div class="setting-desc">将所有设置恢复为默认值,此操作会重启启动器</div>
</div>
<div class="setting-control">
<label class="toggle-switch">
<input type="checkbox" id="settings-reset-default">
<span class="slider"></span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 外观设置 -->
<div id="settings-content-appearance" class="settings-content-panel hidden">
<div class="settings-page-inner">
<div class="settings-group">
<h2 class="settings-group-title">外观设置</h2>
<p class="settings-group-desc">配置启动器外观相关参数</p>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">主题模式</div>
<div class="setting-desc">选择启动器主题</div>
</div>
<div class="setting-control">
<div class="custom-select" data-select-id="settings-theme">
<div class="custom-select-trigger">
<span class="custom-select-value">浅色模式</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option" data-value="dark">深色模式</div>
<div class="custom-select-option selected" data-value="light">浅色模式</div>
</div>
</div>
</div>
</div>
</div>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">界面缩放</div>
<div class="setting-desc">调整界面元素大小</div>
</div>
<div class="setting-control">
<div class="custom-select" data-select-id="settings-scale">
<div class="custom-select-trigger">
<span class="custom-select-value">100% (默认)</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option" data-value="75">75%</div>
<div class="custom-select-option" data-value="90">90%</div>
<div class="custom-select-option selected" data-value="100">100%</div>
<div class="custom-select-option" data-value="110">110%</div>
<div class="custom-select-option" data-value="125">125%</div>
<div class="custom-select-option" data-value="150">150%</div>
<div class="custom-select-option" data-value="180">180%</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 下载设置 -->
<div id="settings-content-download" class="settings-content-panel hidden">
<div class="settings-page-inner">
<div class="settings-group">
<h2 class="settings-group-title">下载设置</h2>
<p class="settings-group-desc">配置Minecraft版本下载相关参数</p>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">最大线程数</div>
<div class="setting-desc">在下载时由于小文件较多需要使用多线程下载提高速度此处数值越高代表可建立的连接数越高但越消耗性能。建议值64-128。</div>
</div>
<div class="setting-control">
<button class="num-btn" id="btn-minus"></button>
<input type="number" class="num-display" id="num-threads" value="64" min="1" max="128">
<button class="num-btn" id="btn-plus">+</button>
</div>
</div>
</div>
<!-- Java镜像源设置 -->
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">Java 镜像源</div>
<div class="setting-desc">选择Java运行时的下载镜像源</div>
</div>
<div class="setting-control">
<div class="custom-select" data-select-id="java-mirror-select">
<div class="custom-select-trigger">
<span class="custom-select-value">清华大学镜像(推荐)</span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option selected" data-value="tsinghua">清华大学镜像(推荐)</div>
<div class="custom-select-option" data-value="custom">自定义镜像源</div>
</div>
</div>
</div>
</div>
<div id="custom-java-mirror-container" class="setting-row hidden">
<div class="setting-left">
<div class="setting-label">自定义镜像源地址</div>
<div class="setting-desc">支持使用 {v} 变量代表Java版本例如https://example.com/java/{v}/OpenJDK{v}U-jre_x64_windows_hotspot_{v}.zip<br>或不使用变量https://example.com/java/OpenJDK8U-jre_x64_windows_hotspot_8u402b06.zip</div>
</div>
<div class="setting-control" style="flex-direction: column; align-items: flex-start;">
<input type="text" id="custom-java-mirror-url" class="setting-input" placeholder="https://example.com/java/{v}/OpenJDK{v}U-jre_x64_windows_hotspot_{v}.zip">
<div class="setting-desc" style="margin-top: 8px;">
<strong>支持的变量:</strong>
<ul style="margin: 5px 0 5px 20px;">
<li><code>{v}</code> - Java版本号如 8, 17, 21</li>
<li><code>{version}</code> - 完整版本号(如 8u402b06</li>
</ul>
<strong>示例:</strong>
<div style="margin-top: 5px; padding: 8px; background: rgba(255,255,255,0.05); border-radius: 4px; font-family: monospace;">
<div style="color: #4fc3f7;">清华大学:</div>
<div style="color: #b8c0d0;">https://mirrors.tuna.tsinghua.edu.cn/Adoptium/{v}/jre/x64/windows/</div>
<div style="color: #4fc3f7; margin-top: 8px;">自定义:</div>
<div style="color: #b8c0d0;">https://cdn.example.com/java/{v}/OpenJDK{v}U-jre_x64_windows_hotspot_{v}.zip</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 高级设置 -->
<div id="settings-content-advanced" class="settings-content-panel hidden">
<div class="settings-page-inner">
<div class="settings-group">
<h2 class="settings-group-title">高级设置</h2>
<p class="settings-group-desc">配置高级选项</p>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">防止多次启动</div>
<div class="setting-desc">开启后将阻止启动器重复运行</div>
</div>
<div class="setting-control">
<label class="switch">
<input type="checkbox" id="prevent-multiple-launch" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">播放启动动画</div>
<div class="setting-desc">启动游戏时全屏播放 Minecraft 欢迎动画</div>
</div>
<div class="setting-control">
<label class="switch">
<input type="checkbox" id="play-startup-animation">
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">开发者选项</div>
<div class="setting-desc">启用后允许打开 DevTools、右键菜单及刷新页面</div>
</div>
<div class="setting-control">
<label class="switch">
<input type="checkbox" id="developer-mode">
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">自动清除日志</div>
<div class="setting-desc">开启后将在启动时自动清理过期的日志文件</div>
</div>
<div class="setting-control">
<label class="switch">
<input type="checkbox" id="auto-clear-logs" checked>
<span class="slider"></span>
</label>
</div>
</div>
<div id="log-retention-container" class="setting-row setting-row-indent">
<div class="setting-left">
<div class="setting-label">保留日志时间</div>
<div class="setting-desc">设置日志文件的保留时长</div>
</div>
<div class="setting-control">
<input type="number" id="log-retention-value" class="num-display" value="7" min="1">
<div class="custom-select small" data-select-id="log-retention-unit">
<div class="custom-select-trigger">
<span class="custom-select-value"></span>
<span class="custom-select-arrow"></span>
</div>
<div class="custom-select-dropdown">
<div class="custom-select-option" data-value="hour"></div>
<div class="custom-select-option selected" data-value="day"></div>
<div class="custom-select-option" data-value="month"></div>
<div class="custom-select-option" data-value="year"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 关于页面 -->
<div id="settings-content-about" class="settings-content-panel hidden">
<div class="settings-page-inner">
<div class="settings-group">
<h2 class="settings-group-title">关于</h2>
<p class="settings-group-desc">关于 Good Plan Craft Launcher</p>
<div class="setting-card about-card">
<div class="about-header">
<img src="icon.ico" alt="图标" class="about-icon" onerror="this.style.display='none'">
<div class="about-info">
<div class="about-title">Good Plan Craft Launcher</div>
<div class="about-version" id="about-version">版本: 1.1.0</div>
</div>
</div>
<button id="check-update-btn" class="check-update-btn">检查更新</button>
<div id="update-status" class="update-status"></div>
<button id="go-download-btn" class="go-download-btn hidden">立即前往下载</button>
</div>
<div class="setting-card">
<div class="setting-row">
<div class="setting-left">
<div class="setting-label">自动检查更新</div>
<div class="setting-desc">启动时自动检查是否有新版本可用</div>
</div>
<div class="setting-control">
<label class="switch">
<input type="checkbox" id="auto-check-update" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="toast-container" id="toast-container"></div>
</div>
</main>
</div>
<div id="dialog-overlay" class="dialog-overlay hidden">
<div class="dialog-box">
<div class="dialog-header">
<span id="dialog-icon" class="dialog-icon"></span>
<h3 id="dialog-title" class="dialog-title">提示</h3>
</div>
<div class="dialog-body">
<p id="dialog-message" class="dialog-message">消息内容</p>
</div>
<div class="dialog-footer" id="dialog-footer-single">
<button id="dialog-btn-ok" class="dialog-btn dialog-btn-primary">确定</button>
</div>
<div class="dialog-footer hidden" id="dialog-footer-confirm">
<button id="dialog-btn-cancel" class="dialog-btn dialog-btn-secondary"></button>
<button id="dialog-btn-yes" class="dialog-btn dialog-btn-primary"></button>
</div>
</div>
<div id="float-notification" class="float-notification hidden">
<div class="float-notification-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<circle cx="12" cy="13" r="3" />
</svg>
</div>
<div class="float-notification-content">
<div class="float-notification-title" id="float-notification-title"></div>
<div class="float-notification-text" id="float-notification-text"></div>
</div>
<div class="float-notification-progress">
<div class="float-progress-bar">
<div class="float-progress-fill" id="float-progress-fill"></div>
</div>
<span class="float-progress-text" id="float-progress-text">0%</span>
</div>
<button class="float-notification-close" id="float-notification-close">&times;</button>
</div>
</div>
<div id="launch-animation-overlay" class="launch-animation-overlay hidden">
<div class="launch-animation-content">
<div class="pickaxe-container">
<div class="pickaxe" id="pickaxe">
<svg viewBox="0 0 64 64" width="80" height="80">
<rect x="22" y="28" width="4" height="32" fill="#8D6E63"/>
<rect x="21" y="28" width="2" height="32" fill="#A1887F"/>
<rect x="27" y="28" width="1" height="32" fill="#6D4C41"/>
<rect x="20" y="24" width="10" height="4" fill="#006064"/>
<rect x="18" y="18" width="10" height="6" fill="#00838F"/>
<rect x="16" y="12" width="6" height="6" fill="#0097A7"/>
<polygon points="24,20 54,10 54,25 24,30" fill="#4DD0E1"/>
<polygon points="24,30 54,25 54,35 24,38" fill="#26C6DA"/>
<polygon points="24,20 54,10 39,5" fill="#80DEEA"/>
<polygon points="54,10 59,15 54,25" fill="#00ACC1"/>
<polygon points="24,20 24,30 19,25" fill="#00BCD4"/>
</svg>
</div>
<div class="block-container">
<div class="block" id="block">
<svg viewBox="0 0 64 64" width="60" height="60">
<rect x="2" y="2" width="60" height="60" fill="#3E3E3E" stroke="#2a2a2a" stroke-width="2"/>
<rect x="6" y="6" width="12" height="12" fill="#4a4a4a"/>
<rect x="22" y="8" width="10" height="8" fill="#353535"/>
<rect x="40" y="4" width="14" height="10" fill="#484848"/>
<rect x="4" y="24" width="16" height="14" fill="#424242"/>
<rect x="26" y="20" width="12" height="12" fill="#383838"/>
<rect x="44" y="18" width="10" height="14" fill="#454545"/>
<rect x="6" y="42" width="14" height="10" fill="#3a3a3a"/>
<rect x="24" y="38" width="18" height="12" fill="#404040"/>
<rect x="46" y="36" width="12" height="16" fill="#464646"/>
<rect x="8" y="54" width="10" height="8" fill="#393939"/>
<rect x="24" y="52" width="14" height="10" fill="#434343"/>
<rect x="42" y="54" width="16" height="8" fill="#474747"/>
</svg>
</div>
<div class="particles" id="particles"></div>
</div>
</div>
<div class="launch-status">
<div id="launch-status-text" class="status-text">正在启动游戏...</div>
<div id="launch-progress-bar" class="progress-bar-container">
<div id="launch-progress-fill" class="progress-bar-fill"></div>
</div>
<div id="launch-version-info" class="version-info"></div>
</div>
<button id="launch-cancel-btn" class="launch-cancel-btn">取消启动</button>
<div class="launch-tip-card">
<div class="launch-tip-header">
<span class="launch-tip-icon">💡</span>
<span class="launch-tip-label">你知道吗?</span>
</div>
<div id="launch-tip-text" class="launch-tip-text"></div>
</div>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>

14
renderer/js/chart.umd.js Normal file

File diff suppressed because one or more lines are too long

BIN
renderer/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

3270
renderer/renderer.js Normal file
View File

@ -0,0 +1,3270 @@
/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
* Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
*/
let GAME_DIR = null;
async function initGameDir() {
if (!GAME_DIR) {
GAME_DIR = await window.gpcl.getGameDir();
}
return GAME_DIR;
}
const usernameInput = document.getElementById('username');
const versionSelect = document.getElementById('version');
const launchBtn = document.getElementById('launch-btn');
const statusDiv = document.getElementById('status');
// Download page elements
const cancelDownloadBtn = document.getElementById('cancel-download-btn');
const downloadBtn = document.getElementById('download-btn-page');
const versionListContainer = document.getElementById('version-list-container');
const versionListSection = document.getElementById('version-list-section');
const versionDetailPage = document.getElementById('version-detail-page');
const detailBackBtn = document.getElementById('detail-back-btn');
const detailDownloadBtn = document.getElementById('detail-download-btn');
const versionCountLabel = document.getElementById('version-count-label');
const menuDownload = document.getElementById('menu-download');
const menuLaunch = document.getElementById('menu-launch');
const menuSettings = document.getElementById('menu-settings');
const downloadPage = document.getElementById('download-page');
const launchPanel = document.getElementById('launch-panel');
const mainContent = document.querySelector('.main-content .container');
const minimizeBtn = document.getElementById('minimize-btn');
const closeBtn = document.getElementById('close-btn');
// Toast notification system
const toastContainer = document.getElementById('toast-container');
const toastHistory = new Map();
const TOAST_DEDUP_MS = 3000;
const dialogOverlay = document.getElementById('dialog-overlay');
const dialogIcon = document.getElementById('dialog-icon');
const dialogTitle = document.getElementById('dialog-title');
const dialogMessage = document.getElementById('dialog-message');
const dialogFooterSingle = document.getElementById('dialog-footer-single');
const dialogFooterConfirm = document.getElementById('dialog-footer-confirm');
const dialogBtnOk = document.getElementById('dialog-btn-ok');
const dialogBtnYes = document.getElementById('dialog-btn-yes');
const dialogBtnCancel = document.getElementById('dialog-btn-cancel');
let dialogResolve = null;
function showDialog(options) {
return new Promise((resolve) => {
dialogResolve = resolve;
const icons = {
info: '',
warning: '⚠️',
error: '❌',
question: '❓'
};
dialogIcon.textContent = icons[options.type] || icons.info;
dialogTitle.textContent = options.title || '提示';
dialogMessage.textContent = options.message || '';
if (options.type === 'confirm' || options.buttons) {
dialogFooterSingle.classList.add('hidden');
dialogFooterConfirm.classList.remove('hidden');
} else {
dialogFooterSingle.classList.remove('hidden');
dialogFooterConfirm.classList.add('hidden');
}
dialogOverlay.classList.remove('hidden');
});
}
function hideDialog() {
dialogOverlay.classList.add('hidden');
dialogResolve = null;
}
if (dialogBtnOk) {
dialogBtnOk.addEventListener('click', () => {
if (dialogResolve) dialogResolve(true);
hideDialog();
});
}
if (dialogBtnYes) {
dialogBtnYes.addEventListener('click', () => {
if (dialogResolve) dialogResolve(true);
hideDialog();
});
}
if (dialogBtnCancel) {
dialogBtnCancel.addEventListener('click', () => {
if (dialogResolve) dialogResolve(false);
hideDialog();
});
}
// 监听下载时关闭窗口的询问
if (window.gpcl && window.gpcl.onConfirmCloseWhileDownloading) {
window.gpcl.onConfirmCloseWhileDownloading(async () => {
const result = await showDialog({
type: 'confirm',
title: '确认关闭',
message: '当前正在下载中,关闭会停止下载并清理已下载的文件。确定要关闭吗?'
});
if (result) {
// 用户点击"是",确认关闭并清理
if (window.gpcl && window.gpcl.confirmCloseDownload) {
await window.gpcl.confirmCloseDownload();
}
} else {
// 用户点击"否",取消关闭
if (window.gpcl && window.gpcl.cancelClose) {
await window.gpcl.cancelClose();
}
}
});
}
function showToast(title, message, type = 'info', key = null) {
if (!toastContainer) return;
if (key) {
const last = toastHistory.get(key);
if (last && Date.now() - last < TOAST_DEDUP_MS) return;
toastHistory.set(key, Date.now());
}
const icons = { success: '✅', error: '❌', warning: '⚠️', info: '' };
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `
<span class="toast-icon">${icons[type] || icons.info}</span>
<div style="flex:1">
<div class="toast-title">${title}</div>
${message ? `<div class="toast-message">${message}</div>` : ''}
</div>
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
`;
toastContainer.appendChild(toast);
setTimeout(() => {
toast.classList.add('toast-out');
setTimeout(() => toast.remove(), 300);
}, 4000);
}
// Chart elements
let chartLoaded = false;
function loadChartJs() {
return new Promise((resolve) => {
if (chartLoaded || window.Chart) {
resolve();
return;
}
const script = document.createElement('script');
script.src = 'js/chart.umd.js';
script.onload = () => {
chartLoaded = true;
resolve();
};
script.onerror = () => resolve();
document.head.appendChild(script);
});
}
let speedChart = null;
let speedData = [];
let peakSpeed = 0;
const downloadPanel = document.getElementById('download-panel');
let currentPage = 'launch';
function getMaxConcurrentFromSettings() {
return DEFAULT_MAX_THREADS;
}
function showPage(name) {
currentPage = name;
const settingsPage = document.getElementById('settings-page');
const settingsSidebar = document.getElementById('settings-sidebar');
if (name === 'download') {
if (downloadPage) downloadPage.classList.remove('hidden');
if (launchPanel) launchPanel.classList.add('hidden');
if (settingsPage) settingsPage.classList.add('hidden');
} else if (name === 'launch') {
if (downloadPage) downloadPage.classList.add('hidden');
if (launchPanel) launchPanel.classList.remove('hidden');
if (settingsPage) settingsPage.classList.add('hidden');
} else if (name === 'settings') {
if (downloadPage) downloadPage.classList.add('hidden');
if (launchPanel) launchPanel.classList.add('hidden');
if (settingsPage) settingsPage.classList.remove('hidden');
// 默认选中第一个子页面(游戏)
switchSettingsTab('game');
}
if (menuDownload) menuDownload.classList.toggle('active', name === 'download');
if (menuLaunch) menuLaunch.classList.toggle('active', name === 'launch');
if (menuSettings) menuSettings.classList.toggle('active', name === 'settings');
}
function switchSettingsTab(tabName) {
const sidebarItems = document.querySelectorAll('.settings-sidebar-item');
const contentPanels = document.querySelectorAll('.settings-content-panel');
sidebarItems.forEach(item => {
item.classList.toggle('active', item.dataset.tab === tabName);
});
contentPanels.forEach(panel => {
panel.classList.toggle('active', panel.id === `settings-content-${tabName}`);
panel.classList.toggle('hidden', panel.id !== `settings-content-${tabName}`);
});
}
// 更新菜单选中状态(确保菜单始终与当前页面同步)
function updateMenuActive(pageName) {
if (menuDownload) menuDownload.classList.toggle('active', pageName === 'download');
if (menuLaunch) menuLaunch.classList.toggle('active', pageName === 'launch');
if (menuSettings) menuSettings.classList.toggle('active', pageName === 'settings');
}
if (menuDownload) menuDownload.addEventListener('click', () => showPage('download'));
if (menuLaunch) menuLaunch.addEventListener('click', () => showPage('launch'));
if (menuSettings) menuSettings.addEventListener('click', () => showPage('settings'));
if (minimizeBtn) minimizeBtn.addEventListener('click', () => { if (window.gpcl && window.gpcl.minimizeWindow) window.gpcl.minimizeWindow(); });
if (closeBtn) closeBtn.addEventListener('click', () => {
if (window.gpcl && typeof window.gpcl.closeWindow === 'function') {
window.gpcl.closeWindow();
} else {
// 备选方案:尝试直接关闭窗口
window.close();
}
});
const customSelectHandlers = new Map();
function initCustomSelect(selectId, onChangeCallback) {
const selectContainer = document.querySelector(`.custom-select[data-select-id="${selectId}"]`);
if (!selectContainer) return;
const trigger = selectContainer.querySelector('.custom-select-trigger');
const dropdown = selectContainer.querySelector('.custom-select-dropdown');
const valueSpan = selectContainer.querySelector('.custom-select-value');
const options = selectContainer.querySelectorAll('.custom-select-option');
// 保存初始状态
let currentValue = null;
options.forEach(option => {
if (option.classList.contains('selected')) {
currentValue = option.dataset.value;
}
});
// 点击触发器打开/关闭下拉框
trigger.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = dropdown.classList.contains('open');
closeAllCustomSelects();
if (!isOpen) {
dropdown.classList.add('open');
trigger.classList.add('active');
}
});
// 点击选项
options.forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
if (option.classList.contains('disabled')) return;
const value = option.dataset.value;
const label = option.textContent;
// 更新选中状态
options.forEach(opt => opt.classList.remove('selected'));
option.classList.add('selected');
valueSpan.textContent = label;
currentValue = value;
// 关闭下拉框
closeAllCustomSelects();
// 调用回调
if (onChangeCallback) {
onChangeCallback(value);
}
});
});
// 保存处理函数以便后续使用
customSelectHandlers.set(selectId, {
setValue: (value, label) => {
options.forEach(opt => opt.classList.remove('selected'));
const targetOpt = selectContainer.querySelector(`.custom-select-option[data-value="${value}"]`);
if (targetOpt) {
targetOpt.classList.add('selected');
valueSpan.textContent = targetOpt.textContent;
currentValue = value;
} else if (label) {
valueSpan.textContent = label;
currentValue = value;
}
},
getValue: () => currentValue
});
}
function closeAllCustomSelects() {
document.querySelectorAll('.custom-select-dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.custom-select-trigger').forEach(t => t.classList.remove('active'));
}
// 点击页面其他地方关闭下拉框
document.addEventListener('click', closeAllCustomSelects);
// 提供全局访问方法
function setCustomSelectValue(selectId, value, label) {
const handler = customSelectHandlers.get(selectId);
if (handler) {
handler.setValue(value, label);
}
}
function getCustomSelectValue(selectId) {
const handler = customSelectHandlers.get(selectId);
return handler ? handler.getValue() : null;
}
const DEFAULT_MAX_THREADS = 64;
const MAX_THREADS_LIMIT = 128;
// Java版本 - 官方runtime目录名映射用于显示提示
const JAVA_RUNTIME_NAME_MAP = {
"8": "jre-legacy",
"16": "java-runtime-beta",
"17": "java-runtime-gamma",
"21": "java-runtime-delta",
"25": "java-runtime-epsilon"
};
// Java版本说明改进版更准确的推荐
const JAVA_VERSION_DESC = {
"8": { mcVersions: "1.7.10 - 1.16.5", desc: "经典版本兼容" },
"17": { mcVersions: "1.17 - 1.20.4", desc: "最稳定,推荐使用" },
"21": { mcVersions: "1.20.5 - 1.21+", desc: "最新LTS版本" },
"25": { mcVersions: "1.21+", desc: "最新尝鲜版" }
};
function getJavaRuntimeName(javaVersion) {
return JAVA_RUNTIME_NAME_MAP[javaVersion] || `jre${javaVersion}`;
}
// 初始化Java版本状态检测
async function initJavaVersionStatus() {
const javaVersions = ["8", "17", "21", "25"];
for (const ver of javaVersions) {
try {
const result = await window.gpcl.checkJava(ver);
const card = document.querySelector(`.java-version-card[data-version="${ver}"]`);
const badge = document.getElementById(`java-${ver}-status`);
const btn = card?.querySelector('.java-install-btn');
if (result.installed) {
if (card) card.classList.add('installed');
if (badge) {
badge.textContent = '已安装';
badge.className = 'java-version-badge installed';
}
if (btn) {
btn.textContent = '卸载';
btn.classList.add('installed');
btn.disabled = false;
}
}
} catch (e) {
console.error(`检查Java ${ver} 状态失败`, e);
}
}
// 更新Java安装状态提示
updateJavaInstallStatus();
}
// 更新Java安装状态提示
function updateJavaInstallStatus() {
const statusEl = document.getElementById('java-install-status');
if (!statusEl) return;
const javaVersions = ["8", "17", "21", "25"];
const installed = [];
const notInstalled = [];
javaVersions.forEach(ver => {
const card = document.querySelector(`.java-version-card[data-version="${ver}"]`);
if (card?.classList.contains('installed')) {
installed.push(ver);
} else {
notInstalled.push(ver);
}
});
if (installed.length === javaVersions.length) {
statusEl.textContent = '✅ 所有Java版本均已安装可以运行任何Minecraft版本';
statusEl.style.background = 'rgba(76, 175, 80, 0.15)';
} else {
statusEl.textContent = `已安装 Java ${installed.join(', ') || '无'} | 未安装 Java ${notInstalled.join(', ')}`;
statusEl.style.background = 'rgba(79, 195, 247, 0.08)';
}
}
// 绑定Java安装按钮事件
function bindJavaInstallButtons() {
const buttons = document.querySelectorAll('.java-install-btn');
buttons.forEach(btn => {
btn.addEventListener('click', async () => {
const version = btn.dataset.version;
if (btn.classList.contains('installed')) {
await uninstallJavaWithPanel(version);
} else {
await installJavaWithPanel(version);
}
});
});
}
// 使用通用面板安装Java
async function installJavaWithPanel(javaVersion) {
const filenameEl = document.getElementById('download-filename');
const downloadStatusEl = document.getElementById('download-status');
const downloadPanel = document.getElementById('download-panel');
const progressFill = document.getElementById('progress-fill');
const progressText = document.getElementById('progress-text');
const cancelBtn = document.getElementById('cancel-download-btn');
// 滚动到下载页面顶部(用户主动操作)
const downloadPage = document.getElementById('download-page');
if (downloadPage) {
downloadPage.scrollTop = 0;
}
// 显示通用下载面板
if (downloadPanel) {
downloadPanel.classList.remove('hidden');
downloadPanel.classList.add('download-panel-container');
}
if (filenameEl) filenameEl.textContent = `正在安装: Java ${javaVersion}`;
if (downloadStatusEl) downloadStatusEl.textContent = `准备下载 Java ${javaVersion}...`;
if (progressFill) progressFill.style.width = '0%';
if (progressText) progressText.textContent = '0%';
if (cancelBtn) cancelBtn.disabled = false;
// 禁用Java安装按钮
document.querySelectorAll('.java-install-btn').forEach(btn => {
btn.disabled = true;
});
try {
const result = await window.gpcl.installJava(javaVersion);
if (result.success) {
if (downloadStatusEl) downloadStatusEl.textContent = `✅ Java ${javaVersion} 安装成功!`;
if (progressFill) progressFill.style.width = '100%';
if (progressText) progressText.textContent = '100%';
showToast('安装成功', `Java ${javaVersion} 已安装完成`, 'success', 'java-install-success');
// 更新Java卡片状态
const card = document.querySelector(`.java-version-card[data-version="${javaVersion}"]`);
const badge = document.getElementById(`java-${javaVersion}-status`);
const btn = card?.querySelector('.java-install-btn');
if (card) card.classList.add('installed');
if (badge) {
badge.textContent = '已安装';
badge.className = 'java-version-badge installed';
}
if (btn) {
btn.textContent = '卸载';
btn.classList.add('installed');
btn.disabled = false;
}
updateJavaInstallStatus();
setTimeout(() => {
if (downloadPanel) {
downloadPanel.classList.add('hidden');
downloadPanel.classList.remove('download-panel-container');
}
}, 2000);
} else {
throw new Error(result.error || '安装失败');
}
} catch (err) {
if (downloadStatusEl) downloadStatusEl.textContent = `❌ 安装失败: ${err.message || err}`;
showToast('安装失败', err.message || String(err), 'error', 'java-install-failed');
}
// 重新启用Java安装按钮
document.querySelectorAll('.java-install-btn').forEach(btn => {
if (!btn.classList.contains('installed')) {
btn.disabled = false;
}
});
if (cancelBtn) cancelBtn.disabled = true;
}
// 卸载Java运行时使用弹窗确认
async function uninstallJavaWithPanel(javaVersion) {
const confirmed = await showDialog({
type: 'confirm',
title: '确认卸载',
message: `确定要卸载 Java ${javaVersion} 吗?`
});
if (!confirmed) return;
showToast('正在卸载', `正在卸载 Java ${javaVersion}...`, 'info', 'java-uninstalling');
try {
const result = await window.gpcl.uninstallJava(javaVersion);
if (result.success) {
showToast('卸载成功', `Java ${javaVersion} 已卸载`, 'success', 'java-uninstall-success');
// 更新Java卡片状态
const card = document.querySelector(`.java-version-card[data-version="${javaVersion}"]`);
const badge = document.getElementById(`java-${javaVersion}-status`);
const btn = card?.querySelector('.java-install-btn');
if (card) card.classList.remove('installed');
if (badge) {
badge.textContent = '';
badge.className = 'java-version-badge';
}
if (btn) {
btn.textContent = '安装';
btn.classList.remove('installed');
btn.disabled = false;
}
updateJavaInstallStatus();
} else {
throw new Error(result.error || '卸载失败');
}
} catch (err) {
showToast('卸载失败', err.message || String(err), 'error', 'java-uninstall-failed');
}
}
// 改进的Java版本推荐基于Minecraft版本JSON
function getRecommendedJavaVersionFromMC(mcVersion) {
// 解析Minecraft版本号
const parts = mcVersion.split('.');
let major = parseInt(parts[0], 10);
let minor = parseInt(parts[1], 10);
let patch = parts.length > 2 ? parseInt(parts[2], 10) || 0 : 0;
// 处理无效 major
if (isNaN(major)) major = 1;
// Minecraft版本对应Java版本Mojang官方推荐
// 1.7-1.16: Java 8
// 1.17-1.20.4: Java 17
// 1.20.5-1.21.4: Java 21
// 1.22+ / 21+: Java 21
// 26+: Java 21
// 处理像 "26" 这种只有 major 没有 minor 的情况
if (isNaN(minor)) {
// 26 及以上使用 Java 25
if (major >= 26) {
return { version: '25', reason: `Minecraft ${mcVersion} 推荐使用 Java 25` };
}
// 21-25 使用 Java 21
if (major >= 21) {
return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` };
}
return { version: '17', reason: '使用推荐的稳定版' };
}
// 处理 1.x.x 的旧版本
if (major === 1) {
if (minor <= 16) {
return { version: '8', reason: `Minecraft ${mcVersion} 官方推荐 Java 8` };
} else if (minor <= 20 || (minor === 20 && patch <= 4)) {
return { version: '17', reason: `Minecraft ${mcVersion} 官方推荐 Java 17` };
} else if (minor === 20 && patch >= 5) {
return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` };
} else if (minor >= 21) {
return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` };
}
}
// 处理 2.x.x 和更大的版本21, 24, 26, ...
if (major >= 2) {
// 26 及以上使用 Java 25
if (major >= 26) {
return { version: '25', reason: `Minecraft ${mcVersion} 推荐使用 Java 25` };
}
return { version: '21', reason: `Minecraft ${mcVersion} 推荐使用 Java 21` };
}
// 默认返回最稳定的版本
return { version: '17', reason: '使用推荐的稳定版' };
}
// 监听Java下载进度
if (window.gpcl && window.gpcl.onDownloadProgress) {
const originalCallback = window.gpcl.onDownloadProgress;
window.gpcl.onDownloadProgress((data) => {
// 更新通用下载面板的进度
const progressFill = document.getElementById('progress-fill');
const progressText = document.getElementById('progress-text');
const filenameEl = document.getElementById('download-filename');
const downloadStatusEl = document.getElementById('download-status');
if (progressFill && progressText) {
const percent = data.percent || 0;
progressFill.style.width = percent + '%';
progressText.textContent = percent.toFixed(1) + '%';
}
if (data.label && filenameEl) {
filenameEl.textContent = data.label;
}
if (data.label && downloadStatusEl) {
downloadStatusEl.textContent = `正在下载: ${data.label}`;
}
// 更新漂浮通知的进度
const floatProgressFill = document.getElementById('float-progress-fill');
const floatProgressText = document.getElementById('float-progress-text');
const floatNotification = document.getElementById('float-notification');
if (floatProgressFill && floatProgressText && !floatNotification.classList.contains('hidden')) {
const percent = data.percent || 0;
floatProgressFill.style.width = percent + '%';
floatProgressText.textContent = percent.toFixed(1) + '%';
}
// 调用原始回调
if (typeof originalCallback === 'function') {
originalCallback(data);
}
});
}
// 漂浮通知控制函数
function showFloatNotification(title, text) {
const notification = document.getElementById('float-notification');
const titleEl = document.getElementById('float-notification-title');
const textEl = document.getElementById('float-notification-text');
if (notification && titleEl && textEl) {
titleEl.textContent = title;
textEl.textContent = text;
// 重置进度
const progressFill = document.getElementById('float-progress-fill');
const progressText = document.getElementById('float-progress-text');
if (progressFill) progressFill.style.width = '0%';
if (progressText) progressText.textContent = '0%';
notification.classList.remove('hidden');
}
}
function hideFloatNotification() {
const notification = document.getElementById('float-notification');
if (notification) {
notification.classList.add('hidden');
}
}
// 监听自动下载开始事件(通过游戏日志检测)
if (window.gpcl && window.gpcl.onGameLog) {
window.gpcl.onGameLog((text) => {
if (text.includes('Java') && text.includes('未安装') && text.includes('自动开始下载')) {
const match = text.match(/Java\s+(\d+)/);
if (match) {
showFloatNotification(
`正在下载 Java ${match[1]}`,
`检测到您没有 Java ${match[1]},正在自动下载`
);
}
}
});
}
// 绑定漂浮通知关闭按钮
const floatCloseBtn = document.getElementById('float-notification-close');
if (floatCloseBtn) {
floatCloseBtn.addEventListener('click', hideFloatNotification);
}
// 监听Java下载完成
if (window.gpcl && window.gpcl.onJavaDownloadCompleted) {
window.gpcl.onJavaDownloadCompleted((data) => {
const { javaVersion } = data;
const downloadStatusEl = document.getElementById('download-status');
if (downloadStatusEl) {
downloadStatusEl.textContent = '✅ 下载完成,正在解压安装...';
}
});
}
// 监听Java下载失败
if (window.gpcl && window.gpcl.onJavaDownloadFailed) {
window.gpcl.onJavaDownloadFailed((data) => {
const { error } = data;
const downloadStatusEl = document.getElementById('download-status');
if (downloadStatusEl) {
downloadStatusEl.textContent = `❌ 下载失败: ${error}`;
}
showToast('下载失败', error, 'error', 'java-download-failed');
});
}
async function loadSettingFromStorage() {
try {
const settings = await loadSettings();
return settings.download?.maxConcurrent || DEFAULT_MAX_THREADS;
} catch (e) {
return DEFAULT_MAX_THREADS;
}
}
async function saveSettingToStorage(maxConcurrent) {
try {
const settings = await loadSettings();
settings.download.maxConcurrent = maxConcurrent;
await gpcl.saveSettings(settings);
return true;
} catch (e) { return false; }
}
const settingsNumInput = document.getElementById('num-threads');
const settingsBtnMinus = document.getElementById('btn-minus');
const settingsBtnPlus = document.getElementById('btn-plus');
const settingsBtnSave = document.getElementById('btn-save');
if (settingsNumInput) {
(async () => {
settingsNumInput.value = await loadSettingFromStorage();
})();
function clampSettingsInput() {
let v = parseInt(settingsNumInput.value, 10);
if (isNaN(v) || v < 1) v = 1;
if (v > MAX_THREADS_LIMIT) v = MAX_THREADS_LIMIT;
settingsNumInput.value = v;
}
settingsNumInput.addEventListener('change', clampSettingsInput);
settingsNumInput.addEventListener('blur', clampSettingsInput);
if (settingsBtnMinus) {
settingsBtnMinus.addEventListener('click', () => {
let v = parseInt(settingsNumInput.value, 10) || 1;
if (v > 1) { settingsNumInput.value = v - 1; clampSettingsInput(); }
});
}
if (settingsBtnPlus) {
settingsBtnPlus.addEventListener('click', () => {
let v = parseInt(settingsNumInput.value, 10) || 1;
if (v < MAX_THREADS_LIMIT) { settingsNumInput.value = v + 1; clampSettingsInput(); }
});
}
if (settingsBtnSave) {
settingsBtnSave.addEventListener('click', async () => {
clampSettingsInput();
await saveSettingToStorage(parseInt(settingsNumInput.value, 10));
const orig = settingsBtnSave.textContent;
settingsBtnSave.textContent = '已保存!';
setTimeout(() => { settingsBtnSave.textContent = orig; }, 1500);
});
}
async function reloadAllSettingsToUI() {
const settings = await loadSettings();
const settingsMemory = document.getElementById('settings-memory');
if (settingsMemory) {
settingsMemory.value = settings.game?.memory || '4';
}
const settingsWindowMode = document.getElementById('settings-window-mode');
if (settingsWindowMode) {
settingsWindowMode.value = settings.game?.windowMode || 'windowed';
}
const settingsTheme = document.getElementById('settings-theme');
if (settingsTheme) {
settingsTheme.value = settings.appearance?.theme || 'dark';
applyTheme(settings.appearance?.theme || 'dark');
}
const settingsScale = document.getElementById('settings-scale');
if (settingsScale) {
settingsScale.value = settings.appearance?.scale || '100';
applyScale(settings.appearance?.scale || '100');
}
const settingsNumInput = document.getElementById('num-threads');
if (settingsNumInput) {
settingsNumInput.value = settings.download?.maxConcurrent || 64;
}
const javaMirrorSelect = document.getElementById('java-mirror-select');
const customMirrorContainer = document.getElementById('custom-java-mirror-container');
const customMirrorUrl = document.getElementById('custom-java-mirror-url');
if (javaMirrorSelect) {
javaMirrorSelect.value = settings.download?.javaMirror || 'tsinghua';
if (customMirrorContainer) {
customMirrorContainer.classList.toggle('hidden', javaMirrorSelect.value !== 'custom');
}
if (customMirrorUrl) {
customMirrorUrl.value = settings.download?.customJavaMirror || '';
}
}
const autoCheckUpdate = document.getElementById('auto-check-update');
if (autoCheckUpdate) {
autoCheckUpdate.checked = settings.advanced?.autoCheckUpdate !== false;
}
const preventMultipleLaunch = document.getElementById('prevent-multiple-launch');
if (preventMultipleLaunch) {
preventMultipleLaunch.checked = settings.advanced?.preventMultipleLaunch !== false;
}
const autoClearLogs = document.getElementById('auto-clear-logs');
const logRetentionContainer = document.getElementById('log-retention-container');
const logRetentionValue = document.getElementById('log-retention-value');
const logRetentionUnit = document.getElementById('log-retention-unit');
if (autoClearLogs) {
autoClearLogs.checked = settings.advanced?.autoClearLogs !== false;
}
if (logRetentionContainer) {
logRetentionContainer.classList.toggle('hidden', settings.advanced?.autoClearLogs === false);
}
if (logRetentionValue) {
logRetentionValue.value = settings.advanced?.logRetentionValue || 7;
}
if (logRetentionUnit) {
logRetentionUnit.value = settings.advanced?.logRetentionUnit || 'day';
}
const playStartupAnimationEl = document.getElementById('play-startup-animation');
if (playStartupAnimationEl) {
playStartupAnimationEl.checked = settings.advanced?.playStartupAnimation === true;
}
const developerModeEl = document.getElementById('developer-mode');
if (developerModeEl) {
developerModeEl.checked = settings.advanced?.developerMode === true;
}
}
}
function initChart() {
const ctx = document.getElementById('speedChart');
if (!ctx) return;
const chartCtx = ctx.getContext('2d');
const gradient = chartCtx.createLinearGradient(0, 0, 0, 180);
gradient.addColorStop(0, 'rgba(79, 195, 247, 0.3)');
gradient.addColorStop(1, 'rgba(79, 195, 247, 0)');
speedChart = new Chart(chartCtx, {
type: 'line',
data: {
labels: Array(40).fill(''),
datasets: [{
label: '下载速度',
data: Array(40).fill(0),
borderColor: '#4fc3f7',
backgroundColor: gradient,
borderWidth: 2,
fill: true,
tension: 0.4,
pointRadius: 0,
pointHoverRadius: 4,
pointHoverBackgroundColor: '#4fc3f7'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#fff',
bodyColor: '#4fc3f7',
borderColor: 'rgba(79, 195, 247, 0.3)',
borderWidth: 1,
padding: 10,
callbacks: {
label: function(context) {
return `速度: ${context.parsed.y.toFixed(2)} Mbps`;
}
}
}
},
scales: {
x: { display: false },
y: {
beginAtZero: true,
suggestedMax: 10,
grid: { color: 'rgba(255, 255, 255, 0.05)' },
ticks: { display: false }
}
}
}
});
}
function updateChart(speed) {
if (!speedChart) return;
speedData.push(speed);
if (speedData.length > 40) speedData.shift();
speedChart.data.datasets[0].data = speedData;
const maxSpeed = Math.max(...speedData, 1);
speedChart.options.scales.y.suggestedMax = maxSpeed * 1.5;
speedChart.update('none');
}
function resetChart() {
speedData = Array(40).fill(0);
peakSpeed = 0;
if (speedChart) {
speedChart.data.datasets[0].data = speedData;
speedChart.update();
}
const currentSpeedEl = document.getElementById('current-speed');
const peakSpeedEl = document.getElementById('peak-speed');
const progressFillEl = document.getElementById('progress-fill');
const progressTextEl = document.getElementById('progress-text');
if (currentSpeedEl) currentSpeedEl.textContent = '0.00 Mbps';
if (peakSpeedEl) peakSpeedEl.textContent = '0.00 Mbps';
if (progressFillEl) progressFillEl.style.width = '0%';
if (progressTextEl) progressTextEl.textContent = '0%';
}
// 注意:下载进度监听在 startDownload 函数中单独处理,避免全局监听器冲突
// addLog removed - using toast notifications instead
function setStatus(text, type = 'info') {
statusDiv.textContent = text;
statusDiv.style.color =
type === 'error' ? '#ef5350' :
type === 'success' ? '#66bb6a' :
'#e0e0e0';
}
async function loadVersions(silent = false) {
try {
let versions = [];
// 尝试调用主进程API
if (window.gpcl && typeof window.gpcl.scanVersions === 'function') {
try {
versions = await window.gpcl.scanVersions(null, silent);
} catch (e) {
console.error('调用scanVersions失败:', e);
}
}
// 更新启动按钮状态(无论 versionSelect 是否存在都要更新)
if (!versions || versions.length === 0) {
if (!silent) setStatus('没有本地版本,请先下载');
// 切换按钮为前往下载状态
updateLaunchButtonState(false);
} else {
if (!silent) setStatus(`发现 ${versions.length} 个已安装版本`);
// 切换按钮为开始游戏状态
updateLaunchButtonState(true);
}
// 检查 versionSelect 是否存在
if (!versionSelect) {
if (!silent) console.warn('versionSelect 元素不存在,跳过版本列表渲染');
return;
}
versionSelect.innerHTML = '<option value="">请选择版本</option>';
if (!versions || versions.length === 0) {
const opt = document.createElement('option');
opt.disabled = true;
opt.textContent = '未发现已安装的版本';
versionSelect.appendChild(opt);
return;
}
versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
for (const v of versions) {
let displayName = `Minecraft ${v}`;
if (window.gpcl && typeof window.gpcl.getVersionDisplayName === 'function') {
try {
displayName = await window.gpcl.getVersionDisplayName(v);
} catch (e) {
console.warn(`获取版本显示名称失败: ${v}`, e);
}
}
const opt = document.createElement('option');
opt.value = v;
opt.textContent = displayName;
versionSelect.appendChild(opt);
}
} catch (err) {
if (!silent) {
setStatus('扫描本地版本失败', 'error');
showToast('扫描失败', err.message || String(err), 'error');
}
// 出错时也更新按钮为无游戏状态
updateLaunchButtonState(false);
}
}
// 更新启动按钮状态
function updateLaunchButtonState(hasGame) {
const launchBtn = document.getElementById('launch-btn');
const launchText = launchBtn?.querySelector('.launch-text');
if (launchBtn && launchText) {
if (hasGame) {
launchText.textContent = '▶ 开始游戏';
launchBtn.dataset.mode = 'launch';
// 移除红色样式
launchBtn.classList.remove('no-game');
} else {
launchText.textContent = '⏬ 前往下载';
launchBtn.dataset.mode = 'download';
// 添加红色样式
launchBtn.classList.add('no-game');
}
}
}
// 版本时间记录相关功能
const VERSION_HISTORY_FILE = 'gpcl_version_history.json';
const VERSION_HISTORY_PATH = 'users';
// 获取版本历史记录
async function getVersionHistory() {
try {
if (window.gpcl && typeof window.gpcl.readJsonFile === 'function') {
const history = await window.gpcl.readJsonFile(VERSION_HISTORY_PATH, VERSION_HISTORY_FILE);
return history || {};
}
} catch (e) {
console.error('从文件读取版本历史失败:', e);
}
return {};
}
// 保存版本历史记录
async function saveVersionHistory(history) {
if (window.gpcl && typeof window.gpcl.writeJsonFile === 'function') {
try {
await window.gpcl.writeJsonFile(VERSION_HISTORY_PATH, VERSION_HISTORY_FILE, history);
} catch (e) {
console.error('写入版本历史到文件失败:', e);
}
}
}
// 记录版本启动时间
async function recordVersionLaunch(versionId) {
try {
const history = await getVersionHistory();
if (!history[versionId]) {
history[versionId] = {};
}
history[versionId].lastLaunch = Date.now();
await saveVersionHistory(history);
} catch (e) {
console.error('记录版本启动时间失败:', e);
}
}
// 记录版本下载成功时间
async function recordVersionDownload(versionId) {
try {
const history = await getVersionHistory();
if (!history[versionId]) {
history[versionId] = {};
}
history[versionId].downloadTime = Date.now();
history[versionId].lastLaunch = Date.now(); // 下载成功也作为最后启动时间
await saveVersionHistory(history);
} catch (e) {
console.error('记录版本下载时间失败:', e);
}
}
// 渲染版本选择列表
async function renderVersionSelectList() {
const container = document.getElementById('launch-version-list');
const countEl = document.getElementById('version-count');
if (!container) return;
try {
let versions = [];
// 尝试调用主进程API
if (window.gpcl && typeof window.gpcl.scanVersions === 'function') {
try {
versions = await window.gpcl.scanVersions(null, true);
} catch (e) {
console.error('调用scanVersions失败:', e);
}
}
const history = await getVersionHistory();
if (!versions || versions.length === 0) {
container.innerHTML = '<div class="version-list-empty">暂无已安装的版本</div>';
countEl.textContent = '0 个版本';
return;
}
// 按最后启动时间排序,最近启动的排在前面
versions.sort((a, b) => {
const timeA = history[a]?.lastLaunch || history[a]?.downloadTime || 0;
const timeB = history[b]?.lastLaunch || history[b]?.downloadTime || 0;
return timeB - timeA;
});
container.innerHTML = '';
versions.forEach((versionId, index) => {
const item = document.createElement('div');
item.className = 'version-item';
item.dataset.versionId = versionId;
// 获取版本类型图标
const icon = index === 0 && (history[versionId]?.lastLaunch || history[versionId]?.downloadTime)
? '⭐' : '📦';
// 格式化时间
const lastTime = history[versionId]?.lastLaunch || history[versionId]?.downloadTime;
let timeText = '';
if (lastTime) {
const date = new Date(lastTime);
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff < 60000) {
timeText = '刚刚';
} else if (diff < 3600000) {
timeText = `${Math.floor(diff / 60000)} 分钟前`;
} else if (diff < 86400000) {
timeText = `${Math.floor(diff / 3600000)} 小时前`;
} else {
timeText = `${date.getMonth() + 1}/${date.getDate()}`;
}
}
item.innerHTML = `
<span class="version-item-icon">${icon}</span>
<span class="version-item-id">${versionId}</span>
<span class="version-item-time">${timeText}</span>
`;
item.addEventListener('click', () => {
selectVersion(versionId);
});
container.appendChild(item);
});
countEl.textContent = `${versions.length} 个版本`;
// 如果没有选中的版本,自动选中第一个
if (!selectedVersionId && versions.length > 0) {
selectVersion(versions[0]);
}
} catch (err) {
container.innerHTML = '<div class="version-list-empty">加载版本列表失败</div>';
countEl.textContent = '加载失败';
}
}
// 选择版本
function selectVersion(versionId) {
selectedVersionId = versionId;
// 更新UI
const items = document.querySelectorAll('.version-item');
items.forEach(item => {
item.classList.toggle('selected', item.dataset.versionId === versionId);
});
const infoEl = document.getElementById('selected-version-info');
const nameEl = document.getElementById('selected-version-name');
if (infoEl && nameEl) {
nameEl.textContent = `Minecraft ${versionId}`;
infoEl.classList.remove('hidden');
}
// 更新启动按钮下方的小字显示
const displayEl = document.getElementById('selected-version-display');
const textEl = document.getElementById('selected-version-text');
if (displayEl && textEl) {
textEl.textContent = versionId;
displayEl.classList.remove('hidden');
}
// 更新启动按钮状态
updateLaunchButtonState(true);
}
// 检查版本是否已安装
async function isVersionInstalled(versionId) {
try {
const versions = await window.gpcl.scanVersions(null, true);
return versions.includes(versionId);
} catch {
return false;
}
}
let allVersions = [];
let selectedVersionId = null;
// 模组加载器相关状态
let modLoaderState = {
forge: { available: false, versions: [], selected: null, expanded: false },
fabric: { available: false, versions: [], selected: null, expanded: false },
optifine: { available: false, versions: [], selected: null, expanded: false }
};
// 模组加载器兼容性规则
const MOD_LOADER_COMPATIBILITY = {
// Forge 和 Fabric 互斥
forge: ['fabric'],
fabric: ['forge'],
// OptiFine 与 Forge/Fabric 都互斥
optifine: ['forge', 'fabric']
};
// 检测 Forge 可用性
async function checkForgeAvailability(mcVersion) {
try {
// Forge 支持 1.1 及以上版本
const parts = mcVersion.split('.');
const major = parseInt(parts[0], 10) || 1;
const minor = parseInt(parts[1], 10) || 0;
// 1.1 以下不支持
if (major === 1 && minor < 1) {
return { available: false, versions: [] };
}
// 从主进程获取 Forge 版本列表
const result = await window.gpcl.getForgeVersions(mcVersion);
if (result.success) {
return { available: result.versions.length > 0, versions: result.versions };
}
return { available: false, versions: [] };
} catch (e) {
console.error('检测 Forge 可用性失败:', e);
return { available: false, versions: [] };
}
}
// 检测所有模组加载器的可用性
async function checkAllModLoaders(mcVersion) {
// 重置状态
modLoaderState = {
forge: { available: false, versions: [], selected: null, expanded: false }
};
// 检测 Forge
const forgeResult = await checkForgeAvailability(mcVersion);
modLoaderState.forge = { ...modLoaderState.forge, ...forgeResult };
// 更新 UI
updateModLoaderUI();
}
// 更新模组加载器 UI
function updateModLoaderUI() {
const loaders = ['forge'];
loaders.forEach(loader => {
const state = modLoaderState[loader];
const card = document.getElementById(`${loader}-option`);
const badge = document.getElementById(`${loader}-badge`);
const content = document.getElementById(`${loader}-content`);
const loading = document.getElementById(`${loader}-loading`);
const versionsContainer = document.getElementById(`${loader}-versions`);
const error = document.getElementById(`${loader}-error`);
if (!card || !badge) return;
// 更新徽章状态
if (state.available) {
badge.textContent = '可用';
badge.className = 'detail-option-badge available';
card.classList.remove('not-available');
} else {
badge.textContent = '不可用';
badge.className = 'detail-option-badge unavailable';
card.classList.add('not-available');
}
// 更新内容区域(仅在已展开时显示)
if (state.expanded && state.available) {
content.classList.remove('hidden');
loading.classList.add('hidden');
error.classList.add('hidden');
versionsContainer.classList.remove('hidden');
// 渲染版本列表
renderModLoaderVersionList(loader, state.versions);
} else if (state.expanded && !state.available) {
content.classList.remove('hidden');
loading.classList.add('hidden');
versionsContainer.classList.add('hidden');
error.classList.remove('hidden');
} else {
content.classList.add('hidden');
}
});
}
// 渲染模组加载器版本列表
function renderModLoaderVersionList(loader, versions) {
const container = document.getElementById(`${loader}-versions`);
if (!container) return;
container.innerHTML = '';
versions.forEach((version, index) => {
const item = document.createElement('div');
item.className = 'detail-option-version-item';
item.dataset.versionId = version.id;
item.dataset.version = version.version;
// 如果是已选中的版本,添加选中样式
if (modLoaderState[loader].selected === version.version) {
item.classList.add('selected');
}
item.innerHTML = `
<span class="detail-option-version-name">${version.name}</span>
<span class="detail-option-version-check"></span>
`;
item.addEventListener('click', () => selectModLoaderVersion(loader, version.version, version.id));
container.appendChild(item);
});
}
// 选择模组加载器版本
function selectModLoaderVersion(loader, version, versionId) {
const state = modLoaderState[loader];
// 如果点击的是已选中的版本,则取消选择
if (state.selected === version) {
state.selected = null;
} else {
state.selected = version;
}
// 更新版本列表 UI
const container = document.getElementById(`${loader}-versions`);
if (container) {
const items = container.querySelectorAll('.detail-option-version-item');
items.forEach(item => {
item.classList.toggle('selected', item.dataset.version === state.selected);
});
}
}
// 更新互斥 UI
function updateIncompatibilityUI() {
// 目前只有 Forge无需处理互斥
}
// 获取加载器显示名称
function getLoaderDisplayName(loader) {
const names = {
forge: 'Forge'
};
return names[loader] || loader;
}
// 切换模组加载器展开/折叠
function toggleModLoader(loader) {
const state = modLoaderState[loader];
const card = document.getElementById(`${loader}-option`);
const content = document.getElementById(`${loader}-content`);
const arrow = card?.querySelector('.detail-option-arrow');
if (!content) return;
// 如果不可用,不允许展开
if (!state.available) return;
state.expanded = !state.expanded;
if (state.expanded) {
if (arrow) arrow.classList.add('expanded');
} else {
if (arrow) arrow.classList.remove('expanded');
}
// 重新更新整个 UI
updateModLoaderUI();
}
// 绑定模组加载器事件
function bindModLoaderEvents() {
const loaders = ['forge'];
loaders.forEach(loader => {
const card = document.getElementById(`${loader}-option`);
if (!card) return;
const header = card.querySelector('.detail-option-header');
if (header) {
header.addEventListener('click', () => toggleModLoader(loader));
}
});
}
// 重置模组加载器状态
function resetModLoaderState() {
modLoaderState = {
forge: { available: false, versions: [], selected: null, expanded: false }
};
// 重置 UI
const loaders = ['forge'];
loaders.forEach(loader => {
const card = document.getElementById(`${loader}-option`);
const badge = document.getElementById(`${loader}-badge`);
const content = document.getElementById(`${loader}-content`);
const loading = document.getElementById(`${loader}-loading`);
const versions = document.getElementById(`${loader}-versions`);
const error = document.getElementById(`${loader}-error`);
const arrow = card?.querySelector('.detail-option-arrow');
if (card) {
card.classList.remove('not-available', 'incompatible');
}
if (badge) {
badge.textContent = '检测中...';
badge.className = 'detail-option-badge';
}
if (content) content.classList.add('hidden');
if (loading) loading.classList.remove('hidden');
if (versions) versions.classList.add('hidden');
if (error) error.classList.add('hidden');
if (arrow) arrow.classList.remove('expanded');
});
}
function getCategoryLabel(type) {
const map = { release: '正式版', snapshot: '快照版', old_beta: '老版本 Beta', old_alpha: '老版本 Alpha' };
return map[type] || type;
}
function getCategoryIcon(type) {
const map = { release: '🟢', snapshot: '🟡', old_beta: '🟠', old_alpha: '🔴' };
return map[type] || '⚪';
}
function formatDate(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}
function renderVersionList(versionMap) {
if (!versionListContainer) return;
versionListContainer.innerHTML = '';
const categoryOrder = ['release', 'snapshot', 'old_beta', 'old_alpha'];
let totalCount = 0;
categoryOrder.forEach(type => {
const items = versionMap[type];
if (!items || items.length === 0) return;
totalCount += items.length;
const categoryEl = document.createElement('div');
categoryEl.className = 'version-category';
categoryEl.dataset.category = type;
const isRelease = type === 'release';
const header = document.createElement('div');
header.className = 'version-category-header';
header.innerHTML = `
<span class="version-category-arrow ${isRelease ? '' : 'collapsed'}"></span>
<span class="version-category-icon">${getCategoryIcon(type)}</span>
<span class="version-category-name">${getCategoryLabel(type)}</span>
<span class="version-category-count">${items.length} 个版本</span>
`;
const body = document.createElement('div');
body.className = `version-category-items ${isRelease ? '' : 'collapsed'}`;
items.forEach(v => {
const item = document.createElement('div');
item.className = 'version-item';
item.dataset.versionId = v.id;
const displayName = v.displayName || v.id;
item.innerHTML = `
<span class="version-item-id">${displayName}</span>
<span class="version-item-time">${formatDate(v.releaseTime)}</span>
`;
item.addEventListener('click', (e) => {
e.stopPropagation();
showVersionDetail(v);
});
body.appendChild(item);
});
header.addEventListener('click', () => {
const arrow = header.querySelector('.version-category-arrow');
const isCollapsed = body.classList.toggle('collapsed');
arrow.classList.toggle('collapsed', isCollapsed);
});
categoryEl.appendChild(header);
categoryEl.appendChild(body);
versionListContainer.appendChild(categoryEl);
});
if (versionCountLabel) {
versionCountLabel.textContent = `${totalCount} 个版本`;
}
}
function showVersionDetail(version) {
selectedVersionId = version.id;
if (versionListSection) versionListSection.classList.add('hidden');
if (versionDetailPage) versionDetailPage.classList.remove('hidden');
// 重置下载按钮状态,确保它是可用的
if (detailDownloadBtn) detailDownloadBtn.disabled = false;
const idEl = document.getElementById('detail-version-id');
const typeEl = document.getElementById('detail-version-type');
const typeLabelEl = document.getElementById('detail-type-label');
const timeEl = document.getElementById('detail-release-time');
if (idEl) idEl.textContent = `Minecraft ${version.id}`;
if (typeEl) typeEl.textContent = getCategoryLabel(version.type);
if (typeLabelEl) typeLabelEl.textContent = `${getCategoryIcon(version.type)} ${getCategoryLabel(version.type)}`;
if (timeEl) timeEl.textContent = formatDate(version.releaseTime) || '未知';
// 重置并检测模组加载器可用性
resetModLoaderState();
checkAllModLoaders(version.id);
}
function hideVersionDetail() {
selectedVersionId = null;
if (versionDetailPage) versionDetailPage.classList.add('hidden');
if (versionListSection) versionListSection.classList.remove('hidden');
}
async function loadRemoteVersions() {
const refreshBtn = document.getElementById('refresh-btn-page');
try {
if (refreshBtn) refreshBtn.classList.add('hidden');
if (versionListContainer) {
versionListContainer.innerHTML = '<div class="version-list-loading">正在加载版本列表...</div>';
}
let versions = [];
// 尝试调用主进程API
if (window.gpcl && typeof window.gpcl.getVersionManifest === 'function') {
try {
versions = await window.gpcl.getVersionManifest();
} catch (e) {
console.error('调用getVersionManifest失败:', e);
}
}
// 尝试从文件缓存获取
if (!versions || versions.length === 0) {
try {
if (window.gpcl && typeof window.gpcl.readJsonFile === 'function') {
const cached = await window.gpcl.readJsonFile('cache', 'remote_versions.json');
if (cached) {
versions = cached;
}
}
} catch (e) {
console.error('从文件缓存获取远程版本列表失败:', e);
}
}
// 如果还是没有数据,显示错误或示例数据
if (!versions || versions.length === 0) {
setStatus('无法获取远程版本列表', 'error');
if (versionListContainer) {
versionListContainer.innerHTML = '<div class="version-list-error">无法获取版本列表<br><button class="refresh-btn" style="margin-top:12px;" onclick="loadRemoteVersions()">重试</button></div>';
}
if (refreshBtn) refreshBtn.classList.remove('hidden');
return;
}
// Group versions by type
const versionMap = {};
versions.forEach(v => {
if (!versionMap[v.type]) versionMap[v.type] = [];
versionMap[v.type].push(v);
});
// 保存到文件缓存
try {
if (window.gpcl && typeof window.gpcl.writeJsonFile === 'function') {
await window.gpcl.writeJsonFile('cache', 'remote_versions.json', versions);
}
} catch (e) {
console.error('保存远程版本列表到缓存失败:', e);
}
allVersions = versions;
renderVersionList(versionMap);
setStatus('远程版本列表已加载');
} catch (err) {
setStatus('无法获取远程版本列表,请检查网络', 'error');
showToast('版本列表加载失败', err.message || '请检查网络连接', 'error', 'remote-versions-error');
if (versionListContainer) {
versionListContainer.innerHTML = '<div class="version-list-error">加载失败,请检查网络连接<br><button class="refresh-btn" style="margin-top:12px;" onclick="loadRemoteVersions()">重试</button></div>';
}
if (refreshBtn) refreshBtn.classList.remove('hidden');
}
}
async function launchGame() {
// 检查按钮模式
const launchBtn = document.getElementById('launch-btn');
const mode = launchBtn?.dataset.mode || 'launch';
if (mode === 'download') {
// 下载模式:跳转到下载页面
showPage('download');
setStatus('请选择要下载的游戏版本');
return;
}
// 启动模式:正常启动游戏
setStatus('[动画] launchGame 函数被调用了');
const username = usernameInput.value.trim() || 'GPCL_Player';
// 使用右侧面板选择的版本
const versionId = selectedVersionId;
if (!versionId) {
setStatus('请先选择已安装的版本', 'error');
return;
}
// 记录版本启动时间(不管是否成功都会记录)
await recordVersionLaunch(versionId);
if (window.gpcl && window.gpcl.savePlayerName) {
await window.gpcl.savePlayerName(username);
}
launchBtn.disabled = true;
// 显示启动动画层
showLaunchAnimation(versionId);
try {
// 尝试启动游戏主进程会自动处理Java下载
const gameDir = await initGameDir();
setStatus('[动画] 开始启动游戏');
// 获取窗口模式设置
const settings = await loadSettings();
let windowMode = settings.game?.windowMode || 'windowed';
let welcomeAnimPlaying = false;
if (settings.advanced?.playStartupAnimation && window.gpcl && window.gpcl.playStartupAnimation) {
setStatus('[启动动画] 正在播放欢迎动画...');
welcomeAnimPlaying = true;
windowMode = 'fullscreen';
settings.advanced.playStartupAnimation = false;
await gpcl.saveSettings(settings);
const playStartupEl = document.getElementById('play-startup-animation');
if (playStartupEl) playStartupEl.checked = false;
await window.gpcl.playStartupAnimation();
setStatus('[启动动画] 动画播放完毕,正在启动游戏...');
}
// 确保游戏窗口创建事件监听器已设置
setupGameWindowListener();
const result = await window.gpcl.launch({
versionId,
username,
gameDir,
windowMode
});
if (result.success) {
setStatus('[动画] 游戏启动成功,窗口已创建');
if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) {
window.gpcl.dismissStartupAnimation();
}
// 显示成功动画后隐藏
showLaunchSuccess();
setTimeout(() => {
hideLaunchAnimation();
}, 800);
setStatus(`游戏已启动PID: ${result.pid}`, 'success');
showToast('游戏已启动', `Minecraft ${versionId} (${username})`, 'success', 'game-started');
} else if (result.cancelled) {
// 用户取消了启动
if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) {
window.gpcl.dismissStartupAnimation();
}
hideLaunchAnimation();
setStatus('启动已取消', 'info');
showToast('启动已取消', '用户取消了游戏启动', 'info', 'launch-cancelled');
} else {
if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) {
window.gpcl.dismissStartupAnimation();
}
hideLaunchAnimation();
setStatus(result.error, 'error');
showToast('启动失败', result.error, 'error', 'game-launch-failed');
}
} catch (err) {
if (welcomeAnimPlaying && window.gpcl && window.gpcl.dismissStartupAnimation) {
window.gpcl.dismissStartupAnimation();
}
hideLaunchAnimation();
setStatus('启动出错', 'error');
showToast('启动出错', err.message, 'error', 'launch-error');
} finally {
launchBtn.disabled = false;
}
}
// 显示启动动画
async function showLaunchAnimation(versionId) {
try {
const overlay = document.getElementById('launch-animation-overlay');
const statusText = document.getElementById('launch-status-text');
const versionInfo = document.getElementById('launch-version-info');
const progressFill = document.getElementById('launch-progress-fill');
if (overlay) {
overlay.classList.remove('hidden', 'fade-out', 'launch-success');
overlay.style.display = 'flex';
void overlay.offsetHeight;
overlay.classList.add('show');
setStatus('[动画] 启动动画层已显示');
} else {
setStatus('[动画] 未找到启动动画层元素', 'error');
return;
}
if (statusText) {
statusText.textContent = '正在启动游戏...';
}
// 获取启动显示名称(不包含 OptiFine
let displayName = versionId;
if (window.gpcl && typeof window.gpcl.getLaunchDisplayName === 'function') {
try {
displayName = await window.gpcl.getLaunchDisplayName(versionId);
} catch (e) {
console.warn(`获取启动显示名称失败: ${versionId}`, e);
}
}
if (versionInfo) {
versionInfo.textContent = `将启动: ${displayName}`;
}
if (progressFill) {
progressFill.style.width = '0%';
}
simulateLaunchProgress();
showRandomTip();
} catch (err) {
setStatus('[动画] 显示启动动画失败: ' + err.message, 'error');
}
}
// 小知识列表
const launchTips = [
'Minecraft 最初由 Markus "Notch" Persson 于 2009 年独立开发,最初版本仅用 6 天完成。',
'Minecraft 的苦力怕Creeper是 Notch 在尝试制作猪模型时,因搞错长宽比而意外诞生的。',
'末影人Enderman会随机搬运一些方块这是参考了现实中的都市传说 Slender Man。',
'Minecraft 中最大的自然结构是下界要塞Nether Fortress可绵延数百个区块。',
'Minecraft 中钻石矿石只在 Y 坐标 16 以下生成,最集中的区域是 Y=5 到 Y=12 之间。',
'Minecraft 的附魔台周围的图书馆架数量会影响附魔等级,最高可达到 30 级。',
'红石Redstone是 Minecraft 中的"电力"系统,可以构建从简单门到计算机的各种电路。',
'Minecraft 中潮涌核心Conduit可以为水下玩家提供无限呼吸和夜视效果。',
'Minecraft 的下界合金Netherite装备需要先制作品铁装备再锻造升级。',
'Minecraft 中的嗅探兽Sniffer是 2022 年 Minecraft Live 由玩家投票选出的生物。',
'Minecraft 中沼泽小屋Swamp Hut必定会生成女巫是获取红石粉的稳定来源。',
'Minecraft 中你可以用线制作羊毛,但不能用羊毛反合成线。',
'Minecraft 的创造模式最初是为开发调试而加入的,后来才成为正式游戏模式。',
'Minecraft 中装备了冰霜行者Frost Walker附魔的靴子可以在水上行走。',
'Minecraft 中每个区块Chunk是 16×16 方块大小,游戏世界是通过区块流式加载的。',
'Minecraft 中鞘翅Elytra可以在末地船中找到配合烟花火箭可以实现飞行。',
'Minecraft 中下界对应主世界的比例为 1:8在下界走 1 格相当于主世界 8 格。',
'Minecraft 中掠夺者前哨站Pillager Outpost会生成灾厄旗帜周围的灾厄村民不会攻击。',
'Minecraft 中蜜蜂Bee在采蜜后会帮助作物加速生长。',
'Minecraft 中史莱姆Slime只在特定区块生成可以使用种子计算器定位史莱姆区块。',
'Minecraft 的唱片机播放的是 C418 创作的音乐13 和 cat 两张唱片可在要塞的箱子中找到。',
'Minecraft 中深海监守者Warden是盲人依靠声音和振动感知玩家。',
'Minecraft 的紫颂果Chorus Fruit食用后会随机传送但会失去一些饥饿值。',
'Minecraft 中袭击Raid是不祥之兆效果进入村庄时触发的多波战斗事件。',
'Minecraft 中下界传送门Nether Portal的大小最小为 4×5最大为 23×23。',
'Minecraft 中海龟壳帽Turtle Shell可以让玩家在水下呼吸更长时间。',
'Minecraft 中甜浆果Sweet Berries可以在针叶林生物群系中找到也可种植。',
'Minecraft 中你已经可以用铜Copper制作避雷针、望远镜和装饰方块。',
'Minecraft 中营火Campfire可以烹饪食物而且不会像熔炉那样消耗燃料。',
'Minecraft 中幽匿块Sculk会在深暗之域生成挖掉之前最好先准备好。',
'GPCL 是云云一个人开发的启动器,并没有什么所谓的开发团队。',
];
// 显示随机小知识
function showRandomTip() {
const tipText = document.getElementById('launch-tip-text');
if (!tipText) return;
const randomIndex = Math.floor(Math.random() * launchTips.length);
tipText.textContent = launchTips[randomIndex];
}
// 模拟启动进度
function simulateLaunchProgress() {
const progressFill = document.getElementById('launch-progress-fill');
const statusText = document.getElementById('launch-status-text');
let progress = 0;
const statusMessages = [
'正在检查游戏文件...',
'正在准备Java运行时...',
'正在初始化游戏环境...',
'正在启动游戏窗口...'
];
const interval = setInterval(() => {
// 随机增加进度
progress += Math.random() * 15 + 5;
if (progress >= 90) {
progress = 90;
}
if (progressFill) {
progressFill.style.width = `${progress}%`;
}
// 更新状态文字
const messageIndex = Math.floor(progress / 25);
if (statusText && messageIndex < statusMessages.length) {
statusText.textContent = statusMessages[messageIndex];
}
}, 200);
// 保存interval ID以便清除
window.launchProgressInterval = interval;
}
// 游戏窗口创建监听器设置
let gameWindowResolve = null;
let gameWindowTimeout = null;
function setupGameWindowListener() {
// 移除旧的监听器避免重复
if (window.gpcl && window.gpcl.removeAllListeners) {
window.gpcl.removeAllListeners();
}
// 重新注册关闭确认监听器
if (window.gpcl && window.gpcl.onConfirmCloseWhileDownloading) {
window.gpcl.onConfirmCloseWhileDownloading(async () => {
const result = await showDialog({
type: 'confirm',
title: '确认关闭',
message: '当前正在下载中,关闭会停止下载并清理已下载的文件。确定要关闭吗?如有不完整的"libraries"需自行清理。'
});
if (result) {
if (window.gpcl && window.gpcl.confirmCloseDownload) {
await window.gpcl.confirmCloseDownload();
}
} else {
if (window.gpcl && window.gpcl.cancelClose) {
await window.gpcl.cancelClose();
}
}
});
}
// 注册游戏窗口创建监听器
if (window.gpcl && window.gpcl.onGameWindowCreated) {
window.gpcl.onGameWindowCreated((data) => {
setStatus('[动画] 收到游戏窗口创建事件');
if (gameWindowResolve) {
clearTimeout(gameWindowTimeout);
gameWindowResolve();
gameWindowResolve = null;
gameWindowTimeout = null;
}
});
setStatus('[动画] 游戏窗口监听器已设置');
}
}
// 等待游戏窗口创建成功
function waitForGameWindow() {
return new Promise((resolve) => {
gameWindowResolve = resolve;
gameWindowTimeout = setTimeout(() => {
setStatus('[动画] 窗口检测超时');
gameWindowResolve = null;
gameWindowTimeout = null;
resolve();
}, 10000); // 10秒超时
});
}
// 显示成功动画
function showLaunchSuccess() {
try {
const overlay = document.getElementById('launch-animation-overlay');
const pickaxe = document.getElementById('pickaxe');
const block = document.getElementById('block');
if (overlay) {
overlay.classList.add('launch-success');
setStatus('[动画] 显示成功动画');
}
if (block) {
block.classList.add('breaking');
}
// 创建粒子效果
createParticles();
} catch (err) {
setStatus('[动画] 显示成功动画失败: ' + err.message, 'error');
}
}
// 创建粒子效果
function createParticles() {
const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return;
// 清除现有粒子
particlesContainer.innerHTML = '';
// 创建多个粒子
for (let i = 0; i < 12; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
// 随机方向
const angle = (Math.PI * 2 * i) / 12;
const distance = 50 + Math.random() * 50;
particle.style.setProperty('--dx', `${Math.cos(angle) * distance}px`);
particle.style.setProperty('--dy', `${Math.sin(angle) * distance}px`);
// 随机大小和延迟
particle.style.width = `${4 + Math.random() * 8}px`;
particle.style.height = particle.style.width;
particle.style.animationDelay = `${Math.random() * 0.2}s`;
particlesContainer.appendChild(particle);
}
}
// 隐藏启动动画
function hideLaunchAnimation() {
try {
const overlay = document.getElementById('launch-animation-overlay');
if (overlay) {
overlay.classList.remove('show');
overlay.classList.add('fade-out');
setTimeout(() => {
overlay.style.display = 'none';
overlay.classList.remove('fade-out', 'launch-success');
overlay.classList.add('hidden');
setStatus('[动画] 启动动画层已隐藏');
}, 400);
}
// 清除进度interval
if (window.launchProgressInterval) {
clearInterval(window.launchProgressInterval);
window.launchProgressInterval = null;
}
} catch (err) {
setStatus('[动画] 隐藏启动动画失败: ' + err.message, 'error');
}
}
async function startDownload(versionId) {
if (!versionId) { setStatus('请先选择要下载的版本', 'error'); return; }
// 检查版本是否已安装
const installed = await isVersionInstalled(versionId);
// 检查是否选择了模组加载器
let selectedModLoader = null;
let selectedModLoaderVersion = null;
let modLoaderName = '';
for (const [loader, state] of Object.entries(modLoaderState)) {
if (state.selected) {
selectedModLoader = loader;
selectedModLoaderVersion = state.selected;
if (loader === 'forge') modLoaderName = 'Forge';
break;
}
}
if (detailDownloadBtn) detailDownloadBtn.disabled = true;
await loadChartJs();
initChart();
resetChart();
if (downloadPanel) downloadPanel.classList.remove('hidden');
// 从详情页回到列表页,显示下载监控
hideVersionDetail();
showPage('download');
// 滚动到下载页面顶部
const scrollContainer = document.querySelector('.main-content');
if (scrollContainer) {
scrollContainer.scrollTop = 0;
}
const downloadStatusEl = document.getElementById('download-status');
const filenameEl = document.getElementById('download-filename');
const cancelBtn = document.getElementById('cancel-download-btn');
// 重置按钮状态
if (cancelBtn) {
cancelBtn.textContent = '取消下载';
cancelBtn.classList.remove('complete');
cancelBtn.disabled = false;
}
let displayName = `Minecraft ${versionId}`;
if (selectedModLoader) {
displayName = `${modLoaderName} ${versionId}`;
}
if (downloadStatusEl) downloadStatusEl.textContent = `开始下载 ${displayName} ...`;
if (filenameEl) filenameEl.textContent = `正在下载: ${displayName}`;
window.gpcl.removeAllListeners();
// 重新注册关闭确认监听器不会被removeAllListeners移除
window.gpcl.onConfirmCloseWhileDownloading(async () => {
const result = await showDialog({
type: 'confirm',
title: '确认关闭',
message: '当前正在下载中,关闭会停止下载并清理已下载的文件。确定要关闭吗?如有不完整的"libraries"需自行清理。'
});
if (result) {
if (window.gpcl && window.gpcl.confirmCloseDownload) {
await window.gpcl.confirmCloseDownload();
}
} else {
if (window.gpcl && window.gpcl.cancelClose) {
await window.gpcl.cancelClose();
}
}
});
let lastTime = Date.now();
let lastBytes = 0;
let lastLabel = '';
window.gpcl.onDownloadProgress((data) => {
const currentTime = Date.now();
const timeDiff = (currentTime - lastTime) / 1000;
const currentSpeedEl = document.getElementById('current-speed');
const peakSpeedEl = document.getElementById('peak-speed');
const progressFillEl = document.getElementById('progress-fill');
const progressTextEl = document.getElementById('progress-text');
if (data.label && data.label !== lastLabel) {
lastLabel = data.label;
lastBytes = 0;
lastTime = currentTime;
}
if (timeDiff >= 0.5 && data.bytesDownloaded !== undefined) {
const bytesDiff = data.bytesDownloaded - lastBytes;
if (bytesDiff < 0) {
lastBytes = data.bytesDownloaded;
lastTime = currentTime;
return;
}
const speedBps = Math.max(0, (bytesDiff * 8) / timeDiff / 1000000);
if (currentSpeedEl) currentSpeedEl.textContent = speedBps.toFixed(2) + ' Mbps';
if (speedBps > peakSpeed) {
peakSpeed = speedBps;
if (peakSpeedEl) peakSpeedEl.textContent = peakSpeed.toFixed(2) + ' Mbps';
}
updateChart(speedBps);
lastTime = currentTime;
lastBytes = data.bytesDownloaded;
}
const percent = data.percent || 0;
if (progressFillEl) progressFillEl.style.width = percent + '%';
if (progressTextEl) progressTextEl.textContent = percent.toFixed(1) + '%';
if (data.label && filenameEl) {
filenameEl.textContent = data.label;
}
if (downloadStatusEl) downloadStatusEl.textContent = `正在下载 ${versionId}: ${percent.toFixed(1)}%`;
if (menuDownload) menuDownload.classList.add('active');
if (data.percent >= 100 && menuDownload) {
menuDownload.classList.remove('active');
}
});
try {
const maxConcurrent = getMaxConcurrentFromSettings();
let result;
if (selectedModLoader) {
// 下载带模组加载器的版本
result = await window.gpcl.downloadWithModLoader(
versionId,
selectedModLoader,
selectedModLoaderVersion,
maxConcurrent
);
} else {
// 下载原版
result = await window.gpcl.downloadVersion(versionId, maxConcurrent);
}
if (result.success) {
// 记录版本下载时间
await recordVersionDownload(result.finalVersionId || versionId);
const finalDisplayName = selectedModLoader
? `${modLoaderName} ${versionId}`
: `Minecraft ${versionId}`;
setStatus('下载完成', 'success');
if (downloadStatusEl) downloadStatusEl.textContent = `下载完成: ${finalDisplayName}`;
const progressFillEl = document.getElementById('progress-fill');
const progressTextEl = document.getElementById('progress-text');
if (progressFillEl) progressFillEl.style.width = '100%';
if (progressTextEl) progressTextEl.textContent = '100%';
showToast('下载完成', `${finalDisplayName} 已准备就绪`, 'success', 'download-complete');
await loadVersions();
// 刷新版本选择列表
renderVersionSelectList();
// 修改按钮状态为下载完成(绿色)
if (cancelBtn) {
cancelBtn.textContent = '下载完成';
cancelBtn.classList.add('complete');
cancelBtn.disabled = true;
}
} else {
setStatus(`下载失败: ${result.error}`, 'error');
if (downloadStatusEl) downloadStatusEl.textContent = `下载失败: ${result.error}`;
}
} catch (err) {
setStatus('下载出错', 'error');
if (downloadStatusEl) downloadStatusEl.textContent = `下载出错: ${err.message || err}`;
showToast('下载出错', err.message || String(err), 'error', 'download-error');
}
if (detailDownloadBtn) detailDownloadBtn.disabled = false;
}
// 详情页面事件
if (detailBackBtn) {
detailBackBtn.addEventListener('click', hideVersionDetail);
}
if (detailDownloadBtn) {
detailDownloadBtn.addEventListener('click', () => {
if (selectedVersionId) {
startDownload(selectedVersionId);
}
});
}
window.gpcl.onGameClosed((code) => {
setStatus('游戏已退出');
showToast('游戏已退出', `退出码: ${code}`, 'info', 'game-closed');
launchBtn.disabled = false;
});
window.gpcl.onGameError((message) => {
setStatus(`启动出错: ${message}`, 'error');
showToast('游戏错误', message, 'error', 'game-error');
launchBtn.disabled = false;
});
launchBtn.addEventListener('click', launchGame);
(async function init() {
setStatus('正在加载...');
initTheme();
initScale();
if (menuLaunch) menuLaunch.classList.add('active');
if (usernameInput && window.gpcl && window.gpcl.getPlayerName) {
const savedName = await window.gpcl.getPlayerName();
if (savedName) usernameInput.value = savedName;
}
await loadVersions();
await loadRemoteVersions();
// 初始化版本选择列表并自动选中最近启动的版本
await renderVersionSelectList();
// 初始化Java版本状态
await initJavaVersionStatus();
bindJavaInstallButtons();
// 绑定模组加载器事件
bindModLoaderEvents();
setInterval(async () => {
// 刷新游戏版本(使用静默模式,不写日志)
if (currentPage === 'launch') {
await loadVersions(true);
await renderVersionSelectList();
}
// 刷新Java版本状态在所有页面都刷新
if (currentPage === 'download') {
await refreshJavaStatus();
}
}, 2000);
// 刷新Java版本状态的函数
async function refreshJavaStatus() {
const javaVersions = ["8", "17", "21", "25"];
for (const ver of javaVersions) {
try {
const result = await window.gpcl.checkJava(ver);
const card = document.querySelector(`.java-version-card[data-version="${ver}"]`);
const badge = document.getElementById(`java-${ver}-status`);
const btn = card?.querySelector('.java-install-btn');
if (result.installed) {
if (!card?.classList.contains('installed')) {
if (card) card.classList.add('installed');
if (badge) {
badge.textContent = '已安装';
badge.className = 'java-version-badge installed';
}
if (btn) {
btn.textContent = '卸载';
btn.classList.add('installed');
btn.disabled = false;
}
}
} else {
if (card?.classList.contains('installed')) {
card.classList.remove('installed');
if (badge) {
badge.textContent = '';
badge.className = 'java-version-badge';
}
if (btn) {
btn.textContent = '安装';
btn.classList.remove('installed');
btn.disabled = false;
}
}
}
} catch (e) {
console.error(`检查Java ${ver} 状态失败`, e);
}
}
updateJavaInstallStatus();
}
// bind refresh button
const refreshBtn = document.getElementById('refresh-btn-page');
if (refreshBtn) refreshBtn.addEventListener('click', async () => {
setStatus('重试加载远程版本...');
await loadRemoteVersions();
// 同时刷新Java状态
await refreshJavaStatus();
setStatus('准备就绪');
});
// bind cancel download button
if (cancelDownloadBtn) cancelDownloadBtn.addEventListener('click', async () => {
const result = await showDialog({
type: 'confirm',
title: '确认取消',
message: '确定要取消当前下载吗?已下载的文件将被清理。'
});
if (result) {
if (window.gpcl && window.gpcl.cancelDownloadOnly) {
await window.gpcl.cancelDownloadOnly();
if (downloadPanel) downloadPanel.classList.add('hidden');
if (detailDownloadBtn) detailDownloadBtn.disabled = false;
showToast('已取消', '下载已取消,文件已清理', 'info');
}
}
});
setStatus('准备就绪');
// 启动器加载完成后自动前置窗口
if (window.gpcl && window.gpcl.focusWindow) {
setTimeout(() => {
window.gpcl.focusWindow();
}, 100);
}
// 绑定启动取消按钮
const launchCancelBtn = document.getElementById('launch-cancel-btn');
if (launchCancelBtn) {
launchCancelBtn.addEventListener('click', () => {
setStatus('[动画] 用户取消启动');
if (window.gpcl && window.gpcl.cancelLaunch) {
window.gpcl.cancelLaunch();
}
hideLaunchAnimation();
});
}
// 静默检查更新
checkForUpdates(true);
// 绑定检查更新按钮
const checkUpdateBtn = document.getElementById('check-update-btn');
if (checkUpdateBtn) {
checkUpdateBtn.addEventListener('click', async () => {
checkUpdateBtn.disabled = true;
checkUpdateBtn.textContent = '检查中...';
await checkForUpdates(false);
const updateStatus = document.getElementById('update-status');
const goDownloadBtn = document.getElementById('go-download-btn');
if (updateAvailable) {
const newest = allNewerVersions[allNewerVersions.length - 1];
checkUpdateBtn.textContent = `发现 ${allNewerVersions.length} 个新版本`;
checkUpdateBtn.classList.add('new-version');
if (updateStatus) {
updateStatus.innerHTML = `
<div class="update-log-title">发现 ${allNewerVersions.length} 个新版本</div>
${renderUpdateVersionCards(allNewerVersions)}
`;
updateStatus.classList.add('show', 'has-update');
}
if (goDownloadBtn) {
goDownloadBtn.classList.remove('hidden');
}
} else {
checkUpdateBtn.textContent = '已是最新版本';
checkUpdateBtn.classList.remove('new-version');
if (updateStatus) {
updateStatus.innerHTML = '当前已是最新版本';
updateStatus.classList.add('show');
updateStatus.classList.remove('has-update');
}
if (goDownloadBtn) {
goDownloadBtn.classList.add('hidden');
}
}
checkUpdateBtn.disabled = false;
});
}
// 绑定立即前往下载按钮
const goDownloadBtn = document.getElementById('go-download-btn');
// 绑定自动检查更新开关
const autoCheckUpdate = document.getElementById('auto-check-update');
// 绑定防止多次启动开关
const preventMultipleLaunch = document.getElementById('prevent-multiple-launch');
// 绑定自动清除日志开关
const autoClearLogs = document.getElementById('auto-clear-logs');
const logRetentionContainer = document.getElementById('log-retention-container');
const logRetentionValue = document.getElementById('log-retention-value');
const logRetentionUnit = document.getElementById('log-retention-unit');
// 初始化版本显示
const aboutVersion = document.getElementById('about-version');
const customMirrorContainer = document.getElementById('custom-java-mirror-container');
const customMirrorUrl = document.getElementById('custom-java-mirror-url');
// 游戏内存
initCustomSelect('settings-memory', async (value) => {
const settings = await loadSettings();
settings.game.memory = value;
await gpcl.saveSettings(settings);
});
// 窗口模式
initCustomSelect('settings-window-mode', async (value) => {
const settings = await loadSettings();
if (!settings.game) settings.game = {};
settings.game.windowMode = value;
await gpcl.saveSettings(settings);
});
// 主题模式
initCustomSelect('settings-theme', async (value) => {
const settings = await loadSettings();
if (!settings.appearance) settings.appearance = {};
settings.appearance.theme = value;
await gpcl.saveSettings(settings);
applyTheme(value);
});
// 界面缩放
initCustomSelect('settings-scale', async (value) => {
const settings = await loadSettings();
if (!settings.appearance) settings.appearance = {};
settings.appearance.scale = value;
await gpcl.saveSettings(settings);
applyScale(value);
});
// Java镜像源
initCustomSelect('java-mirror-select', async (value) => {
if (customMirrorContainer) {
customMirrorContainer.classList.toggle('hidden', value !== 'custom');
}
await saveJavaMirrorSettings(value, customMirrorUrl?.value || '');
});
// 日志保留时间单位
initCustomSelect('log-retention-unit', async (unit) => {
// 根据单位限制当前值
const maxValues = {
hour: 23,
day: 30,
month: 11,
year: 10
};
if (logRetentionValue) {
let value = parseInt(logRetentionValue.value, 10);
if (isNaN(value) || value < 1) value = 1;
if (value > maxValues[unit]) {
value = maxValues[unit];
logRetentionValue.value = value;
}
}
const settings = await loadSettings();
settings.advanced.logRetentionUnit = unit;
if (logRetentionValue) {
settings.advanced.logRetentionValue = parseInt(logRetentionValue.value, 10);
}
await gpcl.saveSettings(settings);
});
// 绑定立即前往下载按钮
if (goDownloadBtn) {
goDownloadBtn.addEventListener('click', () => {
if (window.gpcl && window.gpcl.openExternal) {
window.gpcl.openExternal('https://gamets.caellab.com/gpcl/#download');
}
});
}
// 绑定自动检查更新开关
if (autoCheckUpdate) {
// 从设置中加载状态
(async () => {
const settings = await loadSettings();
autoCheckUpdate.checked = settings.advanced?.autoCheckUpdate !== false;
})();
autoCheckUpdate.addEventListener('change', async () => {
const settings = await loadSettings();
settings.advanced.autoCheckUpdate = autoCheckUpdate.checked;
await gpcl.saveSettings(settings);
updateSettingsBadge();
});
}
// 绑定防止多次启动开关
if (preventMultipleLaunch) {
(async () => {
const settings = await loadSettings();
preventMultipleLaunch.checked = settings.advanced?.preventMultipleLaunch !== false;
})();
preventMultipleLaunch.addEventListener('change', async () => {
const settings = await loadSettings();
settings.advanced.preventMultipleLaunch = preventMultipleLaunch.checked;
await gpcl.saveSettings(settings);
});
}
const playStartupAnimation = document.getElementById('play-startup-animation');
if (playStartupAnimation) {
(async () => {
const settings = await loadSettings();
playStartupAnimation.checked = settings.advanced?.playStartupAnimation === true;
})();
playStartupAnimation.addEventListener('change', async () => {
const settings = await loadSettings();
settings.advanced.playStartupAnimation = playStartupAnimation.checked;
await gpcl.saveSettings(settings);
});
}
const developerMode = document.getElementById('developer-mode');
if (developerMode) {
(async () => {
const settings = await loadSettings();
developerMode.checked = settings.advanced?.developerMode === true;
})();
developerMode.addEventListener('change', async () => {
const settings = await loadSettings();
settings.advanced.developerMode = developerMode.checked;
await gpcl.saveSettings(settings);
if (window.gpcl && window.gpcl.setDeveloperMode) {
await window.gpcl.setDeveloperMode(developerMode.checked);
}
});
}
// 绑定自动清除日志开关
if (autoClearLogs) {
(async () => {
const settings = await loadSettings();
autoClearLogs.checked = settings.advanced?.autoClearLogs !== false;
})();
autoClearLogs.addEventListener('change', async () => {
const settings = await loadSettings();
settings.advanced.autoClearLogs = autoClearLogs.checked;
await gpcl.saveSettings(settings);
if (logRetentionContainer) {
logRetentionContainer.classList.toggle('hidden', !autoClearLogs.checked);
}
});
}
// 绑定保留日志时间输入
if (logRetentionValue) {
(async () => {
const settings = await loadSettings();
logRetentionValue.value = settings.advanced?.logRetentionValue || 7;
})();
logRetentionValue.addEventListener('change', async () => {
let value = parseInt(logRetentionValue.value, 10);
const unit = getCustomSelectValue('log-retention-unit') || 'day';
// 根据单位限制最大值
const maxValues = {
hour: 23,
day: 30,
month: 11,
year: 10
};
if (isNaN(value) || value < 1) {
value = 1;
} else if (value > maxValues[unit]) {
value = maxValues[unit];
}
logRetentionValue.value = value;
const settings = await loadSettings();
settings.advanced.logRetentionValue = value;
await gpcl.saveSettings(settings);
});
}
// 初始化版本显示
if (aboutVersion) {
const version = await getCurrentVersion();
aboutVersion.textContent = `版本: ${version}`;
}
// 绑定Java镜像URL输入
if (customMirrorUrl) {
customMirrorUrl.addEventListener('input', async () => {
await saveJavaMirrorSettings(getCustomSelectValue('java-mirror-select') || 'tsinghua', customMirrorUrl.value);
});
}
// 加载所有设置到界面
(async () => {
const settings = await loadSettings();
// 游戏内存
let memoryValue = '2'; // 默认2GB
if (settings.game?.memory) {
const storedMemory = String(settings.game.memory);
if (storedMemory.includes('096')) {
// 旧格式转换为GB
const mb = parseInt(storedMemory);
if (!isNaN(mb)) {
memoryValue = String(mb / 1024);
}
} else {
memoryValue = storedMemory;
}
}
setCustomSelectValue('settings-memory', memoryValue);
// 窗口模式
setCustomSelectValue('settings-window-mode', settings.game?.windowMode || 'windowed');
// 主题
const theme = settings.appearance?.theme || 'dark';
setCustomSelectValue('settings-theme', theme);
applyTheme(theme);
// 缩放
const scale = settings.appearance?.scale || '100';
setCustomSelectValue('settings-scale', scale);
applyScale(scale);
// Java镜像
const javaMirror = settings.download?.javaMirror || 'tsinghua';
setCustomSelectValue('java-mirror-select', javaMirror);
if (customMirrorContainer) {
customMirrorContainer.classList.toggle('hidden', javaMirror !== 'custom');
}
if (customMirrorUrl && settings.download?.customJavaMirror) {
customMirrorUrl.value = settings.download.customJavaMirror;
}
// 日志保留时间单位
setCustomSelectValue('log-retention-unit', settings.advanced?.logRetentionUnit || 'day');
})();
// 绑定兼容性设置
(async () => {
const resetDefaultToggle = document.getElementById('settings-reset-default');
if (resetDefaultToggle) {
resetDefaultToggle.checked = false; // 默认关闭
resetDefaultToggle.addEventListener('change', async function() {
if (this.checked) {
// 显示确认对话框
const confirmed = await showDialog({
type: 'confirm',
title: '恢复默认设置',
message: '确定要将所有设置恢复为默认值吗?\n\n此操作会重启启动器。'
});
if (confirmed) {
// 恢复默认设置
if (window.gpcl && window.gpcl.resetSettings) {
const result = await window.gpcl.resetSettings();
if (result.success) {
// 重启应用
if (window.gpcl && window.gpcl.restartApp) {
await window.gpcl.restartApp();
}
}
}
} else {
// 用户取消,重置开关状态
this.checked = false;
}
}
});
}
})();
// 绑定设置侧边栏点击事件
const settingsSidebarItems = document.querySelectorAll('.settings-sidebar-item');
settingsSidebarItems.forEach(item => {
item.addEventListener('click', () => {
const tabName = item.dataset.tab;
if (tabName) {
switchSettingsTab(tabName);
}
});
});
// 绑定版本设置按钮 - 先声明所有需要的变量
let currentVersionForSettings = null;
const versionSettingsBtn = document.getElementById('version-settings');
const versionSettingsPanel = document.getElementById('version-settings-panel');
const versionSettingsBackBtn = document.getElementById('version-settings-back-btn');
const versionSettingsTitle = document.getElementById('version-settings-title');
const versionDeleteEnable = document.getElementById('version-delete-enable');
const versionServerIpContainer = document.getElementById('version-server-ip-container');
const versionServerIpInput = document.getElementById('version-server-ip');
const versionSelectPanel = document.getElementById('version-select-panel');
const statusElement = document.getElementById('status');
async function getVersionSettings(versionId) {
try {
if (window.gpcl && window.gpcl.getVersionSettings) {
const result = await window.gpcl.getVersionSettings(versionId);
if (result.success && result.settings) {
return result.settings;
}
}
} catch (e) {
console.error('获取版本设置失败:', e);
}
return {};
}
async function saveVersionSettings(versionId, settings) {
try {
if (window.gpcl && window.gpcl.saveVersionSettings) {
const result = await window.gpcl.saveVersionSettings(versionId, settings);
return result.success;
}
} catch (e) {
console.error('保存版本设置失败:', e);
}
return false;
}
// 更新服务器IP输入框的显示状态
function updateServerIpVisibility() {
const startupMode = getCustomSelectValue('version-startup-mode');
if (startupMode === 'join') {
versionServerIpContainer.classList.add('visible');
} else {
versionServerIpContainer.classList.remove('visible');
}
}
async function loadVersionSettingsToUI(versionId) {
const settings = await getVersionSettings(versionId);
versionDeleteEnable.checked = false;
setCustomSelectValue('version-memory', settings.memory || 'global');
setCustomSelectValue('version-window-mode', settings.windowMode || 'global');
setCustomSelectValue('version-startup-mode', settings.startupMode || 'default');
versionServerIpInput.value = settings.serverIp || '';
updateServerIpVisibility();
}
let saveTimeout = null;
function saveCurrentVersionSettings() {
if (!currentVersionForSettings) return;
const settings = {
memory: getCustomSelectValue('version-memory') || 'global',
windowMode: getCustomSelectValue('version-window-mode') || 'global',
startupMode: getCustomSelectValue('version-startup-mode') || 'default',
serverIp: versionServerIpInput.value || ''
};
if (saveTimeout) clearTimeout(saveTimeout);
saveTimeout = setTimeout(async () => {
const success = await saveVersionSettings(currentVersionForSettings, settings);
if (success) {
showToast('保存成功', `版本 ${currentVersionForSettings} 的设置已保存`, 'success');
} else {
showToast('保存失败', '无法保存版本设置', 'error');
}
}, 300);
}
async function showVersionSettingsPanel(versionId) {
if (!versionSettingsPanel) return;
currentVersionForSettings = versionId;
versionSettingsTitle.textContent = `版本设置 - ${versionId}`;
versionSelectPanel.classList.add('hidden');
if (statusElement) statusElement.style.display = 'none';
versionSettingsPanel.classList.remove('hidden');
versionSettingsPanel.classList.remove('slide-out');
await loadVersionSettingsToUI(versionId);
}
async function hideVersionSettingsPanel() {
if (!versionSettingsPanel) return;
// 立即保存当前设置(取消延迟保存)
if (saveTimeout) clearTimeout(saveTimeout);
if (currentVersionForSettings) {
const settings = {
memory: getCustomSelectValue('version-memory') || 'global',
windowMode: getCustomSelectValue('version-window-mode') || 'global',
startupMode: getCustomSelectValue('version-startup-mode') || 'default',
serverIp: versionServerIpInput.value || ''
};
await saveVersionSettings(currentVersionForSettings, settings);
}
versionSettingsPanel.classList.add('slide-out');
setTimeout(() => {
versionSettingsPanel.classList.add('hidden');
versionSettingsPanel.classList.remove('slide-out');
if (statusElement) statusElement.style.display = '';
currentVersionForSettings = null;
}, 300);
}
if (versionSettingsBtn) {
versionSettingsBtn.addEventListener('click', async () => {
if (selectedVersionId) {
// 如果版本选择面板是打开的,先关闭它
if (!versionSelectPanel.classList.contains('hidden')) {
versionSelectPanel.classList.add('hidden');
}
await showVersionSettingsPanel(selectedVersionId);
} else {
showToast('未选择版本', '请先选择一个版本', 'warning');
}
});
}
if (versionSettingsBackBtn) {
versionSettingsBackBtn.addEventListener('click', async () => {
await hideVersionSettingsPanel();
});
}
if (versionDeleteEnable) {
versionDeleteEnable.addEventListener('change', async function() {
if (this.checked) {
const confirmed = await showDialog({
type: 'confirm',
title: '确认删除',
message: `确定要删除版本 "${currentVersionForSettings}" 吗?此操作不可恢复!`
});
if (confirmed) {
if (window.gpcl && window.gpcl.deleteVersion) {
try {
const result = await window.gpcl.deleteVersion(currentVersionForSettings);
if (result.success) {
showToast('删除成功', `版本 ${currentVersionForSettings} 已删除`, 'success');
await hideVersionSettingsPanel();
loadVersions(true);
renderVersionSelectList();
} else {
showToast('删除失败', result.error || '无法删除版本', 'error');
this.checked = false;
}
} catch (e) {
showToast('删除失败', e.message, 'error');
this.checked = false;
}
}
} else {
this.checked = false;
}
}
});
}
// 初始化版本设置下拉框
initCustomSelect('version-memory', () => {
saveCurrentVersionSettings();
});
initCustomSelect('version-window-mode', () => {
saveCurrentVersionSettings();
});
initCustomSelect('version-startup-mode', () => {
updateServerIpVisibility();
saveCurrentVersionSettings();
});
// 版本服务器IP输入框
if (versionServerIpInput) {
versionServerIpInput.addEventListener('input', () => {
saveCurrentVersionSettings();
});
}
// 绑定版本选择按钮
const versionSelectBtn = document.getElementById('version-select');
const versionPanel = document.getElementById('version-select-panel');
if (versionSelectBtn && versionPanel) {
versionSelectBtn.addEventListener('click', () => {
// 如果版本设置面板是打开的,就不动
if (!versionSettingsPanel.classList.contains('hidden')) {
return;
}
// 切换版本面板显示状态
if (versionPanel.classList.contains('hidden')) {
versionPanel.classList.remove('hidden');
renderVersionSelectList();
} else {
versionPanel.classList.add('hidden');
}
});
}
// 绑定正版登录按钮(显示提示)
const authMicrosoftBtn = document.getElementById('auth-microsoft');
if (authMicrosoftBtn) {
authMicrosoftBtn.addEventListener('click', () => {
showToast('正版登录', '正版登录功能尚未对接微软账号系统', 'info');
});
}
})();
// 版本更新检查
let latestVersion = null;
let latestVersionLog = null;
let updateAvailable = false;
let allNewerVersions = [];
async function getCurrentVersion() {
try {
if (window.gpcl && window.gpcl.getAppVersion) {
return await window.gpcl.getAppVersion();
}
} catch (e) {}
return '1.0.0';
}
function compareVersions(current, latest) {
const currentParts = current.split('.').map(Number);
const latestParts = latest.split('.').map(Number);
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
const c = currentParts[i] || 0;
const l = latestParts[i] || 0;
if (l > c) return 1;
if (c > l) return -1;
}
return 0;
}
function parseLauncherVersions(launcherData) {
const versions = [];
let current = null;
for (const item of launcherData) {
if (item.name === 'version' && item.key) {
if (current) versions.push(current);
current = { version: item.key, title: '', log: '' };
} else if (current) {
if (item.name === 'versionlog' && item.key) {
current.log = item.key;
} else if (item.name === 'versiontitle' && item.key) {
current.title = item.key;
} else if (!item.key && item.name && item.name !== 'versionlog' && item.name !== 'versiontitle') {
current.title = item.name;
}
}
}
if (current) versions.push(current);
return versions;
}
function renderUpdateVersionCards(newerVersions) {
if (!newerVersions || newerVersions.length === 0) return '';
const cards = newerVersions.map((v, index) => {
const isNewest = index === newerVersions.length - 1;
const titleHtml = v.title
? `<div class="update-card-title">${v.title}</div>`
: '';
const logHtml = v.log
? `<div class="update-card-log">${v.log}</div>`
: '';
return `
<div class="update-version-card ${isNewest ? 'newest' : ''}">
<div class="update-card-header">
<span class="update-card-version">${v.version}</span>
${isNewest ? '<span class="update-card-badge">最新</span>' : ''}
</div>
${titleHtml}
${logHtml}
</div>
`;
}).reverse().join('');
return `<div class="update-versions-list">${cards}</div>`;
}
async function checkForUpdates(silent = false) {
setStatus('[版本检查] 开始检查更新...');
try {
const result = await window.gpcl.checkForUpdates();
if (!result.success) {
throw new Error(result.error || '检查更新失败');
}
const data = result.data;
const launcherData = data.Launcher || [];
const parsedVersions = parseLauncherVersions(launcherData);
if (parsedVersions.length === 0) throw new Error('无法获取版本信息');
latestVersion = parsedVersions[0].version;
latestVersionLog = parsedVersions[0].log;
const currentVersion = await getCurrentVersion();
setStatus(`[版本检查] 当前版本: ${currentVersion}, 最新版本: ${latestVersion}`);
allNewerVersions = parsedVersions
.filter(v => compareVersions(currentVersion, v.version) > 0)
.sort((a, b) => compareVersions(a.version, b.version));
updateAvailable = allNewerVersions.length > 0;
setStatus(`[版本检查] 有更新: ${updateAvailable}, 新版本数: ${allNewerVersions.length}`);
if (!silent) {
if (updateAvailable) {
setStatus(`发现 ${allNewerVersions.length} 个新版本`, 'warning');
} else {
setStatus('当前已是最新版本', 'success');
}
}
if (updateAvailable && silent) {
const newest = allNewerVersions[allNewerVersions.length - 1];
const titlePart = newest.title ? ` "${newest.title}"` : '';
showToast('发现新版本', `GPCL ${newest.version}${titlePart} 已发布`, 'warning', 'update-available');
}
await updateSettingsBadge();
} catch (e) {
setStatus(`[版本检查] 错误: ${e.message}`, 'error');
console.error('[版本检查] 详细错误:', e);
if (!silent) {
setStatus('检查更新失败: ' + e.message, 'error');
}
updateAvailable = false;
allNewerVersions = [];
await updateSettingsBadge();
}
}
async function updateSettingsBadge() {
const settingsMenu = document.getElementById('menu-settings');
const checkUpdateBtn = document.getElementById('check-update-btn');
if (!settingsMenu) return;
// 移除旧的红点
const oldBadge = settingsMenu.querySelector('.update-badge');
if (oldBadge) oldBadge.remove();
// 获取设置
const settings = await loadSettings();
// 只有在有新版本且自动检查更新开启时才显示红点
if (updateAvailable && settings.advanced?.autoCheckUpdate !== false) {
const badge = document.createElement('span');
badge.className = 'update-badge';
badge.textContent = '';
badge.style.cssText = 'position:absolute;top:2px;right:2px;width:8px;height:8px;background:#f44336;border-radius:50%;';
settingsMenu.style.position = 'relative';
settingsMenu.appendChild(badge);
// 检查更新按钮也标红
if (checkUpdateBtn) {
checkUpdateBtn.classList.add('new-version');
checkUpdateBtn.textContent = `发现 ${allNewerVersions.length} 个新版本`;
}
} else {
// 移除检查更新按钮的红标(如果当前没有更新)
if (checkUpdateBtn && !updateAvailable) {
checkUpdateBtn.classList.remove('new-version');
checkUpdateBtn.textContent = '检查更新';
}
}
}
async function loadSettings() {
try {
return await gpcl.getSettings();
} catch (e) {
console.error('加载设置失败:', e);
return {
game: {
memory: '2',
playerName: 'GPCL_Player',
javaPath: '',
jvmArgs: '',
windowMode: 'windowed'
},
appearance: {
theme: 'light',
scale: '100'
},
download: {
maxConcurrent: 64,
javaMirror: 'tsinghua',
customJavaMirror: ''
},
advanced: {
autoCheckUpdate: true,
preventMultipleLaunch: true,
autoClearLogs: true,
logRetentionValue: 7,
logRetentionUnit: 'day',
playStartupAnimation: true,
developerMode: false
}
};
}
}
function applyTheme(theme) {
if (theme === 'light') {
document.body.classList.add('light-mode');
} else {
document.body.classList.remove('light-mode');
}
}
async function saveThemeSetting(theme) {
const settings = await loadSettings();
settings.appearance.theme = theme;
await gpcl.saveSettings(settings);
}
async function initTheme() {
const settings = await loadSettings();
const theme = settings.appearance?.theme || 'dark';
applyTheme(theme);
const themeSelect = document.getElementById('settings-theme');
if (themeSelect) {
themeSelect.value = theme;
themeSelect.addEventListener('change', async () => {
const newTheme = themeSelect.value;
applyTheme(newTheme);
await saveThemeSetting(newTheme);
});
}
}
function applyScale(scale) {
const factor = parseInt(scale, 10) / 100;
document.body.style.zoom = factor;
}
async function saveScaleSetting(scale) {
const settings = await loadSettings();
settings.appearance.scale = scale;
await gpcl.saveSettings(settings);
}
async function initScale() {
const settings = await loadSettings();
const scale = settings.appearance?.scale || '100';
applyScale(scale);
const scaleSelect = document.getElementById('settings-scale');
if (scaleSelect) {
scaleSelect.value = scale;
scaleSelect.addEventListener('change', async () => {
const newScale = scaleSelect.value;
applyScale(newScale);
await saveScaleSetting(newScale);
});
}
}
async function saveJavaMirrorSettings(mirror, customUrl) {
const settings = await loadSettings();
settings.download.javaMirror = mirror;
settings.download.customJavaMirror = customUrl;
await gpcl.saveSettings(settings);
}

BIN
renderer/skin/Alex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

187
renderer/splash.html Normal file
View File

@ -0,0 +1,187 @@
<!--
CaelLab BY-SA Code License
Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPCL - Loading</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #0d0d15 0%, #1a1a2e 50%, #0d0d15 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.splash-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
padding: 40px;
}
.logo {
width: 120px;
height: 120px;
border-radius: 24px;
background: linear-gradient(135deg, rgba(79, 195, 247, 0.2), rgba(41, 182, 246, 0.1));
padding: 12px;
animation: pulse 2s ease-in-out infinite;
}
.logo img {
width: 100%;
height: 100%;
border-radius: 18px;
}
@keyframes pulse {
0%, 100% {
box-shadow: 0 0 30px rgba(79, 195, 247, 0.3);
}
50% {
box-shadow: 0 0 60px rgba(79, 195, 247, 0.5);
}
}
.brand-name {
font-size: 24px;
font-weight: 600;
color: #a0aec0;
letter-spacing: 2px;
text-align: center;
}
.brand-name .highlight {
color: #4fc3f7;
font-weight: 700;
}
.version-info {
font-size: 12px;
color: #5f6d85;
margin-top: 10px;
}
.copyright {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
font-size: 11px;
color: #5f6d85;
text-align: center;
}
.particle-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
overflow: hidden;
}
.particle {
position: absolute;
width: 2px;
height: 2px;
background: rgba(79, 195, 247, 0.5);
border-radius: 50%;
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100vh) translateX(20px);
opacity: 0;
}
}
.fade-out {
animation: fadeOut 0.5s ease-out forwards;
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
</style>
</head>
<body>
<div class="particle-container" id="particles"></div>
<div class="splash-container" id="splash-content">
<div class="logo">
<img src="logo.png" alt="GPCL Logo">
</div>
<div class="brand-name">
<span class="highlight">G</span>ood <span class="highlight">P</span>lan <span class="highlight">C</span>raft <span class="highlight">L</span>auncher
</div>
<div class="version-info" id="version-info">v1.1.0</div>
</div>
<script>
function createParticles() {
const container = document.getElementById('particles');
for (let i = 0; i < 25; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.bottom = '-10px';
particle.style.animationDelay = Math.random() * 6 + 's';
particle.style.animationDuration = (4 + Math.random() * 4) + 's';
container.appendChild(particle);
}
}
createParticles();
window.addEventListener('DOMContentLoaded', () => {
if (window.gpcl && window.gpcl.getAppVersion) {
window.gpcl.getAppVersion().then(version => {
document.getElementById('version-info').textContent = 'v' + version;
});
}
setTimeout(() => {
const splashContent = document.getElementById('splash-content');
splashContent.classList.add('fade-out');
setTimeout(() => {
window.location.href = 'index.html';
}, 500);
}, 2000);
});
</script>
</body>
</html>

2964
renderer/style.css Normal file
View File

@ -0,0 +1,2964 @@
/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
* Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
*/
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.hidden {
display: none !important;
}
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-tertiary: rgba(15, 15, 25, 0.95);
--bg-card: rgba(255, 255, 255, 0.05);
--bg-card-hover: rgba(255, 255, 255, 0.08);
--bg-input: rgba(255, 255, 255, 0.08);
--bg-input-hover: rgba(255, 255, 255, 0.12);
--bg-dark: rgba(0, 0, 0, 0.2);
--bg-sidebar: rgba(0, 0, 0, 0.2);
--text-primary: #e0e0e0;
--text-secondary: #b0bec5;
--text-tertiary: #90a4ae;
--text-muted: #78909c;
--text-dim: #5f6d85;
--text-label: #dde4f0;
--text-title: #e8edf5;
--accent-primary: #4fc3f7;
--accent-secondary: #29b6f6;
--accent-tertiary: #03a9f4;
--accent-glow: rgba(79, 195, 247, 0.3);
--border-light: rgba(255, 255, 255, 0.1);
--border-medium: rgba(255, 255, 255, 0.15);
--border-accent: rgba(79, 195, 247, 0.2);
--success: #66bb6a;
--error: #ef5350;
--warning: #ffa726;
--scrollbar-thumb: rgba(79, 195, 247, 0.6);
--scrollbar-thumb-hover: rgba(79, 195, 247, 0.8);
--scrollbar-thumb-active: rgba(41, 182, 246, 0.9);
}
body.light-mode {
--bg-primary: #f0f8ff;
--bg-secondary: #e6f2ff;
--bg-tertiary: #ffffff;
--bg-card: rgba(255, 255, 255, 0.9);
--bg-card-hover: rgba(200, 230, 255, 0.5);
--bg-input: rgba(240, 248, 255, 0.8);
--bg-input-hover: rgba(200, 230, 255, 0.6);
--bg-dark: rgba(0, 100, 180, 0.08);
--bg-sidebar: rgba(240, 248, 255, 0.95);
--text-primary: #1a3a5c;
--text-secondary: #2d4a6f;
--text-tertiary: #4a6a8a;
--text-muted: #6a8aa6;
--text-dim: #8aa0c0;
--text-label: #0a2040;
--text-title: #001530;
--accent-primary: #0099ff;
--accent-secondary: #0077cc;
--accent-tertiary: #0055aa;
--accent-glow: rgba(0, 153, 255, 0.2);
--border-light: rgba(0, 100, 180, 0.1);
--border-medium: rgba(0, 100, 180, 0.2);
--border-accent: rgba(0, 153, 255, 0.3);
--scrollbar-thumb: rgba(0, 153, 255, 0.5);
--scrollbar-thumb-hover: rgba(0, 153, 255, 0.7);
--scrollbar-thumb-active: rgba(0, 119, 204, 0.8);
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-button {
display: none !important;
width: 0 !important;
height: 0 !important;
background: transparent !important;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 4px;
min-height: 24px;
transition: background 0.2s;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover);
}
::-webkit-scrollbar-thumb:active {
background: var(--scrollbar-thumb-active);
}
.main-content {
scrollbar-color: var(--scrollbar-thumb) transparent;
scrollbar-width: thin;
}
.version-list-container {
scrollbar-color: var(--scrollbar-thumb) transparent;
scrollbar-width: thin;
}
.settings-page {
scrollbar-color: var(--scrollbar-thumb) transparent;
scrollbar-width: thin;
}
body {
font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
color: var(--text-primary);
min-height: 100vh;
overflow: hidden;
}
.app-layout {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.header h1 {
font-size: 48px;
color: var(--accent-primary);
text-shadow: 0 0 20px var(--accent-glow);
margin-bottom: 5px;
}
.subtitle {
color: var(--text-muted);
font-size: 14px;
}
.launch-panel {
background: var(--bg-card);
border-radius: 12px;
padding: 0;
margin-bottom: 20px;
backdrop-filter: blur(10px);
border: 1px solid var(--border-light);
display: flex;
min-height: 480px;
overflow: hidden; /* 禁用外层滚动条 */
}
.launch-sidebar {
width: 33.33%;
background: linear-gradient(180deg, rgba(79, 195, 247, 0.08) 0%, rgba(79, 195, 247, 0.02) 100%);
border-right: 1px solid var(--border-light);
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 400px;
}
.launch-content {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
}
.version-select-panel {
background: var(--bg-card);
border-radius: 10px;
padding: 16px;
border: 1px solid var(--border-light);
flex: 1;
display: flex;
flex-direction: column;
min-height: 300px;
}
.version-settings-panel {
background: var(--bg-card);
border-radius: 10px;
padding: 16px;
border: 1px solid var(--border-light);
flex: 1;
display: flex;
flex-direction: column;
min-height: 300px;
animation: slideInRight 0.3s ease-out;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(100%);
}
}
.version-settings-panel.slide-out {
animation: slideOutRight 0.3s ease-in;
}
.version-settings-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-light);
}
.version-settings-back {
background: none;
border: 1px solid var(--border-light);
color: var(--text-secondary);
width: 36px;
height: 36px;
border-radius: 8px;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.version-settings-back:hover {
background: var(--bg-card-hover);
color: var(--accent-primary);
border-color: var(--accent-primary);
}
.version-settings-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--text-title);
}
.version-settings-body {
flex: 1;
overflow-y: auto;
}
.version-settings-section {
margin-bottom: 20px;
}
.version-settings-section .section-title {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.version-settings-section .section-desc {
margin: -4px 0 12px 0;
font-size: 12px;
color: var(--text-muted);
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: var(--bg-dark);
border-radius: 8px;
margin-bottom: 8px;
transition: background 0.2s;
}
.setting-item:hover {
background: var(--bg-card-hover);
}
.setting-info {
flex: 1;
margin-right: 16px;
}
.setting-info .setting-label {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 2px;
}
.setting-info .setting-desc {
font-size: 12px;
color: var(--text-muted);
}
/* 自定义下拉框容器 */
.custom-select {
position: relative;
min-width: 140px;
font-size: 13px;
}
.custom-select-trigger {
background: var(--bg-card);
border: 1px solid var(--border-light);
color: var(--text-primary);
padding: 8px 14px;
border-radius: 8px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.2s;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.custom-select-trigger:hover {
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.1);
}
.custom-select-trigger.active {
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.2);
}
.custom-select-arrow {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
color: var(--text-secondary);
}
.custom-select-trigger.active .custom-select-arrow {
transform: rotate(180deg);
color: var(--accent-primary);
}
.custom-select-dropdown {
position: absolute;
top: calc(100% + 8px);
left: 0;
right: 0;
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 8px;
overflow: hidden;
z-index: 1000;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.2s;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
body.light-theme .custom-select-dropdown {
box-shadow: 0 8px 24px rgba(0, 100, 180, 0.15);
}
.custom-select-dropdown.open {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.custom-select-option {
padding: 10px 14px;
color: var(--text-primary);
cursor: pointer;
transition: all 0.15s;
user-select: none;
}
.custom-select-option:hover {
background: var(--bg-card-hover);
color: var(--accent-primary);
}
.custom-select-option.selected {
background: rgba(79, 195, 247, 0.1);
color: var(--accent-primary);
}
.custom-select-option.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.custom-select-option.disabled:hover {
background: transparent;
color: var(--text-muted);
}
/* 小尺寸下拉框 */
.custom-select.small {
min-width: 80px;
}
.custom-select.small .custom-select-trigger {
padding: 6px 10px;
font-size: 12px;
}
.custom-select.small .custom-select-option {
padding: 8px 10px;
font-size: 12px;
}
/* 保持旧的setting-select样式用于兼容 */
.setting-select {
background: var(--bg-card);
border: 1px solid var(--border-light);
color: var(--text-primary);
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
min-width: 140px;
}
.setting-select:hover {
border-color: var(--accent-primary);
}
.setting-select:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.2);
}
.toggle-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-switch .slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--bg-dark);
border: 1px solid var(--border-light);
transition: 0.3s;
border-radius: 24px;
}
.toggle-switch .slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background: var(--text-muted);
transition: 0.3s;
border-radius: 50%;
}
.toggle-switch input:checked + .slider {
background: var(--accent-primary);
border-color: var(--accent-primary);
}
.toggle-switch input:checked + .slider:before {
transform: translateX(20px);
background: white;
}
.version-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-light);
}
.version-panel-title {
font-size: 16px;
font-weight: 600;
color: var(--text-title);
margin: 0;
}
.version-count {
font-size: 12px;
color: var(--text-muted);
}
.version-list-container {
flex: 1;
overflow-y: auto;
margin-bottom: 12px;
}
.version-list-loading {
text-align: center;
padding: 20px;
color: var(--text-muted);
font-size: 13px;
}
.version-list-empty {
text-align: center;
padding: 20px;
color: var(--text-muted);
font-size: 13px;
}
.version-item {
display: flex;
align-items: center;
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
margin-bottom: 4px;
}
.version-item:hover {
background: var(--bg-card-hover);
}
.version-item.selected {
background: rgba(79, 195, 247, 0.15);
border-left: 3px solid var(--accent-primary);
}
.version-item-icon {
font-size: 14px;
margin-right: 10px;
}
.version-item-id {
flex: 1;
font-size: 14px;
font-weight: 500;
color: var(--text-label);
}
.version-item-time {
font-size: 11px;
color: var(--text-dim);
}
.selected-version-info {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
background: var(--bg-card-hover);
border-radius: 6px;
}
.selected-version-label {
font-size: 12px;
color: var(--text-muted);
}
.selected-version-name {
font-size: 14px;
font-weight: 600;
color: var(--accent-primary);
}
.auth-tabs {
display: flex;
width: 100%;
gap: 8px;
margin-bottom: 24px;
}
.auth-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: 10px 8px;
background: var(--bg-input);
border: 1px solid var(--border-medium);
border-radius: 8px;
cursor: pointer;
transition: all 0.25s;
}
.auth-tab:hover:not(:disabled) {
background: var(--bg-input-hover);
border-color: var(--accent-primary);
}
.auth-tab.auth-tab-active {
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
border-color: var(--accent-primary);
color: var(--bg-primary);
}
.auth-tab.auth-tab-disabled {
opacity: 0.5;
cursor: not-allowed;
}
.auth-label {
font-size: 13px;
font-weight: 600;
}
.auth-badge {
font-size: 10px;
padding: 2px 6px;
background: rgba(255, 87, 87, 0.2);
color: var(--error);
border-radius: 10px;
margin-top: 2px;
}
.auth-tab-active .auth-badge {
background: rgba(0, 0, 0, 0.2);
color: rgba(255, 255, 255, 0.8);
}
.skin-container {
width: 100%;
flex: 1;
margin-bottom: 8px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.skin-wrapper {
width: 100%;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.skin-wrapper:hover {
transform: scale(1.1);
}
.skin-image {
width: 50px;
height: 50px;
object-fit: contain;
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.4));
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.skin-wrapper:hover .skin-image {
transform: scale(1.15);
}
.skin-reflection {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 12px;
background: linear-gradient(180deg, rgba(79, 195, 247, 0.2) 0%, transparent 100%);
border-radius: 50%;
filter: blur(6px);
}
.username-input-group {
width: 100%;
margin-bottom: 12px;
}
.username-label {
display: block;
font-size: 12px;
color: var(--text-tertiary);
margin-bottom: 6px;
text-align: center;
}
.username-input {
width: 100%;
padding: 10px 12px;
background: var(--bg-input);
border: 1px solid var(--border-medium);
border-radius: 8px;
color: var(--text-primary);
font-size: 14px;
text-align: center;
outline: none;
transition: border-color 0.3s;
}
.username-input:focus {
border-color: var(--accent-primary);
}
.launch-button {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: var(--bg-primary);
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: auto;
}
.launch-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 20px var(--accent-glow);
}
.launch-button:active {
transform: translateY(0);
}
.launch-button:disabled {
background: var(--text-muted);
cursor: not-allowed;
box-shadow: none;
transform: none;
}
.launch-button.no-game {
background: linear-gradient(135deg, #f44336, #e53935);
animation: pulse-red 2s infinite;
}
.launch-button.no-game:hover:not(:disabled) {
background: linear-gradient(135deg, #e53935, #d32f2f);
box-shadow: 0 5px 20px rgba(244, 67, 54, 0.3);
}
.launch-text {
font-size: 16px;
}
.selected-version-display {
width: 100%;
text-align: center;
margin-top: 8px;
margin-bottom: 8px;
font-size: 12px;
color: var(--text-tertiary);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.selected-version-display.hidden {
display: none !important;
}
.version-display-label {
color: var(--text-muted);
}
.version-display-text {
color: var(--accent-primary);
font-weight: 600;
}
/* 设置输入框 */
.setting-input {
background: var(--bg-card);
border: 1px solid var(--border-light);
color: var(--text-primary);
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
transition: all 0.2s;
min-width: 180px;
outline: none;
}
.setting-input:focus {
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.15);
}
.setting-input::placeholder {
color: var(--text-tertiary);
}
/* 服务器IP输入框默认隐藏 */
#version-server-ip-container {
display: none;
}
#version-server-ip-container.visible {
display: flex;
}
.launch-footer {
width: 100%;
display: flex;
gap: 8px;
margin-top: 12px;
}
.footer-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 8px;
background: transparent;
border: 1px solid var(--border-light);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
color: var(--text-tertiary);
}
.footer-btn:hover {
background: var(--bg-card-hover);
border-color: var(--border-accent);
color: var(--text-secondary);
}
.footer-text {
font-size: 12px;
}
.download-panel {
background: var(--bg-card);
border-radius: 12px;
padding: 25px;
margin-bottom: 20px;
backdrop-filter: blur(10px);
border: 1px solid var(--border-light);
}
.download-panel h3 {
margin-bottom: 15px;
color: var(--text-tertiary);
font-size: 16px;
}
.input-group {
margin-bottom: 18px;
}
.input-group label {
display: block;
margin-bottom: 8px;
color: var(--text-tertiary);
font-size: 14px;
}
.input-group input,
.input-group select {
width: 100%;
padding: 12px;
background: var(--bg-input);
border: 1px solid var(--border-medium);
border-radius: 8px;
color: var(--text-primary);
font-size: 15px;
outline: none;
transition: border-color 0.3s;
}
.input-group input:focus,
.input-group select:focus {
border-color: var(--accent-primary);
}
.launch-button {
width: 100%;
padding: 14px;
background: var(--accent-primary);
color: var(--bg-primary);
border: none;
border-radius: 8px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
margin-top: 5px;
}
.launch-button:hover {
background: var(--accent-secondary);
transform: translateY(-2px);
box-shadow: 0 5px 20px var(--accent-glow);
}
.launch-button:active {
transform: translateY(0);
}
.launch-button:disabled {
background: var(--text-muted);
cursor: not-allowed;
box-shadow: none;
transform: none;
}
.status {
margin-top: 15px;
padding: 10px;
border-radius: 6px;
text-align: center;
font-size: 14px;
background: var(--bg-card);
display: none !important;
height: 0;
padding: 0;
margin: 0;
overflow: hidden;
}
.progress-container {
margin-top: 15px;
}
.progress-text {
margin-bottom: 8px;
font-size: 13px;
color: var(--text-secondary);
}
.progress-bar-bg {
background: var(--bg-input);
border-radius: 4px;
height: 8px;
overflow: hidden;
}
.progress-bar-fill {
background: var(--accent-primary);
height: 100%;
width: 0%;
transition: width 0.3s ease;
}
select option {
background-color: var(--bg-primary);
color: var(--text-primary);
}
select {
cursor: pointer;
}
.titlebar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 120;
-webkit-app-region: drag;
height: 52px;
display: flex;
align-items: center;
padding: 0 16px;
background: var(--bg-tertiary);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-light);
}
.titlebar .title {
color: var(--accent-primary);
font-weight: 700;
font-size: 18px;
letter-spacing: 1px;
}
.titlebar-left {
-webkit-app-region: drag;
}
.titlebar-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
-webkit-app-region: no-drag;
}
.window-controls { display: flex; gap: 6px; margin-left: auto; }
.window-controls button {
-webkit-app-region: no-drag;
border: none;
background: transparent;
color: var(--text-primary);
width: 40px;
height: 28px;
cursor: pointer;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.window-controls button.min-btn:hover { background: var(--bg-card-hover); }
.window-controls button.close-btn:hover { background: rgba(255,80,80,0.9); }
.menu-bar {
-webkit-app-region: no-drag;
display: flex;
gap: 6px;
}
.menu {
list-style: none;
display: flex;
gap: 6px;
margin: 0;
padding: 0;
}
.menu-item {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
color: var(--text-secondary);
font-size: 15px;
font-weight: 500;
-webkit-app-region: no-drag;
transition: all 0.25s;
background: var(--bg-card);
border: 1px solid var(--border-light);
}
.menu-item:hover {
background: var(--bg-card-hover);
color: var(--text-primary);
border-color: var(--border-accent);
}
.menu-item.active {
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: var(--bg-primary);
border-color: var(--accent-primary);
box-shadow: 0 4px 12px var(--accent-glow);
}
.menu-icon {
font-size: 14px;
}
.main-content {
flex: 1;
margin-top: 52px;
padding: 24px;
overflow-y: auto;
max-height: calc(100vh - 52px);
}
.container {
max-width: 800px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.hidden {
display: none !important;
}
/* ==================== 下载页面样式 ==================== */
.download-page {
padding: 0;
}
.download-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.download-header h3 {
color: var(--accent-primary);
font-size: 20px;
margin: 0;
}
.download-status-panel {
background: var(--bg-card);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid var(--border-light);
}
.download-stats {
display: flex;
gap: 30px;
}
.chart-container {
flex: 1;
background: var(--bg-dark);
border-radius: 8px;
padding: 15px;
min-height: 200px;
}
#speedChart {
width: 100% !important;
height: 180px !important;
}
.progress-section {
width: 320px;
display: flex;
flex-direction: column;
gap: 15px;
}
.speed-info {
display: flex;
gap: 30px;
}
.speed-item {
flex: 1;
background: var(--bg-dark);
border-radius: 8px;
padding: 12px;
text-align: center;
}
.speed-label {
display: block;
font-size: 12px;
color: var(--text-muted);
margin-bottom: 5px;
}
.speed-value {
display: block;
font-size: 20px;
font-weight: bold;
color: var(--accent-primary);
}
.progress-bar-wrapper {
background: var(--bg-dark);
border-radius: 8px;
padding: 15px;
}
.progress-bar-bg {
background: var(--bg-input);
border-radius: 6px;
height: 12px;
overflow: hidden;
position: relative;
}
.progress-bar-fill {
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
height: 100%;
width: 0%;
transition: width 0.3s ease;
border-radius: 6px;
box-shadow: 0 0 10px var(--accent-glow);
}
.progress-text {
text-align: center;
margin-top: 8px;
font-size: 14px;
color: var(--text-secondary);
font-weight: 500;
}
.download-filename {
background: var(--bg-dark);
border-radius: 8px;
padding: 12px 15px;
font-size: 13px;
color: var(--text-tertiary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.refresh-btn {
background: var(--bg-card-hover);
color: var(--text-primary);
border: 1px solid var(--border-accent);
border-radius:6px;
cursor:pointer;
padding: 8px 16px;
}
.refresh-btn:hover { background: var(--bg-input-hover); }
.cancel-download-btn {
width: 100%;
padding: 10px;
background: rgba(255, 87, 87, 0.15);
color: var(--error);
border: 1px solid rgba(255, 87, 87, 0.3);
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
margin-top: 10px;
}
.cancel-download-btn:hover {
background: rgba(255, 87, 87, 0.25);
border-color: rgba(255, 87, 87, 0.5);
}
.cancel-download-btn.complete {
background: rgba(102, 187, 106, 0.15);
color: #66bb6a;
border: 1px solid rgba(102, 187, 106, 0.3);
cursor: default;
}
.cancel-download-btn.complete:hover {
background: rgba(102, 187, 106, 0.15);
border-color: rgba(102, 187, 106, 0.3);
}
/* ==================== 版本列表样式 ==================== */
.version-list-section {
background: var(--bg-card);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid var(--border-light);
}
.version-list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.version-list-header .section-title {
font-size: 20px;
font-weight: 600;
color: var(--accent-primary);
margin: 0;
}
.version-count-label {
font-size: 13px;
color: var(--text-tertiary);
}
.version-list-loading,
.version-list-error {
text-align: center;
padding: 24px;
color: var(--text-tertiary);
font-size: 14px;
}
.version-category {
margin-bottom: 4px;
}
.version-category-header {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: var(--bg-card);
border-radius: 6px;
cursor: pointer;
user-select: none;
transition: background 0.2s;
margin-bottom: 2px;
}
.version-category-header:hover {
background: var(--bg-card-hover);
}
.version-category-arrow {
font-size: 12px;
color: var(--text-muted);
transition: transform 0.25s ease;
flex-shrink: 0;
}
.version-category-arrow.collapsed {
transform: rotate(-90deg);
}
.version-category-icon {
font-size: 14px;
flex-shrink: 0;
}
.version-category-name {
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
}
.version-category-count {
font-size: 12px;
color: var(--text-dim);
margin-left: auto;
}
.version-category-items {
overflow-y: auto;
max-height: 5000px;
transition: max-height 0.3s ease-out;
}
.version-category-items.collapsed {
max-height: 0;
overflow-y: hidden;
}
.version-items {
padding-left: 24px;
}
.version-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 14px 8px 28px;
cursor: pointer;
border-radius: 4px;
transition: background 0.15s;
}
.version-item:hover {
background: var(--bg-card-hover);
}
.version-item.active {
background: var(--bg-card-hover);
}
.version-item-id {
font-size: 14px;
font-weight: 500;
color: var(--text-label);
}
.version-item-time {
font-size: 11px;
color: var(--text-dim);
margin-left: auto;
flex-shrink: 0;
}
/* 版本详情页面 */
.version-detail-page {
background: var(--bg-card);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid var(--border-light);
}
.detail-back-btn {
background: none;
border: none;
color: var(--text-muted);
font-size: 14px;
cursor: pointer;
padding: 4px 0;
margin-bottom: 16px;
transition: color 0.2s;
}
.detail-back-btn:hover {
color: var(--accent-primary);
}
.detail-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.detail-version-id {
font-size: 24px;
font-weight: 700;
color: var(--text-title);
}
.detail-version-type {
font-size: 12px;
color: var(--text-tertiary);
background: var(--bg-card);
padding: 2px 10px;
border-radius: 10px;
}
.detail-info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 20px;
}
.detail-info-item {
background: var(--bg-dark);
border-radius: 8px;
padding: 12px 14px;
}
.detail-info-label {
display: block;
font-size: 11px;
color: var(--text-dim);
margin-bottom: 4px;
}
.detail-info-value {
display: block;
font-size: 14px;
color: var(--text-secondary);
font-weight: 500;
}
.detail-options {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.detail-option-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
overflow: hidden;
}
.detail-option-card:hover {
border-color: var(--border-accent);
}
.detail-option-card.disabled-option {
opacity: 0.5;
cursor: not-allowed;
}
.detail-option-card.not-available {
opacity: 0.6;
}
.detail-option-card.not-available .detail-option-header {
cursor: not-allowed;
}
.detail-option-card.incompatible {
opacity: 0.7;
border-color: rgba(255, 87, 87, 0.3);
}
.detail-option-card.incompatible .detail-option-badge {
background: rgba(255, 87, 87, 0.15);
color: var(--error);
}
.detail-option-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
cursor: pointer;
transition: background 0.2s;
}
.detail-option-header:hover {
background: var(--bg-card-hover);
}
.detail-option-left {
display: flex;
align-items: center;
gap: 12px;
}
.detail-option-right {
display: flex;
align-items: center;
gap: 10px;
}
.detail-option-icon {
font-size: 20px;
}
.detail-option-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.detail-option-name {
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
}
.detail-option-desc {
font-size: 11px;
color: var(--text-dim);
}
.detail-option-badge {
font-size: 11px;
padding: 4px 10px;
border-radius: 4px;
background: var(--bg-input);
color: var(--text-muted);
transition: all 0.2s;
}
.detail-option-badge.available {
background: rgba(102, 187, 106, 0.15);
color: var(--success);
}
.detail-option-badge.unavailable {
background: rgba(255, 87, 87, 0.15);
color: var(--error);
}
.detail-option-badge.coming-soon {
background: rgba(255, 193, 7, 0.15);
color: var(--warning);
}
.detail-option-arrow {
font-size: 12px;
color: var(--text-muted);
transition: transform 0.2s;
}
.detail-option-arrow.expanded {
transform: rotate(180deg);
}
.detail-option-content {
padding: 0 14px 14px 14px;
border-top: 1px solid var(--border-light);
}
.detail-option-loading {
padding: 20px;
text-align: center;
color: var(--text-muted);
font-size: 13px;
}
.detail-option-versions {
padding: 10px 0;
max-height: 200px;
overflow-y: auto;
}
.detail-option-version-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
margin-bottom: 6px;
background: var(--bg-dark);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.detail-option-version-item:hover {
background: var(--bg-card-hover);
}
.detail-option-version-item.selected {
background: rgba(79, 195, 247, 0.15);
border: 1px solid var(--accent-primary);
}
.detail-option-version-name {
font-size: 13px;
color: var(--text-secondary);
}
.detail-option-version-check {
font-size: 14px;
color: var(--accent-primary);
opacity: 0;
transition: opacity 0.2s;
}
.detail-option-version-item.selected .detail-option-version-check {
opacity: 1;
}
.detail-option-error {
padding: 20px;
text-align: center;
color: var(--error);
font-size: 13px;
}
.detail-option-incompatible-msg {
padding: 10px 12px;
background: rgba(255, 87, 87, 0.1);
border-radius: 6px;
font-size: 12px;
color: var(--error);
margin-top: 8px;
}
.detail-download-btn {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: var(--bg-primary);
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: all 0.3s;
margin-top: 4px;
}
.detail-download-btn:hover {
background: linear-gradient(135deg, var(--accent-secondary), var(--accent-tertiary));
transform: translateY(-2px);
box-shadow: 0 5px 20px var(--accent-glow);
}
.detail-download-btn:disabled {
background: var(--text-muted);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* ==================== Java下载区域样式 ==================== */
.java-download-section {
background: var(--bg-card);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid var(--border-light);
margin-top: 20px;
}
.java-download-section .section-title {
font-size: 20px;
font-weight: 600;
color: var(--accent-primary);
margin: 0 0 8px 0;
}
.java-download-section .section-desc {
font-size: 13px;
color: var(--text-tertiary);
margin: 0 0 16px 0;
}
.java-install-status {
margin-bottom: 16px;
padding: 12px;
background: var(--bg-card-hover);
border-radius: 8px;
font-size: 13px;
color: var(--text-secondary);
}
.java-version-selector {
margin-top: 16px;
}
.java-version-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}
.java-version-card {
background: var(--bg-card);
border: 2px solid var(--border-light);
border-radius: 12px;
padding: 16px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.java-version-card:hover {
border-color: var(--accent-primary);
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--accent-glow);
}
.java-version-card.installed {
border-color: var(--success);
background: var(--bg-card);
}
.java-version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.java-version-num {
font-size: 18px;
font-weight: 700;
color: var(--text-title);
}
.java-version-badge {
display: inline-block;
padding: 3px 10px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: var(--bg-primary);
font-size: 11px;
font-weight: 700;
border-radius: 12px;
text-transform: uppercase;
}
.java-version-info {
margin-bottom: 12px;
}
.java-info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid var(--border-light);
}
.java-info-row:last-child {
border-bottom: none;
}
.info-label {
font-size: 12px;
color: var(--text-tertiary);
}
.info-value {
font-size: 12px;
color: var(--text-secondary);
font-weight: 500;
}
.java-install-btn {
width: 100%;
padding: 10px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: var(--bg-primary);
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.java-install-btn:hover:not(:disabled) {
background: linear-gradient(135deg, var(--accent-secondary), var(--accent-tertiary));
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--accent-glow);
}
.java-install-btn:disabled {
background: var(--text-muted);
color: var(--text-secondary);
cursor: not-allowed;
transform: none;
}
.java-install-btn.installed {
background: rgba(255, 87, 87, 0.15);
color: var(--error);
border: 1px solid rgba(255, 87, 87, 0.3);
}
.java-install-btn.installed:hover:not(:disabled) {
background: rgba(255, 87, 87, 0.25);
border-color: rgba(255, 87, 87, 0.5);
box-shadow: 0 4px 12px rgba(255, 87, 87, 0.2);
}
/* ==================== 设置页面样式 ==================== */
.settings-page {
padding: 0;
max-width: none;
margin: 0;
display: flex;
min-height: calc(100vh - 100px);
height: calc(100vh - 100px);
overflow: hidden;
}
.settings-sidebar {
width: 180px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border-light);
padding: 20px 0;
flex-shrink: 0;
overflow-y: auto;
}
.settings-sidebar-menu {
list-style: none;
padding: 0;
margin: 0;
}
.settings-sidebar-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 20px;
cursor: pointer;
color: var(--text-tertiary);
transition: all 0.2s;
border-left: 3px solid transparent;
}
.settings-sidebar-item:hover {
background: var(--bg-card-hover);
color: var(--text-secondary);
}
.settings-sidebar-item.active {
background: var(--bg-card-hover);
color: var(--accent-primary);
border-left-color: var(--accent-primary);
}
.settings-content {
flex: 1;
padding: 28px 36px;
overflow-y: auto;
display: flex;
flex-direction: column;
max-height: calc(100vh - 100px);
}
.settings-content-panel {
display: none;
flex: 1;
}
.settings-content-panel.active {
display: block;
flex: 1;
}
.settings-page-inner {
display: flex;
flex-direction: column;
gap: 28px;
padding-bottom: 20px;
}
.settings-group {
margin-bottom: 0;
}
.settings-group-title {
font-size: 22px;
font-weight: 600;
color: var(--text-title);
margin-bottom: 4px;
}
.settings-group-desc {
font-size: 13px;
color: var(--text-dim);
margin-bottom: 22px;
}
.setting-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
}
.setting-card:last-child {
margin-bottom: 0;
}
.setting-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 20px;
flex-wrap: wrap;
}
.setting-row-indent {
padding-left: 30px;
margin-top: 12px;
}
.setting-left {
flex: 1;
min-width: 200px;
max-width: 60%;
}
.setting-label {
font-size: 14px;
font-weight: 500;
color: var(--text-label);
}
.setting-desc {
font-size: 12px;
color: var(--text-dim);
margin-top: 4px;
line-height: 1.55;
word-break: break-word;
overflow-wrap: break-word;
}
.setting-control {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
flex-wrap: wrap;
}
.setting-control > * {
pointer-events: auto;
}
.num-btn {
width: 32px;
height: 32px;
border-radius: 6px;
border: 1px solid var(--border-light);
background: var(--bg-input);
color: var(--text-secondary);
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.num-btn:hover {
border-color: var(--accent-primary);
color: var(--accent-primary);
background: var(--bg-card-hover);
}
.num-btn:active { transform: scale(0.94); }
.num-display {
width: 56px;
height: 32px;
border-radius: 6px;
border: 1px solid var(--border-light);
background: var(--bg-input);
color: var(--text-title);
font-size: 14px;
font-weight: 600;
text-align: center;
outline: none;
}
.num-display:focus {
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px var(--accent-glow);
}
.setting-select {
padding: 8px 12px;
background: var(--bg-input);
border: 1px solid var(--border-medium);
border-radius: 6px;
color: var(--text-title);
font-size: 14px;
min-width: 180px;
cursor: pointer;
transition: all 0.15s;
}
.setting-select:hover {
border-color: var(--accent-primary);
background: var(--bg-input-hover);
}
.setting-select:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px var(--accent-glow);
}
.setting-select.small {
min-width: 80px;
padding: 8px 10px;
font-size: 13px;
}
.setting-input {
padding: 8px 12px;
background: var(--bg-input);
border: 1px solid var(--border-medium);
border-radius: 6px;
color: var(--text-title);
font-size: 14px;
min-width: 200px;
transition: all 0.15s;
}
.setting-input:hover {
border-color: var(--accent-primary);
background: var(--bg-input-hover);
}
.setting-input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px var(--accent-glow);
}
.setting-input::placeholder {
color: var(--text-dim);
}
.setting-input code {
background: var(--bg-card-hover);
color: var(--accent-primary);
padding: 2px 6px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--text-muted);
transition: 0.3s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--accent-primary);
}
input:checked + .slider:before {
transform: translateX(24px);
}
input:checked + .slider {
box-shadow: 0 0 10px var(--accent-glow);
}
.settings-page-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 20px;
border-top: 1px solid var(--border-light);
margin-top: auto;
flex-shrink: 0;
background: var(--bg-primary);
position: sticky;
bottom: 0;
}
.btn {
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.2s;
pointer-events: auto;
}
.btn-reset {
background: transparent;
color: var(--text-muted);
border: 1px solid var(--border-light);
}
.btn-reset:hover {
color: var(--text-secondary);
border-color: var(--border-medium);
background: var(--bg-card);
}
.btn-save {
background: var(--accent-primary);
color: #fff;
}
.btn-save:hover {
background: var(--accent-secondary);
box-shadow: 0 2px 12px var(--accent-glow);
}
.btn-save:active { transform: scale(0.97); }
.btn-browse {
padding: 8px 16px;
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 6px;
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
margin-left: 8px;
}
.btn-browse:hover {
border-color: var(--accent-primary);
color: var(--accent-primary);
}
.about-card {
margin-bottom: 20px;
}
.about-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.about-icon {
width: 64px;
height: 64px;
border-radius: 12px;
background: var(--bg-card-hover);
padding: 8px;
}
.about-info {
flex: 1;
}
.about-title {
font-size: 20px;
font-weight: 600;
color: var(--text-title);
margin-bottom: 4px;
}
.about-version {
font-size: 14px;
color: var(--text-tertiary);
}
.check-update-btn {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
border: none;
border-radius: 8px;
color: var(--bg-primary);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.check-update-btn:hover {
background: linear-gradient(135deg, var(--accent-secondary), var(--accent-tertiary));
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--accent-glow);
}
.check-update-btn.new-version {
background: linear-gradient(135deg, #f44336, #e53935);
animation: pulse-red 2s infinite;
}
@keyframes pulse-red {
0%, 100% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.4); }
50% { box-shadow: 0 0 0 8px rgba(244, 67, 54, 0); }
}
.update-status {
margin-top: 12px;
padding: 12px;
background: var(--bg-card);
border-radius: 8px;
font-size: 13px;
color: var(--text-secondary);
line-height: 1.6;
white-space: pre-wrap;
display: none;
}
.update-status.show {
display: block;
}
.update-status.has-update {
background: rgba(244, 67, 54, 0.1);
border: 1px solid rgba(244, 67, 54, 0.2);
}
.update-log-title {
font-size: 15px;
font-weight: 600;
color: #f44336;
margin-bottom: 8px;
}
.update-log-content {
margin-top: 8px;
}
.update-log-label {
font-size: 12px;
color: var(--text-tertiary);
margin-bottom: 4px;
font-weight: 500;
}
.update-log-text {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.8;
padding: 8px 10px;
background: rgba(255, 255, 255, 0.03);
border-radius: 6px;
white-space: normal;
word-break: break-word;
}
body.light-mode .update-log-text {
background: rgba(0, 0, 0, 0.03);
}
.update-versions-list {
max-height: 280px;
overflow-y: auto;
margin-top: 10px;
padding-right: 4px;
}
.update-versions-list::-webkit-scrollbar {
width: 5px;
}
.update-versions-list::-webkit-scrollbar-track {
background: transparent;
}
.update-versions-list::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
border-radius: 3px;
}
.update-versions-list::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.25);
}
body.light-mode .update-versions-list::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
}
body.light-mode .update-versions-list::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.25);
}
.update-version-card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 8px;
padding: 12px 14px;
margin-bottom: 8px;
transition: border-color 0.2s;
}
.update-version-card:last-child {
margin-bottom: 0;
}
.update-version-card.newest {
border-color: rgba(244, 67, 54, 0.3);
background: rgba(244, 67, 54, 0.06);
}
body.light-mode .update-version-card {
background: rgba(0, 0, 0, 0.03);
border-color: rgba(0, 0, 0, 0.08);
}
body.light-mode .update-version-card.newest {
border-color: rgba(244, 67, 54, 0.3);
background: rgba(244, 67, 54, 0.04);
}
.update-card-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.update-card-version {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.update-card-badge {
font-size: 11px;
padding: 1px 6px;
border-radius: 4px;
background: rgba(244, 67, 54, 0.2);
color: #f44336;
font-weight: 500;
}
.update-card-title {
font-size: 13px;
color: #4fc3f7;
font-weight: 500;
margin-bottom: 6px;
}
.update-card-log {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.7;
padding: 8px 10px;
background: rgba(255, 255, 255, 0.03);
border-radius: 6px;
white-space: normal;
word-break: break-word;
}
body.light-mode .update-card-log {
background: rgba(0, 0, 0, 0.02);
}
.go-download-btn {
width: 100%;
padding: 12px;
background: rgba(244, 67, 54, 0.15);
color: #f44336;
border: 1px solid rgba(244, 67, 54, 0.3);
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 12px;
}
.go-download-btn:hover {
background: rgba(244, 67, 54, 0.25);
border-color: rgba(244, 67, 54, 0.5);
}
.status-message {
margin-top: 15px;
padding: 12px;
border-radius: 8px;
font-size: 14px;
background: var(--bg-card);
color: var(--text-secondary);
}
.toast-container {
position: fixed;
top: 70px;
right: 20px;
z-index: 200;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.toast {
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: 10px;
padding: 12px 16px;
min-width: 260px;
max-width: 360px;
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
pointer-events: auto;
animation: toastIn 0.3s ease;
display: flex;
align-items: flex-start;
gap: 10px;
}
.toast.toast-success { border-left: 3px solid var(--success); }
.toast.toast-error { border-left: 3px solid var(--error); }
.toast.toast-warning { border-left: 3px solid var(--warning); }
.toast.toast-info { border-left: 3px solid var(--accent-primary); }
.toast-icon {
font-size: 16px;
line-height: 1;
flex-shrink: 0;
margin-top: 1px;
}
.toast-success .toast-icon { color: var(--success); }
.toast-error .toast-icon { color: var(--error); }
.toast-warning .toast-icon { color: var(--warning); }
.toast-info .toast-icon { color: var(--accent-primary); }
.toast-title {
font-size: 13px;
font-weight: 600;
color: var(--text-title);
margin-bottom: 2px;
}
.toast-message {
font-size: 12px;
color: var(--text-tertiary);
line-height: 1.4;
}
.toast-close {
background: none;
border: none;
color: var(--text-dim);
cursor: pointer;
font-size: 14px;
padding: 0;
margin-left: auto;
flex-shrink: 0;
line-height: 1;
transition: color 0.15s;
}
.toast-close:hover { color: var(--text-tertiary); }
@keyframes toastIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* ==================== 对话框 ==================== */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.dialog-box {
background: linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
border: 2px solid var(--border-medium);
border-radius: 4px;
box-shadow: 0 0 0 1px var(--bg-dark), 0 8px 32px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.05);
min-width: 400px;
max-width: 500px;
animation: dialogSlideIn 0.2s ease;
}
.dialog-header {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px 12px;
border-bottom: 1px solid var(--border-light);
}
.dialog-icon {
font-size: 20px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-title {
font-size: 16px;
font-weight: 600;
color: var(--text-title);
margin: 0;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.dialog-body {
padding: 20px;
min-height: 60px;
}
.dialog-message {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin: 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 12px 20px 16px;
border-top: 1px solid var(--border-light);
}
.dialog-btn {
padding: 8px 20px;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
border: 1px solid;
transition: all 0.15s;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.dialog-btn-primary {
background: linear-gradient(180deg, #5a8f3e 0%, #4a7a32 100%);
border-color: #3d6a2a;
color: #fff;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 2px rgba(0, 0, 0, 0.3);
}
.dialog-btn-primary:hover {
background: linear-gradient(180deg, #6a9f4e 0%, #5a8a42 100%);
border-color: #4d7a3a;
}
.dialog-btn-primary:active {
background: linear-gradient(180deg, #4a7f32 0%, #3a6a22 100%);
transform: translateY(1px);
}
.dialog-btn-secondary {
background: linear-gradient(180deg, var(--bg-card-hover) 0%, var(--bg-card) 100%);
border-color: var(--border-medium);
color: var(--text-secondary);
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 1px 2px rgba(0, 0, 0, 0.3);
}
.dialog-btn-secondary:hover {
background: linear-gradient(180deg, var(--bg-input-hover) 0%, var(--bg-card-hover) 100%);
border-color: var(--border-medium);
color: var(--text-primary);
}
.dialog-btn-secondary:active {
background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-dark) 100%);
transform: translateY(1px);
}
@keyframes dialogSlideIn {
from { transform: scale(0.9); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* ==================== 漂浮下载通知 ==================== */
.float-notification {
position: fixed;
bottom: 20px;
right: 20px;
width: 320px;
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 150;
display: flex;
align-items: center;
gap: 12px;
}
.float-notification-icon {
flex-shrink: 0;
color: var(--accent-primary);
}
.float-notification-content {
flex: 1;
min-width: 0;
}
.float-notification-title {
font-size: 14px;
font-weight: 600;
color: var(--text-title);
margin-bottom: 4px;
}
.float-notification-text {
font-size: 12px;
color: var(--text-tertiary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.float-notification-progress {
flex-shrink: 0;
width: 100px;
display: flex;
flex-direction: column;
gap: 4px;
}
.float-progress-bar {
width: 100%;
height: 6px;
background: var(--bg-input);
border-radius: 3px;
overflow: hidden;
}
.float-progress-fill {
height: 100%;
background: var(--accent-primary);
width: 0%;
transition: width 0.3s ease;
}
.float-progress-text {
font-size: 11px;
color: var(--text-muted);
text-align: right;
}
.float-notification-close {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
color: var(--text-dim);
font-size: 18px;
cursor: pointer;
padding: 0;
line-height: 1;
}
.float-notification-close:hover {
color: var(--text-secondary);
}
/* ==================== 启动动画 ==================== */
.launch-animation-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(10, 10, 20, 0.95);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(8px);
}
body.light-theme .launch-animation-overlay {
background: rgba(240, 248, 255, 0.95);
}
.launch-animation-content {
text-align: center;
padding: 40px;
max-width: 600px;
}
.pickaxe-container {
display: flex;
align-items: center;
justify-content: center;
gap: 80px;
margin-bottom: 40px;
}
.pickaxe {
transform-origin: right center;
animation: pickaxeSwing 0.8s ease-in-out infinite;
}
.block-container {
position: relative;
}
.block {
animation: blockShake 0.4s ease-in-out infinite;
}
.particles {
position: absolute;
top: 50%;
left: 50%;
pointer-events: none;
}
@keyframes pickaxeSwing {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-20deg); }
50% { transform: rotate(0deg); }
75% { transform: rotate(20deg); }
}
@keyframes blockShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px); }
75% { transform: translateX(2px); }
}
.launch-status {
margin-bottom: 30px;
}
.status-text {
font-size: 18px;
color: #e8edf5;
margin-bottom: 16px;
}
body.light-theme .status-text {
color: #1a3a5c;
}
.progress-bar-container {
width: 100%;
max-width: 400px;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
margin: 0 auto;
overflow: hidden;
}
body.light-theme .progress-bar-container {
background: rgba(0, 100, 180, 0.1);
}
.progress-bar-container .progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #4fc3f7, #29b6f6);
width: 0%;
transition: width 0.3s ease;
border-radius: 4px;
}
body.light-theme .progress-bar-container .progress-bar-fill {
background: linear-gradient(90deg, #0099ff, #0077cc);
}
.version-info {
font-size: 13px;
color: #78909c;
margin-top: 12px;
}
body.light-theme .version-info {
color: #6a8aa6;
}
.launch-cancel-btn {
background: rgba(255, 255, 255, 0.08);
color: #b0bec5;
border: 1px solid rgba(255, 255, 255, 0.12);
padding: 10px 24px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.launch-cancel-btn:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.18);
}
body.light-theme .launch-cancel-btn {
background: rgba(0, 100, 180, 0.08);
color: #2d4a6f;
border: 1px solid rgba(0, 100, 180, 0.2);
}
body.light-theme .launch-cancel-btn:hover {
background: rgba(0, 100, 180, 0.12);
border-color: rgba(0, 100, 180, 0.3);
}
/* ==================== 启动界面的你知道吗?卡片 ==================== */
.launch-tip-card {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
padding: 14px 20px;
max-width: 480px;
width: calc(100% - 80px);
backdrop-filter: blur(4px);
animation: tipFadeIn 0.6s ease-out;
}
.launch-tip-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.launch-tip-icon {
font-size: 14px;
}
.launch-tip-label {
font-size: 12px;
font-weight: 600;
color: #90caf9;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.launch-tip-text {
font-size: 14px;
line-height: 1.6;
color: #b0bec5;
}
@keyframes tipFadeIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(10px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
#floating-download-wrapper {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 60;
}

BIN
resources/header.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
resources/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
resources/sidebar.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
resources/sidebar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

22
scripts/copy-static.js Normal file
View File

@ -0,0 +1,22 @@
/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
* Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
*/
const fs = require('fs');
const path = require('path');
const pkg = require('../package.json');
const output = {
name: pkg.name,
version: pkg.version,
main: pkg.main,
type: pkg.type
};
const destPath = path.resolve('build/app/package.json');
fs.writeFileSync(destPath, JSON.stringify(output, null, 2));
console.log('✅ copy-static 完成');

47
scripts/copy.js Normal file
View File

@ -0,0 +1,47 @@
/*
* CaelLab BY-SA Code License
* Copyright (c) 2026 Yunyun(云云) By 虚舟实验室(CaelLab) / CaelLabGameTS
* Source: https://git.caellab.com/yunyun/GoodPlanCraftLauncher
* Source: https://github.com/yunyun-3782/GoodPlanCraftLauncher
*/
const fs = require('fs');
const path = require('path');
const dest = path.resolve('build/app');
if (fs.existsSync(dest)) {
fs.rmSync(dest, { recursive: true, force: true });
}
fs.mkdirSync(path.join(dest, 'renderer'), { recursive: true });
const files = [
'main.js',
'preload.js',
'gpcl-icon.ico',
'package.json'
];
files.forEach(file => {
const src = path.resolve(file);
const target = path.join(dest, file);
if (fs.existsSync(src)) {
fs.copyFileSync(src, target);
}
});
const rendererSrc = path.resolve('renderer');
const rendererDest = path.join(dest, 'renderer');
if (fs.existsSync(rendererSrc)) {
fs.cpSync(rendererSrc, rendererDest, { recursive: true });
}
const iconSrc = path.resolve('static', 'icon');
const iconDest = path.join(dest, 'static', 'icon');
if (fs.existsSync(iconSrc)) {
fs.mkdirSync(path.join(dest, 'static'), { recursive: true });
fs.cpSync(iconSrc, iconDest, { recursive: true });
}
console.log('✅ copy-files 完成');

BIN
static/icon/GPCL.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
static/icon/caellab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
static/icon/gamets.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
static/icon/gamets.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB