const wu = require("./wuLib.js"); const {getZ} = require("./wuRestoreZ.js"); const {wxsBeautify} = require("./wuJs.js"); const fs = require('fs'); const path = require("path"); const esprima = require('esprima'); const {VM} = require('vm2'); const escodegen = require('escodegen'); function analyze(core, z, namePool, xPool, fakePool = {}, zMulName = "0") { function anaRecursion(core, fakePool = {}) { return analyze(core, z, namePool, xPool, fakePool, 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.mul[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;//def:item let index = f.arguments[7].value;//def:index let data = z[f.arguments[0].value]; let key = escodegen.generate(f.arguments[8]).slice(1, -1);//f.arguments[8].value;//def:"" 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;//def:item let index = f.arguments[8].value;//def:index let data = z.mul[zMulName][f.arguments[1].value]; let key = escodegen.generate(f.arguments[9]).slice(1, -1);//f.arguments[9].value;//def:"" 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": {//template import 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": //ignore _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 == "=") { //no special use } 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": push(dec.id.name, {tag: dec.init.arguments[0].value, 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.mul[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.mul[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"://template use/is { let is = namePool[dec.init.arguments[1].name].content; let data = null, obj = null; ei++; for (let e of core[ei].consequent.body) { if (e.type == "VariableDeclaration") { for (let f of e.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.mul[zMulName][f.init.left.arguments[1].value]; } } } else if (e.type == "ExpressionStatement") { let f = e.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: is, data: 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") {//include //do nothing } else if (dec.init.property.name == "i") {//import //do nothing } 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; case "IfStatement": if (e.test.callee.name.startsWith("_o")) { function parse_OFun(e) { if (e.test.callee.name == "_o") return z[e.test.arguments[0].value]; else if (e.test.callee.name == "_oz") return z.mul[zMulName][e.test.arguments[1].value]; else throw Error("Unknown if statement test callee name:" + e.test.callee.name); } let vname = e.consequent.body[0].expression.left.object.name; let nif = {tag: "block", v: {"wx:if": parse_OFun(e)}, son: []}; anaRecursion(e.consequent.body, {[vname]: nif}); pushSon(vname, nif); if (e.alternate) { while (e.alternate && e.alternate.type == "IfStatement") { e = e.alternate; nif = {tag: "block", v: {"wx:elif": parse_OFun(e)}, son: []}; anaRecursion(e.consequent.body, {[vname]: nif}); pushSon(vname, nif); } if (e.alternate && e.alternate.type == "BlockStatement") { e = e.alternate; nif = {tag: "block", v: {"wx:else": null}, son: []}; anaRecursion(e.body, {[vname]: nif}); pushSon(vname, nif); } } } else throw Error("Unknown if statement."); break; default: throw Error("Unknown type " + e.type); } } } function wxmlify(str, isText) { if (typeof str == "undefined" || str === null) return "Empty";//throw Error("Empty str in "+(isText?"text":"prop")); if (isText) return str;//may have some bugs in some specific case(undocumented by tx) else return str.replace(/"/g, '\\"'); } function elemToString(elem, dep, moreInfo = false) { const longerList = [];//put tag name which can't be style. const indent = ' '.repeat(4); function isTextTag(elem) { return elem.tag == "__textNode__" && elem.textNode; } function elemRecursion(elem, dep) { return elemToString(elem, dep, moreInfo); } function trimMerge(rets) { let needTrimLeft = false, ans = ""; for (let ret of rets) { if (ret.textNode == 1) { if (!needTrimLeft) { needTrimLeft = true; ans = ans.trimRight(); } } else if (needTrimLeft) { needTrimLeft = false; ret = ret.trimLeft(); } ans += ret; } return ans; } if (isTextTag(elem)) { //In comment, you can use typify text node, which beautify its code, but may destroy ui. //So, we use a "hack" way to solve this problem by letting typify program stop when face textNode let str = new String(wxmlify(elem.content, true)); str.textNode = 1; return wxmlify(str, true);//indent.repeat(dep)+wxmlify(elem.content.trim(),true)+"\n"; } if (elem.tag == "block" && !moreInfo) { if (elem.son.length == 1 && !isTextTag(elem.son[0])) { let ok = true, s = elem.son[0]; for (let x in elem.v) if (x in s.v) { ok = false; break; } if (ok && !(("wx:for" in s.v || "wx:if" in s.v) && ("wx:if" in elem.v || "wx:else" in elem.v || "wx:elif" in elem.v))) {//if for and if in one tag, the default result is an if in for. And we should block if nested in elif/else been combined. Object.assign(s.v, elem.v); return elemRecursion(s, dep); } } else if (Object.keys(elem.v).length == 0) { let ret = []; for (let s of elem.son) ret.push(elemRecursion(s, dep)); return trimMerge(ret); } } let ret = indent.repeat(dep) + "<" + elem.tag; for (let v in elem.v) ret += " " + v + (elem.v[v] !== null ? "=\"" + wxmlify(elem.v[v]) + "\"" : ""); if (elem.son.length == 0) { if (longerList.includes(elem.tag)) return ret + " />\n"; else return ret + ">\n"; } ret += ">\n"; let rets = [ret]; for (let s of elem.son) rets.push(elemRecursion(s, dep + 1)); rets.push(indent.repeat(dep) + "\n"); return trimMerge(rets); } function doWxml(state, dir, name, code, z, xPool, rDs, wxsList, moreInfo) { let rname = code.slice(code.lastIndexOf("return") + 6).replace(/[\;\}]/g, "").trim(); code = code.slice(code.indexOf("\n"), code.lastIndexOf("return")).trim(); let r = {son: []}; analyze(esprima.parseScript(code).body, z, {[rname]: r}, xPool, {[rname]: r}); let ans = []; for (let elem of r.son) ans.push(elemToString(elem, 0, moreInfo)); let result = [ans.join("")]; for (let v in rDs) { state[0] = v; let oriCode = rDs[v].toString(); let rname = oriCode.slice(oriCode.lastIndexOf("return") + 6).replace(/[\;\}]/g, "").trim(); let tryPtr = oriCode.indexOf("\ntry{"); let zPtr = oriCode.indexOf("var z=gz$gwx"); let code = oriCode.slice(tryPtr + 5, oriCode.lastIndexOf("\n}catch(")).trim(); if (zPtr != -1 && tryPtr > zPtr) { let attach = oriCode.slice(zPtr); attach = attach.slice(0, attach.indexOf("()")) + "()\n"; code = attach + code; } let r = {tag: "template", v: {name: v}, son: []}; analyze(esprima.parseScript(code).body, z, {[rname]: r}, xPool, {[rname]: r}); result.unshift(elemToString(r, 0, moreInfo)); } name = path.resolve(dir, name); if (wxsList[name]) result.push(wxsList[name]); wu.save(name, result.join("")); } function tryWxml(dir, name, code, z, xPool, rDs, ...args) { console.log("Decompile " + name + "..."); let state = [null]; try { doWxml(state, dir, name, code, z, xPool, rDs, ...args); console.log("Decompile success!"); } catch (e) { console.log("error on " + name + "(" + (state[0] === null ? "Main" : "Template-" + state[0]) + ")\nerr: ", e); if (state[0] === null) wu.save(path.resolve(dir, name + ".ori.js"), code); else wu.save(path.resolve(dir, name + ".tem-" + state[0] + ".ori.js"), rDs[state[0]].toString()); } } function doWxs(code, name) { name = name || ''; name = name.substring(0, name.lastIndexOf('/') + 1); const before = 'nv_module={nv_exports:{}};'; return wxsBeautify(code.slice(code.indexOf(before) + before.length, code.lastIndexOf('return nv_module.nv_exports;}')).replace(eval('/' + ('p_' + name).replace(/\//g, '\\/') + '/g'), '').replace(/nv\_/g, '').replace(/(require\(.*?\))\(\)/g,'$1')); } function doFrame(name, cb, order, mainDir) { let moreInfo = order.includes("m"); wxsList = {}; wu.get(name, code => { getZ(code, z => { const before = "\nvar nv_require=function(){var nnm="; code = code.slice(code.lastIndexOf(before) + before.length, code.lastIndexOf("if(path&&e_[path]){")); json = code.slice(0, code.indexOf("};") + 1); let endOfRequire = code.indexOf("()\r\n") + 4; if (endOfRequire == 4 - 1) endOfRequire = code.indexOf("()\n") + 3; code = code.slice(endOfRequire); let rD = {}, rE = {}, rF = {}, requireInfo = {}, x, vm = new VM({ sandbox: { d_: rD, e_: rE, f_: rF, _vmRev_(data) { [x, requireInfo] = data; }, nv_require(path) { return () => path; } } }); let vmCode = code + "\n_vmRev_([x," + json + "])"; vm.run(vmCode); let dir = mainDir || path.dirname(name), pF = []; for (let info in rF) if (typeof rF[info] == "function") { let name = path.resolve(dir, (info[0] == '/' ? '.' : '') + info), ref = rF[info](); pF[ref] = info; wu.save(name, doWxs(requireInfo[ref].toString(), info)); } for (let info in rF) if (typeof rF[info] == "object") { let name = path.resolve(dir, (info[0] == '/' ? '.' : '') + info); let res = [], now = rF[info]; for (let deps in now) { let ref = now[deps](); if (ref.includes(":")) res.push("\n" + doWxs(requireInfo[ref].toString()) + "\n"); else if (pF[ref]) res.push(""); else res.push(""); wxsList[name] = res.join("\n"); } } for (let name in rE) tryWxml(dir, name, rE[name].f.toString(), z, x, rD[name], wxsList, moreInfo); cb({[name]: 4}); }); }); } module.exports = {doFrame: doFrame}; if (require.main === module) { wu.commandExecute(doFrame, "Restore wxml files.\n\n\n\n restore wxml file from page-frame.html or app-wxss.js."); }