• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 22018525015

14 Feb 2026 01:48PM UTC coverage: 34.4% (+0.02%) from 34.379%
22018525015

push

github

SamboyCoding
Improve handling of endianess

* Use BinaryPrimitives instead of BitConverter
* Add IsLittleEndian property to EndianAwareBinaryReader
* Remove byte array allocations for reading big endian primitives

1819 of 6636 branches covered (27.41%)

Branch coverage included in aggregate %.

10 of 20 new or added lines in 3 files covered. (50.0%)

4212 of 10896 relevant lines covered (38.66%)

211063.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.47
/LibCpp2IL/ClassReadingBinaryReader.cs
1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.IO;
5
using System.Linq;
6
using System.Reflection;
7
using System.Text;
8
using System.Threading;
9

10
namespace LibCpp2IL;
11

12
public abstract class ClassReadingBinaryReader : EndianAwareBinaryReader
13
{
14
    /// <summary>
15
    /// Set this to true to enable storing of amount of bytes read of each readable structure.
16
    /// </summary>
17
    public static bool EnableReadableSizeInformation = false;
18

19
    private SpinLock PositionShiftLock;
20

21
    public bool is32Bit;
22
    private MemoryStream? _memoryStream;
23

24
    public ulong PointerSize => is32Bit ? 4ul : 8ul;
2,115,862✔
25

26
    protected bool _hasFinishedInitialRead;
27
    private bool _inReadableRead;
28
    public ConcurrentDictionary<Type, int> BytesReadPerClass = new();
72✔
29
    
30
    public abstract float MetadataVersion { get; }
31

32

33
    public ClassReadingBinaryReader(MemoryStream input) : base(input)
72✔
34
    {
35
        _memoryStream = input;
72✔
36
    }
72✔
37

38
    public ClassReadingBinaryReader(Stream input) : base(input)
×
39
    {
40
        _memoryStream = null;
×
41
    }
×
42

43
    public long Position
44
    {
45
        get => BaseStream.Position;
9,059,723✔
46
        set => BaseStream.Position = value;
8,822,432✔
47
    }
48

49
    public long Length => BaseStream.Length;
2,115,862✔
50

51
    internal virtual object? ReadPrimitive(Type type, bool overrideArchCheck = false)
52
    {
53
        if (type == typeof(bool))
2,605,780!
54
            return ReadBoolean();
×
55

56
        if (type == typeof(char))
2,605,780!
57
            return ReadChar();
×
58

59
        if (type == typeof(int))
2,605,780✔
60
            return ReadInt32();
848,220✔
61

62
        if (type == typeof(uint))
1,757,560✔
63
            return ReadUInt32();
1,604,194✔
64

65
        if (type == typeof(short))
153,366!
66
            return ReadInt16();
×
67

68
        if (type == typeof(ushort))
153,366!
69
            return ReadUInt16();
×
70

71
        if (type == typeof(sbyte))
153,366!
72
            return ReadSByte();
×
73

74
        if (type == typeof(byte))
153,366!
75
            return ReadByte();
×
76

77
        if (type == typeof(long))
153,366!
78
            return is32Bit && !overrideArchCheck ? ReadInt32() : ReadInt64();
153,366✔
79

80
        if (type == typeof(ulong))
×
81
            return is32Bit && !overrideArchCheck ? ReadUInt32() : ReadUInt64();
×
82

83
        if (type == typeof(float))
×
84
            return ReadSingle();
×
85

86
        if (type == typeof(double))
×
87
            return ReadDouble();
×
88

89
        return null;
×
90
    }
91

92
    public uint ReadUnityCompressedUIntAtRawAddr(long offset, out int bytesRead)
93
    {
94
        GetLockOrThrow();
210✔
95

96
        try
97
        {
98
            return ReadUnityCompressedUIntAtRawAddrNoLock(offset, out bytesRead);
210✔
99
        }
100
        finally
101
        {
102
            ReleaseLock();
210✔
103
        }
210✔
104
    }
210✔
105

106
    protected internal uint ReadUnityCompressedUIntAtRawAddrNoLock(long offset, out int bytesRead)
107
    {
108
        if (offset >= 0)
19,350✔
109
            Position = offset;
19,350✔
110

111
        //Ref Unity.IL2CPP.dll, Unity.IL2CPP.Metadata.MetadataUtils::WriteCompressedUInt32
112
        //Read first byte
113
        var b = ReadByte();
19,350✔
114
        bytesRead = 1;
19,350✔
115
        if (b < 128)
19,350✔
116
            return b;
12,188✔
117
        if (b == 240)
7,162✔
118
        {
119
            //Full Uint
120
            bytesRead = 5;
120✔
121
            return ReadUInt32();
120✔
122
        }
123

124
        //Special constant values
125
        if (b == byte.MaxValue)
7,042✔
126
            return uint.MaxValue;
36✔
127
        if (b == 254)
7,006✔
128
            return uint.MaxValue - 1;
12✔
129

130
        if ((b & 192) == 192)
6,994✔
131
        {
132
            //3 more to read
133
            bytesRead = 4;
690✔
134
            return (b & ~192U) << 24 | (uint)(ReadByte() << 16) | (uint)(ReadByte() << 8) | ReadByte();
690✔
135
        }
136

137
        if ((b & 128) == 128)
6,304!
138
        {
139
            //1 more to read
140
            bytesRead = 2;
6,304✔
141
            return (b & ~128U) << 8 | ReadByte();
6,304✔
142
        }
143

144

145
        throw new Exception($"How did we even get here? Invalid compressed int first byte {b}");
×
146
    }
147

148
    public int ReadUnityCompressedIntAtRawAddr(long position, out int bytesRead)
149
        => ReadUnityCompressedIntAtRawAddr(position, true, out bytesRead);
×
150

151
    protected internal int ReadUnityCompressedIntAtRawAddr(long position, bool doLock, out int bytesRead)
152
    {
153
        //Ref libil2cpp, il2cpp\utils\ReadCompressedInt32
154
        uint unsigned;
155
        if (doLock)
18,978!
156
            unsigned = ReadUnityCompressedUIntAtRawAddr(position, out bytesRead);
×
157
        else
158
            unsigned = ReadUnityCompressedUIntAtRawAddrNoLock(position, out bytesRead);
18,978✔
159

160
        if (unsigned == uint.MaxValue)
18,978✔
161
            return int.MinValue;
30✔
162

163
        var isNegative = (unsigned & 1) == 1;
18,948✔
164
        unsigned >>= 1;
18,948✔
165
        if (isNegative)
18,948✔
166
            return -(int)(unsigned + 1);
174✔
167

168
        return (int)unsigned;
18,774✔
169
    }
170

171
    private T InternalReadClass<T>(bool overrideArchCheck = false) where T : new()
172
    {
173
        return (T)InternalReadClass(typeof(T), overrideArchCheck);
2,258,343✔
174
    }
175

176
    private T InternalReadReadableClass<T>() where T : ReadableClass, new()
177
    {
178
        var t = new T { MetadataVersion = MetadataVersion };
12,240,282✔
179

180
        if (!_inReadableRead)
12,240,282✔
181
        {
182
            _inReadableRead = true;
11,449,731✔
183
            t.Read(this);
11,449,731✔
184
            _inReadableRead = false;
11,449,731✔
185
        }
186
        else
187
        {
188
            t.Read(this);
790,551✔
189
        }
190

191
        return t;
12,240,282✔
192
    }
193

194
    private object InternalReadClass(Type type, bool overrideArchCheck = false)
195
    {
196
        if (type.IsPrimitive)
2,258,343!
197
        {
198
            return ReadAndConvertPrimitive(overrideArchCheck, type);
2,258,343✔
199
        }
200

201
        if (type.IsEnum)
×
202
        {
203
            var value = ReadPrimitive(type.GetEnumUnderlyingType());
×
204

205
            return value!;
×
206
        }
207

208
        throw new("Support for reading classes has been removed. Please inherit from ReadableClass and call ReadReadable on your local binary reader.");
×
209
    }
210

211
    private object ReadAndConvertPrimitive(bool overrideArchCheck, Type type)
212
    {
213
        var value = ReadPrimitive(type, overrideArchCheck);
2,258,343✔
214

215
        //32-bit fixes...
216
        if (value is uint && type == typeof(ulong))
2,258,343!
217
            value = Convert.ToUInt64(value);
×
218
        if (value is int && type == typeof(long))
2,258,343!
219
            value = Convert.ToInt64(value);
×
220

221
        return value!;
2,258,343✔
222
    }
223

224
    public T[] ReadClassArrayAtRawAddr<T>(long offset, long count) where T : new()
225
    {
226
        var t = new T[count];
246✔
227

228
        GetLockOrThrow();
246✔
229

230
        try
231
        {
232
            if (offset != -1) Position = offset;
492✔
233

234
            for (var i = 0; i < count; i++)
4,517,178✔
235
            {
236
                t[i] = InternalReadClass<T>();
2,258,343✔
237
            }
238

239
            return t;
246✔
240
        }
241
        finally
242
        {
243
            ReleaseLock();
246✔
244
        }
246✔
245
    }
246✔
246

247
    public string ReadStringToNull(ulong offset) => ReadStringToNull((long)offset);
7,644✔
248

249
    public virtual string ReadStringToNull(long offset)
250
    {
251
        GetLockOrThrow();
9,238✔
252

253
        try
254
        {
255
            return ReadStringToNullNoLock(offset);
9,238✔
256
        }
257
        finally
258
        {
259
            ReleaseLock();
9,238✔
260
        }
9,238✔
261
    }
9,238✔
262

263
    internal string ReadStringToNullNoLock(long offset)
264
    {
265
        var builder = new List<byte>();
1,182,912✔
266

267
        if (offset != -1)
1,182,912✔
268
            Position = offset;
1,182,912✔
269

270
        try
271
        {
272
            byte b;
273
            while ((b = ReadByte()) != 0)
23,387,018✔
274
                builder.Add(b);
22,204,106✔
275

276
            return Encoding.UTF8.GetString(builder.ToArray());
1,182,912✔
277
        }
278
        finally
279
        {
280
            var bytesRead = (int)(Position - offset);
1,182,912✔
281
            TrackRead<string>(bytesRead);
1,182,912✔
282
        }
1,182,912✔
283
    }
1,182,912✔
284

285
    public string ReadStringToNullAtCurrentPos()
286
        => ReadStringToNullNoLock(-1);
×
287

288
    public byte[] ReadByteArrayAtRawAddress(long offset, int count)
289
    {
290
        GetLockOrThrow();
19,299✔
291

292
        try
293
        {
294
            return ReadByteArrayAtRawAddressNoLock(offset, count);
19,299✔
295
        }
296
        finally
297
        {
298
            ReleaseLock();
19,299✔
299
        }
19,299✔
300
    }
19,299✔
301

302
    protected internal byte[] ReadByteArrayAtRawAddressNoLock(long offset, int count)
303
    {
304
        if (offset != -1)
21,693✔
305
            Position = offset;
21,693✔
306

307
        if (count == 0)
21,693✔
308
            return [];
165✔
309

310
        try
311
        {
312
            var ret = new byte[count];
21,528✔
313
            Read(ret, 0, count);
21,528✔
314

315
            return ret;
21,528✔
316
        }
317
        finally
318
        {
319
            TrackRead<byte>(count, false);
21,528✔
320
        }
21,528✔
321
    }
21,528✔
322

323
    protected internal void GetLockOrThrow()
324
    {
325
        var obtained = false;
4,617,962✔
326
        PositionShiftLock.Enter(ref obtained);
4,617,962✔
327

328
        if (!obtained)
4,617,962!
329
            throw new Exception("Failed to obtain lock");
×
330
    }
4,617,962✔
331

332
    protected internal void ReleaseLock()
333
    {
334
        PositionShiftLock.Exit();
4,617,962✔
335
    }
4,617,962✔
336

337
    public ulong ReadNUintAtRawAddress(long offset)
338
    {
339
        if (offset > Length)
×
340
            throw new EndOfStreamException($"ReadNUintAtRawAddress: Offset 0x{offset:X} is beyond the end of the stream (length 0x{Length:X})");
×
341

342
        GetLockOrThrow();
×
343

344
        try
345
        {
346
            Position = offset;
×
347
            return ReadNUint();
×
348
        }
349
        finally
350
        {
351
            ReleaseLock();
×
352

353
            TrackRead<ulong>((int)PointerSize, false);
×
354
        }
×
355
    }
×
356

357
    public ulong[] ReadNUintArrayAtRawAddress(long offset, int count)
358
    {
359
        if (offset > Length)
1,057,931!
360
            throw new EndOfStreamException($"ReadNUintArrayAtRawAddress: Offset 0x{offset:X} is beyond the end of the stream (length 0x{Length:X})");
×
361

362
        var inBounds = offset + count * (int)PointerSize <= Length;
1,057,931✔
363
        if (!inBounds)
1,057,931!
364
            throw new EndOfStreamException($"ReadNUintArrayAtRawAddress: Attempted to read {count} pointers (pointer length {PointerSize}) at offset 0x{offset:X}, but this goes beyond the end of the stream (length 0x{Length:X})");
×
365

366
        GetLockOrThrow();
1,057,931✔
367

368
        try
369
        {
370
            Position = offset;
1,057,931✔
371

372
            var ret = new ulong[count];
1,057,931✔
373

374
            for (var i = 0; i < count; i++)
12,590,340✔
375
            {
376
                ret[i] = ReadNUint();
5,237,239✔
377
            }
378

379
            return ret;
1,057,931✔
380
        }
381
        finally
382
        {
383
            ReleaseLock();
1,057,931✔
384

385
            var bytesRead = count * (int)PointerSize;
1,057,931✔
386
            TrackRead<ulong>(bytesRead, false);
1,057,931✔
387
        }
1,057,931✔
388
    }
1,057,931✔
389

390

391
    /// <summary>
392
    /// Read a native-sized integer (i.e. 32 or 64 bit, depending on platform) at the current position
393
    /// </summary>
394
    public virtual long ReadNInt() => is32Bit ? ReadInt32() : ReadInt64();
×
395

396
    /// <summary>
397
    /// Read a native-sized unsigned integer (i.e. 32 or 64 bit, depending on platform) at the current position
398
    /// </summary>
399
    public virtual ulong ReadNUint() => is32Bit ? ReadUInt32() : ReadUInt64();
×
400

401
    protected void WriteWord(int position, ulong word) => WriteWord(position, (long)word);
441,488✔
402

403
    /// <summary>
404
    /// Used for ELF Relocations.
405
    /// </summary>
406
    protected void WriteWord(int position, long word)
407
    {
408
        if (_memoryStream == null)
441,488!
409
            throw new("WriteWord is not supported in non-memory-backed readers");
×
410

411
        GetLockOrThrow();
441,488✔
412

413
        try
414
        {
415
            byte[] rawBytes;
416
            if (is32Bit)
441,488✔
417
            {
418
                var value = (int)word;
1,579✔
419
                rawBytes = BitConverter.GetBytes(value);
1,579✔
420
            }
421
            else
422
            {
423
                rawBytes = BitConverter.GetBytes(word);
439,909✔
424
            }
425

426
            if (BitConverter.IsLittleEndian != IsLittleEndian)
441,488!
NEW
427
                Array.Reverse(rawBytes);
×
428

429
            if (position > _memoryStream.Length)
441,488!
430
                throw new Exception($"WriteWord: Position {position} beyond length {_memoryStream.Length}");
×
431

432
            var count = is32Bit ? 4 : 8;
441,488✔
433
            if (position + count > _memoryStream.Length)
441,488!
434
                throw new Exception($"WriteWord: Writing {count} bytes at {position} would go beyond length {_memoryStream.Length}");
×
435

436
            if (rawBytes.Length != count)
441,488!
437
                throw new Exception($"WriteWord: Expected {count} bytes from BitConverter, got {position}");
×
438

439
            try
440
            {
441
                _memoryStream.Seek(position, SeekOrigin.Begin);
441,488✔
442
                _memoryStream.Write(rawBytes, 0, count);
441,488✔
443
            }
441,488✔
444
            catch
×
445
            {
446
                Logging.LibLogger.ErrorNewline("WriteWord: Unexpected exception!");
×
447
                throw;
×
448
            }
449
        }
450
        finally
451
        {
452
            ReleaseLock();
441,488✔
453
        }
441,488✔
454
    }
441,488✔
455

456
    public T ReadReadableHereNoLock<T>() where T : ReadableClass, new() => InternalReadReadableClass<T>();
791,178✔
457

458
    public T ReadReadable<T>(long offset = -1) where T : ReadableClass, new()
459
    {
460
        GetLockOrThrow();
2,337,405✔
461

462
        if (offset >= 0)
2,337,405✔
463
            Position = offset;
2,337,301✔
464

465
        var initialPos = Position;
2,337,405✔
466

467
        try
468
        {
469
            return InternalReadReadableClass<T>();
2,337,405✔
470
        }
471
        finally
472
        {
473
            var bytesRead = (int)(Position - initialPos);
2,337,405✔
474
            TrackRead<T>(bytesRead, trackIfFinishedReading: true);
2,337,405✔
475

476
            ReleaseLock();
2,337,405✔
477
        }
2,337,405✔
478
    }
2,337,405✔
479

480
    public T[] ReadReadableArrayAtRawAddr<T>(long offset, long count) where T : ReadableClass, new()
481
    {
482
        var t = new T[count];
932✔
483

484
        GetLockOrThrow();
932✔
485

486
        if (offset != -1)
932✔
487
            Position = offset;
898✔
488

489
        try
490
        {
491
            //This handles the actual reading into the array, and tracking read counts, for us.
492
            FillReadableArrayHereNoLock(t);
932✔
493
        }
932✔
494
        finally
495
        {
496
            ReleaseLock();
932✔
497
        }
932✔
498

499
        return t;
932✔
500
    }
501

502
    public void FillReadableArrayHereNoLock<T>(T[] array, int startOffset = 0) where T : ReadableClass, new()
503
    {
504
        var initialPos = Position;
1,559✔
505

506
        try
507
        {
508
            var i = startOffset;
1,559✔
509
            for (; i < array.Length; i++)
18,224,957✔
510
            {
511
                array[i] = InternalReadReadableClass<T>();
9,111,699✔
512
            }
513
        }
1,559✔
514
        finally
515
        {
516
            var bytesRead = (int)(Position - initialPos);
1,559✔
517
            TrackRead<T>(bytesRead, trackIfFinishedReading: true);
1,559✔
518
        }
1,559✔
519
    }
1,559✔
520

521
    public void TrackRead<T>(int bytesRead, bool trackIfInReadableRead = true, bool trackIfFinishedReading = false)
522
    {
523
        if (!EnableReadableSizeInformation)
4,601,962!
524
            return;
4,601,962✔
525

526
        if (!trackIfInReadableRead && _inReadableRead)
×
527
            return;
×
528

529
        if (_hasFinishedInitialRead && !trackIfFinishedReading)
×
530
            return;
×
531

532
        BytesReadPerClass[typeof(T)] = BytesReadPerClass.GetOrDefault(typeof(T)) + bytesRead;
×
533
    }
×
534
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc