aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs
blob: 1200f1def6de710cd462d8e89504e6bdf2641da7 (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
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;
        }
    }
}