From 26905015c2f1c307937ea095690cc57d73234d20 Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 31 Mar 2015 14:34:49 +0400 Subject: [PATCH] Marble engine resources implementation. --- ArcFormats/ArcMBL.cs | 94 +++++++++++++++++ ArcFormats/AudioWADY.cs | 227 ++++++++++++++++++++++++++++++++++++++++ ArcFormats/ImagePRS.cs | 221 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 542 insertions(+) create mode 100644 ArcFormats/ArcMBL.cs create mode 100644 ArcFormats/AudioWADY.cs create mode 100644 ArcFormats/ImagePRS.cs diff --git a/ArcFormats/ArcMBL.cs b/ArcFormats/ArcMBL.cs new file mode 100644 index 00000000..ea51e46b --- /dev/null +++ b/ArcFormats/ArcMBL.cs @@ -0,0 +1,94 @@ +//! \file ArcMBL.cs +//! \date Fri Mar 27 23:11:19 2015 +//! \brief Marble Engine archive 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 GameRes.Utility; + +namespace GameRes.Formats.Marble +{ + [Export(typeof(ArchiveFormat))] + public class MblOpener : ArchiveFormat + { + public override string Tag { get { return "MBL"; } } + public override string Description { get { return "Marble 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 override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (count <= 0 || count > 0xfffff) + return null; + ArcFile arc = null; + uint filename_len = file.View.ReadUInt32 (4); + if (filename_len > 0 && filename_len <= 0xff) + arc = ReadIndex (file, count, filename_len, 8); + if (null == arc) + arc = ReadIndex (file, count, 0x10, 4); + return arc; + } + + private ArcFile ReadIndex (ArcView file, int count, uint filename_len, uint index_offset) + { + uint index_size = (8u + filename_len) * (uint)count; + if (index_size > file.View.Reserve (index_offset, index_size)) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + string name = file.View.ReadString (index_offset, filename_len); + if (name.Length+1 < filename_len) + { + uint ext_length = (uint)Math.Min (filename_len-name.Length-1, 3); + string ext = file.View.ReadString (index_offset+name.Length+1, ext_length); + if (!string.IsNullOrEmpty (ext)) + { + if ("OG" == ext || "O" == ext) + ext = "OGG"; + else if ("PR" == ext || "P" == ext) + ext = "PRS"; + else if ("WA" == ext || "W" == ext) + ext = "WAY"; + name = Path.ChangeExtension (name, ext); + } + } + name = name.ToLowerInvariant(); + var entry = FormatCatalog.Instance.CreateEntry (name); + index_offset += (uint)filename_len; + entry.Offset = file.View.ReadUInt32 (index_offset); + entry.Size = file.View.ReadUInt32 (index_offset+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 8; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/AudioWADY.cs b/ArcFormats/AudioWADY.cs new file mode 100644 index 00000000..7a7fd9a8 --- /dev/null +++ b/ArcFormats/AudioWADY.cs @@ -0,0 +1,227 @@ +//! \file AudioWADY.cs +//! \date Sat Mar 28 01:36:42 2015 +//! \brief Marble engine audio 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.ComponentModel.Composition; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Marble +{ + public class WadyInput : SoundInput + { + byte MulValue; + + public override long Position + { + get { return m_input.Position; } + set { m_input.Position = value; } + } + + public override bool CanSeek { get { return m_input.CanSeek; } } + + public override int SourceBitrate + { + get { return (int)Format.AverageBytesPerSecond * 8; } + } + + public override long Seek (long offset, SeekOrigin origin) + { + return m_input.Seek (offset, origin); + } + + public override int Read (byte[] buffer, int offset, int count) + { + return m_input.Read (buffer, offset, count); + } + + public override int ReadByte () + { + return m_input.ReadByte(); + } + + public WadyInput (Stream file) : base (new MemoryStream()) + { + using (var input = new BinaryReader (file)) + { + input.BaseStream.Seek (5, SeekOrigin.Begin); + MulValue = input.ReadByte(); + input.BaseStream.Seek (6, SeekOrigin.Current); + int src_size = input.ReadInt32(); + input.BaseStream.Seek (16, SeekOrigin.Current); + var format = new WaveFormat(); + format.FormatTag = input.ReadUInt16(); + format.Channels = input.ReadUInt16(); + format.SamplesPerSecond = input.ReadUInt32(); + format.AverageBytesPerSecond = input.ReadUInt32(); + format.BlockAlign = input.ReadUInt16(); + format.BitsPerSample = input.ReadUInt16(); + format.ExtraSize = 0; + this.Format = format; + int remaining = (int)(input.BaseStream.Length-input.BaseStream.Position); + if (remaining == src_size) + { + (m_input as MemoryStream).Capacity = src_size * 2; + Decode (input, src_size, m_input); + } + else + Decode2 (input, m_input); + m_input.Position = 0; + this.PcmSize = m_input.Length; + } + } + + private void Decode (BinaryReader input, int count, Stream output) + { + using (var buffer = new BinaryWriter (output, Encoding.ASCII, true)) + { + ushort sampleL = 0; + ushort sampleR = 0; + for (int i = 0; i < count; ++i) + { + byte v = input.ReadByte(); + if (0 != (v & 0x80)) + sampleL = (ushort)(v << 9); + else + sampleL += (ushort)(MulValue * SampleTable[v]); + buffer.Write (sampleL); + if (1 != Format.Channels) + { + ++i; + v = input.ReadByte(); + if (0 != (v & 0x80)) + sampleR = (ushort)(v << 9); + else + sampleR += (ushort)(MulValue * SampleTable[v]); + buffer.Write (sampleR); + } + } + } + } + + private void Decode2 (BinaryReader input, Stream output) + { + if (1 != Format.Channels) + { + int channel_size = input.ReadInt32(); + Decode3 (input, output, 2); + input.BaseStream.Position = 0x38 + channel_size; + output.Position = 2; + Decode3 (input, output, 2); + } + else + Decode3 (input, output, 0); + } + + private void Decode3 (BinaryReader input, Stream output, int step) + { + input.ReadInt32(); // unpacked_size + int count = input.ReadInt32(); + using (var buffer = new BinaryWriter (output, Encoding.ASCII, true)) + { + short sample = input.ReadInt16(); + buffer.Write (sample); + for (int i = 0; i < count; ++i) + { + if (count - 300 == i) + sample = 0; + ushort v = input.ReadByte(); + if (0 != (v & 1)) + { + ushort v14 = (ushort)((v >> 1) & 0x7F); + if (0 != (v14 & 0x40)) + sample = (short)(v14 << 10); + else + sample += (short)SampleTable2[v14]; + buffer.Write (sample); + if (step != 0) + buffer.BaseStream.Seek (step, SeekOrigin.Current); + } + else + { + v |= (ushort)(input.ReadByte() << 8); + int repeat = SizeTable[(v >> 1) & 7]; + short end = (short)(v & 0xFFF0); + double inc = (end - sample) / (double)repeat; + double v8 = sample; + for (int j = 0; j < repeat; ++j) + { + v8 += inc; + buffer.Write ((short)v8); + if (step != 0) + buffer.BaseStream.Seek (step, SeekOrigin.Current); + } + sample = end; + } + } + } + } + + static readonly int[] SizeTable = new int[] { 3, 4, 5, 6, 8, 0x10, 0x20, 0x100 }; + + static readonly ushort[] SampleTable = new ushort[] { + 0x0000, 0x0002, 0x0004, 0x0006, 0x0008, 0x000A, 0x000C, 0x000F, + 0x0012, 0x0015, 0x0018, 0x001C, 0x0020, 0x0024, 0x0028, 0x002C, + 0x0031, 0x0036, 0x003B, 0x0040, 0x0046, 0x004C, 0x0052, 0x0058, + 0x005F, 0x0066, 0x006D, 0x0074, 0x007C, 0x0084, 0x008C, 0x0094, + 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00C8, 0x00D2, 0x00DC, 0x00E6, + 0x00F0, 0x00FF, 0x010E, 0x011D, 0x012C, 0x0140, 0x0154, 0x0168, + 0x017C, 0x0190, 0x01A9, 0x01C2, 0x01DB, 0x01F4, 0x020D, 0x0226, + 0x0244, 0x0262, 0x028A, 0x02BC, 0x02EE, 0x0320, 0x0384, 0x03E8, + 0x0000, 0xFFFE, 0xFFFC, 0xFFFA, 0xFFF8, 0xFFF6, 0xFFF4, 0xFFF1, + 0xFFEE, 0xFFEB, 0xFFE8, 0xFFE4, 0xFFE0, 0xFFDC, 0xFFD8, 0xFFD4, + 0xFFCF, 0xFFCA, 0xFFC5, 0xFFC0, 0xFFBA, 0xFFB4, 0xFFAE, 0xFFA8, + 0xFFA1, 0xFF9A, 0xFF93, 0xFF8C, 0xFF84, 0xFF7C, 0xFF74, 0xFF6C, + 0xFF60, 0xFF56, 0xFF4C, 0xFF42, 0xFF38, 0xFF2E, 0xFF24, 0xFF1A, + 0xFF10, 0xFF01, 0xFEF2, 0xFEE3, 0xFED4, 0xFEC0, 0xFEAC, 0xFE98, + 0xFE84, 0xFE70, 0xFE57, 0xFE3E, 0xFE25, 0xFE0C, 0xFDF3, 0xFDDA, + 0xFDBC, 0xFD9E, 0xFD76, 0xFD44, 0xFD12, 0xFCE0, 0xFC7C, 0xFC18, + }; + + static readonly ushort[] SampleTable2 = new ushort[] { + 0x0000, 0x0004, 0x0008, 0x000C, 0x0013, 0x0018, 0x001E, 0x0026, + 0x002F, 0x003B, 0x004A, 0x005C, 0x0073, 0x0090, 0x00B4, 0x00E1, + 0x0119, 0x0160, 0x01B8, 0x0226, 0x02AF, 0x035B, 0x0431, 0x053E, + 0x068E, 0x0831, 0x0A3D, 0x0CCD, 0x1000, 0x1400, 0x1900, 0x1F40, + 0x0000, 0xFFFC, 0xFFF8, 0xFFF4, 0xFFED, 0xFFE8, 0xFFE2, 0xFFDA, + 0xFFD1, 0xFFC5, 0xFFB6, 0xFFA4, 0xFF8D, 0xFF70, 0xFF4C, 0xFF1F, + 0xFEE7, 0xFEA0, 0xFE48, 0xFDDA, 0xFD51, 0xFCA5, 0xFBCF, 0xFAC2, + 0xF972, 0xF7CF, 0xF5C3, 0xF333, 0xF000, 0xEC00, 0xE700, 0xE0C0, + }; + } + + [Export(typeof(AudioFormat))] + public class WadyAudio : AudioFormat + { + public override string Tag { get { return "WAY"; } } + public override string Description { get { return "Marble engine wave audio format"; } } + public override uint Signature { get { return 0x59444157u; } } // 'WADY' + + public override SoundInput TryOpen (Stream file) + { + return new WadyInput (file); + } + } +} diff --git a/ArcFormats/ImagePRS.cs b/ArcFormats/ImagePRS.cs new file mode 100644 index 00000000..3d47614d --- /dev/null +++ b/ArcFormats/ImagePRS.cs @@ -0,0 +1,221 @@ +//! \file ImagePRS.cs +//! \date Sat Mar 28 00:15:43 2015 +//! \brief Marble engine 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.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Marble +{ + internal class PrsMetaData : ImageMetaData + { + public byte Flag; + public uint PackedSize; + } + + [Export(typeof(ImageFormat))] + public class PrsFormat : ImageFormat + { + public override string Tag { get { return "PRS"; } } + public override string Description { get { return "Marble engine image format"; } } + public override uint Signature { get { return 0; } } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("PrsFormat.Write not implemented"); + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x10]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + if (header[0] != 'Y' || header[1] != 'B' || header[3] != 3) + return null; + + return new PrsMetaData + { + Width = LittleEndian.ToUInt16 (header, 12), + Height = LittleEndian.ToUInt16 (header, 14), + BPP = 24, + Flag = header[2], + PackedSize = LittleEndian.ToUInt32 (header, 4), + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as PrsMetaData; + if (null == meta) + throw new ArgumentException ("PrsFormat.Read should be supplied with PrsMetaData", "info"); + + stream.Position = 0x10; + using (var reader = new Reader (stream, meta)) + { + reader.Unpack(); + byte[] pixels = reader.Data; + var bitmap = BitmapSource.Create ((int)meta.Width, (int)meta.Height, 96, 96, + PixelFormats.Bgr24, null, pixels, (int)meta.Width*3); + bitmap.Freeze(); + return new ImageData (bitmap, meta); + } + } + + internal class Reader : IDisposable + { + BinaryReader m_input; + byte[] m_output; + uint m_size; + byte m_flag; + + public byte[] Data { get { return m_output; } } + + public Reader (Stream file, PrsMetaData info) + { + m_input = new BinaryReader (file, Encoding.ASCII, true); + m_output = new byte[info.Width*info.Height*3]; + m_size = info.PackedSize; + m_flag = info.Flag; + } + + static readonly int[] LengthTable = InitLengthTable(); + + private static int[] InitLengthTable () + { + var length_table = new int[256]; + for (int i = 0; i < 0xfe; ++i) + length_table[i] = i + 3; + length_table[0xfe] = 0x400; + length_table[0xff] = 0x1000; + return length_table; + } + + public void Unpack () + { + int dst = 0; + int remaining = (int)m_size; + int bit = 0; + int ctl = 0; + while (remaining > 0 && dst < m_output.Length) + { + bit >>= 1; + if (0 == bit) + { + ctl = m_input.ReadByte(); + --remaining; + bit = 0x80; + } + if (0 == (ctl & bit)) + { + m_output[dst++] = m_input.ReadByte(); + --remaining; + continue; + } + int b = m_input.ReadByte(); + --remaining; + int length = 0; + int shift = 0; + + if (0 != (b & 0x80)) + { + if (remaining <= 0) + break; + shift = m_input.ReadByte(); + --remaining; + shift |= (b & 0x3f) << 8; + if (0 != (b & 0x40)) + { + if (remaining <= 0) + break; + int offset = m_input.ReadByte(); + --remaining; + length = LengthTable[offset]; + } + else + { + length = (shift & 0xf) + 3; + shift >>= 4; + } + } + else + { + length = b >> 2; + b &= 3; + if (3 == b) + { + length += 9; + int read = m_input.Read (m_output, dst, length); + if (read < length) + break; + remaining -= length; + dst += length; + continue; + } + shift = length; + length = b + 2; + } + ++shift; + if (dst < shift) + throw new InvalidFormatException ("Invalid offset value"); + Binary.CopyOverlapped (m_output, dst-shift, dst, length); + dst += length; + } + if (m_flag != 0) + { + for (int i = 3; i < m_output.Length; ++i) + m_output[i] += m_output[i-3]; + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + m_input.Dispose(); + } + disposed = true; + } + } + #endregion + } + } +}