From a63e9e4d6b6edec41d9444b2a301b9580a616b56 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 12 Oct 2016 04:25:40 +0400 Subject: [PATCH] (IBinaryStream): new interface for binary streams. --- GameRes/ArcView.cs | 161 ++++++++++++- GameRes/BinaryStream.cs | 507 ++++++++++++++++++++++++++++++++++++++++ GameRes/GameRes.csproj | 1 + 3 files changed, 657 insertions(+), 12 deletions(-) create mode 100644 GameRes/BinaryStream.cs diff --git a/GameRes/ArcView.cs b/GameRes/ArcView.cs index 29a50fa2..fd2efa82 100644 --- a/GameRes/ArcView.cs +++ b/GameRes/ArcView.cs @@ -28,6 +28,7 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; using System.Text; +using GameRes.Utility; namespace GameRes { @@ -482,17 +483,21 @@ namespace GameRes #endregion } - public class ArcStream : System.IO.Stream + public class ArcStream : Stream, IBinaryStream { private Frame m_view; private long m_start; private long m_size; private long m_position; + public string Name { get; set; } + public uint Signature { get { return ReadSignature(); } } + public Stream AsStream { get { return this; } } + public override bool CanRead { get { return !disposed; } } - public override bool CanSeek { get { return !disposed; } } + public override bool CanSeek { get { return !disposed; } } public override bool CanWrite { get { return false; } } - public override long Length { get { return m_size; } } + public override long Length { get { return m_size; } } public override long Position { get { return m_position; } @@ -536,6 +541,147 @@ namespace GameRes return m_view.ReadUInt32 (m_start); } + byte[] m_header; + int m_header_size; + + public CowArray ReadHeader (int size) + { + if (m_header_size < size) + { + if (null == m_header || m_header.Length < size) + Array.Resize (ref m_header, (size + 0xF) & ~0xF); + long position = m_start + m_header_size; + m_header_size += m_view.Read (position, m_header, m_header_size, (uint)(size - m_header_size)); + } + size = Math.Min (size, m_header_size); + Position = size; + return new CowArray (m_header, 0, size); + } + + public int PeekByte () + { + if (m_position >= m_size) + return -1; + return m_view.ReadByte (m_start+m_position); + } + + public override int ReadByte () + { + int b = PeekByte(); + if (-1 != b) + ++m_position; + return b; + } + + public sbyte ReadSByte () + { + int b = ReadByte(); + if (-1 == b) + throw new EndOfStreamException(); + return (sbyte)b; + } + + public short ReadInt16 () + { + if (m_position + 2 > m_size) + throw new EndOfStreamException(); + var v = m_view.ReadInt16 (m_start+m_position); + m_position += 2; + return v; + } + + public ushort ReadUInt16 () + { + return (ushort)ReadInt16(); + } + + public int ReadInt24 () + { + if (m_position + 3 > m_size) + throw new EndOfStreamException(); + int v = m_view.ReadUInt16 (m_start+m_position); + v |= m_view.ReadByte (m_start+m_position+2); + m_position += 3; + return v; + } + + public int ReadInt32 () + { + if (m_position + 4 > m_size) + throw new EndOfStreamException(); + var v = m_view.ReadInt32 (m_start+m_position); + m_position += 4; + return v; + } + + public uint ReadUInt32 () + { + return (uint)ReadInt32(); + } + + public long ReadInt64 () + { + if (m_position + 8 > m_size) + throw new EndOfStreamException(); + var v = m_view.ReadInt64 (m_start+m_position); + m_position += 8; + return v; + } + + public ulong ReadUInt64 () + { + return (ulong)ReadInt64(); + } + + byte[] m_string_buf; + + public string ReadCString (int length) + { + return ReadCString (length, Encodings.cp932); + } + + public string ReadCString (int length, Encoding enc) + { + if (null == m_string_buf || m_string_buf.Length < length) + m_string_buf = new byte[Math.Max (length, 0x20)]; + + length = Read (m_string_buf, 0, length); + return Binary.GetCString (m_string_buf, 0, length); + } + + public string ReadCString () + { + return ReadCString (Encodings.cp932); + } + + public string ReadCString (Encoding enc) + { + if (null == m_string_buf) + m_string_buf = new byte[0x20]; + int size = 0; + for (;;) + { + int b = ReadByte(); + if (-1 == b || 0 == b) + break; + if (m_string_buf.Length == size) + { + Array.Resize (ref m_string_buf, checked(size*3/2)); + } + m_string_buf[size++] = (byte)b; + } + return enc.GetString (m_string_buf, 0, size); + } + + public byte[] ReadBytes (int count) + { + if (m_position >= m_size) + return new byte[0]; + var bytes = m_view.ReadBytes (m_start+m_position, (uint)Math.Min (count, m_size - m_position)); + m_position += bytes.Length; + return bytes; + } + #region System.IO.Stream methods public override void Flush() { @@ -569,15 +715,6 @@ namespace GameRes return read; } - public override int ReadByte () - { - if (m_position >= m_size) - return -1; - byte b = m_view.ReadByte (m_start+m_position); - ++m_position; - return b; - } - public override void Write (byte[] buffer, int offset, int count) { throw new NotSupportedException("GameRes.ArcStream.Write method is not supported"); diff --git a/GameRes/BinaryStream.cs b/GameRes/BinaryStream.cs new file mode 100644 index 00000000..76aa3505 --- /dev/null +++ b/GameRes/BinaryStream.cs @@ -0,0 +1,507 @@ +//! \file BinaryStream.cs +//! \date Sun Mar 13 00:37:07 2016 +//! \brief Wrapper around IO.Stream to read binary streams. +// +// Copyright (C) 2016 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; +using System.Collections.Generic; +using System.IO; +using System.Text; +using GameRes.Utility; + +namespace GameRes +{ + public interface IBinaryStream + { + /// + /// Name of the stream (could be name of the underlying file) or an empty string. + /// + string Name { get; } + /// + /// First 4 bytes of the stream as a little-endian integer. + /// + uint Signature { get; } + Stream AsStream { get; } + long Length { get; } + long Position { get; set; } + + long Seek (long offset, SeekOrigin origin); + + int Read (byte[] buffer, int offset, int count); + int ReadByte (); + + /// + /// Read next byte from stream without advancing stream position. + /// + /// Next byte, or -1 if end of stream is reached. + int PeekByte (); + + sbyte ReadSByte (); + short ReadInt16 (); + ushort ReadUInt16 (); + int ReadInt24 (); + int ReadInt32 (); + uint ReadUInt32 (); + long ReadInt64 (); + ulong ReadUInt64 (); + + /// + /// Read first bytes of stream and return them in a copy-on-write array. + /// + CowArray ReadHeader (int size); + + /// + /// Read zero-terminated string at most of bytes length from stream in + /// specified encoding. + /// Advances stream position forward by bytes, regardless of where zero byte + /// was encountered. + /// + string ReadCString (int length, Encoding enc); + /// + /// Does ReadCString with CP932 encoding. + /// + string ReadCString (int length); + + /// + /// Read zero-terminated string from stream in specefied encoding. + /// Stream is positioned after a zero byte that terminates a string, or at end of stream, whichever + /// comes first. + /// + string ReadCString (Encoding enc); + /// + /// Does ReadCString with CP932 encoding. + /// + string ReadCString (); + + /// + /// Read bytes starting from the current position into array and return it. + /// If there's less than bytes left in the stream, array size will be + /// smaller than count. + /// + byte[] ReadBytes (int count); + } + + /// + /// Array segment with copy-on-write semantics. + /// + public struct CowArray : IReadOnlyList + { + T[] m_source; + int m_offset; + int m_count; + bool m_own_copy; + + public CowArray (T[] src) : this (src, 0, src.Length) + { + } + + public CowArray (T[] src, int start, int length) + { + m_source = src; + m_offset = start; + m_count = length; + m_own_copy = false; + } + + public int Count { get { return m_count; } } + public int Length { get { return Count; } } + public T this[int pos] + { + get { return m_source[m_offset+pos]; } + set + { + if (!m_own_copy) + { + m_source = ToArray(); + m_offset = 0; + m_own_copy = true; + } + m_source[pos] = value; + } + } + + public IEnumerator GetEnumerator () + { + for (int i = 0; i < m_count; ++i) + yield return m_source[m_offset + i]; + } + + IEnumerator IEnumerable.GetEnumerator () + { + return GetEnumerator(); + } + + public T[] ToArray () + { + if (m_own_copy) + return m_source; + var copy = new T[m_count]; + Array.Copy (m_source, m_offset, copy, 0, m_count); + return copy; + } + } + + public static class CowByteArray + { + public static ushort ToUInt16 (this CowArray arr, int index) + { + return (ushort)(arr[index] | arr[index+1] << 8); + } + + public static short ToInt16 (this CowArray arr, int index) + { + return (short)(arr[index] | arr[index+1] << 8); + } + + public static int ToInt24 (this CowArray arr, int index) + { + return arr[index] | arr[index+1] << 8 | arr[index+2] << 16; + } + + public static uint ToUInt32 (this CowArray arr, int index) + { + return (uint)(arr[index] | arr[index+1] << 8 | arr[index+2] << 16 | arr[index+3] << 24); + } + + public static int ToInt32 (this CowArray arr, int index) + { + return (int)ToUInt32 (arr, index); + } + + public static ulong ToUInt64 (this CowArray arr, int index) + { + return (ulong)ToUInt32 (arr, index) | ((ulong)ToUInt32 (arr, index+4) << 32); + } + + public static long ToInt64 (this CowArray arr, int index) + { + return (long)ToUInt64 (arr, index); + } + } + + public class BinaryStream : Stream, IBinaryStream + { + Stream m_source; + bool m_should_dispose; + byte[] m_buffer; + int m_buffer_pos; + int m_buffer_end; + Lazy m_signature; + byte[] m_header; + int m_header_size; + + public string Name { get; private set; } + public uint Signature { get { return m_signature.Value; } } + public Stream AsStream { get { return this; } } + + public BinaryStream (Stream input, bool leave_open = false) : this (input, "", leave_open) + { + } + + public BinaryStream (Stream input, string name, bool leave_open = false) + { + if (null == name) + name = ""; + m_source = input; + m_should_dispose = !leave_open; + m_buffer = new byte[0x10]; + m_buffer_pos = 0; + m_buffer_end = 0; + m_signature = new Lazy (ReadSignature); + m_header_size = 0; + Name = name; + } + + uint ReadSignature () + { + if (m_header_size >= 4) + { + return LittleEndian.ToUInt32 (m_header, 0); + } + var pos = Position; + if (pos != 0) + Position = 0; + uint signature = ReadUInt32(); + Position = pos; + return signature; + } + + public CowArray ReadHeader (int size) + { + if (m_header_size < size) + { + if (null == m_header || m_header.Length < size) + Array.Resize (ref m_header, (size + 0xF) & ~0xF); + Position = m_header_size; + m_header_size += Read (m_header, m_header_size, size - m_header_size); + } + size = Math.Min (size, m_header_size); + Position = size; + return new CowArray (m_header, 0, size); + } + + private int FillBuffer (int count) + { + int cached = m_buffer_end - m_buffer_pos; + if (count <= cached) + return count; + if (m_buffer_pos + count > m_buffer.Length) + { + if (cached + count > m_buffer.Length) + { + var copy = new byte[(count + 0xF) & ~0xF]; + if (cached > 0) + Buffer.BlockCopy (m_buffer, m_buffer_pos, copy, 0, cached); + m_buffer = copy; + } + else if (cached > 0) + { + Buffer.BlockCopy (m_buffer, m_buffer_pos, m_buffer, 0, cached); + } + m_buffer_pos = 0; + m_buffer_end = cached; + } + m_buffer_end += m_source.Read (m_buffer, m_buffer_end, count - cached); + return Math.Min (count, m_buffer_end - m_buffer_pos); + } + + public int PeekByte () + { + if (1 != FillBuffer (1)) + return -1; + return m_buffer[m_buffer_pos]; + } + + public sbyte ReadSByte () + { + if (1 != FillBuffer (1)) + throw new EndOfStreamException(); + return (sbyte)m_buffer[m_buffer_pos++]; + } + + public short ReadInt16 () + { + if (2 != FillBuffer (2)) + throw new EndOfStreamException(); + short v = LittleEndian.ToInt16 (m_buffer, m_buffer_pos); + m_buffer_pos += 2; + return v; + } + + public ushort ReadUInt16 () + { + return (ushort)ReadInt16(); + } + + public int ReadInt24 () + { + if (3 != FillBuffer (3)) + throw new EndOfStreamException(); + int v = LittleEndian.ToUInt16 (m_buffer, m_buffer_pos); + v |= m_buffer[m_buffer_pos+2] << 16; + m_buffer_pos += 3; + return v; + } + + public int ReadInt32 () + { + if (4 != FillBuffer (4)) + throw new EndOfStreamException(); + int v = LittleEndian.ToInt32 (m_buffer, m_buffer_pos); + m_buffer_pos += 4; + return v; + } + + public uint ReadUInt32 () + { + return (uint)ReadInt32(); + } + + public long ReadInt64 () + { + if (8 != FillBuffer (8)) + throw new EndOfStreamException(); + long v = LittleEndian.ToInt64 (m_buffer, m_buffer_pos); + m_buffer_pos += 8; + return v; + } + + public ulong ReadUInt64 () + { + return (ulong)ReadInt64(); + } + + public string ReadCString (int length) + { + return ReadCString (length, Encodings.cp932); + } + + public string ReadCString (int length, Encoding enc) + { + length = FillBuffer (length); + int i; + for (i = 0; i < length; ++i) + if (0 == m_buffer[m_buffer_pos+i]) + break; + string s = enc.GetString (m_buffer, m_buffer_pos, i); + m_buffer_pos += length; + return s; + } + + public string ReadCString () + { + return ReadCString (Encodings.cp932); + } + + public string ReadCString (Encoding enc) + { + int count = 0; + int cached; + for (;;) + { + cached = FillBuffer (count+1); + if (cached < count+1) + break; + if (0 == m_buffer[m_buffer_pos+count]) + break; + ++count; + } + var s = enc.GetString (m_buffer, m_buffer_pos, count); + m_buffer_pos += Math.Min (count+1, cached); + return s; + } + + public byte[] ReadBytes (int count) + { + var buffer = new byte[count]; + int pos = 0; + int cached = m_buffer_end - m_buffer_pos; + if (cached > 0) + { + Buffer.BlockCopy (m_buffer, m_buffer_pos, buffer, 0, cached); + pos = cached; + m_buffer_end = m_buffer_pos = 0; + count -= cached; + } + pos += m_source.Read (buffer, pos, count); + if (pos < buffer.Length) + { + var copy = new byte[pos]; + Buffer.BlockCopy (buffer, 0, copy, 0, copy.Length); + buffer = copy; + } + return buffer; + } + + #region IO.Stream Members + public override bool CanRead { get { return m_source.CanRead; } } + public override bool CanSeek { get { return m_source.CanSeek; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return m_source.Length; } } + public override long Position + { + get { return m_source.Position - (m_buffer_end - m_buffer_pos); } + set + { + m_source.Position = value; + m_buffer_end = m_buffer_pos = 0; + } + } + + public override int Read (byte[] buffer, int offset, int count) + { + int read = 0; + int cached = Math.Min (m_buffer_end - m_buffer_pos, count); + if (cached > 0) + { + Buffer.BlockCopy (m_buffer, m_buffer_pos, buffer, offset, cached); + m_buffer_pos += cached; + offset += cached; + count -= cached; + read += cached; + } + if (count > 0) + { + read += m_source.Read (buffer, offset, count); + } + return read; + } + + public override int ReadByte () + { + int b; + if (m_buffer_pos < m_buffer_end) + b = m_buffer[m_buffer_pos++]; + else + b = m_source.ReadByte(); + return b; + } + + public override void Flush() + { + m_source.Flush(); + } + + public override long Seek (long offset, SeekOrigin origin) + { + if (SeekOrigin.Current == origin) + offset -= m_buffer_end - m_buffer_pos; + m_buffer_end = m_buffer_pos = 0; + return m_source.Seek (offset, origin); + } + + public override void SetLength (long length) + { + throw new NotSupportedException ("BinaryStream.SetLength method is not supported"); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotSupportedException ("BinaryStream.Write method is not supported"); + } + + public override void WriteByte (byte value) + { + throw new NotSupportedException ("BinaryStream.WriteByte method is not supported"); + } + #endregion + + #region IDisposable Members + bool _disposed = false; + protected override void Dispose (bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + if (m_should_dispose) + m_source.Dispose(); + } + _disposed = true; + base.Dispose (disposing); + } + #endregion + } +} diff --git a/GameRes/GameRes.csproj b/GameRes/GameRes.csproj index dc7b0328..d44ee8ab 100644 --- a/GameRes/GameRes.csproj +++ b/GameRes/GameRes.csproj @@ -66,6 +66,7 @@ +