From 5edbc9aa3fc4934e8e035e078cc19f7934706454 Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 21 Sep 2015 17:56:27 +0400 Subject: [PATCH] implemented ALF archives and AGF images. --- ArcFormats/ArcFormats.csproj | 3 + ArcFormats/Eushully/ArcALF.cs | 111 ++++++++++++++ ArcFormats/Eushully/ImageAGF.cs | 254 ++++++++++++++++++++++++++++++++ supported.html | 3 + 4 files changed, 371 insertions(+) create mode 100644 ArcFormats/Eushully/ArcALF.cs create mode 100644 ArcFormats/Eushully/ImageAGF.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 8013ea75..35c1df96 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -68,6 +68,8 @@ + + @@ -439,6 +441,7 @@ + perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName) diff --git a/ArcFormats/Eushully/ArcALF.cs b/ArcFormats/Eushully/ArcALF.cs new file mode 100644 index 00000000..2b5263c0 --- /dev/null +++ b/ArcFormats/Eushully/ArcALF.cs @@ -0,0 +1,111 @@ +//! \file ArcALF.cs +//! \date Sun Sep 20 13:58:52 2015 +//! \brief Eushully and its subsidiaries 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; +using GameRes.Compression; + +namespace GameRes.Formats.Eushully +{ + [Export(typeof(ArchiveFormat))] + public class AlfOpener : ArchiveFormat + { + public override string Tag { get { return "ALF"; } } + public override string Description { get { return "Eushully resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + string ini_path = VFS.CombinePath (Path.GetDirectoryName (file.Name), "sys4ini.bin"); + if (!VFS.FileExists (ini_path)) + return null; + + var dir = ReadIndex (ini_path, Path.GetFileName (file.Name)); + if (null == dir) + return null; + return new ArcFile (file, this, dir); + } + + Tuple>> LastAccessedIndex; + + List ReadIndex (string ini_file, string arc_name) + { + if (null == LastAccessedIndex + || !LastAccessedIndex.Item1.Equals (ini_file, StringComparison.InvariantCultureIgnoreCase)) + { + LastAccessedIndex = null; + using (var ini = VFS.OpenView (ini_file)) + { + if (!ini.View.AsciiEqual (0, "S4IC") && !ini.View.AsciiEqual (0, "S3IC")) + return null; + uint packed_size = ini.View.ReadUInt32 (0x134); + using (var packed = ini.CreateStream (0x138, packed_size)) + using (var unpacked = new LzssStream (packed)) + using (var index = new BinaryReader (unpacked)) + { + int arc_count = index.ReadInt32(); + var name_buf = new byte[0x100]; + var file_table = new Dictionary> (arc_count, StringComparer.InvariantCultureIgnoreCase); + var arc_list = new List> (arc_count); + for (int i = 0; i < arc_count; ++i) + { + index.Read (name_buf, 0, name_buf.Length); + var file_list = new List(); + file_table.Add (Binary.GetCString (name_buf, 0, name_buf.Length), file_list); + arc_list.Add (file_list); + } + int file_count = index.ReadInt32(); + for (int i = 0; i < file_count; ++i) + { + index.Read (name_buf, 0, 0x40); + int arc_id = index.ReadInt32(); + if (arc_id < 0 || arc_id >= arc_list.Count) + return null; + index.ReadInt32(); // file number + uint offset = index.ReadUInt32(); + uint size = index.ReadUInt32(); + var name = Binary.GetCString (name_buf, 0, 0x40); + if ("@" == name) + continue; + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = offset; + entry.Size = size; + arc_list[arc_id].Add (entry); + } + LastAccessedIndex = Tuple.Create (ini_file, file_table); + } + } + } + List dir = null; + LastAccessedIndex.Item2.TryGetValue (arc_name, out dir); + return dir; + } + } +} diff --git a/ArcFormats/Eushully/ImageAGF.cs b/ArcFormats/Eushully/ImageAGF.cs new file mode 100644 index 00000000..42af8085 --- /dev/null +++ b/ArcFormats/Eushully/ImageAGF.cs @@ -0,0 +1,254 @@ +//! \file ImageAGF.cs +//! \date Sun Sep 20 16:17:19 2015 +//! \brief Eushully image 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 GameRes.Compression; +using GameRes.Utility; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; + +namespace GameRes.Formats.Eushully +{ + internal class AgfMetaData : ImageMetaData + { + public int SourceBPP; + public uint DataOffset; + public Color[] Palette; + } + + [Export(typeof(ImageFormat))] + public class AgfFormat : ImageFormat + { + public override string Tag { get { return "AGF"; } } + public override string Description { get { return "Eushully image format"; } } + public override uint Signature { get { return 0x46474341; } } // 'ACGF' + + public AgfFormat () + { + Signatures = new uint[] { 0x46474341, 0 }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x20]; + if (0x18 != stream.Read (header, 0, 0x18)) + return null; + uint id = LittleEndian.ToUInt32 (header, 0); + if (Signature != id && 0 != id) + return null; + int type = LittleEndian.ToInt32 (header, 4); + if (type != 1 && type != 2) + return null; + uint unpacked_size = LittleEndian.ToUInt32 (header, 0x10); + uint packed_size = LittleEndian.ToUInt32 (header, 0x14); + Stream unpacked; + if (unpacked_size != packed_size) + unpacked = new LzssStream (stream, LzssMode.Decompress, true); + else + unpacked = new StreamRegion (stream, stream.Position, packed_size, true); + using (unpacked) + using (var reader = new BinaryReader (unpacked)) + { + if (0x20 != reader.Read (header, 0, 0x20)) + return null; + var info = new AgfMetaData + { + Width = LittleEndian.ToUInt32 (header, 0x14), + Height = LittleEndian.ToUInt32 (header, 0x18), + BPP = 1 == type ? 24 : 32, + SourceBPP = LittleEndian.ToInt16 (header, 0x1E), + DataOffset = 0x18 + packed_size, + }; + if (0 == info.SourceBPP) + return null; + if (8 == info.SourceBPP) + { + reader.Read (header, 0, 0x18); // skip rest of the header + info.Palette = ReadPalette (reader.BaseStream); + } + return info; + } + } + + static Color[] ReadPalette (Stream input) + { + var palette_data = new byte[0x400]; + if (0x400 != input.Read (palette_data, 0, 0x400)) + throw new EndOfStreamException(); + var palette = new Color[0x100]; + for (int i = 0; i < palette.Length; ++i) + { + palette[i] = Color.FromRgb (palette_data[i*4+2], palette_data[i*4+1], palette_data[i*4]); + } + return palette; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as AgfMetaData; + if (null == meta) + throw new ArgumentException ("AgfFormat.Read should be supplied with AgfMetaData", "info"); + + using (var reader = new AgfReader (stream, meta)) + { + reader.Unpack(); + return ImageData.Create (info, reader.Format, null, reader.Data); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("AgfFormat.Write not implemented"); + } + } + + internal sealed class AgfReader : IDisposable + { + BinaryReader m_input; + byte[] m_output; + int m_width; + int m_height; + int m_bpp; + int m_source_bpp; + Color[] m_palette; + + public PixelFormat Format { get; private set; } + public byte[] Data { get { return m_output; } } + + public AgfReader (Stream input, AgfMetaData info) + { + m_input = new ArcView.Reader (input); + m_bpp = info.BPP; + m_source_bpp = info.SourceBPP; + input.Position = info.DataOffset; + + m_width = (int)info.Width; + m_height = (int)info.Height; + m_output = new byte[m_height * m_width * m_bpp / 8]; + m_palette = info.Palette; + } + + public void Unpack () + { + m_input.ReadInt32(); + int data_size = m_input.ReadInt32(); + int packed_size = m_input.ReadInt32(); + var data_pos = m_input.BaseStream.Position; + var bmp_data = new byte[data_size]; + using (var unpacked = new LzssStream (m_input.BaseStream, LzssMode.Decompress, true)) + { + if (data_size != unpacked.Read (bmp_data, 0, data_size)) + throw new EndOfStreamException(); + } + byte[] alpha = null; + if (32 == m_bpp) + { + m_input.BaseStream.Position = data_pos + packed_size; + alpha = ReadAlphaChannel(); + if (null == alpha) + m_bpp = 24; + } + Format = 32 == m_bpp ? PixelFormats.Bgra32 : PixelFormats.Bgr24; + int src_pixel_size = m_source_bpp / 8; + int dst_pixel_size = m_bpp / 8; + int src_stride = (m_width * src_pixel_size + 3) & ~3; + int dst_stride = m_width * dst_pixel_size; + + int src_row = (m_height - 1) * src_stride; + int dst_row = 0; + int src_alpha = 0; + RowUnpacker repack_row = RepackRowTrue; + if (1 == src_pixel_size) + repack_row = RepackRow8; + while (src_row >= 0) + { + repack_row (bmp_data, src_row, src_pixel_size, dst_row, dst_pixel_size, alpha, src_alpha); + src_row -= src_stride; + dst_row += dst_stride; + src_alpha += m_width; + } + } + + delegate void RowUnpacker (byte[] bmp, int src, int src_pixel_size, int dst, int dst_pixel_size, byte[] alpha, int src_alpha); + + void RepackRow8 (byte[] bmp, int src, int src_pixel_size, int dst, int dst_pixel_size, byte[] alpha, int src_alpha) + { + for (int i = 0; i < m_width; ++i) + { + var color = m_palette[bmp[src++]]; + m_output[dst] = color.B; + m_output[dst+1] = color.G; + m_output[dst+2] = color.R; + if (null != alpha) + m_output[dst+3] = alpha[src_alpha++]; + dst += dst_pixel_size; + } + } + + void RepackRowTrue (byte[] bmp, int src, int src_pixel_size, int dst, int dst_pixel_size, byte[] alpha, int src_alpha) + { + for (int i = 0; i < m_width; ++i) + { + m_output[dst] = bmp[src]; + m_output[dst+1] = bmp[src+1]; + m_output[dst+2] = bmp[src+2]; + if (null != alpha) + m_output[dst+3] = alpha[src_alpha++]; + src += src_pixel_size; + dst += dst_pixel_size; + } + } + + byte[] ReadAlphaChannel () + { + var header = new byte[0x24]; + if (0x24 != m_input.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, 0, "ACIF")) + return null; + int unpacked_size = LittleEndian.ToInt32 (header, 0x1C); + if (m_width*m_height != unpacked_size) + return null; + var alpha = new byte[unpacked_size]; + using (var unpacked = new LzssStream (m_input.BaseStream, LzssMode.Decompress, true)) + if (unpacked_size != unpacked.Read (alpha, 0, unpacked_size)) + return null; + return alpha; + } + + #region IDisposable methods + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } +} diff --git a/supported.html b/supported.html index 46a0719e..51a6d510 100644 --- a/supported.html +++ b/supported.html @@ -387,6 +387,9 @@ Futamajo
*.pakPAKNoDebonosu Works Gigai no Alruna
+*.alf
sys4ini.binS4ICNoEushully +Soukai no Oujo-tachi
+

1 Non-encrypted only