From 7f42177c48417168b373e8b9798263003e35f8f4 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 27 Jun 2015 14:33:58 +0400 Subject: [PATCH] implemented Macromedia Director resources. - *.cct archives (XFIR signature) - 'Edim' audio - 'Bitd' images --- ArcFormats/ArcCCT.cs | 258 ++++++++++++++++++++++++++ ArcFormats/ArcFormats.csproj | 3 + ArcFormats/AudioEDIM.cs | 50 +++++ ArcFormats/ImageBITD.cs | 198 ++++++++++++++++++++ ArcFormats/Properties/AssemblyInfo.cs | 4 +- 5 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/ArcCCT.cs create mode 100644 ArcFormats/AudioEDIM.cs create mode 100644 ArcFormats/ImageBITD.cs diff --git a/ArcFormats/ArcCCT.cs b/ArcFormats/ArcCCT.cs new file mode 100644 index 00000000..b53fa2bd --- /dev/null +++ b/ArcFormats/ArcCCT.cs @@ -0,0 +1,258 @@ +//! \file ArcCCT.cs +//! \date Fri Jun 26 01:15:26 2015 +//! \brief Macromedia Director archive access implementation. +// +// 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.Linq; +using System.Text; +using GameRes.Utility; +using ZLibNet; + +namespace GameRes.Formats.Selen +{ + [Export(typeof(ArchiveFormat))] + public class CctOpener : ArchiveFormat + { + public override string Tag { get { return "CCT"; } } + public override string Description { get { return "Macromedia Director resource archive"; } } + public override uint Signature { get { return 0x52494658; } } // 'XFIR' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public CctOpener () + { + Extensions = new string[] { "cct", "dcr" }; + } + + public override ArcFile TryOpen (ArcView file) + { + uint id = file.View.ReadUInt32 (8); + if (id != 0x46474443 && id != 0x4647444D) // 'CDGF' || 'MDGF' + return null; + + var reader = new CctReader (file); + var dir = reader.ReadIndex(); + if (null != dir) + { + var arc = new ArcFile (file, this, dir); + SetEntryTypes (arc); + return arc; + } + return null; + } + + 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); + } + + private void SetEntryTypes (ArcFile arc) + { + foreach (var entry in arc.Dir.OrderBy (x => x.Offset)) + { + if (entry.Name.EndsWith (".edim")) + entry.Type = DetectEdimType (arc, entry); + else if (entry.Name.EndsWith (".bitd")) + entry.Type = "image"; + else if (entry.Name.EndsWith (".xmed")) + entry.Type = "script"; + } + } + + private string DetectEdimType (ArcFile arc, Entry entry) + { + using (var input = OpenEntry (arc, entry)) + { + uint signature = (uint)input.ReadByte() << 24; + signature |= (uint)input.ReadByte() << 16; + signature |= (uint)input.ReadByte() << 8; + signature |= (byte)input.ReadByte(); + if (0xffd8ffe0 == signature) + return "image"; + uint real_size = (entry as PackedEntry).UnpackedSize; + if (signature > 0xffff || signature+4 > real_size) + return ""; + var header = new byte[signature+0x10]; + if (header.Length != input.Read (header, 0, header.Length)) + return ""; + if (0xff == header[signature]) + return "audio"; + return ""; + } + } + } + + internal class CctReader + { + ArcView m_file; + long m_offset; + + public CctReader (ArcView file) + { + m_file = file; + m_offset = 0x0C; + } + + byte[] m_size_buffer = new byte[10]; + + public List ReadIndex () + { + uint section_size = ReadSectionSize ("Fver"); + m_offset += section_size; + section_size = ReadSectionSize ("Fcdr"); + /* + int Mcdr_size; + var Mcdr = ZlibUnpack (m_offset, section_size, out Mcdr_size); + */ + m_offset += section_size; + uint abmp_size = ReadSectionSize ("ABMP"); + int max_count = m_file.View.Read (m_offset, m_size_buffer, 0, Math.Min (10, abmp_size)); + int size_offset = 0; + ReadValue (m_size_buffer, ref size_offset, max_count); + max_count -= size_offset; + + int bmp_unpacked_size = (int)ReadValue (m_size_buffer, ref size_offset, max_count); + m_offset += size_offset; + abmp_size -= (uint)size_offset; + int index_size; + var index = ZlibUnpack (m_offset, abmp_size, out index_size, bmp_unpacked_size); + m_offset += abmp_size; + section_size = ReadSectionSize ("FGEI"); + if (0 != section_size) + throw new NotSupportedException(); + + int index_offset = 0; + ReadValue (index, ref index_offset, index_size-index_offset); + ReadValue (index, ref index_offset, index_size-index_offset); + int entry_count = (int)ReadValue (index, ref index_offset, index_size-index_offset); + if (entry_count <= 0 || entry_count > 0xfffff) + return null; + + var type_buf = new char[4]; + var dir = new List (entry_count); + for (int i = 0; i < entry_count; ++i) + { + uint id = ReadValue (index, ref index_offset, index_size-index_offset); + uint offset = ReadValue (index, ref index_offset, index_size-index_offset); + uint size = ReadValue (index, ref index_offset, index_size-index_offset); + uint unpacked_size = ReadValue (index, ref index_offset, index_size-index_offset); + uint flag = ReadValue (index, ref index_offset, index_size-index_offset); + + if (index_size-index_offset < 4) + return null; + uint type_id = LittleEndian.ToUInt32 (index, index_offset); + index_offset += 4; + if (0 == type_id || uint.MaxValue == offset) + continue; + + Encoding.ASCII.GetChars (index, index_offset-4, 4, type_buf, 0); + var entry = new PackedEntry + { + Name = CreateName (id, type_buf), + Offset = (long)m_offset + offset, + Size = size, + UnpackedSize = unpacked_size, + IsPacked = 0 == flag, + }; + if (entry.CheckPlacement (m_file.MaxOffset)) + { + dir.Add (entry); + } + } + return dir; + } + + string CreateName (uint id, char[] type_buf) + { + Array.Reverse (type_buf); + int t = 3; + while (t >= 0 && ' ' == type_buf[t]) + t--; + if (t >= 0) + { + string ext = new string (type_buf, 0, t+1); + return string.Format ("{0:X8}.{1}", id, ext.ToLowerInvariant()); + } + else + return id.ToString ("X8"); + } + + byte[] ZlibUnpack (long offset, uint size, out int actual_size, int unpacked_size_hint = 0) + { + using (var input = m_file.CreateStream (offset, size)) + using (var zstream = new ZLibStream (input, CompressionMode.Decompress)) + using (var mem = new MemoryStream (unpacked_size_hint)) + { + zstream.CopyTo (mem); + actual_size = (int)mem.Length; + return mem.GetBuffer(); + } + } + + uint ReadSectionSize (string id_str) + { + uint id = ConvertId (id_str); + if (id != m_file.View.ReadUInt32 (m_offset)) + throw new InvalidFormatException(); + m_offset += 4; + if (5 != m_file.View.Read (m_offset, m_size_buffer, 0, 5)) + throw new InvalidFormatException(); + int off_count = 0; + uint size = ReadValue (m_size_buffer, ref off_count, 5); + m_offset += off_count; + return size; + } + + static uint ReadValue (byte[] buffer, ref int offset, int length) + { + uint n = 0; + for (int off_count = 0; off_count < length; ++off_count) + { + uint bits = buffer[offset++]; + n = (n << 7) | (bits & 0x7F); + if (0 == (bits & 0x80)) + return n; + } + throw new InvalidFormatException(); + } + + static uint ConvertId (string id) + { + if (id.Length != 4) + throw new ArgumentException ("Invalid section id"); + uint n = 0; + for (int i = 0; i < 4; ++i) + n = (n << 8) | (byte)id[i]; + return n; + } + } +} diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 665a419d..a9789f60 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -74,6 +74,7 @@ + @@ -124,6 +125,7 @@ + @@ -169,6 +171,7 @@ + diff --git a/ArcFormats/AudioEDIM.cs b/ArcFormats/AudioEDIM.cs new file mode 100644 index 00000000..c26d36c4 --- /dev/null +++ b/ArcFormats/AudioEDIM.cs @@ -0,0 +1,50 @@ +//! \file AudioEDIM.cs +//! \date Fri Jun 26 06:52:33 2015 +//! \brief Selen wrapper around MP3 stream. +// +// 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 GameRes.Utility; + +namespace GameRes.Formats.Selen +{ + [Export(typeof(AudioFormat))] + public class EdimAudio : Mp3Audio + { + public override string Tag { get { return "EDIM"; } } + public override string Description { get { return "Selen audio format (MP3)"; } } + public override uint Signature { get { return 0x40010000; } } + + public override SoundInput TryOpen (Stream file) + { + uint offset = 4 + Binary.BigEndian (FormatCatalog.ReadSignature (file)); + return base.TryOpen (new StreamRegion (file, offset)); + } + + public override void Write (SoundInput source, Stream output) + { + throw new System.NotImplementedException ("EdimFormat.Write not implemenented"); + } + } +} diff --git a/ArcFormats/ImageBITD.cs b/ArcFormats/ImageBITD.cs new file mode 100644 index 00000000..fb7d0fcd --- /dev/null +++ b/ArcFormats/ImageBITD.cs @@ -0,0 +1,198 @@ +//! \file ImageBITD.cs +//! \date Fri Jun 26 07:45:01 2015 +//! \brief Selen 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 System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.Selen +{ + [Export(typeof(ImageFormat))] + public class BitdFormat : ImageFormat + { + public override string Tag { get { return "BITD"; } } + public override string Description { get { return "Selen RLE-compressed bitmap"; } } + public override uint Signature { get { return 0; } } + + public override ImageMetaData ReadMetaData (Stream stream) + { + if (stream.Length > 0xffffff) + return null; + var scanner = new BitdScanner (stream); + return scanner.GetInfo(); + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var reader = new BitdReader (stream, info); + reader.Unpack(); + return ImageData.Create (info, reader.Format, null, reader.Data); + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("BitdFormat.Write not implemented"); + } + } + + internal class BitdScanner + { + Stream m_input; + + protected Stream Input { get { return m_input; } } + + public BitdScanner (Stream input) + { + m_input = input; + } + + const int MaxScanLine = 2048; + + public ImageMetaData GetInfo () + { + int total = 0; + var scan_lines = new Dictionary(); + var key_lines = new List(); + for (;;) + { + int b = m_input.ReadByte(); + if (-1 == b) + break; + int count = b; + if (b > 0x7f) + count = (byte)-(sbyte)b; + ++count; + if (count > 0x7f) + return null; + m_input.Seek (b > 0x7f ? 1 : count, SeekOrigin.Current); + + key_lines.Clear(); + key_lines.AddRange (scan_lines.Keys); + foreach (var line in key_lines) + { + int width = scan_lines[line]; + if (width < count) + scan_lines.Remove (line); + else if (width == count) + scan_lines[line] = line; + else + scan_lines[line] = width - count; + } + + total += count; + if (total <= MaxScanLine && total >= 8) + scan_lines[total] = total; + if (total > MaxScanLine && !scan_lines.Any()) + return null; + } + int rem; + total = Math.DivRem (total, 4, out rem); + if (rem != 0) + return null; + var valid_lines = from line in scan_lines where line.Key == line.Value + orderby line.Key + select line.Key; + foreach (var width in valid_lines) + { + int height = Math.DivRem (total, width, out rem); + if (0 == rem) + { + return new ImageMetaData + { + Width = (uint)width, + Height = (uint)height, + BPP = 32, + }; + } + } + return null; + } + } + + internal class BitdReader : BitdScanner + { + byte[] m_output; + int m_width; + int m_height; + + public byte[] Data { get { return m_output; } } + public PixelFormat Format { get; private set; } + + public BitdReader (Stream input, ImageMetaData info) : base (input) + { + m_width = (int)info.Width; + m_height = (int)info.Height; + m_output = new byte[m_width * m_height * 4]; + Format = PixelFormats.Bgra32; + } + + public void Unpack () + { + int stride = m_width * 4; + var scan_line = new byte[stride]; + for (int line = 0; line < m_output.Length; line += stride) + { + int dst = 0; + while (dst < stride) + { + int b = Input.ReadByte(); + if (-1 == b) + throw new InvalidFormatException ("Unexpected end of file"); + int count = b; + if (b > 0x7f) + count = (byte)-(sbyte)b; + ++count; + if (dst + count > stride) + throw new InvalidFormatException(); + if (b > 0x7f) + { + b = Input.ReadByte(); + if (-1 == b) + throw new InvalidFormatException ("Unexpected end of file"); + for (int i = 0; i < count; ++i) + scan_line[dst++] = (byte)b; + } + else + { + Input.Read (scan_line, dst, count); + dst += count; + } + } + dst = line; + for (int x = 0; x < m_width; ++x) + { + m_output[dst++] = scan_line[x+m_width*3]; + m_output[dst++] = scan_line[x+m_width*2]; + m_output[dst++] = scan_line[x+m_width]; + m_output[dst++] = scan_line[x]; + } + } + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index dad30a60..ebe0e03b 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.69")] -[assembly: AssemblyFileVersion ("1.0.7.69")] +[assembly: AssemblyVersion ("1.0.7.70")] +[assembly: AssemblyFileVersion ("1.0.7.70")]