public Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { var resources = psb.CollectResources <AudioMetadata>(); Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (extractOption == PsbExtractOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.raw" : $"{psb.Resources[i].Index}.raw"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { foreach (var resource in resources) { if (resource.ChannelList.Count == 1) { var bts = resource.ChannelList[0].TryToWave(context); var relativePath = resource.GetFileName(resource.ChannelList[0].WaveExtension); //WaveExtension may change after ToWave if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } else if (resource.ChannelList.Count > 1) { for (var j = 0; j < resource.ChannelList.Count; j++) { var waveChannel = resource.ChannelList[j]; var bts = waveChannel.TryToWave(context); var relativePath = resource.GetFileName($"-{j}{waveChannel.WaveExtension}"); if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } } } } return(resDictionary); }
/// <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); }
public override Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { //Extra Extract if (extractOption == PsbExtractOption.Extract) { if (psb.Type == PsbType.Tachie) { var bitmaps = TextureCombiner.CombineTachie(psb); foreach (var kv in bitmaps) { kv.Value.Save(Path.Combine(dirPath, $"{kv.Key}{context.ImageFormat.DefaultExtension()}"), context.ImageFormat.ToImageFormat()); } } } return(base.OutputResources(psb, context, name, dirPath, extractOption)); }
public static Dictionary <string, string> OutputExtraResources(PSB psb, FreeMountContext context, string name, string dirPath, out Dictionary <string, float[]> flattenArrays, PsbExtractOption extractOption = PsbExtractOption.Original) { Dictionary <string, string> extraResDictionary = new Dictionary <string, string>(); flattenArrays = null; if (!context.UseFlattenArray() && !Consts.FlattenArrayByDefault) { extractOption = PsbExtractOption.Original; } if (extractOption == PsbExtractOption.Original) { if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } for (int i = 0; i < psb.ExtraResources.Count; i++) { var relativePath = psb.ExtraResources[i].Index == null ? $"#{Consts.ExtraResourceIdentifierChar}{i}.bin" : $"{Consts.ExtraResourceIdentifierChar}{psb.ExtraResources[i].Index}.bin"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.ExtraResources[i].Data); extraResDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{Consts.ExtraResourceFolderName}/{relativePath}"); } } else //Extract { flattenArrays = new Dictionary <string, float[]>(); //context.Context[Consts.Context_FlattenArray] = flattenArrays; for (int i = 0; i < psb.ExtraResources.Count; i++) { var relativePath = psb.ExtraResources[i].Index == null ? $"#{Consts.ExtraResourceIdentifierChar}{i}.bin" : $"{Consts.ExtraResourceIdentifierChar}{psb.ExtraResources[i].Index}.bin"; var data = psb.ExtraResources[i].Data; var resName = Path.GetFileNameWithoutExtension(relativePath); if (data.Length % 4 == 0) { var floats = MemoryMarshal.Cast <byte, float>(data.AsSpan()); flattenArrays.Add(resName, floats.ToArray()); //extraResDictionary.Add(resName, ""); } else { if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.ExtraResources[i].Data); extraResDictionary.Add(resName, $"{name}/{Consts.ExtraResourceFolderName}/{relativePath}"); } } } return(extraResDictionary); }
public static Dictionary <string, string> OutputImageResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png) { var resources = psb.CollectResources <ImageMetadata>(); Dictionary <string, string> resDictionary = new Dictionary <string, string>(); ImageFormat pixelFormat; switch (extractFormat) { case PsbImageFormat.png: pixelFormat = ImageFormat.Png; break; default: pixelFormat = ImageFormat.Bmp; break; } if (extractOption == PsbExtractOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{Consts.ResourceIdentifierChar}{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 var friendlyName = resource.GetFriendlyName(psb.Type); string relativePath = friendlyName; if (string.IsNullOrWhiteSpace(friendlyName)) { relativePath = resource.Resource.Index?.ToString() ?? $"({i})"; friendlyName = i.ToString(); } var currentExtractOption = extractOption; if (resource.Compress != PsbCompressType.Tlg && resource.Compress != PsbCompressType.ByName && (resource.Width <= 0 || resource.Height <= 0)) //impossible to extract, just keep raw { if (currentExtractOption == PsbExtractOption.Extract) { currentExtractOption = PsbExtractOption.Original; } } switch (currentExtractOption) { case PsbExtractOption.Extract: switch (extractFormat) { case PsbImageFormat.png: relativePath += ".png"; break; default: relativePath += ".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, resource.PalData, resource.PalettePixelFormat); } break; case PsbExtractOption.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 PsbExtractOption.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 PsbExtractOption.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(currentExtractOption), currentExtractOption, null); } //Determine save name try { bool indexConflict = false; uint conflictedIndex = 0; if (resource.Resource.Index != null) //try index as name first { if (resDictionary.ContainsKey(resource.Index.ToString())) //index is used before { Console.WriteLine( $"[WARN] Resource Index {resource.Index} conflict. May be resource sharing, but may also be something wrong."); //continue; indexConflict = true; conflictedIndex = resource.Resource.Index.Value; resource.Resource.Index = null; //have another try on friendly name } } if (resource.Resource.Index == null) { if (resDictionary.ContainsKey(friendlyName)) // friendly name is also used (most likely its the same res reused), no name can be used to save { Console.WriteLine( $"[WARN] Resource Name {friendlyName} conflict. May be resource sharing, but may also be something wrong."); continue; //just skip } if (indexConflict) //index is used but friendly name is not (maybe they are different?), save using friendly name { Console.WriteLine($"[FIXED] Resource {friendlyName} is sharing same data with Index {conflictedIndex}"); } } resDictionary.Add(resource.Resource.Index == null ? friendlyName : resource.Index.ToString(), $"{name}/{relativePath}"); } catch (ArgumentException e) { throw new PsbBadFormatException(PsbBadFormatReason.Resources, "Resource Export Error: Name conflict, or Index is not specified. Try Raw export mode.", e); } } } string CheckPath(string rPath, int id) { var k = Path.GetFileNameWithoutExtension(rPath); if (resDictionary.ContainsKey(k)) { return($"{id}{Path.GetExtension(rPath)}"); } return(rPath); } return(resDictionary); }
public virtual Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { return(PsbResHelper.OutputImageResources(psb, context, name, dirPath, extractOption)); }
/// <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="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> public static void DecompileToFile(PSB psb, string outputPath, Dictionary <string, object> additionalContext = null, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png, bool useResx = true, uint?key = null) { var context = FreeMount.CreateContext(additionalContext); if (key != null) { context.Context[Consts.Context_CryptKey] = key; } File.WriteAllText(outputPath, Decompile(psb)); //MARK: breaking change for json path OutputResources(psb, context, outputPath, extractOption, extractFormat, useResx); }
internal static void OutputResources(PSB psb, FreeMountContext context, string filePath, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(filePath); var dirPath = Path.Combine(Path.GetDirectoryName(filePath), name); PsbResourceJson resx = new PsbResourceJson(psb, context.Context); if (File.Exists(dirPath)) { name += "-resources"; dirPath += "-resources"; } var extraDir = Path.Combine(dirPath, Consts.ExtraResourceFolderName); if (!Directory.Exists(dirPath)) //ensure there is no file with same name! { if (psb.Resources.Count != 0) { Directory.CreateDirectory(dirPath); } } if (psb.ExtraResources.Count > 0) { var extraDic = PsbResHelper.OutputExtraResources(psb, context, name, extraDir, out var flattenArrays, extractOption); resx.ExtraResources = extraDic; if (flattenArrays != null && flattenArrays.Count > 0) { resx.ExtraFlattenArrays = flattenArrays; } } var resDictionary = psb.TypeHandler.OutputResources(psb, context, name, dirPath, extractOption); //MARK: We use `.resx.json` to distinguish from psbtools' `.res.json` if (useResx) { resx.Resources = resDictionary; resx.Context = context.Context; string json; if (Consts.JsonArrayCollapse) { json = ArrayCollapseJsonTextWriter.SerializeObject(resx); } else { json = JsonConvert.SerializeObject(resx, Formatting.Indented); } File.WriteAllText(ChangeExtensionForOutputJson(filePath, ".resx.json"), json); } else { if (psb.ExtraResources.Count > 0) { throw new NotSupportedException("PSBv4 cannot use legacy res.json format."); } File.WriteAllText(ChangeExtensionForOutputJson(filePath, ".res.json"), JsonConvert.SerializeObject(resDictionary.Values.ToList(), Formatting.Indented)); } }
/// <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, PsbExtractOption option = PsbExtractOption.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 pixel = texture[Consts.ResourceKey]; if (pixel == null || !(pixel is PsbResource pixelRes)) { throw new PsbBadFormatException(PsbBadFormatReason.Resources, "External Texture PSB is not supported. Please Link textures into PSB."); return; } var md = PsbResHelper.GenerateImageMetadata(texture, pixelRes); 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 PsbExtractOption.Decompress: File.WriteAllBytes(savePath + ".raw", RL.GetPixelBytesFromImage(b, pixelFormat)); break; case PsbExtractOption.Compress: File.WriteAllBytes(savePath + ".rl", RL.CompressImage(b, pixelFormat)); break; case PsbExtractOption.Original: case PsbExtractOption.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(); } }
public Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { var resources = psb.CollectResources <AudioMetadata>(); Dictionary <string, string> resDictionary = new Dictionary <string, string>(); if (extractOption == PsbExtractOption.Original) { for (int i = 0; i < psb.Resources.Count; i++) { var relativePath = psb.Resources[i].Index == null ? $"#{i}.raw" : $"{psb.Resources[i].Index}.raw"; File.WriteAllBytes( Path.Combine(dirPath, relativePath), psb.Resources[i].Data); resDictionary.Add(Path.GetFileNameWithoutExtension(relativePath), $"{name}/{relativePath}"); } } else { foreach (var resource in resources) { if (resource.ChannelList.Count == 1) { var bts = resource.ChannelList[0].TryToWave(context); var relativePath = resource.GetFileName(resource.ChannelList[0].Extension + resource.ChannelList[0].WaveExtension); //WaveExtension may change after ToWave if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } else if (resource.ChannelList.Count > 1) { if (resource.Pan == PsbAudioPan.LeftRight) //load audio.vag.l.wav & audio.vag.r.wav { var left = resource.GetLeftChannel(); var relativePathL = resource.GetFileName($"{left.Extension}.l{left.WaveExtension}"); var btsL = left.TryToWave(context); if (btsL != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePathL), btsL); } else { relativePathL = resource.GetFileName($"{left.Extension}.l{left.Extension}"); File.WriteAllBytes(Path.Combine(dirPath, relativePathL), left.Data.Data); resDictionary.Add(resource.Name, $"{name}/{relativePathL}"); } var right = resource.GetRightChannel(); var relativePathR = resource.GetFileName($"{right.Extension}.r{right.WaveExtension}"); var btsR = right.TryToWave(context); if (btsR != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePathR), btsR); } else { relativePathR = resource.GetFileName($"{right.Extension}.r{right.Extension}"); File.WriteAllBytes(Path.Combine(dirPath, relativePathR), right.Data.Data); resDictionary.Add(resource.Name, $"{name}/{relativePathR}"); } if (btsL != null && btsR != null) { var relativePath = resource.GetFileName($"{left.Extension}{left.WaveExtension}"); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); //a virtual file path } } else //not LeftRight { for (var j = 0; j < resource.ChannelList.Count; j++) //load audio.vag.1.wav etc. { var waveChannel = resource.ChannelList[j]; if (waveChannel.Data.Index == null) { Console.WriteLine($"[WARN] Channel {j} is not linked with a Resource."); continue; } var bts = waveChannel.TryToWave(context); var noStr = waveChannel.Data.Index == null ? $".@{j}" : $".#{waveChannel.Data.Index.Value}"; //TODO: handle @ - channel internal No. var relativePath = resource.GetFileName($"{waveChannel.Extension}{noStr}{waveChannel.WaveExtension}"); if (bts != null) { File.WriteAllBytes(Path.Combine(dirPath, relativePath), bts); resDictionary.Add(resource.Name, $"{name}/{relativePath}"); } } } } } } return(resDictionary); }
internal static void OutputResources(PSB psb, FreeMountContext context, string filePath, PsbExtractOption extractOption = PsbExtractOption.Original, PsbImageFormat extractFormat = PsbImageFormat.png, bool useResx = true) { var name = Path.GetFileNameWithoutExtension(filePath); var dirPath = Path.Combine(Path.GetDirectoryName(filePath), name); 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) { Directory.CreateDirectory(dirPath); } } var resDictionary = psb.TypeHandler.OutputResources(psb, context, name, dirPath, extractOption); //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)); } }
public Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { return(null); }
public override Dictionary <string, string> OutputResources(PSB psb, FreeMountContext context, string name, string dirPath, PsbExtractOption extractOption = PsbExtractOption.Original) { return(base.OutputResources(psb, context, name, dirPath, extractOption)); }