216 lines
7.8 KiB
C#
216 lines
7.8 KiB
C#
|
using System;
|
||
|
using System.IO;
|
||
|
using System.IO.Compression;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Runtime.InteropServices;
|
||
|
|
||
|
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Binary data serializer.
|
||
|
/// </summary>
|
||
|
struct BinarySerializer
|
||
|
{
|
||
|
private readonly Stream _stream;
|
||
|
private Stream _activeStream;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a new binary serializer.
|
||
|
/// </summary>
|
||
|
/// <param name="stream">Stream to read from or write into</param>
|
||
|
public BinarySerializer(Stream stream)
|
||
|
{
|
||
|
_stream = stream;
|
||
|
_activeStream = stream;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads data from the stream.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||
|
/// <param name="data">Data read</param>
|
||
|
public void Read<T>(ref T data) where T : unmanaged
|
||
|
{
|
||
|
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
|
||
|
for (int offset = 0; offset < buffer.Length;)
|
||
|
{
|
||
|
offset += _activeStream.Read(buffer.Slice(offset));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tries to read data from the stream.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||
|
/// <param name="data">Data read</param>
|
||
|
/// <returns>True if the read was successful, false otherwise</returns>
|
||
|
public bool TryRead<T>(ref T data) where T : unmanaged
|
||
|
{
|
||
|
// Length is unknown on compressed streams.
|
||
|
if (_activeStream == _stream)
|
||
|
{
|
||
|
int size = Unsafe.SizeOf<T>();
|
||
|
if (_activeStream.Length - _activeStream.Position < size)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Read(ref data);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads data prefixed with a magic and size from the stream.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||
|
/// <param name="data">Data read</param>
|
||
|
/// <param name="magic">Expected magic value, for validation</param>
|
||
|
public void ReadWithMagicAndSize<T>(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<T>())
|
||
|
{
|
||
|
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength);
|
||
|
}
|
||
|
|
||
|
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1)).Slice(0, size);
|
||
|
for (int offset = 0; offset < buffer.Length;)
|
||
|
{
|
||
|
offset += _activeStream.Read(buffer.Slice(offset));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes data into the stream.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||
|
/// <param name="data">Data to be written</param>
|
||
|
public void Write<T>(ref T data) where T : unmanaged
|
||
|
{
|
||
|
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
|
||
|
_activeStream.Write(buffer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes data prefixed with a magic and size into the stream.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||
|
/// <param name="data">Data to write</param>
|
||
|
/// <param name="magic">Magic value to write</param>
|
||
|
public void WriteWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
|
||
|
{
|
||
|
int size = Unsafe.SizeOf<T>();
|
||
|
Write(ref magic);
|
||
|
Write(ref size);
|
||
|
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
|
||
|
_activeStream.Write(buffer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Indicates that all data that will be read from the stream has been compressed.
|
||
|
/// </summary>
|
||
|
public void BeginCompression()
|
||
|
{
|
||
|
CompressionAlgorithm algorithm = CompressionAlgorithm.None;
|
||
|
Read(ref algorithm);
|
||
|
|
||
|
if (algorithm == CompressionAlgorithm.Deflate)
|
||
|
{
|
||
|
_activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Indicates that all data that will be written into the stream should be compressed.
|
||
|
/// </summary>
|
||
|
/// <param name="algorithm">Compression algorithm that should be used</param>
|
||
|
public void BeginCompression(CompressionAlgorithm algorithm)
|
||
|
{
|
||
|
Write(ref algorithm);
|
||
|
|
||
|
if (algorithm == CompressionAlgorithm.Deflate)
|
||
|
{
|
||
|
_activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Indicates the end of a compressed chunck.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Any data written after this will not be compressed unless <see cref="BeginCompression(CompressionAlgorithm)"/> is called again.
|
||
|
/// Any data read after this will be assumed to be uncompressed unless <see cref="BeginCompression"/> is called again.
|
||
|
/// </remarks>
|
||
|
public void EndCompression()
|
||
|
{
|
||
|
if (_activeStream != _stream)
|
||
|
{
|
||
|
_activeStream.Dispose();
|
||
|
_activeStream = _stream;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads compressed data from the stream.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// <paramref name="data"/> must have the exact length of the uncompressed data,
|
||
|
/// otherwise decompression will fail.
|
||
|
/// </remarks>
|
||
|
/// <param name="stream">Stream to read from</param>
|
||
|
/// <param name="data">Buffer to write the uncompressed data into</param>
|
||
|
public static void ReadCompressed(Stream stream, Span<byte> 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Compresses and writes the compressed data into the stream.
|
||
|
/// </summary>
|
||
|
/// <param name="stream">Stream to write into</param>
|
||
|
/// <param name="data">Data to compress</param>
|
||
|
/// <param name="algorithm">Compression algorithm to be used</param>
|
||
|
public static void WriteCompressed(Stream stream, ReadOnlySpan<byte> 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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|