aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs')
-rw-r--r--src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs
new file mode 100644
index 00000000..1200f1de
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Ngc.Detail
+{
+ class CompressedArray
+ {
+ private const int MaxUncompressedEntries = 64;
+ private const int CompressedEntriesPerBlock = 64;
+ private const int BitsPerWord = Set.BitsPerWord;
+
+ private readonly struct BitfieldRange
+ {
+ private readonly uint _range;
+ private readonly int _baseValue;
+
+ public int BitfieldIndex => (int)(_range & 0x7ffffff);
+ public int BitfieldLength => (int)(_range >> 27) + 1;
+ public int BaseValue => _baseValue;
+
+ public BitfieldRange(uint range, int baseValue)
+ {
+ _range = range;
+ _baseValue = baseValue;
+ }
+ }
+
+ private uint[] _bitfieldRanges;
+ private uint[] _bitfields;
+ private int[] _uncompressedArray;
+
+ public int Length => (_bitfieldRanges.Length / 2) * CompressedEntriesPerBlock + _uncompressedArray.Length;
+
+ public int this[int index]
+ {
+ get
+ {
+ var ranges = GetBitfieldRanges();
+
+ int rangeBlockIndex = index / CompressedEntriesPerBlock;
+
+ if (rangeBlockIndex < ranges.Length)
+ {
+ var range = ranges[rangeBlockIndex];
+
+ int bitfieldLength = range.BitfieldLength;
+ int bitfieldOffset = (index % CompressedEntriesPerBlock) * bitfieldLength;
+ int bitfieldIndex = range.BitfieldIndex + (bitfieldOffset / BitsPerWord);
+ int bitOffset = bitfieldOffset % BitsPerWord;
+
+ ulong bitfieldValue = _bitfields[bitfieldIndex];
+
+ // If the bit fields crosses the word boundary, let's load the next one to ensure we
+ // have access to the full value.
+ if (bitOffset + bitfieldLength > BitsPerWord)
+ {
+ bitfieldValue |= (ulong)_bitfields[bitfieldIndex + 1] << 32;
+ }
+
+ int value = (int)(bitfieldValue >> bitOffset) & ((1 << bitfieldLength) - 1);
+
+ // Sign-extend.
+ int remainderBits = BitsPerWord - bitfieldLength;
+ value <<= remainderBits;
+ value >>= remainderBits;
+
+ return value + range.BaseValue;
+ }
+ else if (rangeBlockIndex < _uncompressedArray.Length + _bitfieldRanges.Length * BitsPerWord)
+ {
+ return _uncompressedArray[index % MaxUncompressedEntries];
+ }
+
+ return 0;
+ }
+ }
+
+ private ReadOnlySpan<BitfieldRange> GetBitfieldRanges()
+ {
+ return MemoryMarshal.Cast<uint, BitfieldRange>(_bitfieldRanges);
+ }
+
+ public bool Import(ref BinaryReader reader)
+ {
+ if (!reader.Read(out int bitfieldRangesCount) ||
+ reader.AllocateAndReadArray(ref _bitfieldRanges, bitfieldRangesCount) != bitfieldRangesCount)
+ {
+ return false;
+ }
+
+ if (!reader.Read(out int bitfieldsCount) || reader.AllocateAndReadArray(ref _bitfields, bitfieldsCount) != bitfieldsCount)
+ {
+ return false;
+ }
+
+ return reader.Read(out byte uncompressedArrayLength) &&
+ reader.AllocateAndReadArray(ref _uncompressedArray, uncompressedArrayLength, MaxUncompressedEntries) == uncompressedArrayLength;
+ }
+ }
+}