using System.Reflection; using System.Reflection.Emit; using System.Runtime.Loader; if (args.Length < 2) { Console.Error.WriteLine("Usage: LegacyAssemblyInspector "); return 2; } var assemblyPath = Path.GetFullPath(args[0]); AssemblyLoadContext.Default.Resolving += (_, name) => { var candidate = Path.Combine(Path.GetDirectoryName(assemblyPath)!, $"{name.Name}.dll"); return File.Exists(candidate) ? AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate) : null; }; var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); var filter = args[1]; var oneByte = new OpCode[0x100]; var twoByte = new OpCode[0x100]; foreach (var field in typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static)) { if (field.GetValue(null) is not OpCode code) continue; var value = unchecked((ushort)code.Value); if (value < 0x100) oneByte[value] = code; else if ((value & 0xff00) == 0xfe00) twoByte[value & 0xff] = code; } Type[] assemblyTypes; try { assemblyTypes = assembly.GetTypes(); } catch (ReflectionTypeLoadException error) { assemblyTypes = error.Types.Where(type => type is not null).Cast().ToArray(); Console.Error.WriteLine($"Loaded {assemblyTypes.Length} partial types; {error.LoaderExceptions.Length} types could not be loaded."); foreach (var loaderError in error.LoaderExceptions.Where(item => item is not null).Select(item => item!.Message).Distinct().Take(20)) Console.Error.WriteLine($" {loaderError}"); } foreach (var type in assemblyTypes.Where(type => type.FullName?.Contains(filter, StringComparison.OrdinalIgnoreCase) == true || type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) .Any(method => method.Name.Contains(filter, StringComparison.OrdinalIgnoreCase)))) { Console.WriteLine($"TYPE {type.FullName}"); foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { Console.WriteLine($" METHOD {method}"); foreach (var attribute in method.CustomAttributes) Console.WriteLine($" ATTR {attribute.AttributeType.FullName} {string.Join(",", attribute.ConstructorArguments.Select(value => value.Value))}"); var body = method.GetMethodBody(); if (body is null) continue; var bytes = body.GetILAsByteArray() ?? []; for (var offset = 0; offset < bytes.Length;) { var instructionOffset = offset; var code = bytes[offset++] == 0xfe ? twoByte[bytes[offset++]] : oneByte[bytes[offset - 1]]; try { object? operand = null; switch (code.OperandType) { case OperandType.InlineString: operand = method.Module.ResolveString(BitConverter.ToInt32(bytes, offset)); offset += 4; break; case OperandType.InlineMethod: case OperandType.InlineField: case OperandType.InlineType: case OperandType.InlineTok: operand = method.Module.ResolveMember(BitConverter.ToInt32(bytes, offset), type.GetGenericArguments(), method.GetGenericArguments()); offset += 4; break; case OperandType.ShortInlineI: operand = bytes[offset++]; break; case OperandType.InlineI: operand = BitConverter.ToInt32(bytes, offset); offset += 4; break; case OperandType.InlineI8: operand = BitConverter.ToInt64(bytes, offset); offset += 8; break; case OperandType.ShortInlineR: operand = BitConverter.ToSingle(bytes, offset); offset += 4; break; case OperandType.InlineR: operand = BitConverter.ToDouble(bytes, offset); offset += 8; break; case OperandType.ShortInlineBrTarget: operand = offset + 1 + (sbyte)bytes[offset]; offset += 1; break; case OperandType.InlineBrTarget: operand = offset + 4 + BitConverter.ToInt32(bytes, offset); offset += 4; break; case OperandType.InlineSwitch: var count = BitConverter.ToInt32(bytes, offset); offset += 4 + count * 4; operand = $"{count} targets"; break; case OperandType.InlineVar: operand = BitConverter.ToUInt16(bytes, offset); offset += 2; break; case OperandType.ShortInlineVar: operand = bytes[offset++]; break; } if (operand is string || operand is MemberInfo) Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} {operand}"); } catch (Exception error) { Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} <{error.GetType().Name}>"); break; } } } } return 0;