Files
Matt Nadareski 7689c6dd07 Libraries
This change looks dramatic, but it's just separating out the already-split namespaces into separate top-level folders. In theory, every single one could be built into their own Nuget package. `SabreTools.Serialization` still builds the normal Nuget package that is used by all other projects and includes all namespaces.
2026-03-21 16:26:56 -04:00

183 lines
5.1 KiB
C#

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();
}
}
}