using System.Reflection.Emit; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; if (args.Length < 2) { Console.Error.WriteLine("Usage: LegacyMetadataInspector "); return 2; } using var stream = File.OpenRead(Path.GetFullPath(args[0])); using var pe = new PEReader(stream); var metadata = pe.GetMetadataReader(); var filter = args[1]; var oneByte = new OpCode[0x100]; var twoByte = new OpCode[0x100]; foreach (var field in typeof(OpCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.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; } var methodsWithMatchingString = new HashSet(); foreach (var methodHandle in metadata.MethodDefinitions) { var method = metadata.GetMethodDefinition(methodHandle); if (method.RelativeVirtualAddress != 0 && MethodContainsString(method)) methodsWithMatchingString.Add(methodHandle); } foreach (var typeHandle in metadata.TypeDefinitions) { var type = metadata.GetTypeDefinition(typeHandle); var typeName = FullTypeName(type.Namespace, type.Name); var methods = type.GetMethods().Select(handle => (Handle: handle, Value: metadata.GetMethodDefinition(handle))).ToArray(); var typeMatches = typeName.Contains(filter, StringComparison.OrdinalIgnoreCase); var selectedMethods = typeMatches ? methods : methods.Where(item => metadata.GetString(item.Value.Name).Contains(filter, StringComparison.OrdinalIgnoreCase) || methodsWithMatchingString.Contains(item.Handle)).ToArray(); if (selectedMethods.Length == 0) continue; Console.WriteLine($"TYPE {typeName}"); foreach (var methodItem in selectedMethods) { var method = methodItem.Value; Console.WriteLine($" METHOD {metadata.GetString(method.Name)}"); if (method.RelativeVirtualAddress == 0) continue; var bytes = pe.GetMethodBody(method.RelativeVirtualAddress).GetILBytes(); if (bytes is null) continue; for (var offset = 0; offset < bytes.Length;) { var instructionOffset = offset; var code = bytes[offset++] == 0xfe ? twoByte[bytes[offset++]] : oneByte[bytes[offset - 1]]; try { string? printedOperand = null; switch (code.OperandType) { case OperandType.InlineString: var userStringToken = BitConverter.ToInt32(bytes, offset); offset += 4; printedOperand = $"\"{metadata.GetUserString(MetadataTokens.UserStringHandle(userStringToken))}\""; break; case OperandType.InlineMethod: case OperandType.InlineField: case OperandType.InlineType: case OperandType.InlineTok: var entityToken = BitConverter.ToInt32(bytes, offset); offset += 4; printedOperand = DescribeEntity(MetadataTokens.EntityHandle(entityToken)); break; case OperandType.InlineSig: offset += 4; break; case OperandType.ShortInlineI: offset += 1; break; case OperandType.InlineI: offset += 4; break; case OperandType.InlineI8: offset += 8; break; case OperandType.ShortInlineR: offset += 4; break; case OperandType.InlineR: offset += 8; break; case OperandType.ShortInlineBrTarget: offset += 1; break; case OperandType.InlineBrTarget: offset += 4; break; case OperandType.InlineSwitch: var count = BitConverter.ToInt32(bytes, offset); offset += 4 + count * 4; break; case OperandType.InlineVar: offset += 2; break; case OperandType.ShortInlineVar: offset += 1; break; } if (printedOperand is not null) Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} {printedOperand}"); } catch (Exception error) { Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} <{error.GetType().Name}>"); break; } } } } return 0; string FullTypeName(StringHandle namespaceHandle, StringHandle nameHandle) { var typeNamespace = metadata.GetString(namespaceHandle); var typeName = metadata.GetString(nameHandle); return string.IsNullOrEmpty(typeNamespace) ? typeName : $"{typeNamespace}.{typeName}"; } string DescribeEntity(EntityHandle handle) => handle.Kind switch { HandleKind.MethodDefinition => $"method {metadata.GetString(metadata.GetMethodDefinition((MethodDefinitionHandle)handle).Name)}", HandleKind.FieldDefinition => $"field {metadata.GetString(metadata.GetFieldDefinition((FieldDefinitionHandle)handle).Name)}", HandleKind.MemberReference => $"member {metadata.GetString(metadata.GetMemberReference((MemberReferenceHandle)handle).Name)}", HandleKind.TypeDefinition => $"type {FullTypeName(metadata.GetTypeDefinition((TypeDefinitionHandle)handle).Namespace, metadata.GetTypeDefinition((TypeDefinitionHandle)handle).Name)}", HandleKind.TypeReference => $"type {FullTypeName(metadata.GetTypeReference((TypeReferenceHandle)handle).Namespace, metadata.GetTypeReference((TypeReferenceHandle)handle).Name)}", _ => handle.Kind.ToString() }; bool MethodContainsString(MethodDefinition method) { try { var bytes = pe.GetMethodBody(method.RelativeVirtualAddress).GetILBytes(); if (bytes is null) return false; for (var offset = 0; offset < bytes.Length;) { var code = bytes[offset++] == 0xfe ? twoByte[bytes[offset++]] : oneByte[bytes[offset - 1]]; switch (code.OperandType) { case OperandType.InlineString: var token = BitConverter.ToInt32(bytes, offset); offset += 4; if (metadata.GetUserString(MetadataTokens.UserStringHandle(token)).Contains(filter, StringComparison.OrdinalIgnoreCase)) return true; break; case OperandType.InlineMethod: case OperandType.InlineField: case OperandType.InlineType: case OperandType.InlineTok: case OperandType.InlineSig: case OperandType.ShortInlineR: case OperandType.InlineI: case OperandType.InlineBrTarget: offset += 4; break; case OperandType.InlineI8: case OperandType.InlineR: offset += 8; break; case OperandType.ShortInlineI: case OperandType.ShortInlineBrTarget: case OperandType.ShortInlineVar: offset += 1; break; case OperandType.InlineVar: offset += 2; break; case OperandType.InlineSwitch: var count = BitConverter.ToInt32(bytes, offset); offset += 4 + count * 4; break; } } } catch { return false; } return false; }