/ src / Ryujinx.Common / Extensions / SequenceReaderExtensions.cs
SequenceReaderExtensions.cs
  1  using System;
  2  using System.Buffers;
  3  using System.Diagnostics;
  4  using System.Runtime.CompilerServices;
  5  using System.Runtime.InteropServices;
  6  
  7  namespace Ryujinx.Common.Extensions
  8  {
  9      public static class SequenceReaderExtensions
 10      {
 11          /// <summary>
 12          /// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
 13          /// Useful for debugging purposes.
 14          /// </summary>
 15          /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param>
 16          /// <param name="fileFullName">The path and name of the file to create and dump to</param>
 17          public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName)
 18          {
 19              var initialConsumed = reader.Consumed;
 20  
 21              reader.Rewind(initialConsumed);
 22  
 23              using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
 24              {
 25                  while (reader.End == false)
 26                  {
 27                      var span = reader.CurrentSpan;
 28                      fileStream.Write(span);
 29                      reader.Advance(span.Length);
 30                  }
 31              }
 32  
 33              reader.SetConsumed(initialConsumed);
 34          }
 35  
 36          /// <summary>
 37          /// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value
 38          /// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
 39          /// </summary>
 40          /// <typeparam name="T">Type to get</typeparam>
 41          /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
 42          /// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param>
 43          /// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns>
 44          /// <remarks>
 45          /// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
 46          /// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
 47          /// To discourage use, it is recommended to call this method like the following:
 48          /// <c>
 49          ///     ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
 50          /// </c>
 51          /// </remarks>
 52          /// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
 53          public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
 54          {
 55              int lengthRequired = Unsafe.SizeOf<T>();
 56  
 57              ReadOnlySpan<byte> span = reader.UnreadSpan;
 58              if (lengthRequired <= span.Length)
 59              {
 60                  reader.Advance(lengthRequired);
 61  
 62                  copyDestinationIfRequiredDoNotUse = default;
 63  
 64                  ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span);
 65  
 66                  return ref spanOfT[0];
 67              }
 68              else
 69              {
 70                  copyDestinationIfRequiredDoNotUse = default;
 71  
 72                  Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);
 73  
 74                  Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);
 75  
 76                  if (!reader.TryCopyTo(valueBytesSpan))
 77                  {
 78                      throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
 79                  }
 80  
 81                  reader.Advance(lengthRequired);
 82  
 83                  return ref valueSpan[0];
 84              }
 85          }
 86  
 87          /// <summary>
 88          /// Reads an <see cref="int"/> as little endian.
 89          /// </summary>
 90          /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
 91          /// <param name="value">A location to receive the read value</param>
 92          /// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
 93          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 94          public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value)
 95          {
 96              if (!reader.TryReadLittleEndian(out value))
 97              {
 98                  throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
 99              }
100          }
101  
102          /// <summary>
103          /// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
104          /// </summary>
105          /// <typeparam name="T">Type to read</typeparam>
106          /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
107          /// <param name="value">The target that will receive the read value</param>
108          /// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
109          [MethodImpl(MethodImplOptions.AggressiveInlining)]
110          public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged
111          {
112              if (!reader.TryReadUnmanaged(out value))
113              {
114                  throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
115              }
116          }
117  
118          /// <summary>
119          /// Sets the reader's position as bytes consumed.
120          /// </summary>
121          /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
122          /// <param name="consumed">The number of bytes consumed</param>
123          public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed)
124          {
125              reader.Rewind(reader.Consumed);
126              reader.Advance(consumed);
127          }
128  
129          /// <summary>
130          /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
131          /// structs - see remarks for full details.
132          /// </summary>
133          /// <typeparam name="T">Type to read</typeparam>
134          /// <remarks>
135          /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
136          /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
137          /// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
138          /// </remarks>
139          /// <returns>
140          /// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
141          /// </returns>
142          [MethodImpl(MethodImplOptions.AggressiveInlining)]
143          public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
144          {
145              ReadOnlySpan<byte> span = reader.UnreadSpan;
146  
147              if (span.Length < sizeof(T))
148              {
149                  return TryReadUnmanagedMultiSegment(ref reader, out value);
150              }
151  
152              value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
153  
154              reader.Advance(sizeof(T));
155  
156              return true;
157          }
158  
159          private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
160          {
161              Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
162  
163              // Not enough data in the current segment, try to peek for the data we need.
164              T buffer = default;
165  
166              Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
167  
168              if (!reader.TryCopyTo(tempSpan))
169              {
170                  value = default;
171                  return false;
172              }
173  
174              value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
175  
176              reader.Advance(sizeof(T));
177  
178              return true;
179          }
180      }
181  }