public void TestTlgNative() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "title-pimg"); var path = Path.Combine(resPath, "title-pimg", "566.tlg"); var bmp = TlgImageFormatter.LoadTlg(File.ReadAllBytes(path), out int ver); var width = bmp.Width; var height = bmp.Height; bmp.Save("tlg.png", ImageFormat.Png); path = Path.Combine(resPath, "emote_test.pure", "tex#000-texture.png"); Bitmap bmp2 = new Bitmap(path); var bts = TlgImageFormatter.SaveTlg(bmp2); TlgImageConverter converter = new TlgImageConverter(); using (var ms = new MemoryStream(bts)) { using (var br = new BinaryReader(ms)) { var bmp3 = converter.Read(br); bmp3.Save("tlg2.png", ImageFormat.Png); } } }
/// <summary> /// Load TLG /// </summary> /// <param name="tlgData"></param> /// <param name="version">TLG version, can be 0(unknown),5,6</param> /// <returns></returns> public static Bitmap LoadTlg(byte[] tlgData, out int version) { if (!PreferManaged) { try { return(TlgNative.ToBitmap(tlgData, out version)); } catch (Exception) { //ignored, fallback to managed decoder } } if (_managedConverter == null) { _managedConverter = new TlgImageConverter(); } using (var ms = new MemoryStream(tlgData)) { using (var br = new BinaryReader(ms)) { var bmp = _managedConverter.ReadAndGetMetaData(br, out var md); version = md.Version; return(bmp); } } }
public void ManagedBenchmark() { TlgImageConverter converter = new TlgImageConverter(); using (BinaryReader br = new BinaryReader(new MemoryStream(_tlgBytes), Encoding.UTF8, false)) { Bitmap b = converter.Read(br); int w = b.Width; b.Dispose(); } }
static void Main(string[] args) { //MemoryTest(); var target = File.Exists("test.tlg") ? "test.tlg" : "NewGame5.tlg"; TlgImageConverter converter = new TlgImageConverter(); var original = File.ReadAllBytes(target); byte[] converted = null; using (var fs = File.Open(target, FileMode.Open)) { using (var br = new BinaryReader(fs)) { var bmp = converter.Read(br); converted = bmp.ToTlg6(); if (converted == null) { Console.WriteLine("Conversion failed."); } //else //{ // Console.WriteLine($"Totally equal: {(original.SequenceEqual(converted) ? "Yes" : "No")}"); //} } } if (converted != null) { using (var ms = new MemoryStream(converted)) { using (var br = new BinaryReader(ms)) { var bmp = converter.Read(br); bmp.Save("output.png", ImageFormat.Png); } } } Console.WriteLine($"IsTLG: {(TlgNative.CheckTlg(original) ? "Yes" : "No")}"); if (TlgNative.GetInfoTlg(original, out int w, out int h, out int v)) { Console.WriteLine($"TLGv{v} Size: {w} x {h}"); } //var bmp2 = TlgNative.ToBitmap(original, out _); var t = TlgNative.ToBitmap(original); Console.WriteLine(t.Item2); t.Item1.Save("output2.png", ImageFormat.Png); Console.WriteLine("Done."); Console.ReadLine(); }
public void TestTlgDecode() { var resPath = Path.Combine(Environment.CurrentDirectory, @"..\..\Res"); //var path = Path.Combine(resPath, "title-pimg"); var path = Path.Combine(resPath, "conf"); TlgImageConverter image = new TlgImageConverter(); foreach (var tlg in Directory.EnumerateFiles(path, "*.tlg")) { using (var stream = File.OpenRead(tlg)) { var br = new BinaryReader(stream); var img = image.Read(br); img.Save($"{tlg}.png", ImageFormat.Png); } } }
/// <summary> /// Load TLG /// </summary> /// <param name="tlgData"></param> /// <param name="version">TLG version, can be 0(unknown),5,6</param> /// <returns></returns> public static Bitmap LoadTlg(byte[] tlgData, out int version) { if (_managedConverter == null) { _managedConverter = new TlgImageConverter(); } using (var ms = new MemoryStream(tlgData)) { using (var br = new BinaryReader(ms)) { var bmp = _managedConverter.ReadAndGetMetaData(br, out var md); version = md.Version; return(bmp); } } }
static void MemoryTest() { var target = File.Exists("test.tlg") ? "test.tlg" : "NewGame5.tlg"; TlgImageConverter converter = new TlgImageConverter(); var original = File.ReadAllBytes(target); Console.WriteLine("TLG bytes loaded."); Console.ReadLine(); //Pure managed! using (var ms = new MemoryStream(original)) { using (BinaryReader br = new BinaryReader(ms)) { Bitmap b = converter.Read(br); b.Dispose(); } } Console.WriteLine("Managed done."); Console.ReadLine(); GC.Collect(); using (TlgLoader ldr = new TlgLoader(original)) { Bitmap b = ldr.Bitmap; b.Dispose(); } //You can still access b's properties here (out of `using` scope) if you don't dispose it, //but you can not call `Save` or `LockBits` etc. because the data is deleted. Console.WriteLine("NativeLoader done."); Console.ReadLine(); GC.Collect(); //By default `ToBitmap` copies data to managed side so you can call `Save` as you want. //Set `useUnmanagedScan0 = true` to make it behave like TlgLoader but no one deletes the data!! Bitmap b2 = TlgNative.ToBitmap(original, out _); b2.Dispose(); Console.WriteLine("NativeCopy done."); Console.ReadLine(); GC.Collect(); Console.WriteLine("All done."); Console.ReadLine(); }
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); }
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="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)); } }