// Yet another PSB bad design: some types do not have a type id while others do. public static string DefaultTypeId(this PsbType type) { switch (type) { case PsbType.Tachie: return("image"); case PsbType.ArchiveInfo: return("archive"); case PsbType.BmpFont: return("font"); case PsbType.Motion: return("motion"); case PsbType.SoundArchive: return("sound_archive"); case PsbType.PSB: case PsbType.Mmo: case PsbType.Scn: case PsbType.Pimg: default: return(""); } }
/// <summary> /// Get <see cref="PsbType"/>'s default extension /// </summary> /// <param name="type"></param> /// <returns></returns> public static string DefaultExtension(this PsbType type) { switch (type) { case PsbType.Pimg: return("pimg"); case PsbType.Scn: return("scn"); case PsbType.Motion: default: return("psb"); } }
/// <summary> /// Get <see cref="PsbType"/>'s default extension /// </summary> /// <param name="type"></param> /// <returns></returns> public static string DefaultExtension(this PsbType type) { switch (type) { case PsbType.Mmo: return(".mmo"); case PsbType.Pimg: return(".pimg"); case PsbType.Scn: return(".scn"); case PsbType.ArchiveInfo: return(".psb.m"); case PsbType.Motion: default: return(".psb"); } }
/// <summary> /// Decompile Pure PSB as Json /// </summary> /// <param name="path"></param> /// <param name="psb"></param> /// <param name="context"></param> /// <param name="psbType"></param> /// <returns></returns> public static string Decompile(string path, out PSB psb, Dictionary <string, object> context = null, PsbType psbType = PsbType.PSB) { using var fs = File.OpenRead(path); var ctx = FreeMount.CreateContext(context); string type = null; Stream stream = fs; using var ms = ctx.OpenFromShell(fs, ref type); if (ms != null) { ctx.Context[Consts.Context_PsbShellType] = type; fs.Dispose(); stream = ms; } try { psb = new PSB(stream, false); } catch (PsbBadFormatException e) when(e.Reason == PsbBadFormatReason.Header || e.Reason == PsbBadFormatReason.Array || e.Reason == PsbBadFormatReason.Body) //maybe encrypted { stream.Position = 0; uint?key = null; if (ctx.Context.ContainsKey(Consts.Context_CryptKey)) { key = ctx.Context[Consts.Context_CryptKey] as uint?; } else { key = ctx.GetKey(stream); } stream.Position = 0; if (key != null) //try use key { try { using (var mms = new MemoryStream((int)stream.Length)) { PsbFile.Encode(key.Value, EncodeMode.Decrypt, EncodePosition.Auto, stream, mms); stream.Dispose(); psb = new PSB(mms); ctx.Context[Consts.Context_CryptKey] = key; } } catch { throw e; } } else //key = null { if (e.Reason == PsbBadFormatReason.Header || e.Reason == PsbBadFormatReason.Array) //now try Dullahan loading { psb = PSB.DullahanLoad(stream); } else { throw; } } } if (psbType != PsbType.PSB) { psb.Type = psbType; } return(Decompile(psb)); }
/// <summary> /// Decompile to files /// </summary> /// <param name="inputPath">PSB file path</param> /// <param name="extractOption">whether to extract image to common format</param> /// <param name="extractFormat">if extract, what format do you want</param> /// <param name="useResx">if false, use array-based resource json (legacy)</param> /// <param name="key">PSB CryptKey</param> /// <param name="type">Specify PSB type, if not set, infer type automatically</param> public static void DecompileToFile(string inputPath, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png, bool useResx = true, uint?key = null, PsbType type = PsbType.PSB) { var context = FreeMount.CreateContext(); if (key != null) { context.Context[Consts.Context_CryptKey] = key; } File.WriteAllText(ChangeExtensionForOutputJson(inputPath, ".json"), Decompile(inputPath, out var psb, context.Context)); OutputResources(psb, context, inputPath, extractOption, extractFormat, useResx); }
static void Main(string[] args) { Console.WriteLine("FreeMote PSB Decompiler"); Console.WriteLine("by Ulysses, [email protected]"); FreeMount.Init(); Console.WriteLine($"{FreeMount.PluginsCount} Plugins Loaded."); 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", "Output raw resources", 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 optNoParallel = app.Option("-1by1|--enumerate", "Disable parallel processing (can be very slow)", 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); var optDisableFlattenArray = app.Option("-dfa|--disable-flatten-array", "Disable represent extra resource as flatten arrays", CommandOptionType.NoValue, inherited: true); var optType = app.Option <PsbType>("-t|--type <TYPE>", "Set PSB type manually", CommandOptionType.SingleValue, inherited: true); //args var argPath = app.Argument("Files", "File paths", multipleValues: true); //command: image app.Command("image", imageCmd => { //help imageCmd.Description = "Extract (combined) textures from PSBs"; imageCmd.HelpOption(); imageCmd.ExtendedHelpText = @" Example: PsbDecompile image sample.psb PsbDecompile image sample-resource-folder "; //args var argPsbPath = imageCmd.Argument("Path", "PSB paths or PSB directory paths").IsRequired(); imageCmd.OnExecute(() => { var enableParallel = !optNoParallel.HasValue(); var psbPaths = argPsbPath.Values; foreach (var psbPath in psbPaths) { if (File.Exists(psbPath)) { try { PsbDecompiler.ExtractImageFiles(psbPath); } catch (Exception e) { Console.WriteLine(e); } } else if (Directory.Exists(psbPath)) { var files = FreeMoteExtension.GetFiles(psbPath, new[] { "*.psb", "*.pimg", "*.m", "*.bytes" }); if (enableParallel) { Parallel.ForEach(files, (s, state) => { try { PsbDecompiler.ExtractImageFiles(s); } catch (Exception e) { Console.WriteLine(e); } }); } else { foreach (var s in files) { try { PsbDecompiler.ExtractImageFiles(s); } catch (Exception e) { Console.WriteLine(e); } } } } } }); }); //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, 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 sample_info.psb.m PsbDecompile info-psb -k 1234567890ab -l 131 -a sample_info.psb.m 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); //args var argPsbPaths = archiveCmd.Argument("PSB", "Archive Info PSB Paths", true); archiveCmd.OnExecute(() => { if (optOom.HasValue()) { InMemoryLoading = false; } if (optArray.HasValue()) { JsonArrayCollapse = false; } if (optHex.HasValue()) { JsonUseHexNumber = true; } if (optDisableFlattenArray.HasValue()) { FlattenArrayByDefault = false; } bool extractAll = optExtractAll.HasValue(); var outputRaw = optRaw.HasValue(); bool enableParallel = FastMode; if (optNoParallel.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[Context_MdfKeyLength] = (uint)keyLen; } Stopwatch sw = Stopwatch.StartNew(); foreach (var s in argPsbPaths.Values) { ExtractArchive(s, key, context, outputRaw, extractAll, enableParallel); } sw.Stop(); Console.WriteLine($"Process time: {sw.Elapsed:g}"); }); }); app.OnExecute(() => { if (optOom.HasValue()) { InMemoryLoading = false; } if (optArray.HasValue()) { JsonArrayCollapse = false; } if (optHex.HasValue()) { JsonUseHexNumber = true; } if (optDisableFlattenArray.HasValue()) { FlattenArrayByDefault = false; } bool useRaw = optRaw.HasValue(); //PsbImageFormat format = optFormat.HasValue() ? optFormat.ParsedValue : PsbImageFormat.png; uint?key = optKey.HasValue() ? optKey.ParsedValue : (uint?)null; PsbType type = PsbType.PSB; if (optType.HasValue()) { type = optType.ParsedValue; } foreach (var s in argPath.Values) { if (File.Exists(s)) { Decompile(s, useRaw, PsbImageFormat.png, key, type); } else if (Directory.Exists(s)) { foreach (var file in FreeMoteExtension.GetFiles(s, new[] { "*.psb", "*.mmo", "*.pimg", "*.scn", "*.dpak", "*.psz", "*.psp", "*.bytes", "*.m" })) { Decompile(s, useRaw, PsbImageFormat.png, key, type); } } } }); if (args.Length == 0) { app.ShowHelp(); return; } app.Execute(args); Console.WriteLine("Done."); }