BinarySerializer.cs
1 using System; 2 using System.IO; 3 using System.IO.Compression; 4 using System.Runtime.CompilerServices; 5 using System.Runtime.InteropServices; 6 7 namespace Ryujinx.Graphics.Gpu.Shader.DiskCache 8 { 9 /// <summary> 10 /// Binary data serializer. 11 /// </summary> 12 struct BinarySerializer 13 { 14 private readonly Stream _stream; 15 private Stream _activeStream; 16 17 /// <summary> 18 /// Creates a new binary serializer. 19 /// </summary> 20 /// <param name="stream">Stream to read from or write into</param> 21 public BinarySerializer(Stream stream) 22 { 23 _stream = stream; 24 _activeStream = stream; 25 } 26 27 /// <summary> 28 /// Reads data from the stream. 29 /// </summary> 30 /// <typeparam name="T">Type of the data</typeparam> 31 /// <param name="data">Data read</param> 32 public readonly void Read<T>(ref T data) where T : unmanaged 33 { 34 Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1)); 35 for (int offset = 0; offset < buffer.Length;) 36 { 37 offset += _activeStream.Read(buffer[offset..]); 38 } 39 } 40 41 /// <summary> 42 /// Tries to read data from the stream. 43 /// </summary> 44 /// <typeparam name="T">Type of the data</typeparam> 45 /// <param name="data">Data read</param> 46 /// <returns>True if the read was successful, false otherwise</returns> 47 public readonly bool TryRead<T>(ref T data) where T : unmanaged 48 { 49 // Length is unknown on compressed streams. 50 if (_activeStream == _stream) 51 { 52 int size = Unsafe.SizeOf<T>(); 53 if (_activeStream.Length - _activeStream.Position < size) 54 { 55 return false; 56 } 57 } 58 59 Read(ref data); 60 return true; 61 } 62 63 /// <summary> 64 /// Reads data prefixed with a magic and size from the stream. 65 /// </summary> 66 /// <typeparam name="T">Type of the data</typeparam> 67 /// <param name="data">Data read</param> 68 /// <param name="magic">Expected magic value, for validation</param> 69 public readonly void ReadWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged 70 { 71 uint actualMagic = 0; 72 int size = 0; 73 Read(ref actualMagic); 74 Read(ref size); 75 76 if (actualMagic != magic) 77 { 78 throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidMagic); 79 } 80 81 // Structs are expected to expand but not shrink between versions. 82 if (size > Unsafe.SizeOf<T>()) 83 { 84 throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength); 85 } 86 87 Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1))[..size]; 88 for (int offset = 0; offset < buffer.Length;) 89 { 90 offset += _activeStream.Read(buffer[offset..]); 91 } 92 } 93 94 /// <summary> 95 /// Writes data into the stream. 96 /// </summary> 97 /// <typeparam name="T">Type of the data</typeparam> 98 /// <param name="data">Data to be written</param> 99 public readonly void Write<T>(ref T data) where T : unmanaged 100 { 101 Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1)); 102 _activeStream.Write(buffer); 103 } 104 105 /// <summary> 106 /// Writes data prefixed with a magic and size into the stream. 107 /// </summary> 108 /// <typeparam name="T">Type of the data</typeparam> 109 /// <param name="data">Data to write</param> 110 /// <param name="magic">Magic value to write</param> 111 public readonly void WriteWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged 112 { 113 int size = Unsafe.SizeOf<T>(); 114 Write(ref magic); 115 Write(ref size); 116 Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1)); 117 _activeStream.Write(buffer); 118 } 119 120 /// <summary> 121 /// Indicates that all data that will be read from the stream has been compressed. 122 /// </summary> 123 public void BeginCompression() 124 { 125 CompressionAlgorithm algorithm = CompressionAlgorithm.None; 126 Read(ref algorithm); 127 128 switch (algorithm) 129 { 130 case CompressionAlgorithm.None: 131 break; 132 case CompressionAlgorithm.Deflate: 133 _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true); 134 break; 135 case CompressionAlgorithm.Brotli: 136 _activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true); 137 break; 138 default: 139 throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\""); 140 } 141 } 142 143 /// <summary> 144 /// Indicates that all data that will be written into the stream should be compressed. 145 /// </summary> 146 /// <param name="algorithm">Compression algorithm that should be used</param> 147 public void BeginCompression(CompressionAlgorithm algorithm) 148 { 149 Write(ref algorithm); 150 151 switch (algorithm) 152 { 153 case CompressionAlgorithm.None: 154 break; 155 case CompressionAlgorithm.Deflate: 156 _activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true); 157 break; 158 case CompressionAlgorithm.Brotli: 159 _activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true); 160 break; 161 default: 162 throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\""); 163 } 164 } 165 166 /// <summary> 167 /// Indicates the end of a compressed chunck. 168 /// </summary> 169 /// <remarks> 170 /// Any data written after this will not be compressed unless <see cref="BeginCompression(CompressionAlgorithm)"/> is called again. 171 /// Any data read after this will be assumed to be uncompressed unless <see cref="BeginCompression"/> is called again. 172 /// </remarks> 173 public void EndCompression() 174 { 175 if (_activeStream != _stream) 176 { 177 _activeStream.Dispose(); 178 _activeStream = _stream; 179 } 180 } 181 182 /// <summary> 183 /// Reads compressed data from the stream. 184 /// </summary> 185 /// <remarks> 186 /// <paramref name="data"/> must have the exact length of the uncompressed data, 187 /// otherwise decompression will fail. 188 /// </remarks> 189 /// <param name="stream">Stream to read from</param> 190 /// <param name="data">Buffer to write the uncompressed data into</param> 191 public static void ReadCompressed(Stream stream, Span<byte> data) 192 { 193 CompressionAlgorithm algorithm = (CompressionAlgorithm)stream.ReadByte(); 194 195 switch (algorithm) 196 { 197 case CompressionAlgorithm.None: 198 stream.ReadExactly(data); 199 break; 200 case CompressionAlgorithm.Deflate: 201 stream = new DeflateStream(stream, CompressionMode.Decompress, true); 202 for (int offset = 0; offset < data.Length;) 203 { 204 offset += stream.Read(data[offset..]); 205 } 206 stream.Dispose(); 207 break; 208 case CompressionAlgorithm.Brotli: 209 stream = new BrotliStream(stream, CompressionMode.Decompress, true); 210 for (int offset = 0; offset < data.Length;) 211 { 212 offset += stream.Read(data[offset..]); 213 } 214 stream.Dispose(); 215 break; 216 } 217 } 218 219 /// <summary> 220 /// Compresses and writes the compressed data into the stream. 221 /// </summary> 222 /// <param name="stream">Stream to write into</param> 223 /// <param name="data">Data to compress</param> 224 /// <param name="algorithm">Compression algorithm to be used</param> 225 public static void WriteCompressed(Stream stream, ReadOnlySpan<byte> data, CompressionAlgorithm algorithm) 226 { 227 stream.WriteByte((byte)algorithm); 228 229 switch (algorithm) 230 { 231 case CompressionAlgorithm.None: 232 stream.Write(data); 233 break; 234 case CompressionAlgorithm.Deflate: 235 stream = new DeflateStream(stream, CompressionLevel.Fastest, true); 236 stream.Write(data); 237 stream.Dispose(); 238 break; 239 case CompressionAlgorithm.Brotli: 240 stream = new BrotliStream(stream, CompressionLevel.Fastest, true); 241 stream.Write(data); 242 stream.Dispose(); 243 break; 244 } 245 } 246 } 247 }