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 *.dat
PACKDAT.
No
SYSTEM-ε
Aoiro Rinne
Cartagra
+Houkago no Senpai
PP -Pianissimo-
Ryoujoku Guerilla Gari 3
Nagomibako
@@ -1052,6 +1053,13 @@ Boku no Te no Naka no Rakuen