diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0792b5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/ +example/ +node_modules/ +package-lock.json \ No newline at end of file diff --git a/DETAILS.md b/DETAILS.md new file mode 100644 index 0000000..c766da9 --- /dev/null +++ b/DETAILS.md @@ -0,0 +1,334 @@ +# 关于还原的详细信息 + +### wxapkg 包 + +对于 wxapkg 包文件格式的分析已在网上广泛流传,可整理为如下内容(请注意该文件中的`uint32`都是以`大端序`方式存放): + +```c++ +typedef unsigned char uint8; +typedef unsigned int uint32;//Notice: uint32 use BIG-ENDIAN, not Little. + +struct wxHeader { + uint8 firstMark;// one of magic number, which is equal to 0xbe + uint32 unknownInfo;// this info was always set to zero. maybe it's the verison of file? + uint32 infoListLength;// the length of wxFileInfoList + uint32 dataLength;// the length of dataBuf + uint8 lastMark;// another magic number, which is equal to 0xed +}; + +struct wxFileInfo {// illustrate one file in wxapkg pack + uint32 nameLen;// the length of filename + char name[nameLen];// filename, use UTF-8 encoding (translating it to GBK is required in Win) + uint32 fileOff;// the offset of this file (0 is pointing to the begining of this file[struct wxapkgFile]) + uint32 fileLen;// the length of this file +}; + +struct wxFileInfoList { + uint32 fileCount;// The count of file + wxFileInfo fileInfos[fileCount]; +}; + +struct wxapkgFile { + wxHeader header; + wxFileInfoList fileInfoList; + uint8 dataBuf[dataLength]; +}; +``` + +由上可知,在wxapkg 包中文件头后的位置上有`文件名+文件内容起始地址及长度`信息,且各个文件内容也全是以明文方式存放在包内,从而我们可以获取包内文件。 + +通过解包可知,这个包中的文件内容主要如下: + +- app-config.json +- app-service.js +- page-frame.html ( 也可能是由 app-wxss.js 和 page-frame.js 组成相关信息 ) +- 其他一堆放在各文件夹中的.html文件 +- 和源码包内位置和内容相同的图片等资源文件 + +微信开发者工具并不能识别这些文件,它要求我们提供由`wxml/wxss/js/wxs/json`组成的源码才能进行模拟/调试。 + +### js + +注意到`app-service.js`中的内容由 + +```javascript +define('xxx.js',function(...){ +//The content of xxx.js +});require('xxx.js'); +define('yyy.js',function(...){ +//The content of xxx.js +});require('yyy.js'); +.... +``` + +组成,很显然,我们只要定义自己的`define`函数就可以将这些 js 文件恢复到源码中所对应的位置。当然,这些 js 文件中的内容经过压缩,即使使用 UglifyJS 这样的工具进行美化,也无法还原一些原始变量名。 + +### wxss + +所有在 wxapkg 包中的 html 文件都调用了`setCssToHead`函数,其代码如下 + +```javascript +var setCssToHead = function(file, _xcInvalid) { + var Ca = {}; + var _C = [...arrays...]; + function makeup(file, suffix) { + var _n = typeof file === "number"; + if (_n && Ca.hasOwnProperty(file)) return ""; + if (_n) Ca[file] = 1; + var ex = _n ? _C[file] : file; + var res = ""; + for (var i = ex.length - 1; i >= 0; i--) { + var content = ex[i]; + if (typeof content === "object") { + var op = content[0]; + if (op == 0) res = transformRPX(content[1]) + "px" + res; else if (op == 1) res = suffix + res; else if (op == 2) res = makeup(content[1], suffix) + res; + } else res = content + res; + } + return res; + } + return function(suffix, opt) { + if (typeof suffix === "undefined") suffix = ""; + if (opt && opt.allowIllegalSelector != undefined && _xcInvalid != undefined) { + if (opt.allowIllegalSelector) console.warn("For developer:" + _xcInvalid); else { + console.error(_xcInvalid + "This wxss file is ignored."); + return; + } + } + Ca = {}; + css = makeup(file, suffix); + var style = document.createElement("style"); + var head = document.head || document.getElementsByTagName("head")[0]; + style.type = "text/css"; + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + head.appendChild(style); + }; +}; +``` + +阅读这段代码可知,它把 wxss 代码拆分成几段数组,数组中的内容可以是一段将要作为 css 文件的字符串,也可以是一个表示 这里要添加一个公共后缀 或 这里要包含另一段代码 或 要将以 wxss 专供的 rpx 单位表达的数字换算成能由浏览器渲染的 px 单位所对应的数字 的数组。 + +同时,它还将所有被`@import`引用的 wxss 文件所对应的数组内嵌在该函数中的 _C 变量中。 + +我们可以修改`setCssToHead`,然后执行所有的`setCssToHead`,第一遍先判断出 _C 变量中所有的内容是哪个要被引用的 wxss 提供的,第二遍还原所有的 wxss。值得注意的是,可能出于兼容性原因,微信为很多属性自动补上含有`-webkit-`开头的版本,另外几乎所有的 tag 都加上了`wx-`前缀,并将`page`变成了`body`。通过一些 CSS 的 AST ,例如 [CSSTree](https://github.com/csstree/csstree),我们可以去掉这些东西。 + +### json + +app-config.json 中的`page`对象内就是其他各页面所对应的 json , 直接还原即可,余下的内容便是 app.json 中的内容了,除了格式上要作相应转换外,微信还将`iconPath`的内容由原先指向图片文件的地址转换成`iconData`中图片内容的 base64 编码,所幸原来的图片文件仍然保留在包内,通过比较`iconData`中的内容和其他包内文件,我们找到原始的`iconPath`。 + +### wxs + +在 page-frame.html ( 或 app-wxss.js ) 中,我们找到了这样的内容 + +```javascript +f_['a/comm.wxs'] = nv_require("p_a/comm.wxs"); +function np_0(){var nv_module={nv_exports:{}};nv_module.nv_exports = ({nv_bar:nv_some_msg,});return nv_module.nv_exports;} + +f_['b/comm.wxs'] = nv_require("p_b/comm.wxs"); +function np_1(){var nv_module={nv_exports:{}};nv_module.nv_exports = ({nv_bar:nv_some_msg,});return nv_module.nv_exports;} + +f_['b/index.wxml']={}; +f_['b/index.wxml']['foo'] =nv_require("m_b/index.wxml:foo"); +function np_2(){var nv_module={nv_exports:{}};var nv_some_msg = "hello world";nv_module.nv_exports = ({nv_msg:nv_some_msg,});return nv_module.nv_exports;} +f_['b/index.wxml']['some_comms'] =f_['b/comm.wxs'] || nv_require("p_b/comm.wxs"); +f_['b/index.wxml']['some_comms'](); +f_['b/index.wxml']['some_commsb'] =f_['a/comm.wxs'] || nv_require("p_a/comm.wxs"); +f_['b/index.wxml']['some_commsb'](); +``` + +可以看出微信将内嵌和外置的 wxs 都转译成`np_%d`函数,并由`f_`数组来描述他们。转译的主要变换是调用的函数名称都加上了`nv_`前缀。在不严谨的场合,我们可以直接通过文本替换去除这些前缀。 + +### wxml + +相比其他内容,这一段比较复杂,因为微信将原本 类 xml 格式的 wxml 文件直接编译成了 js 代码放入 page-frame.html ( 或 app-wxss.js ) 中,之后通过调用这些代码来构造 virtual-dom,进而渲染网页。 +首先,微信将所有要动态计算的变量放在了一个由函数构造的`z`数组中,构造部分代码如下: + +```javascript +(function(z){var a=11;function Z(ops){z.push(ops)} +Z([3,'index']); +Z([[8],'text',[[4],[[5],[[5],[[5],[1,1]],[1,2]],[1,3]]]]); +})(z); +``` + +其实可以将`[[id],xxx,yyy]`看作由指令与操作数的组合。注意每个这样的数组作为指令所产生的结果会作为外层数组中的操作数,这样可以构成一个树形结构。通过将递归计算的过程改成拼接源代码字符串的过程,我们可以还原出每个数组所对应的实际内容(值得注意的是,由于微信的`Token`解析程序采用了贪心算法,我们必须将连续的`}`翻译为`} }`而非`}}`,否则会被误认为是`Mustache`的结束符)。下文中,将这个数组中记为`z`。 + +然后,对于 wxml 文件的结构,可以将每种可能的 js 语句拆分成 指令 来分析,这里可以用到 [Esprima](https://github.com/jquery/esprima) 这样的 js 的 AST 来简化识别操作,可以很容易分析出以下内容,例如: + +- `var {name}=_n('{tag}')` 创建名称为`{name}`, tag 为`{tag}`的节点。 +- `_r({name},'{attrName}',{id},e,s,gg)` 将`{name}`的`{attrName}`属性修改为`z[{id}]`的值。 +- `_({parName},{name})` 将`{name}`作为`{parName}`的子节点。 +- `var {name}=_o({id},..,..,..)` 创建名称为`{name}`,内容为`z[{id}]`的文本节点。 +- `var {name}=_v()` 创建名称为`{name}`的虚节点( wxml 里恰好提供了功能相当的虚结点`block`, 这句话相当于`var {name}=_n('block')`)。 +- `var {name}=_m('{tag}',['{attrName1}',{id1},'{attrName2}',{id2},...],[],..,..,..)` 创建名称为`{name}`, tag 为`{tag}`的节点,同时将`{attrNameX}`属性修改为`z[f({idX})]`的值(`f`定义为`{idX}`与`{base}`的和;`{base}`初始为`0`,`f`返回的第一个正值后`{base}`即改为该返回值;若返回负值,表示该属性无值)。 +- `return {name}` 名称为`{name}`的节点设为主节点。 +- `cs.***` 调试用语句,无视之。 + +此外`wx:if`结构和`wx:for`可做递归处理。例如,对于如下`wx:if`结构: + +```javascript +var {name}=_v() +_({parName},{name}) +if(_o({id1},e,s,gg)){oD.wxVkey=1 +//content1 +} +else if(_o({id2},e,s,gg)){oD.wxVkey=2 +//content2 +} +else{oD.wxVkey=3 +//content3 +} +``` + +相当于将以下节点放入`{parName}`节点下(`z[{id1}]`应替换为对应的`z`数组中的值): + +```xml + + + + + + + + + +``` + +具体实现中可以将递归时创建好多个`block`,调用子函数时指明将放入`{name}`下(`_({name},{son})`)识别为放入对应`{block}`下。`wx:for`也可类似处理,例如: + +```javascript +var {name}=_v() +_({parName},{name}) +var {funcName}=function(..,..,{fakeRoot},..){ +//content +return {fakeRoot} +} +aDB.wxXCkey=2 +_2({id},{funcName},..,..,..,..,'{item}','{index}','{key}') +``` + +对应(`z[{id1}]`应替换为对应的`z`数组中的值): + +```xml + + + +``` + +调用子函数时指明将放入`{fakeRoot}`下(`_({fakeRoot},{son})`)识别为放入`{name}`下。 + +除此之外,有时我们还要将一组代码标记为一个指令,例如下面: + +```javascript +var lK=_v() +_({parName},lK) +var aL=_o({isId},e,s,gg) +var tM=_gd(x[0],aL,e_,d_) +if(tM){ +var eN=_1({dataId},e,s,gg) || {} +var cur_globalf=gg.f +lK.wxXCkey=3 +tM(eN,eN,lK,gg) +gg.f=cur_globalf +} +else _w(aL,x[0],11,26) +``` + +对应于`{parName}`下添加如下节点: + +```xml + +``` + +还有`import`和`include`的代码比较分散,但其实只要抓住重点的一句话就可以了,例如: + +```javascript +var {name}=e_[x[{to}]].i +//Other code +_ai({name},x[{from}],e_,x[{to}],..,..) +//Other code +{name}.pop() +``` + +对应与(其中的`x`是直接定义在 page-frame.html ( 或 app-wxss.js ) 中的字符串数组): + +```xml + +``` + +而`include`类似: + +```javascript +var {name}=e_[x[0]].j +//Other code +_ic(x[{from}],e_,x[{to}],..,..,..,..); +//Other code +{name}.pop() +``` + +对应与: + +```xml + +``` + +可以看到我们可以在处理时忽略前后两句话,把中间的`_ic`和`_ai`处理好就行了。 + +通过解析 js 把 wxml 大概结构还原后,可能相比编译前的 wxml 显得臃肿,可以考虑自动简化,例如: + +```xml + + + + + +``` + +可简化为: + +```xml + + + +``` + +这样,我们完成了几乎所有 wxapkg包 内容的还原。 + +### 对`z`数组优化后的支持方法 + +`wcc-v0.5vv_20180626_syb_zp`后通过只加载`z`数组中需要的部分来提高小程序运行速度,这也会导致仅考虑到上述内容的解包程序解包失败,这一更新的主要内容如下: + +- 增加z数组的函数:`_rz` `_2z` `_mz` `_1z` `_oz` +- 在每个函数头部增加了`var z=gz$gwx_{$id}()`,来标识使用的z数组id +- 原有的z数组不再存在 +- z数组已以下固定格式出现: + +```javascript +function gz$gwx_{$id}(){ +if( __WXML_GLOBAL__.ops_cached.$gwx_{$id})return __WXML_GLOBAL__.ops_cached.$gwx_{$id} +__WXML_GLOBAL__.ops_cached.$gwx_{$id}=[]; +(function(z){var a=11;function Z(ops){z.push(ops)} + +//... (Z({$content})) + +})(__WXML_GLOBAL__.ops_cached.$gwx_{$id});return __WXML_GLOBAL__.ops_cached.$gwx_{$id} +} +``` + +对于上述变更,将获取`z`数组处修改并添加对`_rz` `_2z` `_mz` `_1z` `_oz`的支持即可。 + +需要注意的是开发版的`z`数组转为如下结构: + +```javascript +(function(z){var a=11;function Z(ops,debugLine){z.push(['11182016',ops,debugLine])} +//... +})//... +``` + +探测到为开发版后应将获取到的`z`数组仅保留数组中的第二项。 + +以及含分包的子包采用 `gz$gwx{$subPackageId}_{$id}` 命名,其中`{$subPackageId}`是一个数字。 + +另外还需要注意,`template`的 `var z=gz$gwx_{$id}` 在`try`块外。 \ No newline at end of file diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..cebaa5c --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,25 @@ +发表 Issue 前请仔细阅读以下内容: + +1. 如果你是要反馈 bug, 请按以下`模板`书写 Issue; +2. 如果你遇到的是 Node.js 使用问题, 请尽可能依赖搜索引擎解决问题; +3. 遇到包依赖问题,请联系对应项目; +4. 任何对某类小程序包的适配问题都应提供 wxapkg 程序包,否则直接 Close 处理; +5. 提交前请确认 wxapkg 程序包版本不小于 v0.6vv_20180111_fbi (直接用文本编辑器打开 wxapkg包搜索 v0.6vv 或 v0.5vv 即可查到,注意版本大小主要比较的是日期), 旧版本不提供支持, 相关 Issue 直接 Close 处理; +6. 直接分包和直接处理含插件的包两个功能暂不支持, 请勿重复发表 Issue, 新样例可在已存在的 Issue 下提出; +7. 请不要在其他 Issue 下发表与该 Issue 无关的回复, 否则将有可能被删除。 + +模板内容如下: + +程序执行命令(可选): + +程序执行错误信息(如果反馈是抛出异常的错误,必填): + +``` +复制到这里 +``` + +程序结果错误信息(如果反馈不是抛出异常的错误, 必填, 请尽可能详细描述): + +程序包(你所要解压的程序包地址, 可为网盘链接, 也可直接上传[上传前请先打包]. 必填): + +其他附加内容: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2147f31 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# wxappUnpacker + +![版本 0.3](https://img.shields.io/badge/版本-0.3-red.svg) ![支持的微信版本 >20180111](https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E7%89%88%E6%9C%AC-%3E=20180111-brightgreen.svg) ![高级特性支持度 0](https://img.shields.io/badge/%E6%94%AF%E6%8C%81-0%25-yellow.svg) + +> Forked from https://github.com/qwerty472123/wxappUnpacker + +> Wechat App(微信小程序, .wxapkg)解包及相关文件(.wxss, .json, .wxs, .wxml)还原工具 + +> 欢迎大家使用本程序解包一些开源或经作者授权的小程序包供学习小程序编写或供在电脑端使用小程序或通过研究本项目代码来了解小程序本地运行的部分原理、发现小程序编译时本身的[一些问题](https://github.com/qwerty472123/wxappUnpacker/commit/73580c3afecad8c59e14ea7252dcedd8034e6c3a)(这个或许现在已经修了)... + +## 当前功能如下(分包功能尚未完成) + +- `node wuConfig.js ` 将 app-config.json 中的内容拆分到各个文件对应的 .json 和 app.json , 并通过搜索 app-config.json 所在文件夹下的所有文件尝试将 iconData 还原为 iconPath 。 +- `node wuJs.js ` 将 app-service.js (或小游戏中的 game.js ) 拆分成一系列原先独立的 javascript 文件,并使用 Uglify-ES 美化,从而尽可能还原编译前的情况。 +- `node wuWxml.js [-m] ` 将编译/混合到 page-frame.html ( 或 app-wxss.js ) 中的 wxml 和 wxs 文件还原为独立的、未编译的文件。如果加上`-m`指令,就会阻止`block`块自动省略,可能帮助解决一些相关过程的 bug 。 +- `node wuWxss.js ` 通过获取文件夹下的 page-frame.html ( 或 app-wxss.js ) 和其他 html 文件的内容,还原出编译前 wxss 文件的内容。 +- `node wuWxapkg.js [-o] [-d] [-s=
] ` 将 wxapkg 文件解包,并将包中上述命令中所提的被编译/混合的文件自动地恢复原状。如果加上`-o`指令,表示仅解包,不做后续操作。如果加上`-d`指令,就会保留编译/混合后所生成的新文件,否则会自动删去这些文件。同时,前面命令中的指令也可直接加在这一命令上。~~而如果需要解压分包,请先解压主包,然后执行`node wuWxapkg.js [-d] -s=
`,其中`Main Dir`为主包解压地址。除`-d`与`-s`外,这些指令两两共存的后果是未定义的(当然,是不会有危险的)。~~ + +### wxapkg 包的获取 + +Android 手机最近使用过的微信小程序所对应的 wxapkg 包文件都存储在特定文件夹下,可通过以下命令查看: + + adb pull /data/data/com.tencent.mm/MicroMsg/{User}/appbrand/pkg + +其中`{User}` 为当前用户的用户名,类似于 `2bc**************b65`。 + +### 另注 + +所有命令上都可以使用`-f`指令来提高一定的并行度,但输出信息会混乱。 + +如果发现包内文件“缺失”,请先检查解包时是否出现提示`NOTICE: SubPackages exist in this package.`。如存在,请在寻找好分包后,按上文提示操作。(小程序需要访问特定页面;小游戏需要触发特定函数,然后分包才会被下载。) + +### 局限(包括但可能不限于以下内容) + +- 实现中很多功能基于特定的版本(`wcc-v0.6vv_20180111_fbi`, 且不考虑面向低版本适配)和字符串搜索,所以不能很好的适应各种特殊情况。 +- wxml 文件拥有不同于 xml 和 html 文件的字符转义规则,且尚未公开(并非"没有"),因此未能很好的还原相关内容。 +- js 文件被压缩后会丢失原始变量名等信息内容无法还原;wxss 文件压缩后的注释也会丢失。 +- wxs 文件会将所有的变量如 Math 改为 nv_Math ,这里仅通过字符串替换去除。 +- 一些被引用 wxss 文件本身的源文件丢失,因此无法恢复原始目录。 +- 有些项目开启了难以复原的`es6转es5`选项,检验本项目结果是否正确时需要关闭项目中的`es6转es5`选项。 +- wxml 中一些无法找到相对应 的正向语句的内容无法还原。 +- json 中`components`项丢失,仅会标注被其他包引用的自定义组件。 + +## 依赖 + +这些 node.js 程序除了自带的 API 外还依赖于以下包: +[cssbeautify](https://github.com/senchalabs/cssbeautify)、[CSSTree](https://github.com/csstree/csstree)、[VM2](https://github.com/patriksimek/vm2)、[Esprima](https://github.com/jquery/esprima)、[UglifyES](https://github.com/mishoo/UglifyJS2/tree/harmony)、[js-beautify](https://github.com/beautify-web/js-beautify) + +您需要安装这些包才能正确执行这些程序,为了做到这一点,您可以执行`npm install`;另外如需全局安装这些包可执行以下命令: + + npm install esprima -g + npm install css-tree -g + npm install cssbeautify -g + npm install vm2 -g + npm install uglify-es -g + npm install js-beautify -g + npm install escodegen -g + +此外,这些 node.js 程序之间也有一定的依赖关系,比如他们都依赖于 wuLib.js 。 + + +## 参考 + +这些实现除了参考微信小程序开发文档、 一个开源微信小程序和一些 Issue 提供的 wxapkg 文件解包后的内容以及通过开发者工具编译的一些 wxml 外,还参考了一些 github 上的相关内容的分析( [unwxapkg.py](https://gist.github.com/feix/32ab8f0dfe99aa8efa84f81ed68a0f3e)、[wechat-app-unpack](https://github.com/leo9960/wechat-app-unpack/) ),在此感谢他们。 + +另外,如果您对本程序的一些具体实现细节感兴趣,可以参考 [DETAILS.md](https://github.com/qwerty472123/wxappUnpacker/blob/master/DETAILS.md) 。 + +## 关于 Issue 的一些说明 + +1. Issue 可以发的内容为项目本身的 bug 反馈或问题解决方案(可为文本、可公开访问的链接等形式)。其他内容一律 Close 或 Delete 处理。 +2. 所有的 bug 反馈不再作强制格式要求,反正我大概也不会看(学业繁忙,告辞)。不过建议按格式来,不然其他人也很难得知具体的问题。当然请对自己在 Issue 中发布的内容负责。 diff --git a/package.json b/package.json new file mode 100644 index 0000000..33db075 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "wxapp-unpacker", + "version": "0.3", + "description": "Wechat App(微信小程序, .wxapkg)解包及相关文件(.wxss, .json, .wxs, .wxml)还原工具", + "main": "wuWxapkg.js", + "repository": { + "type": "git", + "url": "git+https://github.com/qwerty472123/wxappUnpacker.git" + }, + "author": "qwerty472123", + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/qwerty472123/wxappUnpacker/issues" + }, + "homepage": "https://github.com/qwerty472123/wxappUnpacker#readme", + "dependencies": { + "css-tree": "^1.0.0-alpha.37", + "cssbeautify": "^0.3.1", + "escodegen": "^1.12.0", + "esprima": "^4.0.1", + "js-beautify": "^1.10.2", + "uglify-es": "^3.3.9", + "vm2": "^3.8.4" + } +} diff --git a/wuConfig.js b/wuConfig.js new file mode 100644 index 0000000..f8ea27c --- /dev/null +++ b/wuConfig.js @@ -0,0 +1,106 @@ +const wu=require("./wuLib.js"); +const fs=require("fs"); +const path=require("path"); +const crypto=require("crypto"); +const {VM}=require('vm2'); +function getWorkerPath(name){ + let code=fs.readFileSync(name,{encoding:'utf8'}); + let commPath=false; + let vm=new VM({sandbox:{ + require(){}, + define(name){ + name=path.dirname(name)+'/'; + if(commPath===false)commPath=name; + commPath=wu.commonDir(commPath,name); + } + }}); + vm.run(code.slice(code.indexOf("define("))); + if(commPath.length>0)commPath=commPath.slice(0,-1); + console.log("Worker path: \""+commPath+"\""); + return commPath; +} +function doConfig(configFile,cb){ + let dir=path.dirname(configFile); + wu.get(configFile,content=>{ + let e=JSON.parse(content); + let k=e.pages; + k.splice(k.indexOf(wu.changeExt(e.entryPagePath)),1); + k.unshift(wu.changeExt(e.entryPagePath)); + let app={pages:k,window:e.global&&e.global.window,tabBar:e.tabBar,networkTimeout:e.networkTimeout}; + if(e.subPackages){ + app.subPackages=e.subPackages; + console.log("=======================================================\nNOTICE: SubPackages exist in this package.\nDetails: ",app.subPackages,"\n======================================================="); + } + if(e.navigateToMiniProgramAppIdList)app.navigateToMiniProgramAppIdList=e.navigateToMiniProgramAppIdList; + if(fs.existsSync(path.resolve(dir,"workers.js")))app.workers=getWorkerPath(path.resolve(dir,"workers.js")); + if(e.extAppid) + wu.save(path.resolve(dir,'ext.json'),JSON.stringify({extEnable:true,extAppid:e.extAppid,ext:e.ext},null,4)); + if(typeof e.debug!="undefined")app.debug=e.debug; + let cur=path.resolve("./file"); + for(let a in e.page)if(e.page[a].window.usingComponents) + for(let name in e.page[a].window.usingComponents){ + let componentPath=e.page[a].window.usingComponents[name]+".html"; + let file=componentPath.startsWith('/')?componentPath.slice(1):wu.toDir(path.resolve(path.dirname(a),componentPath),cur); + if(!e.page[file])e.page[file]={}; + if(!e.page[file].window)e.page[file].window={}; + e.page[file].window.component=true; + } + if(fs.existsSync(path.resolve(dir,"app-service.js"))){ + let matches=fs.readFileSync(path.resolve(dir,"app-service.js"),{encoding:'utf8'}).match(/\_\_wxAppCode\_\_\['[^\.]+\.json[^;]+\;/g); + if(matches){ + let attachInfo={}; + (new VM({sandbox:{ + __wxAppCode__:attachInfo + }})).run(matches.join("")); + for(let name in attachInfo)e.page[wu.changeExt(name,".html")]={window:attachInfo[name]}; + } + } + let delWeight=8; + for(let a in e.page){ + let fileName=path.resolve(dir,wu.changeExt(a,".json")); + wu.save(fileName,JSON.stringify(e.page[a].window,null,4)); + if(configFile==fileName)delWeight=0; + } + if(app.tabBar&&app.tabBar.list) wu.scanDirByExt(dir,"",li=>{//search all files + let digests=[],digestsEvent=new wu.CntEvent,rdir=path.resolve(dir); + function fixDir(dir){return dir.startsWith(rdir)?dir.slice(rdir.length+1):dir;} + digestsEvent.add(()=>{ + for(let e of app.tabBar.list){ + e.pagePath=wu.changeExt(e.pagePath); + if(e.iconData){ + let hash=crypto.createHash("MD5").update(e.iconData,'base64').digest(); + for(let [buf,name] of digests)if(hash.equals(buf)){ + delete e.iconData; + e.iconPath=fixDir(name).replace(/\\/g,'/'); + break; + } + } + if(e.selectedIconData){ + let hash=crypto.createHash("MD5").update(e.selectedIconData,'base64').digest(); + for(let [buf,name] of digests)if(hash.equals(buf)){ + delete e.selectedIconData; + e.selectedIconPath=fixDir(name).replace(/\\/g,'/'); + break; + } + } + } + wu.save(path.resolve(dir,'app.json'),JSON.stringify(app,null,4)); + cb({[configFile]:delWeight}); + }); + for(let name of li){ + digestsEvent.encount(); + wu.get(name,data=>{ + digests.push([crypto.createHash("MD5").update(data).digest(),name]); + digestsEvent.decount(); + },{}); + } + });else{ + wu.save(path.resolve(dir,'app.json'),JSON.stringify(app,null,4)); + cb({[configFile]:delWeight}); + } + }); +} +module.exports={doConfig:doConfig}; +if(require.main===module){ + wu.commandExecute(doConfig,"Split and make up weapp app-config.json file.\n\n\n\n app-config.json files to split and make up."); +} diff --git a/wuJs.js b/wuJs.js new file mode 100644 index 0000000..b9db608 --- /dev/null +++ b/wuJs.js @@ -0,0 +1,39 @@ +const wu=require("./wuLib.js"); +const path=require("path"); +const UglifyJS=require("uglify-es"); +const {js_beautify}=require("js-beautify"); +const {VM}=require('vm2'); +function jsBeautify(code){ + return UglifyJS.minify(code,{mangle:false,compress:false,output:{beautify:true,comments:true}}).code; +} +function splitJs(name,cb){ + let dir=path.dirname(name); + wu.get(name,code=>{ + let needDelList={}; + let vm=new VM({sandbox:{ + require(){}, + define(name,func){ + let code=func.toString(); + code=code.slice(code.indexOf("{")+1,code.lastIndexOf("}")-1).trim(); + let bcode=code; + if(code.startsWith('"use strict";')||code.startsWith("'use strict';"))code=code.slice(13); + else if((code.startsWith('(function(){"use strict";')||code.startsWith("(function(){'use strict';"))&&code.endsWith("})();"))code=code.slice(25,-5); + let res=jsBeautify(code); + if(typeof res=="undefined"){ + console.log("Fail to delete 'use strict' in \""+name+"\"."); + res=jsBeautify(bcode); + } + needDelList[path.resolve(dir,name)]=-8; + wu.save(path.resolve(dir,name),jsBeautify(res)); + } + }}); + vm.run(code.slice(code.indexOf("define("))); + console.log("Splitting \""+name+"\" done."); + if(!needDelList[name])needDelList[name]=8; + cb(needDelList); + }); +} +module.exports={jsBeautify:jsBeautify,wxsBeautify:js_beautify,splitJs:splitJs}; +if(require.main===module){ + wu.commandExecute(splitJs,"Split and beautify weapp js file.\n\n\n\n js files to split and beautify."); +} diff --git a/wuLib.js b/wuLib.js new file mode 100644 index 0000000..8850d51 --- /dev/null +++ b/wuLib.js @@ -0,0 +1,165 @@ +const fs=require("fs"); +const path=require("path"); +class CntEvent{ + constructor(){ + this.cnt=0; + this.emptyEvent=[]; + this.encount=this.encount.bind(this); + this.decount=this.decount.bind(this); + this.add=this.add.bind(this); + } + encount(delta=1){ + this.cnt+=delta; + } + decount(){ + if(this.cnt>0)--this.cnt; + if(this.cnt==0){ + for(let info of this.emptyEvent)info[0](...info[1]); + this.emptyEvent=[]; + } + } + add(cb,...attach){ + this.emptyEvent.push([cb,attach]); + } + check(cb,...attach){ + if(this.cnt==0)cb(...attach); + else this.add(cb,...attach); + } +} +class LimitedRunner{ + constructor(limit){ + this.limit=limit; + this.cnt=0; + this.funcs=[]; + } + run(func){ + if(this.cnt0)this.cnt--; + if(this.funcs.length>0){ + this.cnt++; + setTimeout(this.funcs.shift(),0); + } + } + runWithCb(func,...args){ + let cb=args.pop(),self=this; + function agent(...args){ + self.done(); + return cb.apply(this,args); + } + args.push(agent); + this.run(()=>func(...args)); + } +} +let ioEvent=new CntEvent; +let ioLimit=new LimitedRunner(4096); +function mkdirs(dir,cb){ + ioLimit.runWithCb(fs.stat.bind(fs),dir,(err,stats)=>{ + if(err)mkdirs(path.dirname(dir),()=>fs.mkdir(dir,cb)); + else if(stats.isFile())throw Error(dir+" was created as a file, so we cannot put file into it."); + else cb(); + }); +} +function save(name,content){ + ioEvent.encount(); + mkdirs(path.dirname(name),()=>ioLimit.runWithCb(fs.writeFile.bind(fs),name,content,err=>{ + if(err)throw Error("Save file error: "+err); + ioEvent.decount(); + })); +} +function get(name,cb,opt={encoding:'utf8'}){ + ioEvent.encount(); + ioLimit.runWithCb(fs.readFile.bind(fs),name,opt,(err,data)=>{ + if(err)throw Error("Read file error: "+err); + else cb(data); + ioEvent.decount(); + }); +} +function del(name){ + ioEvent.encount(); + ioLimit.runWithCb(fs.unlink.bind(fs),name,ioEvent.decount); +} +function changeExt(name,ext=""){ + return name.slice(0,name.lastIndexOf("."))+ext; +} +function scanDirByExt(dir,ext,cb){ + let result=[],scanEvent=new CntEvent; + function helper(dir){ + scanEvent.encount(); + ioLimit.runWithCb(fs.readdir.bind(fs),dir,(err,files)=>{ + if(err)throw Error("Scan dir error: "+err); + for(let file of files){ + scanEvent.encount(); + let name=path.resolve(dir,file); + fs.stat(name,(err,stats)=>{ + if(err)throw Error("Scan dir error: "+err); + if(stats.isDirectory())helper(name); + else if(stats.isFile()&&name.endsWith(ext))result.push(name); + scanEvent.decount(); + }); + } + scanEvent.decount(); + }); + } + scanEvent.add(cb,result); + helper(dir,ext,scanEvent); +} +function toDir(to,from){//get relative path without posix/win32 problem + if(from[0]==".")from=from.slice(1); + if(to[0]==".")to=to.slice(1); + from=from.replace(/\\/g,'/');to=to.replace(/\\/g,'/'); + let a=Math.min(to.length,from.length); + for(let i=1,m=Math.min(to.length,from.length);i<=m;i++)if(!to.startsWith(from.slice(0,i))){a=i-1;break;} + let pub=from.slice(0,a); + let len=pub.lastIndexOf("/")+1; + let k=from.slice(len); + let ret=""; + for(let i=0;iconsole.timeEnd("Total use")); + } + let orders=[]; + for(let order of process.argv)if(order.startsWith("-"))orders.push(order.slice(1)); + let iter=process.argv[Symbol.iterator](),nxt=iter.next(),called=false,faster=orders.includes("f"),fastCnt; + if(faster){ + fastCnt=new CntEvent; + fastCnt.add(endTime); + } + function doNext(){ + let nxt=iter.next(); + while(!nxt.done&&nxt.value.startsWith("-"))nxt=iter.next(); + if(nxt.done){ + if(!called)console.log("Command Line Helper:\n\n"+helper); + else if(!faster)endTime(); + }else{ + called=true; + if(faster)fastCnt.encount(),cb(nxt.value,fastCnt.decount,orders),doNext(); + else cb(nxt.value,doNext,orders); + } + } + while(!nxt.done&&!nxt.value.endsWith(".js"))nxt=iter.next(); + doNext(); +} +module.exports={mkdirs:mkdirs,get:get,save:save,toDir:toDir,del:del,addIO:ioEvent.add, + changeExt:changeExt,CntEvent:CntEvent,scanDirByExt:scanDirByExt,commonDir:commonDir, + commandExecute:commandExecute}; diff --git a/wuRestoreZ.js b/wuRestoreZ.js new file mode 100644 index 0000000..8f63690 --- /dev/null +++ b/wuRestoreZ.js @@ -0,0 +1,245 @@ +const wu=require("./wuLib.js"); +const {VM}=require('vm2'); +function catchZGroup(code,groupPreStr,cb){ + const debugPre="(function(z){var a=11;function Z(ops,debugLine){"; + let zArr={}; + for(let preStr of groupPreStr){ + let content=code.slice(code.indexOf(preStr)),z=[]; + content=content.slice(content.indexOf("(function(z){var a=11;")); + content=content.slice(0,content.indexOf("})(__WXML_GLOBAL__.ops_cached.$gwx"))+"})(z);"; + let vm=new VM({sandbox:{z:z,debugInfo:[]}}); + vm.run(content); + if(content.startsWith(debugPre))for(let i=0;i=":11,"<=":11,">":11,"<":11,"<<":12,">>":12, + "-":len==3?13:16 + }; + return priorList[op]?priorList[op]:0; + } + function getOp(i){ + let ret=restoreNext(ops[i],true); + if(ops[i] instanceof Object&&typeof ops[i][0]=="object"&&ops[i][0][0]==2){ + //Add brackets if we need + 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; + }//shoud not add more in there![fall through] + default: + ans=getOp(1)+op[1]+getOp(2); + } + break; + } + case 4://unkown-arrayStart? + ans=restoreNext(ops[1],true); + break; + case 5://merge-array + { + switch (ops.length) { + case 2: + ans=enBrace(restoreNext(ops[1],true),'['); + break; + case 1: + ans='[]'; + break; + default: + { + let a=restoreNext(ops[1],true); + //console.log(a,a.startsWith('[')&&a.endsWith(']')); + if(a.startsWith('[')&&a.endsWith(']')){ + if(a!='[]'){ + ans=enBrace(a.slice(1,-1).trim()+','+restoreNext(ops[2],true),'['); + //console.log('-',a); + }else{ + ans=enBrace(restoreNext(ops[2],true),'['); + } + }else{ + ans=enBrace('...'+a+','+restoreNext(ops[2],true),'[');//may/must not support in fact + } + } + } + break; + } + case 6://get value of an object + { + 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)/*is a qualified id*/) + attach='.'+sonName; + else attach=enBrace(sonName,'['); + ans=restoreNext(ops[1],true)+attach; + } + break; + } + case 7://get value of str + { + 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://first object + ans=enBrace(ops[1]+':'+restoreNext(ops[2],true),'{');//ops[1] have only this way to define + break; + case 9://object + { + function type(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(); + //console.log(l,r); + ans=enBrace(a+','+b,'{'); + } + break; + } + case 10://...object + 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 restoreGroup(z){ + let ans=[]; + for(let g in z.mul){ + let singleAns=[]; + for(let e of z.mul[g])singleAns.push(restoreSingle(e,false)); + ans[g]=singleAns; + } + let ret=[];//Keep a null array for remaining global Z array. + ret.mul=ans; + return ret; +} +function restoreAll(z){ + if(z.mul)return restoreGroup(z); + let ans=[]; + for(let e of z)ans.push(restoreSingle(e,false)); + return ans; +} +module.exports={getZ(code,cb){ + catchZ(code,z=>cb(restoreAll(z))); +}}; diff --git a/wuWxapkg.js b/wuWxapkg.js new file mode 100644 index 0000000..b4a07da --- /dev/null +++ b/wuWxapkg.js @@ -0,0 +1,120 @@ +const wu=require("./wuLib.js"); +const wuJs=require("./wuJs.js"); +const wuCfg=require("./wuConfig.js"); +const wuMl=require("./wuWxml.js"); +const wuSs=require("./wuWxss.js"); +const path=require("path"); +const fs=require("fs"); +function header(buf){ + console.log("\nHeader info:"); + let firstMark=buf.readUInt8(0); + console.log(" firstMark: 0x%s",firstMark.toString(16)); + let unknownInfo=buf.readUInt32BE(1); + console.log(" unknownInfo: ",unknownInfo); + let infoListLength=buf.readUInt32BE(5); + console.log(" infoListLength: ",infoListLength); + let dataLength=buf.readUInt32BE(9); + console.log(" dataLength: ",dataLength); + let lastMark=buf.readUInt8(13); + console.log(" lastMark: 0x%s",lastMark.toString(16)); + if(firstMark!=0xbe||lastMark!=0xed)throw Error("Magic number is not correct!"); + return [infoListLength,dataLength]; +} +function genList(buf){ + console.log("\nFile list info:"); + let fileCount=buf.readUInt32BE(0); + console.log(" fileCount: ",fileCount); + let fileInfo=[],off=4; + for(let i=0;i{ + wu.addIO(()=>{ + console.log("Split and make up done."); + if(!order.includes("d")){ + console.log("Delete files..."); + wu.addIO(()=>console.log("Deleted.\n\nFile done.")); + for(let name in needDelete)if(needDelete[name]>=8)wu.del(name); + } + cb(); + }); + }); + function doBack(deletable){ + for(let key in deletable){ + if(!needDelete[key])needDelete[key]=0; + needDelete[key]+=deletable[key];//all file have score bigger than 8 will be delete. + } + weappEvent.decount(); + } + //This will be the only func running this time, so async is needless. + if(fs.existsSync(path.resolve(dir,"app-service.js"))){//weapp + console.log("Split app-service.js and make up configs & wxss & wxml & wxs..."); + wuCfg.doConfig(path.resolve(dir,"app-config.json"),doBack); + wuJs.splitJs(path.resolve(dir,"app-service.js"),doBack); + if(fs.existsSync(path.resolve(dir,"workers.js"))) + wuJs.splitJs(path.resolve(dir,"workers.js"),doBack); + if(fs.existsSync(path.resolve(dir,"page-frame.html"))) + wuMl.doFrame(path.resolve(dir,"page-frame.html"),doBack,order); + else if(fs.existsSync(path.resolve(dir,"app-wxss.js"))) { + wuMl.doFrame(path.resolve(dir,"app-wxss.js"),doBack,order); + if(!needDelete[path.resolve(dir,"page-frame.js")])needDelete[path.resolve(dir,"page-frame.js")]=8; + } else throw Error("page-frame-like file is not found in the package by auto."); + wuSs.doWxss(dir,doBack);//Force it run at last, becuase lots of error occured in this part + }else if(fs.existsSync(path.resolve(dir,"game.js"))){//wegame + console.log("Split game.js and rewrite game.json..."); + let gameCfg=path.resolve(dir,"app-config.json"); + wu.get(gameCfg,cfgPlain=>{ + let cfg=JSON.parse(cfgPlain); + if(cfg.subContext){ + console.log("Found subContext, splitting it...") + delete cfg.subContext; + let contextPath=path.resolve(dir,"subContext.js"); + wuJs.splitJs(contextPath,()=>wu.del(contextPath)); + } + wu.save(path.resolve(dir,"game.json"),JSON.stringify(cfg,null,4)); + wu.del(gameCfg); + }); + wuJs.splitJs(path.resolve(dir,"game.js"),()=>{ + wu.addIO(()=>{ + console.log("Split and rewrite done."); + cb(); + }); + }); + }else throw Error("This package is unrecognizable.\nMay be this package is a subPackage which should be unpacked with -s=.\nOtherwise, please decrypted every type of file by hand.") +} +function doFile(name,cb,order){ + for(let ord of order)if(ord.startsWith("s="))global.subPack=ord.slice(3); + console.log("Unpack file "+name+"..."); + let dir=path.resolve(name,"..",path.basename(name,".wxapkg")); + wu.get(name,buf=>{ + let [infoListLength,dataLength]=header(buf.slice(0,14)); + if(order.includes("o"))wu.addIO(console.log.bind(console),"Unpack done."); + else wu.addIO(packDone,dir,cb,order); + saveFile(dir,buf,genList(buf.slice(14,infoListLength+14))); + },{}); +} +module.exports={doFile:doFile}; +if(require.main===module){ + wu.commandExecute(doFile,"Unpack a wxapkg file.\n\n[-o] [-d] [-s=
] \n\n-d Do not delete transformed unpacked files.\n-o Do not execute any operation after unpack.\n-s=
Regard all packages provided as subPackages and\n regard
as the directory of sources of the main package.\n wxapkg files to unpack"); +} diff --git a/wuWxml.js b/wuWxml.js new file mode 100644 index 0000000..50a442f --- /dev/null +++ b/wuWxml.js @@ -0,0 +1,392 @@ +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;ei0) + 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){ + const before='nv_module={nv_exports:{}};'; + return wxsBeautify(code.slice(code.indexOf(before)+before.length,code.lastIndexOf('return nv_module.nv_exports;}')).replace(/nv\_/g,'')); +} +function doFrame(name,cb,order){ + 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.indexOf(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; + }}}); + vm.run(code+"\n_vmRev_([x,"+json+"])"); + let dir=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())); + } + 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."); +} diff --git a/wuWxss.js b/wuWxss.js new file mode 100644 index 0000000..7322178 --- /dev/null +++ b/wuWxss.js @@ -0,0 +1,215 @@ +const wu=require("./wuLib.js"); +const path=require("path"); +const fs=require("fs"); +const {VM}=require('vm2'); +const cssbeautify=require('cssbeautify'); +const csstree=require('css-tree'); +function doWxss(dir,cb){ + function GwxCfg(){} + GwxCfg.prototype={$gwx(){}}; + for(let i=0;i<300;i++)GwxCfg.prototype["$gwx"+i]=GwxCfg.prototype.$gwx; + let runList={},pureData={},result={},actualPure={},importCnt={},frameName="",onlyTest=true,blockCss=[];//custom block css file which won't be imported by others.(no extension name) + function cssRebuild(data){//need to bind this as {cssFile:__name__} before call + let cssFile; + function statistic(data){ + function addStat(id){ + if(!importCnt[id])importCnt[id]=1,statistic(pureData[id]); + else ++importCnt[id]; + } + if(typeof data==="number")return addStat(data); + for(let content of data)if(typeof content==="object"&&content[0]==2)addStat(content[1]); + } + function makeup(data){ + var isPure=typeof data==="number"; + if(onlyTest){ + statistic(data); + if(!isPure){ + if(data.length==1&&data[0][0]==2)data=data[0][1]; + else return ""; + } + if(!actualPure[data]&&!blockCss.includes(wu.changeExt(wu.toDir(cssFile,frameName),""))){ + console.log("Regard "+cssFile+" as pure import file."); + actualPure[data]=cssFile; + } + return ""; + } + let res=[],attach=""; + if(isPure&&actualPure[data]!=cssFile){ + if(actualPure[data])return '@import "'+wu.changeExt(wu.toDir(actualPure[data],cssFile),".wxss")+'";\n'; + else{ + res.push("/*! Import by _C["+data+"], whose real path we cannot found. */"); + attach="/*! Import end */"; + } + } + let exactData=isPure?pureData[data]:data; + for(let content of exactData) + if(typeof content==="object"){ + switch(content[0]){ + case 0://rpx + res.push(content[1]+"rpx"); + break; + case 1://add suffix, ignore it for restoring correct! + break; + case 2://import + res.push(makeup(content[1])); + break; + } + }else res.push(content); + return res.join("")+attach; + } + return ()=>{ + cssFile=this.cssFile; + if(!result[cssFile])result[cssFile]=""; + result[cssFile]+=makeup(data); + }; + } + function runVM(name,code){ + // let wxAppCode={},handle={cssFile:name}; + // let vm=new VM({sandbox:Object.assign(new GwxCfg(),{__wxAppCode__:wxAppCode,setCssToHead:cssRebuild.bind(handle)})}); + // vm.run(code); + // for(let name in wxAppCode)if(name.endsWith(".wxss")){ + // handle.cssFile=path.resolve(frameName,"..",name); + // wxAppCode[name](); + // } + let wxAppCode = {}; + let handle = {cssFile: name}; + let gg = new GwxCfg(); + let tsandbox = { + $gwx: GwxCfg.prototype["$gwx"], + __mainPageFrameReady__: GwxCfg.prototype["$gwx"], //解决 $gwx is not defined + __vd_version_info__: GwxCfg.prototype["$gwx"], //解决 __vd_version_info__ is not defined + __wxAppCode__: wxAppCode, + setCssToHead: cssRebuild.bind(handle) + } + + let vm = new VM({sandbox: tsandbox}); + vm.run(code); + for (let name in wxAppCode) { + if (name.endsWith(".wxss")) { + handle.cssFile = path.resolve(frameName, "..", name); + wxAppCode[name](); + } + } + } + function preRun(dir,frameFile,mainCode,files,cb){ + wu.addIO(cb); + runList[path.resolve(dir,"./app.wxss")]=mainCode; + for(let name of files)if(name!=frameFile){ + wu.get(name,code=>{ + code=code.slice(0,code.indexOf("\n")); + if(code.indexOf("setCssToHead")>-1)runList[name]=code.slice(code.indexOf("setCssToHead")); + }); + } + } + function runOnce(){ + for(let name in runList)runVM(name,runList[name]); + } + function transformCss(style){ + let ast=csstree.parse(style); + csstree.walk(ast,function(node){ + if(node.type=="Comment"){//Change the comment because the limit of css-tree + node.type="Raw"; + node.value="\n/*"+node.value+"*/\n"; + } + if(node.type=="TypeSelector"){ + if(node.name.startsWith("wx-"))node.name=node.name.slice(3); + else if(node.name=="body")node.name="page"; + } + if(node.children){ + const removeType=["webkit","moz","ms","o"]; + let list={}; + node.children.each((son,item)=>{ + if(son.type=="Declaration"){ + if(list[son.property]){ + let a=item,b=list[son.property],x=son,y=b.data,ans=null; + if(x.value.type=='Raw'&&x.value.value.startsWith("progid:DXImageTransform")){ + node.children.remove(a); + ans=b; + }else if(y.value.type=='Raw'&&y.value.value.startsWith("progid:DXImageTransform")){ + node.children.remove(b); + ans=a; + }else{ + let xValue=x.value.children&&x.value.children.head&&x.value.children.head.data.name,yValue=y.value.children&&y.value.children.head&&y.value.children.head.data.name; + if(xValue&&yValue)for(let type of removeType)if(xValue==`-${type}-${yValue}`){ + node.children.remove(a); + ans=b; + break; + }else if(yValue==`-${type}-${xValue}`){ + node.children.remove(b); + ans=a; + break; + }else{ + let mValue=`-${type}-`; + if(xValue.startsWith(mValue))xValue=xValue.slice(mValue.length); + if(yValue.startsWith(mValue))yValue=yValue.slice(mValue.length); + } + if(ans===null)ans=b; + } + list[son.property]=ans; + }else list[son.property]=item; + } + }); + for(let name in list)if(!name.startsWith('-')) + for(let type of removeType){ + let fullName=`-${type}-${name}`; + if(list[fullName]){ + node.children.remove(list[fullName]); + delete list[fullName]; + } + } + } + }); + return cssbeautify(csstree.generate(ast),{indent:' ',autosemicolon:true}); + } + wu.scanDirByExt(dir,".html",files=>{ + let frameFile=""; + if(fs.existsSync(path.resolve(dir,"page-frame.html"))) + frameFile=path.resolve(dir,"page-frame.html"); + else if(fs.existsSync(path.resolve(dir,"app-wxss.js"))) + frameFile=path.resolve(dir,"app-wxss.js"); + else if(fs.existsSync(path.resolve(dir,"page-frame.js"))) + frameFile=path.resolve(dir,"page-frame.js"); + else throw Error("page-frame-like file is not found in the package by auto."); + wu.get(frameFile,code=>{ + code=code.slice(code.indexOf('var setCssToHead = function(file, _xcInvalid')); + code=code.slice(code.indexOf('\nvar _C= ')+1); + let oriCode=code; + code=code.slice(0,code.indexOf('\n')); + let vm=new VM({sandbox:{}}); + pureData=vm.run(code+"\n_C"); + let mainCode=oriCode.slice(oriCode.indexOf("setCssToHead"),oriCode.lastIndexOf(";var __pageFrameEndTime__")); + console.log("Guess wxss(first turn)..."); + preRun(dir,frameFile,mainCode,files,()=>{ + frameName=frameFile; + onlyTest=true; + runOnce(); + onlyTest=false; + console.log("Import count info: %j",importCnt); + for(let id in pureData)if(!actualPure[id]){ + if(!importCnt[id])importCnt[id]=0; + if(importCnt[id]<=1){ + console.log("Cannot find pure import for _C["+id+"] which is only imported "+importCnt[id]+" times. Let importing become copying."); + }else{ + let newFile=path.resolve(dir,"__wuBaseWxss__/"+id+".wxss"); + console.log("Cannot find pure import for _C["+id+"], force to save it in ("+newFile+")."); + id=Number.parseInt(id); + actualPure[id]=newFile; + cssRebuild.call({cssFile:newFile},id)(); + } + } + console.log("Guess wxss(first turn) done.\nGenerate wxss(second turn)..."); + runOnce() + console.log("Generate wxss(second turn) done.\nSave wxss..."); + for(let name in result)wu.save(wu.changeExt(name,".wxss"),transformCss(result[name])); + let delFiles={}; + for(let name of files)delFiles[name]=8; + delFiles[frameFile]=4; + cb(delFiles); + }); + }); + }); +} +module.exports={doWxss:doWxss}; +if(require.main===module){ + wu.commandExecute(doWxss,"Restore wxss files.\n\n\n\n restore wxss file from a unpacked directory(Have page-frame.html (or app-wxss.js) and other html file)."); +}