From 9ef70bea7248679c20299e504ff15f98ad92c41e Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 7 Apr 2018 03:04:34 +0400 Subject: [PATCH] (Legacy): GM1.0 archives and WV3.0 audio files. --- Legacy/Eve/ArcGM.cs | 185 ++++++++++++++++++++++++++++++++++++++++++ Legacy/Eve/AudioWV.cs | 131 ++++++++++++++++++++++++++++++ Legacy/Legacy.csproj | 2 + 3 files changed, 318 insertions(+) create mode 100644 Legacy/Eve/ArcGM.cs create mode 100644 Legacy/Eve/AudioWV.cs diff --git a/Legacy/Eve/ArcGM.cs b/Legacy/Eve/ArcGM.cs new file mode 100644 index 00000000..58a5c78f --- /dev/null +++ b/Legacy/Eve/ArcGM.cs @@ -0,0 +1,185 @@ +//! \file ArcGM.cs +//! \date 2018 Apr 05 +//! \brief Eve resource archive. +// +// Copyright (C) 2018 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.Compression; + +// [021206][Eve] Mitsugetsu ~Secret Moon~ + +namespace GameRes.Formats.Eve +{ + [Export(typeof(ArchiveFormat))] + public class GmDatOpener : ArchiveFormat + { + public override string Tag { get { return "DAT/GM"; } } + public override string Description { get { return "Eve resource archive"; } } + public override uint Signature { get { return 0x2E314D47; } } // 'GM1.0' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (file.View.ReadByte (4) != '0') + return null; + using (var index = file.CreateStream()) + { + var signature = index.ReadCString(); + index.Position = (((int)index.Position + 4) >> 2) << 2; + uint data_offset = index.ReadUInt16(); + uint index_size = index.ReadUInt32(); + uint index_offset = index.ReadUInt32(); + int count = index.ReadInt32(); + if (!IsSaneCount (count)) + return null; + int key_length = index.ReadUInt16(); + int flags = index.ReadUInt16(); + index.Position = 20; + var key = index.ReadBytes (key_length); + index.Position = index_offset + 0xC00; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + uint offset = index.ReadUInt32() + data_offset; + uint size = index.ReadUInt32(); + int name_len = index.ReadUInt8(); + var name = index.ReadCString (name_len); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = offset; + entry.Size = size; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var header = arc.File.View.ReadBytes (entry.Offset, 25); + if (header[0] < 'B' || header[0] > 'E' || header[1] != '1') + return arc.File.CreateStream (entry.Offset, entry.Size); + if ('E' == header[0]) + { + byte t = header[17]; + header[17] = header[23]; + header[23] = t; + t = header[19]; + header[19] = header[24]; + header[24] = t; + } + int unpacked_size = header.ToInt32 (6); + var data = new byte[unpacked_size]; + Stream input = arc.File.CreateStream (entry.Offset+header.Length, entry.Size-(uint)header.Length); + input = new PrefixStream (header, input); + input.Position = 10; + using (input = new LzssStream (input)) + input.Read (data, 0, unpacked_size); + if (data.AsciiEqual ("BPR01")) + return new PackedStream (Stream.Null, new BprDecompressor (data)); + else + return new BinMemoryStream (data, entry.Name); + } + + void DecryptIndex (byte[] index, int length, byte[] key) + { + for (int i = 0; i < length; ++i) + { + index[i] ^= key[i % key.Length]; + } + } + } + + internal class BprDecompressor : Decompressor + { + IBinaryStream m_input; + + public BprDecompressor () + { + } + + public BprDecompressor (byte[] data) + { + m_input = new BinMemoryStream (data, 5, data.Length-5); + } + + public override void Initialize (Stream input) + { + } + + protected override IEnumerator Unpack () + { + for (;;) + { + int ctl = m_input.ReadByte(); + if (-1 == ctl || 0xFF == ctl) + yield break; + int count = m_input.ReadInt32(); + if (1 == ctl) + { + byte v = m_input.ReadUInt8(); + while (count --> 0) + { + m_buffer[m_pos++] = v; + if (0 == --m_length) + yield return m_pos; + } + } + else + { + while (count > 0) + { + int chunk = Math.Min (count, m_length); + chunk = m_input.Read (m_buffer, m_pos, chunk); + if (0 == chunk) + yield break; + m_pos += chunk; + m_length -= chunk; + count -= chunk; + if (0 == m_length) + yield return m_pos; + } + } + } + } + + #region IDisposable Members + bool m_disposed = false; + protected override void Dispose (bool disposing) + { + if (!m_disposed) + { + if (disposing && m_input != null) + m_input.Dispose(); + m_disposed = true; + base.Dispose(); + } + } + #endregion + } +} diff --git a/Legacy/Eve/AudioWV.cs b/Legacy/Eve/AudioWV.cs new file mode 100644 index 00000000..466cb44c --- /dev/null +++ b/Legacy/Eve/AudioWV.cs @@ -0,0 +1,131 @@ +//! \file AudioWV.cs +//! \date 2018 Apr 06 +//! \brief Eve compressed audio. +// +// Copyright (C) 2018 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; + +namespace GameRes.Formats.Eve +{ + [Export(typeof(AudioFormat))] + public sealed class Wv3Audio : AudioFormat + { + public override string Tag { get { return "WAV/WV3"; } } + public override string Description { get { return "Eve compressed audio format"; } } + public override uint Signature { get { return 0x2E335657; } } // 'WV3.0' + public override bool CanWrite { get { return false; } } + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x26); + if (header[4] != '0') + return null; + var decoder = new Wv3Decoder (file); + var data = decoder.Decode(); + var pcm = new MemoryStream (data); + return new RawPcmInput (pcm, decoder.Format); + } + } + + internal class Wv3Decoder + { + IBinaryStream m_input; + uint m_data_offset; + int m_sample_count; + byte[] m_output; + WaveFormat m_format; + + public WaveFormat Format { get { return m_format; } } + + public Wv3Decoder (IBinaryStream file) + { + m_input = file; + var header = m_input.ReadHeader (0x26); + m_data_offset = header.ToUInt32 (6); + m_sample_count = header.ToInt32 (0x1A); + m_output = new byte[m_sample_count * 280]; + + m_format = new WaveFormat { + FormatTag = 1, + Channels = 2, + SamplesPerSecond = header.ToUInt32 (0xE), + BitsPerSample = 16, + BlockAlign = 4 + }; + m_format.SetBPS(); + } + + public byte[] Decode () + { + m_input.Position = m_data_offset; + var input = new byte[72]; + var output = new short[140]; + var back = new int[2,2]; + int out_pos = 0; + for (int i = 0; i < m_sample_count; ++i) + { + if (72 != m_input.Read (input, 0, 72)) + break; + for (int k = 0; k < 2; ++k) + { + byte v = input[k]; + int shift = v & 0xF; + v >>= 4; + short scale0 = ScaleMap[v+8]; + short scale1 = ScaleMap[v]; + int src = 35 * k + 2; + for (int j = 0; j < 35; ++j) + { + int dst = k + (j << 2); + v = input[src++]; + int x = v & 0xF; + if ((x & 8) != 0) + x |= -0x10; + int sample = ((scale0 * back[0,k] + scale1 * back[1,k]) >> 8) + (x << shift); + back[0,k] = back[1,k]; + back[1,k] = sample; + output[dst] = (short)sample; + + x = v >> 4; + if ((x & 8) != 0) + x |= -0x10; + sample = ((scale0 * back[0,k] + scale1 * back[1,k]) >> 8) + (x << shift); + back[0,k] = back[1,k]; + back[1,k] = sample; + output[dst + 2] = (short)sample; + } + } + Buffer.BlockCopy (output, 0, m_output, out_pos, 280); + out_pos += 280; + } + return m_output; + } + + static readonly short[] ScaleMap = { + 0, 240, 128, 192, 320, 460, 392, 488, + 0, 0, -12, -56, -88, -208, -220, -240, + }; + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index a1ed0a12..10967552 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -76,6 +76,8 @@ + +