diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 241fb33..ca7841b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -23,6 +23,7 @@ builds: - CGO_ENABLED=0 goos: - windows + - darwin archives: - format: binary diff --git a/cmd/root.go b/cmd/root.go index b4b7954..866323b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,8 +1,6 @@ package cmd import ( - "flag" - "fmt" "log" "sync" @@ -11,13 +9,7 @@ import ( "github.com/Ackites/KillWxapkg/internal/restore" ) -func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bool) { - if appID == "" || input == "" { - fmt.Println("使用方法: program -id= -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录> [-ext=<文件后缀>] [-restore]") - flag.PrintDefaults() - return - } - +func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bool, noClean bool) { // 存储配置 configManager := NewSharedConfigManager() configManager.Set("appID", appID) @@ -26,6 +18,7 @@ func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bo configManager.Set("fileExt", fileExt) configManager.Set("restoreDir", restoreDir) configManager.Set("pretty", pretty) + configManager.Set("noClean", noClean) inputFiles := ParseInput(input, fileExt) diff --git a/go.mod b/go.mod index f75a79b..323f0be 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.22 require ( github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c - github.com/dop251/goja v0.0.0-20240707163329-b1681fb2a2f5 - github.com/gorilla/css v1.0.1 + github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e + github.com/tdewolff/parse/v2 v2.7.15 github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 golang.org/x/crypto v0.25.0 golang.org/x/net v0.27.0 diff --git a/go.sum b/go.sum index 7e5d3c2..f67fe2c 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,16 @@ github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9 github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dop251/goja v0.0.0-20240707163329-b1681fb2a2f5 h1:ZRqTaoW9WZ2DqeOQGhK9q73eCb47SEs30GV2IRHT9bo= -github.com/dop251/goja v0.0.0-20240707163329-b1681fb2a2f5/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw= +github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e h1:jhfevGje1Gw4uDNv30Kj+tl/SpHb4La9InZOnwMrNUs= +github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= -github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= -github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw= +github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts= github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= diff --git a/internal/config/delete.go b/internal/config/delete.go index 0fe0e7f..4774532 100644 --- a/internal/config/delete.go +++ b/internal/config/delete.go @@ -54,6 +54,10 @@ func (f *FileDeletionManager) DeleteFiles() { log.Println("文件删除操作已取消") return default: + // 判断文件是否存在 + if _, err := os.Stat(file); os.IsNotExist(err) { + continue + } err := os.Remove(file) if err != nil { log.Printf("删除文件 %s 失败: %v\n", file, err) diff --git a/internal/restore/decompiler.go b/internal/restore/decompiler.go index 6e97004..a53b0e1 100644 --- a/internal/restore/decompiler.go +++ b/internal/restore/decompiler.go @@ -25,7 +25,7 @@ func isParserV1(wxapkg *config.WxapkgInfo) bool { } } -func isParserV3(wxapkg *config.WxapkgInfo) bool { +func isParserV2(wxapkg *config.WxapkgInfo) bool { switch wxapkg.WxapkgType { case enum.App_V3, enum.App_V4, enum.APP_SUBPACKAGE_V2, enum.APP_PLUGIN_V1: return true @@ -89,6 +89,7 @@ func (d *WxapkgDecompiler) Decompile(outputDir string) { wxapkgManager := config.GetWxapkgManager() for _, wxapkg := range wxapkgManager.Packages { + log.Println(wxapkg.WxapkgType) switch wxapkg.WxapkgType { case enum.App_V1, enum.App_V4: wxapkg.Option = &config.WxapkgOption{ @@ -152,8 +153,39 @@ func setApp(wxapkg *config.WxapkgInfo) { } wxapkg.Parsers = append(wxapkg.Parsers, &unpack.JavaScriptParser{OutputDir: OutputDir}) + wxapkg.Parsers = append(wxapkg.Parsers, &unpack.XssParser{OutputDir: OutputDir}) + if isParserV1(wxapkg) { + wxapkg.Parsers = append(wxapkg.Parsers, &unpack.XmlParser{OutputDir: OutputDir, Version: "v1"}) + } else if isParserV2(wxapkg) { + wxapkg.Parsers = append(wxapkg.Parsers, &unpack.XmlParser{OutputDir: OutputDir, Version: "v2"}) + } + + // 清除无用文件 + cleanApp(wxapkg.SourcePath) } -func cleanApp() { +func cleanApp(path string) { + // 创建文件删除管理器 + manager := config.NewFileDeletionManager() + + // 删除相关的JS文件, unlinks + unlinks := []string{ + //".appservice.js", + "appservice.js", + "app-config.json", + "app-service.js", + "app-wxss.js", + "appservice.app.js", + "common.app.js", + "page-frame.js", + "page-frame.html", + "pageframe.js", + "webview.app.js", + "subContext.js", + "plugin.js", + } + for _, unlink := range unlinks { + manager.AddFile(filepath.Join(path, unlink)) + } } diff --git a/internal/restore/restore.go b/internal/restore/restore.go index 67b06a8..3c1441f 100644 --- a/internal/restore/restore.go +++ b/internal/restore/restore.go @@ -39,9 +39,20 @@ func ProjectStructure(outputDir string, restoreDir bool) { return } + configManager := config.NewSharedConfigManager() + // 创建文件删除管理器 manager := config.NewFileDeletionManager() + defer func() { + if noClean, ok := configManager.Get("noClean"); ok { + if !noClean.(bool) { + // 执行删除文件操作 + manager.DeleteFiles() + } + } + }() + // 包管理器 wxakpgManager := config.GetWxapkgManager() @@ -60,7 +71,4 @@ func ProjectStructure(outputDir string, restoreDir bool) { // 创建命令执行器, 执行解析器 executor := NewCommandExecutor(wxakpgManager) executor.ExecuteAll() - - // 执行删除文件操作 - manager.DeleteFiles() } diff --git a/internal/unpack/fileMagicNumbers.go b/internal/unpack/fileMagicNumbers.go deleted file mode 100644 index 5b77ccc..0000000 --- a/internal/unpack/fileMagicNumbers.go +++ /dev/null @@ -1,94 +0,0 @@ -package unpack - -// FileMagicNumbers 文件类型及其对应的魔数(文件头标识) -var FileMagicNumbers = map[string][]byte{ - // 图像文件 - ".png": {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}, - ".jpg": {0xFF, 0xD8, 0xFF}, - ".jpeg": {0xFF, 0xD8, 0xFF}, - ".gif": {'G', 'I', 'F', '8'}, - ".bmp": {'B', 'M'}, - ".webp": {'R', 'I', 'F', 'F'}, - ".tiff": {0x49, 0x49, 0x2A, 0x00}, - ".cr2": {0x49, 0x49, 0x2A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0x52}, - ".ico": {0x00, 0x00, 0x01, 0x00}, - ".heic": {'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'}, - - // 文档文件 - ".pdf": {'%', 'P', 'D', 'F', '-'}, - ".doc": {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1}, - ".docx": {'P', 'K', 0x03, 0x04}, - ".xls": {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1}, - ".xlsx": {'P', 'K', 0x03, 0x04}, - ".ppt": {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1}, - ".pptx": {'P', 'K', 0x03, 0x04}, - ".odt": {'P', 'K', 0x03, 0x04}, - ".ods": {'P', 'K', 0x03, 0x04}, - ".odp": {'P', 'K', 0x03, 0x04}, - ".rtf": {'{', '\\', 'r', 't', 'f', '1'}, - ".epub": {'P', 'K', 0x03, 0x04}, - ".mobi": {'M', 'O', 'B', 'I'}, - - // 压缩文件 - ".zip": {'P', 'K', 0x03, 0x04}, - ".rar": {'R', 'a', 'r', '!', 0x1A, 0x07, 0x00}, - ".7z": {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}, - ".gz": {0x1F, 0x8B, 0x08}, - ".tar": {'u', 's', 't', 'a', 'r'}, - ".bz2": {'B', 'Z', 'h'}, - ".xz": {0xFD, '7', 'z', 'X', 'Z', 0x00}, - - // 音频文件 - ".mp3": {0xFF, 0xFB}, - ".wav": {'R', 'I', 'F', 'F'}, - ".ogg": {'O', 'g', 'g', 'S'}, - ".flac": {'f', 'L', 'a', 'C'}, - ".aac": {0xFF, 0xF1}, - ".m4a": {'f', 't', 'y', 'p', 'M', '4', 'A', ' '}, - ".mid": {'M', 'T', 'h', 'd'}, - ".aiff": {'F', 'O', 'R', 'M'}, - - // 视频文件 - ".mp4": {0x00, 0x00, 0x00, 0x18, 'f', 't', 'y', 'p'}, - ".avi": {'R', 'I', 'F', 'F'}, - ".mkv": {0x1A, 0x45, 0xDF, 0xA3}, - ".flv": {'F', 'L', 'V'}, - ".mov": {'f', 't', 'y', 'p', 'q', 't', ' ', ' '}, - ".webm": {0x1A, 0x45, 0xDF, 0xA3}, - ".mpg": {0x00, 0x00, 0x01, 0xBA}, - ".wmv": {0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11}, - - // 可执行文件和库文件 - ".exe": {'M', 'Z'}, - ".elf": {0x7F, 'E', 'L', 'F'}, - ".so": {0x7F, 'E', 'L', 'F'}, - ".dll": {'M', 'Z'}, - ".app": {0xCA, 0xFE, 0xBA, 0xBE}, // Mach-O 文件格式 (macOS) - - // 脚本和标记语言文件 - ".json": {'{'}, // 假设 JSON 文件以 { 开头 - ".xml": {'<', '?', 'x', 'm', 'l'}, - ".html": {'<', '!', 'D', 'O', 'C', 'T', 'Y', 'P', 'E'}, - - // 字体文件 - ".ttf": {0x00, 0x01, 0x00, 0x00, 0x00}, - ".otf": {'O', 'T', 'T', 'O'}, - ".woff": {'w', 'O', 'F', 'F'}, - ".woff2": {'w', 'O', 'F', '2'}, - - // 数据库文件 - ".sqlite": {'S', 'Q', 'L', 'i', 't', 'e', ' ', 'f', 'o', 'r', 'm', 'a', 't', ' ', '3', 0x00}, - ".db": {0x00, 0x06, 0x15, 0x61, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00}, - - // 其他 - ".swf": {'F', 'W', 'S'}, - ".class": {0xCA, 0xFE, 0xBA, 0xBE}, - ".psd": {'8', 'B', 'P', 'S'}, - ".torrent": {'d', '8', ':', 'a', 'n', 'n', 'o', 'u', 'n', 'c', 'e'}, - ".blend": {'B', 'L', 'E', 'N', 'D', 'E', 'R'}, - ".pcap": {0xD4, 0xC3, 0xB2, 0xA1}, - ".dwg": {0x41, 0x43, 0x31, 0x30}, - ".iso": {0x43, 0x44, 0x30, 0x30, 0x31}, - ".vsd": {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1}, - ".mdb": {0x00, 0x01, 0x00, 0x00, 0x53, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x72, 0x64, 0x20, 0x4A, 0x65, 0x74}, -} diff --git a/internal/unpack/uxml.go b/internal/unpack/uxml.go index 40d1a4a..c1116db 100644 --- a/internal/unpack/uxml.go +++ b/internal/unpack/uxml.go @@ -1 +1,308 @@ package unpack + +import ( + "errors" + "fmt" + "log" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" + "sync" + + "github.com/Ackites/KillWxapkg/internal/config" + "github.com/dop251/goja" +) + +type XmlParser struct { + OutputDir string + // 解析器版本 + Version string +} + +// 获取生成函数 +func getFuc(code string, gwx map[string]interface{}) { + re := regexp.MustCompile(`else __wxAppCode__\['([^']+\.wxml)'\]\s*=\s*([^;]+;)`) + + // 匹配生成函数 + matches := re.FindAllStringSubmatch(code, -1) + if len(matches) > 0 { + for _, match := range matches { + if !strings.HasPrefix(match[2], "$") { + continue + } + gwx[match[1]] = match[2] + } + } +} + +// 提取函数名和参数 +func extractFuncNameAndArgs(gencode string) (string, []interface{}) { + re := regexp.MustCompile(`(\$\w+)\s*\(\s*'(.*?)'\s*\)`) + matches := re.FindStringSubmatch(gencode) + if len(matches) < 3 { + return "", nil + } + + funcName := matches[1] + arg := matches[2] + + return funcName, []interface{}{arg} +} + +// 递归调用函数直到获得非函数结果 +func getFinalResult(vm *goja.Runtime, value goja.Value) (goja.Value, error) { + for value.ExportType().Kind() == reflect.Func { + fn, ok := goja.AssertFunction(value) + if !ok { + return nil, fmt.Errorf("expected function, got %T", value.Export()) + } + + var err error + value, err = fn(goja.Undefined()) + if err != nil { + return nil, err + } + } + return value, nil +} + +// 生成视图代码 +func getDomTree(node interface{}) string { + // 用于构建 XML 字符串的函数 + var processNodes func(node map[string]interface{}, indentLevel int, isRoot bool) string + processNodes = func(node map[string]interface{}, indentLevel int, isRoot bool) string { + var sb strings.Builder + + // 生成缩进 + indent := strings.Repeat("\t", indentLevel) + + // 获取标签名称 + tag, ok := node["tag"].(string) + if !ok { + return "" + } + tag = strings.TrimPrefix(tag, "wx-") // 去除前缀 wx- + + // 如果是根节点,不添加开始标签 + if !isRoot { + // 开始标签 + sb.WriteString(indent) + sb.WriteString("<") + sb.WriteString(tag) + + // 处理属性 + if attr, ok := node["attr"].(map[string]interface{}); ok { + for key, value := range attr { + sb.WriteString(fmt.Sprintf(" %s=\"%v\"", key, value)) + } + } + + // 结束标签 + sb.WriteString(">") + } + + // 处理子节点 + if children, ok := node["children"].([]interface{}); ok { + if len(children) > 0 && !isRoot { + sb.WriteString("\n") + } + for _, child := range children { + if childMap, ok := child.(map[string]interface{}); ok { + sb.WriteString(processNodes(childMap, indentLevel+1, false)) + } else { + // 如果 children 是字符串且字符串为空,则不换行 + if str, ok := child.(string); ok { + if str != "" { + sb.WriteString(strings.Repeat("\t", indentLevel+1)) + sb.WriteString(str + "\n") + } + } + } + } + } + + // 结束标签(如果不是根节点) + if !isRoot { + sb.WriteString(indent) + sb.WriteString("\n") + } + + return sb.String() + } + + // 将根节点转换为 map + rootNode, ok := node.(map[string]interface{}) + if !ok { + return "" + } + + // 生成并返回最终的 XML 字符串,不包括根标签 + return processNodes(rootNode, 0, true) +} + +func getXml(path string, scriptCode, gencode string, results chan<- map[string]interface{}, wg *sync.WaitGroup, version string, sem chan struct{}) { + defer wg.Done() + + // 限制并发数 + sem <- struct{}{} + // 释放信号量 + defer func() { <-sem }() + + // 提取函数名和参数 + funcName, params := extractFuncNameAndArgs(gencode) + if funcName == "" { + log.Printf("Error extracting function name and arguments from gencode: %s\n", gencode) + return + } + + vm := goja.New() + + // 包裹 try...catch 语句以捕获 JavaScript 错误 + safeScript := ` + try { + ` + scriptCode + ` + } catch (e) { + //console.error(e); + } + ` + + // 定义 console 对象 + console := vm.NewObject() + _ = console.Set("log", func(call goja.FunctionCall) goja.Value { + // 使用 call.Arguments 获取传递给 console.log 的参数 + args := call.Arguments + for _, arg := range args { + fmt.Println(arg.String()) + } + return goja.Undefined() + }) + _ = console.Set("error", func(call goja.FunctionCall) goja.Value { + args := call.Arguments + for _, arg := range args { + fmt.Println("ERROR:", arg.String()) + } + return goja.Undefined() + }) + _ = console.Set("warn", func(call goja.FunctionCall) goja.Value { + args := call.Arguments + for _, arg := range args { + fmt.Println("WARN:", arg.String()) + } + return goja.Undefined() + }) + _ = vm.Set("console", console) + + // 运行脚本代码,定义所有函数 + _, err := vm.RunString(safeScript) + if err != nil { + var gojaErr *goja.Exception + if errors.As(err, &gojaErr) { + log.Println("JavaScript error:", gojaErr.String()) + } else { + log.Println("Error running script:", err) + } + return + } + + // 获取函数对象 + fn, ok := goja.AssertFunction(vm.Get(funcName)) + if !ok { + log.Printf("Error asserting function for %s\n", funcName) + return + } + + // 准备参数列表 + args := make([]goja.Value, len(params)) + for i, param := range params { + args[i] = vm.ToValue(param) + } + + // 调用函数并获取结果 + result, err := fn(goja.Undefined(), args...) + if err != nil { + log.Printf("Error calling function: %v\n", err) + return + } + + // 递归调用函数直到获得非函数结果 + finalResult, err := getFinalResult(vm, result) + if err != nil { + log.Printf("Error getting final result: %v\n", err) + return + } + + // 保存结果 + results <- map[string]interface{}{path: finalResult.Export()} +} + +func (p *XmlParser) Parse(option config.WxapkgInfo) error { + saveDir := p.OutputDir + + var frameFile = option.Option.ViewSource + // 存放生成函数代码 + var gwx = make(map[string]interface{}) + results := make(chan map[string]interface{}) + var wg sync.WaitGroup + + // 最大并发数 + const maxConcurrent = 5 + sem := make(chan struct{}, maxConcurrent) + + code, err := os.ReadFile(frameFile) + if err != nil { + log.Printf("Error reading file: %v\n", err) + return err + } + + codeStr := string(code) + scriptCode := codeStr + + scriptCode = strings.Replace(scriptCode, "var setCssToHead =", "var setCssToHead2 =", 1) + scriptCode = strings.Replace(scriptCode, "var noCss", "var noCss2", -1) + + // 防止报错 + patch := `var noCss=true;var window={};var navigator={};navigator.userAgent="iPhone";window.screen={};document={};function define(){};function require(){};` + + // 如果是 html 文件,提取 script 代码 + if strings.HasSuffix(frameFile, ".html") { + scriptCode = matchScripts(codeStr) + } + + // 正则匹配生成函数 + getFuc(scriptCode, gwx) + + scriptCode = patch + scriptCode + + // 运行生成函数 + for path, gencode := range gwx { + wg.Add(1) + go getXml(path, scriptCode, gencode.(string), results, &wg, p.Version, sem) + } + + go func() { + wg.Wait() + close(results) + }() + + finalResults := make(map[string]string) + for result := range results { + for k, v := range result { + finalResults[k] = getDomTree(v) + } + } + + for name, content := range finalResults { + name = filepath.Join(saveDir, name) + err = save(name, []byte(content)) + if err != nil { + log.Printf("Error saving file: %v\n", err) + } + log.Printf("Saved file: %s\n", name) + } + + return nil +} diff --git a/main.go b/main.go index 04afb5a..f9bf65c 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ var ( fileExt string restoreDir bool pretty bool + noClean bool ) func init() { @@ -23,6 +24,7 @@ func init() { flag.StringVar(&fileExt, "ext", ".wxapkg", "处理的文件后缀") flag.BoolVar(&restoreDir, "restore", false, "是否还原工程目录结构") flag.BoolVar(&pretty, "pretty", false, "是否美化输出") + flag.BoolVar(&noClean, "noClean", false, "是否清理中间文件") } func main() { @@ -30,7 +32,7 @@ func main() { flag.Parse() if appID == "" || input == "" { - fmt.Println("使用方法: program -id= -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录> [-ext=<文件后缀>] [-restore] [-pretty]") + fmt.Println("使用方法: program -id= -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean]") flag.PrintDefaults() return } @@ -43,10 +45,10 @@ func main() { | |\ \| | | | \ / / /_/ / (_| \__ \ <| | | | \_| \_/_|_|_| \/ \__,_|\__,_|___/_|\_\_| |_| - Wxapkg Decompiler Tool v1.0.0 + Wxapkg Decompiler Tool v2.0.0 ` fmt.Println(banner) // 执行命令 - cmd.Execute(appID, input, outputDir, fileExt, restoreDir, pretty) + cmd.Execute(appID, input, outputDir, fileExt, restoreDir, pretty, noClean) }