From 420f35a5e4973664a62ddd1093448c4b81f0ec4f Mon Sep 17 00:00:00 2001 From: Minoricew <154642983+Minoricew@users.noreply.github.com> Date: Tue, 16 Dec 2025 02:04:17 +0800 Subject: [PATCH] =?UTF-8?q?[=E2=9C=A8=20Feat]=20Impl=20file-based=20IPC=20?= =?UTF-8?q?for=20Aikari=20installation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- .../init/main/ipcModules/aikariIpcHandler.js | 367 ++++++++++++------ .../behaviourCtrl/aikariStatus.js | 6 +- 3 files changed, 247 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 62c7cee..e53ac83 100755 --- a/README.md +++ b/README.md @@ -9,11 +9,12 @@
- +
+

> [!TIP] diff --git a/src/aura/init/main/ipcModules/aikariIpcHandler.js b/src/aura/init/main/ipcModules/aikariIpcHandler.js index bf19d73..5890052 100644 --- a/src/aura/init/main/ipcModules/aikariIpcHandler.js +++ b/src/aura/init/main/ipcModules/aikariIpcHandler.js @@ -68,149 +68,166 @@ const functions = { * @param {"stable" | "alpha"} channel * @param {(arg: DownloadTask) => any} callbackFn * @param {string} binPath + * @param {string} logPath + * @param {string} progressFilePath */ - handleAikariDlAndInstall: async (channel, callbackFn, binPath) => { - // TODO: Channel selection - const apiInfo = global.__HUGO_AURA_API__; + handleAikariDlAndInstall: async ( + channel, + callbackFn, + binPath, + logPath, + progressFilePath + ) => { + let dlResult = false; + if (fs.existsSync(path.join(path.dirname(binPath), ".force"))) { + dlResult = true; + } else { + // TODO: Channel selection + const apiInfo = global.__HUGO_AURA_API__; - const getVerPromise = new Promise(async (resolveGetVerReq) => { - // ↓ 目前 channel param 没有什么用处 - for (const apiDomain of apiInfo.domains) { - const reqPromise = new Promise((resolveHttpRequest) => { - nodeHttps - .get( - `${apiDomain}${apiInfo.aikariUpdate}?channel=${channel}`, - (rep) => { - let dataChunk = ""; - rep.on("data", (chunk) => { - dataChunk += chunk; - }); + const getVerPromise = new Promise(async (resolveGetVerReq) => { + // ↓ 目前 channel param 没有什么用处 + for (const apiDomain of apiInfo.domains) { + const reqPromise = new Promise((resolveHttpRequest) => { + nodeHttps + .get( + `${apiDomain}${apiInfo.aikariUpdate}?channel=${channel}`, + (rep) => { + let dataChunk = ""; + rep.on("data", (chunk) => { + dataChunk += chunk; + }); - rep.on("end", () => { - let parsedData = {}; - try { - parsedData = JSON.parse(dataChunk); - } catch (e) { - callbackFn({ - id: "", - progress: 0, - status: "struggling", - dlUrl: null, - savePath: null, - message: `数据解析失败, 正在尝试 API 域名 ${ - apiInfo.domains[apiInfo.domains.indexOf(apiDomain) + 1] - } ...`, - errorObj: e, - }); - - setTimeout(() => { - resolveHttpRequest({ - success: false, + rep.on("end", () => { + let parsedData = {}; + try { + parsedData = JSON.parse(dataChunk); + } catch (e) { + callbackFn({ + id: "", + progress: 0, + status: "struggling", + dlUrl: null, + savePath: null, + message: `数据解析失败, 正在尝试 API 域名 ${ + apiInfo.domains[ + apiInfo.domains.indexOf(apiDomain) + 1 + ] + } ...`, errorObj: e, }); - }, 1000); + + setTimeout(() => { + resolveHttpRequest({ + success: false, + errorObj: e, + }); + }, 1000); + return; + } + + resolveHttpRequest({ + success: true, + data: parsedData, + }); return; - } - - resolveHttpRequest({ - success: true, - data: parsedData, }); - return; - }); - } - ) - .on("error", (e) => { - callbackFn({ - id: "", - progress: 0, - status: "struggling", - dlUrl: null, - savePath: null, - message: `连接失败, 正在尝试 API 域名 ${ - apiInfo.domains[apiInfo.domains.indexOf(apiDomain) + 1] - } ...`, - errorObj: e, - }); - - setTimeout(() => { - resolveHttpRequest({ - success: false, + } + ) + .on("error", (e) => { + callbackFn({ + id: "", + progress: 0, + status: "struggling", + dlUrl: null, + savePath: null, + message: `连接失败, 正在尝试 API 域名 ${ + apiInfo.domains[apiInfo.domains.indexOf(apiDomain) + 1] + } ...`, errorObj: e, }); - }, 1000); - }); - }); - const requestResult = await reqPromise; - if (requestResult.success) { - resolveGetVerReq({ - success: true, - data: requestResult.data, + setTimeout(() => { + resolveHttpRequest({ + success: false, + errorObj: e, + }); + }, 1000); + }); }); - break; - } else { - continue; + + const requestResult = await reqPromise; + if (requestResult.success) { + resolveGetVerReq({ + success: true, + data: requestResult.data, + }); + break; + } else { + continue; + } } + + resolveGetVerReq({ + success: false, + data: null, + }); + }); + + const rawResInfo = await getVerPromise; + if (!rawResInfo.success) { + callbackFn({ + id: "", + progress: 0, + status: "failed", + dlUrl: null, + savePath: null, + message: + "未能获取 Aikari 版本信息, 所有 API 域名均无法连接, 建议前往 GitHub 下载安装包并自行安装", + }); + return false; } - resolveGetVerReq({ - success: false, - data: null, - }); - }); + const aikariVersionInfo = rawResInfo.data; - const rawResInfo = await getVerPromise; - if (!rawResInfo.success) { - callbackFn({ - id: "", - progress: 0, - status: "failed", - dlUrl: null, - savePath: null, - message: - "未能获取 Aikari 版本信息, 所有 API 域名均无法连接, 建议前往 GitHub 下载安装包并自行安装", - }); - return false; - } + let deviceArch = process.env.PROCESSOR_ARCHITEW6432 + ? process.env.PROCESSOR_ARCHITEW6432 + : process.env.PROCESSOR_ARCHITECTURE; + // @ts-expect-error + deviceArch = deviceArch.toLowerCase(); - const aikariVersionInfo = rawResInfo.data; + if ( + !Object.keys(aikariVersionInfo.data.downloadUrl).includes(deviceArch) + ) { + callbackFn({ + id: "", + progress: 0, + status: "failed", + dlUrl: null, + savePath: null, + message: `不支持的处理器架构, 检测到的架构: "${deviceArch}"`, + }); + return false; + } - let deviceArch = process.env.PROCESSOR_ARCHITEW6432 - ? process.env.PROCESSOR_ARCHITEW6432 - : process.env.PROCESSOR_ARCHITECTURE; - // @ts-expect-error - deviceArch = deviceArch.toLowerCase(); + const downloadFilePromise = new Promise((resolve) => { + fsComposables.downloadFile( + aikariVersionInfo.data.downloadUrl[deviceArch], + binPath, + (...args) => { + if (args[0].status === "done") { + resolve(true); + } else if (args[0].status === "failed") { + resolve(false); + } - if (!Object.keys(aikariVersionInfo.data.downloadUrl).includes(deviceArch)) { - callbackFn({ - id: "", - progress: 0, - status: "failed", - dlUrl: null, - savePath: null, - message: `不支持的处理器架构, 检测到的架构: "${deviceArch}"`, - }); - return false; - } - - const downloadFilePromise = new Promise((resolve) => { - fsComposables.downloadFile( - aikariVersionInfo.data.downloadUrl[deviceArch], - binPath, - (...args) => { - if (args[0].status === "done") { - resolve(true); - } else if (args[0].status === "failed") { - resolve(false); + callbackFn(...args); } + ); + }); - callbackFn(...args); - } - ); - }); - - const dlResult = await downloadFilePromise; + dlResult = await downloadFilePromise; + } if (dlResult) { callbackFn({ @@ -224,7 +241,7 @@ const functions = { const runInstPromise = new Promise((resolve) => { exec( - `"${binPath}" /VERYSILENT /SUPPRESSMSGBOXES /LOG="${binPath}\\..\\Aikari-Install.log"`, + `"${binPath}" /VERYSILENT /SUPPRESSMSGBOXES /LOG="${logPath}" /PROGRESS_FILE="${progressFilePath}"`, (err, stdout, stderr) => { if (err) { console.error( @@ -237,6 +254,13 @@ const functions = { ); }); + if (!fs.existsSync(path.dirname(progressFilePath))) { + fs.mkdirSync(path.dirname(progressFilePath)); + } + if (!fs.existsSync(progressFilePath)) { + fs.writeFileSync(progressFilePath, ""); + } + callbackFn({ id: "INSTALL_STAGE", progress: 15, @@ -246,8 +270,87 @@ const functions = { message: "正在安装 Aikari...", }); + let instFilesStageLastFakeLoadProgressPercent = -1; + + const fileProgressWatcher = fs.watch(progressFilePath, (eventType) => { + if (eventType === "change") { + try { + const content = fs.readFileSync(progressFilePath, "utf-8").trim(); + if (content === "") { + callbackFn({ + id: "INSTALL_STAGE", + progress: 20, + status: "progressing", + dlUrl: null, + savePath: null, + message: "正在准备安装...", + }); + } else { + const contentArr = content.split(";"); + switch (contentArr[0]) { + case "DL_VC_REDIST": + callbackFn({ + id: "INSTALL_STAGE", + progress: + 20 + + Math.round((parseFloat(contentArr[1]) / 10) * 2), // 20 + [0, 20] === 20 ~ 40 + status: "progressing", + dlUrl: null, + savePath: null, + message: `正在下载 VC++ 运行时... (${contentArr[1]}%)`, + }); + break; + + case "INST_FILES": + if (instFilesStageLastFakeLoadProgressPercent === -1) { + instFilesStageLastFakeLoadProgressPercent = 60; + } else if (instFilesStageLastFakeLoadProgressPercent < 85) { + instFilesStageLastFakeLoadProgressPercent += + Math.random() * (5 - 1) + 1; + } + callbackFn({ + id: "INSTALL_STAGE", + progress: instFilesStageLastFakeLoadProgressPercent, + status: "progressing", + dlUrl: null, + savePath: null, + message: `正在解压文件...`, + }); + break; + + case "INST_VC_REDIST": + callbackFn({ + id: "INSTALL_STAGE", + progress: 90, + status: "progressing", + dlUrl: null, + savePath: null, + message: `正在安装 VC++ 运行时...`, + }); + break; + + case "DONE": + callbackFn({ + id: "INSTALL_STAGE", + progress: 99, + status: "progressing", + dlUrl: null, + savePath: null, + message: `即将完成`, + }); + break; + } + } + } catch (e) { + // Ignore + } + } + }); + const instResult = await runInstPromise; + fileProgressWatcher.close(); + callbackFn({ id: "INSTALL_STAGE", progress: 100, @@ -267,6 +370,8 @@ const functions = { } fs.unlinkSync(binPath); + fs.unlinkSync(logPath); + fs.unlinkSync(progressFilePath); } }, clearHostFileItem: async () => { @@ -341,6 +446,14 @@ const applyAikariIpcHandler = (ipcMain) => { AIKARI_TEMP_DL_DIR, "Aikari-Installer.exe" ); + const AIKARI_INSTALLER_LOG_FILENAME = path.join( + AIKARI_TEMP_DL_DIR, + "install.log" + ); + const AIKARI_INSTALLER_PROGRESS_FIPC_FILENAME = path.join( + AIKARI_TEMP_DL_DIR, + "PROGRESS" + ); // Prev PLS Cfg const OLD_PLS_INSTALL_DIR = path.join( @@ -694,6 +807,8 @@ const applyAikariIpcHandler = (ipcMain) => { async (_evt, arg) => { if (fs.existsSync(AIKARI_TEMP_DL_DIR)) { try { + fs.unlinkSync(AIKARI_INSTALLER_LOG_FILENAME); + fs.unlinkSync(AIKARI_INSTALLER_PROGRESS_FIPC_FILENAME); fs.unlinkSync(AIKARI_TEMP_DL_DIR); } catch (err) { console.error(err); @@ -725,7 +840,9 @@ const applyAikariIpcHandler = (ipcMain) => { status ); }, - AIKARI_TEMP_INSTALLER_FILENAME + AIKARI_TEMP_INSTALLER_FILENAME, + AIKARI_INSTALLER_LOG_FILENAME, + AIKARI_INSTALLER_PROGRESS_FIPC_FILENAME ); } ); diff --git a/src/aura/ui/pages/configSubPages/behaviourCtrl/aikariStatus.js b/src/aura/ui/pages/configSubPages/behaviourCtrl/aikariStatus.js index 7e793ae..e38132c 100644 --- a/src/aura/ui/pages/configSubPages/behaviourCtrl/aikariStatus.js +++ b/src/aura/ui/pages/configSubPages/behaviourCtrl/aikariStatus.js @@ -31,8 +31,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) getRetryStatusDescByErrId: (errIdString) => { switch (errIdString) { case "E_AUTH_TOKEN_GET_FAILED": - return `

Aikari 注册表访问失败, 这是一个极为罕见的问题

-

请检查 HKEY_USERS\\.DEFAULT 是否存在, 并反馈至 GitHub Issues

`; + return `

Aikari 注册表访问失败, 这可能代表 Aikari 在启动后立即发生了崩溃

+

请将此情况反馈至 GitHub Issues, 并附上您的系统版本信息

`; case "E_WS_CONN_FAILED_AFT_MULTIPLE_TRIES": return `

在多次尝试连接后仍然失败, 请检查服务是否已启动

`; case "E_IS_LOADING": @@ -921,7 +921,7 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig) } else { GLOBAL_FUNCTIONS.updatePBarStatus( info.progress, - `正在安装 Aikari...`, + info.message, "normal", false );