diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index e4562467..138fef26 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -98,6 +98,8 @@
+
+
diff --git a/ArcFormats/Kaguya/ArcANM.cs b/ArcFormats/Kaguya/ArcANM.cs
new file mode 100644
index 00000000..e7a04ea5
--- /dev/null
+++ b/ArcFormats/Kaguya/ArcANM.cs
@@ -0,0 +1,111 @@
+//! \file ArcANM.cs
+//! \date Sat Jan 23 04:23:39 2016
+//! \brief KaGuYa script engine animation resource.
+//
+// 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;
+
+namespace GameRes.Formats.Kaguya
+{
+ internal class AnmArchive : ArcFile
+ {
+ public readonly ImageMetaData ImageInfo;
+
+ public AnmArchive (ArcView arc, ArchiveFormat impl, ICollection dir, ImageMetaData base_info)
+ : base (arc, impl, dir)
+ {
+ ImageInfo = base_info;
+ }
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class AnmOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "ANM/KAGUYA"; } }
+ public override string Description { get { return "KaGuYa script engine animation resource"; } }
+ public override uint Signature { get { return 0x30304E41; } } // 'AN00'
+ public override bool IsHierarchic { get { return false; } }
+ public override bool CanCreate { get { return false; } }
+
+ public AnmOpener ()
+ {
+ Extensions = new string[] { "anm" };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ int frame_count = file.View.ReadInt16 (0x14);
+ uint current_offset = 0x18 + (uint)frame_count * 4;
+ int count = file.View.ReadInt16 (current_offset);
+ if (!IsSaneCount (count))
+ return null;
+ var base_info = new ImageMetaData
+ {
+ OffsetX = file.View.ReadInt32 (4),
+ OffsetY = file.View.ReadInt32 (8),
+ Width = file.View.ReadUInt32 (0x0C),
+ Height = file.View.ReadUInt32 (0x10),
+ BPP = 32,
+ };
+ current_offset += 2;
+ string base_name = Path.GetFileNameWithoutExtension (file.Name);
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ uint width = file.View.ReadUInt32 (current_offset+8);
+ uint height = file.View.ReadUInt32 (current_offset+12);
+ var entry = new Entry
+ {
+ Name = string.Format ("{0}#{1:D2}.tga", base_name, i),
+ Type = "image",
+ Offset = current_offset,
+ Size = 0x10 + 4*width*height,
+ };
+ dir.Add (entry);
+ current_offset += entry.Size;
+ }
+ return new AnmArchive (file, this, dir, base_info);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var base_info = ((AnmArchive)arc).ImageInfo;
+ // emulate TGA image
+ var offset = entry.Offset;
+ var info = new ImageMetaData
+ {
+ OffsetX = base_info.OffsetX + arc.File.View.ReadInt32 (offset),
+ OffsetY = base_info.OffsetY + arc.File.View.ReadInt32 (offset+4),
+ Width = arc.File.View.ReadUInt32 (offset+8),
+ Height = arc.File.View.ReadUInt32 (offset+12),
+ BPP = 32,
+ };
+ offset += 0x10;
+ var pixels = arc.File.View.ReadBytes (offset, 4*info.Width*info.Height);
+ return TgaStream.Create (info, pixels, true);
+ }
+ }
+}
diff --git a/ArcFormats/Kaguya/ArcLINK.cs b/ArcFormats/Kaguya/ArcLINK.cs
new file mode 100644
index 00000000..8002318a
--- /dev/null
+++ b/ArcFormats/Kaguya/ArcLINK.cs
@@ -0,0 +1,252 @@
+//! \file ArcLINK.cs
+//! \date Fri Jan 22 18:44:56 2016
+//! \brief KaGuYa archive 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;
+
+namespace GameRes.Formats.Kaguya
+{
+ [Export(typeof(ArchiveFormat))]
+ public class PakOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "LINK/KAGUYA"; } }
+ public override string Description { get { return "KaGuYa script engine resource archive"; } }
+ public override uint Signature { get { return 0x4B4E494C; } } // 'LINK'
+ public override bool IsHierarchic { get { return false; } }
+ public override bool CanCreate { get { return false; } }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ int version = file.View.ReadByte (4) - '0';
+ if (version != 3)
+ return null;
+
+ long current_offset = 8;
+ var dir = new List();
+ while (current_offset+4 < file.MaxOffset)
+ {
+ uint size = file.View.ReadUInt32 (current_offset);
+ if (0 == size)
+ break;
+ if (size < 0x10)
+ return null;
+ bool is_compressed = file.View.ReadInt32 (current_offset+4) != 0;
+ uint name_length = file.View.ReadByte (current_offset+0xD);
+ var name = file.View.ReadString (current_offset+0x10, name_length);
+ current_offset += 0x10 + name_length;
+ var entry = FormatCatalog.Instance.Create (name);
+ entry.Offset = current_offset;
+ entry.Size = size - (0x10 + name_length);
+ entry.IsPacked = is_compressed;
+ dir.Add (entry);
+ current_offset += entry.Size;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var pent = entry as PackedEntry;
+ if (null == pent || !pent.IsPacked || !arc.File.View.AsciiEqual (entry.Offset, "BMR"))
+ return base.OpenEntry (arc, entry);
+ using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
+ using (var bmr = new BmrDecoder (input))
+ {
+ bmr.Unpack();
+ return new MemoryStream (bmr.Data);
+ }
+ }
+ }
+
+ internal class BmrDecoder : IDisposable
+ {
+ byte[] m_output;
+ MsbBitStream m_input;
+ int m_final_size;
+ int m_step;
+ int m_key;
+
+ public byte[] Data { get { return m_output; } }
+
+ public BmrDecoder (Stream input)
+ {
+ input.Position = 3;
+ using (var header = new ArcView.Reader (input))
+ {
+ m_step = header.ReadByte();
+ m_final_size = header.ReadInt32();
+ m_key = header.ReadInt32();
+ int unpacked_size = header.ReadInt32();
+ m_output = new byte[unpacked_size];
+ m_input = new MsbBitStream (input, true);
+ }
+ }
+
+ public void Unpack ()
+ {
+ m_input.Input.Position = 0x14;
+ UnpackHuffman();
+ DescrambleOutput();
+ m_output = Decode (m_output, m_key);
+ if (m_step != 0)
+ m_output = DecompressRLE();
+ }
+
+ byte[] DecompressRLE ()
+ {
+ var result = new byte[m_final_size];
+ int src = 0;
+ for (int i = 0; i < m_step; ++i)
+ {
+ byte v1 = m_output[src++];
+ result[i] = v1;
+ int dst = i + m_step;
+ while (dst < result.Length)
+ {
+ byte v2 = m_output[src++];
+ result[dst] = v2;
+ dst += m_step;
+ if (v2 == v1)
+ {
+ int count = m_output[src++];
+ if (0 != (count & 0x80))
+ count = m_output[src++] + ((count & 0x7F) << 8) + 128;
+ while (count --> 0)
+ {
+ result[dst] = v2;
+ dst += m_step;
+ }
+ if (dst < m_output.Length)
+ {
+ v2 = m_output[src++];
+ result[dst] = v2;
+ dst += m_step;
+ }
+ }
+ v1 = v2;
+ }
+ }
+ return result;
+ }
+
+
+ void DescrambleOutput ()
+ {
+ var scramble = new byte[256];
+ for (int i = 0; i < 256; ++i)
+ scramble[i] = (byte)i;
+ for (int i = 0; i < m_output.Length; ++i)
+ {
+ byte v = m_output[i];
+ m_output[i] = scramble[v];
+ for (int j = v; j > 0; --j)
+ {
+ scramble[j] = scramble[j-1];
+ }
+ scramble[0] = m_output[i];
+ }
+ }
+
+ byte[] Decode (byte[] input, int key)
+ {
+ var freq_table = new int[256];
+ for (int i = 0; i < input.Length; ++i)
+ {
+ ++freq_table[input[i]];
+ }
+ for (int i = 1; i < 256; ++i)
+ {
+ freq_table[i] += freq_table[i-1];
+ }
+ var distrib_table = new int[input.Length];
+ for (int i = input.Length-1; i >= 0; --i)
+ {
+ int v = input[i];
+ int freq = freq_table[v] - 1;
+ freq_table[v] = freq;
+ distrib_table[freq] = i;
+ }
+ int pos = key;
+ var copy_out = new byte[input.Length];
+ for (int i = 0; i < copy_out.Length; ++i)
+ {
+ pos = distrib_table[pos];
+ copy_out[i] = input[pos];
+ }
+ return copy_out;
+ }
+
+ ushort m_token;
+ ushort[,] m_tree = new ushort[2,256];
+
+ void UnpackHuffman ()
+ {
+ m_token = 256;
+ ushort root = CreateHuffmanTree();
+ int dst = 0;
+ while (dst < m_output.Length)
+ {
+ ushort symbol = root;
+ while (symbol >= 0x100)
+ {
+ int bit = m_input.GetNextBit();
+ if (-1 == bit)
+ throw new EndOfStreamException();
+ symbol = m_tree[bit,symbol-256];
+ }
+ m_output[dst++] = (byte)symbol;
+ }
+ }
+
+ ushort CreateHuffmanTree ()
+ {
+ if (0 != m_input.GetNextBit())
+ {
+ ushort v = m_token++;
+ m_tree[0,v-256] = CreateHuffmanTree();
+ m_tree[1,v-256] = CreateHuffmanTree();
+ return v;
+ }
+ else
+ {
+ return (ushort)m_input.GetBits (8);
+ }
+ }
+
+ #region IDisposable Members
+ bool _disposed = false;
+ public void Dispose ()
+ {
+ if (!_disposed)
+ {
+ m_input.Dispose();
+ _disposed = true;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/ArcFormats/Kaguya/ImageAP.cs b/ArcFormats/Kaguya/ImageAP.cs
index 736f55c8..4253f021 100644
--- a/ArcFormats/Kaguya/ImageAP.cs
+++ b/ArcFormats/Kaguya/ImageAP.cs
@@ -112,4 +112,48 @@ namespace GameRes.Formats.Kaguya
}
}
}
+
+ [Export(typeof(ImageFormat))]
+ public class Ap2Format : ImageFormat
+ {
+ public override string Tag { get { return "AP-2"; } }
+ public override string Description { get { return "KaGuYa script engine image format"; } }
+ public override uint Signature { get { return 0x322D5041; } } // 'AP-2'
+
+ public Ap2Format ()
+ {
+ Extensions = new string[] { "alp" };
+ }
+
+ public override ImageMetaData ReadMetaData (Stream stream)
+ {
+ stream.Position = 4;
+ using (var file = new ArcView.Reader (stream))
+ {
+ var info = new ImageMetaData();
+ info.OffsetX = file.ReadInt32();
+ info.OffsetY = file.ReadInt32();
+ info.Width = file.ReadUInt32();
+ info.Height = file.ReadUInt32();
+ info.BPP = 32;
+ if (info.Width > 0x8000 || info.Height > 0x8000)
+ return null;
+ return info;
+ }
+ }
+
+ public override ImageData Read (Stream stream, ImageMetaData info)
+ {
+ stream.Position = 0x18;
+ var pixels = new byte[4*info.Width*info.Height];
+ if (pixels.Length != stream.Read (pixels, 0, pixels.Length))
+ throw new EndOfStreamException();
+ return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, pixels, 4*(int)info.Width);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Ap2Format.Write not implemented");
+ }
+ }
}
diff --git a/supported.html b/supported.html
index 7f6cd349..70010012 100644
--- a/supported.html
+++ b/supported.html
@@ -631,6 +631,11 @@ Eien no Aselia -The Spirit of Eternity Sword-
*.dat | DAF2 | No | DenSDK |
Ayakashi H
|
+*.arc | LINK3 | No | KaGuYa |
+Dokidoki Onee-san
+ |
+*.alp | AP-2 | No |
+*.anm | AN00 | No |
Non-encrypted only