using System; using System.ComponentModel; using System.IO; using System.Threading; using StormLibSharp.Native; #pragma warning disable CA1510 // Use ArgumentNullException throw helper #pragma warning disable CA1512 // Use ArgumentOutOfRangeException throw helper #pragma warning disable CA1513 // Use ObjectDisposedException throw helper #pragma warning disable CA2208 // Instantiate argument exceptions correctly namespace StormLibSharp { public class MpqFileStream : Stream { private MpqFileSafeHandle? _handle; private readonly FileAccess _accessType; private MpqArchive? _owner; internal MpqFileStream(MpqFileSafeHandle handle, FileAccess accessType, MpqArchive owner) { _handle = handle; _accessType = accessType; _owner = owner; } private void VerifyHandle() { if (_handle is null || _handle.IsInvalid || _handle.IsClosed) throw new ObjectDisposedException("MpqFileStream"); } public override bool CanRead { get { VerifyHandle(); return true; } } public override bool CanSeek { get { VerifyHandle(); return true; } } public override bool CanWrite { get { VerifyHandle(); return _accessType != FileAccess.Read; } } public override void Flush() { VerifyHandle(); _owner?.Flush(); } public override long Length { get { VerifyHandle(); uint high = 0; uint low = NativeMethods.SFileGetFileSize(_handle, ref high); ulong val = (high << 32) | low; return unchecked((long)val); } } public override long Position { get { VerifyHandle(); return NativeMethods.SFileGetFilePointer(_handle); } set { Seek(value, SeekOrigin.Begin); } } public override unsafe int Read(byte[] buffer, int offset, int count) { if (buffer is null) throw new ArgumentNullException(nameof(buffer)); if (offset > buffer.Length || (offset + count) > buffer.Length) throw new ArgumentException(); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); VerifyHandle(); bool success; uint read; fixed (byte* pb = &buffer[offset]) { NativeOverlapped overlapped = default; success = NativeMethods.SFileReadFile(_handle, new IntPtr(pb), unchecked((uint)count), out read, ref overlapped); } if (!success) { int lastError = Win32Methods.GetLastError(); if (lastError != 38) // EOF throw new Win32Exception(lastError); } return unchecked((int)read); } public override long Seek(long offset, SeekOrigin origin) { VerifyHandle(); uint low = unchecked((uint)(offset & 0xffffffffu)); uint high = unchecked((uint)(offset >> 32)); return NativeMethods.SFileSetFilePointer(_handle, low, ref high, (uint)origin); } public override void SetLength(long value) { throw new NotSupportedException(); } public override unsafe void Write(byte[] buffer, int offset, int count) { VerifyHandle(); if (buffer is null) throw new ArgumentNullException(nameof(buffer)); if (offset > buffer.Length || (offset + count) > buffer.Length) throw new ArgumentException(); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); VerifyHandle(); bool success; fixed (byte* pb = &buffer[offset]) { success = NativeMethods.SFileWriteFile(_handle, new IntPtr(pb), unchecked((uint)count), 0u); } if (!success) throw new Win32Exception(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { if (_handle is not null && !_handle.IsInvalid) { _handle.Close(); _handle = null; } _owner?.RemoveOwnedFile(this); _owner = null; } } // TODO: Seems like the right place for SFileGetFileInfo, but will need to determine // what value add these features have except for sophisticated debugging purposes // (like in Ladis' MPQ Editor app). public int ChecksumCrc32 { get { throw new NotImplementedException(); } } public byte[] GetMd5Hash() { throw new NotImplementedException(); } } }