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

SamboyCoding / Cpp2IL / 23779472822

31 Mar 2026 03:46AM UTC coverage: 35.366% (-0.02%) from 35.39%
23779472822

Pull #539

github

web-flow
Merge 6ef414ea4 into 72c306a27
Pull Request #539: Refactor Il2CppBinary and related components

1964 of 6925 branches covered (28.36%)

Branch coverage included in aggregate %.

21 of 56 new or added lines in 20 files covered. (37.5%)

134 existing lines in 6 files now uncovered.

4468 of 11262 relevant lines covered (39.67%)

225256.39 hits per line

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

0.0
/LibCpp2IL/NintendoSwitch/NsoFile.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics.CodeAnalysis;
4
using System.IO;
5
using System.Linq;
6
using LibCpp2IL.Elf;
7
using LibCpp2IL.Logging;
8

9
namespace LibCpp2IL.NintendoSwitch;
10

11
public sealed class NsoFile : Il2CppBinary
12
{
13
    private const ulong NsoGlobalOffset = 0;
14

15
    private readonly byte[] _raw;
16

17
    private readonly NsoHeader _header;
18
    private readonly bool _isTextCompressed;
19
    private readonly bool _isRoDataCompressed;
20
    private readonly bool _isDataCompressed;
21

22
    private NsoModHeader _modHeader = null!;
23
    private ElfDynamicSymbol64[] _symbolTable = null!;
24
    private List<NsoSegmentHeader> segments = [];
×
25
    private List<ElfDynamicEntry> dynamicEntries = [];
×
26

27
    private bool IsCompressed => _isTextCompressed || _isRoDataCompressed || _isDataCompressed;
×
28

29
    public NsoFile(MemoryStream input) : base(input)
×
30
    {
31
        _raw = input.GetBuffer();
×
32
        is32Bit = false;
×
33
        InstructionSetId = DefaultInstructionSets.ARM_V8;
×
34

35
        LibLogger.VerboseNewline("\tReading NSO Early Header...");
×
36
        _header = new() { Magic = ReadUInt32(), Version = ReadUInt32(), Reserved = ReadUInt32(), Flags = ReadUInt32() };
×
37

38
        if (_header.Magic != 0x304F534E)
×
39
            throw new($"NSO file should have a magic number of 0x304F534E, got 0x{_header.Magic:X}");
×
40

41
        LibLogger.VerboseNewline($"\tOK. Magic number is 0x{_header.Magic:X}, version is {_header.Version}.");
×
42

43
        _isTextCompressed = (_header.Flags & 1) != 0;
×
44
        _isRoDataCompressed = (_header.Flags & 2) != 0;
×
45
        _isDataCompressed = (_header.Flags & 4) != 0;
×
46

47
        LibLogger.VerboseNewline($"\tCompression flags: text: {_isTextCompressed}, rodata: {_isRoDataCompressed}, data: {_isDataCompressed}.");
×
48

49
        _header.TextSegment = new() { FileOffset = ReadUInt32(), MemoryOffset = ReadUInt32(), DecompressedSize = ReadUInt32() };
×
50
        segments.Add(_header.TextSegment);
×
51

52
        LibLogger.VerboseNewline($"\tRead text segment header ok. Reading rodata segment header...");
×
53

54
        _header.ModuleOffset = ReadUInt32();
×
55
        _header.RoDataSegment = new() { FileOffset = ReadUInt32(), MemoryOffset = ReadUInt32(), DecompressedSize = ReadUInt32() };
×
56
        segments.Add(_header.RoDataSegment);
×
57

58
        LibLogger.VerboseNewline($"\tRead rodata segment header OK. Reading data segment header...");
×
59

60
        _header.ModuleFileSize = ReadUInt32();
×
61
        _header.DataSegment = new() { FileOffset = ReadUInt32(), MemoryOffset = ReadUInt32(), DecompressedSize = ReadUInt32() };
×
62
        segments.Add(_header.DataSegment);
×
63

64
        LibLogger.VerboseNewline($"\tRead data segment OK. Reading post-segment fields...");
×
65

66
        _header.BssSize = ReadUInt32();
×
67
        _header.DigestBuildId = ReadBytes(0x20);
×
68
        _header.TextCompressedSize = ReadUInt32();
×
69
        _header.RoDataCompressedSize = ReadUInt32();
×
70
        _header.DataCompressedSize = ReadUInt32();
×
71
        _header.NsoHeaderReserved = ReadBytes(0x1C);
×
72

73
        LibLogger.VerboseNewline("\tRead post-segment fields OK. Reading Dynamic section and Api Info offsets...");
×
74

75
        _header.ApiInfo = new() { RegionRoDataOffset = ReadUInt32(), RegionSize = ReadUInt32() };
×
76
        _header.DynStr = new() { RegionRoDataOffset = ReadUInt32(), RegionSize = ReadUInt32() };
×
77
        _header.DynSym = new() { RegionRoDataOffset = ReadUInt32(), RegionSize = ReadUInt32() };
×
78

79
        LibLogger.VerboseNewline($"\tRead offsets OK. Reading hashes...");
×
80

81
        _header.TextHash = ReadBytes(0x20);
×
82
        _header.RoDataHash = ReadBytes(0x20);
×
83
        _header.DataHash = ReadBytes(0x20);
×
84

85
        LibLogger.VerboseNewline($"\tRead hashes ok.");
×
86

87
        if (!IsCompressed)
×
88
        {
89
            ReadModHeader();
×
90
            ReadDynamicSection();
×
91
            ReadSymbolTable();
×
92
            ApplyRelocations();
×
93
        }
94

95
        LibLogger.VerboseNewline($"\tNSO Read completed OK.");
×
96
    }
×
97

98
    private void ReadModHeader()
99
    {
100
        LibLogger.VerboseNewline($"\tNSO is decompressed. Reading MOD segment header...");
×
101

102
        _modHeader = new();
×
103

104
        //Location of real mod header must be at .text + 4
105
        //Which allows .text + 0 to be a jump over it
106
        Position = _header.TextSegment.FileOffset + 4;
×
107
        _modHeader.ModOffset = ReadUInt32();
×
108

109
        //Now we have the real mod header position, go to it and read
110
        Position = _header.TextSegment.FileOffset + _modHeader.ModOffset + 4;
×
111
        _modHeader.DynamicOffset = ReadUInt32() + _modHeader.ModOffset;
×
112
        _modHeader.BssStart = ReadUInt32();
×
113
        _modHeader.BssEnd = ReadUInt32();
×
114

115
        //Construct the bss segment information from the fields we just read
116
        _modHeader.BssSegment = new() { FileOffset = _modHeader.BssStart, MemoryOffset = _modHeader.BssStart, DecompressedSize = _modHeader.BssEnd - _modHeader.BssStart };
×
117

118
        _modHeader.EhFrameHdrStart = ReadUInt32();
×
119
        _modHeader.EhFrameHdrEnd = ReadUInt32();
×
120
    }
×
121

122
    private void ReadDynamicSection()
123
    {
124
        LibLogger.VerboseNewline($"\tReading NSO Dynamic section...");
×
125

126
        Position = MapVirtualAddressToRaw(_modHeader.DynamicOffset);
×
127

128
        //This is mostly a sanity check so we don't read the entire damn file 16 bytes at a time
129
        //This will be way more than we need in general (like, 100 times more)
130
        var endOfData = _header.DataSegment.MemoryOffset + _header.DataSegment.DecompressedSize;
×
131
        var maxPossibleDynSectionEntryCount = (endOfData - _modHeader.DynamicOffset) / 16; //16 being sizeof(ElfDynamicEntry) on 64-bit
×
132

133
        for (var i = 0; i < maxPossibleDynSectionEntryCount; i++)
×
134
        {
135
            var dynEntry = ReadReadable<ElfDynamicEntry>();
×
136
            if (dynEntry.Tag == ElfDynamicType.DT_NULL)
×
137
                //End of dynamic section
138
                break;
139
            dynamicEntries.Add(dynEntry);
×
140
        }
141
    }
×
142

143
    private void ReadSymbolTable()
144
    {
145
        LibLogger.Verbose($"\tReading NSO symbol table...");
×
146

147
        var hash = GetDynamicEntry(ElfDynamicType.DT_HASH);
×
148

149
        if (hash == null)
×
150
        {
151
            LibLogger.WarnNewline("\tNo DT_HASH found in NSO, symbols will not be resolved");
×
152
            return;
×
153
        }
154

155
        Position = MapVirtualAddressToRaw(hash.Value);
×
156
        ReadUInt32(); //Ignored
×
157
        var symbolCount = ReadUInt32();
×
158

159
        var symTab = GetDynamicEntry(ElfDynamicType.DT_SYMTAB);
×
160

161
        if (symTab == null)
×
162
        {
163
            LibLogger.WarnNewline("\tNo DT_SYMTAB found in NSO, symbols will not be resolved");
×
164
            return;
×
165
        }
166

167
        _symbolTable = ReadReadableArrayAtVirtualAddress<ElfDynamicSymbol64>((ulong)MapVirtualAddressToRaw(symTab.Value), symbolCount);
×
168

169
        LibLogger.VerboseNewline($"Got {_symbolTable.Length} symbols");
×
170
    }
×
171

172
    private void ApplyRelocations()
173
    {
174
        ElfRelaEntry[] relaEntries;
175

176
        try
177
        {
178
            var dtRela = GetDynamicEntry(ElfDynamicType.DT_RELA) ?? throw new("No relocations found in NSO");
×
179
            var dtRelaSize = GetDynamicEntry(ElfDynamicType.DT_RELASZ) ?? throw new("No relocation size entry found in NSO");
×
180
            relaEntries = ReadReadableArrayAtVirtualAddress<ElfRelaEntry>(dtRela.Value, (long)(dtRelaSize.Value / 24)); //24 being sizeof(ElfRelaEntry) on 64-bit
×
181
        }
×
182
        catch
×
183
        {
184
            //If we don't have relocations, that's fine.
185
            return;
×
186
        }
187

188
        LibLogger.VerboseNewline($"\tApplying {relaEntries.Length} relocations from DT_RELA...");
×
189

190
        foreach (var elfRelaEntry in relaEntries)
×
191
        {
192
            switch (elfRelaEntry.Type)
×
193
            {
194
                case ElfRelocationType.R_AARCH64_ABS64:
195
                case ElfRelocationType.R_AARCH64_GLOB_DAT:
196
                    var symbol = _symbolTable[elfRelaEntry.Symbol];
×
197
                    WriteWord((int)MapVirtualAddressToRaw(elfRelaEntry.Offset), symbol.Value + elfRelaEntry.Addend);
×
198
                    break;
×
199
                case ElfRelocationType.R_AARCH64_RELATIVE:
200
                    WriteWord((int)MapVirtualAddressToRaw(elfRelaEntry.Offset), elfRelaEntry.Addend);
×
201
                    break;
×
202
                default:
203
                    LibLogger.WarnNewline($"Unknown relocation type {elfRelaEntry.Type}");
×
204
                    break;
205
            }
206
        }
207
    }
×
208

209
    public ElfDynamicEntry? GetDynamicEntry(ElfDynamicType tag) => dynamicEntries.Find(x => x.Tag == tag);
×
210

211
    [SuppressMessage("ReSharper", "MustUseReturnValue")]
212
    public NsoFile Decompress()
213
    {
214
        if (!IsCompressed)
×
215
            return this;
×
216

217
        LibLogger.InfoNewline("\tDecompressing NSO file...");
×
218

219
        var unCompressedStream = new MemoryStream();
×
220
        var writer = new BinaryWriter(unCompressedStream);
×
221
        writer.Write(_header.Magic);
×
222
        writer.Write(_header.Version);
×
223
        writer.Write(_header.Reserved);
×
224
        writer.Write(0); //Flags
×
225
        writer.Write(_header.TextSegment.FileOffset);
×
226
        writer.Write(_header.TextSegment.MemoryOffset);
×
227
        writer.Write(_header.TextSegment.DecompressedSize);
×
228
        writer.Write(_header.ModuleOffset);
×
229
        var roOffset = _header.TextSegment.FileOffset + _header.TextSegment.DecompressedSize;
×
230
        writer.Write(roOffset); //header.RoDataSegment.FileOffset
×
231
        writer.Write(_header.RoDataSegment.MemoryOffset);
×
232
        writer.Write(_header.RoDataSegment.DecompressedSize);
×
233
        writer.Write(_header.ModuleFileSize);
×
234
        writer.Write(roOffset + _header.RoDataSegment.DecompressedSize); //header.DataSegment.FileOffset
×
235
        writer.Write(_header.DataSegment.MemoryOffset);
×
236
        writer.Write(_header.DataSegment.DecompressedSize);
×
237
        writer.Write(_header.BssSize);
×
238
        writer.Write(_header.DigestBuildId);
×
239
        writer.Write(_header.TextCompressedSize);
×
240
        writer.Write(_header.RoDataCompressedSize);
×
241
        writer.Write(_header.DataCompressedSize);
×
242
        writer.Write(_header.NsoHeaderReserved);
×
243
        writer.Write(_header.ApiInfo.RegionRoDataOffset);
×
244
        writer.Write(_header.ApiInfo.RegionSize);
×
245
        writer.Write(_header.DynStr.RegionRoDataOffset);
×
246
        writer.Write(_header.DynStr.RegionSize);
×
247
        writer.Write(_header.DynSym.RegionRoDataOffset);
×
248
        writer.Write(_header.DynSym.RegionSize);
×
249
        writer.Write(_header.TextHash);
×
250
        writer.Write(_header.RoDataHash);
×
251
        writer.Write(_header.DataHash);
×
252
        writer.BaseStream.Position = _header.TextSegment.FileOffset;
×
253
        Position = _header.TextSegment.FileOffset;
×
254
        var textBytes = ReadBytes((int)_header.TextCompressedSize);
×
255
        if (_isTextCompressed)
×
256
        {
257
            var unCompressedData = new byte[_header.TextSegment.DecompressedSize];
×
258
            using (var decoder = new Lz4DecodeStream(new MemoryStream(textBytes)))
×
259
            {
260
                decoder.ReadExactly(unCompressedData, 0, unCompressedData.Length);
×
261
            }
×
262

263
            writer.Write(unCompressedData);
×
264
        }
265
        else
266
        {
267
            writer.Write(textBytes);
×
268
        }
269

270
        var roDataBytes = ReadBytes((int)_header.RoDataCompressedSize);
×
271
        if (_isRoDataCompressed)
×
272
        {
273
            var unCompressedData = new byte[_header.RoDataSegment.DecompressedSize];
×
274
            using (var decoder = new Lz4DecodeStream(new MemoryStream(roDataBytes)))
×
275
            {
276
                decoder.ReadExactly(unCompressedData, 0, unCompressedData.Length);
×
277
            }
×
278

279
            writer.Write(unCompressedData);
×
280
        }
281
        else
282
        {
283
            writer.Write(roDataBytes);
×
284
        }
285

286
        var dataBytes = ReadBytes((int)_header.DataCompressedSize);
×
287
        if (_isDataCompressed)
×
288
        {
289
            var unCompressedData = new byte[_header.DataSegment.DecompressedSize];
×
290
            using (var decoder = new Lz4DecodeStream(new MemoryStream(dataBytes)))
×
291
            {
292
                decoder.ReadExactly(unCompressedData, 0, unCompressedData.Length);
×
293
            }
×
294

295
            writer.Write(unCompressedData);
×
296
        }
297
        else
298
        {
299
            writer.Write(dataBytes);
×
300
        }
301

302
        writer.Flush();
×
303
        unCompressedStream.Position = 0;
×
304
        return new(unCompressedStream);
×
305
    }
306

307
    public override long RawLength => _raw.Length;
×
308
    public override byte GetByteAtRawAddress(ulong addr) => _raw[addr];
×
309

310
    public override long MapVirtualAddressToRaw(ulong addr, bool throwOnError = true)
311
    {
312
        var segment = segments.FirstOrDefault(x => addr - NsoGlobalOffset >= x.MemoryOffset && addr - NsoGlobalOffset < x.MemoryOffset + x.DecompressedSize);
×
313
        if (segment == null)
×
314
            if (throwOnError)
×
315
                throw new InvalidOperationException($"NSO: Address 0x{addr:X} is not present in any of the segments. Known segment ends are (hex) {string.Join(", ", segments.Select(s => (s.MemoryOffset + s.DecompressedSize).ToString("X")))}");
×
316
            else
317
                return VirtToRawInvalidNoMatch;
×
318

319
        return (long)(addr - (segment.MemoryOffset + NsoGlobalOffset) + segment.FileOffset);
×
320
    }
321

322
    public override ulong MapRawAddressToVirtual(uint offset, bool throwOnError = true)
323
    {
324
        var segment = segments.FirstOrDefault(x => offset >= x.FileOffset && offset < x.FileOffset + x.DecompressedSize);
×
325
        if (segment == null)
×
326
            if (throwOnError)
×
327
                throw new InvalidOperationException($"NSO: Offset 0x{offset:X} is not present in any of the segments. Known segment starts are (hex) {string.Join(", ", segments.Select(s => s.FileOffset.ToString("X")))}");
×
328
            else
329
                return 0;
×
330

331
        return offset - segment.FileOffset + (NsoGlobalOffset + segment.MemoryOffset);
×
332
    }
333

334
    public override ulong GetRva(ulong pointer)
335
    {
336
        return pointer;
×
337
    }
338

NEW
339
    public override ReadOnlyMemory<byte> GetRawBinaryContent() => _raw;
×
340

341
    public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind)
342
    {
UNCOV
343
        return 0;
×
344
    }
345

346
    public override ReadOnlyMemory<byte> GetEntirePrimaryExecutableSection()
347
    {
NEW
348
        return _raw.AsMemory((int)_header.TextSegment.FileOffset, (int)_header.TextSegment.DecompressedSize);
×
349
    }
350

351
    public override ulong GetVirtualAddressOfPrimaryExecutableSection()
352
    {
UNCOV
353
        return _header.TextSegment.MemoryOffset;
×
354
    }
355
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc