diff --git a/floatUI.js b/floatUI.js index cbc82bfc..6ba11d1a 100644 --- a/floatUI.js +++ b/floatUI.js @@ -7665,7 +7665,7 @@ function algo_init() { click(convertCoords(clickSets.back)); find(string.support, parseInt(limit.timeout)); } - if (findID("questLinkList") || findID("questWrapTitle")) { + if (findID("questLinkList") || findID("questWrapTitle") || match(string.regex_event_branch)) { //助战选择失败后会点击返回 //这里不检测BATTLE文字是否出现,避免叫做“BATTLE”的恶搞玩家名干扰 state = STATE_MENU; diff --git a/gen.js b/gen.js index 4fa1e5b0..058413ae 100644 --- a/gen.js +++ b/gen.js @@ -2,11 +2,12 @@ const fs = require("fs"); const fsPromises = require("fs/promises"); const path = require("path"); const crypto = require("crypto"); +const util = require("util"); const rootpath = __dirname; const hashFuncName = 'sha256'; -const includeRules = [ +const inclusionRules = [ { dirname: ".", recursive: false, @@ -40,7 +41,7 @@ async function walkThrough(fullpath) { let relativepath = path.relative(rootpath, fullpath).replace(new RegExp('\\' + path.sep, 'g'), '/'); if ((await fsPromises.stat(fullpath)).isDirectory()) { - if (includeRules.find((rule) => { + if (inclusionRules.find((rule) => { if (rule.dirname === path.dirname(relativepath)) { return true; } else if (rule.recursive) { @@ -62,7 +63,7 @@ async function walkThrough(fullpath) { return result.length > 0 ? result : null; } } else { - if (includeRules.find((rule) => { + if (inclusionRules.find((rule) => { if (path.basename(relativepath).match(rule.filename)) { if (rule.dirname === path.dirname(relativepath)) { return true; @@ -92,7 +93,46 @@ const resultPath = path.join(resultDir, "updateList.json"); const projectJsonPath = path.join(rootpath, "project.json"); const projectUpdatedJsonPath = path.join(rootpath, "project-updated.json"); +const keyPairPath = path.join(rootpath, "..", "magireco_autoBattle_private"); +const publicKeyPath = path.join(keyPairPath, "publicKey.pem"); +const privateKeyPath = path.join(keyPairPath, "privateKey.pem"); +const signaturePath = path.join(resultDir, "updateList.json.sig.txt"); + +async function getPrivateKey() { + const yellow = '\x1b[33m\x1b[40m%s\x1b[0m'; + try { + return crypto.createPrivateKey({ + key: await fsPromises.readFile(privateKeyPath), + type: "pkcs8", + format: "pem", + });; + } catch (e) { + console.warn(yellow, `Cannot read existing private key from ${privateKeyPath}`); + console.log(e); + } + console.warn(yellow, "================================\nGENERATING NEW KEYPAIR..."); + const keypair = await util.promisify(crypto.generateKeyPair)("rsa", { + modulusLength: 2048, + }); + const publicKey = keypair.publicKey.export({ + type: "spki", + format: "pem", + }); + const privateKey = keypair.privateKey.export({ + type: "pkcs8", + format: "pem", + }); + await fsPromises.mkdir(keyPairPath, {recursive: true}); + await fsPromises.writeFile(publicKeyPath, publicKey); + console.warn("Written newly generated public key to "+publicKeyPath); + await fsPromises.writeFile(privateKeyPath, privateKey); + console.warn("Written newly generated private key to "+privateKeyPath); + console.warn(yellow, "NEW KEYPAIR GENERATED!\n================================"); + console.warn(publicKey.endsWith("\n") ? publicKey.slice(0, -1) : publicKey); + console.warn(yellow, "================================\nPLEASE REPLACE PUBLIC KEY USED IN THE CODE!\n================================"); + return keypair.privateKey; +} async function regenerate() { console.log("Regenerating update/updateList.json ..."); let projectJson = await fsPromises.readFile(projectJsonPath); @@ -103,6 +143,13 @@ async function regenerate() { fileRootNode: await walkThrough(rootpath), } if (result.fileRootNode == null) result = []; + const toBeSigned = JSON.stringify(result); + + const signer = crypto.createSign("RSA-SHA256"); + signer.update(toBeSigned); + signer.end(); + const signatureBase64 = signer.sign(await getPrivateKey(), "base64"); + try { let stat = await fsPromises.stat(resultDir); if (!stat.isDirectory()) { @@ -115,8 +162,10 @@ async function regenerate() { throw e; } } - await fsPromises.writeFile(resultPath, JSON.stringify(result)); - console.log("Written to "+resultPath); + await fsPromises.writeFile(resultPath, toBeSigned); + console.log("Result written to "+resultPath); + await fsPromises.writeFile(signaturePath, signatureBase64); + console.log("Signature written to "+signaturePath); return result; } regenerate(); @@ -185,6 +234,7 @@ const HTMLHead2 = +"\n " +"\n JSON data of all files (URLs and SRI hashes):
" +"\n application/json update/updateList.json
" ++"\n application/json update/updateList.json.sig.txt
" +"\n SRI-consistent downloaded data (click links below to show here):
" +"\n " +"\n Links of all files:
" @@ -267,7 +317,7 @@ function isTooFrequent() { const server = http.createServer(async (req, res) => { let relativepath = req.url.replace(/^\//, ""); let fullpath = path.resolve(relativepath); - let found = includeRules.find((rule) => { + let found = inclusionRules.find((rule) => { if (path.basename(relativepath).match(rule.filename)) { if (rule.dirname === path.dirname(relativepath)) { return true; @@ -304,7 +354,7 @@ const server = http.createServer(async (req, res) => { res.setHeader('Cache-control', 'no-cache'); console.log(`Serving JSON data`); res.end(JSON.stringify(await regenerate())); - } else if (found != null) { + } else if (found != null || "/update/updateList.json.sig.txt" === req.url) { res.setHeader('Cache-control', 'no-cache'); let servingfilepath = path.resolve(path.join(rootpath, relativepath)); if (path.relative(rootpath, servingfilepath).includes("..") || relativepath.includes(":") || relativepath.includes("$")) { diff --git a/main.js b/main.js index 1ea37134..852fc731 100644 --- a/main.js +++ b/main.js @@ -25,19 +25,62 @@ importClass(Packages.androidx.appcompat.content.res.AppCompatResources) // } //} -const updateListPath = files.join(files.join(files.cwd(), "update"), "updateList.json"); +const updateListPath = ["update", "updateList.json"].reduce((p, c) => files.join(p, c), files.cwd()); +const updateListSigPath = ["update", "updateList.json.sig.txt"].reduce((p, c) => files.join(p, c), files.cwd()); + +const knownPubKeyBase64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1NaWmmEr4JkbtG5TTp7F" + +"o0ZuXJy018JxO0nsKKGc+KfBVFai6EizRj2cff5VZC5mTX2UfmvRHktquCYk5ZYQ" + +"wiqmUzAmwvRuCa9gOvaEdxmGZ4Fe5W5LGvZS8AY6hzxB+o/RkVvEAJ4ud5v+9BRY" + +"PPFGDDC3BZaY9uUB7KxvBNPDQUkZJGcF7fnjaylRGoSzo+OH6qStJffbAuGbW/UD" + +"gH95VpqEK65kDaiHR8L5bT++T5I+sxaSJ1X7K/GklCCwAd3rIqJ5Kcabpea9I8vm" + +"HUosZ6gQyAweNgvZBdV7x+2BGKsMmLxtJWnqxXEwtyzuQLSU0nLWJIqfEsmOWQ+n" + +"OQIDAQAB"; + +function decodeBase64(encodedStr) { + return android.util.Base64.decode(encodedStr, android.util.Base64.DEFAULT); +} +function base64Encode(bytes) { + return android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP); +} + +function verifySignature(msgBase64, sigBase64, pubkeyBase64) { + if (typeof msgBase64 !== "string" || typeof sigBase64 !== "string" || typeof pubkeyBase64 !== "string") { + log("msgBase64/sigBase64/pubkeyBase64 must be string"); + return false; + } + try { + const msgBytes = decodeBase64(msgBase64); + const sigBytes = decodeBase64(sigBase64); + const pubKeySpec = new java.security.spec.X509EncodedKeySpec(decodeBase64(pubkeyBase64)); + const pubKey = java.security.KeyFactory.getInstance("RSA").generatePublic(pubKeySpec); + const verifier = java.security.Signature.getInstance("SHA256withRSA"); + verifier.initVerify(pubKey); + verifier.update(msgBytes); + return verifier.verify(sigBytes); + } catch (e) { + log(e); + return false; + } +} function readUpdateList() { log("读取文件数据列表..."); try { - if (files.exists(updateListPath) && files.isFile(updateListPath)) { - let fileContent = files.read(updateListPath); - let result = JSON.parse(fileContent); - log("已读取文件数据列表"); - return result; - } else { - log("文件数据列表文件不存在"); + if ([updateListPath, updateListSigPath].find((path) => !files.exists(path) || !files.isFile(path))) { + log("文件数据列表路径不存在"); + return; + } + let fileContent = files.read(updateListPath) + let fileBytes = new java.lang.String(fileContent).getBytes(); + let sigBase64 = files.read(updateListSigPath); + log("已读取文件数据列表,校验数字签名..."); + if (!verifySignature(base64Encode(fileBytes), sigBase64, knownPubKeyBase64)) { + log("文件数据列表数字签名校验失败"); + return; } + log("文件数据列表数字签名校验通过"); + let result = JSON.parse(fileContent); + return result; } catch (e) { log(e); log("读取文件数据列表时出错"); @@ -455,6 +498,9 @@ ui.emitter.on("create_options_menu", menu => { item = menu.add("检查文件数据"); item.setIcon(getTintDrawable("ic_find_in_page_black_48dp", colors.WHITE)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT); + item = menu.add("切换下载源"); + item.setIcon(getTintDrawable("ic_cloud_download_black_48dp", colors.WHITE)); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT); item = menu.add("魔纪百科"); item.setIcon(getTintDrawable("ic_book_black_48dp", colors.WHITE)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -628,6 +674,9 @@ ui.emitter.on("options_item_selected", (e, item) => { case "检查文件数据": threads.start(function () {checkAgainstUpdateListAndFix(true);}); break; + case "切换下载源": + chooseDownloadSource(); + break; case "魔纪百科": app.openUrl("https://magireco.moe/"); break; @@ -1085,27 +1134,37 @@ var latestVersionName = null; var refreshUpdateStatus = sync(function () { http.__okhttp__.setTimeout(5000); try { - let res = null; - try { - res = http.get("https://api.github.com/repos/icegreentee/magireco_autoBattle/releases/latest"); - } catch (e) { - res = null; - } - if (res == null || res.statusCode != 200) { - log("直接从GitHub检测更新失败"); - res = http.get("https://cdn.jsdelivr.net/gh/icegreentee/magireco_autoBattle@latest/project.json"); - } - if (res.statusCode != 200) { + const updateStatusURLs = isDevMode ? [urlBases.local+"/project.json"] : [ + "https://api.github.com/repos/magirecoautobattle/magireco_autoBattle/releases/latest", + urlBases.githubPages+"/project.json", + urlBases.cloudflarePages+"/project.json", + urlBases.jsdelivr+"@latest/project.json", + ]; + let resJson = null; + updateStatusURLs.find((url) => { + try { + log("尝试检测更新 URL=["+url+"] ..."); + res = http.get(url); + if (res.statusCode == 200) { + resJson = res.body.json(); + return true; + } + } catch (e) { + log(e); + resJson = null; + } + log("尝试检测更新 URL=["+url+"] 失败"); + }); + if (resJson == null) { setVersionMsgToastLog("更新信息获取失败 "+res.statusCode+" "+res.statusMessage, "#666666", true); } else { - let resJson = res.body.json(); latestVersionName = null; if (resJson.tag_name != null) { latestVersionName = resJson.tag_name; log("从GitHub获取最新版本号"+latestVersionName); } else if (resJson.versionName != null) { latestVersionName = resJson.versionName; - log("从JSDelivr获取最新版本号"+latestVersionName); + log("从其他途径获取最新版本号"+latestVersionName); } if (parseInt(latestVersionName.split(".").join("")) <= parseInt(version.split(".").join(""))) { setVersionMsgLog("当前无需更新", "#666666", false); @@ -1114,10 +1173,9 @@ var refreshUpdateStatus = sync(function () { } } } catch (e) { - setVersionMsgToastLog("获取更新信息时出错", "#666666", false); + log(e); + setVersionMsgToastLog("获取更新信息时出错", "#666666", true); } - //检查当前版本的文件数据 - checkAgainstUpdateListAndFix(); }); threads.start(function () { //绕开CwvqLU 9.1.0版上的奇怪假死问题,refreshUpdateStatus里的ui.run貌似必须等到对悬浮窗的Java反射操作完成后再进行,否则会假死 @@ -1126,19 +1184,55 @@ threads.start(function () { floatUI.floatyHangWorkaroundLock.lock(); floatUI.floatyHangWorkaroundLock.unlock(); refreshUpdateStatus(); + //检查当前版本的文件数据 + checkAgainstUpdateListAndFix(); }); //版本更新 -const jsdelivrURLBase = "https://cdn.jsdelivr.net/gh/icegreentee/magireco_autoBattle"; -const localURLBase = "http://127.0.0.1:9090" //用于调试,从本地gen.js开发服务器下载文件 +const urlBases = { + jsdelivr: "https://fastly.jsdelivr.net/gh/magirecoautobattle/magireco_autoBattle", + githubPages: "https://magirecoautobattle.github.io/magireco_autoBattle", + cloudflarePages: "https://magireco-autobattle.pages.dev", + local: "http://127.0.0.1:9090", //用于调试,从本地gen.js开发服务器下载文件 +} var isDevMode = false; //isDevMode = true; + +function getChosenDownloadSource() { + if (isDevMode) return "local"; + else return storage.get("chosenDownloadSource", "jsdelivr"); +} + function getDownloadURLBase(specifiedVersionName) { - if (specifiedVersionName == null) specifiedVersionName = "latest"; - if (isDevMode) return localURLBase; - else return jsdelivrURLBase+"@"+specifiedVersionName; + let chosen = getChosenDownloadSource(); + let urlBase = urlBases[chosen]; + if (urlBase == null) throw new Error("urlBases[chosen] is null"); + let suffix = ""; + if (chosen === "jsdelivr") { + suffix = "@"+(specifiedVersionName==null?"latest":specifiedVersionName); + } + return urlBase+suffix; +} + +function chooseDownloadSource() { + let oldSource = storage.get("chosenDownloadSource", "jsdelivr"); + let sources = []; + for (let sourceName in urlBases) { + if (sourceName === "local") continue; + if (oldSource === sourceName) sourceName += " [✓]"; + sources.push(sourceName); + } + dialogs.select("请选择新的下载源"+(isDevMode?"\nisDevMode==true 固定使用本地下载源":""), sources).then((i) => { + let newSource = sources[i]; + if (newSource == null) { + toastLog("未切换下载源,继续使用 ["+oldSource+"]"); + return; + } + storage.put("chosenDownloadSource", newSource); + toastLog("下载源已切换到 ["+newSource+"]"); + }); } var updateRestartPending = false; @@ -1154,6 +1248,67 @@ function restartApp() { engines.stopAll(); } +function walkThrough(data) { + let result = []; + function _recurse(data) { + if (Array.isArray(data)) { + data.forEach((item) => _recurse(item)); + } else { + result.push(data); + } + } + _recurse(data); + return result; +} + +function downloadAndVerifyEssentialFiles(versionName, extraFileNames) { + if (typeof versionName !== "string") throw new Error("versionName must be string"); + if (extraFileNames == null || !Array.isArray(extraFileNames)) throw new Error("extraFileNames must be array"); + const msgFileName = "update/updateList.json", sigFileName = "update/updateList.json.sig.txt"; + const fileNames = [msgFileName, sigFileName].concat(extraFileNames); + let downloaded = {}, expectedHashes = {}; + for (let i=0; i expectedHashes[item.src] = item.integrity); + break; + case sigFileName: + let msgBase64 = base64Encode(downloaded[msgFileName]); + let sigBase64 = "" + fileContent; + if (!verifySignature(msgBase64, sigBase64, knownPubKeyBase64)) + throw new Error("invalid signature"); + break; + default: + let actualFileHash = "sha256-"+$crypto.digest(fileBytes, "SHA-256", {input: "bytes", output: "base64"}); + if (expectedHashes[fileName] == null) + throw new Error("expected hash not found"); + if (actualFileHash !== expectedHashes[fileName]) + throw new Error("hash mismatch"); + } + //验证通过 + downloaded[fileName] = fileBytes; + } catch (e) { + log("文件 ["+fileName+"] 下载或验证失败"); + log(e); + break; + } + } + if (fileNames.find((fileName) => downloaded[fileName] == null)) { + log("有文件下载或验证失败"); + return null; + } + return downloaded; +} + var toUpdate = sync(function () { refreshUpdateStatus(); if (updateRestartPending) { @@ -1163,78 +1318,64 @@ var toUpdate = sync(function () { try { if (latestVersionName == null) { toastLog("无法获取最新版本号"); - } else { - log("isDevMode=["+isDevMode+"] 之前已经获取到最新版本号latestVersionName=["+latestVersionName+"]"); - if (isDevMode || parseInt(latestVersionName.split(".").join("")) > parseInt(version.split(".").join(""))) { - let essentialFileList = ["update/updateList.json", "main.js", "floatUI.js"]; - let responses = []; - essentialFileList.forEach((item) => { - let resp = null; - try { - resp = http.get(getDownloadURLBase(latestVersionName)+"/"+item); - } catch (e) { - resp = null; - } - responses.push({ - fileName: item, - httpResponse: resp, - }); - }); - if (responses.find((item) => item.httpResponse != null && item.httpResponse.statusCode != 200) == null) { - setVersionMsgLog("已下载必要文件,写入...", "#666666", true); - responses.forEach((item) => { - let writeToPath = files.join(files.cwd(), item.fileName); - let fileBytes = item.httpResponse.body.bytes(); - files.writeBytes(writeToPath, fileBytes); - }); - checkAgainstUpdateListAndFix(false, latestVersionName); - } else { - toast("有文件下载失败,请稍后重试"); - } - } else { - toastLog("无需更新"); - } + return; } - } catch (error) { + log("isDevMode=["+isDevMode+"] 之前已经获取到最新版本号latestVersionName=["+latestVersionName+"]"); + if (!isDevMode && parseInt(latestVersionName.split(".").join("")) <= parseInt(version.split(".").join(""))) { + toastLog("无需更新"); + //检查当前版本的文件数据 + checkAgainstUpdateListAndFix(); + return; + } + const extraFileNames = ["main.js", "floatUI.js"]; + let downloaded = downloadAndVerifyEssentialFiles(latestVersionName, extraFileNames); + if (downloaded == null) { + setVersionMsgToastLog("有文件下载或验证失败,请稍后重试"); + return; + } + setVersionMsgLog("已下载必要文件,写入...", "#666666", true); + for (let fileName in downloaded) { + let writeToPath = fileName.split("/").reduce((p, c) => files.join(p, c), files.cwd()); + files.ensureDir(writeToPath); + files.writeBytes(writeToPath, downloaded[fileName]); + } + restartApp(); //如果有文件缺失或损坏,重启后会处理 + } catch (e) { toastLog("更新过程出错"); + log(e); } finally { ui.run(function() {ui.swipe.setRefreshing(false);}); } }); //检查或下载文件数据 -function downloadUpdateListJSON(specifiedVersionName) { - try { - let updateListURL = getDownloadURLBase(specifiedVersionName)+"/update/updateList.json"; - log("正在下载文件数据列表 ["+updateListURL+"]"); - let resp = http.get(updateListURL); - if (resp.statusCode == 200) { - log("已下载文件数据列表"); - - let result = resp.body.string(); - - files.ensureDir(updateListPath); +function downloadAndWriteUpdateListJSON(specifiedVersionName) { + let downloaded = downloadAndVerifyEssentialFiles(specifiedVersionName, []); + if (downloaded == null) { + setVersionMsgToastLog("下载文件数据列表失败"); + return; + } - if (files.exists(updateListPath) && files.isEmptyDir(updateListPath)) { + try { + for (let fileName in downloaded) { + let result = downloaded[fileName]; + let writeToPath = fileName.split("/").reduce((p, c) => files.join(p, c), files.cwd()); + files.ensureDir(writeToPath); + if (files.exists(writeToPath) && files.isEmptyDir(writeToPath)) { log("为了写入文件数据列表而删除占位的空目录"); - files.remove(updateListPath); + files.remove(writeToPath); } - - if (!files.exists(updateListPath) || files.isFile(updateListPath)) { - files.write(updateListPath, result); - log("写入文件数据列表到文件"); - //只有下载并写入成功时,才会返回下载到的JSON字符串 - if (files.read(updateListPath) === result) return result; - else toastLog("文件数据列表内容不符,写入失败"); - } else { + if (files.exists(writeToPath) && !files.isFile(writeToPath)) { toastLog("可能被非空目录占位,无法写入文件数据列表到文件"); + return; } - } else { - setVersionMsgToastLog("下载文件数据列表失败\n"+resp.statusCode+" "+resp.statusMessage); + log("写入文件数据列表到文件"); + files.writeBytes(writeToPath, result); } + return downloaded["update/updateList.json"]; } catch (e) { log(e); - setVersionMsgToastLog("下载文件数据列表失败"); + setVersionMsgToastLog("写入文件数据列表失败"); } } @@ -1264,48 +1405,44 @@ function checkFile(fileName, fileHash) { return true; } -function findCorruptOrMissingFile(specifiedVersionName) { +function findCorruptOrMissingFile() { //从6.1.4开始修正在线更新认不清版本的bug - if (specifiedVersionName == null) specifiedVersionName = version; - if (parseInt(version.split(".").join("")) < parseInt("6.1.4".split(".").join(""))) { - specifiedVersionName = "6.1.4"; - log("版本低于6.1.4,先更新文件数据列表到6.1.4"); - if (downloadUpdateListJSON(specifiedVersionName) == null) { + //从6.2.0开始验证数字签名 + let specifiedVersionName = version; + if (parseInt(version.split(".").join("")) < parseInt("6.2.0".split(".").join(""))) { + specifiedVersionName = "6.2.0"; + log("版本低于6.2.0,先更新文件数据列表到6.2.0"); + if (downloadAndWriteUpdateListJSON(specifiedVersionName) == null) { //如果下载或写入不成功 - ui.post(function () {dialogs.alert("警告", "下载文件数据列表失败,无法检查文件数据,不能确保文件数据无误");}); - return false; + ui.post(function () {dialogs.alert("警告", + "下载文件数据列表失败,无法检查文件数据,不能确保文件数据无误。\n" + +"可以试试在右上角菜单中选择\"切换下载源\"。" + );}); + return; } } let updateListObj = readUpdateList(); if (updateListObj == null) { - downloadUpdateListJSON(specifiedVersionName); + downloadAndWriteUpdateListJSON(specifiedVersionName); updateListObj = readUpdateList(); } if (updateListObj == null) { toastLog("无法读取或下载文件数据列表"); - return false; + return; } if (updateListObj.versionName == null) { toastLog("文件数据列表缺失版本号"); - return false; + return; } if (updateListObj.fileRootNode == null || !Array.isArray(updateListObj.fileRootNode) || updateListObj.fileRootNode.length == 0) { toastLog("文件数据列表不含有效文件信息"); - return false; + return; } - let updateList = []; - function walkThrough(data) { - if (Array.isArray(data)) { - data.forEach((item) => walkThrough(item)); - } else { - updateList.push(data); - } - } - walkThrough(updateListObj.fileRootNode); + let updateList = walkThrough(updateListObj.fileRootNode); let corruptOrMissingFileList = []; updateList.forEach((item) => { @@ -1420,12 +1557,20 @@ var fixFiles = sync(function (corruptOrMissingFileList, specifiedVersionName) { return true; }); -var checkAgainstUpdateListAndFix = sync(function (showResult, specifiedVersionName) { - let ret = findCorruptOrMissingFile(specifiedVersionName); +var checkAgainstUpdateListAndFix = sync(function (showResult) { + let ret = findCorruptOrMissingFile(); + if (ret == null) { + toastLog("检查文件时出错"); + return; + } let corruptOrMissingFileList = ret.corruptOrMissingFileList; let specifiedVersionName = ret.versionName; if (Array.isArray(corruptOrMissingFileList)) { if (corruptOrMissingFileList.length > 0) { + if (specifiedVersionName !== latestVersionName && getChosenDownloadSource() !== "jsdelivr") { + dialogs.alert("发现文件缺失或损坏", "请确保网络通畅,然后下拉更新到最新版"); + return; //只有jsdelivr支持指定版本号下载 + } function promptRepair(textMsg) {ui.post(function () { dialogs.confirm( "发现文件缺失或损坏", @@ -1435,6 +1580,7 @@ var checkAgainstUpdateListAndFix = sync(function (showResult, specifiedVersionNa threads.start(function () { if (!fixFiles(corruptOrMissingFileList, specifiedVersionName)) { promptRepair("下载或写入文件失败。要重试么?\n" + +"也可以试试先点击\"取消\"再在右上角菜单中选择\"切换下载源\"。\n" +"一共有"+corruptOrMissingFileList.length+"个文件需要修复,\n" +"其中有"+corruptOrMissingFileList.filter((item) => item.dataBytes == null).length+"个文件需要重新下载。"); } diff --git a/project.json b/project.json index 947d83e9..d39c3766 100644 --- a/project.json +++ b/project.json @@ -5,7 +5,7 @@ "build" ], "packageName": "top.momoe.auto", - "versionName": "6.1.9", + "versionName": "6.2.0", "versionInfo": "", "versionCode": 1, "icon":"./images/icon.png", diff --git a/update/updateList.json b/update/updateList.json index f9a345c9..2b891fe7 100644 --- a/update/updateList.json +++ b/update/updateList.json @@ -1 +1 @@ -{"packageName":"top.momoe.auto","versionName":"6.1.9","fileRootNode":[[{"src":"bin/scrcap2bmp-arm","integrity":"sha256-EPmaVWp++E7F9AgvYOm9LJlPt516LG44El752+rlEJk="},{"src":"bin/scrcap2bmp-arm64","integrity":"sha256-eVXMCpt1I99OnClUHHp34D0EXXw9enmPafrvIXWXKmg="},{"src":"bin/scrcap2bmp-x86","integrity":"sha256-mXCcEoSnrvQBjKZACQiSICQp3uVq9VMSxs6NbQnET+g="},{"src":"bin/scrcap2bmp-x86_64","integrity":"sha256-7CkgvLdJdYL1z/KB3XnN+rHimb4IJbWgJK85M0xokPg="},{"src":"bin/scrcap2bmp.c","integrity":"sha256-nR6KCZHaA1mS5N+teR517IsC6PTbanq9WIEejKwH+hk="}],{"src":"floatUI.js","integrity":"sha256-nwsDWpGho/bt4U3nTYz/ASGfUpfWMitNPTFAaqGk26I="},{"src":"gen.js","integrity":"sha256-PGL+/4EnYI9vMmy1uJr9Nouw0Ah+bY0fqiQ5uqPu4R4="},[{"src":"images/accel.png","integrity":"sha256-UlrQwrRe8dOBxiMk2NDcFxeG2LSI4bG3nMgaPt8zbKQ="},{"src":"images/blast.png","integrity":"sha256-nmYrh9JgPImqNtp3UgP9fo5LmZMKtd4Tel1w/4dpefU="},{"src":"images/charge.png","integrity":"sha256-BmTzqEMGLoFPrJKMd0s+vR7fVZqJ79a4u02vvlLe6XE="},{"src":"images/connectIndicator.png","integrity":"sha256-aJPx9ulXXUZIBDfM9Z5vhctfN7NlcxBsvw1azTBcdzU="},{"src":"images/connectIndicatorBtnDown.png","integrity":"sha256-U3gXx2AasIjmjZDmk/l2tFjl+t5U29EaTVA0gbKRR+o="},{"src":"images/dark.png","integrity":"sha256-wz4LbTyP/DyyYkyfHa3jkcPEga59h/MMRClFxGwyTIE="},{"src":"images/darkBtnDown.png","integrity":"sha256-lu3FuVUDdZKN6Bt5R+wgj+B/KgaJvSvIgBmdzaExTTQ="},{"src":"images/doppel.png","integrity":"sha256-UfHzavfCcMrYsBlrblN84DsdkRaqIxAzn0GsJJTl3Q0="},{"src":"images/fire.png","integrity":"sha256-yzm1z5MCw5B5+emWjIcnH+MJSO9r7n0FPNCbYFM8Two="},{"src":"images/fireBtnDown.png","integrity":"sha256-fGrOcH9LMSZRkkSRFiia9ixdKvQdTJ2QTB2wG+5VXgQ="},{"src":"images/icon.png","integrity":"sha256-nCS4XyLVff1lOyqflpX9mzKzyWTXgHyn+99ShSWYp2s="},{"src":"images/light.png","integrity":"sha256-y5kbLsEw4i1hHj79FRM4rYr8/cI2APMZwRCKfz8g0nI="},{"src":"images/lightBtnDown.png","integrity":"sha256-QnG5sE/fnwPJC2ZsR6rxOQP1ntMMclI3tx80PlTq+Xo="},{"src":"images/magia.png","integrity":"sha256-+QTCSTSq0j9GTwUBmvUc9wbg3zyCjj/mXPVzhHuAp2I="},{"src":"images/mirrorsLose.png","integrity":"sha256-d8h2EWUc3o29ZPv5g6xT6zj8rU3SY3SNFw+bKHCSd5I="},{"src":"images/mirrorsWinLetterI.png","integrity":"sha256-WHubrQ5sgWJYfg96s+JS2PjoUSiXvh56Zm5VpsnQVMQ="},{"src":"images/none.png","integrity":"sha256-IK8U+DkPSBV7MSx5W++j1EgVSFmWexoYf51ipBWsL1g="},{"src":"images/noneBtnDown.png","integrity":"sha256-brVs5j2DsDi02kndGiosXVTFeAhZ96vhoHxY3Diy4SU="},{"src":"images/OKButton.png","integrity":"sha256-KFuIDf/NSJ9udU26WP8NTXjnommz76tuPIc0cfnQ9hw="},{"src":"images/OKButtonGray.png","integrity":"sha256-PeGWvERvcXqCHXpAH/PiDHgZkTgqxizrjbATWZ/Qbco="},{"src":"images/qb.png","integrity":"sha256-8j2wlTdvM0alP+BSFpna+tlFyaqGhwT4yhh7alqy+9w="},{"src":"images/skillEmptyCHS.png","integrity":"sha256-JAptr6ngZeVGE400WHStdUEtUCiT8Gwnil1WmRXTlSI="},{"src":"images/skillEmptyCHT.png","integrity":"sha256-zYKfj2CiuqhPg3hncyEFczqQ+cqExFmnbGE9EUHk0wU="},{"src":"images/skillEmptyJP.png","integrity":"sha256-iupPfmSYlfBO6+L8v1VaLlSUsqhZf0UzCoJgM/PfzqE="},{"src":"images/skillLocked.png","integrity":"sha256-3IDe9rTa1ph8L0NE2wLgWjQ/lO51wj157nqZt0nNs+U="},{"src":"images/start.jpg","integrity":"sha256-J5brNdm2qmrbdgUN7XcI9UcvBt+9MuGdob1ZU9HN8FY="},{"src":"images/water.png","integrity":"sha256-IZHLtWilwZubGYwwIJ5JCc8UAMBzE9E+PqKqN0Mwvp8="},{"src":"images/waterBtnDown.png","integrity":"sha256-16JL5HtrREbiEIfuUME4ozmHo9wB8wlP0JJNxmxqgAY="},{"src":"images/wood.png","integrity":"sha256-CueYQz07Rb8HYWxYO/b30+ux8OeMIHbSXf4cPpZel9M="},{"src":"images/woodBtnDown.png","integrity":"sha256-CqJWlGHeBZn62bCc7KR9d7RPbjIyAzW4tOh1kLzcxFs="}],{"src":"main.js","integrity":"sha256-u/7Z0veSBdgX6NPIIq1+cM1MGC20zoxTHX38nDPN+Qs="},{"src":"project.json","integrity":"sha256-JLAoMTu2tjkuCpX681wZ3CMxVwrDpihT1o7vIRR81rM="}]} \ No newline at end of file +{"packageName":"top.momoe.auto","versionName":"6.2.0","fileRootNode":[[{"src":"bin/scrcap2bmp-arm","integrity":"sha256-EPmaVWp++E7F9AgvYOm9LJlPt516LG44El752+rlEJk="},{"src":"bin/scrcap2bmp-arm64","integrity":"sha256-eVXMCpt1I99OnClUHHp34D0EXXw9enmPafrvIXWXKmg="},{"src":"bin/scrcap2bmp-x86","integrity":"sha256-mXCcEoSnrvQBjKZACQiSICQp3uVq9VMSxs6NbQnET+g="},{"src":"bin/scrcap2bmp-x86_64","integrity":"sha256-7CkgvLdJdYL1z/KB3XnN+rHimb4IJbWgJK85M0xokPg="},{"src":"bin/scrcap2bmp.c","integrity":"sha256-nR6KCZHaA1mS5N+teR517IsC6PTbanq9WIEejKwH+hk="}],{"src":"floatUI.js","integrity":"sha256-2ijJz0MlcnKlOkkrD4pOdH4fDvQnGgpXokFlbT50Mc8="},{"src":"gen.js","integrity":"sha256-Qi/jmYvCvwzjM7o2DhBt3OIdGwgUDi5pCwiSjwHhTlI="},[{"src":"images/accel.png","integrity":"sha256-UlrQwrRe8dOBxiMk2NDcFxeG2LSI4bG3nMgaPt8zbKQ="},{"src":"images/blast.png","integrity":"sha256-nmYrh9JgPImqNtp3UgP9fo5LmZMKtd4Tel1w/4dpefU="},{"src":"images/charge.png","integrity":"sha256-BmTzqEMGLoFPrJKMd0s+vR7fVZqJ79a4u02vvlLe6XE="},{"src":"images/connectIndicator.png","integrity":"sha256-aJPx9ulXXUZIBDfM9Z5vhctfN7NlcxBsvw1azTBcdzU="},{"src":"images/connectIndicatorBtnDown.png","integrity":"sha256-U3gXx2AasIjmjZDmk/l2tFjl+t5U29EaTVA0gbKRR+o="},{"src":"images/dark.png","integrity":"sha256-wz4LbTyP/DyyYkyfHa3jkcPEga59h/MMRClFxGwyTIE="},{"src":"images/darkBtnDown.png","integrity":"sha256-lu3FuVUDdZKN6Bt5R+wgj+B/KgaJvSvIgBmdzaExTTQ="},{"src":"images/doppel.png","integrity":"sha256-UfHzavfCcMrYsBlrblN84DsdkRaqIxAzn0GsJJTl3Q0="},{"src":"images/fire.png","integrity":"sha256-yzm1z5MCw5B5+emWjIcnH+MJSO9r7n0FPNCbYFM8Two="},{"src":"images/fireBtnDown.png","integrity":"sha256-fGrOcH9LMSZRkkSRFiia9ixdKvQdTJ2QTB2wG+5VXgQ="},{"src":"images/icon.png","integrity":"sha256-nCS4XyLVff1lOyqflpX9mzKzyWTXgHyn+99ShSWYp2s="},{"src":"images/light.png","integrity":"sha256-y5kbLsEw4i1hHj79FRM4rYr8/cI2APMZwRCKfz8g0nI="},{"src":"images/lightBtnDown.png","integrity":"sha256-QnG5sE/fnwPJC2ZsR6rxOQP1ntMMclI3tx80PlTq+Xo="},{"src":"images/magia.png","integrity":"sha256-+QTCSTSq0j9GTwUBmvUc9wbg3zyCjj/mXPVzhHuAp2I="},{"src":"images/mirrorsLose.png","integrity":"sha256-d8h2EWUc3o29ZPv5g6xT6zj8rU3SY3SNFw+bKHCSd5I="},{"src":"images/mirrorsWinLetterI.png","integrity":"sha256-WHubrQ5sgWJYfg96s+JS2PjoUSiXvh56Zm5VpsnQVMQ="},{"src":"images/none.png","integrity":"sha256-IK8U+DkPSBV7MSx5W++j1EgVSFmWexoYf51ipBWsL1g="},{"src":"images/noneBtnDown.png","integrity":"sha256-brVs5j2DsDi02kndGiosXVTFeAhZ96vhoHxY3Diy4SU="},{"src":"images/OKButton.png","integrity":"sha256-KFuIDf/NSJ9udU26WP8NTXjnommz76tuPIc0cfnQ9hw="},{"src":"images/OKButtonGray.png","integrity":"sha256-PeGWvERvcXqCHXpAH/PiDHgZkTgqxizrjbATWZ/Qbco="},{"src":"images/qb.png","integrity":"sha256-8j2wlTdvM0alP+BSFpna+tlFyaqGhwT4yhh7alqy+9w="},{"src":"images/skillEmptyCHS.png","integrity":"sha256-JAptr6ngZeVGE400WHStdUEtUCiT8Gwnil1WmRXTlSI="},{"src":"images/skillEmptyCHT.png","integrity":"sha256-zYKfj2CiuqhPg3hncyEFczqQ+cqExFmnbGE9EUHk0wU="},{"src":"images/skillEmptyJP.png","integrity":"sha256-iupPfmSYlfBO6+L8v1VaLlSUsqhZf0UzCoJgM/PfzqE="},{"src":"images/skillLocked.png","integrity":"sha256-3IDe9rTa1ph8L0NE2wLgWjQ/lO51wj157nqZt0nNs+U="},{"src":"images/start.jpg","integrity":"sha256-J5brNdm2qmrbdgUN7XcI9UcvBt+9MuGdob1ZU9HN8FY="},{"src":"images/water.png","integrity":"sha256-IZHLtWilwZubGYwwIJ5JCc8UAMBzE9E+PqKqN0Mwvp8="},{"src":"images/waterBtnDown.png","integrity":"sha256-16JL5HtrREbiEIfuUME4ozmHo9wB8wlP0JJNxmxqgAY="},{"src":"images/wood.png","integrity":"sha256-CueYQz07Rb8HYWxYO/b30+ux8OeMIHbSXf4cPpZel9M="},{"src":"images/woodBtnDown.png","integrity":"sha256-CqJWlGHeBZn62bCc7KR9d7RPbjIyAzW4tOh1kLzcxFs="}],{"src":"main.js","integrity":"sha256-JJ0MmezmwcmONUOIjRubO8D72M6/c9/EUIjA5THzb8s="},{"src":"project.json","integrity":"sha256-kgAwi3ChrjpbzJQMZ2/C+i005/Hpf/FduhzZLfp2abM="}]} \ No newline at end of file diff --git a/update/updateList.json.sig.txt b/update/updateList.json.sig.txt new file mode 100644 index 00000000..f5d73709 --- /dev/null +++ b/update/updateList.json.sig.txt @@ -0,0 +1 @@ +UH2oweppm26J+yJOr/bKBKBI3dQeGVEZQU9QtL1SkZtA0uvTD9pHUuvLmSrQZRvmaLqIzibKjDRQ210AaX3fqW4JA6e5DBE5XWTJodX7PmgpV+z2GfrivXhb+MrZ7t7dQ2lxDlMz/+KHx6vB5y7LUnNvP0hSBt2N7NhhHTbi89Il2ttIoMznAigIE78RFgcC14ifmDleZUVM6gsQG2mhcXvpaGR6cpGQUJ6eYI8sMSLBds12OCBLRh+95N+kBAIWFPCPMNwfpghtOJ33zYHYDci7IR0k1EWB+z0/5E+Lmk90kwSogLPQ85lLyUBoZFryzxV8TC8+sTa7JGU5Vjqx2g== \ No newline at end of file