This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const wu = require("./wuLib.js"); | |
const {getZ, catchM0} = 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 <x /> 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 + "></" + elem.tag + ">\n"; | |
} | |
ret += ">\n"; | |
let rets = [ret]; | |
for (let s of elem.son) rets.push(elemRecursion(s, dep + 1)); | |
rets.push(indent.repeat(dep) + "</" + elem.tag + ">\n"); | |
return trimMerge(rets); | |
} | |
/** | |
* 转换wxml | |
* @param {*} state | |
* @param {*} dir 主目录 | |
* @param {*} name xml文件名, 存储于x数组中 | |
* @param {*} code m0的函数体 | |
* @param {*} z z数组 | |
* @param {*} xPool 文件列表 | |
* @param {*} rDs | |
* @param {*} wxsList | |
* @param {*} moreInfo | |
*/ | |
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(); | |
// console.log(code); | |
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 + "])"; | |
// fs.writeFileSync(`${name}-vmCode.txt`, vmCode); | |
// 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("<wxs module=\"" + deps + "\">\n" + doWxs(requireInfo[ref].toString()) + "\n</wxs>"); | |
// else if (pF[ref]) res.push("<wxs module=\"" + deps + "\" src=\"" + wu.toDir(pF[ref], info) + "\" />"); | |
// else res.push("<wxs module=\"" + deps + "\" src=\"" + wu.toDir(ref.slice(2), info) + "\" />"); | |
// wxsList[name] = res.join("\n"); | |
// } | |
// } | |
catchM0(code, z, m0 => { | |
for (let name in m0) tryWxml(dir, name, m0[name].f.toString(), z, Object.keys(m0), [], [], moreInfo); | |
cb({[name]: 4}); | |
}) | |
}); | |
}); | |
} | |
module.exports = {doFrame: doFrame}; | |
if (require.main === module) { | |
wu.commandExecute(doFrame, "Restore wxml files.\n\n<files...>\n\n<files...> restore wxml file from page-frame.html or app-wxss.js."); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment