wedecode
Version:
微信小程序源代码还原工具, 线上代码安全审计
1,585 lines (1,584 loc) • 99.9 kB
JavaScript
import fs from "node:fs";
import path from "node:path";
import process$1 from "node:process";
import { stdout } from "single-line-log";
import JS from "js-beautify";
import os from "node:os";
import axios from "axios";
import { deepmerge } from "@biggerstar/deepmerge";
import colors from "picocolors";
import { VM } from "vm2";
import { JSDOM } from "jsdom";
import { glob } from "glob";
import * as cheerio from "cheerio";
import cssbeautify from "cssbeautify";
import esprima from "esprima";
import escodegen from "escodegen";
import crypto from "node:crypto";
const isWindows = /^win/.test(process.platform);
const isMac = /^darwin/.test(process.platform);
const cssBodyToPageReg = /body\s*\{/g;
const PUBLIC_OUTPUT_PATH = "OUTPUT";
const pluginDirRename = ["__plugin__", "plugin_"];
const removeAppFileList = [
// 'app-config.json',
"page-frame.html",
"app-wxss.js",
"app-service.js",
"index.appservice.js",
"index.webview.js",
"appservice.app.js",
"page-frame.js",
"webview.app.js",
"common.app.js"
// 'plugin.json',
];
const removeGameFileList = [
// 'app-config.json',
// 'game.js',
"subContext.js",
"worker.js"
];
const appJsonExcludeKeys = [
"navigateToMiniProgramAppIdList"
];
const GameJsonExcludeKeys = [
"openDataContext"
];
const macGlob = [
// 版本3+
"/Users/*/Library/Containers/*/Data/.wxapplet/packages",
// 版本4.0+
"/Users/*/Library/Containers/*/Data/Documents/app_data/radium/Applet/packages",
// 版本4.1.7+ (Arm架构)
"/Users/*/Library/Containers/*/Data/Documents/app_data/radium/users/*/applet/packages"
];
const winGlob = [
// 版本3+
"C:/Users/*/weixin/WeChat Files",
"C:/Users/*/Documents/WeChat Files/Applet",
// 版本4.0+
"C:/Users/*/Documents/xwechat_files",
"C:/Users/*/AppData/Roaming/*/xwechat/radium/Applet/packages",
// 版本4.1.7+
"C:/Users/*/AppData/Roaming/*/xwechat/radium/users",
// 安装到其他盘
"D:/WeChat Files/Applet",
"E:/WeChat Files/Applet",
"F:/WeChat Files/Applet"
];
const linuxGlob = [
"/home/*/.config/WeChat/Applet"
];
function getPlatformGlob() {
const platform = os.platform();
switch (platform) {
case "win32":
return winGlob;
case "darwin":
return macGlob;
case "linux":
return linuxGlob;
default:
return [];
}
}
const globPathList = getPlatformGlob();
const AppMainPackageNames = ["__APP__.wxapkg", "app.wxapkg"];
var CacheClearEnum = /* @__PURE__ */ ((CacheClearEnum2) => {
CacheClearEnum2["clear"] = "清空";
CacheClearEnum2["notClear"] = "不清空";
return CacheClearEnum2;
})(CacheClearEnum || {});
var OperationModeEnum = /* @__PURE__ */ ((OperationModeEnum2) => {
OperationModeEnum2["autoScan"] = "▶ 自动扫描小程序包";
OperationModeEnum2["manualScan"] = "▶ 手动设定扫描目录";
OperationModeEnum2["manualDir"] = "▶ 直接指定包路径( 非扫描 )";
return OperationModeEnum2;
})(OperationModeEnum || {});
var StreamPathDefaultEnum = /* @__PURE__ */ ((StreamPathDefaultEnum2) => {
StreamPathDefaultEnum2["inputPath"] = "./";
StreamPathDefaultEnum2[StreamPathDefaultEnum2["publicOutputPath"] = PUBLIC_OUTPUT_PATH] = "publicOutputPath";
StreamPathDefaultEnum2["defaultOutputPath"] = "default";
return StreamPathDefaultEnum2;
})(StreamPathDefaultEnum || {});
var YesOrNoEnum = /* @__PURE__ */ ((YesOrNoEnum2) => {
YesOrNoEnum2["yes"] = "是";
YesOrNoEnum2["no"] = "否";
return YesOrNoEnum2;
})(YesOrNoEnum || {});
const isDev = process$1.env.DEV === "true";
function getPathResolveInfo(outputDir) {
let _packRootPath = outputDir;
const resolve = (_new_resolve_path = "./", ...args) => {
return path.resolve(outputDir, _packRootPath, _new_resolve_path, ...args);
};
const outputResolve = (_new_resolve_path = "./", ...args) => {
return path.resolve(outputDir, _new_resolve_path, ...args);
};
return {
/** 相对当前包( 子包, 主包, 插件包都算当前路径 )作为根路径路径进行解析 */
resolve,
/** 相对当前主包路径进行解析 */
outputResolve,
outputPath: outputDir,
join(_path) {
return path.join(_packRootPath, _path);
},
setPackRootPath(rootPath) {
_packRootPath = rootPath;
},
/**
* 当前的包根路径, 主包为 ./ , 分包为相对主包根的相对路径
* */
get packRootPath() {
return _packRootPath === outputDir ? "./" : _packRootPath;
},
get appJsonPath() {
return resolve("app.json");
},
get appConfigJsonPath() {
return resolve("app-config.json");
},
get projectPrivateConfigJsonPath() {
return resolve("project.private.config.json");
},
get appWxssPath() {
return resolve("app-wxss.js");
},
get workersPath() {
return resolve("workers.js");
},
get pageFramePath() {
return resolve("page-frame.js");
},
get pageFrameHtmlPath() {
return resolve("page-frame.html");
},
get appJsPath() {
return resolve("app.js");
},
get appServicePath() {
return resolve("app-service.js");
},
get appServiceAppPath() {
return resolve("appservice.app.js");
},
get gameJsonPath() {
return resolve("game.json");
},
get gameJsPath() {
return resolve("game.js");
},
get subContextJsPath() {
return resolve("subContext.js");
}
};
}
function jsBeautify(code) {
return JS.js_beautify(code, { indent_size: 2 });
}
function traverseDOMTree(parentElement, astVNode, callback) {
if (!astVNode)
return;
const newElement = callback(parentElement, astVNode);
if (!newElement)
return;
const VNodeChildren = Array.from(astVNode.children).filter(Boolean);
if (!VNodeChildren.length)
return;
for (let i = 0; i < VNodeChildren.length; i++) {
traverseDOMTree(newElement, VNodeChildren[i], callback);
}
}
function clearScreen() {
process$1.stdout.write(process$1.platform === "win32" ? "\x1Bc" : "\x1B[2J\x1B[3J\x1B[H");
}
function limitPush(arr, data, limit = 10) {
if (arr.length - 1 > limit)
arr.shift();
arr.push(data);
}
const openStreamLog = false;
const isChildProcess = process$1.argv.some((arg) => arg.includes("decompilation-cli.js") || arg.includes("decompilation-controller.js")) || process$1.env.WEDECODE_CHILD_PROCESS === "true";
const excludesLogMatch = isDev && !isChildProcess ? [
"Completed"
] : [];
function printLog(log, opt = {}) {
if (excludesLogMatch.some((item) => log.includes(item)))
return;
if (isChildProcess || !openStreamLog) {
console.log(log);
return;
}
if (!log || !log.trim())
return;
if (opt.interceptor)
printLog["interceptor"] = opt.interceptor;
if (opt.space1)
printLog["space1"] = opt.space1;
if (opt.space2)
printLog["space2"] = opt.space2;
if (opt.nativeOnly)
printLog["nativeOnly"] = opt.nativeOnly;
if (!printLog["middleLogList"])
printLog["middleLogList"] = [];
if (!printLog["startLogList"])
printLog["startLogList"] = [];
if (!printLog["endLogList"])
printLog["endLogList"] = [];
if (typeof printLog["interceptor"] === "function" && printLog["interceptor"](log) === false) {
return;
}
if (printLog["nativeOnly"]) {
console.log.call(console, log);
return;
}
if (opt.isStart) {
limitPush(printLog["startLogList"], log, opt.starLimit || 20);
} else if (opt.isEnd) {
limitPush(printLog["endLogList"], log, opt.middleLimit || 6);
} else {
limitPush(printLog["middleLogList"], log, opt.endLimit || 20);
}
log = printLog["startLogList"].join("\n") + (printLog["space1"] || "") + printLog["middleLogList"].join("\n") + (printLog["space2"] || "") + printLog["endLogList"].join("\n");
clearScreen();
stdout(log);
}
function removeElement(arr, elementToRemove) {
const index = arr.indexOf(elementToRemove);
if (index > -1) {
arr.splice(index, 1);
}
}
function commonDir(pathA, pathB) {
if (pathA[0] === ".")
pathA = pathA.slice(1);
if (pathB[0] === ".")
pathB = pathB.slice(1);
pathA = pathA.replace(/\\/g, "/");
pathB = pathB.replace(/\\/g, "/");
let a = Math.min(pathA.length, pathB.length);
for (let i = 1, m = Math.min(pathA.length, pathB.length); i <= m; i++)
if (!pathA.startsWith(pathB.slice(0, i))) {
a = i - 1;
break;
}
let pub = pathB.slice(0, a);
let len = pub.lastIndexOf("/") + 1;
return pathA.slice(0, len);
}
function findCommonRoot(paths) {
const splitPaths = paths.map((path2) => path2.split("/").filter(Boolean));
const commonRoot = [];
for (let i = 0; i < splitPaths[0].length; i++) {
const partsMatch = splitPaths.every((path2) => path2[i] === splitPaths[0][i]);
if (partsMatch) {
commonRoot.push(splitPaths[0][i]);
} else {
break;
}
}
return commonRoot.join("/");
}
function replaceExt(name, ext = "") {
const hasSuffix = name.lastIndexOf(".") > 2;
return hasSuffix ? name.slice(0, name.lastIndexOf(".")) + ext : `${name}${ext}`;
}
function sleep(time) {
return new Promise((resolve1) => setTimeout(resolve1, time));
}
function arrayDeduplication(arr, cb) {
return arr.reduce((pre, cur) => {
const res = cb ? cb(pre, cur) : void 0;
const isRes = typeof res === "boolean";
isRes ? res && pre.push(cur) : !pre.includes(cur) && pre.push(cur);
return pre;
}, []);
}
function removeVM2ExceptionLine(code) {
const reg = /\s*[a-z]\x20?=\x20?VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL\.handleException\([a-z]\);?/g;
return code.replace(reg, "");
}
function resetWxsRequirePath(p, resetString = "") {
return p.replaceAll("p_./", resetString).replaceAll("m_./", resetString);
}
function isPluginPath(path2) {
return path2.startsWith("plugin-private://") || path2.startsWith("plugin://");
}
function resetPluginPath(_path, prefixDir) {
return path.join(
prefixDir || "./",
_path.replaceAll("plugin-private://", "").replaceAll("plugin://", "")
);
}
function getParameterNames(fn) {
if (typeof fn !== "function")
return [];
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const code = fn.toString().replace(COMMENTS, "");
const result = code.slice(code.indexOf("(") + 1, code.indexOf(")")).match(/([^\s,]+)/g);
return result === null ? [] : result;
}
function isWxAppid(str) {
const reg = /^wx[0-9a-f]{16}$/i;
str = str.trim();
return str.length === 18 && reg.test(str);
}
function readLocalFile(path2, encoding = "utf-8") {
return fs.existsSync(path2) ? fs.readFileSync(path2, encoding) : "";
}
function readLocalJsonFile(path2, encoding = "utf-8") {
try {
return JSON.parse(readLocalFile(path2, encoding));
} catch (e) {
return null;
}
}
function readFileUntilContainContent(pathList, encoding = "utf-8") {
for (const filePath of pathList) {
if (fs.existsSync(filePath)) {
const data = fs.readFileSync(filePath, encoding);
if (data.length) {
return {
found: true,
data,
path: filePath
};
}
}
}
return {
found: false,
data: "",
path: ""
};
}
function saveLocalFile(filepath, data, opt = {}) {
filepath = filepath.replace(pluginDirRename[0], pluginDirRename[1]);
const targetData = fs.existsSync(filepath) ? fs.readFileSync(filepath, { encoding: "utf-8" }).trim() : "";
let force = typeof opt.force === "boolean" ? opt.force : opt.emptyInstead || !targetData.length;
const outputDirPath = path.dirname(filepath);
const isExistsFile = fs.existsSync(filepath);
const isExistsPath = fs.existsSync(outputDirPath);
if (isExistsFile && !force)
return false;
if (!isExistsPath) {
fs.mkdirSync(outputDirPath, { recursive: true });
}
fs.writeFileSync(filepath, data);
return true;
}
function deleteLocalFile(path2, opt = {}) {
try {
fs.rmSync(path2, opt);
} catch (e) {
if (!opt.catch)
throw e;
}
}
class WxAppInfoUtils {
/**
* 从远程API获取小程序信息
* @param appid 小程序的appid
* @returns 小程序信息或空对象
*/
static async getWxAppInfo(appid) {
try {
printLog(`🔍 正在获取小程序信息: ${appid}`, { isEnd: true });
const response = await axios.post("https://kainy.cn/api/weapp/info/", {
appid
}, {
timeout: 1e4,
// 10秒超时
headers: {
"Content-Type": "application/json"
}
});
if (response.data && response.data.code === 0 && response.data.data && response.data.data.nickname) {
const appInfo = response.data.data;
printLog(`✅ 成功获取小程序信息: ${appInfo.nickname}`, { isEnd: true });
return appInfo;
} else {
printLog(`⚠️ 获取到的小程序信息为空或格式不正确`, { isEnd: true });
return {};
}
} catch (error) {
printLog(`❌ 获取小程序信息失败: ${error.message}`, { isEnd: true });
if (error.response) {
printLog(`📄 API错误响应: ${JSON.stringify(error.response.data)}`, { isEnd: true });
}
return {};
}
}
/**
* 更新工作区的小程序信息
* @param workspaceId 工作区ID
* @param appInfo 小程序信息
* @param serverPort 服务器端口,默认3000
*/
static async updateWorkspaceAppInfo(workspaceId, appInfo, serverPort = 3e3) {
try {
console.log(`正在更新工作区 ${workspaceId} 的小程序信息`);
const response = await axios.put(
`http://localhost:${serverPort}/api/workspaces/${workspaceId}/appinfo`,
{ appInfo },
// 包装在对象中
{
timeout: 5e3,
headers: {
"Content-Type": "application/json"
}
}
);
if (response.status === 200) {
console.log("工作区小程序信息更新成功");
} else {
console.error("工作区小程序信息更新失败:", response.statusText);
}
} catch (error) {
console.error("更新工作区小程序信息时发生错误:", error);
}
}
/**
* 获取并更新小程序信息的完整流程
* @param workspaceId 工作区ID
* @param appid 小程序appid
* @param outputPath 输出路径,用于提取本地信息(已废弃,不再使用)
* @param serverPort 服务器端口,默认3000
*/
static async fetchAndUpdateAppInfo(workspaceId, appid, outputPath, serverPort = 3e3) {
const appInfo = await this.getWxAppInfo(appid);
if (appInfo && Object.keys(appInfo).length > 0) {
await this.updateWorkspaceAppInfo(workspaceId, appInfo, serverPort);
}
}
/**
* 从app.json文件中提取appid
* @param appJsonPath app.json文件路径
* @returns appid或null
*/
static extractAppIdFromAppJson(appJsonPath) {
try {
if (!fs.existsSync(appJsonPath)) {
return null;
}
const appJsonContent = readLocalFile(appJsonPath);
if (!appJsonContent) {
return null;
}
const appJson = JSON.parse(appJsonContent);
return appJson.appid || null;
} catch (error) {
console.warn("解析app.json失败:", error.message);
return null;
}
}
/**
* 从app-config.json文件中提取appid
* @param appConfigPath app-config.json文件路径
* @returns appid或null
*/
static extractAppIdFromConfig(appConfigPath) {
try {
if (!fs.existsSync(appConfigPath)) {
return null;
}
const appConfigContent = readLocalFile(appConfigPath);
if (!appConfigContent) {
return null;
}
const appConfig = JSON.parse(appConfigContent);
return appConfig.appid || appConfig.extAppid || null;
} catch (error) {
console.warn("解析app-config.json失败:", error.message);
return null;
}
}
/**
* 从多个来源提取小程序ID(只从app.json中提取)
* @param outputPath 输出路径
* @returns appid或null
*/
static extractAppIdFromMultipleSources(outputPath) {
const appJsonPath = path.join(outputPath, "app.json");
let appId = this.extractAppIdFromAppJson(appJsonPath);
if (appId) {
printLog(`📱 从app.json中找到appid: ${appId}`, { isEnd: true });
return appId;
}
return null;
}
/**
* 尝试从反编译结果中获取并更新小程序信息
* @param workspaceId 工作区ID
* @param packInfo 反编译包信息
* @param serverPort 服务器端口,默认3000
* @param wxid 用户提供的微信小程序ID,优先使用
*/
static async tryGetAndUpdateAppInfoFromPack(workspaceId, packInfo, serverPort = 3e3, wxid) {
var _a;
if (!workspaceId)
return;
try {
let appid = null;
if (wxid && wxid.trim()) {
appid = wxid.trim();
} else {
const outputPath = ((_a = packInfo.pathInfo) == null ? void 0 : _a.outputPath) || packInfo.outputPath;
appid = this.extractAppIdFromMultipleSources(outputPath);
if (appid) {
printLog(`🔍 从反编译文件中发现小程序ID: ${appid},正在获取详细信息...`, { isEnd: true });
}
}
if (appid) {
await this.fetchAndUpdateAppInfo(workspaceId, appid, void 0, serverPort);
} else {
printLog(`⚠️ 未能找到小程序ID,跳过信息获取`, { isEnd: true });
}
} catch (error) {
console.warn("获取小程序信息失败:", error.message);
}
}
}
class ProjectConfigUtils {
/**
* 生成小程序的项目配置文件
* @param outputPath 输出路径
*/
static async generateProjectConfigFiles(outputPath) {
const projectPrivateConfigJsonPath = path.join(outputPath, "project.private.config.json");
const DEV_defaultConfigData = {
"setting": {
"ignoreDevUnusedFiles": false,
"ignoreUploadUnusedFiles": false
}
};
const defaultConfigData = {
"setting": {
"es6": false,
"urlCheck": false
}
};
if (isDev) {
Object.assign(defaultConfigData.setting, DEV_defaultConfigData.setting);
}
let finallyConfig = {};
const projectPrivateConfigString = readLocalFile(projectPrivateConfigJsonPath);
if (projectPrivateConfigString) {
const projectPrivateConfigData = JSON.parse(projectPrivateConfigString);
deepmerge(projectPrivateConfigData, defaultConfigData);
finallyConfig = projectPrivateConfigData;
} else {
finallyConfig = defaultConfigData;
}
saveLocalFile(projectPrivateConfigJsonPath, JSON.stringify(finallyConfig, null, 2), { force: true });
printLog(` ▶ 生成项目配置文件成功.
`, { isStart: true });
}
}
const proxyableSymbol = Symbol("proxyable");
const systemInfo = {
windowWidth: 375,
windowHeight: 600,
pixelRatio: 2,
language: "en",
version: "1.9.90",
platform: "ios"
};
function makeProxiesAble(defaultValue) {
const knownProps = Object.keys(defaultValue);
defaultValue[proxyableSymbol] = true;
return new Proxy(defaultValue, {
get(target, prop) {
const value = target[prop];
if (typeof value === "function") {
return value.bind(target);
}
if ((value == null ? void 0 : value[proxyableSymbol]) || typeof value === "symbol" || typeof prop === "symbol") {
return value;
}
if (!knownProps.includes(prop)) {
console.log("获取到未知属性: 请反馈到 wedecode 项目中, 谢谢! ", prop);
}
return function() {
return makeProxiesAble({});
};
},
set(target, prop, value) {
target[prop] = value;
return true;
}
});
}
const WX_API = {
request() {
},
getExtConfig() {
},
getExtConfigSync() {
},
postMessageToReferrerPage: function() {
},
postMessageToReferrerMiniProgram: function() {
},
onUnhandledRejection: function() {
},
onThemeChange: function() {
},
onPageNotFound: function() {
},
onLazyLoadError: function() {
},
onError: function() {
},
onAudioInterruptionEnd: function() {
},
onAudioInterruptionBegin: function() {
},
onAppShow: function() {
},
onAppHide: function() {
},
offUnhandledRejection: function() {
},
offThemeChange: function() {
},
offPageNotFound: function() {
},
offLazyLoadError: function() {
},
offError: function() {
},
offAudioInterruptionEnd: function() {
},
offAudioInterruptionBegin: function() {
},
offAppShow: function() {
},
offAppHide: function() {
},
getStorageSync: function() {
},
setStorageSync: function() {
},
getStorage: function() {
},
setStorage: function() {
},
getSystemInfo() {
return systemInfo;
},
getSystemInfoSync() {
return systemInfo;
},
getRealtimeLogManager() {
return {
log: (msg) => console.log(msg),
err: (msg) => console.error(msg)
};
},
getMenuButtonBoundingClientRect() {
return {
top: 0,
right: 0,
bottom: 0,
left: 0,
width: 0,
height: 0
};
}
};
function createWxFakeDom() {
return makeProxiesAble({
console,
setTimeout,
setInterval,
clearTimeout,
clearInterval,
__wxConfig: {},
App() {
},
Component() {
},
Page() {
},
getApp: () => ({}),
require: () => void 0,
module: {},
exports: {},
global: {},
Behavior: function() {
},
getCurrentPages: () => [],
requireMiniProgram: function() {
},
$gwx: () => void 0,
WXWebAssembly: {},
__wxCodeSpace__: {},
wx: makeProxiesAble(WX_API),
// 4.x support
__appServiceSDK__: makeProxiesAble({
loadSubpackage: () => void 0
}),
__subContextEngine__: makeProxiesAble({
injectEntryFile: () => void 0,
loadJsFiles: () => void 0
})
});
}
function createVM(vmOptions = {}) {
const domBaseHtml = `<!DOCTYPE html><html lang="en"><head><title>''</title></head><body></body></html>`;
const dom = new JSDOM(domBaseHtml);
const vm_window = dom.window;
const vm_navigator = dom.window.navigator;
const vm_document = dom.window.document;
const __wxAppCode__ = {};
const fakeGlobal = {
__wxAppCode__,
publishDomainComponents: () => void 0
};
Object.assign(vm_window, fakeGlobal);
return new VM(deepmerge({
sandbox: {
...createWxFakeDom(),
setInterval: () => null,
setTimeout: () => null,
console: {
...console,
// 在 vm 执行的时候,对于小程序源码中的 info, log, warn 打印直接忽略
log: () => void 0,
warn: () => void 0,
info: () => void 0
},
window: vm_window,
location: dom.window.location,
navigator: vm_navigator,
document: vm_document,
define: () => void 0,
require: () => void 0,
requirePlugin: () => void 0,
global: {
__wcc_version__: "v0.5vv_20211229_syb_scopedata"
},
System: {
register: () => void 0
},
__vd_version_info__: {},
__wxAppCode__,
__wxCodeSpace__: {
setRuntimeGlobals: () => void 0,
addComponentStaticConfig: () => void 0,
setStyleScope: () => void 0,
enableCodeChunk: () => void 0,
initializeCodeChunk: () => void 0,
addTemplateDependencies: () => void 0,
batchAddCompiledScripts: () => void 0,
batchAddCompiledTemplate: () => void 0
}
}
}, vmOptions));
}
function runVmCode(vm, code) {
try {
vm.run(code);
} catch (e) {
console.error(e.message);
}
}
class PloyFillCover {
allPloyFills = [];
constructor(packPath) {
const customHeaderPathPart = path.resolve(path.dirname(packPath), "polyfill");
const customPloyfillGlobMatch = path.resolve(customHeaderPathPart, "./**/*.js");
const customPloyfill = glob.globSync(customPloyfillGlobMatch);
const customPloyfillInfo = customPloyfill.map((str) => {
return { fullPath: str, ployfillPath: path.relative(customHeaderPathPart, str) };
});
const urls = new URL(import.meta.url);
const headerPathPart = path.resolve(path.dirname(urls.pathname), "polyfill");
const ployfillGlobMatch = path.resolve(headerPathPart, "./**/*.js");
let builtinPloyfill = glob.globSync(ployfillGlobMatch);
const builtinPloyfillInfo = builtinPloyfill.map((str) => {
return { fullPath: str, ployfillPath: path.relative(headerPathPart, str) };
});
this.allPloyFills = [...customPloyfillInfo, ...builtinPloyfillInfo];
}
findPloyfill(targetPath) {
return this.allPloyFills.find((item) => {
return targetPath.endsWith(item.ployfillPath);
});
}
}
var PackTypeMapping = /* @__PURE__ */ ((PackTypeMapping2) => {
PackTypeMapping2["main"] = "主包";
PackTypeMapping2["sub"] = "分包";
PackTypeMapping2["independent"] = "独立分包";
return PackTypeMapping2;
})(PackTypeMapping || {});
var AppTypeMapping = /* @__PURE__ */ ((AppTypeMapping2) => {
AppTypeMapping2["app"] = "小程序";
AppTypeMapping2["game"] = "小游戏";
return AppTypeMapping2;
})(AppTypeMapping || {});
class BaseDecompilation {
pathInfo;
outputPathInfo;
packPath;
packType;
appType;
ployFill;
constructor(packInfo) {
this.pathInfo = packInfo.pathInfo;
this.outputPathInfo = packInfo.outputPathInfo;
this.packPath = packInfo.inputPath;
this.packType = packInfo.packType;
this.appType = packInfo.appType;
this.ployFill = new PloyFillCover(this.packPath);
}
async decompileAppWorker() {
await sleep(200);
if (!fs.existsSync(this.pathInfo.workersPath)) {
return;
}
const appConfigString = readLocalFile(this.pathInfo.appJsonPath);
if (!appConfigString)
return;
const appConfig = JSON.parse(appConfigString);
let code = readLocalFile(this.pathInfo.workersPath);
let commPath = "";
let vm = createVM({
sandbox: {
define(name) {
name = path.dirname(name) + "/";
if (!commPath)
commPath = name;
commPath = commonDir(commPath, name);
}
}
});
runVmCode(vm, code.slice(code.indexOf("define(")));
if (commPath.length > 0)
commPath = commPath.slice(0, -1);
printLog(`Worker path: ${commPath}`);
appConfig.workers = commPath;
saveLocalFile(this.pathInfo.appJsonPath, JSON.stringify(appConfig, null, 2));
printLog(` ▶ 反编译 Worker 文件成功.
`, { isStart: true });
}
/**
* 反编译 Worker 文件
* */
async decompileAppWorkers() {
await sleep(200);
if (!fs.existsSync(this.pathInfo.workersPath)) {
return;
}
const _this = this;
let commPath = "";
let code = readLocalFile(this.pathInfo.workersPath);
let vm = createVM({
sandbox: {
define(name, func) {
_this._parseJsDefine(name, func);
const workerPath = path.dirname(name) + "/";
if (!commPath)
commPath = workerPath;
commPath = commonDir(commPath, workerPath);
}
}
});
runVmCode(vm, code);
printLog(`Worker path: ${commPath}`);
if (commPath) {
const configFileName = this.appType === "game" ? this.pathInfo.gameJsonPath : this.pathInfo.appJsonPath;
const appConfig = JSON.parse(readLocalFile(configFileName));
appConfig.workers = commPath;
saveLocalFile(configFileName, JSON.stringify(appConfig, null, 2), { force: true });
}
printLog(` ▶ 反编译 Worker 文件成功.
`, { isStart: true });
}
decompileAll() {
printLog(` ▶ 当前反编译目标[ ${AppTypeMapping[this.appType]} ] (${colors.yellow(PackTypeMapping[this.packType])}) : ` + colors.blue(this.packPath));
printLog(` ▶ 当前输出目录: ${colors.blue(this.pathInfo.outputPath)}
`, {
isEnd: true
});
}
_parseJsDefine(name, func) {
if (path.extname(name) !== ".js")
return;
const foundPloyfill = this.ployFill.findPloyfill(name);
let resultCode = "";
if (foundPloyfill) {
resultCode = readLocalFile(foundPloyfill.fullPath);
} else {
let code = func.toString();
code = code.slice(code.indexOf("{") + 1, code.lastIndexOf("}") - 1).trim();
if (code.startsWith('"use strict";')) {
code = code.replaceAll('"use strict";', "");
} else if (code.startsWith("'use strict';")) {
code = code.replaceAll(`'use strict';`, "");
} else if ((code.startsWith('(function(){"use strict";') || code.startsWith("(function(){'use strict';")) && code.endsWith("})();")) {
code = code.slice(25, -5);
}
code = code.replaceAll('require("@babel', 'require("./@babel');
resultCode = jsBeautify(code.trim());
}
saveLocalFile(
this.pathInfo.outputResolve(name),
removeVM2ExceptionLine(resultCode.trim()),
{ force: true }
);
printLog(` Completed (${resultCode.length}) ` + colors.bold(colors.gray(name)));
}
}
function getAppPackCodeInfo(pathInfo, opt = {}) {
const { adaptLen = 100 } = opt || {};
function __readFile(path2) {
if (!path2)
return "";
const content = readLocalFile(path2);
return content.length > adaptLen ? content : "";
}
let pageFrameHtmlCode = __readFile(pathInfo.pageFrameHtmlPath);
if (pageFrameHtmlCode) {
const $ = cheerio.load(pageFrameHtmlCode);
pageFrameHtmlCode = $("script").text();
}
const appServiceCode = __readFile(pathInfo.appServicePath);
const appServiceAppCode = __readFile(pathInfo.appServiceAppPath);
return {
appConfigJson: __readFile(pathInfo.appConfigJsonPath),
appWxss: __readFile(pathInfo.appWxssPath),
appService: appServiceCode,
appServiceApp: appServiceAppCode,
pageFrame: __readFile(pathInfo.pageFramePath),
workers: __readFile(pathInfo.workersPath),
pageFrameHtml: pageFrameHtmlCode
};
}
function getGamePackCodeInfo(pathInfo, opt = {}) {
const { adaptLen = 100 } = opt || {};
function __readFile(path2) {
if (!path2)
return "";
const content = readLocalFile(path2);
return content.length > adaptLen ? content : "";
}
return {
workers: __readFile(pathInfo.workersPath),
gameJs: __readFile(pathInfo.gameJsPath),
appConfigJson: __readFile(pathInfo.appConfigJsonPath),
subContextJs: __readFile(pathInfo.subContextJsPath)
};
}
class GameDecompilation extends BaseDecompilation {
codeInfo;
wxsList;
allRefComponentList = [];
allSubPackagePages = [];
allPloyFill = [];
constructor(packInfo) {
super(packInfo);
}
/**
* 初始化小游戏所需环境和变量
* */
async initGame() {
this.codeInfo = getGamePackCodeInfo(this.pathInfo);
const loadInfo = {};
for (const name in this.codeInfo) {
loadInfo[name] = this.codeInfo[name].length;
}
console.log(loadInfo);
}
/**
* 反编译 game.json 文件, 只有主包需要处理
* */
async decompileGameJSON() {
if (this.packType !== "main")
return;
await sleep(200);
const gameConfigString = this.codeInfo.appConfigJson;
const gameConfig = JSON.parse(gameConfigString);
Object.assign(gameConfig, gameConfig.global);
GameJsonExcludeKeys.forEach((key) => delete gameConfig[key]);
const outputFileName = "game.json";
const gameConfigSaveString = JSON.stringify(gameConfig, null, 2);
saveLocalFile(this.pathInfo.outputResolve(outputFileName), gameConfigSaveString, { force: true });
printLog(` Completed (${gameConfigSaveString.length}) ` + colors.bold(colors.gray(this.pathInfo.outputResolve(outputFileName))));
printLog(` ▶ 反编译 ${outputFileName} 文件成功.
`, { isStart: true });
}
/**
* 反编译小游戏的js文件
* */
async decompileGameJS() {
const _this = this;
let cont = 0;
const evalCodeList = [
this.codeInfo.subContextJs,
this.codeInfo.gameJs
].filter(Boolean);
const allJsList = [];
const sandbox = {
define(name, func) {
allJsList.push(name);
_this._parseJsDefine(name, func);
cont++;
},
require() {
}
};
evalCodeList.forEach((code) => {
const vm = createVM({ sandbox });
if (!code.includes("define(") || !code.includes("function(require, module, exports)"))
return;
try {
runVmCode(vm, code);
} catch (e) {
console.error(e.message);
}
});
if (cont) {
printLog(` ▶ 反编译所有 js 文件成功.
`);
}
}
async decompileAll() {
super.decompileAll();
await this.initGame();
await this.decompileGameJSON();
await this.decompileGameJS();
await this.decompileAppWorkers();
}
}
function parseParenthesesTyping(str) {
str = str.trim();
const sameObject = str.startsWith("{") && str.endsWith("}");
const sameArray = str.startsWith("[") && str.endsWith("]");
const hasSpreading = (sameArray || sameObject) && str.includes("...");
if (sameObject && !hasSpreading && str.split(":").length === 2) {
return "single";
} else if (sameObject || sameArray) {
return "multiple";
} else {
return "double";
}
}
function restoreSingle(ops, withScope = false) {
if (typeof ops == "undefined")
return "";
function scope(value) {
if (withScope)
return value;
if (value.startsWith("{") && value.endsWith("}"))
return "{{(" + value + ")}}";
if (value.startsWith("..."))
return "{" + value.substring(3) + "}";
return "{{" + value + "}}";
}
function enBrace(value, type = "{") {
if (value.startsWith("{") || value.startsWith("[") || value.startsWith("(") || value.endsWith("}") || value.endsWith("]") || value.endsWith(")")) {
value = " " + value + " ";
}
switch (type) {
case "{":
return "{" + value + "}";
case "[":
return "[" + value + "]";
case "(":
return "(" + value + ")";
default:
throw Error("Unknown brace type " + type);
}
}
function restoreNext(ops2, w = withScope) {
return restoreSingle(ops2, w);
}
function jsoToWxOn(obj) {
let ans = "";
if (typeof obj === "undefined") {
return "undefined";
} else if (obj === null) {
return "null";
} else if (obj instanceof RegExp) {
return obj.toString();
} else if (obj instanceof Array) {
for (let i = 0; i < obj.length; i++)
ans += "," + jsoToWxOn(obj[i]);
return enBrace(ans.slice(1), "[");
} else if (typeof obj == "object") {
for (let k in obj)
ans += "," + k + ":" + jsoToWxOn(obj[k]);
return enBrace(ans.slice(1), "{");
} else if (typeof obj == "string") {
let parts = obj.split('"'), ret = [];
for (let part of parts) {
let atoms = part.split("'"), ans2 = [];
for (let atom of atoms)
ans2.push(JSON.stringify(atom).slice(1, -1));
ret.push(ans2.join("\\'"));
}
return "'" + ret.join('"') + "'";
} else
return JSON.stringify(obj);
}
let op = ops[0];
if (!Array.isArray(op)) {
switch (op) {
case 3:
return ops[1];
case 1:
const val = jsoToWxOn(ops[1]);
return scope(val);
case 11:
let ans = "";
ops.shift();
for (let perOp of ops)
ans += restoreNext(perOp);
return ans;
}
} else {
let ans = "";
switch (op[0]) {
case 2: {
let getPrior = function(op2, len) {
const priorList = {
"?:": 4,
"&&": 6,
"||": 5,
"+": 13,
"*": 14,
"/": 14,
"%": 14,
"|": 7,
"^": 8,
"&": 9,
"!": 16,
"~": 16,
"===": 10,
"==": 10,
"!=": 10,
"!==": 10,
">=": 11,
"<=": 11,
">": 11,
"<": 11,
"<<": 12,
">>": 12,
"-": len === 3 ? 13 : 16
};
return priorList[op2] ? priorList[op2] : 0;
}, getOp = function(i) {
let ret = restoreNext(ops[i], true);
if (ops[i] instanceof Object && typeof ops[i][0] == "object" && ops[i][0][0] === 2) {
if (getPrior(op[1], ops.length) > getPrior(ops[i][0][1], ops[i].length))
ret = enBrace(ret, "(");
}
return ret;
};
switch (op[1]) {
case "?:":
ans = getOp(1) + "?" + getOp(2) + ":" + getOp(3);
break;
case "!":
case "~":
ans = op[1] + getOp(1);
break;
case "-":
if (ops.length !== 3) {
ans = op[1] + getOp(1);
break;
}
default:
ans = getOp(1) + op[1] + getOp(2);
}
break;
}
case 4:
ans = restoreNext(ops[1], true);
break;
case 5: {
switch (ops.length) {
case 2:
ans = enBrace(restoreNext(ops[1], true), "[");
break;
case 1:
ans = "[]";
break;
default: {
let a = restoreNext(ops[1], true);
if (a.startsWith("[") && a.endsWith("]")) {
if (a !== "[]") {
ans = enBrace(a.slice(1, -1).trim() + "," + restoreNext(ops[2], true), "[");
} else {
ans = enBrace(restoreNext(ops[2], true), "[");
}
} else {
ans = enBrace("..." + a + "," + restoreNext(ops[2], true), "[");
}
}
}
break;
}
case 6: {
let sonName = restoreNext(ops[2], true);
if (sonName._type === "var") {
ans = restoreNext(ops[1], true) + enBrace(sonName, "[");
} else {
let attach = "";
if (/^[A-Za-z\_][A-Za-z\d\_]*$/.test(sonName))
attach = "." + sonName;
else
attach = enBrace(sonName, "[");
ans = restoreNext(ops[1], true) + attach;
}
break;
}
case 7: {
switch (ops[1][0]) {
case 11:
ans = enBrace("__unTestedGetValue:" + enBrace(jsoToWxOn(ops), "["), "{");
break;
case 3:
ans = new String(ops[1][1]);
ans["_type"] = "var";
break;
default:
throw Error("Unknown type to get value");
}
break;
}
case 8:
ans = enBrace(ops[1] + ":" + restoreNext(ops[2], true), "{");
break;
case 9: {
let type = function(x) {
if (x.startsWith("..."))
return 1;
if (x.startsWith("{") && x.endsWith("}"))
return 0;
return 2;
};
let a = restoreNext(ops[1], true);
let b = restoreNext(ops[2], true);
let xa = type(a), xb = type(b);
if (xa == 2 || xb == 2)
ans = enBrace("__unkownMerge:" + enBrace(a + "," + b, "["), "{");
else {
if (!xa)
a = a.slice(1, -1).trim();
if (!xb)
b = b.slice(1, -1).trim();
ans = enBrace(a + "," + b, "{");
}
break;
}
case 10:
ans = "..." + restoreNext(ops[1], true);
break;
case 12: {
let arr = restoreNext(ops[2], true);
if (arr.startsWith("[") && arr.endsWith("]"))
ans = restoreNext(ops[1], true) + enBrace(arr.slice(1, -1).trim(), "(");
else
ans = restoreNext(ops[1], true) + ".apply" + enBrace("null," + arr, "(");
break;
}
default:
ans = enBrace("__unkownSpecific:" + jsoToWxOn(ops), "{");
}
return scope(ans);
}
}
function catchZ(code, cb) {
const reg = /function\s+gz\$gwx(\w+)\(\)\{(?:.|\n)*?;return\s+__WXML_GLOBAL__\.ops_cached\.\$gwx[\w\n]+}/g;
const allGwxFunctionMatch = code.match(reg);
if (!allGwxFunctionMatch)
return;
const allFunctionMap = {};
const zObject = {};
const vm = createVM({
sandbox: { __WXML_GLOBAL__: { ops_cached: {} } }
});
allGwxFunctionMatch.forEach((funcString) => {
var _a;
const funcReg = /function\s+gz\$gwx(\w*)\(\)/g;
const funcName = (_a = funcReg.exec(funcString)) == null ? void 0 : _a[1];
if (!funcName)
return;
vm.run(funcString);
const hookZFunc = vm.sandbox[`gz$gwx${funcName}`];
if (hookZFunc) {
allFunctionMap[funcName] = hookZFunc;
zObject[funcName] = hookZFunc();
zObject[funcName] = zObject[funcName].map((data) => {
if (Array.isArray(data) && data[0] === "11182016" && Array.isArray(data[1]))
return data[1];
return data;
});
}
});
cb(zObject);
}
function getZ(code, cb) {
catchZ(code, (z) => {
let ans = {};
for (let gwxFuncName in z) {
ans[gwxFuncName] = z[gwxFuncName].map((gwxData) => restoreSingle(gwxData, false));
}
cb(ans);
});
}
function analyze(core, z, namePool, xPool, fakePool = {}, zMulName = "0") {
function anaRecursion(core2, fakePool2 = {}) {
return analyze(core2, z, namePool, xPool, fakePool2, zMulName);
}
function push(name, elem) {
namePool[name] = elem;
}
function pushSon(pname, son) {
if (fakePool[pname])
fakePool[pname].son.push(son);
else
namePool[pname].son.push(son);
}
for (let ei = 0; ei < core.length; ei++) {
let e = core[ei];
switch (e.type) {
case "ExpressionStatement": {
let f = e.expression;
if (f.callee) {
if (f.callee.type == "Identifier") {
switch (f.callee.name) {
case "_r":
namePool[f.arguments[0].name].v[f.arguments[1].value] = z[f.arguments[2].value];
break;
case "_rz":
namePool[f.arguments[1].name].v[f.arguments[2].value] = z[zMulName][f.arguments[3].value];
break;
case "_":
pushSon(f.arguments[0].name, namePool[f.arguments[1].name]);
break;
case "_2":
{
let item = f.arguments[6].value;
let index = f.arguments[7].value;
let data = z[f.arguments[0].value];
let key = escodegen.generate(f.arguments[8]).slice(1, -1);
let obj = namePool[f.arguments[5].name];
let gen = namePool[f.arguments[1].name];
if (gen.tag == "gen") {
let ret = gen.func.body.body.pop().argument.name;
anaRecursion(gen.func.body.body, { [ret]: obj });
}
obj.v["wx:for"] = data;
if (index != "index")
obj.v["wx:for-index"] = index;
if (item != "item")
obj.v["wx:for-item"] = item;
if (key != "")
obj.v["wx:key"] = key;
}
break;
case "_2z":
{
let item = f.arguments[7].value;
let index = f.arguments[8].value;
let data = z[zMulName][f.arguments[1].value];
let key = escodegen.generate(f.arguments[9]).slice(1, -1);
let obj = namePool[f.arguments[6].name];
let gen = namePool[f.arguments[2].name];
if (gen.tag == "gen") {
let ret = gen.func.body.body.pop().argument.name;
anaRecursion(gen.func.body.body, { [ret]: obj });
}
obj.v["wx:for"] = data;
if (index != "index")
obj.v["wx:for-index"] = index;
if (item != "item")
obj.v["wx:for-item"] = item;
if (key != "")
obj.v["wx:key"] = key;
}
break;
case "_ic":
pushSon(f.arguments[5].name, {
tag: "include",
son: [],
v: { src: xPool[f.arguments[0].property.value] }
});
break;
case "_ai":
{
let to = Object.keys(fakePool)[0];
if (to)
pushSon(to, {
tag: "import",
son: [],
v: { src: xPool[f.arguments[1].property.value] }
});
else
throw Error("Unexpected fake pool");
}
break;
case "_af":
break;
default:
throw Error("Unknown expression callee name " + f.callee.name);
}
} else if (f.callee.type == "MemberExpression") {
if (f.callee.object.name == "cs" || f.callee.property.name == "pop")
break;
throw Error("Unknown member expression");
} else
throw Error("Unknown callee type " + f.callee.type);
} else if (f.type == "AssignmentExpression" && f.operator == "=") {
} else
throw Error("Unknown expression statement.");
break;
}
case "VariableDeclaration":
for (let dec of e.declarations) {
if (dec.init.type == "CallExpression") {
switch (dec.init.callee.name) {
case "_n":
let tagName = dec.init.arguments[0].value;
if (["wx-scope"].includes(tagName)) {
tagName = "view";
}
push(dec.id.name, { tag: tagName, son: [], v: {} });
break;
case "_v":
push(dec.id.name, { tag: "block", son: [], v: {} });
break;
case "_o":
push(dec.id.name, {
tag: "__textNode__",
textNode: true,
content: z[dec.init.arguments[0].value]
});
break;
case "_oz":
push(dec.id.name, {
tag: "__textNode__",
textNode: true,
content: z[zMulName][dec.init.arguments[1].value]
});
break;
case "_m":
{
if (dec.init.arguments[2].elements.length > 0) {
throw Error("Noticable generics content: " + dec.init.arguments[2].toString());
}
let mv = {};
let name = null, base = 0;
for (let x of dec.init.arguments[1].elements) {
let v = x.value;
if (!v && typeof v != "number") {
if (x.type == "UnaryExpression" && x.operator == "-")
v = -x.argument.value;
else
throw Error("Unknown type of object in _m attrs array: " + x.type);
}
if (name === null) {
name = v;
} else {
if (base + v < 0)
mv[name] = null;
else {
mv[name] = z[base + v];
if (base == 0)
base = v;
}
name = null;
}
}
push(dec.id.name, { tag: dec.init.arguments[0].value, son: [], v: mv });
}
break;
case "_mz":
{
if (dec.init.arguments[3].elements.length > 0) {
throw Error("Noticable generics content: " + dec.init.arguments[3].toString());
}
let mv = {};
let name = null, base = 0;
for (let x of dec.init.arguments[2].elements) {
let v = x.value;
if (!v && typeof v != "number") {
if (x.type == "UnaryExpression" && x.operator == "-")
v = -x.argument.value;
else
throw Error("Unknown type of object in _mz attrs array: " + x.type);
}
if (name === null) {
name = v;
} else {
if (base + v < 0)
mv[name] = null;
else {
mv[name] = z[zMulName][base + v];
if (base == 0)
base = v;
}
name = null;
}
}
push(dec.id.name, { tag: dec.init.arguments[1].value, son: [], v: mv });
}
break;
case "_gd":
{
let is = namePool[dec.init.arguments[1].name].content;
let data = null, obj = null;
ei++;
for (let e2 of core[ei].consequent.body) {
if (e2.type == "VariableDeclaration") {
for (let f of e2.declarations) {
if (f.init.type == "LogicalExpression" && f.init.left.type == "CallExpression") {
if (f.init.left.callee.name == "_1")
data = z[f.init.left.arguments[0].value];
else if (f.init.left.callee.name == "_1z")
data = z[zMulName][f.init.left.arguments[1].value];
if (data.startsWith("{{({") && data.endsWith("})}}")) {
data = `{{${data.substring(4, data.length - 4)}}}`;
}
}
}
} else if (e2.type == "ExpressionStatement") {
let f = e2.expression;
if (f.type == "AssignmentExpression" && f.operator == "=" && f.left.property && f.left.property.name == "wxXCkey") {
obj = f.left.object.name;
}
}
}
namePool[obj].tag = "template";
Object.assign(namePool[obj].v, { is, data });
}
break;
default: {
let funName = dec.init.callee.name;
if (funName.startsWith("gz$gwx")) {
zMulName = funName.slice(6);
} else
throw Error("Unknown init callee " + funName);
}
}
} else if (dec.init.type == "FunctionExpression") {
push(dec.id.name, { tag: "gen", func: dec.init });
} else if (dec.init.type == "MemberExpression") {
if (dec.init.object.type == "MemberExpression" && dec.init.object.object.name == "e_" && dec.init.object.property.type == "MemberExpression" && dec.init.object.property.object.name == "x") {
if (dec.init.property.name == "j") {
} else if (dec.init.property.name == "i") {
} else
throw Error("Unknown member expression declaration.");
} else
throw Error("Unknown member expression declaration.");
} else
throw Error("Unknown declaration init type " + dec.init.type);
}
break;