Пример #1
0
        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
                }
            }
        }
Пример #2
0
        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.");
        }