From ca52730701a9c0df1ef817ba7474a5bb9f594a6a Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 19 Aug 2016 10:57:46 +0400 Subject: [PATCH] implemented various Leaf resources. --- ArcFormats/ArcFormats.csproj | 7 + ArcFormats/Leaf/ArcA.cs | 86 +++++++++++ ArcFormats/Leaf/ArcAM.cs | 130 +++++++++++++++++ ArcFormats/Leaf/ArcLAC.cs | 81 +++++++++++ ArcFormats/Leaf/AudioG.cs | 269 +++++++++++++++++++++++++++++++++++ ArcFormats/Leaf/AudioW.cs | 68 +++++++++ ArcFormats/Leaf/ImagePX.cs | 217 ++++++++++++++++++++++++++++ supported.html | 8 ++ 8 files changed, 866 insertions(+) create mode 100644 ArcFormats/Leaf/ArcA.cs create mode 100644 ArcFormats/Leaf/ArcAM.cs create mode 100644 ArcFormats/Leaf/ArcLAC.cs create mode 100644 ArcFormats/Leaf/AudioG.cs create mode 100644 ArcFormats/Leaf/AudioW.cs create mode 100644 ArcFormats/Leaf/ImagePX.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 0f1a1d43..2b81d405 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -92,6 +92,7 @@ + @@ -110,6 +111,12 @@ + + + + + + diff --git a/ArcFormats/Leaf/ArcA.cs b/ArcFormats/Leaf/ArcA.cs new file mode 100644 index 00000000..a3249edc --- /dev/null +++ b/ArcFormats/Leaf/ArcA.cs @@ -0,0 +1,86 @@ +//! \file ArcA.cs +//! \date Wed Aug 17 17:32:54 2016 +//! \brief Leaf resource archive. +// +// Copyright (C) 2016 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Compression; + +namespace GameRes.Formats.Leaf +{ + [Export(typeof(ArchiveFormat))] + public class AOpener : ArchiveFormat + { + public override string Tag { get { return "A/Leaf"; } } + public override string Description { get { return "Leaf resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public AOpener () + { + Extensions = new string[] { "a" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (0xAF1E != file.View.ReadUInt16 (0)) + return null; + int count = file.View.ReadUInt16 (2); + if (!IsSaneCount (count)) + return null; + + long base_offset = 4 + 0x20 * count; + uint index_offset = 4; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_offset, 0x17); + if (string.IsNullOrEmpty (name)) + return null; + var entry = FormatCatalog.Instance.Create (name); + entry.IsPacked = 0 != file.View.ReadByte (index_offset+0x17); + entry.Size = file.View.ReadUInt32 (index_offset+0x18); + entry.Offset = base_offset + file.View.ReadUInt32 (index_offset+0x1C); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x20; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked) + return base.OpenEntry (arc, entry); + if (0 == pent.UnpackedSize) + pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset); + var input = arc.File.CreateStream (entry.Offset+4, entry.Size-4); + return new LzssStream (input); + } + } +} diff --git a/ArcFormats/Leaf/ArcAM.cs b/ArcFormats/Leaf/ArcAM.cs new file mode 100644 index 00000000..4a3502b4 --- /dev/null +++ b/ArcFormats/Leaf/ArcAM.cs @@ -0,0 +1,130 @@ +//! \file ArcAM.cs +//! \date Wed Aug 17 18:31:08 2016 +//! \brief Leaf video resources archive. +// +// Copyright (C) 2016 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.Leaf +{ + [Export(typeof(ArchiveFormat))] + public class AmOpener : ArchiveFormat + { + public override string Tag { get { return "AM/Leaf"; } } + public override string Description { get { return "Leaf video resources archive"; } } + public override uint Signature { get { return 0x30306D61; } } // 'am00' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public AmOpener () + { + Extensions = new string[] { "am" }; + } + + public override ArcFile TryOpen (ArcView file) + { + uint index_size = file.View.ReadUInt32 (4); + byte key = file.View.ReadByte (8); + var index = file.View.ReadBytes (9, index_size); + if (index.Length != index_size) + return null; + for (int i = 0; i < index.Length; ++i) + index[i] ^= key; + + uint base_offset = 9 + index_size; + int index_offset = 0; + var dir = new List(); + while (index_offset < index.Length) + { + int name_end = Array.IndexOf (index, 0, index_offset); + if (-1 == name_end || name_end == index_offset) + return null; + var name = Encodings.cp932.GetString (index, index_offset, name_end-index_offset); + index_offset = name_end+1; + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = base_offset + LittleEndian.ToUInt32 (index, index_offset); + entry.Size = LittleEndian.ToUInt32 (index, index_offset+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 8; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + if (null == DecryptTable) + return input; + return new AmStream (input, DecryptTable); + } + + static byte[] DecryptTable = null; + + public override ResourceScheme Scheme + { + get { return new AmScheme { DecryptTable = DecryptTable }; } + set { DecryptTable = ((AmScheme)value).DecryptTable; } + } + } + + internal class AmStream : InputProxyStream + { + byte[] m_table; + + public AmStream (Stream input, byte[] table) : base (input) + { + m_table = table; + } + + public override int Read (byte[] buffer, int offset, int count) + { + int pos = (int)Position; + int read = BaseStream.Read (buffer, offset, count); + for (int i = 0; i < read; ++i) + { + buffer[offset+i] ^= m_table[(pos+i) & 0xFFFF]; + } + return read; + } + + public override int ReadByte () + { + int b = BaseStream.ReadByte(); + if (-1 != b) + b ^= m_table[(Position-1) & 0xFFFF]; + return b; + } + } + + [Serializable] + public class AmScheme : ResourceScheme + { + public byte[] DecryptTable; + } +} diff --git a/ArcFormats/Leaf/ArcLAC.cs b/ArcFormats/Leaf/ArcLAC.cs new file mode 100644 index 00000000..53b8ccd7 --- /dev/null +++ b/ArcFormats/Leaf/ArcLAC.cs @@ -0,0 +1,81 @@ +//! \file ArcLAC.cs +//! \date Wed Aug 17 15:59:38 2016 +//! \brief Leaf resource archive. +// +// Copyright (C) 2016 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Compression; + +namespace GameRes.Formats.Leaf +{ + [Export(typeof(ArchiveFormat))] + public class LacOpener : ArchiveFormat + { + public override string Tag { get { return "LAC"; } } + public override string Description { get { return "Leaf resource archive"; } } + public override uint Signature { get { return 0x43414C; } } // 'LAC' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + + uint index_offset = 8; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_offset, 0x3E); + if (string.IsNullOrEmpty (name)) + return null; + index_offset += 0x3E; + var entry = FormatCatalog.Instance.Create (name); + entry.IsPacked = 0 != file.View.ReadByte (index_offset); + index_offset += 0xE; + entry.Size = file.View.ReadUInt32 (index_offset); + entry.UnpackedSize = file.View.ReadUInt32 (index_offset+4); + entry.Offset = file.View.ReadInt64 (index_offset+0xC); + index_offset += 0x2C; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = base.OpenEntry (arc, entry); + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked) + return input; + var lzs = new LzssStream (input); + lzs.Config.FrameFill = 0x20; + return lzs; + } + } +} diff --git a/ArcFormats/Leaf/AudioG.cs b/ArcFormats/Leaf/AudioG.cs new file mode 100644 index 00000000..5dd98bd2 --- /dev/null +++ b/ArcFormats/Leaf/AudioG.cs @@ -0,0 +1,269 @@ +//! \file AudioG.cs +//! \date Thu Aug 18 15:50:27 2016 +//! \brief Leaf audio format. +// +// Copyright (C) 2016 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.Leaf +{ + [Export(typeof(AudioFormat))] + public class GAudio : AudioFormat + { + public override string Tag { get { return "G/Leaf"; } } + public override string Description { get { return "Leaf audio format (Ogg/Vorbis)"; } } + public override uint Signature { get { return 0; } } + + public GAudio () + { + Extensions = new string[] { "g" }; + } + + public override SoundInput TryOpen (Stream file) + { + var header = new byte[0x1C]; + if (header.Length != file.Read (header, 0, header.Length)) + return null; + if (header[4] != 0 || header[5] != 2 || LittleEndian.ToInt64 (header, 6) != 0) + return null; + file.Position = 0; + var input = new GStream (file); + return new OggInput (new SeekableStream (input)); + } + } + + internal class GStream : InputProxyStream + { + IEnumerator m_pages; + bool m_eof; + + byte[] m_page = new byte[0x10000]; + int m_page_pos = 0; + int m_page_length = 0; + + public GStream (Stream source) : base (source) + { + m_pages = EnumeratePages(); + m_eof = false; + } + + #region IO.Stream methods + public override bool CanSeek { get { return false; } } + public override long Length { get { throw new NotSupportedException(); } } + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + #endregion + + public override int Read (byte[] buffer, int offset, int count) + { + int total_read = 0; + while (count > 0 && !m_eof) + { + if (m_page_pos == m_page_length) + { + NextPage(); + } + if (m_eof) + break; + int available = Math.Min (m_page_length - m_page_pos, count); + Buffer.BlockCopy (m_page, m_page_pos, buffer, offset, available); + m_page_pos += available; + offset += available; + count -= available; + total_read += available; + } + return total_read; + } + + void NextPage () + { + m_eof = !m_pages.MoveNext(); + m_page_pos = 0; + m_page_length = !m_eof ? m_pages.Current : 0; + } + + enum State + { + Header, + Comment, + Setup, + Payload, + Broken, + }; + + IEnumerator EnumeratePages () + { + var state = State.Header; + for (;;) + { + int page_length = 0x1B; + int read = BaseStream.Read (m_page, 0, page_length); + if (read < page_length) + { + if (read != 0) + yield return read; + yield break; + } + PutString (0, "OggS"); + int segment_count = m_page[0x1A]; + if (0 == segment_count) + { + UpdateCrc (page_length); + yield return page_length; + continue; + } + read = BaseStream.Read (m_page, page_length, segment_count); + page_length += read; + if (read < segment_count) + { + UpdateCrc (page_length); + yield return page_length; + yield break; + } + int segments_size = 0; + for (int i = 0; i < segment_count; ++i) + { + segments_size += m_page[0x1B+i]; + } + int id, next; + switch (state) + { + case State.Header: + id = BaseStream.ReadByte(); + if (-1 == id) + break; + + m_page[page_length++] = (byte)id; + next = BaseStream.ReadByte(); + segments_size -= 2; + if (1 == id) + { + PutString (page_length, "vorbis"); + page_length += 6; + m_page[0x1B] += 5; + state = State.Comment; + } + else + { + m_page[page_length++] = (byte)next; + state = State.Broken; + } + break; + + case State.Comment: + id = BaseStream.ReadByte(); + if (-1 == id) + break; + + m_page[page_length++] = (byte)id; + next = BaseStream.ReadByte(); + segments_size -= 2; + if (3 == id) + { + PutString (page_length, "vorbis"); + page_length += 6; + read = BaseStream.Read (m_page, page_length, m_page[0x1B]-2); + page_length += read; + segments_size -= read; + m_page[0x1B] += 5; + state = State.Setup; + if (segments_size > 0) + goto case State.Setup; + } + else + { + m_page[page_length++] = (byte)next; + state = State.Broken; + } + break; + + case State.Setup: + id = BaseStream.ReadByte(); + if (-1 == id) + break; + + m_page[page_length++] = (byte)id; + next = BaseStream.ReadByte(); + segments_size -= 2; + if (5 == id) + { + PutString (page_length, "vorbis"); + page_length += 6; + m_page[0x1B+segment_count-1] += 5; + state = State.Payload; + } + else + { + m_page[page_length++] = (byte)next; + state = State.Broken; + } + break; + } + read = BaseStream.Read (m_page, page_length, segments_size); + page_length += read; + UpdateCrc (page_length); + yield return page_length; + } + } + + void PutString (int pos, string s) + { + for (int i = 0; i < s.Length; ++i) + m_page[pos+i] = (byte)s[i]; + } + + void UpdateCrc (int page_length) + { + LittleEndian.Pack (0, m_page, 0x16); + uint crc = Crc32Normal.UpdateCrc (0, m_page, 0, page_length); + LittleEndian.Pack (crc, m_page, 0x16); + } + + #region IDisposable Members + bool _g_disposed = false; + protected override void Dispose (bool disposing) + { + if (_g_disposed) + return; + + if (disposing) + { + m_pages.Dispose(); + } + _g_disposed = true; + base.Dispose (disposing); + } + #endregion + } +} diff --git a/ArcFormats/Leaf/AudioW.cs b/ArcFormats/Leaf/AudioW.cs new file mode 100644 index 00000000..689bbb14 --- /dev/null +++ b/ArcFormats/Leaf/AudioW.cs @@ -0,0 +1,68 @@ +//! \file AudioW.cs +//! \date Thu Aug 18 06:04:50 2016 +//! \brief Leaf PCM audio. +// +// Copyright (C) 2016 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; +using GameRes.Utility; + +namespace GameRes.Formats.Leaf +{ + [Export(typeof(AudioFormat))] + public class WAudio : AudioFormat + { + public override string Tag { get { return "W/Leaf"; } } + public override string Description { get { return "Leaf PCM audio"; } } + public override uint Signature { get { return 0; } } + + public WAudio () + { + Extensions = new string[] { "w" }; + } + + public override SoundInput TryOpen (Stream file) + { + var header = new byte[0x12]; + if (header.Length != file.Read (header, 0, header.Length)) + return null; + var format = new WaveFormat + { + FormatTag = 1, + Channels = header[0], + SamplesPerSecond = LittleEndian.ToUInt16 (header, 2), + AverageBytesPerSecond = LittleEndian.ToUInt32 (header, 6), + BlockAlign = header[1], + BitsPerSample = LittleEndian.ToUInt16 (header, 4), + }; + uint pcm_size = LittleEndian.ToUInt32 (header, 0xA); + if (0 == pcm_size || 0 == format.AverageBytesPerSecond || format.BitsPerSample < 8 + || 0 == format.Channels || format.Channels > 8 + || (format.BlockAlign * format.SamplesPerSecond != format.AverageBytesPerSecond) + || (pcm_size + 0x12 != file.Length)) + return null; + var pcm = new StreamRegion (file, 0x12, pcm_size); + return new RawPcmInput (pcm, format); + } + } +} diff --git a/ArcFormats/Leaf/ImagePX.cs b/ArcFormats/Leaf/ImagePX.cs new file mode 100644 index 00000000..16f114c0 --- /dev/null +++ b/ArcFormats/Leaf/ImagePX.cs @@ -0,0 +1,217 @@ +//! \file ImagePX.cs +//! \date Thu Aug 18 05:35:34 2016 +//! \brief Leaf image format. +// +// Copyright (C) 2016 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.Leaf +{ + internal class PxMetaData : ImageMetaData + { + public int Type; + public int FrameCount; + public int BlockSize; + public int BlocksWidth; + public int BlocksHeight; + } + + // XXX this format changes significantly from game to game + + [Export(typeof(ImageFormat))] + public class PxFormat : ImageFormat + { + public override string Tag { get { return "PX"; } } + public override string Description { get { return "Leaf image format"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x20]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int type = LittleEndian.ToUInt16 (header, 0x10); + if (0x0C == type) + { + int count = LittleEndian.ToInt32 (header, 0); + if (!ArchiveFormat.IsSaneCount (count)) + return null; + int block_size = LittleEndian.ToInt32 (header, 4); + if (block_size <= 0) + return null; + int bpp = LittleEndian.ToUInt16 (header, 0x12); + uint width = LittleEndian.ToUInt16 (header, 0x14); + uint height = LittleEndian.ToUInt16 (header, 0x16); + if (bpp != 32 || 0 == width || 0 == height) + return null; + return new PxMetaData + { + Width = width, + Height = height, + BPP = bpp, + Type = type, + FrameCount = count, + BlockSize = block_size, + BlocksWidth = LittleEndian.ToUInt16 (header, 0x1C), + BlocksHeight = LittleEndian.ToUInt16 (header, 0x1E), + }; + } + else if (0x80 == type || 0x90 == type) + { + if (!Binary.AsciiEqual (header, 0x14, "Leaf")) + return null; + int count = LittleEndian.ToInt32 (header, 4); + if (!ArchiveFormat.IsSaneCount (count)) + return null; + if (0x20 != stream.Read (header, 0, 0x20)) + return null; + if (0x0A != LittleEndian.ToUInt16 (header, 0x10)) + return null; + return new PxMetaData + { + Width = LittleEndian.ToUInt32 (header, 0), + Height = LittleEndian.ToUInt32 (header, 0), + BPP = LittleEndian.ToUInt16 (header, 0x12), + Type = type, + FrameCount = count, + }; + } + return null; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + using (var reader = new PxReader (stream, (PxMetaData)info)) + { + var pixels = reader.Unpack(); + return ImageData.Create (info, reader.Format, null, pixels, reader.Stride); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PxFormat.Write not implemented"); + } + } + + internal sealed class PxReader : IDisposable + { + BinaryReader m_input; + PxMetaData m_info; + byte[] m_output; + int m_pixel_size; + int m_stride; + + public PixelFormat Format { get { return PixelFormats.Bgra32; } } + public byte[] Data { get { return m_output; } } + public int Stride { get { return m_stride; } } + public int FrameCount { get { return m_info.FrameCount; } } + + public PxReader (Stream input, PxMetaData info) + { + m_input = new ArcView.Reader (input); + m_info = info; + m_pixel_size = m_info.BPP / 8; + m_stride = (int)m_info.Width * m_pixel_size; + m_output = new byte[m_stride * (int)m_info.Height]; + } + + public byte[] Unpack (int frame = 0) + { + if (frame < 0 || frame >= FrameCount) + throw new ArgumentException ("[PX] Invalid frame number", "frame"); + switch (m_info.Type) + { + case 0x0C: Unpack0C (frame); break; + case 0x90: Unpack90 (frame); break; + case 0x80: Unpack80 (frame); break; + default: throw new NotImplementedException(); + } + return m_output; + } + + void Unpack0C (int frame) + { + var stream = m_input.BaseStream; + int block_count = m_info.BlocksWidth * m_info.BlocksHeight; + var block_table = new ushort[block_count]; + stream.Position = 0x20 + frame * block_count * 2; + for (int i = 0; i < block_count; ++i) + block_table[i] = m_input.ReadUInt16(); + int data_pos = 0x20 + FrameCount * block_count * 2; + int block_length = 2 + (m_info.BlockSize + 2) * (m_info.BlockSize + 2) * m_pixel_size; + int current_block = 0; + int dst_line = 0; + for (int by = 0; by < m_info.BlocksHeight; ++by) + { + for (int bx = 0; bx < m_info.BlocksWidth; ++bx) + { + int dst = dst_line + bx * m_info.BlockSize * m_pixel_size; + int block_num = block_table[current_block++]; + if (block_num != 0) + { + stream.Position = data_pos + (block_num - 1) * block_length; + int block_width = m_input.ReadByte() - 2; + int block_height = m_input.ReadByte() - 2; + int line_length = block_width * m_pixel_size; + for (int y = 0; y < block_height; ++y) + { + m_input.Read (m_output, dst, line_length); + dst += m_stride; + stream.Seek (8, SeekOrigin.Current); + } + } + } + dst_line += m_info.BlockSize * m_stride; + } + } + + void Unpack90 (int frame) + { + m_input.BaseStream.Position = 0x40 + frame * (0x20 + m_output.Length); + if (m_output.Length != m_input.Read (m_output, 0, m_output.Length)) + throw new EndOfStreamException(); + } + + void Unpack80 (int frame) + { + throw new NotImplementedException(); + } + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } +} diff --git a/supported.html b/supported.html index 91b39d69..d9f947d6 100644 --- a/supported.html +++ b/supported.html @@ -89,6 +89,7 @@ Utatemeguri
*.pak
*.datPACKDAT.NoSYSTEM-ε Aoiro Rinne
Cartagra
+Houkago no Senpai
PP -Pianissimo-
Ryoujoku Guerilla Gari 3
Nagomibako
@@ -1052,6 +1053,13 @@ Boku no Te no Naka no Rakuen
*.datYOXNoShelf Kagiroi ~Shaku Kei~
+*.lacLACNoLeaf +ToHeart2 AnotherDays
+ +*.a\x1E\xAFNo +*.amam00No +*.g-No +*.w-No

1 Non-encrypted only