const fs = require("fs"); const path = require("path"); const zlib = require("zlib"); const INPUT = "/Users/lanmu/Desktop/ai-chatbot-main/analysis/qiaodou-extract/app.arm64"; const OUTPUT_ROOT = "/Users/lanmu/Desktop/ai-chatbot-main/analysis/qiaodou-extract/extracted"; const MANIFEST = "/Users/lanmu/Desktop/ai-chatbot-main/analysis/qiaodou-extract/manifest.json"; const ASSET_REGEX = /\/assets\/[A-Za-z0-9 _.@/-]+\.(?:mjs|wasm|webp|png|svg|css|js|ico)(?=[^A-Za-z0-9_.@/-]|$)/g; const INDEX_HTML_REGEX = /\/index\.html/g; function collectOffsets(text, regex) { const hits = []; let match; while ((match = regex.exec(text)) !== null) { hits.push({ value: match[0], offset: match.index, }); } return hits; } function uniqueByOffset(items) { const seen = new Set(); return items.filter((item) => { if (seen.has(item.offset)) { return false; } seen.add(item.offset); return true; }); } function ensureDir(filePath) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); } function main() { const buffer = fs.readFileSync(INPUT); const text = buffer.toString("latin1"); const assets = uniqueByOffset(collectOffsets(text, ASSET_REGEX)).sort( (a, b) => a.offset - b.offset, ); const indexHtmlHits = uniqueByOffset(collectOffsets(text, INDEX_HTML_REGEX)).sort( (a, b) => a.offset - b.offset, ); if (!assets.length) { throw new Error("No embedded /assets/* entries were found."); } const nextBoundaries = [...assets.map((a) => a.offset), ...indexHtmlHits.map((a) => a.offset)] .sort((a, b) => a - b); const manifest = []; for (const asset of assets) { const contentStart = asset.offset + Buffer.byteLength(asset.value); const nextOffset = nextBoundaries.find((offset) => offset > asset.offset); if (!nextOffset) { manifest.push({ asset: asset.value, offset: asset.offset, status: "skipped_no_boundary", }); continue; } const compressed = buffer.subarray(contentStart, nextOffset); try { const decompressed = zlib.brotliDecompressSync(compressed); const outputPath = path.join(OUTPUT_ROOT, asset.value.replace(/^\//, "")); ensureDir(outputPath); fs.writeFileSync(outputPath, decompressed); manifest.push({ asset: asset.value, offset: asset.offset, nextOffset, compressedBytes: compressed.length, decompressedBytes: decompressed.length, outputPath, status: "ok", }); } catch (error) { manifest.push({ asset: asset.value, offset: asset.offset, nextOffset, compressedBytes: compressed.length, status: "decompress_failed", error: error instanceof Error ? error.message : String(error), }); } } fs.mkdirSync(OUTPUT_ROOT, { recursive: true }); fs.writeFileSync(MANIFEST, `${JSON.stringify(manifest, null, 2)}\n`); const ok = manifest.filter((item) => item.status === "ok").length; const failed = manifest.filter((item) => item.status !== "ok").length; console.log(`Extracted: ${ok}`); console.log(`Failed: ${failed}`); console.log(`Manifest: ${MANIFEST}`); } main();