using System; using System.Collections.Generic; namespace Ryujinx.Memory.Range { /// /// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to. /// public readonly struct MultiRange : IEquatable { private const ulong InvalidAddress = ulong.MaxValue; private readonly MemoryRange _singleRange; private readonly MemoryRange[] _ranges; private bool HasSingleRange => _ranges == null; /// /// Total of physical sub-ranges on the virtual memory region. /// public int Count => HasSingleRange ? 1 : _ranges.Length; /// /// Creates a new multi-range with a single physical region. /// /// Start address of the region /// Size of the region in bytes public MultiRange(ulong address, ulong size) { _singleRange = new MemoryRange(address, size); _ranges = null; } /// /// Creates a new multi-range with multiple physical regions. /// /// Array of physical regions /// is null public MultiRange(MemoryRange[] ranges) { _singleRange = MemoryRange.Empty; _ranges = ranges ?? throw new ArgumentNullException(nameof(ranges)); } /// /// Gets a slice of the multi-range. /// /// Offset of the slice into the multi-range in bytes /// Size of the slice in bytes /// A new multi-range representing the given slice of this one public MultiRange Slice(ulong offset, ulong size) { if (HasSingleRange) { if (_singleRange.Size - offset < size) { throw new ArgumentOutOfRangeException(nameof(size)); } return new MultiRange(_singleRange.Address + offset, size); } else { var ranges = new List(); foreach (MemoryRange range in _ranges) { if ((long)offset <= 0) { ranges.Add(new MemoryRange(range.Address, Math.Min(size, range.Size))); size -= range.Size; } else if (offset < range.Size) { ulong sliceSize = Math.Min(size, range.Size - offset); if (range.Address == InvalidAddress) { ranges.Add(new MemoryRange(range.Address, sliceSize)); } else { ranges.Add(new MemoryRange(range.Address + offset, sliceSize)); } size -= sliceSize; } if ((long)size <= 0) { break; } offset -= range.Size; } return new MultiRange(ranges.ToArray()); } } /// /// Gets the physical region at the specified index. /// /// Index of the physical region /// Region at the index specified /// is invalid public MemoryRange GetSubRange(int index) { if (HasSingleRange) { if (index != 0) { throw new ArgumentOutOfRangeException(nameof(index)); } return _singleRange; } else { if ((uint)index >= _ranges.Length) { throw new ArgumentOutOfRangeException(nameof(index)); } return _ranges[index]; } } /// /// Gets the physical region at the specified index, without explicit bounds checking. /// /// Index of the physical region /// Region at the index specified private MemoryRange GetSubRangeUnchecked(int index) { return HasSingleRange ? _singleRange : _ranges[index]; } /// /// Check if two multi-ranges overlap with each other. /// /// Other multi-range to check for overlap /// True if any sub-range overlaps, false otherwise public bool OverlapsWith(MultiRange other) { if (HasSingleRange && other.HasSingleRange) { return _singleRange.OverlapsWith(other._singleRange); } else { for (int i = 0; i < Count; i++) { MemoryRange currentRange = GetSubRangeUnchecked(i); for (int j = 0; j < other.Count; j++) { if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j))) { return true; } } } } return false; } /// /// Checks if a given multi-range is fully contained inside another. /// /// Multi-range to be checked /// True if all the sub-ranges on are contained inside the multi-range, with the same order, false otherwise public bool Contains(MultiRange other) { return FindOffset(other) >= 0; } /// /// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained /// inside the other multi-range, otherwise returns -1. /// /// Multi-range that should be fully contained inside this one /// Offset in bytes if fully contained, otherwise -1 public int FindOffset(MultiRange other) { int thisCount = Count; int otherCount = other.Count; if (thisCount == 1 && otherCount == 1) { MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0); MemoryRange currentFirstRange = GetSubRangeUnchecked(0); if (otherFirstRange.Address >= currentFirstRange.Address && otherFirstRange.EndAddress <= currentFirstRange.EndAddress) { return (int)(otherFirstRange.Address - currentFirstRange.Address); } } else if (thisCount >= otherCount) { ulong baseOffset = 0; MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0); MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1); for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++) { MemoryRange currentFirstRange = GetSubRangeUnchecked(i); MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1); if (otherCount > 1) { if (otherFirstRange.Address < currentFirstRange.Address || otherFirstRange.EndAddress != currentFirstRange.EndAddress) { continue; } if (otherLastRange.Address != currentLastRange.Address || otherLastRange.EndAddress > currentLastRange.EndAddress) { continue; } bool fullMatch = true; for (int j = 1; j < otherCount - 1; j++) { if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j))) { fullMatch = false; break; } } if (!fullMatch) { continue; } } else if (currentFirstRange.Address > otherFirstRange.Address || currentFirstRange.EndAddress < otherFirstRange.EndAddress) { continue; } return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address)); } } return -1; } /// /// Gets the total size of all sub-ranges in bytes. /// /// Total size in bytes public ulong GetSize() { if (HasSingleRange) { return _singleRange.Size; } ulong sum = 0; foreach (MemoryRange range in _ranges) { sum += range.Size; } return sum; } public override bool Equals(object obj) { return obj is MultiRange other && Equals(other); } public bool Equals(MultiRange other) { if (HasSingleRange && other.HasSingleRange) { return _singleRange.Equals(other._singleRange); } int thisCount = Count; if (thisCount != other.Count) { return false; } for (int i = 0; i < thisCount; i++) { if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i))) { return false; } } return true; } public override int GetHashCode() { if (HasSingleRange) { return _singleRange.GetHashCode(); } HashCode hash = new HashCode(); foreach (MemoryRange range in _ranges) { hash.Add(range); } return hash.ToHashCode(); } /// /// Returns a string summary of the ranges contained in the MultiRange. /// /// A string summary of the ranges contained within public override string ToString() { return HasSingleRange ? _singleRange.ToString() : string.Join(", ", _ranges); } } }