diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 245cab82..a0d0e18b 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -88,6 +88,7 @@ + @@ -108,6 +109,7 @@ + @@ -154,6 +156,7 @@ + diff --git a/ArcFormats/ArcKaguya.cs b/ArcFormats/ArcKaguya.cs new file mode 100644 index 00000000..bff6db5f --- /dev/null +++ b/ArcFormats/ArcKaguya.cs @@ -0,0 +1,275 @@ +//! \file ArcKaguya.cs +//! \date Mon Jun 01 07:03:03 2015 +//! \brief KaGuYa archive format. +// +// 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.Kaguya +{ + internal class AriEntry : PackedEntry + { + public ushort Mode; + } + + [Export(typeof(ArchiveFormat))] + public class ArcOpener : ArchiveFormat + { + public override string Tag { get { return "ARI"; } } + public override string Description { get { return "KaGuYa script engine resource archive"; } } + public override uint Signature { get { return 0x314c4657; } } // 'WFL1' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public ArcOpener () + { + Extensions = new string[] { "arc" }; + } + + public override ArcFile TryOpen (ArcView file) + { + var reader = new IndexReader(); + var dir = reader.ReadIndex (file); + if (null == dir || 0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var packed_entry = entry as PackedEntry; + if (null == packed_entry || !packed_entry.IsPacked) + return arc.File.CreateStream (entry.Offset, entry.Size); + if (0 == packed_entry.UnpackedSize) + packed_entry.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset-4); + using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) + { + var reader = new LzReader (input, entry.Size, packed_entry.UnpackedSize); + reader.Unpack(); + return new MemoryStream (reader.Data); + } + } + } + + internal class IndexReader + { + byte[] m_name_buf = new byte[0x20]; + List m_dir = new List(); + + public List ReadIndex (ArcView file) + { + string ari_name = Path.ChangeExtension (file.Name, "ari"); + List dir = null; + if (File.Exists (ari_name)) + dir = ReadAriIndex (file, ari_name); + if (null == dir || 0 == dir.Count) + dir = BuildIndex (file); + return dir; + } + + List ReadAriIndex (ArcView file, string ari_name) + { + long arc_offset = 4; + using (var ari = new ArcView (ari_name)) + { + long index_offset = 0; + while (index_offset+4 < ari.MaxOffset) + { + int name_len = ari.View.ReadInt32 (index_offset); + var name = ReadName (ari, index_offset+4, name_len); + if (null == name) + return null; + var entry = new AriEntry { Name = name }; + index_offset += name_len + 4; + entry.Mode = ari.View.ReadUInt16 (index_offset); + entry.Size = ari.View.ReadUInt32 (index_offset+2); + entry.UnpackedSize = 0; + SetType (entry); + index_offset += 6; + arc_offset += name_len + 10; + if (1 == entry.Mode) + { + entry.IsPacked = true; + arc_offset += 4; + } + entry.Offset = arc_offset; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + arc_offset += entry.Size; + m_dir.Add (entry); + } + } + return m_dir; + } + + List BuildIndex (ArcView file) + { + long arc_offset = 4; + while (arc_offset+4 < file.MaxOffset) + { + int name_len = file.View.ReadInt32 (arc_offset); + var name = ReadName (file, arc_offset+4, name_len); + if (null == name) + return null; + var entry = new AriEntry { Name = name }; + arc_offset += name_len + 4; + entry.Mode = file.View.ReadUInt16 (arc_offset); + entry.Size = file.View.ReadUInt32 (arc_offset+2); + SetType (entry); + arc_offset += 6; + if (1 == entry.Mode) + { + entry.IsPacked = true; + entry.UnpackedSize = file.View.ReadUInt32 (arc_offset); + arc_offset += 4; + } + entry.Offset = arc_offset; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + arc_offset += entry.Size; + m_dir.Add (entry); + } + return m_dir; + } + + void SetType (AriEntry entry) + { + if (2 == entry.Mode) + entry.Type = "audio"; + else if (1 == entry.Mode) + entry.Type = "image"; + else + entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); + } + + string ReadName (ArcView file, long offset, int name_len) + { + if (name_len <= 0 || offset+name_len+6 > file.MaxOffset || name_len > 0x100) + return null; + if (name_len > m_name_buf.Length) + m_name_buf = new byte[name_len]; + file.View.Read (offset, m_name_buf, 0, (uint)name_len); + return DecryptName (m_name_buf, name_len); + } + + string DecryptName (byte[] name_buf, int name_len) + { + for (int i = 0; i < name_len; ++i) + name_buf[i] ^= 0xff; + return Encodings.cp932.GetString (name_buf, 0, name_len); + } + } + + internal class LzReader : IDataUnpacker + { + Stream m_input; + byte[] m_output; + uint m_size; + + public byte[] Data { get { return m_output; } } + + public LzReader (Stream input, uint packed_size, uint unpacked_size) + { + m_input = input; + m_output = new byte[unpacked_size]; + m_size = packed_size; + m_curbit = 8; + m_curbyte = 0; + } + + int m_curbit; + int m_curbyte; + + public void Unpack () + { + int dst = 0; + int frame_pos = 1; + byte[] frame = new byte[4096]; + + while (dst < m_output.Length) + { + int i; + if (0 != GetNextBit()) + { + int data = 0; + for (i = 0; i < 8; i++) + { + int bit = GetNextBit(); + if (-1 == bit) + return; + data = (data << 1) | bit; + } + m_output[dst++] = (byte)data; + frame[frame_pos++] = (byte)data; + frame_pos &= frame.Length - 1; + } + else + { + int count, win_offset = 0; + for (i = 0; i < 12; i++) + { + int bit = GetNextBit(); + if (-1 == bit) + return; + win_offset = (win_offset << 1) | bit; + } + if (0 == win_offset) + break; + + count = 0; + for (i = 0; i < 4; i++) + { + int bit = GetNextBit(); + if (-1 == bit) + return; + count = (count << 1) | bit; + } + count += 2; + for (i = 0; i < count; i++) + { + byte data = frame[(win_offset + i) & (frame.Length - 1)]; + m_output[dst++] = data; + frame[frame_pos++] = data; + frame_pos &= frame.Length - 1; + } + } + } + } + + int GetNextBit () + { + if (8 == m_curbit) + { + m_curbyte = m_input.ReadByte(); + if (m_curbyte < 0) + return -1; + m_curbit = 0; + } + return 0 == (m_curbyte & (1 << (7 - m_curbit++))) ? 0 : 1; + } + } +} diff --git a/ArcFormats/ArcSAF.cs b/ArcFormats/ArcSAF.cs new file mode 100644 index 00000000..b928198d --- /dev/null +++ b/ArcFormats/ArcSAF.cs @@ -0,0 +1,170 @@ +//! \file ArcSAF.cs +//! \date Mon Jun 01 03:09:22 2015 +//! \brief SAF archive file format implemenation. +// +// 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; +using ZLibNet; + +namespace GameRes.Formats.Lune +{ + [Export(typeof(ArchiveFormat))] + public class SafOpener : ArchiveFormat + { + public override string Tag { get { return "SAF"; } } + public override string Description { get { return "Lune resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + int id = file.View.ReadInt16 (0); + int count = file.View.ReadInt16 (2); + if (count <= 0) + return null; + var index_buffer = new byte[32 * count]; + if (index_buffer.Length != file.View.Read (4, index_buffer, 0, (uint)index_buffer.Length)) + return null; + if (0x501 == id) + DecryptIndex (index_buffer, count); + var reader = new IndexReader (index_buffer, count); + var dir = reader.Scan(); + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var packed_entry = entry as PackedEntry; + if (null == packed_entry || !packed_entry.IsPacked) + return input; + else + return new ZLibStream (input, CompressionMode.Decompress); + } + + void DecryptIndex (byte[] index, int count) + { + int offset = 0; + for (int i = 0; i < count; ++i) + { + byte key = 0xdf; + for (int j = 0; j < 0x20; ++j) + { + index[offset++] ^= key++; + } + } + } + + internal class IndexReader + { + byte[] m_index; + int m_count; + List m_dir; + + public List Dir { get { return m_dir; } } + + public IndexReader (byte[] index, int count) + { + m_index = index; + m_count = count; + m_dir = new List (count); + } + + bool m_ignore_dirs = false; + + public List Scan () + { + string root_name; + int root_offset; + int root_count; + if (0 == (m_index[0] & 0x80)) + { + root_name = ""; + root_offset = 0; + root_count = m_count; + m_ignore_dirs = true; + } + else + { + root_name = ReadName (0); + if ("root" == root_name) + { + root_name = ""; + } + root_offset = LittleEndian.ToInt32 (m_index, 0x14); + root_count = LittleEndian.ToInt32 (m_index, 0x1c); + } + ReadDir (root_name, root_offset, root_count); + return m_dir; + } + + void ReadDir (string dir_name, int index, int count) + { + if (index + count > m_count) + throw new InvalidFormatException(); + int index_offset = index * 0x20; + for (int i = 0; i < count; ++i, index_offset += 0x20) + { + if (m_index[index_offset] > 0x7f) + { + if (m_ignore_dirs) + continue; + int subdir_index = LittleEndian.ToInt32 (m_index, index_offset+0x14); + if (subdir_index < index + count) + continue; + var subdir_name = ReadName (index_offset); + int subdir_count = LittleEndian.ToInt32 (m_index, index_offset+0x1c); + ReadDir (Path.Combine (dir_name, subdir_name), subdir_index, subdir_count); + } + else + { + var name = ReadName (index_offset); + name = Path.Combine (dir_name, name); + var entry = new PackedEntry + { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + Offset = 0x800L * LittleEndian.ToUInt32 (m_index, index_offset+0x14), + Size = LittleEndian.ToUInt32 (m_index, index_offset+0x18), + UnpackedSize = LittleEndian.ToUInt32 (m_index, index_offset+0x1c), + }; + entry.IsPacked = entry.UnpackedSize != 0; + m_dir.Add (entry); + } + } + } + + string ReadName (int offset) + { + m_index[offset] &= 0x7f; + string name = Encodings.cp932.GetString (m_index, offset, 0x14); + return name.TrimEnd(); + } + } + } +} diff --git a/ArcFormats/ImageAP.cs b/ArcFormats/ImageAP.cs new file mode 100644 index 00000000..897a7f17 --- /dev/null +++ b/ArcFormats/ImageAP.cs @@ -0,0 +1,107 @@ +//! \file ImageAP.cs +//! \date Mon Jun 01 09:22:41 2015 +//! \brief KaGuYa script engine bitmap format. +// +// 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; +using System.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Kaguya +{ + [Export(typeof(ImageFormat))] + public class ApFormat : ImageFormat + { + public override string Tag { get { return "AP"; } } + public override string Description { get { return "KaGuYa script engine image format"; } } + public override uint Signature { get { return 0; } } + + public ApFormat () + { + Extensions = new string[] { "bg_", "cg_", "cgw", "sp_", "aps", "alp" }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + int A = stream.ReadByte(); + int P = stream.ReadByte(); + if ('A' != A || 'P' != P) + return null; + using (var file = new ArcView.Reader (stream)) + { + var info = new ImageMetaData(); + info.Width = file.ReadUInt32(); + info.Height = file.ReadUInt32(); + info.BPP = file.ReadInt16(); + if (info.Width > 0x8000 || info.Height > 0x8000 || !(32 == info.BPP || 24 == info.BPP)) + return null; + return info; + } + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + stream.Position = 12; + int stride = (int)info.Width*4; + var pixels = new byte[stride*info.Height]; + for (int row = (int)info.Height-1; row >= 0; --row) + { + if (stride != stream.Read (pixels, row*stride, stride)) + throw new InvalidFormatException(); + } + PixelFormat format = PixelFormats.Bgra32; + return ImageData.Create (info, format, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + using (var output = new BinaryWriter (file, Encoding.ASCII, true)) + { + output.Write ((byte)'A'); + output.Write ((byte)'P'); + output.Write (image.Width); + output.Write (image.Height); + output.Write ((short)24); + + var bitmap = image.Bitmap; + if (bitmap.Format != PixelFormats.Bgra32) + { + bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgra32, null, 0); + } + int stride = (int)image.Width * 4; + byte[] row_data = new byte[stride]; + Int32Rect rect = new Int32Rect (0, (int)image.Height, (int)image.Width, 1); + for (uint row = 0; row < image.Height; ++row) + { + --rect.Y; + bitmap.CopyPixels (rect, row_data, stride, 0); + output.Write (row_data); + } + } + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 80106624..f7d9a5e5 100644 --- a/ArcFormats/Properties/AssemblyInfo.cs +++ b/ArcFormats/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.5.55")] -[assembly: AssemblyFileVersion ("1.0.5.55")] +[assembly: AssemblyVersion ("1.0.5.56")] +[assembly: AssemblyFileVersion ("1.0.5.56")] diff --git a/supported.html b/supported.html index dc3a08d9..488e6440 100644 --- a/supported.html +++ b/supported.html @@ -173,9 +173,15 @@ Mainichi Shabutte Ii Desu ka?
*.datSPackNoGoku-FeroInchuu Reiki Elenova *.fpk-NoCandy SoftJii -Nozoki no Houshuu- *.noa
*.datEntis\x1aNoEntis GLS -Yatohime Zankikou - -*.eriEntis\x1aNo +Konneko
+Yatohime Zankikou
+ +*.eri
*.mioEntis\x1aNo +*.saf-NoLuneRinkan Gakuen +*.arc+*.ariWFL1NoKaGuYa +Onna Kyoushi + +*.bg_
*.cg_APYes

[1] Non-encrypted only