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