From 557aff8fe45f1c6eacb028a9b6a9549beae42e48 Mon Sep 17 00:00:00 2001 From: morkt Date: Sun, 19 Jul 2015 15:30:16 +0400 Subject: [PATCH] implemented rare "Seraphim" engine formats. --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/ArcSeraph.cs | 160 +++++++++ ArcFormats/ImageSeraph.cs | 469 ++++++++++++++++++++++++++ ArcFormats/Properties/AssemblyInfo.cs | 4 +- 4 files changed, 633 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/ArcSeraph.cs create mode 100644 ArcFormats/ImageSeraph.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index c3ab6ab9..0e57c509 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -124,6 +124,7 @@ + @@ -219,6 +220,7 @@ + diff --git a/ArcFormats/ArcSeraph.cs b/ArcFormats/ArcSeraph.cs new file mode 100644 index 00000000..ce8592b0 --- /dev/null +++ b/ArcFormats/ArcSeraph.cs @@ -0,0 +1,160 @@ +//! \file ArcSeraph.cs +//! \date Fri Jul 17 13:47:34 2015 +//! \brief Seraphim engine 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 System.Text.RegularExpressions; +using ZLibNet; + +namespace GameRes.Formats.Seraphim +{ + internal class ArchPacEntry : Entry + { + } + + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag { get { return "DAT"; } } + public override string Description { get { return "Seraphim engine resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + public bool IsAmbiguous { get { return true; } } + + static readonly Regex VoiceRe = new Regex (@"^Voice\d\.dat$", RegexOptions.IgnoreCase); + + public override ArcFile TryOpen (ArcView file) + { + if (file.MaxOffset > uint.MaxValue) + return null; + string name = Path.GetFileName (file.Name); + if (name.Equals ("ArchPac.dat", StringComparison.InvariantCultureIgnoreCase)) + return OpenArchPac (file); + else if (VoiceRe.Match (name).Success) + return OpenVoice (file); +// else if (name.Equals ("ArchCash.dat", StringComparison.InvariantCultureIgnoreCase)) +// return OpenArchCash (file); + return null; + } + + const long ArchPacOffset = 0x0C23659F; + + private ArcFile OpenArchPac (ArcView file) + { + long index_offset = ArchPacOffset; + int base_count = file.View.ReadInt32 (index_offset); + int file_count = file.View.ReadInt32 (index_offset + 4); + index_offset += 8; + if (base_count <= 0 || base_count > 0x100 || !IsSaneCount (file_count)) + return null; + var base_offsets = new List> (base_count); + int total_count = 0; + for (int i = 0; i < base_count; ++i) + { + uint offset = file.View.ReadUInt32 (index_offset); + int count = file.View.ReadInt32 (index_offset+4); + if (count <= 0 || count > file_count || offset > file.MaxOffset) + return null; + total_count += count; + if (total_count > file_count) + return null; + base_offsets.Add (Tuple.Create (offset, count)); + index_offset += 8; + } + if (total_count != file_count) + return null; + var dir = new List (file_count); + for (int j = base_count-1; j >= 0; --j) + { + uint next_offset = file.View.ReadUInt32 (index_offset); + index_offset += 4; + for (int i = 0; i < base_offsets[j].Item2; ++i) + { + var entry = new ArchPacEntry { Name = string.Format ("{0}-{1:D5}.cts", j, i), Type = "image" }; + entry.Offset = next_offset; + next_offset = file.View.ReadUInt32 (index_offset); + index_offset += 4; + if (next_offset < entry.Offset) + return null; + entry.Size = (uint)(next_offset - entry.Offset); + entry.Offset += base_offsets[j].Item1; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + } + return new ArcFile (file, this, dir); + } + + private ArcFile OpenVoice (ArcView file) + { + int count = file.View.ReadInt16 (0); + if (!IsSaneCount (count)) + return null; + uint data_offset = 2 + 4 * (uint)count; + if (data_offset > file.View.Reserve (0, data_offset)) + return null; + + int index_offset = 2; + uint next_offset = file.View.ReadUInt32 (index_offset); + if (next_offset < data_offset) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + index_offset += 4; + var entry = new Entry { Name = string.Format ("{0:D5}.wav", i), Type = "audio" }; + entry.Offset = next_offset; + if (i + 1 == count) + next_offset = (uint)file.MaxOffset; + else + next_offset = file.View.ReadUInt32 (index_offset); + if (next_offset <= entry.Offset) + return null; + entry.Size = next_offset - (uint)entry.Offset; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (0 == entry.Size) + return Stream.Null; + var input = arc.File.CreateStream (entry.Offset, entry.Size); + if (!(entry is ArchPacEntry)) + return input; + int signature = arc.File.View.ReadUInt16 (entry.Offset); + if (0x9C78 != signature) + return input; + return new ZLibStream (input, CompressionMode.Decompress); + } + } +} diff --git a/ArcFormats/ImageSeraph.cs b/ArcFormats/ImageSeraph.cs new file mode 100644 index 00000000..78ca5d18 --- /dev/null +++ b/ArcFormats/ImageSeraph.cs @@ -0,0 +1,469 @@ +//! \file ImageSeraph.cs +//! \date Sat Jul 18 12:16:42 2015 +//! \brief Seraphim engine images. +// +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Seraphim +{ + internal class SeraphMetaData : ImageMetaData + { + public int PackedSize; + public int Colors; + } + + [Export(typeof(ImageFormat))] + public class SeraphCfImage : ImageFormat + { + public override string Tag { get { return "CF"; } } + public override string Description { get { return "Seraphim engine image format"; } } + public override uint Signature { get { return 0x4643; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x10]; + if (0x10 != stream.Read (header, 0, 0x10)) + return null; + int packed_size = LittleEndian.ToInt32 (header, 12); + if (packed_size <= 0 || packed_size > stream.Length-0x10) + return null; + uint width = LittleEndian.ToUInt16 (header, 8); + uint height = LittleEndian.ToUInt16 (header, 10); + if (0 == width || 0 == height) + return null; + return new SeraphMetaData + { + OffsetX = LittleEndian.ToInt16 (header, 4), + OffsetY = LittleEndian.ToInt16 (header, 6), + Width = width, + Height = height, + BPP = 24, + PackedSize = packed_size, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as SeraphMetaData; + if (null == meta) + throw new ArgumentException ("SeraphCfImage.Read should be supplied with SeraphMetaData", "info"); + + var reader = new SeraphReader (stream, meta); + reader.UnpackCf(); + return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data); + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("SeraphCfImage.Write not implemented"); + } + } + + [Export(typeof(ImageFormat))] + public class SeraphCtImage : SeraphCfImage + { + public override string Tag { get { return "CT"; } } + public override uint Signature { get { return 0x5443; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var info = base.ReadMetaData (stream); + info.BPP = 32; + return info; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as SeraphMetaData; + if (null == meta) + throw new ArgumentException ("SeraphCtImage.Read should be supplied with SeraphMetaData", "info"); + + var reader = new SeraphReader (stream, meta); + reader.UnpackCt(); + return ImageData.Create (info, reader.Format, null, reader.Data); + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("SeraphCtImage.Write not implemented"); + } + } + + [Export(typeof(ImageFormat))] + public class SeraphCbImage : ImageFormat + { + public override string Tag { get { return "CB"; } } + public override string Description { get { return "Seraphim engine image format"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + if ('C' != stream.ReadByte() || 'B' != stream.ReadByte()) + return null; + var header = new byte[0x10]; + if (0xE != stream.Read (header, 2, 0xE)) + return null; + int colors = LittleEndian.ToUInt16 (header, 2); + int packed_size = LittleEndian.ToInt32 (header, 12); + if (packed_size <= 0 || packed_size > stream.Length-0x10) + return null; + uint width = LittleEndian.ToUInt16 (header, 8); + uint height = LittleEndian.ToUInt16 (header, 10); + if (0 == width || 0 == height) + return null; + return new SeraphMetaData + { + OffsetX = LittleEndian.ToInt16 (header, 4), + OffsetY = LittleEndian.ToInt16 (header, 6), + Width = width, + Height = height, + BPP = 8, + PackedSize = packed_size, + Colors = colors, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as SeraphMetaData; + if (null == meta) + throw new ArgumentException ("SeraphCbImage.Read should be supplied with SeraphMetaData", "info"); + + var reader = new SeraphReader (stream, meta, 1); + reader.UnpackCb(); + return ImageData.Create (info, reader.Format, reader.Palette, reader.Data); + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("SeraphCbImage.Write not implemented"); + } + } + + internal class SeraphReader + { + Stream m_input; + byte[] m_output; + int m_width; + int m_height; + int m_stride; + int m_colors; + int m_packed_size; + + public byte[] Data { get { return m_output; } } + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + + public SeraphReader (Stream input, SeraphMetaData info, int pixel_size = 3) + { + m_input = input; + m_input.Position = 0x10; + m_width = (int)info.Width; + m_height = (int)info.Height; + m_stride = m_width * pixel_size; + m_output = new byte[m_stride * m_height]; + m_packed_size = info.PackedSize; + m_colors = info.Colors; + if (1 == pixel_size && m_colors > 0) + Palette = ReadPalette (m_colors); + } + + public BitmapPalette ReadPalette (int colors) + { + int palette_size = colors * 3; + var palette_data = new byte[Math.Max (palette_size, 0x300)]; + if (palette_size != m_input.Read (palette_data, 0, palette_size)) + throw new InvalidFormatException(); + var palette = new Color[0x100]; + if (colors > 0x100) + colors = 0x100; + int src = 0; + for (int i = 0; i < palette.Length; ++i) + { + byte r = palette_data[src++]; + byte g = palette_data[src++]; + byte b = palette_data[src++]; + palette[i] = Color.FromRgb (r, g, b); + } + return new BitmapPalette (palette); + } + + public void UnpackCb () + { + var pixels = UnpackBytes(); + int dst = 0; + for (int src = (m_height-1) * m_width; src >= 0; src -= m_width) + { + Buffer.BlockCopy (pixels, src, m_output, dst, m_width); + dst += m_width; + } + Format = PixelFormats.Indexed8; + } + + public void UnpackCt () + { + UnpackRgb(); + m_input.Position = 0x10 + m_packed_size + 4; + var alpha = UnpackBytes(); + var pixels = new byte[m_width*m_height*4]; + int dst = 0; + for (int y = m_height-1; y >= 0; --y) + { + int rgb = y * m_stride; + int a = y * m_width; + for (int x = 0; x < m_width; ++x) + { + pixels[dst++] = m_output[rgb++]; + pixels[dst++] = m_output[rgb++]; + pixels[dst++] = m_output[rgb++]; + int v = Math.Min (alpha[a++] * 0xff / 0x64, 0xff); + pixels[dst++] = (byte)~v; + } + } + m_output = pixels; + Format = PixelFormats.Bgra32; + } + + public void UnpackCf () + { + UnpackRgb(); + FlipPixels(); + Format = PixelFormats.Bgr24; + } + + private void UnpackRgb () // sub_404250 + { + int dst = 0; + while (dst < m_output.Length) + { + int count; + int v1 = m_input.ReadByte(); + if (-1 == v1) + break; + if ((v1 & 0xF0) == 0xF0) + throw new InvalidFormatException(); + + if (0 == (v1 & 0x80)) + { + if (0 != (v1 & 0x40)) + { + count = (v1 & 0x3F) + 2; + int v2 = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + m_output[dst+i] = (byte)v2; + } + else + { + count = (v1 & 0x3F) + 1; + if (count != m_input.Read (m_output, dst, count)) + break; + } + } + else if (0 == (v1 & 0x40)) + { + int v2 = m_input.ReadByte(); + count = v2 | ((v1 & 0xF) << 8); + switch ((v1 >> 4) & 3) + { + case 0: + count += 2; + v2 = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + m_output[dst+i] = (byte)v2; + break; + case 1: + ++count; + Binary.CopyOverlapped (m_output, dst-m_stride, dst, count); + break; + case 2: + ++count; + Binary.CopyOverlapped (m_output, dst-2*m_stride, dst, count); + break; + case 3: + ++count; + Binary.CopyOverlapped (m_output, dst-4*m_stride, dst, count); + break; + } + } + else if (0 == (v1 & 0x30)) + { + int v2 = m_input.ReadByte(); + int v19 = (v1 >> 3) & 1; + count = v2 + ((v1 & 7) << 8) + 1; + if (0 != v19) + { + m_input.Read (m_output, dst, 6); + Binary.CopyOverlapped (m_output, dst, dst+6, count*6); + ++count; + count *= 6; + } + else + { + m_input.Read (m_output, dst, 3); + Binary.CopyOverlapped (m_output, dst, dst+3, count*3); + ++count; + count *= 3; + } + } + else if (0 == (v1 & 0x20)) + { + int v30 = m_input.ReadByte(); + int v31 = v30 + ((v1 & 0xF) << 8); + count = m_input.ReadByte() + 1; + int src = dst - 3 - 3 * v31; // auto v34 = &unpacked[dst - 3] - 3 * v31; + count *= 3; + Binary.CopyOverlapped (m_output, src, dst, count); + } + else + { + int v37 = m_input.ReadByte(); + int v38 = v37 + ((v1 & 0xF) << 8); + count = m_input.ReadByte() + 1; + int src = dst - 1 - v38; // auto v40 = &unpacked[dst - 1] - v38; + Binary.CopyOverlapped (m_output, src, dst, count); + } + if (0 == count) + throw new InvalidFormatException(); + dst += count; + } + } + + private byte[] UnpackBytes () // sub_403ED0 + { + int total = m_width * m_height; + var output = new byte[total]; + int dst = 0; + while ( dst < total ) + { + int count; + int next = m_input.ReadByte(); + if (-1 == next) + break; + if ((next & 0xF0) == 0xF0) + throw new InvalidFormatException(); + + if (0 == (next & 0x80)) + { + if (0 != (next & 0x40)) + { + count = (next & 0x3F) + 2; + int v4 = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + output[dst+i] = (byte)v4; + } + else + { + count = (next & 0x3F) + 1; + if (count != m_input.Read (output, dst, count)) + break; + } + } + else if (0 == (next & 0x40)) + { + int v2 = m_input.ReadByte(); + count = v2 | ((next & 0xF) << 8); + switch ((next >> 4) & 3) + { + case 0: + count += 2; + v2 = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + output[dst+i] = (byte)v2; + break; + case 1: + ++count; + Binary.CopyOverlapped (output, dst-m_width, dst, count); + break; + case 2: + ++count; + Binary.CopyOverlapped (output, dst-2*m_width, dst, count); + break; + case 3: + ++count; + Binary.CopyOverlapped (output, dst-4*m_width, dst, count); + break; + } + } + else if (0 == (next & 0x20)) + { + count = m_input.ReadByte() + ((next & 7) << 8) + 1; + switch ((next >> 3) & 3) + { + case 0: + m_input.Read (output, dst, 2); + Binary.CopyOverlapped (output, dst, dst+2, count*2); + ++count; + count *= 2; + break; + case 1: + m_input.Read (output, dst, 4); + Binary.CopyOverlapped (output, dst, dst+4, count*4); + ++count; + count *= 4; + break; + case 2: + m_input.Read (output, dst, 8); + Binary.CopyOverlapped (output, dst, dst+8, count*8); + ++count; + count *= 8; + break; + case 3: + m_input.Read (output, dst, 16); + Binary.CopyOverlapped (output, dst, dst+16, count*16); + ++count; + count *= 16; + break; + } + } + else + { + int v36 = m_input.ReadByte() | ((next & 0xF) << 8); + count = m_input.ReadByte() + 1; + int src = dst - 1 - v36; + Binary.CopyOverlapped (output, src, dst, count); + } + dst += count; + } + return output; + } + + private void FlipPixels () + { + // flip pixels vertically + var pixels = new byte[m_output.Length]; + int dst = 0; + for (int src = m_stride * (m_height-1); src >= 0; src -= m_stride) + { + Buffer.BlockCopy (m_output, src, pixels, dst, m_stride); + dst += m_stride; + } + m_output = pixels; + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 9bbba203..2d408f51 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.7.80")] -[assembly: AssemblyFileVersion ("1.0.7.80")] +[assembly: AssemblyVersion ("1.0.7.81")] +[assembly: AssemblyFileVersion ("1.0.7.81")]