unidecompiler-plugin-dotnet-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ """Frontend adapter for .NET CLI assemblies."""
@@ -0,0 +1,675 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import struct
5
+ from typing import Protocol
6
+
7
+ from unidecompiler.plugins import FrontendDecodeError
8
+
9
+
10
+ PE_MAGIC = b"MZ"
11
+
12
+
13
+ class DotNetDecodeError(FrontendDecodeError):
14
+ pass
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class DotNetInstruction:
19
+ offset: int
20
+ opcode: str
21
+ operands: str = ""
22
+ token: int | None = None
23
+ operand_kind: str | None = None
24
+ member_name: str | None = None
25
+ owner_name: str | None = None
26
+ arg_count: int | None = None
27
+ returns_void: bool | None = None
28
+ is_static: bool | None = None
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class DotNetMethodListing:
33
+ name: str
34
+ token: int
35
+ rva: int
36
+ is_static: bool = False
37
+ param_count: int = 0
38
+ max_stack: int | None = None
39
+ code_size: int = 0
40
+ instructions: tuple[DotNetInstruction, ...] = ()
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class DotNetAssembly:
45
+ name: str
46
+ filename: str | None = None
47
+ methods: tuple[DotNetMethodListing, ...] = ()
48
+ decoder_id: str | None = None
49
+
50
+
51
+ class DotNetAssemblyDecoder(Protocol):
52
+ id: str
53
+
54
+ def can_decode(self, data: bytes, filename: str | None = None) -> bool:
55
+ ...
56
+
57
+ def decode(self, data: bytes, filename: str | None = None) -> DotNetAssembly:
58
+ ...
59
+
60
+
61
+ def looks_like_dotnet(data: bytes) -> bool:
62
+ if not data.startswith(PE_MAGIC):
63
+ return False
64
+ try:
65
+ import dnfile
66
+
67
+ pe = dnfile.dnPE(data=data)
68
+ return pe.net is not None
69
+ except Exception:
70
+ return False
71
+
72
+
73
+ class DnfileAssemblyDecoder:
74
+ id = "dnfile"
75
+
76
+ def can_decode(self, data: bytes, filename: str | None = None) -> bool:
77
+ return looks_like_dotnet(data) and _dnfile_module() is not None
78
+
79
+ def decode(self, data: bytes, filename: str | None = None) -> DotNetAssembly:
80
+ dnfile = _dnfile_module()
81
+ if dnfile is None:
82
+ raise DotNetDecodeError("dnfile is not installed")
83
+ try:
84
+ pe = dnfile.dnPE(data=data)
85
+ except Exception as error: # pragma: no cover - parser owns details.
86
+ raise DotNetDecodeError(f"dnfile failed to decode assembly: {error}") from error
87
+ if pe.net is None:
88
+ raise DotNetDecodeError("missing .NET metadata")
89
+ return DotNetAssembly(
90
+ name=_assembly_name(pe, filename),
91
+ filename=filename,
92
+ methods=tuple(_method_listing(pe, row_index, row) for row_index, row in enumerate(_method_rows(pe), start=1)),
93
+ decoder_id=self.id,
94
+ )
95
+
96
+
97
+ def _dnfile_module():
98
+ try:
99
+ import dnfile
100
+ except ImportError:
101
+ return None
102
+ return dnfile
103
+
104
+
105
+ def _method_rows(pe) -> tuple:
106
+ tables = getattr(pe.net, "mdtables", None)
107
+ method_def = getattr(tables, "MethodDef", None)
108
+ return tuple(getattr(method_def, "rows", ()) or ())
109
+
110
+
111
+ def _assembly_name(pe, filename: str | None) -> str:
112
+ tables = getattr(pe.net, "mdtables", None)
113
+ assembly = getattr(tables, "Assembly", None)
114
+ rows = getattr(assembly, "rows", ()) or ()
115
+ if rows:
116
+ name = _heap_text(rows[0].Name)
117
+ if name:
118
+ return name
119
+ if filename:
120
+ return filename
121
+ return "<dotnet-assembly>"
122
+
123
+
124
+ def _method_listing(pe, row_index: int, row) -> DotNetMethodListing:
125
+ rva = int(getattr(row, "Rva", 0) or 0)
126
+ body = _read_method_body(pe, rva) if rva else None
127
+ return DotNetMethodListing(
128
+ name=_heap_text(row.Name) or f"<method_{row_index}>",
129
+ token=0x06000000 | row_index,
130
+ rva=rva,
131
+ is_static=bool(getattr(row.Flags, "mdStatic", False)),
132
+ param_count=_signature_param_count(row.Signature),
133
+ max_stack=None if body is None else body.max_stack,
134
+ code_size=0 if body is None else len(body.code),
135
+ instructions=() if body is None else _decode_il(pe, body.code),
136
+ )
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class _MethodBody:
141
+ max_stack: int
142
+ code: bytes
143
+
144
+
145
+ def _read_method_body(pe, rva: int) -> _MethodBody | None:
146
+ data = pe.get_data(rva, 16)
147
+ if not data:
148
+ return None
149
+ first = data[0]
150
+ kind = first & 0x3
151
+ if kind == 0x2:
152
+ code_size = first >> 2
153
+ return _MethodBody(max_stack=8, code=pe.get_data(rva + 1, code_size))
154
+ if kind == 0x3:
155
+ header = pe.get_data(rva, 12)
156
+ if len(header) < 12:
157
+ return None
158
+ _flags_size, max_stack, code_size, _local_sig = struct.unpack_from("<HHII", header, 0)
159
+ header_size = (_flags_size >> 12) * 4
160
+ if header_size < 12:
161
+ return None
162
+ return _MethodBody(max_stack=max_stack, code=pe.get_data(rva + header_size, code_size))
163
+ return None
164
+
165
+
166
+ def _heap_text(value) -> str | None:
167
+ if value is None:
168
+ return None
169
+ if isinstance(value, str):
170
+ return value
171
+ text = getattr(value, "value", None)
172
+ return text if isinstance(text, str) else None
173
+
174
+
175
+ def _signature_param_count(signature) -> int:
176
+ data = bytes(getattr(signature, "value", b"") or b"")
177
+ if not data:
178
+ return 0
179
+ reader = _BlobReader(data)
180
+ calling_convention = reader.byte()
181
+ if calling_convention & 0x10:
182
+ reader.compressed_uint()
183
+ if reader.eof:
184
+ return 0
185
+ return reader.compressed_uint()
186
+
187
+
188
+ class _BlobReader:
189
+ def __init__(self, data: bytes) -> None:
190
+ self.data = data
191
+ self.offset = 0
192
+
193
+ @property
194
+ def eof(self) -> bool:
195
+ return self.offset >= len(self.data)
196
+
197
+ def byte(self) -> int:
198
+ if self.eof:
199
+ return 0
200
+ value = self.data[self.offset]
201
+ self.offset += 1
202
+ return value
203
+
204
+ def compressed_uint(self) -> int:
205
+ first = self.byte()
206
+ if first & 0x80 == 0:
207
+ return first
208
+ if first & 0xC0 == 0x80:
209
+ return ((first & 0x3F) << 8) | self.byte()
210
+ return ((first & 0x1F) << 24) | (self.byte() << 16) | (self.byte() << 8) | self.byte()
211
+
212
+
213
+ _ONE_BYTE_OPCODES = {
214
+ 0x00: ("nop", "InlineNone"),
215
+ 0x02: ("ldarg.0", "InlineNone"),
216
+ 0x03: ("ldarg.1", "InlineNone"),
217
+ 0x04: ("ldarg.2", "InlineNone"),
218
+ 0x05: ("ldarg.3", "InlineNone"),
219
+ 0x06: ("ldloc.0", "InlineNone"),
220
+ 0x07: ("ldloc.1", "InlineNone"),
221
+ 0x08: ("ldloc.2", "InlineNone"),
222
+ 0x09: ("ldloc.3", "InlineNone"),
223
+ 0x0A: ("stloc.0", "InlineNone"),
224
+ 0x0B: ("stloc.1", "InlineNone"),
225
+ 0x0C: ("stloc.2", "InlineNone"),
226
+ 0x0D: ("stloc.3", "InlineNone"),
227
+ 0x0E: ("ldarg.s", "ShortInlineVar"),
228
+ 0x0F: ("ldarga.s", "ShortInlineVar"),
229
+ 0x10: ("starg.s", "ShortInlineVar"),
230
+ 0x11: ("ldloc.s", "ShortInlineVar"),
231
+ 0x12: ("ldloca.s", "ShortInlineVar"),
232
+ 0x13: ("stloc.s", "ShortInlineVar"),
233
+ 0x14: ("ldnull", "InlineNone"),
234
+ 0x15: ("ldc.i4.m1", "InlineNone"),
235
+ 0x16: ("ldc.i4.0", "InlineNone"),
236
+ 0x17: ("ldc.i4.1", "InlineNone"),
237
+ 0x18: ("ldc.i4.2", "InlineNone"),
238
+ 0x19: ("ldc.i4.3", "InlineNone"),
239
+ 0x1A: ("ldc.i4.4", "InlineNone"),
240
+ 0x1B: ("ldc.i4.5", "InlineNone"),
241
+ 0x1C: ("ldc.i4.6", "InlineNone"),
242
+ 0x1D: ("ldc.i4.7", "InlineNone"),
243
+ 0x1E: ("ldc.i4.8", "InlineNone"),
244
+ 0x1F: ("ldc.i4.s", "ShortInlineI"),
245
+ 0x20: ("ldc.i4", "InlineI"),
246
+ 0x21: ("ldc.i8", "InlineI8"),
247
+ 0x22: ("ldc.r4", "ShortInlineR"),
248
+ 0x23: ("ldc.r8", "InlineR"),
249
+ 0x25: ("dup", "InlineNone"),
250
+ 0x26: ("pop", "InlineNone"),
251
+ 0x27: ("jmp", "InlineMethod"),
252
+ 0x28: ("call", "InlineMethod"),
253
+ 0x2A: ("ret", "InlineNone"),
254
+ 0x2B: ("br.s", "ShortInlineBrTarget"),
255
+ 0x2C: ("brfalse.s", "ShortInlineBrTarget"),
256
+ 0x2D: ("brtrue.s", "ShortInlineBrTarget"),
257
+ 0x2E: ("beq.s", "ShortInlineBrTarget"),
258
+ 0x2F: ("bge.s", "ShortInlineBrTarget"),
259
+ 0x30: ("bgt.s", "ShortInlineBrTarget"),
260
+ 0x31: ("ble.s", "ShortInlineBrTarget"),
261
+ 0x32: ("blt.s", "ShortInlineBrTarget"),
262
+ 0x33: ("bne.un.s", "ShortInlineBrTarget"),
263
+ 0x34: ("bge.un.s", "ShortInlineBrTarget"),
264
+ 0x35: ("bgt.un.s", "ShortInlineBrTarget"),
265
+ 0x36: ("ble.un.s", "ShortInlineBrTarget"),
266
+ 0x37: ("blt.un.s", "ShortInlineBrTarget"),
267
+ 0x38: ("br", "InlineBrTarget"),
268
+ 0x39: ("brfalse", "InlineBrTarget"),
269
+ 0x3A: ("brtrue", "InlineBrTarget"),
270
+ 0x3B: ("beq", "InlineBrTarget"),
271
+ 0x3C: ("bge", "InlineBrTarget"),
272
+ 0x3D: ("bgt", "InlineBrTarget"),
273
+ 0x3E: ("ble", "InlineBrTarget"),
274
+ 0x3F: ("blt", "InlineBrTarget"),
275
+ 0x40: ("bne.un", "InlineBrTarget"),
276
+ 0x41: ("bge.un", "InlineBrTarget"),
277
+ 0x42: ("bgt.un", "InlineBrTarget"),
278
+ 0x43: ("ble.un", "InlineBrTarget"),
279
+ 0x44: ("blt.un", "InlineBrTarget"),
280
+ 0x45: ("switch", "InlineSwitch"),
281
+ 0x46: ("ldind.i1", "InlineNone"),
282
+ 0x47: ("ldind.u1", "InlineNone"),
283
+ 0x48: ("ldind.i2", "InlineNone"),
284
+ 0x49: ("ldind.u2", "InlineNone"),
285
+ 0x4A: ("ldind.i4", "InlineNone"),
286
+ 0x4B: ("ldind.u4", "InlineNone"),
287
+ 0x4C: ("ldind.i8", "InlineNone"),
288
+ 0x4D: ("ldind.i", "InlineNone"),
289
+ 0x4E: ("ldind.r4", "InlineNone"),
290
+ 0x4F: ("ldind.r8", "InlineNone"),
291
+ 0x50: ("ldind.ref", "InlineNone"),
292
+ 0x51: ("stind.ref", "InlineNone"),
293
+ 0x52: ("stind.i1", "InlineNone"),
294
+ 0x53: ("stind.i2", "InlineNone"),
295
+ 0x54: ("stind.i4", "InlineNone"),
296
+ 0x55: ("stind.i8", "InlineNone"),
297
+ 0x56: ("stind.r4", "InlineNone"),
298
+ 0x57: ("stind.r8", "InlineNone"),
299
+ 0x58: ("add", "InlineNone"),
300
+ 0x59: ("sub", "InlineNone"),
301
+ 0x5A: ("mul", "InlineNone"),
302
+ 0x5B: ("div", "InlineNone"),
303
+ 0x5C: ("div.un", "InlineNone"),
304
+ 0x5D: ("rem", "InlineNone"),
305
+ 0x5E: ("rem.un", "InlineNone"),
306
+ 0x5F: ("and", "InlineNone"),
307
+ 0x60: ("or", "InlineNone"),
308
+ 0x61: ("xor", "InlineNone"),
309
+ 0x62: ("shl", "InlineNone"),
310
+ 0x63: ("shr", "InlineNone"),
311
+ 0x64: ("shr.un", "InlineNone"),
312
+ 0x65: ("neg", "InlineNone"),
313
+ 0x66: ("not", "InlineNone"),
314
+ 0x67: ("conv.i1", "InlineNone"),
315
+ 0x68: ("conv.i2", "InlineNone"),
316
+ 0x69: ("conv.i4", "InlineNone"),
317
+ 0x6A: ("conv.i8", "InlineNone"),
318
+ 0x6B: ("conv.r4", "InlineNone"),
319
+ 0x6C: ("conv.r8", "InlineNone"),
320
+ 0x6D: ("conv.u4", "InlineNone"),
321
+ 0x6E: ("conv.u8", "InlineNone"),
322
+ 0x6F: ("callvirt", "InlineMethod"),
323
+ 0x70: ("cpobj", "InlineType"),
324
+ 0x71: ("ldobj", "InlineType"),
325
+ 0x72: ("ldstr", "InlineString"),
326
+ 0x73: ("newobj", "InlineMethod"),
327
+ 0x74: ("castclass", "InlineType"),
328
+ 0x75: ("isinst", "InlineType"),
329
+ 0x76: ("conv.r.un", "InlineNone"),
330
+ 0x79: ("unbox", "InlineType"),
331
+ 0x7A: ("throw", "InlineNone"),
332
+ 0x7B: ("ldfld", "InlineField"),
333
+ 0x7C: ("ldflda", "InlineField"),
334
+ 0x7D: ("stfld", "InlineField"),
335
+ 0x7E: ("ldsfld", "InlineField"),
336
+ 0x7F: ("ldsflda", "InlineField"),
337
+ 0x80: ("stsfld", "InlineField"),
338
+ 0x81: ("ldelema", "InlineType"),
339
+ 0x8C: ("box", "InlineType"),
340
+ 0x8D: ("newarr", "InlineType"),
341
+ 0x8E: ("ldlen", "InlineNone"),
342
+ 0x8F: ("ldelema", "InlineType"),
343
+ 0x90: ("ldelem.i1", "InlineNone"),
344
+ 0x91: ("ldelem.u1", "InlineNone"),
345
+ 0x92: ("ldelem.i2", "InlineNone"),
346
+ 0x93: ("ldelem.u2", "InlineNone"),
347
+ 0x94: ("ldelem.i4", "InlineNone"),
348
+ 0x95: ("ldelem.u4", "InlineNone"),
349
+ 0x96: ("ldelem.i8", "InlineNone"),
350
+ 0x97: ("ldelem.i", "InlineNone"),
351
+ 0x98: ("ldelem.r4", "InlineNone"),
352
+ 0x99: ("ldelem.r8", "InlineNone"),
353
+ 0x9A: ("ldelem.ref", "InlineNone"),
354
+ 0x9B: ("stelem.i", "InlineNone"),
355
+ 0x9C: ("stelem.i1", "InlineNone"),
356
+ 0x9D: ("stelem.i2", "InlineNone"),
357
+ 0x9E: ("stelem.i4", "InlineNone"),
358
+ 0x9F: ("stelem.i8", "InlineNone"),
359
+ 0xA0: ("stelem.r4", "InlineNone"),
360
+ 0xA1: ("stelem.r8", "InlineNone"),
361
+ 0xA2: ("stelem.ref", "InlineNone"),
362
+ 0xA3: ("ldelem", "InlineType"),
363
+ 0xA4: ("stelem", "InlineType"),
364
+ 0xA5: ("unbox.any", "InlineType"),
365
+ 0xB6: ("tail.", "InlineNone"),
366
+ 0xB7: ("conv.u2", "InlineNone"),
367
+ 0xB8: ("conv.u1", "InlineNone"),
368
+ 0xB9: ("conv.i", "InlineNone"),
369
+ 0xBA: ("conv.ovf.i", "InlineNone"),
370
+ 0xBB: ("conv.ovf.u", "InlineNone"),
371
+ 0xC2: ("refanyval", "InlineType"),
372
+ 0xC3: ("ckfinite", "InlineNone"),
373
+ 0xC6: ("mkrefany", "InlineType"),
374
+ 0xD0: ("ldtoken", "InlineTok"),
375
+ 0xD1: ("conv.u", "InlineNone"),
376
+ 0xD2: ("add.ovf", "InlineNone"),
377
+ 0xD3: ("add.ovf.un", "InlineNone"),
378
+ 0xD4: ("mul.ovf", "InlineNone"),
379
+ 0xD5: ("mul.ovf.un", "InlineNone"),
380
+ 0xD6: ("sub.ovf", "InlineNone"),
381
+ 0xD7: ("sub.ovf.un", "InlineNone"),
382
+ 0xD8: ("endfinally", "InlineNone"),
383
+ 0xDC: ("endfilter", "InlineNone"),
384
+ 0xDD: ("leave", "InlineBrTarget"),
385
+ 0xDE: ("leave.s", "ShortInlineBrTarget"),
386
+ 0xDF: ("stind.i", "InlineNone"),
387
+ 0xE0: ("conv.ovf.i1.un", "InlineNone"),
388
+ 0xE1: ("conv.ovf.i2.un", "InlineNone"),
389
+ 0xE2: ("conv.ovf.i4.un", "InlineNone"),
390
+ 0xE3: ("conv.ovf.i8.un", "InlineNone"),
391
+ 0xE4: ("conv.ovf.u1.un", "InlineNone"),
392
+ 0xE5: ("conv.ovf.u2.un", "InlineNone"),
393
+ 0xE6: ("conv.ovf.u4.un", "InlineNone"),
394
+ 0xE7: ("conv.ovf.u8.un", "InlineNone"),
395
+ 0xE8: ("conv.ovf.i.un", "InlineNone"),
396
+ 0xE9: ("conv.ovf.u.un", "InlineNone"),
397
+ 0xFE: ("prefix", "Prefix"),
398
+ }
399
+
400
+ _TWO_BYTE_OPCODES = {
401
+ 0x00: ("arglist", "InlineNone"),
402
+ 0x01: ("ceq", "InlineNone"),
403
+ 0x02: ("cgt", "InlineNone"),
404
+ 0x03: ("cgt.un", "InlineNone"),
405
+ 0x04: ("clt", "InlineNone"),
406
+ 0x05: ("clt.un", "InlineNone"),
407
+ 0x06: ("ldftn", "InlineMethod"),
408
+ 0x07: ("ldvirtftn", "InlineMethod"),
409
+ 0x09: ("ldarg", "InlineVar"),
410
+ 0x0A: ("ldarga", "InlineVar"),
411
+ 0x0B: ("starg", "InlineVar"),
412
+ 0x0C: ("ldloc", "InlineVar"),
413
+ 0x0D: ("ldloca", "InlineVar"),
414
+ 0x0E: ("stloc", "InlineVar"),
415
+ 0x0F: ("localloc", "InlineNone"),
416
+ 0x11: ("endfilter", "InlineNone"),
417
+ 0x12: ("unaligned.", "ShortInlineI"),
418
+ 0x13: ("volatile.", "InlineNone"),
419
+ 0x14: ("tail.", "InlineNone"),
420
+ 0x15: ("initobj", "InlineType"),
421
+ 0x16: ("constrained.", "InlineType"),
422
+ 0x17: ("cpblk", "InlineNone"),
423
+ 0x18: ("initblk", "InlineNone"),
424
+ 0x1A: ("rethrow", "InlineNone"),
425
+ 0x1C: ("sizeof", "InlineType"),
426
+ 0x1D: ("refanytype", "InlineNone"),
427
+ 0x1E: ("readonly.", "InlineNone"),
428
+ }
429
+
430
+
431
+ def _decode_il(pe, code: bytes) -> tuple[DotNetInstruction, ...]:
432
+ instructions: list[DotNetInstruction] = []
433
+ offset = 0
434
+ while offset < len(code):
435
+ start = offset
436
+ op = code[offset]
437
+ offset += 1
438
+ if op == 0xFE and offset < len(code):
439
+ op2 = code[offset]
440
+ offset += 1
441
+ opcode, operand_kind = _TWO_BYTE_OPCODES.get(op2, (f"unknown.fe{op2:02x}", "InlineNone"))
442
+ else:
443
+ opcode, operand_kind = _ONE_BYTE_OPCODES.get(op, (f"unknown.{op:02x}", "InlineNone"))
444
+ operand, token, offset = _read_operand(pe, code, offset, start, operand_kind)
445
+ instructions.append(
446
+ DotNetInstruction(
447
+ offset=start,
448
+ opcode=opcode,
449
+ operands=operand.text,
450
+ token=token,
451
+ operand_kind=operand_kind,
452
+ member_name=operand.member_name,
453
+ owner_name=operand.owner_name,
454
+ arg_count=operand.arg_count,
455
+ returns_void=operand.returns_void,
456
+ is_static=operand.is_static,
457
+ )
458
+ )
459
+ return tuple(instructions)
460
+
461
+
462
+ @dataclass(frozen=True)
463
+ class _DecodedOperand:
464
+ text: str = ""
465
+ member_name: str | None = None
466
+ owner_name: str | None = None
467
+ arg_count: int | None = None
468
+ returns_void: bool | None = None
469
+ is_static: bool | None = None
470
+
471
+
472
+ def _read_operand(pe, code: bytes, offset: int, instruction_offset: int, kind: str) -> tuple[_DecodedOperand, int | None, int]:
473
+ if kind in {"InlineNone", "Prefix"}:
474
+ return _DecodedOperand(), None, offset
475
+ if kind in {"ShortInlineI", "ShortInlineVar"}:
476
+ if offset >= len(code):
477
+ return _DecodedOperand(), None, offset
478
+ value = struct.unpack_from("<b" if kind == "ShortInlineI" else "<B", code, offset)[0]
479
+ return _DecodedOperand(str(value)), None, offset + 1
480
+ if kind == "InlineVar":
481
+ if offset + 2 > len(code):
482
+ return _DecodedOperand(), None, offset
483
+ return _DecodedOperand(str(struct.unpack_from("<H", code, offset)[0])), None, offset + 2
484
+ if kind == "InlineI":
485
+ if offset + 4 > len(code):
486
+ return _DecodedOperand(), None, offset
487
+ return _DecodedOperand(str(struct.unpack_from("<i", code, offset)[0])), None, offset + 4
488
+ if kind == "InlineI8":
489
+ if offset + 8 > len(code):
490
+ return _DecodedOperand(), None, offset
491
+ return _DecodedOperand(str(struct.unpack_from("<q", code, offset)[0])), None, offset + 8
492
+ if kind == "ShortInlineR":
493
+ if offset + 4 > len(code):
494
+ return _DecodedOperand(), None, offset
495
+ return _DecodedOperand(str(struct.unpack_from("<f", code, offset)[0])), None, offset + 4
496
+ if kind == "InlineR":
497
+ if offset + 8 > len(code):
498
+ return _DecodedOperand(), None, offset
499
+ return _DecodedOperand(str(struct.unpack_from("<d", code, offset)[0])), None, offset + 8
500
+ if kind == "ShortInlineBrTarget":
501
+ if offset >= len(code):
502
+ return _DecodedOperand(), None, offset
503
+ delta = struct.unpack_from("<b", code, offset)[0]
504
+ return _DecodedOperand(str(offset + 1 + delta)), None, offset + 1
505
+ if kind == "InlineBrTarget":
506
+ if offset + 4 > len(code):
507
+ return _DecodedOperand(), None, offset
508
+ delta = struct.unpack_from("<i", code, offset)[0]
509
+ return _DecodedOperand(str(offset + 4 + delta)), None, offset + 4
510
+ if kind == "InlineSwitch":
511
+ if offset + 4 > len(code):
512
+ return _DecodedOperand(), None, offset
513
+ count = struct.unpack_from("<I", code, offset)[0]
514
+ offset += 4
515
+ base = offset + count * 4
516
+ targets: list[str] = []
517
+ for _ in range(count):
518
+ if offset + 4 > len(code):
519
+ break
520
+ targets.append(str(base + struct.unpack_from("<i", code, offset)[0]))
521
+ offset += 4
522
+ return _DecodedOperand(",".join(targets)), None, offset
523
+ if kind in {"InlineMethod", "InlineField", "InlineType", "InlineTok", "InlineString", "InlineSig"}:
524
+ if offset + 4 > len(code):
525
+ return _DecodedOperand(), None, offset
526
+ token = struct.unpack_from("<I", code, offset)[0]
527
+ return _resolve_metadata_operand(pe, token, kind), token, offset + 4
528
+ return _DecodedOperand(), None, offset
529
+
530
+
531
+ def _resolve_metadata_operand(pe, token: int, kind: str) -> _DecodedOperand:
532
+ if kind == "InlineString":
533
+ value = _user_string(pe, token)
534
+ return _DecodedOperand(value if value is not None else f"0x{token:08x}")
535
+ table_id = token >> 24
536
+ row_index = token & 0x00FFFFFF
537
+ row = _metadata_row(pe, table_id, row_index)
538
+ if row is None:
539
+ return _DecodedOperand(f"0x{token:08x}")
540
+ if table_id == 0x06:
541
+ name = _heap_text(getattr(row, "Name", None)) or f"0x{token:08x}"
542
+ owner = _owner_for_method(pe, row_index)
543
+ arg_count, returns_void, _has_this = _signature_shape(getattr(row, "Signature", None))
544
+ is_static = bool(getattr(row.Flags, "mdStatic", False))
545
+ return _DecodedOperand(_qualified_member(owner, name), name, owner, arg_count, returns_void, is_static)
546
+ if table_id == 0x0A:
547
+ name = _heap_text(getattr(row, "Name", None)) or f"0x{token:08x}"
548
+ owner = _type_name_from_coded(getattr(row, "Class", None)) or "<member>"
549
+ arg_count, returns_void, has_this = _signature_shape(getattr(row, "Signature", None))
550
+ return _DecodedOperand(_qualified_member(owner, name), name, owner, arg_count, returns_void, not has_this)
551
+ if table_id == 0x2B:
552
+ method = _methodspec_target(pe, row_index)
553
+ if method is None:
554
+ return _DecodedOperand(f"0x{token:08x}")
555
+ return method
556
+ if table_id == 0x04:
557
+ name = _heap_text(getattr(row, "Name", None)) or f"0x{token:08x}"
558
+ owner = _owner_for_field(pe, row_index)
559
+ return _DecodedOperand(_qualified_member(owner, name), name, owner)
560
+ if table_id in {0x01, 0x02}:
561
+ name = _type_name(row) or f"0x{token:08x}"
562
+ return _DecodedOperand(name, owner_name=name)
563
+ return _DecodedOperand(f"0x{token:08x}")
564
+
565
+
566
+ def _metadata_row(pe, table_id: int, row_index: int):
567
+ table_names = {
568
+ 0x01: "TypeRef",
569
+ 0x02: "TypeDef",
570
+ 0x04: "Field",
571
+ 0x06: "MethodDef",
572
+ 0x0A: "MemberRef",
573
+ 0x2B: "MethodSpec",
574
+ }
575
+ table = getattr(getattr(pe.net, "mdtables", None), table_names.get(table_id, ""), None)
576
+ rows = getattr(table, "rows", ()) or ()
577
+ if row_index <= 0 or row_index > len(rows):
578
+ return None
579
+ return rows[row_index - 1]
580
+
581
+
582
+ def _user_string(pe, token: int) -> str | None:
583
+ heap = getattr(pe.net, "user_strings", None)
584
+ if heap is None:
585
+ return None
586
+ value = heap.get(token & 0x00FFFFFF)
587
+ return _heap_text(value)
588
+
589
+
590
+ def _type_name(row) -> str | None:
591
+ name = _heap_text(getattr(row, "TypeName", None))
592
+ namespace = _heap_text(getattr(row, "TypeNamespace", None))
593
+ if name and namespace:
594
+ return f"{namespace}.{name}"
595
+ return name
596
+
597
+
598
+ def _qualified_member(owner: str | None, name: str) -> str:
599
+ return f"{owner}.{name}" if owner else name
600
+
601
+
602
+ def _type_name_from_coded(value) -> str | None:
603
+ row = getattr(value, "row", None)
604
+ return _type_name(row)
605
+
606
+
607
+ def _owner_for_method(pe, row_index: int) -> str | None:
608
+ typedef = getattr(getattr(pe.net, "mdtables", None), "TypeDef", None)
609
+ for row in getattr(typedef, "rows", ()) or ():
610
+ method_indices = [getattr(index, "row_index", None) for index in getattr(row, "MethodList", ()) or ()]
611
+ if row_index in method_indices:
612
+ return _type_name(row)
613
+ return None
614
+
615
+
616
+ def _owner_for_field(pe, row_index: int) -> str | None:
617
+ typedef = getattr(getattr(pe.net, "mdtables", None), "TypeDef", None)
618
+ for row in getattr(typedef, "rows", ()) or ():
619
+ field_indices = [getattr(index, "row_index", None) for index in getattr(row, "FieldList", ()) or ()]
620
+ if row_index in field_indices:
621
+ return _type_name(row)
622
+ return None
623
+
624
+
625
+ def _methodspec_target(pe, row_index: int) -> _DecodedOperand | None:
626
+ methodspec = getattr(getattr(pe.net, "mdtables", None), "MethodSpec", None)
627
+ rows = getattr(methodspec, "rows", ()) or ()
628
+ if row_index <= 0 or row_index > len(rows):
629
+ return None
630
+ row = rows[row_index - 1]
631
+ method_ref = getattr(row, "Method", None)
632
+ if method_ref is None:
633
+ return None
634
+ target_row = getattr(method_ref, "table", None)
635
+ target_index = getattr(method_ref, "row_index", None)
636
+ if target_row is None or target_index is None:
637
+ return None
638
+ if target_row.__class__.__name__ == "MemberRef":
639
+ member_ref = _metadata_row(pe, 0x0A, target_index)
640
+ if member_ref is None:
641
+ return None
642
+ name = _heap_text(getattr(member_ref, "Name", None)) or f"0x{row_index:08x}"
643
+ owner = _type_name_from_coded(getattr(member_ref, "Class", None)) or "<member>"
644
+ arg_count, returns_void, has_this = _signature_shape(getattr(member_ref, "Signature", None))
645
+ return _DecodedOperand(_qualified_member(owner, name), name, owner, arg_count, returns_void, not has_this)
646
+ if target_row.__class__.__name__ == "MethodDef":
647
+ method_def = _metadata_row(pe, 0x06, target_index)
648
+ if method_def is None:
649
+ return None
650
+ name = _heap_text(getattr(method_def, "Name", None)) or f"0x{row_index:08x}"
651
+ owner = _owner_for_method(pe, target_index)
652
+ arg_count, returns_void, _has_this = _signature_shape(getattr(method_def, "Signature", None))
653
+ is_static = bool(getattr(method_def.Flags, "mdStatic", False))
654
+ return _DecodedOperand(_qualified_member(owner, name), name, owner, arg_count, returns_void, is_static)
655
+ return None
656
+
657
+
658
+ def _signature_shape(signature) -> tuple[int, bool | None, bool]:
659
+ data = bytes(getattr(signature, "value", b"") or b"")
660
+ if not data:
661
+ return 0, None, False
662
+ reader = _BlobReader(data)
663
+ calling_convention = reader.byte()
664
+ has_this = bool(calling_convention & 0x20)
665
+ is_generic = bool(calling_convention & 0x10)
666
+ calling_convention &= 0x0F
667
+ if calling_convention == 0x06:
668
+ return 0, None, has_this
669
+ if is_generic:
670
+ reader.compressed_uint()
671
+ if reader.eof:
672
+ return 0, None, has_this
673
+ param_count = reader.compressed_uint()
674
+ return_type = reader.byte()
675
+ return param_count, return_type == 0x01, has_this