From 7c5b0568bc3e5b39922f07dfc6a4daf6ada006a9 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 17 Mar 2018 12:23:24 +0400 Subject: [PATCH] (Legacy): A5R archives. --- Legacy/Pinky/ArcA5R.cs | 291 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 Legacy/Pinky/ArcA5R.cs diff --git a/Legacy/Pinky/ArcA5R.cs b/Legacy/Pinky/ArcA5R.cs new file mode 100644 index 00000000..31caef12 --- /dev/null +++ b/Legacy/Pinky/ArcA5R.cs @@ -0,0 +1,291 @@ +//! \file ArcA5R.cs +//! \date 2018 Mar 16 +//! \brief Pinky Soft 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 System.Linq; +using GameRes.Compression; + +namespace GameRes.Formats.Pinky +{ + internal class A5Segment + { + public uint Offset; + public uint Size; + public uint UnpackedSize; + public byte Type; + public byte Compression; + + public bool IsCompressed { get { return 3 == Compression; } } + } + + internal class A5rEntry : PackedEntry + { + public IEnumerable Segments; + } + + [Export(typeof(ArchiveFormat))] + public class A5rOpener : ArchiveFormat + { + public override string Tag { get { return "A5R"; } } + public override string Description { get { return "Pinky Soft resource archive"; } } + public override uint Signature { get { return 0x53524350; } } // 'PCRS' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public A5rOpener () + { + Signatures = new uint[] { 0x53524350, 0x42494C50 }; // 'PLIB' + Extensions = new string[] { "a5r", "a5e" }; + } + + public override ArcFile TryOpen (ArcView file) + { + uint id = file.View.ReadUInt32 (0); + if (file.View.ReadUInt32 (4) != ~id) + return null; + int count = file.View.ReadInt32 (0x30); + if (!IsSaneCount (count)) + return null; + uint index_offset = file.View.ReadUInt32 (0x34); + if (index_offset >= file.MaxOffset) + return null; + + var base_name = Path.GetFileNameWithoutExtension (file.Name); + uint next_offset = file.View.ReadUInt32 (index_offset); + var segments = new A5Segment[count]; + for (int i = 0; i < count; ++i) + { + var segment = new A5Segment { + Offset = next_offset, + UnpackedSize = file.View.ReadUInt32 (index_offset+4), + Type = file.View.ReadByte (index_offset+8), + Compression = file.View.ReadByte (index_offset+9), + }; + next_offset = file.View.ReadUInt32 (index_offset+0xA); + if (next_offset > file.MaxOffset || next_offset < segment.Offset) + return null; + segment.Size = (uint)(next_offset - segment.Offset); + segments[i] = segment; + index_offset += 0xA; + } + var dir = new List (count); + var riff_buffer = new byte[8]; + for (int i = 0; i < count; ) + { + A5rEntry entry; + var segment = segments[i]; + var name = string.Format ("{0}#{1:D5}", base_name, i); + if (0x3C == segment.Type) + { + Stream input = file.CreateStream (segment.Offset, segment.Size); + if (3 == segment.Compression) + input = new ZLibStream (input, CompressionMode.Decompress); + using (input) + { + if (8 == input.Read (riff_buffer, 0, 8) && riff_buffer.AsciiEqual ("RIFF")) + { + uint riff_size = riff_buffer.ToUInt32 (4); + entry = new A5rEntry { + Name = name + ".wav", + Type = "audio", + Offset = segment.Offset, + Size = 0, + UnpackedSize = 0, + }; + var segment_list = new List(); + for (;;) + { + entry.Size += segment.Size; + entry.UnpackedSize += segment.UnpackedSize; + entry.IsPacked |= segment.Compression == 3; + segment_list.Add (segment); + ++i; + if (i >= count || entry.UnpackedSize >= riff_size) + break; + segment = segments[i]; + if (segment.Type != 0x3C) + break; + } + entry.Segments = segment_list; + dir.Add (entry); + continue; + } + } + } + if (0x3E == segment.Type) + name += ".bmp"; + entry = new A5rEntry { + Name = name, + Type = 0x3E == segment.Type ? "image" : "", + Offset = segment.Offset, + Size = segment.Size, + UnpackedSize = segment.UnpackedSize, + IsPacked = segment.Compression == 3, + Segments = new A5Segment[1] { segment }, + }; + dir.Add (entry); + ++i; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var a5ent = (A5rEntry)entry; + Stream input; + if (a5ent.Segments.Count() == 1) + { + input = arc.File.CreateStream (entry.Offset, entry.Size); + if (a5ent.IsPacked) + input = new ZLibStream (input, CompressionMode.Decompress); + } + else + { + input = new A5rStream (arc.File, a5ent); + } + return input; + } + } + + internal class A5rStream : Stream + { + ArcView m_file; + A5rEntry m_entry; + IEnumerator m_segment; + Stream m_stream; + long m_offset = 0; + bool m_eof = false; + + public override bool CanRead { get { return !disposed; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return m_entry.UnpackedSize; } } + public override long Position + { + get { return m_offset; } + set { throw new NotSupportedException ("A5rStream.Position not supported."); } + } + + public A5rStream (ArcView file, A5rEntry entry) + { + m_file = file; + m_entry = entry; + m_segment = entry.Segments.GetEnumerator(); + NextSegment(); + } + + private void NextSegment () + { + if (!m_segment.MoveNext()) + { + m_eof = true; + return; + } + if (null != m_stream) + m_stream.Dispose(); + var segment = m_segment.Current; + m_stream = m_file.CreateStream (segment.Offset, segment.Size); + if (segment.IsCompressed) + m_stream = new ZLibStream (m_stream, CompressionMode.Decompress); + } + + public override int Read (byte[] buffer, int offset, int count) + { + int total = 0; + while (!m_eof && count > 0) + { + int read = m_stream.Read (buffer, offset, count); + if (0 != read) + { + m_offset += read; + total += read; + offset += read; + count -= read; + } + if (0 != count) + NextSegment(); + } + return total; + } + + public override int ReadByte () + { + int b = -1; + while (!m_eof) + { + b = m_stream.ReadByte(); + if (-1 != b) + break; + NextSegment(); + } + return b; + } + + public override void Flush () + { + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException ("A5rStream.Seek method is not supported"); + } + + public override void SetLength (long length) + { + throw new NotSupportedException ("A5rStream.SetLength method is not supported"); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotSupportedException ("A5rStream.Write method is not supported"); + } + + public override void WriteByte (byte value) + { + throw new NotSupportedException("A5rStream.WriteByte method is not supported"); + } + + #region IDisposable Members + bool disposed = false; + protected override void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + if (null != m_stream) + m_stream.Dispose(); + m_segment.Dispose(); + } + disposed = true; + base.Dispose (disposing); + } + } + #endregion + } +}