From 6730b14c984c826c40efa9dfdf3acdd9f26eac79 Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 26 Oct 2015 22:17:12 +0400 Subject: [PATCH] implemented Palette PACK2 archives, PGA and CHR images --- ArcFormats/ArcFormats.csproj | 3 + ArcFormats/Palette/ArcCHR.cs | 135 +++++++++++++++++++++++++++++++++ ArcFormats/Palette/ArcPAK.cs | 80 +++++++++++++++++++ ArcFormats/Palette/ImagePGA.cs | 79 +++++++++++++++++++ supported.html | 5 ++ 5 files changed, 302 insertions(+) create mode 100644 ArcFormats/Palette/ArcCHR.cs create mode 100644 ArcFormats/Palette/ArcPAK.cs create mode 100644 ArcFormats/Palette/ImagePGA.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 94cef2aa..702dde3c 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -76,6 +76,9 @@ + + + diff --git a/ArcFormats/Palette/ArcCHR.cs b/ArcFormats/Palette/ArcCHR.cs new file mode 100644 index 00000000..b28c8eb0 --- /dev/null +++ b/ArcFormats/Palette/ArcCHR.cs @@ -0,0 +1,135 @@ +//! \file ArcCHR.cs +//! \date Mon Oct 26 07:26:39 2015 +//! \brief Multi-frame PNG archive. +// +// Copyright (C) 2015 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using GameRes.Utility; +using System.Text; + +namespace GameRes.Formats.Palette +{ + internal class CharEntry : Entry + { + public int OffsetX; + public int OffsetY; + } + + [Export(typeof(ArchiveFormat))] + public class ChrOpener : ArchiveFormat + { + public override string Tag { get { return "CHR/Palette"; } } + public override string Description { get { return "Palette multi-frame PNG archive"; } } + public override uint Signature { get { return 0x72616863; } } // 'char' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public ChrOpener () + { + Extensions = new string[] { "chr" }; + } + + public override ArcFile TryOpen (ArcView file) + { + uint index_offset = file.View.ReadUInt32 (4); + int count = file.View.ReadInt32 (index_offset)+1; + if (!IsSaneCount (count)) + return null; + + string base_name = Path.GetFileNameWithoutExtension (file.Name); + + var dir = new List (count); + dir.Add (new Entry { + Name = string.Format ("{0}#0.png", base_name), + Type = "image", Offset = 8, Size = index_offset-8 + }); + index_offset += 4; + for (int i = 1; i < count; ++i) + { + uint name_length = file.View.ReadByte (index_offset++); + var name = file.View.ReadString (index_offset, name_length); + uint size = file.View.ReadUInt32 (index_offset+name_length); + index_offset += name_length + 4; + if (size > 8) + { + var entry = new CharEntry + { + Name = string.Format ("{0}#{1}.png", base_name, name), + Type = "image", + Offset = index_offset+8, + Size = size-8, + OffsetX = file.View.ReadInt16 (index_offset), + OffsetY = file.View.ReadInt16 (index_offset+2), + //Width = file.View.ReadUInt16 (index_offset+4), + //Height = file.View.ReadUInt16 (index_offset+6), + }; + dir.Add (entry); + } + index_offset += size; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + int extra_length = PgaFormat.PngHeader.Length + PgaFormat.PngFooter.Length; + var png = new MemoryStream ((int)entry.Size + extra_length + 17); + var cent = entry as CharEntry; + png.Write (PgaFormat.PngHeader, 0, PgaFormat.PngHeader.Length); + using (var body = arc.File.CreateStream (entry.Offset, entry.Size)) + { + if (cent != null && cent != arc.Dir.First() + && (cent.OffsetX != 0 || cent.OffsetY != 0)) + { + // inject oFFs chunk into PNG stream + using (var reader = new BinaryReader (body, Encoding.ASCII, true)) + using (var writer = new BinaryWriter (png, Encoding.ASCII, true)) + { + var ihdr_size = reader.ReadInt32(); + writer.Write (ihdr_size); + ihdr_size = Binary.BigEndian (ihdr_size); + writer.Write (reader.ReadBytes (ihdr_size+8)); + + writer.Write (Binary.BigEndian ((int)9)); + int position = (int)writer.BaseStream.Position; + char[] tag = { 'o', 'F', 'F', 's' }; + writer.Write (tag); + writer.Write (Binary.BigEndian (cent.OffsetX)); + writer.Write (Binary.BigEndian (cent.OffsetY)); + writer.Write ((byte)0); + uint crc = Crc32.Compute (png.GetBuffer(), position, 13); + writer.Write (Binary.BigEndian (crc)); + } + } + body.CopyTo (png); + } + png.Write (PgaFormat.PngFooter, 0, PgaFormat.PngFooter.Length); + png.Position = 0; + return png; + } + } +} diff --git a/ArcFormats/Palette/ArcPAK.cs b/ArcFormats/Palette/ArcPAK.cs new file mode 100644 index 00000000..284bf6e1 --- /dev/null +++ b/ArcFormats/Palette/ArcPAK.cs @@ -0,0 +1,80 @@ +//! \file ArcPAK.cs +//! \date Mon Oct 26 04:40:25 2015 +//! \brief Palette PACK2 resource archives. +// +// Copyright (C) 2015 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Utility; + +namespace GameRes.Formats.Palette +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get { return "PAK2/Palette"; } } + public override string Description { get { return "Palette resource archive"; } } + public override uint Signature { get { return 0x43415005; } } // '\x05PAC' + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public PakOpener () + { + Extensions = new string[] { "pak" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, "K2")) + return null; + int count = file.View.ReadInt32 (6); + if (!IsSaneCount (count)) + return null; + + uint index_offset = 0xA; + var name_buf = new byte[0x40]; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + int name_length = file.View.ReadByte (index_offset++); + if (name_length > name_buf.Length) + name_buf = new byte[name_length]; + file.View.Read (index_offset, name_buf, 0, (uint)name_length); + for (int j = 0; j < name_length; ++j) + name_buf[j] ^= 0xFF; + index_offset += (uint)name_length; + var name = Encodings.cp932.GetString (name_buf, 0, name_length); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = file.View.ReadUInt32 (index_offset); + entry.Size = file.View.ReadUInt32 (index_offset+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 8; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/Palette/ImagePGA.cs b/ArcFormats/Palette/ImagePGA.cs new file mode 100644 index 00000000..8932bbdd --- /dev/null +++ b/ArcFormats/Palette/ImagePGA.cs @@ -0,0 +1,79 @@ +//! \file ImagePGA.cs +//! \date Mon Oct 26 06:38:07 2015 +//! \brief Obfuscated PNG image. +// +// Copyright (C) 2015 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Palette +{ + [Export(typeof(ImageFormat))] + public class PgaFormat : PngFormat + { + public override string Tag { get { return "PGA"; } } + public override string Description { get { return "Palette obfuscated PNG image"; } } + public override uint Signature { get { return 0x50414750; } } // 'PGAP' + + public override ImageMetaData ReadMetaData (Stream stream) + { + using (var png = DeobfuscateStream (stream)) + return base.ReadMetaData (png); + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + using (var png = DeobfuscateStream (stream)) + return base.Read (png, info); + } + + public override void Write (Stream file, ImageData image) + { + using (var png = new MemoryStream()) + { + base.Write (png, image); + var buffer = png.GetBuffer(); + for (int i = 0; i < 8; ++i) + buffer[i+8] ^= (byte)"PGAECODE"[i]; + buffer[5] = (byte)'P'; + buffer[6] = (byte)'G'; + buffer[7] = (byte)'A'; + file.Write (buffer, 5, (int)png.Length - 5); + } + } + + public static readonly byte[] PngHeader = { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }; + public static readonly byte[] PngFooter = { 0, 0, 0, 0, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; + + Stream DeobfuscateStream (Stream stream) + { + var png_header = new byte[0x10]; + stream.Read (png_header, 5, 11); + System.Buffer.BlockCopy (PngHeader, 0, png_header, 0, 8); + for (int i = 0; i < 8; ++i) + png_header[i+8] ^= (byte)"PGAECODE"[i]; + var png_body = new StreamRegion (stream, 11, true); + return new PrefixStream (png_header, png_body); + } + } +} diff --git a/supported.html b/supported.html index 40b6ec5e..a78fcc96 100644 --- a/supported.html +++ b/supported.html @@ -445,6 +445,11 @@ Yakata ~Kannou Kitan~
Princess Serenade
*.gfbGFBNo +*.pak\x05PACK2NoPalette +Moshimo Ashita ga Harenaraba
+ +*.pgaPGAPGAHYes +*.chrcharNo

1 Non-encrypted only