wedecode
Version:
微信小程序源代码还原工具, 线上代码安全审计
1,674 lines (1,671 loc) • 101 kB
JavaScript
#!/usr/bin/env node
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
import { Command } from "commander";
import path from "node:path";
import process$1 from "node:process";
import "single-line-log";
import JS from "js-beautify";
import os from "node:os";
import fs from "node:fs";
import { glob } from "glob";
import colors from "picocolors";
import checkForUpdate from "update-check";
import figlet from "figlet";
import inquirer from "inquirer";
import { SelectTableTablePrompt } from "@biggerstar/inquirer-selectable-table";
import axios from "axios";
import openFileExplorer from "open-file-explorer";
import { deepmerge } from "@biggerstar/deepmerge";
import { VM } from "vm2";
import { JSDOM } from "jsdom";
import * as cheerio from "cheerio";
import cssbeautify from "cssbeautify";
import esprima from "esprima";
import escodegen from "escodegen";
const name = "wedecode";
const version = "0.8.1";
const type = "module";
const description = "微信小程序源代码还原工具, 线上代码安全审计";
const bin = {
wedecode: "./dist/wedecode.js"
};
const scripts = {
bootstrap: "pnpm install",
start: "vite build && node dist/wedecode.js",
dev: "vite build --watch",
build: "vite build",
"test:cmd": "wedecode",
"test:cmd:dev": "DEV=true wedecode",
"test:cmd:args": "DEV=true wedecode -o OUTPUT",
link: "vite build && pnpm link --dir= ./",
unlink: "pnpm unlink",
"release:npm": "vite build && npm publish",
"release:git": "vite build && git commit -am v$npm_package_version && git tag $npm_package_version && git push --tags ",
"dev:unpack:dir": "DEV=true wedecode --clear -o OUTPUT pkg/fen-cao",
"dev:unpack:subPack": "DEV=true wedecode --clear -o OUTPUT pkg/mt/_mobike_.wxapkg",
"dev:unpack:game-dir": "DEV=true wedecode --clear -o OUTPUT-GAME pkg/weixin-dushu",
"preview:unpack": "wedecode -ow true -o OUTPUT pkg/issues8-sxd"
};
const repository = {
type: "git",
url: "git+https://github.com/biggerstar/wedecode.git"
};
const license = "GPL-3.0-or-later";
const bugs = {
url: "https://github.com/biggerstar/wedecode/issues"
};
const files = [
"dist",
"decryption-tool"
];
const homepage = "https://github.com/biggerstar/wedecode#readme";
const dependencies = {
"@biggerstar/deepmerge": "^1.0.3",
"@biggerstar/inquirer-selectable-table": "^1.0.12",
axios: "^1.7.4",
cheerio: "1.0.0-rc.12",
commander: "^12.1.0",
cssbeautify: "^0.3.1",
escodegen: "^1.14.3",
esprima: "^4.0.1",
figlet: "^1.7.0",
glob: "^10.4.1",
inquirer: "^9.2.23",
"js-beautify": "^1.15.1",
jsdom: "^24.1.0",
"open-file-explorer": "^1.0.2",
picocolors: "^1.0.1",
"single-line-log": "^1.1.2",
"update-check": "^1.5.4",
vm2: "^3.9.19"
};
const devDependencies = {
"@types/cssbeautify": "^0.3.5",
"@types/escodegen": "^0.0.10",
"@types/esprima": "^4.0.6",
"@types/figlet": "^1.5.8",
"@types/inquirer": "^9.0.7",
"@types/js-beautify": "^1.14.3",
"@types/jsdom": "^21.1.7",
"@types/node": "^20.14.2",
"@types/open-file-explorer": "^1.0.2",
"@types/single-line-log": "^1.1.2",
lerna: "^8.1.7",
"rollup-plugin-copy": "^3.5.0",
vite: "^5.2.13"
};
const keywords = [
"wxapkg",
"Decompilation",
"小程序",
"反编译",
"审计",
"安全"
];
const pkg = {
name,
version,
type,
description,
bin,
scripts,
repository,
license,
bugs,
files,
homepage,
dependencies,
devDependencies,
keywords
};
/^win/.test(process.platform);
/^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"
];
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",
// 安装到其他盘
"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 clearScreen() {
process$1.stdout.write(process$1.platform === "win32" ? "\x1Bc" : "\x1B[2J\x1B[3J\x1B[H");
}
const excludesLogMatch = isDev ? [
"Completed"
] : [];
function printLog(log, opt = {}) {
if (excludesLogMatch.some((item) => log.includes(item)))
return;
{
console.log(log);
return;
}
}
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(name2, ext = "") {
const hasSuffix = name2.lastIndexOf(".") > 2;
return hasSuffix ? name2.slice(0, name2.lastIndexOf(".")) + ext : `${name2}${ext}`;
}
function sleep(time) {
return new Promise((resolve1) => setTimeout(resolve1, time));
}
function arrayDeduplication(arr, cb) {
return arr.reduce((pre, 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);
}
inquirer.registerPrompt("table", SelectTableTablePrompt);
process$1.stdout.setMaxListeners(200);
async function onResize() {
if (lastTableOptions) {
await prompts.showScanPackTable(lastTableOptions);
clearScreen();
}
}
let lastTableOptions = null;
let online = false;
async function checkOnline() {
online = await internetAvailable();
}
setTimeout(checkOnline, 0);
const prompts = {
async selectMode() {
const offlineTip = `( ${colors.yellow("联网可显示小程序信息")} )`;
const onlineTip = `( ${colors.green("网络正常")} )`;
await sleep(1e3);
return inquirer["prompt"](
[
{
type: "list",
message: `请选择操作模式 ? ${!online ? offlineTip : onlineTip}`,
name: "selectMode",
choices: [
OperationModeEnum.autoScan,
OperationModeEnum.manualScan,
OperationModeEnum.manualDir
]
}
]
);
},
async inputManualScanPath() {
return inquirer["prompt"](
[
{
type: "input",
message: `输入您要扫描的小程序包路径 ( ${colors.yellow(".")} 表示使用当前路径 ): `,
name: "manualScanPath",
validate(input) {
if (!input)
return false;
return checkExistsWithFilePath(input, { throw: true, checkWxapkg: false, showInputPathLog: false });
}
}
]
);
},
async showDangerScanPrompt(_path) {
return inquirer["prompt"](
[
{
type: "list",
message: `您指定的路径可能会花大量时间扫描文件系统, 确定继续 ? ${colors.yellow(_path)}`,
name: "dangerScan",
choices: [
YesOrNoEnum.no,
YesOrNoEnum.yes
],
default: YesOrNoEnum.no
}
]
);
},
async showScanPackTable(opt) {
lastTableOptions = opt;
if (!onResize["onResize"]) {
process$1.stdout.on("resize", onResize);
onResize["onResize"] = true;
}
await sleep(50);
clearScreen();
const part = process$1.stdout.columns / 10;
const result = await inquirer["prompt"](
[
{
type: "table",
name: "packInfo",
message: "",
pageSize: 6,
showIndex: true,
tableOptions: {
// wordWrap: true,
wrapOnWordBoundary: true,
colWidths: [part / 2, part * 2, part * 2, part * 5.3].map((n) => Math.floor(n))
},
columns: opt.columns || [],
rows: opt.rows || []
}
]
);
onResize["onResize"] = false;
process$1.stdout.off("resize", onResize);
return result;
},
async questionInputPath() {
return inquirer["prompt"](
[
{
type: "input",
message: `输入 ${colors.blue("wxapkg文件")} 或 ${colors.blue("目录")} 默认为( ${colors.yellow("./")} ): `,
name: "inputPath",
validate(input, _) {
return checkExistsWithFilePath(path.resolve(input), { throw: true });
}
}
]
);
},
async questionOutputPath() {
return inquirer["prompt"](
[
{
type: "input",
message: `输出目录, 默认为当前所在目录的( ${colors.yellow(PUBLIC_OUTPUT_PATH)} ): `,
name: "outputPath"
}
]
);
},
async isClearOldCache(cachePath = "") {
return inquirer["prompt"](
[
{
type: "list",
message: `输出目录中存在上次旧的编译产物,是否清空 ?
${colors.blue(`当前缓存路径( ${colors.yellow(cachePath)} )`)}`,
name: "isClearCache",
choices: [
CacheClearEnum.clear,
CacheClearEnum.notClear
]
}
]
);
},
async showFileExplorer() {
return inquirer["prompt"](
[
{
type: "list",
message: `
将打开文件管理器, 确定继续 ?`,
name: "showFileExplorer",
choices: [
YesOrNoEnum.no,
YesOrNoEnum.yes
],
default: YesOrNoEnum.no
}
]
);
}
};
function createNewVersionUpdateNotice() {
let updateInfo;
return {
/** 进行查询 */
query() {
checkForUpdate(pkg).then((res) => updateInfo = res).catch(() => void 0);
},
/**
* 异步使用, 时间错开,因为查询需要时间, 如果查询到新版本, 则进行通知
* 基于 update-check 如果本次查到更新但是没通知, 下次启动将会从缓存中获取版本信息并通知
* */
async notice() {
await sleep(200);
if (updateInfo && updateInfo.latest) {
printLog(`
🎉 wedecode 有新版本: v${pkg.version} --> v${updateInfo.latest}
🎄 您可以直接使用 ${colors.blue(`npm i -g wedecode@${updateInfo.latest}`)} 进行更新
💬 npm地址: https://www.npmjs.com/package/wedecode
`, {
isStart: true
});
} else {
printLog(`
🎄 当前使用版本: v${pkg.version}
`, {
isStart: true
});
}
}
};
}
function createSlogan(str = " wedecode") {
const slogan = figlet.textSync(str, {
horizontalLayout: "default",
verticalLayout: "default",
whitespaceBreak: true
});
return colors.bold(colors.yellow(slogan));
}
async function startCacheQuestionProcess(isClear, inputPath, outputPath) {
const OUTPUT_PATH = path.resolve(outputPath);
if (fs.existsSync(OUTPUT_PATH)) {
const isClearCache = isClear ? CacheClearEnum.clear : (await prompts.isClearOldCache(OUTPUT_PATH))["isClearCache"];
if (isClearCache === CacheClearEnum.clear || isClear) {
fs.rmSync(OUTPUT_PATH, { recursive: true });
printLog(`
▶ 移除旧产物成功 `);
}
}
}
function checkExistsWithFilePath(targetPath, opt) {
const { throw: isThrow = true, checkWxapkg = true, showInputPathLog = true } = opt || {};
const printErr = (log) => {
if (showInputPathLog) {
console.log("\n输入路径: ", colors.yellow(path.resolve(targetPath)));
}
isThrow && console.log(`${colors.red(`❌ ${log}`)}
`);
};
if (!fs.existsSync(targetPath)) {
printErr("文件 或者 目录不存在, 请检查!");
return false;
}
if (checkWxapkg) {
const isDirectory = fs.statSync(targetPath).isDirectory();
if (isDirectory) {
const wxapkgPathList = glob.globSync(`${targetPath}/*.wxapkg`);
if (!wxapkgPathList.length) {
console.log(
"\n",
colors.red("❌ 文件夹下不存在 .wxapkg 包"),
colors.yellow(path.resolve(targetPath)),
"\n"
);
return false;
}
}
}
return true;
}
function stopCommander() {
console.log(colors.red("❌ 操作已主动终止!"));
process$1.exit(0);
}
function getPathSplitList(_path) {
let delimiter = "\\";
let partList;
partList = _path.split("\\");
if (partList.length <= 1) {
delimiter = "/";
partList = _path.split("/");
}
return {
partList,
delimiter
};
}
function findWxAppIdPath(_path) {
const { partList, delimiter } = getPathSplitList(_path);
let newPathList = [...partList];
for (const index in partList.reverse()) {
const dirName = partList[index];
if (isWxAppid(dirName)) {
break;
}
newPathList.pop();
}
return newPathList.join(delimiter).trim();
}
function findWxAppIdForPath(_path) {
return path.parse(findWxAppIdPath(_path)).name;
}
async function internetAvailable() {
return axios.request({
url: "https://bing.com",
maxRedirects: 0,
timeout: 2e3,
validateStatus: () => true
}).then(() => true).catch(() => Promise.resolve(false));
}
function inDangerScanPathList(_path) {
_path = path.resolve(_path);
let partList;
if (_path.includes(":"))
_path = _path.split(":")[1];
partList = getPathSplitList(_path).partList;
return partList.map((s) => s.trim()).filter(Boolean).length <= 1;
}
function findWxMiniProgramPackDir(manualScanPath) {
const foundPackageList = [];
glob.globSync(path.resolve(manualScanPath, "**/*.wxapkg"), {
dot: true,
windowsPathsNoEscape: true,
nocase: true
}).map((_path) => {
const foundMainPackage = AppMainPackageNames.find((fileName) => _path.endsWith(fileName));
if (foundMainPackage)
return _path;
return false;
}).filter(Boolean).reduce((pre, cur) => {
if (pre.includes(cur))
return pre;
pre.push(cur);
return pre;
}, []).forEach((_path) => {
const foundPath = findWxAppIdPath(_path);
const isFoundWxId = !!foundPath;
let appIdPath = path.dirname(_path);
const { partList } = getPathSplitList(appIdPath);
let appId = partList.filter(Boolean).pop();
if (isFoundWxId) {
appIdPath = foundPath;
appId = findWxAppIdForPath(_path);
}
foundPackageList.push({
isAppId: isFoundWxId,
appId,
path: isFoundWxId ? foundPath : appIdPath,
storagePath: path.dirname(_path)
});
});
return foundPackageList;
}
async function sacnPackages(manualScanPath = "") {
const foundPackageList = [];
let scanPathList = globPathList;
if (Boolean(manualScanPath.trim())) {
const absolutePath = path.resolve(manualScanPath);
if (inDangerScanPathList(absolutePath)) {
const { dangerScan } = await prompts.showDangerScanPrompt(absolutePath);
if (dangerScan === YesOrNoEnum.no) {
stopCommander();
}
}
scanPathList = [absolutePath];
}
if (scanPathList.length) {
console.log(" 扫描中...");
}
scanPathList.forEach((matchPath) => {
const foundPList = findWxMiniProgramPackDir(matchPath);
foundPList.forEach((item) => foundPackageList.push(item));
});
if (foundPackageList.length === 0) {
console.log(`
${colors.red("未找到小程序包,您需要电脑先访问某个小程序后产生缓存再扫描, 如果还扫描不到请反馈 ")}
当前所处目录: $ ${colors.yellow(path.resolve(manualScanPath || "./"))}
▶ 随着微信版本更新, 新版本小程序路径可能和已知位置不一样, 如果出现问题请到 github 反馈
▶ 提交时请带上您电脑中小程序的 '${colors.bold("微信官方的 wxapkg 包在硬盘中的存放路径")}' 和 '${colors.bold("微信版本号")}'
▶ https://github.com/biggerstar/wedecode/issues
`);
stopCommander();
}
return foundPackageList;
}
async function startSacnPackagesProcess(manualScanPath) {
const foundPackageList = await sacnPackages(manualScanPath);
const columns = [
{
name: "名字",
value: "appName"
},
{
name: "修改时间",
value: "updateDate"
},
{
name: "描述",
value: "description"
}
];
const rowsPromiseList = foundPackageList.map(async (item) => {
const statInfo = fs.statSync(item.storagePath);
const date = new Date(statInfo.mtime);
const dateString = `${date.getMonth() + 1}/${date.getDate()} ${date.toLocaleTimeString()}`;
if (!item.isAppId)
return {
appName: item.appId,
updateDate: dateString,
description: item.storagePath
};
const appId = item.appId;
const { nickname, description: description2 } = await getWxAppInfo(appId);
return {
appName: nickname || appId,
updateDate: dateString,
description: description2 || ""
};
});
if (rowsPromiseList.length) {
console.log(" 获取小程序信息中...");
}
const rows = await Promise.all(rowsPromiseList);
if (rowsPromiseList.length) {
clearScreen();
console.log("$ 选择一个包进行编译: ");
}
const result = await prompts.showScanPackTable({
columns,
rows
});
const foundIndex = rows.findIndex((item) => {
var _a;
return item.appName === ((_a = result.packInfo) == null ? void 0 : _a.appName);
});
const packInfo = { ...rows[foundIndex], ...foundPackageList[foundIndex] };
console.log(`$ 选择了 ${packInfo.appName}( ${packInfo.appId} )`);
return packInfo;
}
async function getWxAppInfo(appid) {
const options = {
method: "POST",
timeout: 6e3,
url: "https://kainy.cn/api/weapp/info/",
headers: { "content-type": "application/json" },
data: { appid }
};
return axios.request(options).then((res) => {
var _a;
return Promise.resolve(((_a = res.data) == null ? void 0 : _a.data) || {});
}).catch(() => {
return Promise.resolve({});
});
}
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 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;
}
}
const systemInfo = {
windowWidth: 375,
windowHeight: 600,
pixelRatio: 2,
language: "en",
version: "1.9.90",
platform: "ios"
};
function createWxFakeDom() {
return {
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: {
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 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.log(e.message);
}
}
class PloyFillCover {
constructor(packPath) {
__publicField(this, "allPloyFills", []);
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 {
constructor(packInfo) {
__publicField(this, "pathInfo");
__publicField(this, "outputPathInfo");
__publicField(this, "packPath");
__publicField(this, "packType");
__publicField(this, "appType");
__publicField(this, "ployFill");
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(name2) {
name2 = path.dirname(name2) + "/";
if (!commPath)
commPath = name2;
commPath = commonDir(commPath, name2);
}
}
});
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(name2, func) {
_this._parseJsDefine(name2, func);
const workerPath = path.dirname(name2) + "/";
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(name2, func) {
if (path.extname(name2) !== ".js")
return;
const foundPloyfill = this.ployFill.findPloyfill(name2);
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(name2),
removeVM2ExceptionLine(resultCode.trim()),
{ force: true }
);
printLog(` Completed (${resultCode.length}) ` + colors.bold(colors.gray(name2)));
}
}
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 {
constructor(packInfo) {
super(packInfo);
__publicField(this, "codeInfo");
__publicField(this, "wxsList");
__publicField(this, "allRefComponentList", []);
__publicField(this, "allSubPackagePages", []);
__publicField(this, "allPloyFill", []);
}
/**
* 初始化小游戏所需环境和变量
* */
async initGame() {
this.codeInfo = getGamePackCodeInfo(this.pathInfo);
const loadInfo = {};
for (const name2 in this.codeInfo) {
loadInfo[name2] = this.codeInfo[name2].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 sandbox = {
define(name2, func) {
_this._parseJsDefine(name2, 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.log(e.message);
}
});
if (cont) {
printLog(` ▶ 反编译所有 js 文件成功.
`);
}
}
async decompileAll() {
super.decompileAll();
await this.initGame();
await this.decompileGameJSON();
await this.decompileGameJS();
await this.decompileAppWorkers();
}
}
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, type2 = "{") {
if (value.startsWith("{") || value.startsWith("[") || value.startsWith("(") || value.endsWith("}") || value.endsWith("]") || value.endsWith(")")) {
value = " " + value + " ";
}
switch (type2) {
case "{":
return "{" + value + "}";
case "[":
return "[" + value + "]";
case "(":
return "(" + value + ")";
default:
throw Error("Unknown brace type " + type2);
}
}
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 type2 = 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 = type2(a), xb = type2(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 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) {
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(name2, elem) {
namePool[name2] = 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 "_