diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 48223afa..5bba1f21 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -90,6 +90,9 @@ + + + diff --git a/ArcFormats/Foster/ArcC24.cs b/ArcFormats/Foster/ArcC24.cs new file mode 100644 index 00000000..b734c323 --- /dev/null +++ b/ArcFormats/Foster/ArcC24.cs @@ -0,0 +1,92 @@ +//! \file ArcC24.cs +//! \date Sun Feb 19 14:21:21 2017 +//! \brief Foster's game engine multi-frame image. +// +// Copyright (C) 2017 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.Foster +{ + [Export(typeof(ArchiveFormat))] + public class C24Opener : ArchiveFormat + { + public override string Tag { get { return "C24"; } } + public override string Description { get { return "Foster game engine multi-image"; } } + public override uint Signature { get { return 0x00343243; } } // 'C24' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + + var base_name = Path.GetFileNameWithoutExtension (file.Name); + var dir = new List (count); + uint index_offset = 8; + for (int i = 0; i < count; ++i) + { + uint offset = file.View.ReadUInt32 (index_offset); + index_offset += 4; + if (offset > 0 && offset <= file.MaxOffset) + { + var entry = new Entry + { + Name = string.Format ("{0}@{1:D4}", base_name, i), + Type = "image", + Offset = offset, + }; + dir.Add (entry); + } + } + dir.Sort ((a, b) => (int)(a.Offset - b.Offset)); + for (int i = 1; i < dir.Count; ++i) + { + dir[i-1].Size = (uint)(dir[i].Offset - dir[i-1].Offset); + } + var last_entry = dir[dir.Count-1]; + last_entry.Size = (uint)(file.MaxOffset - last_entry.Offset); + return new ArcFile (file, this, dir); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var offset = entry.Offset; + var info = new C24MetaData + { + Width = arc.File.View.ReadUInt32 (offset), + Height = arc.File.View.ReadUInt32 (offset+4), + OffsetX = arc.File.View.ReadInt32 (offset+8), + OffsetY = arc.File.View.ReadInt32 (offset+12), + BPP = 24, + DataOffset = (uint)(offset + 0x10), + }; + var input = arc.File.CreateStream (0, (uint)arc.File.MaxOffset); + return new C24Decoder (input, info); + } + } +} diff --git a/ArcFormats/Foster/ArcFA2.cs b/ArcFormats/Foster/ArcFA2.cs new file mode 100644 index 00000000..18c61878 --- /dev/null +++ b/ArcFormats/Foster/ArcFA2.cs @@ -0,0 +1,246 @@ +//! \file ArcFA2.cs +//! \date Sun Feb 19 10:46:57 2017 +//! \brief Foster game engine resource archive. +// +// Copyright (C) 2017 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.Foster +{ + [Export(typeof(ArchiveFormat))] + public class Fa2Opener : ArchiveFormat + { + public override string Tag { get { return "FA2"; } } + public override string Description { get { return "Foster game engine resource archive"; } } + public override uint Signature { get { return 0x00324146; } } // 'FA2' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0xC); + if (!IsSaneCount (count)) + return null; + bool is_packed = (file.View.ReadByte (4) & 1) != 0; + uint index_offset = file.View.ReadUInt32 (8); + byte[] index; + using (var input = file.CreateStream (index_offset)) + { + if (is_packed) + index = Decompress (input, (uint)count * 0x20); + else + index = file.View.ReadBytes (index_offset, (uint)(file.MaxOffset - index_offset)); + } + + uint data_offset = 0x10; + int index_pos = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = Binary.GetCString (index, index_pos, 0xF); + index_pos += 0xF; + var entry = FormatCatalog.Instance.Create (name); + entry.IsPacked = (index[index_pos] & 2) != 0; + entry.Offset = data_offset; + index_pos += 9; + entry.UnpackedSize = index.ToUInt32 (index_pos); + entry.Size = index.ToUInt32 (index_pos+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_pos += 8; + data_offset += (entry.Size + 0xFu) & ~0xFu; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked) + return input; + var data = Decompress (input, pent.UnpackedSize); + return new BinMemoryStream (data, entry.Name); + } + + byte[] Decompress (IBinaryStream input, uint unpacked_size) + { + var comp = new Fa2Compression (input, unpacked_size); + return comp.Unpack(); + } + } + + internal class Fa2Compression + { + IBinaryStream m_input; + byte[] m_output; + + public Fa2Compression (IBinaryStream input, uint unpacked_size) + { + m_input = input; + m_output = new byte[unpacked_size]; + } + + public byte[] Unpack () + { + m_bit_count = 0; + int dst = 0; + while (dst < m_output.Length) + { + if (GetNextBit() != 0) + { + m_output[dst++] = m_input.ReadUInt8(); + continue; + } + int offset; + if (GetNextBit() != 0) + { + if (GetNextBit() != 0) + { + offset = m_input.ReadUInt8() << 3; + offset |= GetBits (3); + offset += 0x100; + if (offset >= 0x8FF) + break; + } + else + { + offset = m_input.ReadUInt8(); + } + m_output[dst ] = m_output[dst-offset-1]; + m_output[dst+1] = m_output[dst-offset ]; + dst += 2; + } + else + { + if (GetNextBit() != 0) + { + offset = m_input.ReadUInt8() << 1; + offset |= GetNextBit(); + } + else + { + offset = 0x100; + if (GetNextBit() != 0) + { + offset |= m_input.ReadUInt8(); + offset <<= 1; + offset |= GetNextBit(); + } + else if (GetNextBit() != 0) + { + offset |= m_input.ReadUInt8(); + offset <<= 2; + offset |= GetBits (2); + } + else if (GetNextBit() != 0) + { + offset |= m_input.ReadUInt8(); + offset <<= 3; + offset |= GetBits (3); + } + else + { + offset |= m_input.ReadUInt8(); + offset <<= 4; + offset |= GetBits (4); + } + } + int count = 0; + if (GetNextBit() != 0) + { + count = 3; + } + else if (GetNextBit() != 0) + { + count = 4; + } + else if (GetNextBit() != 0) + { + count = 5 + GetNextBit(); + } + else if (GetNextBit() != 0) + { + count = 7 + GetBits (2); + } + else if (GetNextBit() != 0) + { + count = 11 + GetBits (4); + } + else + { + count = 27 + m_input.ReadUInt8(); + } + Binary.CopyOverlapped (m_output, dst - offset - 1, dst, count); + dst += count; + } + } + return m_output; + } + + uint m_bits; + int m_bit_count; + + void FetchBits () + { + m_bits = m_input.ReadUInt32(); + m_bit_count = 32; + } + + int GetNextBit () + { + if (0 == m_bit_count) + FetchBits(); + int bit = (int)((m_bits >> 31) & 1); + m_bits <<= 1; + --m_bit_count; + return bit; + } + + int GetBits (int count) + { + uint bits = 0; + int avail_bits = Math.Min (count, m_bit_count); + if (avail_bits > 0) + { + bits = m_bits >> (32 - avail_bits); + m_bits <<= avail_bits; + m_bit_count -= avail_bits; + count -= avail_bits; + } + if (count > 0) + { + FetchBits(); + bits = bits << count | m_bits >> (32 - count); + m_bits <<= count; + m_bit_count -= count; + } + return (int)bits; + } + } +} diff --git a/ArcFormats/Foster/ImageC24.cs b/ArcFormats/Foster/ImageC24.cs new file mode 100644 index 00000000..9695f09a --- /dev/null +++ b/ArcFormats/Foster/ImageC24.cs @@ -0,0 +1,162 @@ +//! \file ImageC24.cs +//! \date Sun Feb 19 13:13:16 2017 +//! \brief Foster game engine image format. +// +// Copyright (C) 2017 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.Windows.Media; + +namespace GameRes.Formats.Foster +{ + internal class C24MetaData : ImageMetaData + { + public uint DataOffset; + } + + /// + /// ShiinaRio S25 predecessor. + /// + [Export(typeof(ImageFormat))] + public class C24Format : ImageFormat + { + public override string Tag { get { return "C24"; } } + public override string Description { get { return "Foster game engine image format"; } } + public override uint Signature { get { return 0x00343243; } } // 'C24' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (12); + int count = header.ToInt32 (4); + if (count <= 0) + return null; + file.Position = header.ToUInt32 (8); + var info = new C24MetaData { BPP = 24 }; + info.Width = file.ReadUInt32(); + info.Height = file.ReadUInt32(); + info.OffsetX = file.ReadInt32(); + info.OffsetY = file.ReadInt32(); + info.DataOffset = (uint)file.Position; + return info; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + using (var reader = new C24Decoder (file, (C24MetaData)info, true)) + return reader.Image; + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("C24Format.Write not implemented"); + } + } + + internal sealed class C24Decoder : IImageDecoder + { + IBinaryStream m_input; + C24MetaData m_info; + byte[] m_output; + bool m_should_dispose; + ImageData m_image; + + public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } } + public ImageFormat SourceFormat { get { return null; } } + public ImageMetaData Info { get { return m_info; } } + + public ImageData Image + { + get + { + if (null == m_image) + { + var pixels = Unpack(); + m_image = ImageData.Create (m_info, PixelFormats.Bgr24, null, pixels); + } + return m_image; + } + } + + public C24Decoder (IBinaryStream file, C24MetaData info, bool leave_open = false) + { + m_input = file; + m_info = info; + m_output = new byte[3 * m_info.Width * m_info.Height]; + m_should_dispose = !leave_open; + } + + public byte[] Unpack () + { + m_input.Position = m_info.DataOffset; + var rows = new uint[m_info.Height]; + for (int i = 0; i < rows.Length; ++i) + rows[i] = m_input.ReadUInt32(); + int dst = 0; + int width = (int)m_info.Width; + foreach (uint row_offset in rows) + { + m_input.Position = row_offset; + bool rle = false; + for (int x = 0; x < width; ) + { + int count = m_input.ReadUInt8(); + if (!rle) + { + if (0xFF == count) + count = m_input.ReadUInt16(); + int byte_count = count * 3; + for (int i = 0; i < byte_count; ++i) + m_output[dst++] = 0xFF; + } + else + { + if (0 == count) + count = m_input.ReadUInt16(); + int byte_count = count * 3; + m_input.Read (m_output, dst, byte_count); + dst += byte_count; + } + x += count; + rle = !rle; + } + } + return m_output; + } + + #region IDisposable Members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + if (m_should_dispose) + { + m_input.Dispose(); + } + m_disposed = true; + } + System.GC.SuppressFinalize (this); + } + #endregion + } +}