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 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.Slice(offset)); } } /// /// Tries to read data from the stream. /// /// Type of the data /// Data read /// True if the read was successful, false otherwise public 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 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)).Slice(0, size); for (int offset = 0; offset < buffer.Length;) { offset += _activeStream.Read(buffer.Slice(offset)); } } /// /// Writes data into the stream. /// /// Type of the data /// Data to be written public 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 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); if (algorithm == CompressionAlgorithm.Deflate) { _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true); } } /// /// 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); if (algorithm == CompressionAlgorithm.Deflate) { _activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true); } } /// /// 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.Slice(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.SmallestSize, true); stream.Write(data); stream.Dispose(); break; } } } }