using AssetRipper.IO.Files.Streams.Smart; using Microsoft.Win32.SafeHandles; namespace AssetRipper.IO.Files.Streams; /// /// Read a slice of a file using a SafeFileHandle directly with System.IO.RandomAccess. /// This allows a container file with multiple logical files (eg, AssetBundles) /// to be streamed as if they are separate files, without buffering the entire file at once. /// internal sealed class RandomAccessStream : Stream { public SafeFileHandle Handle { get; } public FileStream Parent { get; } public long BaseOffset { get; } /// /// Raw position in file. /// private long position = 0; public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => false; public override long Length { get; } // position corrected for logical file offset. public override long Position { get => position - BaseOffset; set => position = value + BaseOffset; } public RandomAccessStream(FileStream parent, long offset, long length) { if (parent.Length < offset + length) { throw new ArgumentException("The parent stream is not long enough for the given offset and length."); } Parent = parent; Handle = parent.SafeFileHandle; BaseOffset = offset; Length = length; position = BaseOffset; } public override void Flush() { // Read-only streams shouldn't flush. } public override int Read(byte[] buffer, int offset, int count) { return Read(buffer.AsSpan(offset, count)); } public override int Read(Span buffer) { long toRead = Math.Min(buffer.Length, Length - Position); int read = RandomAccess.Read(Handle, buffer[..(int)toRead], position); position += read; return read; } public override int ReadByte() { if (Position >= Length) { return -1; } Span result = stackalloc byte[1]; int read = RandomAccess.Read(Handle, result, position); if (read > 0) { position += read; return result[0]; } return -1; } public override long Seek(long offset, SeekOrigin origin) { switch (origin) { case SeekOrigin.Current: Position += offset; break; case SeekOrigin.Begin: Position = offset; break; case SeekOrigin.End: position = (Length - offset) + BaseOffset; break; } return Position; } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } ~RandomAccessStream() { GC.KeepAlive(Parent); } }