aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
blob: 79b5d743b99b1e8f865257128f042283caccc656 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Ryujinx.Common.Extensions
{
    public static class SequenceReaderExtensions
    {
        /// <summary>
        /// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
        /// Useful for debugging purposes.
        /// </summary>
        /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param>
        /// <param name="fileFullName">The path and name of the file to create and dump to</param>
        public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName)
        {
            var initialConsumed = reader.Consumed;

            reader.Rewind(initialConsumed);

            using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
            {
                while (reader.End == false)
                {
                    var span = reader.CurrentSpan;
                    fileStream.Write(span);
                    reader.Advance(span.Length);
                }
            }

            reader.SetConsumed(initialConsumed);
        }

        /// <summary>
        /// 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
        /// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
        /// </summary>
        /// <typeparam name="T">Type to get</typeparam>
        /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
        /// <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>
        /// <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>
        /// <remarks>
        /// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
        /// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
        /// To discourage use, it is recommended to call this method like the following:
        /// <c>
        ///     ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
        /// </c>
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
        public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
        {
            int lengthRequired = Unsafe.SizeOf<T>();

            ReadOnlySpan<byte> span = reader.UnreadSpan;
            if (lengthRequired <= span.Length)
            {
                reader.Advance(lengthRequired);

                copyDestinationIfRequiredDoNotUse = default;

                ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span);

                return ref spanOfT[0];
            }
            else
            {
                copyDestinationIfRequiredDoNotUse = default;

                Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);

                Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);

                if (!reader.TryCopyTo(valueBytesSpan))
                {
                    throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
                }

                reader.Advance(lengthRequired);

                return ref valueSpan[0];
            }
        }

        /// <summary>
        /// Reads an <see cref="int"/> as little endian.
        /// </summary>
        /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
        /// <param name="value">A location to receive the read value</param>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value)
        {
            if (!reader.TryReadLittleEndian(out value))
            {
                throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
            }
        }

        /// <summary>
        /// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
        /// </summary>
        /// <typeparam name="T">Type to read</typeparam>
        /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
        /// <param name="value">The target that will receive the read value</param>
        /// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged
        {
            if (!reader.TryReadUnmanaged(out value))
            {
                throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
            }
        }

        /// <summary>
        /// Sets the reader's position as bytes consumed.
        /// </summary>
        /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
        /// <param name="consumed">The number of bytes consumed</param>
        public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed)
        {
            reader.Rewind(reader.Consumed);
            reader.Advance(consumed);
        }

        /// <summary>
        /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
        /// structs - see remarks for full details.
        /// </summary>
        /// <typeparam name="T">Type to read</typeparam>
        /// <remarks>
        /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
        /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
        /// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
        /// </remarks>
        /// <returns>
        /// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
        {
            ReadOnlySpan<byte> span = reader.UnreadSpan;

            if (span.Length < sizeof(T))
            {
                return TryReadUnmanagedMultiSegment(ref reader, out value);
            }

            value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));

            reader.Advance(sizeof(T));

            return true;
        }

        private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
        {
            Debug.Assert(reader.UnreadSpan.Length < sizeof(T));

            // Not enough data in the current segment, try to peek for the data we need.
            T buffer = default;

            Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));

            if (!reader.TryCopyTo(tempSpan))
            {
                value = default;
                return false;
            }

            value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));

            reader.Advance(sizeof(T));

            return true;
        }
    }
}