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.ReadExactly(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;
}
}
}
}