├── .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 | ![版本 0.3](https://img.shields.io/badge/版本-0.3-red.svg) ![支持的微信版本 >20180111](https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E7%89%88%E6%9C%AC-%3E=20180111-brightgreen.svg) 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 | ![Alt text](image.png) 16 | ![Alt text](image-1.png) 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 | ![解包后的目录文件](testpkg/testdir.png) 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 | ![输出结果文件](testpkg/cc.png) 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 + ">\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) + "\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 --------------------------------------------------------------------------------