using System; using System.IO; using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Shader.DiskCache { /// /// Binary data serializer. /// struct BinarySerializer { private readonly Stream _stream; private Stream _activeStream; /// /// Creates a new binary serializer. /// /// Stream to read from or write into public BinarySerializer(Stream stream) { _stream = stream; _activeStream = stream; } /// /// Reads data from the stream. /// /// Type of the data /// Data read public readonly void Read(ref T data) where T : unmanaged { Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1)); for (int offset = 0; offset < buffer.Length;) { offset += _activeStream.Read(buffer[offset..]); } } /// /// Tries to read data from the stream. /// /// Type of the data /// Data read /// True if the read was successful, false otherwise public readonly bool TryRead(ref T data) where T : unmanaged { // Length is unknown on compressed streams. if (_activeStream == _stream) { int size = Unsafe.SizeOf(); if (_activeStream.Length - _activeStream.Position < size) { return false; } } Read(ref data); return true; } /// /// Reads data prefixed with a magic and size from the stream. /// /// Type of the data /// Data read /// Expected magic value, for validation public readonly void ReadWithMagicAndSize(ref T data, uint magic) where T : unmanaged { uint actualMagic = 0; int size = 0; Read(ref actualMagic); Read(ref size); if (actualMagic != magic) { throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidMagic); } // Structs are expected to expand but not shrink between versions. if (size > Unsafe.SizeOf()) { throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength); } Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1))[..size]; for (int offset = 0; offset < buffer.Length;) { offset += _activeStream.Read(buffer[offset..]); } } /// /// Writes data into the stream. /// /// Type of the data /// Data to be written public readonly void Write(ref T data) where T : unmanaged { Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1)); _activeStream.Write(buffer); } /// /// Writes data prefixed with a magic and size into the stream. /// /// Type of the data /// Data to write /// Magic value to write public readonly void WriteWithMagicAndSize(ref T data, uint magic) where T : unmanaged { int size = Unsafe.SizeOf(); Write(ref magic); Write(ref size); Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1)); _activeStream.Write(buffer); } /// /// Indicates that all data that will be read from the stream has been compressed. /// public void BeginCompression() { CompressionAlgorithm algorithm = CompressionAlgorithm.None; Read(ref algorithm); switch (algorithm) { case CompressionAlgorithm.None: break; case CompressionAlgorithm.Deflate: _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true); break; case CompressionAlgorithm.Brotli: _activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true); break; default: throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\""); } } /// /// Indicates that all data that will be written into the stream should be compressed. /// /// Compression algorithm that should be used public void BeginCompression(CompressionAlgorithm algorithm) { Write(ref algorithm); switch (algorithm) { case CompressionAlgorithm.None: break; case CompressionAlgorithm.Deflate: _activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true); break; case CompressionAlgorithm.Brotli: _activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true); break; default: throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\""); } } /// /// Indicates the end of a compressed chunck. /// /// /// Any data written after this will not be compressed unless is called again. /// Any data read after this will be assumed to be uncompressed unless is called again. /// public void EndCompression() { if (_activeStream != _stream) { _activeStream.Dispose(); _activeStream = _stream; } } /// /// Reads compressed data from the stream. /// /// /// must have the exact length of the uncompressed data, /// otherwise decompression will fail. /// /// Stream to read from /// Buffer to write the uncompressed data into public static void ReadCompressed(Stream stream, Span data) { CompressionAlgorithm algorithm = (CompressionAlgorithm)stream.ReadByte(); switch (algorithm) { case CompressionAlgorithm.None: stream.Read(data); break; case CompressionAlgorithm.Deflate: stream = new DeflateStream(stream, CompressionMode.Decompress, true); for (int offset = 0; offset < data.Length;) { offset += stream.Read(data[offset..]); } stream.Dispose(); break; case CompressionAlgorithm.Brotli: stream = new BrotliStream(stream, CompressionMode.Decompress, true); for (int offset = 0; offset < data.Length;) { offset += stream.Read(data[offset..]); } stream.Dispose(); break; } } /// /// Compresses and writes the compressed data into the stream. /// /// Stream to write into /// Data to compress /// Compression algorithm to be used public static void WriteCompressed(Stream stream, ReadOnlySpan data, CompressionAlgorithm algorithm) { stream.WriteByte((byte)algorithm); switch (algorithm) { case CompressionAlgorithm.None: stream.Write(data); break; case CompressionAlgorithm.Deflate: stream = new DeflateStream(stream, CompressionLevel.Fastest, true); stream.Write(data); stream.Dispose(); break; case CompressionAlgorithm.Brotli: stream = new BrotliStream(stream, CompressionLevel.Fastest, true); stream.Write(data); stream.Dispose(); break; } } } }