/// <summary> /// Pack Archive PSB /// </summary> /// <param name="jsonPath">json path</param> /// <param name="key">crypt key</param> /// <param name="intersect">Only pack files which existed in info.psb.m</param> /// <param name="preferPacked">Prefer using PSB files rather than json files in source folder</param> /// <param name="enableParallel">parallel process</param> /// <param name="keyLen">key length</param> public static void PackArchive(string jsonPath, string key, bool intersect, bool preferPacked, bool enableParallel = true, int keyLen = 131) { if (!File.Exists(jsonPath)) { return; } PSB infoPsb = PsbCompiler.LoadPsbFromJsonFile(jsonPath); if (infoPsb.Type != PsbType.ArchiveInfo) { Console.WriteLine("Json is not an ArchiveInfo PSB."); return; } var resx = PsbResourceJson.LoadByPsbJsonPath(jsonPath); if (!resx.Context.ContainsKey(Context_ArchiveSource) || resx.Context[Context_ArchiveSource] == null) { Console.WriteLine("ArchiveSource must be specified in resx.json Context."); return; } if (keyLen > 0) { resx.Context[Context_MdfKeyLength] = keyLen; } string infoKey = null; if (resx.Context[Context_MdfKey] is string mdfKey) { infoKey = mdfKey; } List <string> sourceDirs = null; if (resx.Context[Context_ArchiveSource] is string path) { sourceDirs = new List <string> { path }; } else if (resx.Context[Context_ArchiveSource] is IList paths) { sourceDirs = new List <string>(paths.Count); sourceDirs.AddRange(from object p in paths select p.ToString()); } else { Console.WriteLine("ArchiveSource incorrect."); return; } var baseDir = Path.GetDirectoryName(jsonPath); var files = new Dictionary <string, (string Path, ProcessMethod Method)>(); var suffix = ArchiveInfoPsbGetSuffix(infoPsb); List <string> filter = null; if (intersect) { filter = ArchiveInfoPsbCollectFiles(infoPsb, suffix); } void CollectFiles(string targetDir) { if (!Directory.Exists(targetDir)) { return; } foreach (var f in Directory.EnumerateFiles(targetDir)) { if (f.EndsWith(".resx.json", true, CultureInfo.InvariantCulture)) { continue; } else if (f.EndsWith(".json", true, CultureInfo.InvariantCulture)) { var name = Path.GetFileNameWithoutExtension(f); if (preferPacked && files.ContainsKey(name) && files[name].Method != ProcessMethod.Compile) { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) { //ignore } else { files[name] = (f, ProcessMethod.Compile); } } } else { var name = Path.GetFileName(f); if (!preferPacked && files.ContainsKey(name) && files[name].Method == ProcessMethod.Compile) { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) { //ignore } else { using var fs = File.OpenRead(f); if (!MdfFile.IsSignatureMdf(fs) && name.DefaultShellType() == "MDF") { files[name] = (f, ProcessMethod.EncodeMdf); } else { files[name] = (f, ProcessMethod.None); } } } } } } //Collect files foreach (var sourceDir in sourceDirs) { CollectFiles(Path.IsPathRooted(sourceDir) ? sourceDir : Path.Combine(baseDir, sourceDir)); } var fileName = Path.GetFileName(jsonPath); var packageName = Path.GetFileNameWithoutExtension(fileName); var coreName = PsbExtension.ArchiveInfoGetPackageName(packageName); fileName = string.IsNullOrEmpty(coreName) ? packageName + "_body.bin" : coreName + "_body.bin"; var fileInfoDic = new PsbDictionary(files.Count); var fmContext = FreeMount.CreateContext(resx.Context); byte[] bodyBin = null; if (enableParallel) { var contents = new ConcurrentBag <(string Name, Stream Content)>(); Parallel.ForEach(files, (kv) => { var fileNameWithoutSuffix = ArchiveInfoPsbGetFileName(kv.Key, suffix); if (kv.Value.Method == ProcessMethod.None) { contents.Add((fileNameWithoutSuffix, File.OpenRead(kv.Value.Path))); return; } var mdfContext = new Dictionary <string, object>(resx.Context); var context = FreeMount.CreateContext(mdfContext); if (!string.IsNullOrEmpty(key)) { mdfContext[Context_MdfKey] = key + fileNameWithoutSuffix + suffix; } else if (resx.Context[Context_MdfMtKey] is string mtKey) { mdfContext[Context_MdfKey] = mtKey + fileNameWithoutSuffix + suffix; } else { mdfContext.Remove(Context_MdfKey); } mdfContext.Remove(Context_ArchiveSource); if (kv.Value.Method == ProcessMethod.EncodeMdf) { contents.Add((fileNameWithoutSuffix, context.PackToShell( File.OpenRead(kv.Value.Path), "MDF"))); } else { var content = PsbCompiler.LoadPsbAndContextFromJsonFile(kv.Value.Path); var outputMdf = context.PackToShell(content.Psb.ToStream(), "MDF"); contents.Add((fileNameWithoutSuffix, outputMdf)); } });
static void ExtractArchive(string filePath, string key, Dictionary <string, object> context, bool outputRaw = true, bool extractAll = false, bool enableParallel = true) { if (File.Exists(filePath)) { var fileName = Path.GetFileName(filePath); context[Context_MdfKey] = key + fileName; var dir = Path.GetDirectoryName(Path.GetFullPath(filePath)); var name = ArchiveInfoGetPackageName(fileName); if (name == null) { Console.WriteLine($"File name incorrect: {fileName}"); name = fileName; } var body = Path.Combine(dir ?? "", name + "_body.bin"); bool hasBody = true; if (!File.Exists(body)) { Console.WriteLine($"Can not find body: {body}"); hasBody = false; } try { var baseShellType = Path.GetExtension(fileName).DefaultShellType(); PSB psb = null; using (var fs = File.OpenRead(filePath)) { psb = new PSB(MdfConvert(fs, baseShellType, context)); } File.WriteAllText(Path.GetFullPath(filePath) + ".json", PsbDecompiler.Decompile(psb)); PsbResourceJson resx = new PsbResourceJson(psb, context); var dic = psb.Objects["file_info"] as PsbDictionary; var suffixList = ((PsbList)psb.Objects["expire_suffix_list"]); var suffix = ""; if (suffixList.Count > 0) { suffix = suffixList[0] as PsbString ?? ""; } var shellType = suffix.DefaultShellType(); if (!hasBody) { //Write resx.json resx.Context[Context_ArchiveSource] = new List <string> { name }; File.WriteAllText(Path.GetFullPath(filePath) + ".resx.json", resx.SerializeToJson()); return; } Console.WriteLine($"Extracting info from {fileName} ..."); var extractDir = Path.Combine(dir, name); if (File.Exists(extractDir)) //conflict with File, not Directory { name += "-resources"; extractDir += "-resources"; } if (!Directory.Exists(extractDir)) { Directory.CreateDirectory(extractDir); } if (enableParallel) //parallel! { int count = 0; using var mmFile = MemoryMappedFile.CreateFromFile(body, FileMode.Open, name, 0, MemoryMappedFileAccess.Read); Parallel.ForEach(dic, pair => { count++; //Console.WriteLine($"{(extractAll ? "Decompiling" : "Extracting")} {pair.Key} ..."); var range = ((PsbList)pair.Value); var start = ((PsbNumber)range[0]).UIntValue; var len = ((PsbNumber)range[1]).IntValue; using var mmAccessor = mmFile.CreateViewAccessor(start, len, MemoryMappedFileAccess.Read); var bodyBytes = new byte[len]; mmAccessor.ReadArray(0, bodyBytes, 0, len); var fileNameWithSuffix = ArchiveInfoGetFileNameAppendSuffix(pair.Key, suffix); if (outputRaw) { File.WriteAllBytes(Path.Combine(extractDir, fileNameWithSuffix), bodyBytes); return; } using var ms = MsManager.GetStream(bodyBytes); var bodyContext = new Dictionary <string, object>(context) { [Context_MdfKey] = key + fileNameWithSuffix }; bodyContext.Remove(Context_ArchiveSource); using var mms = MdfConvert(ms, shellType, bodyContext); if (extractAll) { try { PSB bodyPsb = new PSB(mms); PsbDecompiler.DecompileToFile(bodyPsb, Path.Combine(extractDir, fileNameWithSuffix + ".json"), //important, must keep suffix for rebuild bodyContext, PsbExtractOption.Extract); } catch (Exception e) { Console.WriteLine($"Decompile failed: {pair.Key}"); WriteAllBytes(Path.Combine(extractDir, fileNameWithSuffix), mms); //File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } else { WriteAllBytes(Path.Combine(extractDir, fileNameWithSuffix), mms); //File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } }); Console.WriteLine($"{count} files {(extractAll ? "decompiled" : "extracted")}."); } else { //no parallel //var maxLen = dic?.Values.Max(item => item.Children(1).GetInt()) ?? 0; using var mmFile = MemoryMappedFile.CreateFromFile(body, FileMode.Open, name, 0, MemoryMappedFileAccess.Read); foreach (var pair in dic) { Console.WriteLine( $"{(extractAll ? "Decompiling" : "Extracting")} {pair.Key} ..."); var range = ((PsbList)pair.Value); var start = ((PsbNumber)range[0]).IntValue; var len = ((PsbNumber)range[1]).IntValue; using var mmAccessor = mmFile.CreateViewAccessor(start, len, MemoryMappedFileAccess.Read); var bodyBytes = new byte[len]; mmAccessor.ReadArray(0, bodyBytes, 0, len); var fileNameWithSuffix = ArchiveInfoGetFileNameAppendSuffix(pair.Key, suffix); if (outputRaw) { File.WriteAllBytes(Path.Combine(extractDir, fileNameWithSuffix), bodyBytes.AsSpan().Slice(start, len).ToArray()); continue; } using (var ms = MsManager.GetStream(bodyBytes)) { context[Context_MdfKey] = key + fileNameWithSuffix; var mms = MdfConvert(ms, shellType, context); if (extractAll) { try { PSB bodyPsb = new PSB(mms); PsbDecompiler.DecompileToFile(bodyPsb, Path.Combine(extractDir, fileNameWithSuffix + ".json"), context, PsbExtractOption.Extract); } catch (Exception e) { Console.WriteLine($"Decompile failed: {pair.Key}"); WriteAllBytes(Path.Combine(extractDir, fileNameWithSuffix), mms); //File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } else { WriteAllBytes(Path.Combine(extractDir, fileNameWithSuffix), mms); //File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } } } //Write resx.json resx.Context[Context_ArchiveSource] = new List <string> { name }; resx.Context[Context_MdfMtKey] = key; File.WriteAllText(Path.GetFullPath(filePath) + ".resx.json", resx.SerializeToJson()); } catch (Exception e) { Console.WriteLine(e); #if DEBUG throw e; #endif } } }
/// <summary> /// Pack Archive PSB /// </summary> /// <param name="jsonPath">json path</param> /// <param name="key">crypt key</param> /// <param name="intersect">Only pack files which existed in info.psb.m</param> /// <param name="preferPacked">Prefer using PSB files rather than json files in source folder</param> /// <param name="enableParallel">parallel process</param> /// <param name="keyLen">key length</param> /// <param name="keepRaw">Do not try to compile json or pack MDF</param> public static void PackArchive(string jsonPath, string key, bool intersect, bool preferPacked, bool enableParallel = true, int keyLen = 131, bool keepRaw = false) { if (!File.Exists(jsonPath)) { return; } PSB infoPsb = PsbCompiler.LoadPsbFromJsonFile(jsonPath); if (infoPsb.Type != PsbType.ArchiveInfo) { Console.WriteLine("Json is not an ArchiveInfo PSB."); return; } var resx = PsbResourceJson.LoadByPsbJsonPath(jsonPath); if (!resx.Context.ContainsKey(Context_ArchiveSource) || resx.Context[Context_ArchiveSource] == null) { Console.WriteLine("ArchiveSource must be specified in resx.json Context."); return; } if (keyLen > 0) { resx.Context[Context_MdfKeyLength] = keyLen; } string infoKey = null; if (resx.Context[Context_MdfKey] is string mdfKey) { infoKey = mdfKey; } List <string> sourceDirs = null; if (resx.Context[Context_ArchiveSource] is string path) { sourceDirs = new List <string> { path }; } else if (resx.Context[Context_ArchiveSource] is IList paths) { sourceDirs = new List <string>(paths.Count); sourceDirs.AddRange(from object p in paths select p.ToString()); } else { Console.WriteLine("ArchiveSource incorrect."); return; } var baseDir = Path.GetDirectoryName(jsonPath); var files = new Dictionary <string, (string Path, ProcessMethod Method)>(); var suffix = ArchiveInfoGetSuffix(infoPsb); List <string> filter = null; if (intersect) //only collect files appeared in json { filter = ArchiveInfoCollectFiles(infoPsb, suffix).ToList(); } void CollectFiles(string targetDir) { if (!Directory.Exists(targetDir)) { return; } foreach (var f in Directory.EnumerateFiles(targetDir)) { if (f.EndsWith(".resx.json", true, CultureInfo.InvariantCulture)) { continue; } else if (f.EndsWith(".json", true, CultureInfo.InvariantCulture)) //json source, need compile { var name = Path.GetFileNameWithoutExtension(f); if (preferPacked && files.ContainsKey(name) && files[name].Method != ProcessMethod.Compile) //it's always right no matter set or replace { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) //this file is not appeared in json { //ignore } else { files[name] = (f, keepRaw? ProcessMethod.None: ProcessMethod.Compile); } } } else { var name = Path.GetFileName(f); if (!preferPacked && files.ContainsKey(name) && files[name].Method == ProcessMethod.Compile) { //ignore } else { if (intersect && filter != null && !filter.Contains(name)) { //ignore } else { using var fs = File.OpenRead(f); if (!MdfFile.IsSignatureMdf(fs) && name.DefaultShellType() == "MDF") { files[name] = (f, keepRaw? ProcessMethod.None: ProcessMethod.EncodeMdf); } else { files[name] = (f, ProcessMethod.None); } } } } } } //Collect files Console.WriteLine("Collecting files ..."); foreach (var sourceDir in sourceDirs) { CollectFiles(Path.IsPathRooted(sourceDir) ? sourceDir : Path.Combine(baseDir, sourceDir)); } Console.WriteLine($"Packing {files.Count} files ..."); var bodyBinFileName = Path.GetFileName(jsonPath); var packageName = Path.GetFileNameWithoutExtension(bodyBinFileName); var coreName = ArchiveInfoGetPackageName(packageName); bodyBinFileName = string.IsNullOrEmpty(coreName) ? packageName + "_body.bin" : coreName + "_body.bin"; //using var mmFile = // MemoryMappedFile.CreateFromFile(bodyBinFileName, FileMode.Create, coreName, ); using var bodyFs = File.OpenWrite(bodyBinFileName); var fileInfoDic = new PsbDictionary(files.Count); var fmContext = FreeMount.CreateContext(resx.Context); //byte[] bodyBin = null; if (enableParallel) { var contents = new ConcurrentBag <(string Name, Stream Content)>(); Parallel.ForEach(files, (kv) => { var fileNameWithoutSuffix = ArchiveInfoGetFileNameRemoveSuffix(kv.Key, suffix); if (kv.Value.Method == ProcessMethod.None) { contents.Add((fileNameWithoutSuffix, File.OpenRead(kv.Value.Path))); return; } var mdfContext = new Dictionary <string, object>(resx.Context); var context = FreeMount.CreateContext(mdfContext); if (!string.IsNullOrEmpty(key)) { mdfContext[Context_MdfKey] = key + kv.Key; } else if (resx.Context[Context_MdfMtKey] is string mtKey) { mdfContext[Context_MdfKey] = mtKey + kv.Key; } else { mdfContext.Remove(Context_MdfKey); } mdfContext.Remove(Context_ArchiveSource); if (kv.Value.Method == ProcessMethod.EncodeMdf) { using var mmFs = MemoryMappedFile.CreateFromFile(kv.Value.Path, FileMode.Open); //using var fs = File.OpenRead(kv.Value.Path); contents.Add((fileNameWithoutSuffix, context.PackToShell(mmFs.CreateViewStream(), "MDF"))); //disposed later } else { var content = PsbCompiler.LoadPsbAndContextFromJsonFile(kv.Value.Path); var stream = content.Psb.ToStream(); var shellType = kv.Key.DefaultShellType(); //MARK: use shellType in filename, or use suffix in info? if (!string.IsNullOrEmpty(shellType)) { stream = context.PackToShell(stream, shellType); //disposed later } contents.Add((fileNameWithoutSuffix, stream)); } });
static void Main(string[] args) { Console.WriteLine("FreeMote PSB Decompiler"); Console.WriteLine("by Ulysses, [email protected]"); FreeMount.Init(); Console.WriteLine($"{FreeMount.PluginsCount} Plugins Loaded."); PsbConstants.InMemoryLoading = true; Console.WriteLine(); var app = new CommandLineApplication(); app.OptionsComparison = StringComparison.OrdinalIgnoreCase; //help app.HelpOption(); //do not inherit app.ExtendedHelpText = PrintHelp(); //options var optKey = app.Option <uint>("-k|--key", "Set PSB key (uint, dec)", CommandOptionType.SingleValue); var optFormat = app.Option <PsbImageFormat>("-e|--extract <FORMAT>", "Convert textures to Png/Bmp. Default=Png", CommandOptionType.SingleValue, true); var optRaw = app.Option("-raw|--raw", "Keep raw textures", CommandOptionType.NoValue, inherited: true); //メモリ足りない もうどうしよう : https://soundcloud.com/ulysses-wu/Heart-Chrome var optOom = app.Option("-oom|--memory-limit", "Disable In-Memory Loading", CommandOptionType.NoValue, inherited: true); var optHex = app.Option("-hex|--json-hex", "(Json) Use hex numbers", CommandOptionType.NoValue, true); var optArray = app.Option("-indent|--json-array-indent", "(Json) Indent arrays", CommandOptionType.NoValue, true); //args var argPath = app.Argument("Files", "File paths", multipleValues: true); //command: unlink app.Command("unlink", linkCmd => { //help linkCmd.Description = "Unlink textures from PSBs"; linkCmd.HelpOption(); linkCmd.ExtendedHelpText = @" Example: PsbDecompile unlink sample.psb "; //options var optOrder = linkCmd.Option <PsbLinkOrderBy>("-o|--order <ORDER>", "Set texture unlink order (ByName/ByOrder/Convention). Default=ByName", CommandOptionType.SingleValue); //args var argPsbPath = linkCmd.Argument("PSB", "PSB Path").IsRequired(); //var argTexPath = linkCmd.Argument("Textures", "Texture Paths").IsRequired(); linkCmd.OnExecute(() => { PsbImageFormat format = optFormat.HasValue() ? optFormat.ParsedValue : PsbImageFormat.Png; var order = optOrder.HasValue() ? optOrder.ParsedValue : PsbLinkOrderBy.Name; var psbPaths = argPsbPath.Values; foreach (var psbPath in psbPaths) { if (File.Exists(psbPath)) { try { PsbDecompiler.UnlinkToFile(psbPath, format: format, order: order); } catch (Exception e) { Console.WriteLine(e); } } } }); }); //info-psb app.Command("info-psb", archiveCmd => { //help archiveCmd.Description = "Extract files from info.psb.m & body.bin (FreeMote.Plugins required)"; archiveCmd.HelpOption(); archiveCmd.ExtendedHelpText = @" Example: PsbDecompile info-psb -k 1234567890ab -l 131 -a sample_info.psb.m PsbDecompile info-psb -s 1234567890absample_info.psb.m -l 131 sample_info.psb Hint: The body.bin should exist in the same folder and keep both file names correct. "; //options //var optMdfSeed = archiveCmd.Option("-s|--seed <SEED>", // "Set complete seed (Key+FileName)", // CommandOptionType.SingleValue); var optExtractAll = archiveCmd.Option("-a|--all", "Decompile all contents in body.bin if possible (can be slow)", CommandOptionType.NoValue); var optMdfKey = archiveCmd.Option("-k|--key <KEY>", "Set key (Infer file name from path)", CommandOptionType.SingleValue); var optMdfKeyLen = archiveCmd.Option <int>("-l|--length <LEN>", "Set key length. Default=131", CommandOptionType.SingleValue); var optInfoOom = archiveCmd.Option("-1by1|--enumerate", "Disable parallel processing when using `-a` (can be very slow)", CommandOptionType.NoValue); //args var argPsbPaths = archiveCmd.Argument("PSB", "Archive Info PSB Paths", true); archiveCmd.OnExecute(() => { bool extractAll = optExtractAll.HasValue(); bool enableParallel = PsbConstants.FastMode; if (optInfoOom.HasValue()) { enableParallel = false; } string key = optMdfKey.HasValue() ? optMdfKey.Value() : null; //string seed = optMdfSeed.HasValue() ? optMdfSeed.Value() : null; if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key), "No key or seed specified."); } int keyLen = optMdfKeyLen.HasValue() ? optMdfKeyLen.ParsedValue : 0x83; Dictionary <string, object> context = new Dictionary <string, object>(); if (keyLen >= 0) { context["MdfKeyLength"] = (uint)keyLen; } foreach (var s in argPsbPaths.Values) { if (File.Exists(s)) { var fileName = Path.GetFileName(s); context["MdfKey"] = key + fileName; try { var dir = Path.GetDirectoryName(Path.GetFullPath(s)); var name = fileName.Substring(0, fileName.IndexOf("_info.")); var body = Path.Combine(dir, name + "_body.bin"); if (!File.Exists(body)) { Console.WriteLine($"Can not find body: {body}"); continue; } PSB psb = null; using (var fs = File.OpenRead(s)) { psb = new PSB(MdfConvert(fs, context)); } File.WriteAllText(Path.GetFullPath(s) + ".json", PsbDecompiler.Decompile(psb)); PsbResourceJson resx = new PsbResourceJson(psb, context); File.WriteAllText(Path.GetFullPath(s) + ".resx.json", resx.SerializeToJson()); var dic = psb.Objects["file_info"] as PsbDictionary; var suffixList = ((PsbCollection)psb.Objects["expire_suffix_list"]); var suffix = ""; if (suffixList.Count > 0) { suffix = suffixList[0] as PsbString ?? ""; } Console.WriteLine($"Extracting info from {fileName} ..."); var bodyBytes = File.ReadAllBytes(body); var extractDir = Path.Combine(dir, name); if (!Directory.Exists(extractDir)) { Directory.CreateDirectory(extractDir); } #if DEBUG Stopwatch sw = Stopwatch.StartNew(); #endif if (enableParallel) //parallel! { int count = 0; Parallel.ForEach(dic, pair => { count++; //Console.WriteLine($"{(extractAll ? "Decompiling" : "Extracting")} {pair.Key} ..."); var range = ((PsbCollection)pair.Value); var start = ((PsbNumber)range[0]).IntValue; var len = ((PsbNumber)range[1]).IntValue; using (var ms = new MemoryStream(bodyBytes, start, len)) { var bodyContext = new Dictionary <string, object>(context) { ["MdfKey"] = key + pair.Key + suffix }; var mms = MdfConvert(ms, bodyContext); if (extractAll) { try { PSB bodyPsb = new PSB(mms); PsbDecompiler.DecompileToFile(bodyPsb, Path.Combine(extractDir, pair.Key + suffix + ".json"), bodyContext, PsbImageOption.Extract); } catch (Exception e) { Console.WriteLine($"Decompile failed: {pair.Key}"); File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } else { File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } }); Console.WriteLine($"{count} files {(extractAll ? "decompiled" : "extracted")}."); } else { //no parallel foreach (var pair in dic) { Console.WriteLine( $"{(extractAll ? "Decompiling" : "Extracting")} {pair.Key} ..."); var range = ((PsbCollection)pair.Value); var start = ((PsbNumber)range[0]).IntValue; var len = ((PsbNumber)range[1]).IntValue; using (var ms = new MemoryStream(bodyBytes, start, len)) { context["MdfKey"] = key + pair.Key + suffix; var mms = MdfConvert(ms, context); if (extractAll) { try { PSB bodyPsb = new PSB(mms); PsbDecompiler.DecompileToFile(bodyPsb, Path.Combine(extractDir, pair.Key + suffix + ".json"), context, PsbImageOption.Extract); } catch (Exception e) { Console.WriteLine($"Decompile failed: {pair.Key}"); File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } else { File.WriteAllBytes(Path.Combine(extractDir, pair.Key + suffix), mms.ToArray()); } } } } #if DEBUG sw.Stop(); Console.WriteLine($"Process time: {sw.Elapsed:g}"); #endif } catch (Exception e) { Console.WriteLine(e); } } } }); }); app.OnExecute(() => { if (optOom.HasValue()) { PsbConstants.InMemoryLoading = false; } if (optArray.HasValue()) { PsbConstants.JsonArrayCollapse = false; } if (optHex.HasValue()) { PsbConstants.JsonUseHexNumber = true; } bool useRaw = optRaw.HasValue(); PsbImageFormat format = optFormat.HasValue() ? optFormat.ParsedValue : PsbImageFormat.Png; uint?key = optKey.HasValue() ? optKey.ParsedValue : (uint?)null; foreach (var s in argPath.Values) { if (File.Exists(s)) { Decompile(s, useRaw, format, key); } else if (Directory.Exists(s)) { foreach (var file in Directory.EnumerateFiles(s, "*.psb") .Union(Directory.EnumerateFiles(s, "*.mmo")) .Union(Directory.EnumerateFiles(s, "*.pimg")) .Union(Directory.EnumerateFiles(s, "*.scn")) .Union(Directory.EnumerateFiles(s, "*.dpak")) .Union(Directory.EnumerateFiles(s, "*.psz")) .Union(Directory.EnumerateFiles(s, "*.psp")) ) { Decompile(s, useRaw, format, key); } } } }); if (args.Length == 0) { app.ShowHelp(); return; } app.Execute(args); Console.WriteLine("Done."); }