Prepare scanner branch for merge
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
function argValue(name, fallback = "") {
|
||||
const prefix = `--${name}=`;
|
||||
const match = process.argv.find((entry) => entry.startsWith(prefix));
|
||||
return match ? match.slice(prefix.length) : fallback;
|
||||
}
|
||||
|
||||
function hasFlag(name) {
|
||||
return process.argv.includes(`--${name}`);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function parseWaitSeconds(value) {
|
||||
if (value === "") return 0;
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed < 0) {
|
||||
throw new Error("--wait must be a non-negative integer number of seconds.");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function expectedSignature() {
|
||||
const mainPath = path.join(process.cwd(), "electron", "main.ts");
|
||||
const source = fs.readFileSync(mainPath, "utf8");
|
||||
const match = source.match(/APP_RUNTIME_SIGNATURE\s*=\s*"([^"]+)"/);
|
||||
return match?.[1] || "";
|
||||
}
|
||||
|
||||
async function fetchJson(baseUrl, endpoint) {
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(`${baseUrl}${endpoint}`);
|
||||
} catch (error) {
|
||||
throw new Error(`Could not reach ${baseUrl}${endpoint}. Start the elevated app with npm run dev:admin and confirm UAC before running live scans. (${error instanceof Error ? error.message : String(error)})`);
|
||||
}
|
||||
const text = await response.text();
|
||||
let payload;
|
||||
try {
|
||||
payload = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
throw new Error(`${endpoint} returned non-JSON response (${response.status}).`);
|
||||
}
|
||||
if (!response.ok) throw new Error(`${endpoint} returned ${response.status}: ${JSON.stringify(payload)}`);
|
||||
return payload;
|
||||
}
|
||||
|
||||
function validatePreflight({ health, status, expected, requireElevated = true, requireGenshin = true }) {
|
||||
const errors = [];
|
||||
const appBuild = health?.appBuild;
|
||||
const runtime = status?.status?.runtimeInfo;
|
||||
|
||||
if (!appBuild?.signature) {
|
||||
errors.push("/health is missing appBuild.signature.");
|
||||
} else if (expected && appBuild.signature !== expected) {
|
||||
errors.push(`Runtime signature '${appBuild.signature}' does not match source '${expected}'.`);
|
||||
}
|
||||
|
||||
if (!status?.status) errors.push("/scanner/status is missing status payload.");
|
||||
if (!runtime) {
|
||||
errors.push("/scanner/status is missing runtimeInfo. Open the scanner view and restart the elevated app if needed.");
|
||||
} else {
|
||||
if (requireElevated && runtime.isElevated !== true) errors.push("Runtime is not elevated.");
|
||||
if (requireGenshin && runtime.genshinFound !== true) errors.push("Genshin process/window was not found.");
|
||||
}
|
||||
|
||||
return {
|
||||
ok: errors.length === 0,
|
||||
errors,
|
||||
signature: appBuild?.signature || "",
|
||||
expectedSignature: expected || "",
|
||||
isElevated: runtime?.isElevated,
|
||||
genshinFound: runtime?.genshinFound,
|
||||
targetProcess: runtime?.targetProcess || "",
|
||||
foregroundProcess: runtime?.foregroundProcess || "",
|
||||
};
|
||||
}
|
||||
|
||||
function formatSummary(result) {
|
||||
const lines = [
|
||||
`live preflight: ${result.ok ? "PASS" : "FAIL"}`,
|
||||
`signature: ${result.signature || "missing"}`,
|
||||
`expected: ${result.expectedSignature || "unknown"}`,
|
||||
`elevated: ${result.isElevated === true ? "yes" : result.isElevated === false ? "no" : "unknown"}`,
|
||||
`genshin: ${result.genshinFound === true ? "yes" : result.genshinFound === false ? "no" : "unknown"}`,
|
||||
`target: ${result.targetProcess || "unknown"}`,
|
||||
`foreground: ${result.foregroundProcess || "unknown"}`,
|
||||
];
|
||||
if (!result.ok) {
|
||||
lines.push("errors:");
|
||||
for (const error of result.errors) lines.push(`- ${error}`);
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
async function runPreflight({ baseUrl, expected, requireElevated, requireGenshin }) {
|
||||
const health = await fetchJson(baseUrl, "/health");
|
||||
const status = await fetchJson(baseUrl, "/scanner/status");
|
||||
return validatePreflight({ health, status, expected, requireElevated, requireGenshin });
|
||||
}
|
||||
|
||||
async function waitForPreflight(options, waitSeconds) {
|
||||
const deadline = Date.now() + waitSeconds * 1000;
|
||||
let lastError = null;
|
||||
let lastResult = null;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const result = await runPreflight(options);
|
||||
lastResult = result;
|
||||
if (result.ok || Date.now() >= deadline) return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (Date.now() >= deadline) throw lastError;
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const baseUrl = argValue("base-url", "http://127.0.0.1:17317").replace(/\/$/, "");
|
||||
const expected = argValue("expected-signature", expectedSignature());
|
||||
const requireElevated = !hasFlag("allow-standard");
|
||||
const requireGenshin = !hasFlag("allow-missing-genshin");
|
||||
const waitSeconds = parseWaitSeconds(argValue("wait", ""));
|
||||
const options = { baseUrl, expected, requireElevated, requireGenshin };
|
||||
const result = waitSeconds > 0 ? await waitForPreflight(options, waitSeconds) : await runPreflight(options);
|
||||
console.log(hasFlag("json") ? JSON.stringify(result, null, 2) : formatSummary(result));
|
||||
if (!result.ok) process.exit(1);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatSummary,
|
||||
parseWaitSeconds,
|
||||
runPreflight,
|
||||
validatePreflight,
|
||||
};
|
||||
Reference in New Issue
Block a user