/// <summary> /// Decompile to files /// </summary> /// <param name="inputPath">PSB file path</param> /// <param name="imageOption">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> public static void DecompileToFile(string inputPath, PsbImageOption imageOption = PsbImageOption.Original, PsbImageFormat extractFormat = PsbImageFormat.Png, bool useResx = true, uint?key = null) { var context = FreeMount.CreateContext(); if (key != null) { context.Context[FreeMount.CryptKey] = key; } File.WriteAllText(Path.ChangeExtension(inputPath, ".json"), Decompile(inputPath, out var psb, context.Context)); //MARK: breaking change for json path OutputResources(psb, context, inputPath, imageOption, extractFormat, useResx); }
/// <summary> /// Split textures into parts and save to files /// </summary> /// <param name="psb">PSB</param> /// <param name="path">Save directory</param> /// <param name="option">Save option</param> /// <param name="imageFormat">Save format</param> /// <param name="pixelFormat">When save to PSB special formats, specific pixel format to use</param> public static void SplitTextureToFiles(this PSB psb, string path, PsbImageOption option = PsbImageOption.Extract, PsbImageFormat imageFormat = PsbImageFormat.Png, PsbPixelFormat pixelFormat = PsbPixelFormat.None) { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } var source = (PsbDictionary)psb.Objects["source"]; foreach (var texPair in source) { if (!(texPair.Value is PsbDictionary tex)) { continue; } var name = texPair.Key; if (!Directory.Exists(Path.Combine(path, name))) { Directory.CreateDirectory(Path.Combine(path, name)); } var icon = (PsbDictionary)tex["icon"]; var texture = (PsbDictionary)tex["texture"]; //var mipmap = (PsbDictionary)texture["mipMap"]; //TODO: Mipmap? var md = PsbResCollector.GenerateMotionResMetadata(texture, (PsbResource)texture["pixel"]); md.Spec = psb.Platform; //Important Bitmap bmp = md.ToImage(); foreach (var iconPair in icon) { var savePath = Path.Combine(path, name, iconPair.Key); var info = (PsbDictionary)iconPair.Value; var width = (int)(PsbNumber)info["width"]; var height = (int)(PsbNumber)info["height"]; var top = (int)(PsbNumber)info["top"]; var left = (int)(PsbNumber)info["left"]; var attr = (int)(PsbNumber)info["attr"]; Bitmap b = new Bitmap(width, height, PixelFormat.Format32bppArgb); #if USE_FASTBITMAP using (FastBitmap f = b.FastLock()) { f.CopyRegion(bmp, new Rectangle(left, top, width, height), new Rectangle(0, 0, b.Width, b.Height)); } #else Graphics g = Graphics.FromImage(b); //g.InterpolationMode = InterpolationMode.NearestNeighbor; //g.PixelOffsetMode = PixelOffsetMode.Half; g.DrawImage(bmp, new Rectangle(0, 0, b.Width, b.Height), new Rectangle(left, top, width, height), GraphicsUnit.Pixel); g.Dispose(); #endif switch (option) { case PsbImageOption.Decompress: File.WriteAllBytes(savePath + ".raw", RL.GetPixelBytesFromImage(b, pixelFormat)); break; case PsbImageOption.Compress: File.WriteAllBytes(savePath + ".rl", RL.CompressImage(b, pixelFormat)); break; case PsbImageOption.Original: case PsbImageOption.Extract: default: switch (imageFormat) { case PsbImageFormat.Bmp: b.Save(savePath + ".bmp", ImageFormat.Bmp); break; case PsbImageFormat.Png: default: b.Save(savePath + ".png", ImageFormat.Png); //b.Save(savePath + $"_{attr}.png", ImageFormat.Png); break; } break; } } bmp.Dispose(); } }
internal static void OutputResources(PSB psb, FreeMountContext context, string filePath, PsbImageOption imageOption = PsbImageOption.Original, PsbImageFormat extractFormat = PsbImageFormat.Png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(filePath); var dirPath = Path.Combine(Path.GetDirectoryName(filePath), name); var resources = psb.CollectResources(); PsbResourceJson resx = new PsbResourceJson(psb, context.Context); if (File.Exists(dirPath)) { name += "-resources"; dirPath += "-resources"; } if (!Directory.Exists(dirPath)) //ensure there is no file with same name! { if (psb.Resources.Count != 0 || resources.Count != 0) { Directory.CreateDirectory(dirPath); } } Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (imageOption == PsbImageOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.bin" : $"{psb.Resources[i].Index}.bin"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { for (int i = 0; i < resources.Count; i++) { var resource = resources[i]; //Generate Friendly Name string relativePath = resource.GetFriendlyName(psb.Type); switch (imageOption) { case PsbImageOption.Extract: ImageFormat pixelFormat; switch (extractFormat) { case PsbImageFormat.Png: relativePath += ".png"; pixelFormat = ImageFormat.Png; break; default: relativePath += ".bmp"; pixelFormat = ImageFormat.Bmp; break; } relativePath = CheckPath(relativePath, i); if (resource.Compress == PsbCompressType.RL) { RL.DecompressToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } else if (resource.Compress == PsbCompressType.Tlg || resource.Compress == PsbCompressType.ByName) { var bmp = context.ResourceToBitmap(resource.Compress == PsbCompressType.Tlg ? ".tlg" : Path.GetExtension(resource.Name), resource.Data); if (bmp == null) { if (resource.Compress == PsbCompressType.Tlg) //Fallback to managed TLG decoder { using (var ms = new MemoryStream(resource.Data)) using (var br = new BinaryReader(ms)) { bmp = new TlgImageConverter().Read(br); bmp.Save(Path.Combine(dirPath, relativePath), pixelFormat); bmp.Dispose(); } } relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { bmp.Save(Path.Combine(dirPath, relativePath), pixelFormat); bmp.Dispose(); } } //else if (resource.Compress == PsbCompressType.ByName) //{ // relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); // File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); //} else { RL.ConvertToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } break; case PsbImageOption.Original: if (resources[i].Compress == PsbCompressType.RL) { relativePath += ".rl"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.Tlg) { relativePath += ".tlg"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { relativePath += ".raw"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } break; case PsbImageOption.Decompress: relativePath += ".raw"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress == PsbCompressType.RL ? RL.Decompress(resource.Data) : resource.Data); break; case PsbImageOption.Compress: relativePath += ".rl"; relativePath = CheckPath(relativePath, i); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress != PsbCompressType.RL ? RL.Compress(resource.Data) : resource.Data); break; default: throw new ArgumentOutOfRangeException(nameof(imageOption), imageOption, null); } try { resDictionary.Add(i.ToString(), $"{name}/{relativePath}"); } catch (ArgumentException e) { throw new PsbBadFormatException(PsbBadFormatReason.Resources, "There are resources with same names! Try Raw export mode.", e); } } } //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; resx.Context = context.Context; File.WriteAllText(Path.ChangeExtension(filePath, ".resx.json"), JsonConvert.SerializeObject(resx, Formatting.Indented)); } else { File.WriteAllText(Path.ChangeExtension(filePath, ".res.json"), JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } string CheckPath(string rPath, int id) { var k = Path.GetFileNameWithoutExtension(rPath); if (resDictionary.ContainsKey(k)) { return($"{id}{Path.GetExtension(rPath)}"); } return(rPath); } }
/// <summary> /// Decompile to files /// </summary> /// <param name="psb">PSB</param> /// <param name="outputPath">Output json file name, should end with .json</param> /// <param name="additionalContext">additional context used in decompilation</param> /// <param name="imageOption">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> public static void DecompileToFile(PSB psb, string outputPath, Dictionary <string, object> additionalContext = null, PsbImageOption imageOption = PsbImageOption.Original, PsbImageFormat extractFormat = PsbImageFormat.Png, bool useResx = true, uint?key = null) { var context = FreeMount.CreateContext(additionalContext); if (key != null) { context.Context[FreeMount.CryptKey] = key; } File.WriteAllText(outputPath, Decompile(psb)); //MARK: breaking change for json path OutputResources(psb, context, outputPath, imageOption, extractFormat, useResx); }
/// <summary> /// Decompile to files /// </summary> /// <param name="inputPath">PSB file path</param> /// <param name="imageOption">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> public static void DecompileToFile(string inputPath, PsbImageOption imageOption = PsbImageOption.Original, PsbImageFormat extractFormat = PsbImageFormat.Png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(inputPath); var dirPath = Path.Combine(Path.GetDirectoryName(inputPath), name); File.WriteAllText(inputPath + ".json", Decompile(inputPath, out var psb)); var resources = psb.CollectResources(); PsbResourceJson resx = new PsbResourceJson { PsbVersion = psb.Header.Version, PsbType = psb.Type, Platform = psb.Platform, ExternalTextures = psb.Type == PsbType.Motion && psb.Resources.Count <= 0 }; if (!Directory.Exists(dirPath)) //ensure no file with same name! { Directory.CreateDirectory(dirPath); } Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (imageOption == PsbImageOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.bin" : $"{psb.Resources[i].Index}.bin"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { for (int i = 0; i < resources.Count; i++) { var resource = resources[i]; //Generate Friendly Name string relativePath; if (psb.Type == PsbType.Pimg && !string.IsNullOrWhiteSpace(resource.Name)) { relativePath = Path.GetFileNameWithoutExtension(resource.Name); } else if (string.IsNullOrWhiteSpace(resource.Name) || string.IsNullOrWhiteSpace(resource.Part)) { relativePath = resource.Index.ToString(); } else { relativePath = $"{resource.Part}{PsbResCollector.ResourceNameDelimiter}{resource.Name}"; } switch (imageOption) { case PsbImageOption.Extract: //var pixelFormat = resource.Spec.DefaultPixelFormat(); //MARK: PixelFormat should refer `type` switch (extractFormat) { case PsbImageFormat.Png: relativePath += ".png"; if (resource.Compress == PsbCompressType.RL) { RL.UncompressToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, PsbImageFormat.Png, resource.PixelFormat); } else if (resource.Compress == PsbCompressType.Tlg || resource.Compress == PsbCompressType.ByName && resource.Name.EndsWith(".tlg", true, null)) { TlgImageConverter converter = new TlgImageConverter(); using (var ms = new MemoryStream(resource.Data)) { BinaryReader br = new BinaryReader(ms); converter.Read(br).Save(Path.Combine(dirPath, relativePath), ImageFormat.Png); } //WARN: tlg is kept and recorded in resource json for compile relativePath = Path.ChangeExtension(relativePath, ".tlg"); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.ByName) { relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { RL.ConvertToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } break; default: relativePath += ".bmp"; if (resource.Compress == PsbCompressType.RL) { RL.UncompressToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, PsbImageFormat.Bmp, resource.PixelFormat); } else if (resource.Compress == PsbCompressType.Tlg || resource.Compress == PsbCompressType.ByName && resource.Name.EndsWith(".tlg", true, null)) { TlgImageConverter converter = new TlgImageConverter(); using (var ms = new MemoryStream(resource.Data)) { BinaryReader br = new BinaryReader(ms); converter.Read(br).Save(Path.Combine(dirPath, relativePath), ImageFormat.Bmp); } //WARN: tlg is kept and recorded in resource json for compile relativePath = Path.ChangeExtension(relativePath, ".tlg"); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.ByName) { relativePath = Path.ChangeExtension(relativePath, Path.GetExtension(resource.Name)); File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { RL.ConvertToImageFile(resource.Data, Path.Combine(dirPath, relativePath), resource.Height, resource.Width, extractFormat, resource.PixelFormat); } break; } break; case PsbImageOption.Original: if (resources[i].Compress == PsbCompressType.RL) { relativePath += ".rl"; File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else if (resource.Compress == PsbCompressType.Tlg) { relativePath += ".tlg"; File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } else { relativePath += ".raw"; File.WriteAllBytes(Path.Combine(dirPath, relativePath), resource.Data); } break; case PsbImageOption.Uncompress: File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress == PsbCompressType.RL ? RL.Uncompress(resource.Data) : resource.Data); relativePath += ".raw"; break; case PsbImageOption.Compress: File.WriteAllBytes(Path.Combine(dirPath, relativePath), resources[i].Compress != PsbCompressType.RL ? RL.Compress(resource.Data) : resource.Data); relativePath += ".rl"; break; default: throw new ArgumentOutOfRangeException(nameof(imageOption), imageOption, null); } try { resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } catch (ArgumentException e) { throw new BadImageFormatException("There are resources with same names! Try Raw export mode.", e); } } } //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; File.WriteAllText(inputPath + ".resx.json", JsonConvert.SerializeObject(resx, Formatting.Indented)); } else { File.WriteAllText(inputPath + ".res.json", JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } }