From f901f8252fd5c887981c8c288864a4b00cfb6146 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 24 Oct 2015 15:54:07 +0400 Subject: [PATCH] implemented IAR and WAR archives. --- ArcFormats/ArcFormats.csproj | 4 + ArcFormats/Sas5/ArcIAR.cs | 364 +++++++++++++++++++++++++++++++++++ ArcFormats/Sas5/ArcSec5.cs | 173 +++++++++++++++++ ArcFormats/Sas5/ArcWAR.cs | 135 +++++++++++++ ArcFormats/Sas5/ImageIAR.cs | 134 +++++++++++++ supported.html | 9 + 6 files changed, 819 insertions(+) create mode 100644 ArcFormats/Sas5/ArcIAR.cs create mode 100644 ArcFormats/Sas5/ArcSec5.cs create mode 100644 ArcFormats/Sas5/ArcWAR.cs create mode 100644 ArcFormats/Sas5/ImageIAR.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 96ad1485..2140d823 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -70,6 +70,10 @@ + + + + diff --git a/ArcFormats/Sas5/ArcIAR.cs b/ArcFormats/Sas5/ArcIAR.cs new file mode 100644 index 00000000..f1394054 --- /dev/null +++ b/ArcFormats/Sas5/ArcIAR.cs @@ -0,0 +1,364 @@ +//! \file ArcIAR.cs +//! \date Fri Oct 23 12:31:15 2015 +//! \brief Sas5 engine image archive. +// +// 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 System.Text; + +namespace GameRes.Formats.Sas5 +{ + internal class IarArchive : ArcFile + { + public readonly int Version; + + public IarArchive (ArcView arc, ArchiveFormat impl, ICollection dir, int version) + : base (arc, impl, dir) + { + Version = version; + } + } + + internal class IarImageInfo : ImageMetaData + { + public int Flags; + public bool Compressed; + public uint PaletteSize; + public int PackedSize; + public int UnpackedSize; + public int Stride; + } + + [Export(typeof(ArchiveFormat))] + public class IarOpener : ArchiveFormat + { + public override string Tag { get { return "IAR"; } } + public override string Description { get { return "SAS5 engine images archive"; } } + public override uint Signature { get { return 0x20726169; } } // 'iar ' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + int version = file.View.ReadInt16 (4); + if (version < 1 || version > 4) + return null; + int file_count = file.View.ReadInt32 (0x18); + int count = file.View.ReadInt32 (0x1C); + if (count < file_count || !IsSaneCount (count)) + return null; + + var index = Sec5Opener.LookupIndex (file.Name); + string base_name = Path.GetFileNameWithoutExtension (file.Name); + Func CreateEntry; + if (null == index) + CreateEntry = n => GetDefaultEntry (base_name, n); + else + CreateEntry = (n) => { + Entry entry; + if (index.TryGetValue (n, out entry)) + return new Entry { Name = entry.Name, Type = entry.Type }; + return GetDefaultEntry (base_name, n); + }; + + uint offset_size = version < 3 ? 4u : 8u; + Func ReadOffset; + if (version < 3) + ReadOffset = x => file.View.ReadUInt32 (x); + else + ReadOffset = x => file.View.ReadInt64 (x); + + uint index_offset = 0x20; + var dir = new List (count); + var next_offset = ReadOffset (index_offset); + for (int i = 0; i < count; ++i) + { + var entry = CreateEntry (i); + entry.Offset = next_offset; + index_offset += offset_size; + next_offset = (i + 1) == count ? file.MaxOffset : ReadOffset (index_offset); + entry.Size = (uint)(next_offset - entry.Offset); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new IarArchive (file, this, dir, version); + } + + static Entry GetDefaultEntry (string base_name, int n) + { + return new Entry { Name = string.Format ("{0}#{1:D5}", base_name, n) }; + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var iarc = arc as IarArchive; + if (null == iarc) + return base.OpenEntry (arc, entry); + int flags = arc.File.View.ReadUInt16 (entry.Offset); + if (0 != (flags & 0x1000)) + return base.OpenEntry (arc, entry); + + using (var image = new IarImage (iarc, entry)) + { + byte[] pixels = image.Data; + if (0 != (flags & 0x800)) + pixels = CombineImage (pixels, image.Info, iarc); + + // internal 'IAR SAS5' format + var header = new byte[0x28+image.Info.PaletteSize]; + using (var mem = new MemoryStream (header)) + using (var writer = new BinaryWriter (mem)) + { + writer.Write (0x00524149); // 'IAR' + writer.Write (0x35534153); // 'SAS5' + writer.Write (image.Info.Width); + writer.Write (image.Info.Height); + writer.Write (image.Info.OffsetX); + writer.Write (image.Info.OffsetY); + writer.Write (image.Info.BPP); + writer.Write (image.Info.Stride); + writer.Write (image.Info.PaletteSize); + writer.Write (pixels.Length); + if (null != image.Palette) + writer.Write (image.Palette, 0, image.Palette.Length); + return new PrefixStream (header, new MemoryStream (pixels)); + } + } + } + + byte[] CombineImage (byte[] overlay, IarImageInfo info, IarArchive iarc) + { + using (var mem = new MemoryStream (overlay)) + using (var input = new BinaryReader (mem)) + { + var dir = (List)iarc.Dir; + int base_index = input.ReadInt32(); + if (base_index >= dir.Count) + throw new InvalidFormatException ("Invalid base image index"); + int diff_y = input.ReadInt32(); + int diff_count = input.ReadInt32(); + using (var base_image = new IarImage (iarc, dir[base_index])) + { + int base_y = (int)base_image.Info.Height - (int)info.Height; + byte[] output = base_image.Data; + if (base_y != 0 || info.Stride != base_image.Info.Stride) + { + byte[] src = base_image.Data; + int base_stride = Math.Min (info.Stride, base_image.Info.Stride); + output = new byte[info.Height * info.Stride]; + for (int y = base_y; y < base_image.Info.Height; ++y) + { + Buffer.BlockCopy (src, y * base_image.Info.Stride, + output, (y - base_y) * info.Stride, base_stride); + } + } + int pixel_size = info.BPP / 8; + int dst = diff_y * info.Stride; + for (int i = 0; i < diff_count; ++i) + { + int chunk_count = input.ReadUInt16(); + int x = 0; + for (int j = 0; j < chunk_count; ++j) + { + int skip_count = pixel_size * input.ReadUInt16(); + int copy_count = pixel_size * input.ReadUInt16(); + + x += skip_count; + input.Read (output, dst+x, copy_count); + x += copy_count; + } + dst += info.Stride; + } + return output; + } + } + } + } + + internal sealed class IarImage : IDisposable + { + BinaryReader m_input; + IarImageInfo m_info; + byte[] m_palette; + byte[] m_output; + + public IarImageInfo Info { get { return m_info; } } + public byte[] Palette { get { return m_palette; } } + public byte[] Data { get { return m_output; } } + + public IarImage (IarArchive iarc, Entry entry) + { + int flags = iarc.File.View.ReadUInt16 (entry.Offset); + int bpp; + switch (flags & 0x3E) + { + case 0x02: bpp = 8; break; + case 0x1C: bpp = 24; break; + case 0x3C: bpp = 32; break; + default: throw new NotSupportedException ("Not supported IAR image format"); + } + var offset = entry.Offset; + m_info = new IarImageInfo + { + Flags = flags, + BPP = bpp, + Compressed = iarc.File.View.ReadByte (offset+3) != 0, + Width = iarc.File.View.ReadUInt32 (offset+0x20), + Height = iarc.File.View.ReadUInt32 (offset+0x24), + Stride = iarc.File.View.ReadInt32 (offset+0x28), + OffsetX = iarc.File.View.ReadInt32 (offset+0x18), + OffsetY = iarc.File.View.ReadInt32 (offset+0x1C), + UnpackedSize = iarc.File.View.ReadInt32 (offset+8), + PaletteSize = iarc.File.View.ReadUInt32 (offset+0xC), + PackedSize = iarc.File.View.ReadInt32 (offset+0x10), + }; + uint header_size = 1 == iarc.Version ? 0x30u : iarc.Version < 4 ? 0x40u : 0x48u; + offset += header_size; + uint input_size = entry.Size - header_size; + + if (m_info.PaletteSize > 0) + { + m_palette = new byte[m_info.PaletteSize]; + iarc.File.View.Read (offset, m_palette, 0, m_info.PaletteSize); + offset += m_info.PaletteSize; + input_size -= m_info.PaletteSize; + } + var input = iarc.File.CreateStream (offset, input_size); + m_input = new BinaryReader (input); + m_output = new byte[m_info.UnpackedSize]; + if (!m_info.Compressed) + m_input.Read (m_output, 0, m_output.Length); + else + Unpack(); + } + + void Unpack () + { + m_bits = 1; + int dst = 0; + while (dst < m_output.Length) + { + if (1 == GetNextBit()) + { + m_output[dst++] = m_input.ReadByte(); + continue; + } + int offset, count; + if (1 == GetNextBit()) + { + int tmp = GetNextBit(); + if (1 == GetNextBit()) + offset = 1; + else if (1 == GetNextBit()) + offset = 0x201; + else + { + tmp = (tmp << 1) | GetNextBit(); + if (1 == GetNextBit()) + offset = 0x401; + else + { + tmp = (tmp << 1) | GetNextBit(); + if (1 == GetNextBit()) + offset = 0x801; + else + { + offset = 0x1001; + tmp = (tmp << 1) | GetNextBit(); + } + } + } + offset += (tmp << 8) | m_input.ReadByte(); + if (1 == GetNextBit()) + count = 3; + else if (1 == GetNextBit()) + count = 4; + else if (1 == GetNextBit()) + count = 5; + else if (1 == GetNextBit()) + count = 6; + else if (1 == GetNextBit()) + count = 7 + GetNextBit(); + else if (1 == GetNextBit()) + count = 17 + m_input.ReadByte(); + else + { + count = GetNextBit() << 2; + count |= GetNextBit() << 1; + count |= GetNextBit(); + count += 9; + } + } + else + { + count = 2; + if (1 == GetNextBit()) + { + offset = GetNextBit() << 10; + offset |= GetNextBit() << 9; + offset |= GetNextBit() << 8; + offset = (offset | m_input.ReadByte()) + 0x100; + } + else + { + offset = 1 + m_input.ReadByte(); + if (0x100 == offset) + break; + } + } + Binary.CopyOverlapped (m_output, dst - offset, dst, count); + dst += count; + } + } + + int m_bits = 1; + + int GetNextBit () + { + if (1 == m_bits) + { + m_bits = m_input.ReadUInt16() | 0x10000; + } + int b = m_bits & 1; + m_bits >>= 1; + return b; + } + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } +} diff --git a/ArcFormats/Sas5/ArcSec5.cs b/ArcFormats/Sas5/ArcSec5.cs new file mode 100644 index 00000000..b554ec44 --- /dev/null +++ b/ArcFormats/Sas5/ArcSec5.cs @@ -0,0 +1,173 @@ +//! \file ArcSec5.cs +//! \date Fri Oct 23 18:10:06 2015 +//! \brief Sas5 engine resource index file. +// +// 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 System.Text; + +namespace GameRes.Formats.Sas5 +{ + [Export(typeof(ArchiveFormat))] + public class Sec5Opener : ArchiveFormat + { + public override string Tag { get { return "SEC5"; } } + public override string Description { get { return "SAS5 engine resource index file"; } } + public override uint Signature { get { return 0x35434553; } } // 'SEC5' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + uint offset = 8; + var dir = new List(); + while (offset < file.MaxOffset) + { + string name = file.View.ReadString (offset, 4, Encoding.ASCII); + if ("ENDS" == name) + break; + uint section_size = file.View.ReadUInt32 (offset+4); + offset += 8; + var entry = new Entry { + Name = name, + Offset = offset, + Size = section_size, + }; + dir.Add (entry); + offset += section_size; + } + if (dir.Count > 0) + return new ArcFile (file, this, dir); + return null; + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if ("CODE" != entry.Name) + return arc.File.CreateStream (entry.Offset, entry.Size); + + var code = new byte[entry.Size]; + arc.File.View.Read (entry.Offset, code, 0, entry.Size); + DecryptCodeSection (code); + return new MemoryStream (code); + } + + static void DecryptCodeSection (byte[] code) + { + byte key = 0; + for (int i = 0; i < code.Length; ++i) + { + int x = code[i] + 18; + code[i] ^= key; + key += (byte)x; + } + } + + static internal Dictionary> CurrentIndex; + + static internal Dictionary LookupIndex (string filename) + { + if (null == CurrentIndex) + CurrentIndex = FindSec5Resr (filename); + if (null == CurrentIndex) + return null; + Dictionary arc_map = null; + CurrentIndex.TryGetValue (Path.GetFileName (filename), out arc_map); + return arc_map; + } + + static internal Dictionary> FindSec5Resr (string arc_name) + { + string dir_name = Path.GetDirectoryName (arc_name); + var match = Directory.GetFiles (dir_name, "*.sec5"); + if (0 == match.Length) + { + string parent = Path.GetDirectoryName (dir_name); + if (!string.IsNullOrEmpty (parent)) + match = Directory.GetFiles (parent, "*.sec5"); + } + if (0 == match.Length) + return null; + using (var sec5 = new ArcView (match[0])) + { + if (!sec5.View.AsciiEqual (0, "SEC5")) + return null; + uint offset = 8; + while (offset < sec5.MaxOffset) + { + string id = sec5.View.ReadString (offset, 4, Encoding.ASCII); + if ("ENDS" == id) + break; + uint section_size = sec5.View.ReadUInt32 (offset+4); + offset += 8; + if ("RESR" == id) + { + using (var resr = sec5.CreateStream (offset, section_size)) + return ReadResrSection (resr); + } + offset += section_size; + } + } + return null; + } + + static internal Dictionary> ReadResrSection (Stream input) + { + using (var resr = new BinaryReader (input, Encodings.cp932, true)) + { + int count = resr.ReadInt32(); + if (0 == count) + return null; + var map = new Dictionary> (StringComparer.InvariantCultureIgnoreCase); + for (int i = 0; i < count; ++i) + { + string name = resr.BaseStream.ReadCString(); + string type = resr.BaseStream.ReadCString(); + string arc_type = resr.BaseStream.ReadCString(); + int res_length = resr.ReadInt32(); + var next_pos = resr.BaseStream.Position + res_length; + if (arc_type == "file-war" || arc_type == "file-iar") + { + string arc_name = resr.BaseStream.ReadCString(); + int id = resr.ReadInt32(); + var base_arc_name = Path.GetFileName (arc_name); + if (!map.ContainsKey (base_arc_name)) + map[base_arc_name] = new Dictionary(); + var entry = new Entry + { + Name = name, + Type = type, + }; + map[base_arc_name][id] = entry; + } + resr.BaseStream.Position = next_pos; + } + return map.Count > 0 ? map : null; + } + } + } +} diff --git a/ArcFormats/Sas5/ArcWAR.cs b/ArcFormats/Sas5/ArcWAR.cs new file mode 100644 index 00000000..00c96d34 --- /dev/null +++ b/ArcFormats/Sas5/ArcWAR.cs @@ -0,0 +1,135 @@ +//! \file ArcWAR.cs +//! \date Fri Oct 23 19:11:56 2015 +//! \brief Sas5 engine audio archive. +// +// 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.Sas5 +{ + internal class WarEntry : Entry + { + public int Format; + } + + [Export(typeof(ArchiveFormat))] + public class WarOpener : ArchiveFormat + { + public override string Tag { get { return "WAR/SAS5"; } } + public override string Description { get { return "SAS5 engine audio archive"; } } + public override uint Signature { get { return 0x20726177; } } // 'war ' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public WarOpener () + { + Extensions = new string[] { "war" }; + } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (8); + if (!IsSaneCount (count)) + return null; + uint entry_size = file.View.ReadUInt32 (12); + if (entry_size < 0x18) + return null; + + var index = Sec5Opener.LookupIndex (file.Name); + string base_name = Path.GetFileNameWithoutExtension (file.Name); + Func GetEntryName; + if (null == index) + GetEntryName = n => GetDefaultName (base_name, n); + else + GetEntryName = (n) => { + Entry entry; + if (index.TryGetValue (n, out entry)) + return entry.Name; + return GetDefaultName (base_name, n); + }; + + uint index_offset = 0x10; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new WarEntry { + Name = GetEntryName (i), + Offset = file.View.ReadUInt32 (index_offset), + Size = file.View.ReadUInt32 (index_offset+4), + Format = file.View.ReadByte (index_offset+0x14), + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + if (0 == entry.Format) + { + entry.Name = Path.ChangeExtension (entry.Name, "wav"); + entry.Type = "audio"; + } + else if (2 == entry.Format) + { + entry.Name = Path.ChangeExtension (entry.Name, "ogg"); + entry.Type = "audio"; + } + dir.Add (entry); + index_offset += entry_size; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var went = entry as WarEntry; + if (null == went || 0 != went.Format) + return arc.File.CreateStream (entry.Offset, entry.Size); + + uint fmt_size = arc.File.View.ReadUInt32 (entry.Offset); + uint data_size = arc.File.View.ReadUInt32 (entry.Offset+4); + var wav_header = new byte[8+12+fmt_size+8]; + uint total_size = (uint)wav_header.Length + data_size - 8; + arc.File.View.Read (entry.Offset+8, wav_header, 0x14, fmt_size); + using (var mem = new MemoryStream (wav_header)) + using (var buffer = new BinaryWriter (mem)) + { + buffer.Write (AudioFormat.Wav.Signature); + buffer.Write (total_size); + buffer.Write (0x45564157); // 'WAVE' + buffer.Write (0x20746d66); // 'fmt ' + buffer.Write (fmt_size); + buffer.BaseStream.Seek (fmt_size, SeekOrigin.Current); + buffer.Write (0x61746164); // 'data' + buffer.Write (data_size); + } + var pcm_data = arc.File.CreateStream (entry.Offset+8+fmt_size, entry.Size-8-fmt_size); + return new PrefixStream (wav_header, pcm_data); + } + + static string GetDefaultName (string base_name, int n) + { + return string.Format ("{0}#{1:D5}", base_name, n); + } + } +} diff --git a/ArcFormats/Sas5/ImageIAR.cs b/ArcFormats/Sas5/ImageIAR.cs new file mode 100644 index 00000000..fd0dde24 --- /dev/null +++ b/ArcFormats/Sas5/ImageIAR.cs @@ -0,0 +1,134 @@ +//! \file ImageIAR.cs +//! \date Fri Oct 23 15:17:52 2015 +//! \brief Intermediate image format representing IAR archives entries. +// +// 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.Utility; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Sas5 +{ + internal class IarMetaData : ImageMetaData + { + public int Stride; + public int PaletteSize; + public int ImageSize; + } + + // This is an artificial format, used by GARbro internally. structure: + // 0x0000 'IAR\x00' + // 0x0004 'SAS5' + // 0x0008 Width + // 0x000C Height + // 0x0010 OffsetX + // 0x0014 OffsetY + // 0x0018 BPP + // 0x001C Stride + // 0x0020 PaletteSize + // 0x0024 ImageSize + // 0x0028 Palette [if PaletteSize > 0] + // ...... Image data + + [Export(typeof(ImageFormat))] + public class IarFormat : ImageFormat + { + public override string Tag { get { return "IAR/IMAGE"; } } + public override string Description { get { return "SAS5 engine compressed image format"; } } + public override uint Signature { get { return 0x00524149; } } // 'IAR' + + public IarFormat () + { + Extensions = new string[] { "" }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x28]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, 4, "SAS5")) + return null; + return new IarMetaData + { + Width = LittleEndian.ToUInt32 (header, 0x08), + Height = LittleEndian.ToUInt32 (header, 0x0C), + OffsetX = LittleEndian.ToInt32 (header, 0x10), + OffsetY = LittleEndian.ToInt32 (header, 0x14), + BPP = LittleEndian.ToInt32 (header, 0x18), + Stride = LittleEndian.ToInt32 (header, 0x1C), + PaletteSize = LittleEndian.ToInt32 (header, 0x20), + ImageSize = LittleEndian.ToInt32 (header, 0x24), + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as IarMetaData; + if (null == meta) + throw new ArgumentException ("IarFormat.Read should be supplied with IarMetaData", "info"); + + PixelFormat format; + if (32 == meta.BPP) + format = PixelFormats.Bgra32; + else if (24 == meta.BPP) + format = PixelFormats.Bgr24; + else if (0 == meta.PaletteSize) + format = PixelFormats.Gray8; + else + format = PixelFormats.Indexed8; + + stream.Position = 0x28; + BitmapPalette palette = null; + if (meta.PaletteSize > 0) + palette = ReadPalette (stream, meta.PaletteSize); + var pixels = new byte[meta.ImageSize]; + stream.Read (pixels, 0, pixels.Length); + return ImageData.Create (info, format, palette, pixels, meta.Stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("IarFormat.Write not implemented"); + } + + static BitmapPalette ReadPalette (Stream input, int palette_size) + { + var palette_data = new byte[palette_size]; + if (palette_data.Length != input.Read (palette_data, 0, palette_data.Length)) + throw new EndOfStreamException(); + palette_size = Math.Min (0x400, palette_size); + int color_size = palette_size / 0x100; + var palette = new Color[0x100]; + for (int i = 0; i < palette.Length; ++i) + { + int c = i * color_size; + palette[i] = Color.FromRgb (palette_data[c+2], palette_data[c+1], palette_data[c]); + } + return new BitmapPalette (palette); + } + } +} diff --git a/supported.html b/supported.html index 78ef2a58..11569685 100644 --- a/supported.html +++ b/supported.html @@ -426,6 +426,15 @@ Blood Royal
Heroine
Promise
+*.arc-NoSilky's +Shitai o Arau
+ +*.g24-No +*.mskRmskNo +*.iar
*.wariar
warNoStudio Ryokucha +Katakoi no Tsuki
+Katakoi no Tsuki Extra
+

1 Non-encrypted only