├── .DS_Store
├── .gitignore
├── DETAILS.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── bingo.sh
├── de_miniapp.sh
├── image-1.png
├── image.png
├── install.sh
├── package.json
├── proxy_set.sh
├── testpkg
├── .DS_Store
├── _-751579163_42.wxapkg
├── cc.png
├── jinqianli_miniapp_logo.png
├── jinqianli_shoukuan.png
└── testdir.png
├── wuConfig.js
├── wuJs.js
├── wuLib.js
├── wuRestoreZ.js
├── wuWxapkg.js
├── wuWxml.js
├── wuWxss.js
└── 解密工具
├── README.md
└── UnpackMiniApp.exe
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | gen/
4 | .ant-targets-build.xml
5 | project.properties
6 | auto.prop
7 | .classpath
8 | .settings/
9 | *.iml
10 |
11 | .idea/
12 | local.properties
13 | build.xml
14 | 6.1-main.iml
15 | .gradle/
16 | build/
17 | captures/
--------------------------------------------------------------------------------
/DETAILS.md:
--------------------------------------------------------------------------------
1 | # 关于还原的详细信息
2 |
3 | ### wxapkg 包
4 |
5 | 对于 wxapkg 包文件格式的分析已在网上广泛流传,可整理为如下内容(请注意该文件中的`uint32`都是以`大端序`方式存放):
6 |
7 | ```c++
8 | typedef unsigned char uint8;
9 | typedef unsigned int uint32;//Notice: uint32 use BIG-ENDIAN, not Little.
10 |
11 | struct wxHeader {
12 | uint8 firstMark;// one of magic number, which is equal to 0xbe
13 | uint32 unknownInfo;// this info was always set to zero. maybe it's the verison of file?
14 | uint32 infoListLength;// the length of wxFileInfoList
15 | uint32 dataLength;// the length of dataBuf
16 | uint8 lastMark;// another magic number, which is equal to 0xed
17 | };
18 |
19 | struct wxFileInfo {// illustrate one file in wxapkg pack
20 | uint32 nameLen;// the length of filename
21 | char name[nameLen];// filename, use UTF-8 encoding (translating it to GBK is required in Win)
22 | uint32 fileOff;// the offset of this file (0 is pointing to the begining of this file[struct wxapkgFile])
23 | uint32 fileLen;// the length of this file
24 | };
25 |
26 | struct wxFileInfoList {
27 | uint32 fileCount;// The count of file
28 | wxFileInfo fileInfos[fileCount];
29 | };
30 |
31 | struct wxapkgFile {
32 | wxHeader header;
33 | wxFileInfoList fileInfoList;
34 | uint8 dataBuf[dataLength];
35 | };
36 | ```
37 |
38 | 由上可知,在wxapkg 包中文件头后的位置上有`文件名+文件内容起始地址及长度`信息,且各个文件内容也全是以明文方式存放在包内,从而我们可以获取包内文件。
39 |
40 | 通过解包可知,这个包中的文件内容主要如下:
41 |
42 | - app-config.json
43 | - app-service.js
44 | - page-frame.html ( 也可能是由 app-wxss.js 和 page-frame.js 组成相关信息 )
45 | - 其他一堆放在各文件夹中的.html文件
46 | - 和源码包内位置和内容相同的图片等资源文件
47 |
48 | 微信开发者工具并不能识别这些文件,它要求我们提供由`wxml/wxss/js/wxs/json`组成的源码才能进行模拟/调试。
49 |
50 | ### js
51 |
52 | 注意到`app-service.js`中的内容由
53 |
54 | ```javascript
55 | define('xxx.js',function(...){
56 | //The content of xxx.js
57 | });require('xxx.js');
58 | define('yyy.js',function(...){
59 | //The content of xxx.js
60 | });require('yyy.js');
61 | ....
62 | ```
63 |
64 | 组成,很显然,我们只要定义自己的`define`函数就可以将这些 js 文件恢复到源码中所对应的位置。当然,这些 js 文件中的内容经过压缩,即使使用 UglifyJS 这样的工具进行美化,也无法还原一些原始变量名。
65 |
66 | ### wxss
67 |
68 | 所有在 wxapkg 包中的 html 文件都调用了`setCssToHead`函数,其代码如下
69 |
70 | ```javascript
71 | var setCssToHead = function(file, _xcInvalid) {
72 | var Ca = {};
73 | var _C = [...arrays...];
74 | function makeup(file, suffix) {
75 | var _n = typeof file === "number";
76 | if (_n && Ca.hasOwnProperty(file)) return "";
77 | if (_n) Ca[file] = 1;
78 | var ex = _n ? _C[file] : file;
79 | var res = "";
80 | for (var i = ex.length - 1; i >= 0; i--) {
81 | var content = ex[i];
82 | if (typeof content === "object") {
83 | var op = content[0];
84 | 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;
85 | } else res = content + res;
86 | }
87 | return res;
88 | }
89 | return function(suffix, opt) {
90 | if (typeof suffix === "undefined") suffix = "";
91 | if (opt && opt.allowIllegalSelector != undefined && _xcInvalid != undefined) {
92 | if (opt.allowIllegalSelector) console.warn("For developer:" + _xcInvalid); else {
93 | console.error(_xcInvalid + "This wxss file is ignored.");
94 | return;
95 | }
96 | }
97 | Ca = {};
98 | css = makeup(file, suffix);
99 | var style = document.createElement("style");
100 | var head = document.head || document.getElementsByTagName("head")[0];
101 | style.type = "text/css";
102 | if (style.styleSheet) {
103 | style.styleSheet.cssText = css;
104 | } else {
105 | style.appendChild(document.createTextNode(css));
106 | }
107 | head.appendChild(style);
108 | };
109 | };
110 | ```
111 |
112 | 阅读这段代码可知,它把 wxss 代码拆分成几段数组,数组中的内容可以是一段将要作为 css 文件的字符串,也可以是一个表示 这里要添加一个公共后缀 或 这里要包含另一段代码 或 要将以 wxss 专供的 rpx 单位表达的数字换算成能由浏览器渲染的 px 单位所对应的数字 的数组。
113 |
114 | 同时,它还将所有被`@import`引用的 wxss 文件所对应的数组内嵌在该函数中的 _C 变量中。
115 |
116 | 我们可以修改`setCssToHead`,然后执行所有的`setCssToHead`,第一遍先判断出 _C 变量中所有的内容是哪个要被引用的 wxss 提供的,第二遍还原所有的 wxss。值得注意的是,可能出于兼容性原因,微信为很多属性自动补上含有`-webkit-`开头的版本,另外几乎所有的 tag 都加上了`wx-`前缀,并将`page`变成了`body`。通过一些 CSS 的 AST ,例如 [CSSTree](https://github.com/csstree/csstree),我们可以去掉这些东西。
117 |
118 | ### json
119 |
120 | app-config.json 中的`page`对象内就是其他各页面所对应的 json , 直接还原即可,余下的内容便是 app.json 中的内容了,除了格式上要作相应转换外,微信还将`iconPath`的内容由原先指向图片文件的地址转换成`iconData`中图片内容的 base64 编码,所幸原来的图片文件仍然保留在包内,通过比较`iconData`中的内容和其他包内文件,我们找到原始的`iconPath`。
121 |
122 | ### wxs
123 |
124 | 在 page-frame.html ( 或 app-wxss.js ) 中,我们找到了这样的内容
125 |
126 | ```javascript
127 | f_['a/comm.wxs'] = nv_require("p_a/comm.wxs");
128 | function np_0(){var nv_module={nv_exports:{}};nv_module.nv_exports = ({nv_bar:nv_some_msg,});return nv_module.nv_exports;}
129 |
130 | f_['b/comm.wxs'] = nv_require("p_b/comm.wxs");
131 | function np_1(){var nv_module={nv_exports:{}};nv_module.nv_exports = ({nv_bar:nv_some_msg,});return nv_module.nv_exports;}
132 |
133 | f_['b/index.wxml']={};
134 | f_['b/index.wxml']['foo'] =nv_require("m_b/index.wxml:foo");
135 | 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;}
136 | f_['b/index.wxml']['some_comms'] =f_['b/comm.wxs'] || nv_require("p_b/comm.wxs");
137 | f_['b/index.wxml']['some_comms']();
138 | f_['b/index.wxml']['some_commsb'] =f_['a/comm.wxs'] || nv_require("p_a/comm.wxs");
139 | f_['b/index.wxml']['some_commsb']();
140 | ```
141 |
142 | 可以看出微信将内嵌和外置的 wxs 都转译成`np_%d`函数,并由`f_`数组来描述他们。转译的主要变换是调用的函数名称都加上了`nv_`前缀。在不严谨的场合,我们可以直接通过文本替换去除这些前缀。
143 |
144 | ### wxml
145 |
146 | 相比其他内容,这一段比较复杂,因为微信将原本 类 xml 格式的 wxml 文件直接编译成了 js 代码放入 page-frame.html ( 或 app-wxss.js ) 中,之后通过调用这些代码来构造 virtual-dom,进而渲染网页。
147 | 首先,微信将所有要动态计算的变量放在了一个由函数构造的`z`数组中,构造部分代码如下:
148 |
149 | ```javascript
150 | (function(z){var a=11;function Z(ops){z.push(ops)}
151 | Z([3,'index']);
152 | Z([[8],'text',[[4],[[5],[[5],[[5],[1,1]],[1,2]],[1,3]]]]);
153 | })(z);
154 | ```
155 |
156 | 其实可以将`[[id],xxx,yyy]`看作由指令与操作数的组合。注意每个这样的数组作为指令所产生的结果会作为外层数组中的操作数,这样可以构成一个树形结构。通过将递归计算的过程改成拼接源代码字符串的过程,我们可以还原出每个数组所对应的实际内容(值得注意的是,由于微信的`Token`解析程序采用了贪心算法,我们必须将连续的`}`翻译为`} }`而非`}}`,否则会被误认为是`Mustache`的结束符)。下文中,将这个数组中记为`z`。
157 |
158 | 然后,对于 wxml 文件的结构,可以将每种可能的 js 语句拆分成 指令 来分析,这里可以用到 [Esprima](https://github.com/jquery/esprima) 这样的 js 的 AST 来简化识别操作,可以很容易分析出以下内容,例如:
159 |
160 | - `var {name}=_n('{tag}')` 创建名称为`{name}`, tag 为`{tag}`的节点。
161 | - `_r({name},'{attrName}',{id},e,s,gg)` 将`{name}`的`{attrName}`属性修改为`z[{id}]`的值。
162 | - `_({parName},{name})` 将`{name}`作为`{parName}`的子节点。
163 | - `var {name}=_o({id},..,..,..)` 创建名称为`{name}`,内容为`z[{id}]`的文本节点。
164 | - `var {name}=_v()` 创建名称为`{name}`的虚节点( wxml 里恰好提供了功能相当的虚结点`block`, 这句话相当于`var {name}=_n('block')`)。
165 | - `var {name}=_m('{tag}',['{attrName1}',{id1},'{attrName2}',{id2},...],[],..,..,..)` 创建名称为`{name}`, tag 为`{tag}`的节点,同时将`{attrNameX}`属性修改为`z[f({idX})]`的值(`f`定义为`{idX}`与`{base}`的和;`{base}`初始为`0`,`f`返回的第一个正值后`{base}`即改为该返回值;若返回负值,表示该属性无值)。
166 | - `return {name}` 名称为`{name}`的节点设为主节点。
167 | - `cs.***` 调试用语句,无视之。
168 |
169 | 此外`wx:if`结构和`wx:for`可做递归处理。例如,对于如下`wx:if`结构:
170 |
171 | ```javascript
172 | var {name}=_v()
173 | _({parName},{name})
174 | if(_o({id1},e,s,gg)){oD.wxVkey=1
175 | //content1
176 | }
177 | else if(_o({id2},e,s,gg)){oD.wxVkey=2
178 | //content2
179 | }
180 | else{oD.wxVkey=3
181 | //content3
182 | }
183 | ```
184 |
185 | 相当于将以下节点放入`{parName}`节点下(`z[{id1}]`应替换为对应的`z`数组中的值):
186 |
187 | ```xml
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | ```
198 |
199 | 具体实现中可以将递归时创建好多个`block`,调用子函数时指明将放入`{name}`下(`_({name},{son})`)识别为放入对应`{block}`下。`wx:for`也可类似处理,例如:
200 |
201 | ```javascript
202 | var {name}=_v()
203 | _({parName},{name})
204 | var {funcName}=function(..,..,{fakeRoot},..){
205 | //content
206 | return {fakeRoot}
207 | }
208 | aDB.wxXCkey=2
209 | _2({id},{funcName},..,..,..,..,'{item}','{index}','{key}')
210 | ```
211 |
212 | 对应(`z[{id1}]`应替换为对应的`z`数组中的值):
213 |
214 | ```xml
215 |
216 |
217 |
218 | ```
219 |
220 | 调用子函数时指明将放入`{fakeRoot}`下(`_({fakeRoot},{son})`)识别为放入`{name}`下。
221 |
222 | 除此之外,有时我们还要将一组代码标记为一个指令,例如下面:
223 |
224 | ```javascript
225 | var lK=_v()
226 | _({parName},lK)
227 | var aL=_o({isId},e,s,gg)
228 | var tM=_gd(x[0],aL,e_,d_)
229 | if(tM){
230 | var eN=_1({dataId},e,s,gg) || {}
231 | var cur_globalf=gg.f
232 | lK.wxXCkey=3
233 | tM(eN,eN,lK,gg)
234 | gg.f=cur_globalf
235 | }
236 | else _w(aL,x[0],11,26)
237 | ```
238 |
239 | 对应于`{parName}`下添加如下节点:
240 |
241 | ```xml
242 |
243 | ```
244 |
245 | 还有`import`和`include`的代码比较分散,但其实只要抓住重点的一句话就可以了,例如:
246 |
247 | ```javascript
248 | var {name}=e_[x[{to}]].i
249 | //Other code
250 | _ai({name},x[{from}],e_,x[{to}],..,..)
251 | //Other code
252 | {name}.pop()
253 | ```
254 |
255 | 对应与(其中的`x`是直接定义在 page-frame.html ( 或 app-wxss.js ) 中的字符串数组):
256 |
257 | ```xml
258 |
259 | ```
260 |
261 | 而`include`类似:
262 |
263 | ```javascript
264 | var {name}=e_[x[0]].j
265 | //Other code
266 | _ic(x[{from}],e_,x[{to}],..,..,..,..);
267 | //Other code
268 | {name}.pop()
269 | ```
270 |
271 | 对应与:
272 |
273 | ```xml
274 |
275 | ```
276 |
277 | 可以看到我们可以在处理时忽略前后两句话,把中间的`_ic`和`_ai`处理好就行了。
278 |
279 | 通过解析 js 把 wxml 大概结构还原后,可能相比编译前的 wxml 显得臃肿,可以考虑自动简化,例如:
280 |
281 | ```xml
282 |
283 |
284 |
285 |
286 |
287 | ```
288 |
289 | 可简化为:
290 |
291 | ```xml
292 |
293 |
294 |
295 | ```
296 |
297 | 这样,我们完成了几乎所有 wxapkg包 内容的还原。
298 |
299 | ### 对`z`数组优化后的支持方法
300 |
301 | `wcc-v0.5vv_20180626_syb_zp`后通过只加载`z`数组中需要的部分来提高小程序运行速度,这也会导致仅考虑到上述内容的解包程序解包失败,这一更新的主要内容如下:
302 |
303 | - 增加z数组的函数:`_rz` `_2z` `_mz` `_1z` `_oz`
304 | - 在每个函数头部增加了`var z=gz$gwx_{$id}()`,来标识使用的z数组id
305 | - 原有的z数组不再存在
306 | - z数组已以下固定格式出现:
307 |
308 | ```javascript
309 | function gz$gwx_{$id}(){
310 | if( __WXML_GLOBAL__.ops_cached.$gwx_{$id})return __WXML_GLOBAL__.ops_cached.$gwx_{$id}
311 | __WXML_GLOBAL__.ops_cached.$gwx_{$id}=[];
312 | (function(z){var a=11;function Z(ops){z.push(ops)}
313 |
314 | //... (Z({$content}))
315 |
316 | })(__WXML_GLOBAL__.ops_cached.$gwx_{$id});return __WXML_GLOBAL__.ops_cached.$gwx_{$id}
317 | }
318 | ```
319 |
320 | 对于上述变更,将获取`z`数组处修改并添加对`_rz` `_2z` `_mz` `_1z` `_oz`的支持即可。
321 |
322 | 需要注意的是开发版的`z`数组转为如下结构:
323 |
324 | ```javascript
325 | (function(z){var a=11;function Z(ops,debugLine){z.push(['11182016',ops,debugLine])}
326 | //...
327 | })//...
328 | ```
329 |
330 | 探测到为开发版后应将获取到的`z`数组仅保留数组中的第二项。
331 |
332 | 以及含分包的子包采用 `gz$gwx{$subPackageId}_{$id}` 命名,其中`{$subPackageId}`是一个数字。
333 |
334 | 另外还需要注意,`template`的 `var z=gz$gwx_{$id}` 在`try`块外。
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 发表 Issue 前请仔细阅读以下内容:
2 |
3 |
4 | 1. 如果你是要反馈 bug, 请按以下`模板`书写 Issue;
5 | 2. 如果你遇到的是 Node.js 使用问题, 请尽可能依赖搜索引擎解决问题;
6 | 3. 遇到包依赖问题,请联系对应项目;
7 | 4. 任何对某类小程序包的适配问题都应提供 wxapkg 程序包,否则直接 Close 处理;
8 | 5. 提交前请确认 wxapkg 程序包版本不小于 v0.6vv_20180111_fbi (直接用文本编辑器打开 wxapkg包搜索 v0.6vv 或 v0.5vv 即可查到,注意版本大小主要比较的是日期), 旧版本不提供支持, 相关 Issue 直接 Close 处理;
9 | 6. 直接分包和直接处理含插件的包两个功能暂不支持, 请勿重复发表 Issue, 新样例可在已存在的 Issue 下提出;
10 | 7. 请不要在其他 Issue 下发表与该 Issue 无关的回复, 否则将有可能被删除。
11 |
12 | 模板内容如下:
13 |
14 | 程序执行命令(可选):
15 |
16 | 程序执行错误信息(如果反馈是抛出异常的错误,必填):
17 |
18 | ```
19 | 复制到这里
20 | ```
21 |
22 | 程序结果错误信息(如果反馈不是抛出异常的错误, 必填, 请尽可能详细描述):
23 |
24 | 程序包(你所要解压的程序包地址, 可为网盘链接, 也可直接上传[上传前请先打包]. 必填):
25 |
26 | 其他附加内容:
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MyWxAppUnpacker
2 |
3 |  
4 |
5 | > Wechat App(微信小程序, .wxapkg)解包及相关文件(.wxss, .json, .wxs, .wxml)还原工具
6 |
7 | ## update
8 | 本人也是开源工具获益者,在使用工具其中发现的一些问题做了顺手修复
9 |
10 | 主要修复如下问题
11 | 1. upexpected end of input 「这个问题修正后会出现问题2」
12 | 2. 修复抛出异常bridge.from异常。
13 | 3. 有些包在解包时候报错 纠正wxml和 wxss时候报错。可能是由于包被加密了 需要先用解密工具解密一下。「解密工具可见首页另一个项目」
14 |
15 | 
16 | 
17 | ## 1. 说明
18 |
19 | - 本文是基于 [wxappUnpacker](https://github.com/qwerty472123/wxappUnpacker "wxappUnpacker") 创作的。
20 | > - [x] 修复 “ReferenceError: $gwx is not defined” 和 extract wxss 等问题
21 | > - [x] 支持分包
22 | > - [x] 支持一键解包
23 | > - [x] 支持一键安装各种依赖
24 |
25 | 一键匹配、统计文本中的内容,请下载 [calcwords](https://github.com/larack8/calcwords "calcwords") 。
26 |
27 | ### 2. wxapkg 包的获取
28 |
29 | Android 手机最近使用过的微信小程序所对应的 wxapkg 包文件都存储在特定文件夹下,可通过以下命令查看:
30 |
31 | adb pull /data/data/com.tencent.mm/MicroMsg/{User}/appbrand/pkg ./
32 |
33 | 其中`{User}` 为当前用户的用户名,类似于 `2bc**************b65`。
34 |
35 | ## 3. 用法
36 |
37 | 用法分 mac 和 windows,请根据系统来操作
38 |
39 | ### 1. for Mac OS (Mac操作系统)
40 |
41 | - 安装npm和node
42 |
43 | ```bash
44 | ./install.sh -npm
45 | ```
46 |
47 | - 安装依赖
48 |
49 | ```bash
50 | ./install.sh
51 | ```
52 |
53 | - 解包某个小程序
54 |
55 | ```bash
56 | ./de_miniapp.sh -d 小程序包路径(.wxapkg格式)
57 | ```
58 |
59 | - 一键解文件夹下所有小程序
60 |
61 | ```bash
62 | ./de_miniapp.sh 小程序包所在文件夹
63 | ```
64 |
65 | - 一键解当前文件夹下所有小程序
66 |
67 | ```bash
68 | ./de_miniapp.sh
69 | ```
70 |
71 | ** 举例
72 |
73 | Mac OS
74 | ```bash
75 | ./de_miniapp.sh -d ./testpkg/_-751579163_42.wxapkg
76 | ```
77 |
78 | 
79 |
80 | ### 2. for 通用操作系统(Windows 和 Mac)
81 |
82 | - 解包某个小程序
83 |
84 | ```bash
85 | node wuWxapkg.js 小程序包路径(.wxapkg格式)
86 | ```
87 |
88 | ** 举例
89 |
90 | ```bash
91 | node wuWxapkg.js testpkg\_-751579163_42.wxapkg
92 | ```
93 |
94 | - 分包功能
95 |
96 | 当检测到 wxapkg 为子包时, 添加-s 参数指定主包源码路径即可自动将子包的 wxss,wxml,js 解析到主包的对应位置下. 完整流程大致如下:
97 | 1. 获取主包和若干子包
98 | 2. 解包主包 `./bingo.sh testpkg/master-xxx.wxapkg`
99 | 3. 解包子包 `./bingo.sh testpkg/sub-1-xxx.wxapkg -s=../master-xxx`
100 |
101 | TIP
102 | > -s 参数可为相对路径或绝对路径, 推荐使用绝对路径, 因为相对路径的起点不是当前目录 而是子包解包后的目录
103 |
104 | ```
105 | ├── testpkg
106 | │ ├── sub-1-xxx.wxapkg #被解析子包
107 | │ └── sub-1-xxx #相对路径的起点
108 | │ ├── app-service.js
109 | │ ├── master-xxx.wxapkg
110 | │ └── master-xxx # ../master-xxx 就是这个目录
111 | │ ├── app.json
112 | ```
113 |
114 | ### 4. 提取统计WXSS或者其他样式
115 |
116 | `详情参照` [calcwords](https://github.com/larack8/calcwords "calcwords")
117 |
118 | 1. 下载calcwords源码
119 |
120 | ```bash
121 | git clone https://github.com/larack8/calcwords
122 | ```
123 |
124 | 2. 设置统计的.wxapkg路径和输入结果路径,调用 calcWxssStyle
125 |
126 | ```bash
127 | public static void testCalcWords() throws IOException {
128 | String fromFilePath = "/Users/Shared/my_git/java/CalcWords/testletters/";
129 | String resultFilePath = "/Users/Shared/my_git/java/CalcWords/result.txt";
130 |
131 | calcWxssStyle(fromFilePath, resultFilePath);// 统计微信小程序源码WWXSS样式
132 | // calcWxssProperty(fromFilePath, resultFilePath);// 统计微信小程序源码WXSS属性
133 | }
134 | ```
135 |
136 | 3. 打开输出结果文件
137 |
138 | 如下图样式
139 |
140 | 
141 |
--------------------------------------------------------------------------------
/bingo.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # MyWxappUnpacker 项目路径
4 | WXAPPUNPACKER_PATH=`pwd`
5 |
6 | FILE_FORMAT=wxapkg
7 |
8 | wxappUnpacker_pkg() {
9 | echo "node ${WXAPPUNPACKER_PATH}/wuWxapkg.js ${fname}"
10 | node ${WXAPPUNPACKER_PATH}/wuWxapkg.js $2 $1
11 | return 0;
12 | }
13 |
14 | wxappUnpacker() {
15 | de_dir=$1
16 | if [ -z "$1" ]
17 | then
18 | de_dir=`pwd`
19 | fi
20 | echo "${de_dir}"
21 | echo "for wxapkg in `find ${de_dir} -name "*.${FILE_FORMAT}"`"
22 | for fname in `find ${de_dir} -name "*.${FILE_FORMAT}"`
23 | do
24 | wxappUnpacker_pkg ${fname} $2
25 | done
26 | return 0;
27 | }
28 |
29 | de_pkg() {
30 | if [ "-d" == "$1" ]
31 | then
32 | wxappUnpacker $1 $2
33 | else
34 | wxappUnpacker_pkg $1 $2
35 | fi
36 | return 0;
37 | }
38 | # $1: pkg file or pkg dir; $2: order
39 | de_pkg $1 $2
40 |
41 |
42 |
--------------------------------------------------------------------------------
/de_miniapp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # MyWxappUnpacker 项目路径
4 | WXAPPUNPACKER_PATH=`pwd`
5 |
6 | FILE_FORMAT=wxapkg
7 |
8 | wxappUnpacker_pkg()
9 | {
10 | fname=$1
11 | echo "node ${WXAPPUNPACKER_PATH}/wuWxapkg.js ${fname}"
12 | node ${WXAPPUNPACKER_PATH}/wuWxapkg.js ${fname}
13 | return 0;
14 | }
15 |
16 | wxappUnpacker()
17 | {
18 | de_dir=$1
19 | if [ -z "$1" ]
20 | then
21 | de_dir=`pwd`
22 | fi
23 | echo "${de_dir}"
24 | echo "for wxapkg in `find ${de_dir} -name "*.${FILE_FORMAT}"`"
25 | for fname in `find ${de_dir} -name "*.${FILE_FORMAT}"`
26 | do
27 | wxappUnpacker_pkg ${fname}
28 | done
29 | return 0;
30 | }
31 |
32 | de_miniapp()
33 | {
34 | if [ "-d" == "$1" ]
35 | then
36 | wxappUnpacker_pkg $2 $3
37 | else
38 | wxappUnpacker $1 $2
39 | fi
40 | return 0;
41 | }
42 |
43 | de_miniapp $1 $2 $3
44 |
45 |
46 |
--------------------------------------------------------------------------------
/image-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/image-1.png
--------------------------------------------------------------------------------
/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/image.png
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | install_dependency()
4 | {
5 | echo "1. install dependency"
6 |
7 | sudo npm install esprima -g
8 | sudo npm install css-tree -g
9 | sudo npm install cssbeautify -g
10 | sudo npm install vm2 -g
11 | sudo npm install uglify-es -g
12 | sudo npm install js-beautify -g
13 | sudo npm install escodegen -g
14 |
15 | echo "2. npm list"
16 | npm list esprima
17 | npm list css-tree
18 | npm list cssbeautify
19 | npm list vm2
20 | npm list uglify-es
21 | npm list js-beautify
22 | npm list escodegen
23 |
24 | # echo "3. npm install"
25 | # npm install
26 | return 0;
27 | }
28 |
29 | install_npm()
30 | {
31 | echo "install node"
32 | brew install node
33 | echo "node versin is"
34 | node -v
35 | echo "npm versin is"
36 | npm -v
37 | echo "install n"
38 | npm i -g n
39 | echo "update to stable"
40 | n stable
41 | npm i npm
42 |
43 | # npm i handlebars --reg=https://registry.npmjs.org
44 | return 0;
45 | }
46 |
47 | check_menu()
48 | {
49 | menu=$1
50 | args=$2
51 | param=$3
52 |
53 | if [ "-npm" == "$1" ]
54 | then install_npm $args $param
55 | elif [ "-dpc" == "$1" ]
56 | then
57 | install_dependency $args $param
58 | else
59 | install_dependency $1 $2
60 | fi
61 | return 0;
62 | }
63 |
64 | # 菜单
65 | check_menu $1 $2 $3
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wxapp-unpacker",
3 | "version": "6.8.0",
4 | "description": "Wechat App(微信小程序, .wxapkg)解包及相关文件(.wxss, .json, .wxs, .wxml)还原工具",
5 | "main": "wuWxapkg.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/qwerty472123/wxappUnpacker.git"
9 | },
10 | "author": "qwerty472123",
11 | "license": "GPL-3.0-or-later",
12 | "bugs": {
13 | "url": "https://github.com/qwerty472123/wxappUnpacker/issues"
14 | },
15 | "homepage": "https://github.com/qwerty472123/wxappUnpacker#readme",
16 | "dependencies": {
17 | "cheerio": "^1.0.0-rc.2",
18 | "css-tree": "^1.0.0-alpha.28",
19 | "cssbeautify": "^0.3.1",
20 | "escodegen": "^1.11.0",
21 | "esprima": "^4.0.0",
22 | "handlebars": "^4.1.0",
23 | "js-beautify": "^1.8.9",
24 | "npm": "^6.9.0",
25 | "uglify-es": "^3.3.9",
26 | "vm2": "^3.6.10"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/proxy_set.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # npm config set proxy http:web-proxy.tencent.com:8080
4 | # npm config set proxy http://127.0.0.1:12759
5 |
6 | npm_set_proxy()
7 | {
8 | if [ "-s" == "$1" ]
9 | then
10 | echo "npm config set proxy http://127.0.0.1:12759"
11 | npm config set proxy http://127.0.0.1:12759
12 | echo "npm config set https-proxy http://127.0.0.1:12759"
13 | npm config set https-proxy http://127.0.0.1:12759
14 | elif [ "-d" == "$1" ]
15 | then
16 | echo "npm config delete proxy"
17 | npm config delete proxy
18 | echo "npm config delete https-proxy"
19 | npm config delete https-proxy
20 | fi
21 | return 0;
22 | }
23 |
24 | git_set_proxy()
25 | {
26 | if [ "-s" == "$1" ]
27 | then
28 | echo "npm config set proxy http://127.0.0.1:12759"
29 | npm config set proxy http://127.0.0.1:12759
30 | echo "npm config set https-proxy http://127.0.0.1:12759"
31 | npm config set https-proxy http://127.0.0.1:12759
32 | elif [ "-d" == "$1" ]
33 | then
34 | echo "npm config delete proxy"
35 | npm config delete proxy
36 | echo "npm config delete https-proxy"
37 | npm config delete https-proxy
38 | fi
39 | return 0;
40 | }
41 |
42 | check_menu()
43 | {
44 | menu=$1
45 | args=$2
46 | param=$3
47 |
48 | if [ "-git" == "$1" ]
49 | then git_set_proxy $args $param
50 | elif [ "-npm" == "$1" ]
51 | then
52 | npm_set_proxy $args $param
53 | else npm_set_proxy $1 $2
54 | fi
55 | return 0;
56 | }
57 |
58 | # 菜单
59 | check_menu $1 $2 $3
60 |
--------------------------------------------------------------------------------
/testpkg/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/testpkg/.DS_Store
--------------------------------------------------------------------------------
/testpkg/_-751579163_42.wxapkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/testpkg/_-751579163_42.wxapkg
--------------------------------------------------------------------------------
/testpkg/cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/testpkg/cc.png
--------------------------------------------------------------------------------
/testpkg/jinqianli_miniapp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/testpkg/jinqianli_miniapp_logo.png
--------------------------------------------------------------------------------
/testpkg/jinqianli_shoukuan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/testpkg/jinqianli_shoukuan.png
--------------------------------------------------------------------------------
/testpkg/testdir.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/testpkg/testdir.png
--------------------------------------------------------------------------------
/wuConfig.js:
--------------------------------------------------------------------------------
1 | const wu = require("./wuLib.js");
2 | const fs = require("fs");
3 | const path = require("path");
4 | const crypto = require("crypto");
5 | const {VM} = require('vm2');
6 |
7 | function getWorkerPath(name) {
8 | let code = fs.readFileSync(name, {encoding: 'utf8'});
9 | let commPath = false;
10 | let vm = new VM({
11 | sandbox: {
12 | require() {
13 | },
14 | define(name) {
15 | name = path.dirname(name) + '/';
16 | if (commPath === false) commPath = name;
17 | commPath = wu.commonDir(commPath, name);
18 | }
19 | }
20 | });
21 | vm.run(code.slice(code.indexOf("define(")));
22 | if (commPath.length > 0) commPath = commPath.slice(0, -1);
23 | console.log("Worker path: \"" + commPath + "\"");
24 | return commPath;
25 | }
26 |
27 | function doConfig(configFile, cb) {
28 | let dir = path.dirname(configFile);
29 | wu.get(configFile, content => {
30 | let e = JSON.parse(content);
31 | let k = e.pages;
32 | k.splice(k.indexOf(wu.changeExt(e.entryPagePath)), 1);
33 | k.unshift(wu.changeExt(e.entryPagePath));
34 | let app = {pages: k, window: e.global && e.global.window, tabBar: e.tabBar, networkTimeout: e.networkTimeout};
35 | if (e.subPackages) {
36 | let subPackages = [];
37 | let pages = app.pages;
38 | for (let subPackage of e.subPackages) {
39 | let root = subPackage.root;
40 | let lastChar = root.substr(root.length - 1, 1);
41 | if (lastChar !== '/') {
42 | root = root + '/';
43 | }
44 | let firstChar = root.substr(0, 1);
45 | if (firstChar === '/') {
46 | root = root.substring(1);
47 | }
48 | let newPages = [];
49 | for (let page of subPackage.pages) {
50 | let items = page.replace(root, '');
51 | newPages.push(items);
52 | let subIndex = pages.indexOf(root + items);
53 | console.log(root + items, subIndex);
54 | if (subIndex!==-1) {
55 | pages.splice(subIndex, 1);
56 | }
57 | }
58 | subPackage.root = root;
59 | subPackage.pages = newPages;
60 | subPackages.push(subPackage);
61 | }
62 | app.subPackages = subPackages;
63 | app.pages = pages;
64 | console.log("=======================================================\nNOTICE: SubPackages exist in this package.\nDetails: ", app.subPackages.length, "\n=======================================================");
65 | }
66 | if (e.navigateToMiniProgramAppIdList) app.navigateToMiniProgramAppIdList = e.navigateToMiniProgramAppIdList;
67 | if (fs.existsSync(path.resolve(dir, "workers.js"))) app.workers = getWorkerPath(path.resolve(dir, "workers.js"));
68 | if (e.extAppid)
69 | wu.save(path.resolve(dir, 'ext.json'), JSON.stringify({
70 | extEnable: true,
71 | extAppid: e.extAppid,
72 | ext: e.ext
73 | }, null, 4));
74 | if (typeof e.debug != "undefined") app.debug = e.debug;
75 | let cur = path.resolve("./file");
76 | for (let a in e.page) if (e.page[a].window.usingComponents)
77 | for (let name in e.page[a].window.usingComponents) {
78 | let componentPath = e.page[a].window.usingComponents[name] + ".html";
79 | let file = componentPath.startsWith('/') ? componentPath.slice(1) : wu.toDir(path.resolve(path.dirname(a), componentPath), cur);
80 | if (!e.page[file]) e.page[file] = {};
81 | if (!e.page[file].window) e.page[file].window = {};
82 | e.page[file].window.component = true;
83 | }
84 | if (fs.existsSync(path.resolve(dir, "app-service.js"))) {
85 | let matches = fs.readFileSync(path.resolve(dir, "app-service.js"), {encoding: 'utf8'}).match(/\_\_wxAppCode\_\_\['[^\.]+\.json[^;]+\;/g);
86 | if (matches) {
87 | let attachInfo = {};
88 | (new VM({
89 | sandbox: {
90 | __wxAppCode__: attachInfo
91 | }
92 | })).run(matches.join(""));
93 | for (let name in attachInfo) e.page[wu.changeExt(name, ".html")] = {window: attachInfo[name]};
94 | }
95 | }
96 | let delWeight = 8;
97 | for (let a in e.page) {
98 | let fileName = path.resolve(dir, wu.changeExt(a, ".json"));
99 | wu.save(fileName, JSON.stringify(e.page[a].window, null, 4));
100 | //添加默认的 wxs, wxml, wxss
101 | let jsName = wu.changeExt(a, ".js");
102 | let fileNameOfWxs = path.resolve(dir, jsName);
103 | wu.save(fileNameOfWxs, "// " + jsName + "\nPage({data: {}})");
104 | let wxmlName = wu.changeExt(a, ".wxml");
105 | let fileNameOfWxml = path.resolve(dir, wxmlName);
106 | wu.save(fileNameOfWxml, "" + wxmlName + "");
107 | let cssName = wu.changeExt(a, ".wxss");
108 | let fileNameOfWxss = path.resolve(dir, cssName);
109 | wu.save(fileNameOfWxss, "/* " + cssName + " */");
110 | if (configFile == fileName) delWeight = 0;
111 | }
112 | if (app.tabBar && app.tabBar.list) wu.scanDirByExt(dir, "", li => {//search all files
113 | let digests = [], digestsEvent = new wu.CntEvent, rdir = path.resolve(dir);
114 |
115 | function fixDir(dir) {
116 | return dir.startsWith(rdir) ? dir.slice(rdir.length + 1) : dir;
117 | }
118 |
119 | digestsEvent.add(() => {
120 | for (let e of app.tabBar.list) {
121 | e.pagePath = wu.changeExt(e.pagePath);
122 | if (e.iconData) {
123 | let hash = crypto.createHash("MD5").update(e.iconData, 'base64').digest();
124 | for (let [buf, name] of digests) if (hash.equals(buf)) {
125 | delete e.iconData;
126 | e.iconPath = fixDir(name).replace(/\\/g, '/');
127 | break;
128 | }
129 | }
130 | if (e.selectedIconData) {
131 | let hash = crypto.createHash("MD5").update(e.selectedIconData, 'base64').digest();
132 | for (let [buf, name] of digests) if (hash.equals(buf)) {
133 | delete e.selectedIconData;
134 | e.selectedIconPath = fixDir(name).replace(/\\/g, '/');
135 | break;
136 | }
137 | }
138 | }
139 | wu.save(path.resolve(dir, 'app.json'), JSON.stringify(app, null, 4));
140 | cb({[configFile]: delWeight});
141 | });
142 | for (let name of li) {
143 | digestsEvent.encount();
144 | wu.get(name, data => {
145 | digests.push([crypto.createHash("MD5").update(data).digest(), name]);
146 | digestsEvent.decount();
147 | }, {});
148 | }
149 | }); else {
150 | wu.save(path.resolve(dir, 'app.json'), JSON.stringify(app, null, 4));
151 | cb({[configFile]: delWeight});
152 | }
153 | });
154 | }
155 |
156 | module.exports = {doConfig: doConfig};
157 | if (require.main === module) {
158 | 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.");
159 | }
160 |
--------------------------------------------------------------------------------
/wuJs.js:
--------------------------------------------------------------------------------
1 | const wu = require("./wuLib.js");
2 | const path = require("path");
3 | const UglifyJS = require("uglify-es");
4 | const {js_beautify} = require("js-beautify");
5 | const {VM} = require('vm2');
6 |
7 | function jsBeautify(code) {
8 | return UglifyJS.minify(code, {mangle: false, compress: false, output: {beautify: true, comments: true}}).code;
9 | }
10 |
11 | function splitJs(name, cb, mainDir) {
12 | let isSubPkg = mainDir && mainDir.length > 0;
13 | let dir = path.dirname(name);
14 | if (isSubPkg) {
15 | dir = mainDir;
16 | }
17 | wu.get(name, code => {
18 | let needDelList = {};
19 | let vm = new VM({
20 | sandbox: {
21 | require() {
22 | },
23 | define(name, func) {
24 | let code = func.toString();
25 | code = code.slice(code.indexOf("{") + 1, code.lastIndexOf("}") - 1).trim();
26 | let bcode = code;
27 | if (code.startsWith('"use strict";') || code.startsWith("'use strict';")) code = code.slice(13);
28 | else if ((code.startsWith('(function(){"use strict";') || code.startsWith("(function(){'use strict';")) && code.endsWith("})();")) code = code.slice(25, -5);
29 | let res = jsBeautify(code);
30 | if (typeof res == "undefined") {
31 | console.log("Fail to delete 'use strict' in \"" + name + "\".");
32 | res = jsBeautify(bcode);
33 | }
34 | console.log(dir, name);
35 | needDelList[path.resolve(dir, name)] = -8;
36 | wu.save(path.resolve(dir, name), jsBeautify(res));
37 | },
38 | definePlugin() {
39 | },
40 | requirePlugin() {
41 | }
42 | }
43 | });
44 | if (isSubPkg) {
45 | code = code.slice(code.indexOf("define("));
46 | }
47 | console.log('splitJs: ' + name);
48 | vm.run(code);
49 | console.log("Splitting \"" + name + "\" done.");
50 | if (!needDelList[name]) needDelList[name] = 8;
51 | cb(needDelList);
52 | });
53 | }
54 |
55 | module.exports = {jsBeautify: jsBeautify, wxsBeautify: js_beautify, splitJs: splitJs};
56 | if (require.main === module) {
57 | wu.commandExecute(splitJs, "Split and beautify weapp js file.\n\n\n\n js files to split and beautify.");
58 | }
59 |
--------------------------------------------------------------------------------
/wuLib.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 |
4 | class CntEvent {
5 | constructor() {
6 | this.cnt = 0;
7 | this.emptyEvent = [];
8 | this.encount = this.encount.bind(this);
9 | this.decount = this.decount.bind(this);
10 | this.add = this.add.bind(this);
11 | }
12 |
13 | encount(delta = 1) {
14 | this.cnt += delta;
15 | }
16 |
17 | decount() {
18 | if (this.cnt > 0) --this.cnt;
19 | if (this.cnt == 0) {
20 | for (let info of this.emptyEvent) info[0](...info[1]);
21 | this.emptyEvent = [];
22 | }
23 | }
24 |
25 | add(cb, ...attach) {
26 | this.emptyEvent.push([cb, attach]);
27 | }
28 |
29 | check(cb, ...attach) {
30 | if (this.cnt == 0) cb(...attach);
31 | else this.add(cb, ...attach);
32 | }
33 | }
34 |
35 | class LimitedRunner {
36 | constructor(limit) {
37 | this.limit = limit;
38 | this.cnt = 0;
39 | this.funcs = [];
40 | }
41 |
42 | run(func) {
43 | if (this.cnt < this.limit) {
44 | this.cnt++;
45 | setTimeout(func, 0);
46 | } else {
47 | this.funcs.push(func);
48 | }
49 | }
50 |
51 | done() {
52 | if (this.cnt > 0) this.cnt--;
53 | if (this.funcs.length > 0) {
54 | this.cnt++;
55 | setTimeout(this.funcs.shift(), 0);
56 | }
57 | }
58 |
59 | runWithCb(func, ...args) {
60 | let cb = args.pop(), self = this;
61 |
62 | function agent(...args) {
63 | self.done();
64 | return cb.apply(this, args);
65 | }
66 |
67 | args.push(agent);
68 | this.run(() => func(...args));
69 | }
70 | }
71 |
72 | let ioEvent = new CntEvent;
73 | let ioLimit = new LimitedRunner(4096);
74 |
75 | function mkdirs(dir, cb) {
76 | ioLimit.runWithCb(fs.stat.bind(fs), dir, (err, stats) => {
77 | if (err) mkdirs(path.dirname(dir), () => fs.mkdir(dir, cb));
78 | else if (stats.isFile()) throw Error(dir + " was created as a file, so we cannot put file into it.");
79 | else cb();
80 | });
81 | }
82 |
83 | function save(name, content) {
84 | ioEvent.encount();
85 | mkdirs(path.dirname(name), () => ioLimit.runWithCb(fs.writeFile.bind(fs), name, content, err => {
86 | if (err) throw Error("Save file error: " + err);
87 | ioEvent.decount();
88 | }));
89 | }
90 |
91 | function get(name, cb, opt = {encoding: 'utf8'}) {
92 | ioEvent.encount();
93 | ioLimit.runWithCb(fs.readFile.bind(fs), name, opt, (err, data) => {
94 | if (err) throw Error("Read file error: " + err);
95 | else cb(data);
96 | ioEvent.decount();
97 | });
98 | }
99 |
100 | function del(name) {
101 | ioEvent.encount();
102 | ioLimit.runWithCb(fs.unlink.bind(fs), name, ioEvent.decount);
103 | }
104 |
105 | function changeExt(name, ext = "") {
106 | return name.slice(0, name.lastIndexOf(".")) + ext;
107 | }
108 |
109 | function scanDirByExt(dir, ext, cb) {
110 | let result = [], scanEvent = new CntEvent;
111 |
112 | function helper(dir) {
113 | scanEvent.encount();
114 | ioLimit.runWithCb(fs.readdir.bind(fs), dir, (err, files) => {
115 | if (err) throw Error("Scan dir error: " + err);
116 | for (let file of files) {
117 | scanEvent.encount();
118 | let name = path.resolve(dir, file);
119 | fs.stat(name, (err, stats) => {
120 | if (err) throw Error("Scan dir error: " + err);
121 | if (stats.isDirectory()) helper(name);
122 | else if (stats.isFile() && name.endsWith(ext)) result.push(name);
123 | scanEvent.decount();
124 | });
125 | }
126 | scanEvent.decount();
127 | });
128 | }
129 |
130 | scanEvent.add(cb, result);
131 | helper(dir, ext, scanEvent);
132 | }
133 |
134 | function toDir(to, from) {//get relative path without posix/win32 problem
135 | if (from[0] == ".") from = from.slice(1);
136 | if (to[0] == ".") to = to.slice(1);
137 | from = from.replace(/\\/g, '/');
138 | to = to.replace(/\\/g, '/');
139 | let a = Math.min(to.length, from.length);
140 | for (let i = 1, m = Math.min(to.length, from.length); i <= m; i++) if (!to.startsWith(from.slice(0, i))) {
141 | a = i - 1;
142 | break;
143 | }
144 | let pub = from.slice(0, a);
145 | let len = pub.lastIndexOf("/") + 1;
146 | let k = from.slice(len);
147 | let ret = "";
148 | for (let i = 0; i < k.length; i++) if (k[i] == '/') ret += '../';
149 | return ret + to.slice(len);
150 | }
151 |
152 | function commonDir(pathA, pathB) {
153 | if (pathA[0] == ".") pathA = pathA.slice(1);
154 | if (pathB[0] == ".") pathB = pathB.slice(1);
155 | pathA = pathA.replace(/\\/g, '/');
156 | pathB = pathB.replace(/\\/g, '/');
157 | let a = Math.min(pathA.length, pathB.length);
158 | for (let i = 1, m = Math.min(pathA.length, pathB.length); i <= m; i++) if (!pathA.startsWith(pathB.slice(0, i))) {
159 | a = i - 1;
160 | break;
161 | }
162 | let pub = pathB.slice(0, a);
163 | let len = pub.lastIndexOf("/") + 1;
164 | return pathA.slice(0, len);
165 | }
166 |
167 | function commandExecute(cb, helper) {
168 | console.time("Total use");
169 |
170 | function endTime() {
171 | ioEvent.check(() => console.timeEnd("Total use"));
172 | }
173 |
174 | let orders = [];
175 | for (let order of process.argv) if (order.startsWith("-")) orders.push(order.slice(1));
176 | let iter = process.argv[Symbol.iterator](), nxt = iter.next(), called = false, faster = orders.includes("f"),
177 | fastCnt;
178 | if (faster) {
179 | fastCnt = new CntEvent;
180 | fastCnt.add(endTime);
181 | }
182 |
183 | function doNext() {
184 | let nxt = iter.next();
185 | while (!nxt.done && nxt.value.startsWith("-")) nxt = iter.next();
186 | if (nxt.done) {
187 | if (!called) console.log("Command Line Helper:\n\n" + helper);
188 | else if (!faster) endTime();
189 | } else {
190 | called = true;
191 | if (faster) fastCnt.encount(), cb(nxt.value, fastCnt.decount, orders), doNext();
192 | else cb(nxt.value, doNext, orders);
193 | }
194 | }
195 |
196 | while (!nxt.done && !nxt.value.endsWith(".js")) nxt = iter.next();
197 | doNext();
198 | }
199 |
200 | module.exports = {
201 | mkdirs: mkdirs, get: get, save: save, toDir: toDir, del: del, addIO: ioEvent.add,
202 | changeExt: changeExt, CntEvent: CntEvent, scanDirByExt: scanDirByExt, commonDir: commonDir,
203 | commandExecute: commandExecute
204 | };
205 |
--------------------------------------------------------------------------------
/wuRestoreZ.js:
--------------------------------------------------------------------------------
1 | const wu = require("./wuLib.js");
2 | const {VM} = require('vm2');
3 |
4 | function catchZGroup(code, groupPreStr, cb) {
5 | const debugPre = "(function(z){var a=11;function Z(ops,debugLine){";
6 | let zArr = {};
7 | for (let preStr of groupPreStr) {
8 | let content = code.slice(code.indexOf(preStr)), z = [];
9 | content = content.slice(content.indexOf("(function(z){var a=11;"));
10 | content = content.slice(0, content.indexOf("})(__WXML_GLOBAL__.ops_cached.$gwx")) + "})(z);";
11 | let vm = new VM({sandbox: {z: z, debugInfo: []}});
12 | vm.run(content);
13 | if (content.startsWith(debugPre)) for (let i = 0; i < z.length; i++) z[i] = z[i][1];
14 | zArr[preStr.match(/function gz\$gwx(\d*\_\d+)/)[1]] = z;
15 | }
16 | cb({"mul": zArr});
17 | }
18 |
19 | function catchZ(code, cb) {
20 | let groupTest = code.match(/function gz\$gwx(\d*\_\d+)\(\)\{\s*if\( __WXML_GLOBAL__\.ops_cached\.\$gwx\d*\_\d+\)/g);
21 | if (groupTest !== null) return catchZGroup(code, groupTest, cb);
22 | let z = [], vm = new VM({
23 | sandbox: {
24 | z: z,
25 | debugInfo: []
26 | }
27 | });
28 | let lastPtr = code.lastIndexOf("(z);__WXML_GLOBAL__.ops_set.$gwx=z;");
29 | if (lastPtr == -1) lastPtr = code.lastIndexOf("(z);__WXML_GLOBAL__.ops_set.$gwx");
30 | code = code.slice(code.lastIndexOf('(function(z){var a=11;function Z(ops){z.push(ops)}'), lastPtr + 4);
31 | vm.run(code);
32 | cb(z);
33 | }
34 |
35 | function restoreSingle(ops, withScope = false) {
36 | if (typeof ops == "undefined") return "";
37 |
38 | function scope(value) {
39 | if (value.startsWith('{') && value.endsWith('}')) return withScope ? value : "{" + value + "}";
40 | return withScope ? value : "{{" + value + "}}";
41 | }
42 |
43 | function enBrace(value, type = '{') {
44 | if (value.startsWith('{') || value.startsWith('[') || value.startsWith('(') || value.endsWith('}') || value.endsWith(']') || value.endsWith(')')) value = ' ' + value + ' ';
45 | switch (type) {
46 | case '{':
47 | return '{' + value + '}';
48 | case '[':
49 | return '[' + value + ']';
50 | case '(':
51 | return '(' + value + ')';
52 | default:
53 | throw Error("Unknown brace type " + type);
54 | }
55 | }
56 |
57 | function restoreNext(ops, w = withScope) {
58 | return restoreSingle(ops, w);
59 | }
60 |
61 | function jsoToWxon(obj) {//convert JS Object to Wechat Object Notation(No quotes@key+str)
62 | let ans = "";
63 | if (typeof obj === "undefined") {
64 | return 'undefined';
65 | } else if (obj === null) {
66 | return 'null';
67 | } else if (obj instanceof RegExp) {
68 | return obj.toString();
69 | } else if (obj instanceof Array) {
70 | for (let i = 0; i < obj.length; i++) ans += ',' + jsoToWxon(obj[i]);
71 | return enBrace(ans.slice(1), '[');
72 | } else if (typeof obj == "object") {
73 | for (let k in obj) ans += "," + k + ":" + jsoToWxon(obj[k]);
74 | return enBrace(ans.slice(1), '{');
75 | } else if (typeof obj == "string") {
76 | let parts = obj.split('"'), ret = [];
77 | for (let part of parts) {
78 | let atoms = part.split("'"), ans = [];
79 | for (let atom of atoms) ans.push(JSON.stringify(atom).slice(1, -1));
80 | ret.push(ans.join("\\'"));
81 | }
82 | return "'" + ret.join('"') + "'";
83 | } else return JSON.stringify(obj);
84 | }
85 |
86 | let op = ops[0];
87 | if (typeof op != "object") {
88 | switch (op) {
89 | case 3://string
90 | return ops[1];//may cause problems if wx use it to be string
91 | case 1://direct value
92 | return scope(jsoToWxon(ops[1]));
93 | case 11://values list, According to var a = 11;
94 | let ans = "";
95 | ops.shift();
96 | for (let perOp of ops) ans += restoreNext(perOp);
97 | return ans;
98 | }
99 | } else {
100 | let ans = "";
101 | switch (op[0]) {//vop
102 | case 2://arithmetic operator
103 | {
104 | function getPrior(op, len) {
105 | const priorList = {
106 | "?:": 4,
107 | "&&": 6,
108 | "||": 5,
109 | "+": 13,
110 | "*": 14,
111 | "/": 14,
112 | "%": 14,
113 | "|": 7,
114 | "^": 8,
115 | "&": 9,
116 | "!": 16,
117 | "~": 16,
118 | "===": 10,
119 | "==": 10,
120 | "!=": 10,
121 | "!==": 10,
122 | ">=": 11,
123 | "<=": 11,
124 | ">": 11,
125 | "<": 11,
126 | "<<": 12,
127 | ">>": 12,
128 | "-": len == 3 ? 13 : 16
129 | };
130 | return priorList[op] ? priorList[op] : 0;
131 | }
132 |
133 | function getOp(i) {
134 | let ret = restoreNext(ops[i], true);
135 | if (ops[i] instanceof Object && typeof ops[i][0] == "object" && ops[i][0][0] == 2) {
136 | //Add brackets if we need
137 | if (getPrior(op[1], ops.length) > getPrior(ops[i][0][1], ops[i].length)) ret = enBrace(ret, '(');
138 | ;
139 | }
140 | return ret;
141 | }
142 |
143 | switch (op[1]) {
144 | case"?:":
145 | ans = getOp(1) + "?" + getOp(2) + ":" + getOp(3);
146 | break;
147 | case "!":
148 | case "~":
149 | ans = op[1] + getOp(1);
150 | break;
151 | case"-":
152 | if (ops.length != 3) {
153 | ans = op[1] + getOp(1);
154 | break;
155 | }//shoud not add more in there![fall through]
156 | default:
157 | ans = getOp(1) + op[1] + getOp(2);
158 | }
159 | break;
160 | }
161 | case 4://unkown-arrayStart?
162 | ans = restoreNext(ops[1], true);
163 | break;
164 | case 5://merge-array
165 | {
166 | switch (ops.length) {
167 | case 2:
168 | ans = enBrace(restoreNext(ops[1], true), '[');
169 | break;
170 | case 1:
171 | ans = '[]';
172 | break;
173 | default: {
174 | let a = restoreNext(ops[1], true);
175 | //console.log(a,a.startsWith('[')&&a.endsWith(']'));
176 | if (a.startsWith('[') && a.endsWith(']')) {
177 | if (a != '[]') {
178 | ans = enBrace(a.slice(1, -1).trim() + ',' + restoreNext(ops[2], true), '[');
179 | //console.log('-',a);
180 | } else {
181 | ans = enBrace(restoreNext(ops[2], true), '[');
182 | }
183 | } else {
184 | ans = enBrace('...' + a + ',' + restoreNext(ops[2], true), '[');//may/must not support in fact
185 | }
186 | }
187 | }
188 | break;
189 | }
190 | case 6://get value of an object
191 | {
192 | let sonName = restoreNext(ops[2], true);
193 | if (sonName._type === "var")
194 | ans = restoreNext(ops[1], true) + enBrace(sonName, '[');
195 | else {
196 | let attach = "";
197 | if (/^[A-Za-z\_][A-Za-z\d\_]*$/.test(sonName)/*is a qualified id*/)
198 | attach = '.' + sonName;
199 | else attach = enBrace(sonName, '[');
200 | ans = restoreNext(ops[1], true) + attach;
201 | }
202 | break;
203 | }
204 | case 7://get value of str
205 | {
206 | switch (ops[1][0]) {
207 | case 11:
208 | ans = enBrace("__unTestedGetValue:" + enBrace(jsoToWxon(ops), '['), '{');
209 | break;
210 | case 3:
211 | ans = new String(ops[1][1]);
212 | ans._type = "var";
213 | break;
214 | default:
215 | throw Error("Unknown type to get value");
216 | }
217 | break;
218 | }
219 | case 8://first object
220 | ans = enBrace(ops[1] + ':' + restoreNext(ops[2], true), '{');//ops[1] have only this way to define
221 | break;
222 | case 9://object
223 | {
224 | function type(x) {
225 | if (x.startsWith('...')) return 1;
226 | if (x.startsWith('{') && x.endsWith('}')) return 0;
227 | return 2;
228 | }
229 |
230 | let a = restoreNext(ops[1], true);
231 | let b = restoreNext(ops[2], true);
232 | let xa = type(a), xb = type(b);
233 | if (xa == 2 || xb == 2) ans = enBrace("__unkownMerge:" + enBrace(a + "," + b, '['), '{');
234 | else {
235 | if (!xa) a = a.slice(1, -1).trim();
236 | if (!xb) b = b.slice(1, -1).trim();
237 | //console.log(l,r);
238 | ans = enBrace(a + ',' + b, '{');
239 | }
240 | break;
241 | }
242 | case 10://...object
243 | ans = '...' + restoreNext(ops[1], true);
244 | break;
245 | case 12: {
246 | let arr = restoreNext(ops[2], true);
247 | if (arr.startsWith('[') && arr.endsWith(']'))
248 | ans = restoreNext(ops[1], true) + enBrace(arr.slice(1, -1).trim(), '(');
249 | else ans = restoreNext(ops[1], true) + '.apply' + enBrace('null,' + arr, '(');
250 | break;
251 | }
252 | default:
253 | ans = enBrace("__unkownSpecific:" + jsoToWxon(ops), '{');
254 | }
255 | return scope(ans);
256 | }
257 | }
258 |
259 | function restoreGroup(z) {
260 | let ans = [];
261 | for (let g in z.mul) {
262 | let singleAns = [];
263 | for (let e of z.mul[g]) singleAns.push(restoreSingle(e, false));
264 | ans[g] = singleAns;
265 | }
266 | let ret = [];//Keep a null array for remaining global Z array.
267 | ret.mul = ans;
268 | return ret;
269 | }
270 |
271 | function restoreAll(z) {
272 | if (z.mul) return restoreGroup(z);
273 | let ans = [];
274 | for (let e of z) ans.push(restoreSingle(e, false));
275 | return ans;
276 | }
277 |
278 | module.exports = {
279 | getZ(code, cb) {
280 | catchZ(code, z => cb(restoreAll(z)));
281 | }
282 | };
283 |
--------------------------------------------------------------------------------
/wuWxapkg.js:
--------------------------------------------------------------------------------
1 | const wu = require("./wuLib.js");
2 | const wuJs = require("./wuJs.js");
3 | const wuCfg = require("./wuConfig.js");
4 | const wuMl = require("./wuWxml.js");
5 | const wuSs = require("./wuWxss.js");
6 | const path = require("path");
7 | const fs = require("fs");
8 |
9 | function header(buf) {
10 | console.log("\nHeader info:");
11 | let firstMark = buf.readUInt8(0);
12 | console.log(" firstMark: 0x%s", firstMark.toString(16));
13 | let unknownInfo = buf.readUInt32BE(1);
14 | console.log(" unknownInfo: ", unknownInfo);
15 | let infoListLength = buf.readUInt32BE(5);
16 | console.log(" infoListLength: ", infoListLength);
17 | let dataLength = buf.readUInt32BE(9);
18 | console.log(" dataLength: ", dataLength);
19 | let lastMark = buf.readUInt8(13);
20 | console.log(" lastMark: 0x%s", lastMark.toString(16));
21 | if (firstMark != 0xbe || lastMark != 0xed) throw Error("Magic number is not correct!");
22 | return [infoListLength, dataLength];
23 | }
24 |
25 | function genList(buf) {
26 | console.log("\nFile list info:");
27 | let fileCount = buf.readUInt32BE(0);
28 | console.log(" fileCount: ", fileCount);
29 | let fileInfo = [], off = 4;
30 | for (let i = 0; i < fileCount; i++) {
31 | let info = {};
32 | let nameLen = buf.readUInt32BE(off);
33 | off += 4;
34 | info.name = buf.toString('utf8', off, off + nameLen);
35 | off += nameLen;
36 | info.off = buf.readUInt32BE(off);
37 | off += 4;
38 | info.size = buf.readUInt32BE(off);
39 | off += 4;
40 | fileInfo.push(info);
41 | }
42 | return fileInfo;
43 | }
44 |
45 | function saveFile(dir, buf, list) {
46 | console.log("Saving files...");
47 | for (let info of list)
48 | wu.save(path.resolve(dir, (info.name.startsWith("/") ? "." : "") + info.name), buf.slice(info.off, info.off + info.size));
49 | }
50 |
51 | function packDone(dir, cb, order) {
52 | console.log("Unpack done.");
53 | let weappEvent = new wu.CntEvent, needDelete = {};
54 | weappEvent.encount(4);
55 | weappEvent.add(() => {
56 | wu.addIO(() => {
57 | console.log("Split and make up done.");
58 | if (!order.includes("d")) {
59 | console.log("Delete files...");
60 | wu.addIO(() => console.log("Deleted.\n\nFile done."));
61 | for (let name in needDelete) if (needDelete[name] >= 8) wu.del(name);
62 | }
63 | cb();
64 | });
65 | });
66 |
67 | function doBack(deletable) {
68 | for (let key in deletable) {
69 | if (!needDelete[key]) needDelete[key] = 0;
70 | needDelete[key] += deletable[key];//all file have score bigger than 8 will be delete.
71 | }
72 | weappEvent.decount();
73 | }
74 |
75 | function dealThreeThings(dir, mainDir, nowDir) {
76 | console.log("Split app-service.js and make up configs & wxss & wxml & wxs...");
77 |
78 | //deal config
79 | if (fs.existsSync(path.resolve(dir, "app-config.json"))) {
80 | wuCfg.doConfig(path.resolve(dir, "app-config.json"), doBack);
81 | console.log('deal config ok');
82 | }
83 | //deal js
84 | if (fs.existsSync(path.resolve(dir, "app-service.js"))) {
85 | wuJs.splitJs(path.resolve(dir, "app-service.js"), doBack, mainDir);
86 | console.log('deal js ok');
87 | }
88 | if (fs.existsSync(path.resolve(dir, "workers.js"))) {
89 | wuJs.splitJs(path.resolve(dir, "workers.js"), doBack, mainDir);
90 | console.log('deal js2 ok');
91 | }
92 | //deal html
93 | if (mainDir) {
94 | if (fs.existsSync(path.resolve(dir, "page-frame.js"))) {
95 | wuMl.doFrame(path.resolve(dir, "page-frame.js"), doBack, order, mainDir);
96 | console.log('deal sub html ok');
97 | }
98 | wuSs.doWxss(dir, doBack, mainDir, nowDir);
99 | } else {
100 | if (fs.existsSync(path.resolve(dir, "page-frame.html"))) {
101 | wuMl.doFrame(path.resolve(dir, "page-frame.html"), doBack, order, mainDir);
102 | console.log('deal html ok');
103 | } else if (fs.existsSync(path.resolve(dir, "app-wxss.js"))) {
104 | wuMl.doFrame(path.resolve(dir, "app-wxss.js"), doBack, order, mainDir);
105 | if (!needDelete[path.resolve(dir, "page-frame.js")]) {
106 | needDelete[path.resolve(dir, "page-frame.js")] = 8;
107 | }
108 | console.log('deal wxss.js ok');
109 | } else {
110 | throw Error("page-frame-like file is not found in the package by auto.");
111 | }
112 | //Force it run at last, becuase lots of error occured in this part
113 | wuSs.doWxss(dir, doBack);
114 |
115 | console.log('deal css ok');
116 | }
117 |
118 | }
119 |
120 | //This will be the only func running this time, so async is needless.
121 | if (fs.existsSync(path.resolve(dir, "app-service.js"))) {
122 | //weapp
123 | dealThreeThings(dir);
124 | } else if (fs.existsSync(path.resolve(dir, "game.js"))) {
125 | //wegame
126 | console.log("Split game.js and rewrite game.json...");
127 | let gameCfg = path.resolve(dir, "app-config.json");
128 | wu.get(gameCfg, cfgPlain => {
129 | let cfg = JSON.parse(cfgPlain);
130 | if (cfg.subContext) {
131 | console.log("Found subContext, splitting it...")
132 | delete cfg.subContext;
133 | let contextPath = path.resolve(dir, "subContext.js");
134 | wuJs.splitJs(contextPath, () => wu.del(contextPath));
135 | }
136 | wu.save(path.resolve(dir, "game.json"), JSON.stringify(cfg, null, 4));
137 | wu.del(gameCfg);
138 | });
139 | wuJs.splitJs(path.resolve(dir, "game.js"), () => {
140 | wu.addIO(() => {
141 | console.log("Split and rewrite done.");
142 | cb();
143 | });
144 | });
145 | } else {//分包
146 | let doSubPkg = false;
147 | for (const orderElement of order) {
148 | if (orderElement.indexOf('s=') !== -1) {
149 | let mainDir = orderElement.substring(2, orderElement.length);
150 | console.log("now dir: " + dir);
151 | console.log("param of mainDir: " + mainDir);
152 |
153 | let findDir = function (dir, oldDir) {
154 | let files = fs.readdirSync(dir);
155 | for (const file of files) {
156 | let workDir = path.join(dir, file);
157 | if (fs.existsSync(path.resolve(workDir, "app-service.js"))) {
158 | console.log("sub package word dir: " + workDir);
159 | mainDir = path.resolve(oldDir, mainDir);
160 | console.log("real mainDir: " + mainDir);
161 | dealThreeThings(workDir, mainDir, oldDir);
162 | doSubPkg = true;
163 | return true;
164 | } else {
165 | findDir(workDir, oldDir);
166 | }
167 | }
168 |
169 | };
170 |
171 | findDir(dir, dir);
172 |
173 | }
174 | }
175 | if (!doSubPkg) {
176 | throw new Error("This pkg may be a sub pkg, please add -s=Main Dir, like: node wuWxapkg.js -s=./testpkg/test/ ./testpkg/test-pkg-sub.wxapkg");
177 | }
178 | }
179 | }
180 |
181 | function doFile(name, cb, order) {
182 | for (let ord of order) if (ord.startsWith("s=")) global.subPack = ord.slice(3);
183 | console.log("Unpack file " + name + "...");
184 | let dir = path.resolve(name, "..", path.basename(name, ".wxapkg"));
185 | wu.get(name, buf => {
186 | let [infoListLength, dataLength] = header(buf.slice(0, 14));
187 | if (order.includes("o")) wu.addIO(console.log.bind(console), "Unpack done.");
188 | else wu.addIO(packDone, dir, cb, order);
189 | saveFile(dir, buf, genList(buf.slice(14, infoListLength + 14)));
190 | }, {});
191 | }
192 |
193 | module.exports = {doFile: doFile};
194 | if (require.main === module) {
195 | 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");
196 | }
197 |
--------------------------------------------------------------------------------
/wuWxml.js:
--------------------------------------------------------------------------------
1 | const wu = require("./wuLib.js");
2 | const {getZ} = require("./wuRestoreZ.js");
3 | const {wxsBeautify} = require("./wuJs.js");
4 | const fs = require('fs');
5 | const path = require("path");
6 | const esprima = require('esprima');
7 | const {VM} = require('vm2');
8 | const escodegen = require('escodegen');
9 |
10 | function analyze(core, z, namePool, xPool, fakePool = {}, zMulName = "0") {
11 | function anaRecursion(core, fakePool = {}) {
12 | return analyze(core, z, namePool, xPool, fakePool, zMulName);
13 | }
14 |
15 | function push(name, elem) {
16 | namePool[name] = elem;
17 | }
18 |
19 | function pushSon(pname, son) {
20 | if (fakePool[pname]) fakePool[pname].son.push(son);
21 | else namePool[pname].son.push(son);
22 | }
23 |
24 | for (let ei = 0; ei < core.length; ei++) {
25 | let e = core[ei];
26 | switch (e.type) {
27 | case "ExpressionStatement": {
28 | let f = e.expression;
29 | if (f.callee) {
30 | if (f.callee.type == "Identifier") {
31 | switch (f.callee.name) {
32 | case "_r":
33 | namePool[f.arguments[0].name].v[f.arguments[1].value] = z[f.arguments[2].value];
34 | break;
35 | case "_rz":
36 | namePool[f.arguments[1].name].v[f.arguments[2].value] = z.mul[zMulName][f.arguments[3].value];
37 | break;
38 | case "_":
39 | pushSon(f.arguments[0].name, namePool[f.arguments[1].name]);
40 | break;
41 | case "_2": {
42 | let item = f.arguments[6].value;//def:item
43 | let index = f.arguments[7].value;//def:index
44 | let data = z[f.arguments[0].value];
45 | let key = escodegen.generate(f.arguments[8]).slice(1, -1);//f.arguments[8].value;//def:""
46 | let obj = namePool[f.arguments[5].name];
47 | let gen = namePool[f.arguments[1].name];
48 | if (gen.tag == "gen") {
49 | let ret = gen.func.body.body.pop().argument.name;
50 | anaRecursion(gen.func.body.body, {[ret]: obj});
51 | }
52 | obj.v["wx:for"] = data;
53 | if (index != "index") obj.v["wx:for-index"] = index;
54 | if (item != "item") obj.v["wx:for-item"] = item;
55 | if (key != "") obj.v["wx:key"] = key;
56 | }
57 | break;
58 | case "_2z": {
59 | let item = f.arguments[7].value;//def:item
60 | let index = f.arguments[8].value;//def:index
61 | let data = z.mul[zMulName][f.arguments[1].value];
62 | let key = escodegen.generate(f.arguments[9]).slice(1, -1);//f.arguments[9].value;//def:""
63 | let obj = namePool[f.arguments[6].name];
64 | let gen = namePool[f.arguments[2].name];
65 | if (gen.tag == "gen") {
66 | let ret = gen.func.body.body.pop().argument.name;
67 | anaRecursion(gen.func.body.body, {[ret]: obj});
68 | }
69 | obj.v["wx:for"] = data;
70 | if (index != "index") obj.v["wx:for-index"] = index;
71 | if (item != "item") obj.v["wx:for-item"] = item;
72 | if (key != "") obj.v["wx:key"] = key;
73 | }
74 | break;
75 | case "_ic":
76 | pushSon(f.arguments[5].name, {
77 | tag: "include",
78 | son: [],
79 | v: {src: xPool[f.arguments[0].property.value]}
80 | });
81 | break;
82 | case "_ai": {//template import
83 | let to = Object.keys(fakePool)[0];
84 | if (to) pushSon(to, {
85 | tag: "import",
86 | son: [],
87 | v: {src: xPool[f.arguments[1].property.value]}
88 | });
89 | else throw Error("Unexpected fake pool");
90 | }
91 | break;
92 | case "_af":
93 | //ignore _af
94 | break;
95 | default:
96 | throw Error("Unknown expression callee name " + f.callee.name);
97 | }
98 | } else if (f.callee.type == "MemberExpression") {
99 | if (f.callee.object.name == "cs" || f.callee.property.name == "pop") break;
100 | throw Error("Unknown member expression");
101 | } else throw Error("Unknown callee type " + f.callee.type);
102 | } else if (f.type == "AssignmentExpression" && f.operator == "=") {
103 | //no special use
104 | } else throw Error("Unknown expression statement.");
105 | break;
106 | }
107 | case "VariableDeclaration":
108 | for (let dec of e.declarations) {
109 | if (dec.init.type == "CallExpression") {
110 | switch (dec.init.callee.name) {
111 | case "_n":
112 | push(dec.id.name, {tag: dec.init.arguments[0].value, son: [], v: {}});
113 | break;
114 | case "_v":
115 | push(dec.id.name, {tag: "block", son: [], v: {}});
116 | break;
117 | case "_o":
118 | push(dec.id.name, {
119 | tag: "__textNode__",
120 | textNode: true,
121 | content: z[dec.init.arguments[0].value]
122 | });
123 | break;
124 | case "_oz":
125 | push(dec.id.name, {
126 | tag: "__textNode__",
127 | textNode: true,
128 | content: z.mul[zMulName][dec.init.arguments[1].value]
129 | });
130 | break;
131 | case "_m": {
132 | if (dec.init.arguments[2].elements.length > 0)
133 | throw Error("Noticable generics content: " + dec.init.arguments[2].toString());
134 | let mv = {};
135 | let name = null, base = 0;
136 | for (let x of dec.init.arguments[1].elements) {
137 | let v = x.value;
138 | if (!v && typeof v != "number") {
139 | if (x.type == "UnaryExpression" && x.operator == "-") v = -x.argument.value;
140 | else throw Error("Unknown type of object in _m attrs array: " + x.type);
141 | }
142 | if (name === null) {
143 | name = v;
144 | } else {
145 | if (base + v < 0) mv[name] = null; else {
146 | mv[name] = z[base + v];
147 | if (base == 0) base = v;
148 | }
149 | name = null;
150 | }
151 | }
152 | push(dec.id.name, {tag: dec.init.arguments[0].value, son: [], v: mv});
153 | }
154 | break;
155 | case "_mz": {
156 | if (dec.init.arguments[3].elements.length > 0)
157 | throw Error("Noticable generics content: " + dec.init.arguments[3].toString());
158 | let mv = {};
159 | let name = null, base = 0;
160 | for (let x of dec.init.arguments[2].elements) {
161 | let v = x.value;
162 | if (!v && typeof v != "number") {
163 | if (x.type == "UnaryExpression" && x.operator == "-") v = -x.argument.value;
164 | else throw Error("Unknown type of object in _mz attrs array: " + x.type);
165 | }
166 | if (name === null) {
167 | name = v;
168 | } else {
169 | if (base + v < 0) mv[name] = null; else {
170 | mv[name] = z.mul[zMulName][base + v];
171 | if (base == 0) base = v;
172 | }
173 | name = null;
174 | }
175 | }
176 | push(dec.id.name, {tag: dec.init.arguments[1].value, son: [], v: mv});
177 | }
178 | break;
179 | case "_gd"://template use/is
180 | {
181 | let is = namePool[dec.init.arguments[1].name].content;
182 | let data = null, obj = null;
183 | ei++;
184 | for (let e of core[ei].consequent.body) {
185 | if (e.type == "VariableDeclaration") {
186 | for (let f of e.declarations) {
187 | if (f.init.type == "LogicalExpression" && f.init.left.type == "CallExpression") {
188 | if (f.init.left.callee.name == "_1") data = z[f.init.left.arguments[0].value];
189 | else if (f.init.left.callee.name == "_1z") data = z.mul[zMulName][f.init.left.arguments[1].value];
190 | }
191 | }
192 | } else if (e.type == "ExpressionStatement") {
193 | let f = e.expression;
194 | if (f.type == "AssignmentExpression" && f.operator == "=" && f.left.property && f.left.property.name == "wxXCkey") {
195 | obj = f.left.object.name;
196 | }
197 | }
198 | }
199 | namePool[obj].tag = "template";
200 | Object.assign(namePool[obj].v, {is: is, data: data});
201 | }
202 | break;
203 | default: {
204 | let funName = dec.init.callee.name;
205 | if (funName.startsWith("gz$gwx")) {
206 | zMulName = funName.slice(6);
207 | } else throw Error("Unknown init callee " + funName);
208 | }
209 | }
210 | } else if (dec.init.type == "FunctionExpression") {
211 | push(dec.id.name, {tag: "gen", func: dec.init});
212 | } else if (dec.init.type == "MemberExpression") {
213 | 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") {
214 | if (dec.init.property.name == "j") {//include
215 | //do nothing
216 | } else if (dec.init.property.name == "i") {//import
217 | //do nothing
218 | } else throw Error("Unknown member expression declaration.");
219 | } else throw Error("Unknown member expression declaration.");
220 | } else throw Error("Unknown declaration init type " + dec.init.type);
221 | }
222 | break;
223 | case "IfStatement":
224 | if (e.test.callee.name.startsWith("_o")) {
225 | function parse_OFun(e) {
226 | if (e.test.callee.name == "_o") return z[e.test.arguments[0].value];
227 | else if (e.test.callee.name == "_oz") return z.mul[zMulName][e.test.arguments[1].value];
228 | else throw Error("Unknown if statement test callee name:" + e.test.callee.name);
229 | }
230 |
231 | let vname = e.consequent.body[0].expression.left.object.name;
232 | let nif = {tag: "block", v: {"wx:if": parse_OFun(e)}, son: []};
233 | anaRecursion(e.consequent.body, {[vname]: nif});
234 | pushSon(vname, nif);
235 | if (e.alternate) {
236 | while (e.alternate && e.alternate.type == "IfStatement") {
237 | e = e.alternate;
238 | nif = {tag: "block", v: {"wx:elif": parse_OFun(e)}, son: []};
239 | anaRecursion(e.consequent.body, {[vname]: nif});
240 | pushSon(vname, nif);
241 | }
242 | if (e.alternate && e.alternate.type == "BlockStatement") {
243 | e = e.alternate;
244 | nif = {tag: "block", v: {"wx:else": null}, son: []};
245 | anaRecursion(e.body, {[vname]: nif});
246 | pushSon(vname, nif);
247 | }
248 | }
249 | } else throw Error("Unknown if statement.");
250 | break;
251 | default:
252 | throw Error("Unknown type " + e.type);
253 | }
254 | }
255 | }
256 |
257 | function wxmlify(str, isText) {
258 | if (typeof str == "undefined" || str === null) return "Empty";//throw Error("Empty str in "+(isText?"text":"prop"));
259 | if (isText) return str;//may have some bugs in some specific case(undocumented by tx)
260 | else return str.replace(/"/g, '\\"');
261 | }
262 |
263 | function elemToString(elem, dep, moreInfo = false) {
264 | const longerList = [];//put tag name which can't be style.
265 | const indent = ' '.repeat(4);
266 |
267 | function isTextTag(elem) {
268 | return elem.tag == "__textNode__" && elem.textNode;
269 | }
270 |
271 | function elemRecursion(elem, dep) {
272 | return elemToString(elem, dep, moreInfo);
273 | }
274 |
275 | function trimMerge(rets) {
276 | let needTrimLeft = false, ans = "";
277 | for (let ret of rets) {
278 | if (ret.textNode == 1) {
279 | if (!needTrimLeft) {
280 | needTrimLeft = true;
281 | ans = ans.trimRight();
282 | }
283 | } else if (needTrimLeft) {
284 | needTrimLeft = false;
285 | ret = ret.trimLeft();
286 | }
287 | ans += ret;
288 | }
289 | return ans;
290 | }
291 |
292 | if (isTextTag(elem)) {
293 | //In comment, you can use typify text node, which beautify its code, but may destroy ui.
294 | //So, we use a "hack" way to solve this problem by letting typify program stop when face textNode
295 | let str = new String(wxmlify(elem.content, true));
296 | str.textNode = 1;
297 | return wxmlify(str, true);//indent.repeat(dep)+wxmlify(elem.content.trim(),true)+"\n";
298 | }
299 | if (elem.tag == "block" && !moreInfo) {
300 | if (elem.son.length == 1 && !isTextTag(elem.son[0])) {
301 | let ok = true, s = elem.son[0];
302 | for (let x in elem.v) if (x in s.v) {
303 | ok = false;
304 | break;
305 | }
306 | 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.
307 | Object.assign(s.v, elem.v);
308 | return elemRecursion(s, dep);
309 | }
310 | } else if (Object.keys(elem.v).length == 0) {
311 | let ret = [];
312 | for (let s of elem.son) ret.push(elemRecursion(s, dep));
313 | return trimMerge(ret);
314 | }
315 | }
316 | let ret = indent.repeat(dep) + "<" + elem.tag;
317 | for (let v in elem.v) ret += " " + v + (elem.v[v] !== null ? "=\"" + wxmlify(elem.v[v]) + "\"" : "");
318 | if (elem.son.length == 0) {
319 | if (longerList.includes(elem.tag)) return ret + " />\n";
320 | else return ret + ">" + elem.tag + ">\n";
321 | }
322 | ret += ">\n";
323 | let rets = [ret];
324 | for (let s of elem.son) rets.push(elemRecursion(s, dep + 1));
325 | rets.push(indent.repeat(dep) + "" + elem.tag + ">\n");
326 | return trimMerge(rets);
327 | }
328 |
329 | function doWxml(state, dir, name, code, z, xPool, rDs, wxsList, moreInfo) {
330 | let rname = code.slice(code.lastIndexOf("return") + 6).replace(/[\;\}]/g, "").trim();
331 | code = code.slice(code.indexOf("\n"), code.lastIndexOf("return")).trim();
332 | let r = {son: []};
333 | analyze(esprima.parseScript(code).body, z, {[rname]: r}, xPool, {[rname]: r});
334 | let ans = [];
335 | for (let elem of r.son) ans.push(elemToString(elem, 0, moreInfo));
336 | let result = [ans.join("")];
337 | for (let v in rDs) {
338 | state[0] = v;
339 | let oriCode = rDs[v].toString();
340 | let rname = oriCode.slice(oriCode.lastIndexOf("return") + 6).replace(/[\;\}]/g, "").trim();
341 | let tryPtr = oriCode.indexOf("\ntry{");
342 | let zPtr = oriCode.indexOf("var z=gz$gwx");
343 | let code = oriCode.slice(tryPtr + 5, oriCode.lastIndexOf("\n}catch(")).trim();
344 | if (zPtr != -1 && tryPtr > zPtr) {
345 | let attach = oriCode.slice(zPtr);
346 | attach = attach.slice(0, attach.indexOf("()")) + "()\n";
347 | code = attach + code;
348 | }
349 | let r = {tag: "template", v: {name: v}, son: []};
350 | analyze(esprima.parseScript(code).body, z, {[rname]: r}, xPool, {[rname]: r});
351 | result.unshift(elemToString(r, 0, moreInfo));
352 | }
353 | name = path.resolve(dir, name);
354 | if (wxsList[name]) result.push(wxsList[name]);
355 | wu.save(name, result.join(""));
356 | }
357 |
358 | function tryWxml(dir, name, code, z, xPool, rDs, ...args) {
359 | console.log("Decompile " + name + "...");
360 | let state = [null];
361 | try {
362 | doWxml(state, dir, name, code, z, xPool, rDs, ...args);
363 | console.log("Decompile success!");
364 | } catch (e) {
365 | console.log("error on " + name + "(" + (state[0] === null ? "Main" : "Template-" + state[0]) + ")\nerr: ", e);
366 | if (state[0] === null) wu.save(path.resolve(dir, name + ".ori.js"), code);
367 | else wu.save(path.resolve(dir, name + ".tem-" + state[0] + ".ori.js"), rDs[state[0]].toString());
368 | }
369 | }
370 |
371 | function doWxs(code) {
372 | const before = 'nv_module={nv_exports:{}};';
373 | return wxsBeautify(code.slice(code.indexOf(before) + before.length, code.lastIndexOf('return nv_module.nv_exports;}')).replace(/nv\_/g, ''));
374 | }
375 |
376 | function doFrame(name, cb, order, mainDir) {
377 | let moreInfo = order.includes("m");
378 | wxsList = {};
379 | wu.get(name, code => {
380 | getZ(code, z => {
381 | const before = "\nvar nv_require=function(){var nnm=";
382 | code = code.slice(code.lastIndexOf(before) + before.length, code.lastIndexOf("if(path&&e_[path]){"));
383 | json = code.slice(0, code.indexOf("};") + 1);
384 | let endOfRequire = code.indexOf("()\r\n") + 4;
385 | if (endOfRequire == 4 - 1) endOfRequire = code.indexOf("()\n") + 3;
386 | code = code.slice(endOfRequire);
387 | let rD = {}, rE = {}, rF = {}, requireInfo = {}, x, vm = new VM({
388 | sandbox: {
389 | d_: rD, e_: rE, f_: rF, _vmRev_(data) {
390 | [x, requireInfo] = data;
391 | }, nv_require(path) {
392 | return () => path;
393 | }
394 | }
395 | });
396 | let vmCode = code + "\n_vmRev_([x," + json + "])";
397 | vm.run(vmCode);
398 | let dir = mainDir || path.dirname(name), pF = [];
399 | for (let info in rF) if (typeof rF[info] == "function") {
400 | let name = path.resolve(dir, (info[0] == '/' ? '.' : '') + info), ref = rF[info]();
401 | pF[ref] = info;
402 | wu.save(name, doWxs(requireInfo[ref].toString()));
403 | }
404 | for (let info in rF) if (typeof rF[info] == "object") {
405 | let name = path.resolve(dir, (info[0] == '/' ? '.' : '') + info);
406 | let res = [], now = rF[info];
407 | for (let deps in now) {
408 | let ref = now[deps]();
409 | if (ref.includes(":")) res.push("\n" + doWxs(requireInfo[ref].toString()) + "\n");
410 | else if (pF[ref]) res.push("");
411 | else res.push("");
412 | wxsList[name] = res.join("\n");
413 | }
414 | }
415 | for (let name in rE) tryWxml(dir, name, rE[name].f.toString(), z, x, rD[name], wxsList, moreInfo);
416 | cb({[name]: 4});
417 | });
418 | });
419 | }
420 |
421 | module.exports = {doFrame: doFrame};
422 | if (require.main === module) {
423 | wu.commandExecute(doFrame, "Restore wxml files.\n\n\n\n restore wxml file from page-frame.html or app-wxss.js.");
424 | }
425 |
--------------------------------------------------------------------------------
/wuWxss.js:
--------------------------------------------------------------------------------
1 | const wu = require("./wuLib.js");
2 | const path = require("path");
3 | const fs = require("fs");
4 | const {VM} = require('vm2');
5 | const cssbeautify = require('cssbeautify');
6 | const csstree = require('css-tree');
7 | const cheerio = require('cheerio');
8 |
9 | function chomp_balanced(input_str, scan_start, open_char, close_char) {
10 | quote_chars = ['\"', '\''];
11 | let now = scan_start - 1;
12 | let depth = 0;
13 | let in_quote = false;
14 | let start = -1;
15 | let end = -1;
16 | let end_index = input_str.length - 1;
17 | let now_quote_char = undefined;
18 | let c = undefined;
19 | let last_char = undefined;
20 |
21 | let escape_char = '\\';
22 | while (true) {
23 | now += 1;
24 | if (now > end_index) {
25 | break
26 | }
27 | last_char = c;
28 | c = input_str[now];
29 | if (quote_chars.indexOf(c) > -1 && c !== open_char) {
30 | if (in_quote && c === now_quote_char && last_char !== escape_char) {
31 | in_quote = false;
32 | now_quote_char = undefined
33 | } else {
34 | if (!in_quote) {
35 | now_quote_char = c;
36 | }
37 | in_quote = true;
38 | }
39 | continue;
40 | }
41 | if (in_quote) {
42 | continue;
43 | }
44 |
45 | if (open_char !== close_char) {
46 | if (c === open_char) {
47 | depth += 1;
48 | if (start === -1) {
49 | start = now
50 | }
51 | // let left = input_str.substr(now);
52 | // console.log(left);
53 | } else if (c === close_char) {
54 | depth -= 1;
55 | if (depth === 0) {
56 | end = now;
57 | break;
58 | }
59 | }
60 |
61 | } else {
62 | //开闭相同的时候,相同即可退出
63 | if (c === open_char) {
64 | depth += 1;
65 | if (start === -1) {
66 | start = now
67 | }
68 | if (depth === 2) {
69 | // 开闭相同,这个时候就满足条件了
70 | end = now;
71 | break
72 | }
73 | }
74 | }
75 | }
76 | if (start >= 0 && end > 0) {
77 | return {
78 | start: start,
79 | end: end
80 | }
81 |
82 | }
83 | return undefined;
84 | }
85 |
86 | function doWxss(dir, cb, mainDir, nowDir) {
87 | let saveDir = dir;
88 | let isSubPkg = mainDir && mainDir.length > 0;
89 | if (isSubPkg) {
90 | saveDir = mainDir
91 | }
92 | function GwxCfg() {
93 | }
94 |
95 | GwxCfg.prototype = {
96 | $gwx() {
97 | }
98 | };
99 | for (let i = 0; i < 300; i++) GwxCfg.prototype["$gwx" + i] = GwxCfg.prototype.$gwx;
100 | let runList = {}, pureData = {}, result = {}, actualPure = {}, importCnt = {}, frameName = "", onlyTest = true,
101 | blockCss = [];//custom block css file which won't be imported by others.(no extension name)
102 | function cssRebuild(data) {//need to bind this as {cssFile:__name__} before call
103 | let cssFile;
104 |
105 | function statistic(data) {
106 | function addStat(id) {
107 | //修改这里解决 抛出throw bridge.from(e);
108 | // if (!importCnt[id]) importCnt[id] = 1, statistic(pureData[id]);
109 | if(!importCnt[id]){
110 | if(pureData){
111 | importCnt[id]=1;
112 | statistic(pureData[id]);
113 | }
114 | }
115 | else ++importCnt[id];
116 | }
117 |
118 | if (typeof data === "number") return addStat(data);
119 | for (let content of data) if (typeof content === "object" && content[0] == 2) addStat(content[1]);
120 | }
121 |
122 | function makeup(data) {
123 | var isPure = typeof data === "number";
124 | if (onlyTest) {
125 | statistic(data);
126 | if (!isPure) {
127 | if (data.length == 1 && data[0][0] == 2) data = data[0][1];
128 | else return "";
129 | }
130 | if (!actualPure[data] && !blockCss.includes(wu.changeExt(wu.toDir(cssFile, frameName), ""))) {
131 | console.log("Regard " + cssFile + " as pure import file.");
132 | actualPure[data] = cssFile;
133 | }
134 | return "";
135 | }
136 | let res = [], attach = "";
137 | if (isPure && actualPure[data] != cssFile) {
138 | if (actualPure[data]) return '@import "' + wu.changeExt(wu.toDir(actualPure[data], cssFile), ".wxss") + '";\n';
139 | else {
140 | res.push("/*! Import by _C[" + data + "], whose real path we cannot found. */");
141 | attach = "/*! Import end */";
142 | }
143 | }
144 | let exactData = isPure ? pureData[data] : data;
145 | for (let content of exactData)
146 | if (typeof content === "object") {
147 | switch (content[0]) {
148 | case 0://rpx
149 | res.push(content[1] + "rpx");
150 | break;
151 | case 1://add suffix, ignore it for restoring correct!
152 | break;
153 | case 2://import
154 | res.push(makeup(content[1]));
155 | break;
156 | }
157 | } else res.push(content);
158 | return res.join("") + attach;
159 | }
160 |
161 | return () => {
162 | cssFile = this.cssFile;
163 | if (!result[cssFile]) result[cssFile] = "";
164 | result[cssFile] += makeup(data);
165 | };
166 | }
167 |
168 | function runVM(name, code) {
169 | let wxAppCode = {}, handle = {cssFile: name};
170 | let vm = new VM({
171 | sandbox: Object.assign(new GwxCfg(), {
172 | __wxAppCode__: wxAppCode,
173 | setCssToHead: cssRebuild.bind(handle),
174 | $gwx(path, global) {
175 |
176 | }
177 | })
178 | });
179 |
180 | // console.log('do css runVm: ' + name);
181 | vm.run(code);
182 | for (let name in wxAppCode) {
183 | handle.cssFile = path.resolve(saveDir, name);
184 | if (name.endsWith(".wxss")) {
185 | wxAppCode[name]();
186 | }
187 | }
188 | }
189 |
190 | function preRun(dir, frameFile, mainCode, files, cb) {
191 | wu.addIO(cb);
192 | runList[path.resolve(dir, "./app.wxss")] = mainCode;
193 |
194 | for (let name of files) {
195 | if (name != frameFile) {
196 | wu.get(name, code => {
197 | code = code.replace(/display:-webkit-box;display:-webkit-flex;/gm, '');
198 | code = code.slice(0, code.indexOf("\n"));
199 | if (code.indexOf("setCssToHead(") > -1) {
200 | let lastName = name;
201 | let dirSplit = name.split(nowDir + '/');
202 | if (dirSplit.length > 1) {
203 | lastName = path.resolve(saveDir, dirSplit[1]);
204 | }
205 | runList[lastName] = code.slice(code.indexOf("setCssToHead("));
206 | }
207 | });
208 | }
209 | }
210 | }
211 |
212 | function runOnce() {
213 | for (let name in runList) runVM(name, runList[name]);
214 | }
215 |
216 | function transformCss(style) {
217 | let ast = csstree.parse(style);
218 | csstree.walk(ast, function (node) {
219 | if (node.type == "Comment") {//Change the comment because the limit of css-tree
220 | node.type = "Raw";
221 | node.value = "\n/*" + node.value + "*/\n";
222 | }
223 | if (node.type == "TypeSelector") {
224 | if (node.name.startsWith("wx-")) node.name = node.name.slice(3);
225 | else if (node.name == "body") node.name = "page";
226 | }
227 | if (node.children) {
228 | const removeType = ["webkit", "moz", "ms", "o"];
229 | let list = {};
230 | node.children.each((son, item) => {
231 | if (son.type == "Declaration") {
232 | if (list[son.property]) {
233 | let a = item, b = list[son.property], x = son, y = b.data, ans = null;
234 | if (x.value.type == 'Raw' && x.value.value.startsWith("progid:DXImageTransform")) {
235 | node.children.remove(a);
236 | ans = b;
237 | } else if (y.value.type == 'Raw' && y.value.value.startsWith("progid:DXImageTransform")) {
238 | node.children.remove(b);
239 | ans = a;
240 | } else {
241 | let xValue = x.value.children && x.value.children.head && x.value.children.head.data.name,
242 | yValue = y.value.children && y.value.children.head && y.value.children.head.data.name;
243 | if (xValue && yValue) for (let type of removeType) if (xValue == `-${type}-${yValue}`) {
244 | node.children.remove(a);
245 | ans = b;
246 | break;
247 | } else if (yValue == `-${type}-${xValue}`) {
248 | node.children.remove(b);
249 | ans = a;
250 | break;
251 | } else {
252 | let mValue = `-${type}-`;
253 | if (xValue.startsWith(mValue)) xValue = xValue.slice(mValue.length);
254 | if (yValue.startsWith(mValue)) yValue = yValue.slice(mValue.length);
255 | }
256 | if (ans === null) ans = b;
257 | }
258 | list[son.property] = ans;
259 | } else list[son.property] = item;
260 | }
261 | });
262 | for (let name in list) if (!name.startsWith('-'))
263 | for (let type of removeType) {
264 | let fullName = `-${type}-${name}`;
265 | if (list[fullName]) {
266 | node.children.remove(list[fullName]);
267 | delete list[fullName];
268 | }
269 | }
270 | }
271 | });
272 | return cssbeautify(csstree.generate(ast), {indent: ' ', autosemicolon: true});
273 | }
274 |
275 | wu.scanDirByExt(dir, ".html", files => {
276 | let frameFile = "";
277 | if (fs.existsSync(path.resolve(dir, "page-frame.html")))
278 | frameFile = path.resolve(dir, "page-frame.html");
279 | else if (fs.existsSync(path.resolve(dir, "app-wxss.js")))
280 | frameFile = path.resolve(dir, "app-wxss.js");
281 | else if (fs.existsSync(path.resolve(dir, "page-frame.js")))
282 | frameFile = path.resolve(dir, "page-frame.js");
283 | else throw Error("page-frame-like file is not found in the package by auto.");
284 | wu.get(frameFile, code => {
285 | code = code.replace(/display:-webkit-box;display:-webkit-flex;/gm, '');
286 | let scriptCode = code;
287 | //extract script content from html
288 | if (frameFile.endsWith(".html")) {
289 | try {
290 | const $ = cheerio.load(code);
291 | scriptCode = [].join.apply($('html').find('script').map(function (item) {
292 | return $(this).html();
293 | }, "\n"));
294 | } catch (e) {
295 | //ignore
296 | }
297 | }
298 |
299 | let window = {
300 | screen: {
301 | width: 720,
302 | height: 1028,
303 | orientation: {
304 | type: 'vertical'
305 | }
306 | }
307 | };
308 | let navigator = {
309 | userAgent: "iPhone"
310 | };
311 |
312 | scriptCode = scriptCode.slice(scriptCode.lastIndexOf('window.__wcc_version__'));
313 | let mainCode = 'window= ' + JSON.stringify(window) +
314 | ';\nnavigator=' + JSON.stringify(navigator) +
315 | ';\nvar __mainPageFrameReady__ = window.__mainPageFrameReady__ || function(){};var __WXML_GLOBAL__={entrys:{},defines:{},modules:{},ops:[],wxs_nf_init:undefined,total_ops:0};var __vd_version_info__=__vd_version_info__||{}' +
316 | ";\n" + scriptCode;
317 |
318 | //remove setCssToHead function
319 | let setCssToHeadFuctionStartIndex = mainCode.indexOf("var setCssToHead = function");
320 | let setCssToHeadFunctionParamIndexes = chomp_balanced(mainCode, setCssToHeadFuctionStartIndex, '(', ')');
321 | let setCssToHeadFunctionEndIndexes = chomp_balanced(mainCode, setCssToHeadFunctionParamIndexes.end + 1, '{', '}');
322 | mainCode = mainCode.substr(0, setCssToHeadFuctionStartIndex) + mainCode.substr(setCssToHeadFunctionEndIndexes.end + 1);
323 |
324 | //remove var __wxAppCode__ = {};
325 | // let wxAppCodeVarDeclare = "var __wxAppCode__={};";
326 | // let wxAppCodeVarDeclareIndex = mainCode.indexOf(wxAppCodeVarDeclare);
327 | // let wxAppCodeVarDeclareEnd = wxAppCodeVarDeclareIndex + wxAppCodeVarDeclare.length;
328 | // mainCode = mainCode.substr(0, wxAppCodeVarDeclareIndex) + mainCode.substr(wxAppCodeVarDeclareEnd);
329 |
330 | code = code.slice(code.lastIndexOf('var setCssToHead = function(file, _xcInvalid'));
331 | code = code.slice(code.lastIndexOf('\nvar _C= ') + 1);
332 | //let oriCode=code;
333 | code = code.slice(0, code.indexOf('\n'));
334 | let vm = new VM({sandbox: {}});
335 | // pureData = vm.run(code + "\n_C");
336 | pureData = vm.run(code + "}");
337 | //let mainCode=oriCode.slice(oriCode.indexOf("setCssToHead"),oriCode.lastIndexOf(";var __pageFrameEndTime__"));
338 | console.log("Guess wxss(first turn)...");
339 | preRun(dir, frameFile, mainCode, files, () => {
340 | frameName = frameFile;
341 | onlyTest = true;
342 | runOnce();
343 | onlyTest = false;
344 | console.log("Import count info: %j", importCnt);
345 | for (let id in pureData) if (!actualPure[id]) {
346 | if (!importCnt[id]) importCnt[id] = 0;
347 | if (importCnt[id] <= 1) {
348 | console.log("Cannot find pure import for _C[" + id + "] which is only imported " + importCnt[id] + " times. Let importing become copying.");
349 | } else {
350 | let newFile = path.resolve(dir, "__wuBaseWxss__/" + id + ".wxss");
351 | console.log("Cannot find pure import for _C[" + id + "], force to save it in (" + newFile + ").");
352 | id = Number.parseInt(id);
353 | actualPure[id] = newFile;
354 | cssRebuild.call({cssFile: newFile}, id)();
355 | }
356 | }
357 | console.log("Guess wxss(first turn) done.\nGenerate wxss(second turn)...");
358 | runOnce()
359 | console.log("Generate wxss(second turn) done.\nSave wxss...");
360 |
361 | console.log('saveDir: ' + saveDir);
362 | for (let name in result) {
363 | let pathFile = path.resolve(saveDir, wu.changeExt(name, ".wxss"));
364 | wu.save(pathFile, transformCss(result[name]));
365 | }
366 | let delFiles = {};
367 | for (let name of files) delFiles[name] = 8;
368 | delFiles[frameFile] = 4;
369 | cb(delFiles);
370 | });
371 | });
372 | });
373 | }
374 |
375 | module.exports = {doWxss: doWxss};
376 | if (require.main === module) {
377 | 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).");
378 | }
379 |
--------------------------------------------------------------------------------
/解密工具/README.md:
--------------------------------------------------------------------------------
1 | ### 小程序解码反编译
2 |
3 | - 安装pc端微信
4 | - 打开小程序
5 | - 查看微信文件管理目录
6 | - 打开\WeChat Files\Applet查看小程序生成目录
7 | - 打开 UnpackMiniApp.exe选中小程序 __APP__.wxapkg 文件
8 | - 解密到wxpack目录
9 | - cd wxappUnpacker-master
10 | - node wuWxapkg.js ..\wxpack\wx9fcfea1cbb0d10c2.wxapkg
11 |
12 | |
13 | |
14 | |
15 | |
|
16 |
17 |
18 |
--------------------------------------------------------------------------------
/解密工具/UnpackMiniApp.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/threecha/wxappUnpacker/cfc87472325994bb246f9f3f44910f207b40024c/解密工具/UnpackMiniApp.exe
--------------------------------------------------------------------------------