diff --git a/Aaru.Archives/Stfs/Files.cs b/Aaru.Archives/Stfs/Files.cs index 354bf1a56..25dde84d7 100644 --- a/Aaru.Archives/Stfs/Files.cs +++ b/Aaru.Archives/Stfs/Files.cs @@ -1,6 +1,8 @@ using System; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; using Aaru.CommonTypes.Structs; +using Aaru.Filters; using FileAttributes = System.IO.FileAttributes; namespace Aaru.Archives; @@ -125,5 +127,31 @@ public sealed partial class Stfs return ErrorNumber.NoError; } + /// + public ErrorNumber GetEntry(int entryNumber, out IFilter filter) + { + filter = null; + + if(!Opened) return ErrorNumber.NotOpened; + + if(entryNumber < 0 || entryNumber >= _entries.Length) return ErrorNumber.OutOfRange; + + var stream = new StfsStream(_stream, + _entries[entryNumber].StartingBlock, + _entries[entryNumber].FileSize, + _headerSize, + _blockSeparation, + _isConsole); + + filter = new ZZZNoFilter(); + ErrorNumber errno = filter.Open(stream); + + if(errno == ErrorNumber.NoError) return ErrorNumber.NoError; + + stream.Close(); + + return errno; + } + #endregion } \ No newline at end of file diff --git a/Aaru.Archives/Stfs/Open.cs b/Aaru.Archives/Stfs/Open.cs index 1baa7412b..679850ed0 100644 --- a/Aaru.Archives/Stfs/Open.cs +++ b/Aaru.Archives/Stfs/Open.cs @@ -101,6 +101,10 @@ public sealed partial class Stfs _entries[i].IsDirectory = (entries[i].FilenameLength & 0x80) > 0; } + _headerSize = (int)header.Metadata.HeaderSize; + _blockSeparation = vd.BlockSeparation; + _isConsole = header.Magic == PackageMagic.Console; + Opened = true; return ErrorNumber.NoError; diff --git a/Aaru.Archives/Stfs/Stfs.cs b/Aaru.Archives/Stfs/Stfs.cs index cf1409aa1..7d40e2aa4 100644 --- a/Aaru.Archives/Stfs/Stfs.cs +++ b/Aaru.Archives/Stfs/Stfs.cs @@ -6,7 +6,10 @@ namespace Aaru.Archives; public sealed partial class Stfs : IArchive { + byte _blockSeparation; FileEntry[] _entries; + int _headerSize; + bool _isConsole; Stream _stream; #region IArchive Members diff --git a/Aaru.Archives/Stfs/StfsStream.cs b/Aaru.Archives/Stfs/StfsStream.cs new file mode 100644 index 000000000..fcba0c256 --- /dev/null +++ b/Aaru.Archives/Stfs/StfsStream.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; + +namespace Aaru.Archives; + +public sealed partial class Stfs +{ +#region Nested type: StfsStream + + private class StfsStream : Stream + { + readonly Stream _baseStream; + readonly int _blockSeparation; + readonly int _headerSize; + readonly bool _isConsole; + readonly long _length; + readonly int _startingBlock; + long _position; + + internal StfsStream(Stream baseStream, int length, int startingBlock, int headerSize, byte blockSeparation, + bool isConsole) + { + _baseStream = baseStream; + _length = length; + _position = 0; + _startingBlock = startingBlock; + _headerSize = headerSize; + _blockSeparation = blockSeparation; + _isConsole = isConsole; + } + + /// + public override bool CanRead => true; + /// + public override bool CanSeek => true; + /// + public override bool CanWrite => false; + /// + public override long Length => _length; + + /// + public override long Position + { + get => _position; + set => Seek(value, SeekOrigin.Begin); + } + + /// + public override void Flush() + { + // No-op + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + int totalRead = 0; + + // Validate parameters + ArgumentNullException.ThrowIfNull(buffer); + + if(offset < 0 || count < 0) throw new ArgumentOutOfRangeException("Offset and count must be non-negative"); + + if(buffer.Length - offset < count) + throw new ArgumentException("Buffer too small for the requested offset and count"); + + if(_position >= _length) return 0; // EOF + + // Calculate block for current position + int currentBlock = ComputeBlockNumber((int)(_position / 0x1000) + _startingBlock, + _headerSize, + _blockSeparation, + _isConsole); + + // Calculate position within block + int blockOffset = (int)(_position % 0x1000); + + // Calculate absolute position in the stream + long absolutePosition = BlockToPosition(currentBlock, _headerSize) + blockOffset; + + // Seek to the absolute position + _baseStream.Position = absolutePosition; + + // Calculate bytes left to read to fill a block + int leftInBlock = 0x1000 - blockOffset; + + // Read bytes left in the block + _baseStream.ReadExactly(buffer, offset, leftInBlock); + + // Update position and counters + _position += leftInBlock; + offset += leftInBlock; + count -= leftInBlock; + totalRead += leftInBlock; + + // Read full blocks + while(count >= 0x1000 && _position < _length) + { + // Calculate again block number for current position + currentBlock = ComputeBlockNumber((int)(_position / 0x1000) + _startingBlock, + _headerSize, + _blockSeparation, + _isConsole); + + // Calculate absolute position in the stream + absolutePosition = BlockToPosition(currentBlock, _headerSize); + + // Seek to the absolute position + _baseStream.Position = absolutePosition; + + // Read the full block + _baseStream.ReadExactly(buffer, offset, 0x1000); + + _position += 0x1000; + offset += 0x1000; + count -= 0x1000; + totalRead += 0x1000; + + if(_position >= _length) break; // EOF + } + + // Read remaining bytes + if(count <= 0 || _position >= _length) return totalRead; + + // Calculate again block number for current position + currentBlock = ComputeBlockNumber((int)(_position / 0x1000) + _startingBlock, + _headerSize, + _blockSeparation, + _isConsole); + + // Calculate absolute position in the stream + absolutePosition = BlockToPosition(currentBlock, _headerSize); + + // Calculate bytes left to read to fill a block + leftInBlock = (int)(_position % 0x1000); + + // Seek to the absolute position + _baseStream.Position = absolutePosition + leftInBlock; + + // Read remaining bytes + int toRead = (int)Math.Min(count, _length - _position); + _baseStream.ReadExactly(buffer, offset, toRead); + _position += toRead; + totalRead += toRead; + + return totalRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + long newPos = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin)) + }; + + if(newPos < 0 || newPos > _length) throw new IOException("Attempt to seek outside the strean"); + + _position = newPos; + + return _position; + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException("Stream is read-only"); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Stream is read-only"); + } + } + +#endregion +} \ No newline at end of file diff --git a/Aaru.Archives/Stfs/Unimplemented.cs b/Aaru.Archives/Stfs/Unimplemented.cs deleted file mode 100644 index b31d9d200..000000000 --- a/Aaru.Archives/Stfs/Unimplemented.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Aaru.CommonTypes.Enums; -using Aaru.CommonTypes.Interfaces; - -namespace Aaru.Archives; - -public sealed partial class Stfs -{ -#region IArchive Members - - /// - public ErrorNumber GetEntry(int entryNumber, out IFilter filter) => throw new NotImplementedException(); - -#endregion -} \ No newline at end of file