diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
index 6fd6a98aa7..c7bcb84076 100644
--- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
+++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
@@ -93,6 +93,20 @@ namespace Ryujinx.Common.Memory
return copy;
}
+ ///
+ /// Copies into a newly rented byte memory buffer.
+ ///
+ /// The byte buffer to copy
+ /// A wrapping the rented memory with copied to it
+ public static IMemoryOwner RentCopy(ReadOnlySpan buffer)
+ {
+ var copy = RentImpl(buffer.Length);
+
+ buffer.CopyTo(copy.Memory.Span);
+
+ return copy;
+ }
+
private static ByteMemoryPoolBuffer RentImpl(int length)
{
if ((uint)length > Array.MaxLength)
diff --git a/src/Ryujinx.Common/Memory/BytesReadOnlySequenceSegment.cs b/src/Ryujinx.Common/Memory/BytesReadOnlySequenceSegment.cs
new file mode 100644
index 0000000000..9c4cd1ff36
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/BytesReadOnlySequenceSegment.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Buffers;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// A concrete implementation of ,
+ /// with methods to help build a full sequence.
+ ///
+ public sealed class BytesReadOnlySequenceSegment : ReadOnlySequenceSegment
+ {
+ public BytesReadOnlySequenceSegment(Memory memory) => Memory = memory;
+
+ public BytesReadOnlySequenceSegment Append(Memory memory)
+ {
+ var nextSegment = new BytesReadOnlySequenceSegment(memory)
+ {
+ RunningIndex = RunningIndex + Memory.Length
+ };
+
+ Next = nextSegment;
+
+ return nextSegment;
+ }
+ }
+}
diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
index 102cedc94e..4155bec89c 100644
--- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -134,6 +134,16 @@ namespace Ryujinx.Memory
/// Throw for unhandled invalid or unmapped memory accesses
ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false);
+ ///
+ /// Gets a read-only sequence of read-only memory blocks from CPU mapped memory.
+ ///
+ /// Virtual address of the data
+ /// Size of the data
+ /// True if read tracking is triggered on the memory
+ /// A read-only sequence of read-only memory of the data
+ /// Throw for unhandled invalid or unmapped memory accesses
+ ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false);
+
///
/// Gets a read-only span of data from CPU mapped memory.
///
diff --git a/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs b/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs
new file mode 100644
index 0000000000..232bfd8ea8
--- /dev/null
+++ b/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Memory.Range
+{
+ public struct PagedMemoryRangeCoalescingEnumerator
+ {
+ private PagedMemoryRangeEnumerator _enumerator;
+ private MemoryRange? _current;
+
+ public PagedMemoryRangeCoalescingEnumerator(ulong startAddress, int size, int pageSize, Func mapAddress)
+ {
+ _enumerator = new PagedMemoryRangeEnumerator(startAddress, size, pageSize, mapAddress);
+ }
+
+ public readonly MemoryRange Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _current!.Value;
+ }
+
+ ///
+ /// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop.
+ ///
+ public readonly PagedMemoryRangeCoalescingEnumerator GetEnumerator() => this;
+
+ public bool MoveNext()
+ {
+ if (_current is null)
+ {
+ if (_enumerator.MoveNext() == false)
+ {
+ _current = null;
+ return false;
+ }
+ }
+
+ if (_enumerator.HasCurrent)
+ {
+ MemoryRange combinedRange = _enumerator.Current;
+
+ while (_enumerator.MoveNext())
+ {
+ MemoryRange nextRange = _enumerator.Current;
+
+ if (combinedRange.EndAddress == nextRange.Address)
+ {
+ combinedRange = new MemoryRange(combinedRange.Address, combinedRange.Size + nextRange.Size);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ _current = combinedRange;
+ return true;
+ }
+
+ _current = null;
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs b/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
new file mode 100644
index 0000000000..2c1b15df96
--- /dev/null
+++ b/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Memory.Range
+{
+ public struct PagedMemoryRangeEnumerator
+ {
+ private readonly ulong _startAddress;
+ private readonly int _size;
+ private readonly int _pageSize;
+ private readonly ulong _pageMask;
+ private readonly Func _mapAddress;
+ private int _offset;
+ private MemoryRange? _current;
+
+ public PagedMemoryRangeEnumerator(ulong startAddress, int size, int pageSize, Func mapAddress)
+ {
+ _startAddress = startAddress;
+ _size = size;
+ _pageSize = pageSize;
+ _pageMask = (ulong)pageSize - 1;
+ _mapAddress = mapAddress;
+ _offset = 0;
+ }
+
+ public readonly MemoryRange Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _current!.Value;
+ }
+
+ internal readonly bool HasCurrent
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _current.HasValue;
+ }
+
+ ///
+ /// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop.
+ ///
+ public readonly PagedMemoryRangeEnumerator GetEnumerator() => this;
+
+ public bool MoveNext()
+ {
+ if (_offset == 0 && (_startAddress & _pageMask) != 0)
+ {
+ ulong rangeAddress = _mapAddress(_startAddress);
+
+ int rangeSize = Math.Min(_size, _pageSize - (int)(_startAddress & _pageMask));
+
+ SetCurrent(rangeAddress, rangeSize);
+
+ return true;
+ }
+
+ if (_offset < _size)
+ {
+ ulong rangeAddress = _mapAddress(_startAddress + (ulong)_offset);
+
+ int rangeSize = Math.Min(_size - _offset, _pageSize);
+
+ SetCurrent(rangeAddress, rangeSize);
+
+ return true;
+ }
+
+ _current = null;
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetCurrent(ulong address, int size)
+ {
+ _current = new MemoryRange(address, (ulong)size);
+ _offset += size;
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs
new file mode 100644
index 0000000000..bac9e2bbcf
--- /dev/null
+++ b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs
@@ -0,0 +1,169 @@
+using NUnit.Framework;
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Tests.Memory.RangeTests
+{
+ public class PagedMemoryRangeCoalescingEnumeratorTests
+ {
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For3AlignedPages_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 12;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For2PagesWithPartialFirstAndLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 6;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For4PagesWithPartialFirst_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For4PagesWithPartialLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_ForPartiallyDiscontiguous4PagesWithPartialLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 14;
+ const int PageSize = 4;
+ var memoryMap = new Dictionary()
+ {
+ { 0ul, 0ul }, { 4ul, 4ul }, { 8ul, 20ul }, { 12ul, 24ul },
+ };
+ ulong MemoryMapLookup(ulong a) => memoryMap.TryGetValue(a, out var value) ? value : a;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, MemoryMapLookup);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(2, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(PageSize * 2, results[0].Size);
+
+ Assert.AreEqual(20, results[1].Address);
+ Assert.AreEqual(PageSize + (Size % PageSize), results[1].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_ForFullyDiscontiguous4PagesWithPartialLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 14;
+ const int PageSize = 4;
+ var memoryMap = new Dictionary()
+ {
+ { 0ul, 0ul }, { 4ul, 10ul }, { 8ul, 20ul }, { 12ul, 30ul },
+ };
+ ulong MemoryMapLookup(ulong a) => memoryMap.TryGetValue(a, out var value) ? value : a;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, MemoryMapLookup);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(4, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(PageSize, results[0].Size);
+
+ Assert.AreEqual(10, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+
+ Assert.AreEqual(20, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+
+ Assert.AreEqual(30, results[3].Address);
+ Assert.AreEqual(Size % PageSize, results[3].Size);
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs
new file mode 100644
index 0000000000..1da9b02ab9
--- /dev/null
+++ b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs
@@ -0,0 +1,135 @@
+using NUnit.Framework;
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Tests.Memory.RangeTests
+{
+ public class PagedMemoryRangeEnumeratorTests
+ {
+ [Test]
+ public void PagedMemoryRangeEnumerator_For3AlignedPages_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 12;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(3, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(PageSize, results[0].Size);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+
+ Assert.AreEqual(8, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeEnumerator_For2PagesWithPartialFirstAndLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 6;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(2, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(PageSize - StartAddress, results[0].Size);
+ Assert.AreEqual(results[1].Address, results[0].EndAddress);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeEnumerator_For4PagesWithPartialFirst_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(4, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(PageSize - StartAddress, results[0].Size);
+ Assert.AreEqual(results[1].Address, results[0].EndAddress);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+ Assert.AreEqual(results[2].Address, results[1].EndAddress);
+
+ Assert.AreEqual(8, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+ Assert.AreEqual(results[3].Address, results[2].EndAddress);
+
+ Assert.AreEqual(12, results[3].Address);
+ Assert.AreEqual(PageSize, results[3].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeEnumerator_For4PagesWithPartialLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ {
+ results.Add(memoryRange);
+ }
+
+ // Assert
+ Assert.AreEqual(4, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(PageSize, results[0].Size);
+ Assert.AreEqual(results[1].Address, results[0].EndAddress);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+ Assert.AreEqual(results[2].Address, results[1].EndAddress);
+
+ Assert.AreEqual(8, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+ Assert.AreEqual(results[3].Address, results[2].EndAddress);
+
+ Assert.AreEqual(12, results[3].Address);
+ Assert.AreEqual(Size % PageSize, results[3].Size);
+ }
+ }
+}