From 88c48264a1fa60d5283efcb91f7f8ca7123c9891 Mon Sep 17 00:00:00 2001
From: puff <33184334+puff@users.noreply.github.com>
Date: Tue, 7 Nov 2023 15:34:48 -0500
Subject: [PATCH 1/4] Use TypeNameParser for resolving types
---
.../InlineOperands/VMInlineOperandData.cs | 42 ++----
src/EazyDevirt/Core/Architecture/TypeName.cs | 125 ------------------
src/EazyDevirt/Core/IO/Resolver.cs | 111 +++++-----------
3 files changed, 46 insertions(+), 232 deletions(-)
delete mode 100644 src/EazyDevirt/Core/Architecture/TypeName.cs
diff --git a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs
index 8e90a0c..7b72707 100644
--- a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs
+++ b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs
@@ -40,24 +40,20 @@ public static VMInlineOperandData Read(BinaryReader reader)
internal record VMTypeData : VMInlineOperandData
{
public string Name { get; }
- public bool HasGenericTypeArgs { get; }
- public bool IsGenericParameterType { get; }
- public int GenericArgumentIndex { get; }
- public int GenericTypeArgumentIndex { get; }
- public VMInlineOperand[] GenericTypes { get; }
-
- public TypeName TypeName { get; }
+ public bool HasGenericTypeParameters { get; }
+ public bool IsGenericParameterIndex { get; }
+ public int GenericMethodParameterIndex { get; }
+ public int GenericTypeParameterIndex { get; }
+ public VMInlineOperand[] GenericParameters { get; }
public VMTypeData(BinaryReader reader) : base(VMInlineOperandType.Type)
{
Name = reader.ReadString();
- HasGenericTypeArgs = reader.ReadBoolean();
- IsGenericParameterType = reader.ReadBoolean();
- GenericArgumentIndex = reader.ReadInt32();
- GenericTypeArgumentIndex = reader.ReadInt32();
- GenericTypes = VMInlineOperand.ReadArrayInternal(reader);
-
- TypeName = new TypeName(Name);
+ HasGenericTypeParameters = reader.ReadBoolean();
+ IsGenericParameterIndex = reader.ReadBoolean();
+ GenericMethodParameterIndex = reader.ReadInt32();
+ GenericTypeParameterIndex = reader.ReadInt32();
+ GenericParameters = VMInlineOperand.ReadArrayInternal(reader);
}
}
@@ -70,16 +66,6 @@ internal record VMFieldData : VMInlineOperandData
public string Name { get; }
public bool IsStatic { get; }
- public BindingFlags BindingFlags
- {
- get
- {
- var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic;
- bindingFlags |= IsStatic ? BindingFlags.Static : BindingFlags.Instance;
- return bindingFlags;
- }
- }
-
public VMFieldData(BinaryReader reader) : base(VMInlineOperandType.Field)
{
DeclaringType = VMInlineOperand.ReadInternal(reader);
@@ -97,11 +83,11 @@ internal record VMMethodData : VMInlineOperandData
public byte Flags { get; }
public bool IsStatic { get; }
- public bool HasGenericArguments { get; }
+ public bool HasGenericParameters { get; }
public string Name { get; }
public VMInlineOperand ReturnType { get; }
public VMInlineOperand[] Parameters { get; }
- public VMInlineOperand[] GenericArguments { get; }
+ public VMInlineOperand[] GenericParameters { get; }
public BindingFlags BindingFlags
{
@@ -119,11 +105,11 @@ public VMMethodData(BinaryReader reader) : base(VMInlineOperandType.Method)
Flags = reader.ReadByte();
// these constants are also different between versions (#3)
IsStatic = (Flags & 1) > 0;
- HasGenericArguments = (Flags & 2) > 0;
+ HasGenericParameters = (Flags & 2) > 0;
Name = reader.ReadString();
ReturnType = VMInlineOperand.ReadInternal(reader);
Parameters = VMInlineOperand.ReadArrayInternal(reader);
- GenericArguments = VMInlineOperand.ReadArrayInternal(reader);
+ GenericParameters = VMInlineOperand.ReadArrayInternal(reader);
}
}
diff --git a/src/EazyDevirt/Core/Architecture/TypeName.cs b/src/EazyDevirt/Core/Architecture/TypeName.cs
deleted file mode 100644
index b8e5e08..0000000
--- a/src/EazyDevirt/Core/Architecture/TypeName.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-
-using System.Reflection;
-
-namespace EazyDevirt.Core.Architecture;
-
-
-// From saneki's eazdevirt
-
-///
-/// Convenience class for interpreting the type names found in the
-/// encrypted virtualization resources file.
-///
-public class TypeName
-{
- ///
- /// Full name as given in constructor.
- ///
- public string FullName { get; private set; }
-
- public TypeName(string fullName)
- {
- FullName = fullName;
- }
-
- ///
- /// Full assembly name.
- ///
- public string AssemblyFullName =>
- FullName[(FullName.IndexOf(", ", StringComparison.Ordinal) + 2)..];
-
- ///
- /// Assembly name.
- ///
- public AssemblyName AssemblyName => new(AssemblyFullName);
-
- ///
- /// Type name without namespace.
- ///
- public string NameWithoutNamespace => Name.Contains('.') ? Name.Split('.').Last() : Name;
-
- ///
- /// Namespace.
- ///
- public string Namespace => Name.Contains('.')
- ? string.Join(".",
- Name.Split('.').Reverse().Skip(1).Reverse().ToArray())
- : string.Empty;
-
- ///
- /// Type name without assembly info.
- ///
- public string Name
- {
- get
- {
- if (_nameInitialized) return _name;
- if (!FullName.Contains(", ")) return FullName;
-
- GetModifiersStack(FullName.Split(',')[0], out var fixedName);
- _name = fixedName;
- _nameInitialized = true;
- return fixedName;
- }
- }
-
- private bool _nameInitialized;
- private string _name = string.Empty;
-
- public Stack Modifiers => FullName.Contains(", ")
- ? GetModifiersStack(FullName.Split(',')[0], out _)
- : new Stack();
-
- ///
- /// Whether or not this name indicates the type is nested.
- ///
- public bool IsNested => Name.Contains('+');
-
- ///
- /// The parent type name if nested. If not nested, an empty string.
- ///
- public string ParentName => IsNested
- ? string.Join("+",
- Name.Split('+').Reverse().Skip(1).Reverse().ToArray())
- : string.Empty;
-
- ///
- /// Nested parent name without namespace, or empty if not nested.
- ///
- public string ParentNameWithoutNamespace => IsNested
- ? ParentName.Contains('.') ? ParentName.Split('.').Last() : ParentName
- : string.Empty;
-
- ///
- /// The nested child type name if nested. If not nested, an empty string.
- ///
- public string NestedName => IsNested ? Name.Split('+').Last() : string.Empty;
-
- ///
- /// Get a modifiers stack from a deserialized type name, and also
- /// provide the fixed name.
- ///
- /// Deserialized name
- /// Fixed name
- /// Modifiers stack
- private static Stack GetModifiersStack(string rawName, out string fixedName)
- {
- var stack = new Stack();
-
- while (true)
- {
- if (rawName.EndsWith("[]"))
- stack.Push("[]");
- else if (rawName.EndsWith("*"))
- stack.Push("*");
- else if (rawName.EndsWith("&"))
- stack.Push("&");
- else break;
-
- rawName = rawName[..^stack.Peek().Length];
- }
-
- fixedName = rawName;
- return stack;
- }
-}
\ No newline at end of file
diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs
index 0b42a4e..d590123 100644
--- a/src/EazyDevirt/Core/IO/Resolver.cs
+++ b/src/EazyDevirt/Core/IO/Resolver.cs
@@ -1,6 +1,7 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.Signatures.Types;
+using AsmResolver.DotNet.Signatures.Types.Parsing;
using EazyDevirt.Core.Architecture;
using EazyDevirt.Core.Architecture.InlineOperands;
using EazyDevirt.Devirtualization;
@@ -22,21 +23,11 @@ public Resolver(DevirtualizationContext ctx)
private Dictionary Cache { get; }
- private TypeSignature ApplySigModifiers(TypeSignature baseTypeSig, Stack mods)
+ public ITypeDefOrRef? ResolveType(VMInlineOperand inlineOperand)
{
- while (mods.Count > 0)
- {
- var mod = mods.Pop();
- baseTypeSig = mod switch
- {
- "[]" => baseTypeSig.MakeSzArrayType(),
- "*" => baseTypeSig.MakePointerType(),
- "&" => baseTypeSig.MakeByReferenceType(),
- _ => throw new Exception($"Unknown modifier: {mod}")
- };
- }
-
- return baseTypeSig;
+ if (inlineOperand.IsToken)
+ return Ctx.Module.LookupMember(inlineOperand.Token);
+ return ResolveType(inlineOperand.Position);
}
public ITypeDefOrRef? ResolveType(int position)
@@ -57,71 +48,33 @@ private TypeSignature ApplySigModifiers(TypeSignature baseTypeSig, Stack
if (!inlineOperand.HasData || inlineOperand.Data is not VMTypeData data)
throw new Exception("VM inline operand expected to have type data!");
- if (data.IsGenericParameterType)
+ if (data.IsGenericParameterIndex)
{
- if (data.GenericArgumentIndex != -1)
+ if (data.GenericMethodParameterIndex >= 0)
{
- var typeGenericParameterSignature = new GenericParameterSignature(Ctx.Module, GenericParameterType.Method, data.GenericArgumentIndex);
- var typeGenericTypeDefOrRef = typeGenericParameterSignature.ToTypeDefOrRef();
-
- Cache.Add(position, typeGenericTypeDefOrRef);
- return typeGenericTypeDefOrRef;
+ var genericTypeDefOrRef = new GenericParameterSignature(Ctx.Module, GenericParameterType.Method, data.GenericMethodParameterIndex).ToTypeDefOrRef();
+ Cache.Add(position, genericTypeDefOrRef);
+ return genericTypeDefOrRef;
}
- if (data.TypeGenericArgumentIndex != -1)
+ if (data.GenericTypeParameterIndex >= 0)
{
- var typeGenericParameterSignature = new GenericParameterSignature(Ctx.Module, GenericParameterType.Type, data.GenericArgumentIndex);
- var typeGenericTypeDefOrRef = typeGenericParameterSignature.ToTypeDefOrRef();
-
- Cache.Add(position, typeGenericTypeDefOrRef);
- return typeGenericTypeDefOrRef;
+ var genericTypeDefOrRef = new GenericParameterSignature(Ctx.Module, GenericParameterType.Type, data.GenericTypeParameterIndex).ToTypeDefOrRef();
+ Cache.Add(position, genericTypeDefOrRef);
+ return genericTypeDefOrRef;
}
}
-
- // Try to find type definition or reference
- var typeDefOrRef = (ITypeDefOrRef?)Ctx.Module.GetAllTypes()
- .FirstOrDefault(x => x.FullName == data.TypeName.Name) ??
- (ITypeDefOrRef?)Ctx.Module.GetImportedTypeReferences()
- .FirstOrDefault(x => x.FullName == data.TypeName.Name && x.Scope?.Name == data.TypeName.AssemblyName.Name);
- if (typeDefOrRef != null)
- {
- var typeSig = typeDefOrRef.ToTypeSignature();
- if (data.HasGenericTypes)
- typeSig = typeDefOrRef
- .MakeGenericInstanceType(data.GenericTypes.Select(g => ResolveType(g.Position)!.ToTypeSignature())
- .ToArray());
-
- var typeSigWithModifiers = ApplySigModifiers(typeSig, data.TypeName.Modifiers).ToTypeDefOrRef().ImportWith(Ctx.Importer);
- Cache.Add(position, typeSigWithModifiers);
- return typeSigWithModifiers;
- }
- var assemblyRef =
- Ctx.Module.AssemblyReferences.FirstOrDefault(x => x.Name == data.TypeName.AssemblyName.Name) ??
- new AssemblyReference(data.TypeName.AssemblyName.Name, data.TypeName.AssemblyName.Version!,
- data.TypeName.AssemblyName.GetPublicKey() != null,
- data.TypeName.AssemblyName.GetPublicKey() ?? data.TypeName.AssemblyName.GetPublicKeyToken());
- if (assemblyRef == null!)
- {
- Ctx.Console.Warning($"Failed to find vm type {data.Name} assembly reference!");
- return null!;
- }
+ var typeDefOrRef = TypeNameParser.Parse(Ctx.Module, data.Name).GetUnderlyingTypeDefOrRef();
+ if (typeDefOrRef is null)
+ throw new Exception($"Failed to parse vm type {data.Name}");
+
+ if (data.HasGenericTypeParameters)
+ typeDefOrRef = typeDefOrRef.MakeGenericInstanceType(data.GenericParameters.Select(x => ResolveType(x)!.ToTypeSignature()).ToArray()).ToTypeDefOrRef();
- var parentTypeRef = !data.TypeName.IsNested
- ? assemblyRef.CreateTypeReference(data.TypeName.Namespace, data.TypeName.NameWithoutNamespace)
- : assemblyRef.CreateTypeReference(data.TypeName.Namespace, data.TypeName.ParentNameWithoutNamespace);
- var typeRef = !data.TypeName.IsNested
- ? parentTypeRef
- : parentTypeRef.CreateTypeReference(data.TypeName.NestedName);
- var typeBaseSig = typeRef.ToTypeSignature();
- if (data.HasGenericTypes)
- typeBaseSig = typeRef
- .MakeGenericInstanceType(data.GenericTypes.Select(g => ResolveType(g.Position)!.ToTypeSignature())
- .ToArray());
-
- var typeBaseSigWithModifiers = ApplySigModifiers(typeBaseSig, data.TypeName.Modifiers).ToTypeDefOrRef().ImportWith(Ctx.Importer);
- Cache.Add(position, typeBaseSigWithModifiers);
- return typeBaseSigWithModifiers;
+ var imported = typeDefOrRef.ImportWith(Ctx.Importer);
+ Cache.Add(position, imported);
+ return imported;
}
public IFieldDescriptor? ResolveField(int position)
@@ -271,16 +224,16 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
// TODO: Fix resolving imported member references of methods returning a generic parameter
&& (ms.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature ||
ms.ReturnType.FullName == returnType.FullName)
- && ms.GenericParameterCount == data.GenericArguments.Length
+ && ms.GenericParameterCount == data.GenericParameters.Length
&& VerifyMethodParameters(ms, data));
if (importedMemberRef != null)
{
- if (data.HasGenericArguments)
+ if (data.HasGenericParameters)
{
var importedMemberRefGenerics = importedMemberRef
.MakeGenericInstanceMethod(
- data.GenericArguments.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray())
+ data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray())
.ImportWith(Ctx.Importer);
Cache.Add(position, importedMemberRefGenerics);
@@ -297,10 +250,10 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
if (declaringType != null)
{
var methodDef = ResolveMethod(declaringType, data);
- if (data.HasGenericArguments)
+ if (data.HasGenericParameters)
{
var methodDefGenerics = methodDef?
- .MakeGenericInstanceMethod(data.GenericArguments
+ .MakeGenericInstanceMethod(data.GenericParameters
.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray())
.ImportWith(Ctx.Importer);
@@ -317,7 +270,7 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
var declaringTypeSig = declaringTypeDefOrRef.ToTypeSignature();
var parameters = data.Parameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray();
- var genericArgs = data.GenericArguments.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray();
+ var genericArgs = data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray();
var newParams = new List();
// convert generic parameters to their indexes (!!0, !0)
foreach (var parameter in parameters)
@@ -361,13 +314,13 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
var memberRef = declaringTypeDefOrRef
.CreateMemberReference(data.Name, data.IsStatic
? MethodSignature.CreateStatic(
- returnType.ToTypeSignature(), data.GenericArguments.Length,
+ returnType.ToTypeSignature(), data.GenericParameters.Length,
newParams)
: MethodSignature.CreateInstance(
- returnType.ToTypeSignature(), data.GenericArguments.Length,
+ returnType.ToTypeSignature(), data.GenericParameters.Length,
newParams));
- if (data.HasGenericArguments)
+ if (data.HasGenericParameters)
{
var memberRefGenerics = memberRef.MakeGenericInstanceMethod(genericArgs).ImportWith(Ctx.Importer);
Cache.Add(position, memberRefGenerics);
From 375d499878e2548993420b4571a4c2ec37651996 Mon Sep 17 00:00:00 2001
From: puff <33184334+puff@users.noreply.github.com>
Date: Tue, 7 Nov 2023 15:36:37 -0500
Subject: [PATCH 2/4] Check IsStatic when resolving fields
---
src/EazyDevirt/Core/IO/Resolver.cs | 32 +++++++++++++++++++-----------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs
index d590123..20bd143 100644
--- a/src/EazyDevirt/Core/IO/Resolver.cs
+++ b/src/EazyDevirt/Core/IO/Resolver.cs
@@ -81,9 +81,9 @@ public Resolver(DevirtualizationContext ctx)
{
if (Cache.TryGetValue(position, out var result))
return (IFieldDescriptor?)result;
-
+
Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin);
-
+
var inlineOperand = new VMInlineOperand(VMStreamReader);
if (inlineOperand.IsToken)
{
@@ -91,20 +91,28 @@ public Resolver(DevirtualizationContext ctx)
Cache.Add(position, lookup);
return lookup;
}
-
+
if (!inlineOperand.HasData || inlineOperand.Data is not VMFieldData data)
throw new Exception("VM inline operand expected to have field data!");
-
- var declaringTypeSig = ResolveType(data.DeclaringType.Position);
- var declaringType = declaringTypeSig?.Resolve();
- if (declaringType != null)
+
+ var declaringType = ResolveType(data.DeclaringType.Position);
+ if (declaringType is null)
{
- var field = declaringType.Fields.FirstOrDefault(f => f.Name == data.Name)?.ImportWith(Ctx.Importer);
- Cache.Add(position, field);
- return field;
+ Ctx.Console.Error($"Unable to resolve vm field {data.Name} declaring type!");
+ Cache.Add(position, null);
+ return null;
}
-
- Ctx.Console.Error($"Unable to resolve vm field {data.Name} declaring type {declaringTypeSig?.Name} to a TypeDef!");
+
+ if (declaringType.Resolve() is { } declaringTypeDef)
+ {
+ var fieldDef = declaringTypeDef.Fields.FirstOrDefault(f => f.Name == data.Name && f.IsStatic == data.IsStatic)?.ImportWith(Ctx.Importer);
+ Cache.Add(position, fieldDef);
+ return fieldDef;
+ }
+
+ // we can't create our own reference since we don't know the field's type.
+ // maybe it could be inferred from where it's being used, but that would require a lot of rework.
+ Ctx.Console.Error($"Unable to resolve vm field {declaringType?.Name}.{data.Name}");
return null;
}
From e44e4742611106a495f56131ad4ad99972d80cec Mon Sep 17 00:00:00 2001
From: puff <33184334+puff@users.noreply.github.com>
Date: Tue, 5 Nov 2024 07:57:43 -0500
Subject: [PATCH 3/4] Update AsmResolver and Echo
---
deps/Echo | 2 +-
src/EazyDevirt/Core/IO/Resolver.cs | 3 +-
src/EazyDevirt/Core/IO/VMBinaryReader.cs | 32 +++++++++++--------
src/EazyDevirt/EazyDevirt.csproj | 2 +-
.../PatternMatching/Patterns/OpCodes/Arg.cs | 3 +-
5 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/deps/Echo b/deps/Echo
index 3db99c3..a8f5417 160000
--- a/deps/Echo
+++ b/deps/Echo
@@ -1 +1 @@
-Subproject commit 3db99c3e8150b9e4ce2f1fbdacd4fc0240391a7d
+Subproject commit a8f54172ed088c63e04c5ef48ef394ae517758dc
diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs
index 20bd143..c27f4c0 100644
--- a/src/EazyDevirt/Core/IO/Resolver.cs
+++ b/src/EazyDevirt/Core/IO/Resolver.cs
@@ -1,7 +1,6 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
-using AsmResolver.DotNet.Signatures.Types;
-using AsmResolver.DotNet.Signatures.Types.Parsing;
+using AsmResolver.DotNet.Signatures.Parsing;
using EazyDevirt.Core.Architecture;
using EazyDevirt.Core.Architecture.InlineOperands;
using EazyDevirt.Devirtualization;
diff --git a/src/EazyDevirt/Core/IO/VMBinaryReader.cs b/src/EazyDevirt/Core/IO/VMBinaryReader.cs
index 6d17f96..4344bb0 100644
--- a/src/EazyDevirt/Core/IO/VMBinaryReader.cs
+++ b/src/EazyDevirt/Core/IO/VMBinaryReader.cs
@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures.Types;
+using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
using EazyDevirt.Core.Abstractions;
using EazyDevirt.Devirtualization;
@@ -30,6 +30,7 @@ internal class VMBinaryReader : VMBinaryReaderBase
private static MethodDefinition _readDecimalMethodDef = null!;
private static CilVirtualMachine _vm = null!;
+ private static CilThread _thread = null!;
private static BitVector _instanceObj = null!;
private static bool _initialized;
@@ -40,6 +41,8 @@ public VMBinaryReader(Stream input, bool leaveOpen = false) : base(input, Encodi
{
_vm = new CilVirtualMachine(Ctx.Module, false); // 32 bit always breaks something, even on 32 bit only assemblies.
_vm.Dispatcher.BeforeInstructionDispatch += DispatcherOnBeforeInstructionDispatch;
+
+ _thread = _vm.CreateThread();
FindInstanceDefs();
if (_instanceType is null)
@@ -245,11 +248,12 @@ private T ReadEmulated(byte[] bytes)
var instanceObjectHandle = _instanceObj.AsObjectHandle(_vm);
instanceObjectHandle.WriteField(_bufferFieldDef, _vm.ObjectMarshaller.ToBitVector(bytes));
- _vm.CallStack.Peek().WriteArgument(0, _instanceObj);
- _vm.Run();
+
+ _thread.CallStack.Peek().WriteArgument(0, _instanceObj);
+ _thread.Run();
var typeSig = Ctx.Importer.ImportTypeSignature(typeof(T));
- var result = _vm.ObjectMarshaller.ToObject(_vm.CallStack.Peek().EvaluationStack.Pop(typeSig))!;
+ var result = _vm.ObjectMarshaller.ToObject(_thread.CallStack.Peek().EvaluationStack.Pop(typeSig))!;
_vm.ObjectMapMemory.Clear();
return result;
@@ -269,7 +273,7 @@ public override int ReadInt32()
{
var bytes = ReadBytes(4);
- _vm.CallStack.Push(_readInt32MethodDef);
+ _thread.CallStack.Push(_readInt32MethodDef);
return ReadEmulated(bytes);
}
@@ -278,7 +282,7 @@ public override int ReadInt32Special()
{
var bytes = ReadBytes(4);
- _vm.CallStack.Push(_readInt32MethodDef);
+ _thread.CallStack.Push(_readInt32MethodDef);
return ReadEmulated(bytes);
}
@@ -286,7 +290,7 @@ public override uint ReadUInt32()
{
var bytes = ReadBytes(4);
- _vm.CallStack.Push(_readUInt32MethodDef);
+ _thread.CallStack.Push(_readUInt32MethodDef);
return ReadEmulated(bytes);
}
@@ -294,7 +298,7 @@ public override long ReadInt64()
{
var bytes = ReadBytes(8);
- _vm.CallStack.Push(_readInt64MethodDef);
+ _thread.CallStack.Push(_readInt64MethodDef);
return ReadEmulated(bytes);
}
@@ -302,7 +306,7 @@ public override ulong ReadUInt64()
{
var bytes = ReadBytes(8);
- _vm.CallStack.Push(_readUInt64MethodDef);
+ _thread.CallStack.Push(_readUInt64MethodDef);
return ReadEmulated(bytes);
}
@@ -310,7 +314,7 @@ public override short ReadInt16()
{
var bytes = ReadBytes(2);
- _vm.CallStack.Push(_readInt16MethodDef);
+ _thread.CallStack.Push(_readInt16MethodDef);
return ReadEmulated(bytes);
}
@@ -318,7 +322,7 @@ public override ushort ReadUInt16()
{
var bytes = ReadBytes(2);
- _vm.CallStack.Push(_readUInt16MethodDef);
+ _thread.CallStack.Push(_readUInt16MethodDef);
return ReadEmulated(bytes);
}
@@ -326,7 +330,7 @@ public override float ReadSingle()
{
var bytes = ReadBytes(4);
- _vm.CallStack.Push(_readSingleMethodDef);
+ _thread.CallStack.Push(_readSingleMethodDef);
return ReadEmulated(bytes);
}
@@ -334,7 +338,7 @@ public override double ReadDouble()
{
var bytes = ReadBytes(8);
- _vm.CallStack.Push(_readDoubleMethodDef);
+ _thread.CallStack.Push(_readDoubleMethodDef);
return ReadEmulated(bytes);
}
@@ -343,7 +347,7 @@ public override decimal ReadDecimal()
{
var bytes = ReadBytes(16);
- _vm.CallStack.Push(_readDecimalMethodDef);
+ _thread.CallStack.Push(_readDecimalMethodDef);
return ReadEmulated(bytes);
}
diff --git a/src/EazyDevirt/EazyDevirt.csproj b/src/EazyDevirt/EazyDevirt.csproj
index e815f77..3be6a62 100644
--- a/src/EazyDevirt/EazyDevirt.csproj
+++ b/src/EazyDevirt/EazyDevirt.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs b/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs
index 8627041..9086a8c 100644
--- a/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs
+++ b/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs
@@ -1,8 +1,7 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Serialized;
using AsmResolver.PE.DotNet.Cil;
-using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
-using EazyDevirt.Core.Abstractions;
+using AsmResolver.PE.DotNet.Metadata.Tables;
using EazyDevirt.Core.Abstractions.Interfaces;
using EazyDevirt.Core.Architecture;
using EazyDevirt.Devirtualization;
From f8dd4d879a5a9a44a0b918d6813545648da7b6cb Mon Sep 17 00:00:00 2001
From: puff <33184334+puff@users.noreply.github.com>
Date: Tue, 5 Nov 2024 11:08:21 -0500
Subject: [PATCH 4/4] More accurate ResolveMethod
---
src/EazyDevirt/Core/IO/Resolver.cs | 265 ++++++++----------
.../Options/DevirtualizationOptions.cs | 8 +
.../Options/DevirtualizationOptionsBinder.cs | 7 +-
src/EazyDevirt/Program.cs | 14 +-
4 files changed, 143 insertions(+), 151 deletions(-)
diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs
index c27f4c0..c2580fc 100644
--- a/src/EazyDevirt/Core/IO/Resolver.cs
+++ b/src/EazyDevirt/Core/IO/Resolver.cs
@@ -64,14 +64,14 @@ public Resolver(DevirtualizationContext ctx)
}
}
- var typeDefOrRef = TypeNameParser.Parse(Ctx.Module, data.Name).GetUnderlyingTypeDefOrRef();
- if (typeDefOrRef is null)
+ var typeSig = TypeNameParser.Parse(Ctx.Module, data.Name);
+ if (typeSig is null)
throw new Exception($"Failed to parse vm type {data.Name}");
if (data.HasGenericTypeParameters)
- typeDefOrRef = typeDefOrRef.MakeGenericInstanceType(data.GenericParameters.Select(x => ResolveType(x)!.ToTypeSignature()).ToArray()).ToTypeDefOrRef();
+ typeSig = typeSig.MakeGenericInstanceType(data.GenericParameters.Select(x => ResolveType(x)!.ToTypeSignature()).ToArray());
- var imported = typeDefOrRef.ImportWith(Ctx.Importer);
+ var imported = typeSig.ToTypeDefOrRef().ImportWith(Ctx.Importer);
Cache.Add(position, imported);
return imported;
}
@@ -115,12 +115,6 @@ public Resolver(DevirtualizationContext ctx)
return null;
}
- private bool VerifyMethodParameters(MethodSignatureBase method, VMMethodData data) =>
- method.ParameterTypes.Count == data.Parameters.Length && method.ParameterTypes
- .Zip(data.Parameters).All(x =>
- x.First is GenericParameterSignature or GenericInstanceTypeSignature || x.First.FullName ==
- ResolveType(x.Second.Position)?.FullName);
-
private bool VerifyMethodParameters(MethodDefinition method, VMMethodInfo data)
{
var skip = 0;
@@ -138,25 +132,6 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
x.First.ParameterType?.FullName == ResolveType(x.Second.VMType)?.FullName);
}
- private bool VerifyMethodParameters(MethodDefinition method, VMMethodData data)
- {
- var skip = 0;
- if (method.Parameters.ThisParameter != null && data.Parameters.Length > 0 &&
- method.Parameters.ThisParameter.ParameterType.FullName ==
- ResolveType(data.Parameters[0].Position)?.FullName)
- skip++;
-
- return (method.Parameters.Count == data.Parameters.Length || method.Parameters.Count == data.Parameters.Length - skip) && method.Parameters
- .Zip(data.Parameters.Skip(skip)).All(x =>
- // TODO: Should probably resolve these generic parameter signatures and verify them too
- x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignature ||
- (x.First.ParameterType is TypeSpecificationSignature tss &&
- (tss.BaseType is GenericParameterSignature or GenericInstanceTypeSignature ||
- tss.BaseType.FullName == ResolveType(x.Second.Position)?.FullName)) ||
- x.First.ParameterType?.FullName == ResolveType(x.Second.Position)?.FullName);
- ;
- }
-
private MethodDefinition? ResolveMethod(TypeDefinition? declaringType, VMMethodInfo data) =>
declaringType?.Methods.FirstOrDefault(m => m.Name == data.Name
&& (m.Signature?.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature ||
@@ -167,16 +142,12 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
ResolveType(data.VMReturnType)?.FullName)
&& VerifyMethodParameters(m, data));
- private MethodDefinition? ResolveMethod(TypeDefinition? declaringType, VMMethodData data) =>
- declaringType?.Methods.FirstOrDefault(m => m.Name == data.Name
- // TODO: Should probably resolve these generic parameter signatures and verify them too
- && (m.Signature?.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature ||
- (m.Signature?.ReturnType is TypeSpecificationSignature tss &&
- (tss.BaseType is GenericParameterSignature or GenericInstanceTypeSignature ||
- tss.BaseType.FullName == ResolveType(data.ReturnType.Position)?.FullName)) ||
- m.Signature?.ReturnType?.FullName ==
- ResolveType(data.ReturnType.Position)?.FullName)
- && VerifyMethodParameters(m, data));
+ private TypeSignature GetBaseTypeOfTypeSignature(TypeSignature ts)
+ {
+ while (ts is TypeSpecificationSignature tss)
+ ts = tss.BaseType;
+ return ts;
+ }
public IMethodDescriptor? ResolveMethod(int position)
{
@@ -209,134 +180,138 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu
var declaringTypeDefOrRef = ResolveType(data.DeclaringType.Position);
if (declaringTypeDefOrRef == null)
{
- Ctx.Console.Error($"Unable to resolve declaring type on vm method {data.Name}");
+ Ctx.Console.Error($"Failed to resolve declaring type on vm method {data.Name}!");
return null;
}
var returnType = ResolveType(data.ReturnType.Position);
if (returnType == null)
{
- Ctx.Console.Error($"Failed to resolve vm method {data.Name} return type!");
+ Ctx.Console.Error($"Failed to resolve return type on vm method {data.Name}!");
return null;
}
- var declaringTypeDefOrRefUnadorned = declaringTypeDefOrRef.Resolve();
- if (declaringTypeDefOrRef.Scope?.GetAssembly()?.Name != Ctx.Module.Assembly?.Name)
+ var declaringTypeGenericContext = GenericContext.FromType(declaringTypeDefOrRef);
+ var parameters = data.Parameters.Select(x => ResolveType(x)?.ToTypeSignature()!).ToArray();
+
+ // Methods utilizing generics need to be resolved to get 100% accuracy.
+ // This is because Eazfuscator uses the actual generic type instead of the generic parameter type.
+ // Example:
+ // The signature we need is:
+ // !0 class TestClass`1::TestMethod(!0, string, !!1, !!0)
+ // However, the signature Eazfuscator gives us is:
+ // int32 class TestClass`1::TestMethod(int32, string, int32, string)
+ // We can't convert them into their generic parameter type because we don't know the position of the generic type in the arguments, so we could inadvertently replace the wrong argument:
+ // !0 class TestClass`1::TestMethod(!0, !!0, !!1, string)
+ var declaringTypeIsGeneric = declaringTypeDefOrRef is TypeSpecification { Signature: GenericInstanceTypeSignature { TypeArguments.Count: > 0 } declaringTypeGenericSig};
+ if (data.HasGenericParameters || declaringTypeIsGeneric)
{
- var importedMemberRef = Ctx.Module.GetImportedMemberReferences().FirstOrDefault(x =>
- x.IsMethod
- && x.DeclaringType?.FullName == (declaringTypeDefOrRefUnadorned is null ? declaringTypeDefOrRef.FullName : declaringTypeDefOrRefUnadorned.FullName)
- && x.Name == data.Name
- && x.Signature is MethodSignature ms
- // TODO: Fix resolving imported member references of methods returning a generic parameter
- && (ms.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature ||
- ms.ReturnType.FullName == returnType.FullName)
- && ms.GenericParameterCount == data.GenericParameters.Length
- && VerifyMethodParameters(ms, data));
-
- if (importedMemberRef != null)
+ var declaringTypeDef = declaringTypeDefOrRef.Resolve();
+ if (declaringTypeDef is null)
{
- if (data.HasGenericParameters)
+ if (Ctx.Options.RequireDepsForGenericMethods)
{
- var importedMemberRefGenerics = importedMemberRef
- .MakeGenericInstanceMethod(
- data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray())
- .ImportWith(Ctx.Importer);
-
- Cache.Add(position, importedMemberRefGenerics);
- return importedMemberRefGenerics;
+ Ctx.Console.Error($"Unable to resolve generic vm method {data.Name} declaring type (Assembly: {declaringTypeDefOrRef.Scope?.GetAssembly()?.FullName})!");
+ return null;
}
-
- var importedMemberRefImported = importedMemberRef.ImportWith(Ctx.Importer);
- Cache.Add(position, importedMemberRefImported);
- return importedMemberRefImported;
- }
- }
-
- var declaringType = declaringTypeDefOrRef.Resolve();
- if (declaringType != null)
- {
- var methodDef = ResolveMethod(declaringType, data);
- if (data.HasGenericParameters)
- {
- var methodDefGenerics = methodDef?
- .MakeGenericInstanceMethod(data.GenericParameters
- .Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray())
- .ImportWith(Ctx.Importer);
-
- Cache.Add(position, methodDefGenerics);
- return methodDefGenerics;
- }
-
- var methodDefImported = methodDef?.ImportWith(Ctx.Importer);
- Cache.Add(position, methodDefImported);
- return methodDefImported;
- }
-
- // stuff below should only execute on types that aren't able to be resolved (so hopefully never)
-
- var declaringTypeSig = declaringTypeDefOrRef.ToTypeSignature();
- var parameters = data.Parameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray();
- var genericArgs = data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray();
- var newParams = new List();
- // convert generic parameters to their indexes (!!0, !0)
- foreach (var parameter in parameters)
- {
- // TODO: Search through all methods instead of just converting like this
- // these might not resolve or convert correctly if there are other parameters that use the type specified in the generic arg
- // a much better way would be to search for the method through declaringType.Methods and check the generics count and other stuff to ensure it's the correct method
- for (var gi = 0; gi < genericArgs.Length; gi++)
- {
- var genericArg = genericArgs[gi];
+ Ctx.Console.Warning($"Unable to resolve generic vm method {data.Name} declaring type (Assembly: {declaringTypeDefOrRef.Scope?.GetAssembly()?.FullName})! Method reference may be broken.");
- // convert generic parameters into their index form (!!0)
- if (SignatureComparer.Default.Equals(genericArg, parameter))
- newParams.Add(new GenericParameterSignature(GenericParameterType.Method, gi));
+ // This is inaccurate and will probably break your method signatures.
+ var methodSignature = data.IsStatic
+ ? MethodSignature.CreateStatic(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters)
+ : MethodSignature.CreateInstance(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters);
- // convert return type to its index form (!!0) if it matches
- if (SignatureComparer.Default.Equals(genericArg, returnType.ToTypeSignature()))
- returnType = new GenericParameterSignature(GenericParameterType.Method, gi).ToTypeDefOrRef();
+ var methodRef = declaringTypeDefOrRef.CreateMemberReference(data.Name, methodSignature);
+ if (data.HasGenericParameters)
+ {
+ var importedMethodGenericInstance = methodRef
+ .MakeGenericInstanceMethod(data.GenericParameters
+ .Select(x => ResolveType(x)?.ToTypeSignature()!).ToArray()).ImportWith(Ctx.Importer);
+ Cache.Add(position, importedMethodGenericInstance);
+ return importedMethodGenericInstance.ImportWith(Ctx.Importer);
+ }
+
+ var importedMethodRef = methodRef.ImportWith(Ctx.Importer);
+ Cache.Add(position, importedMethodRef);
+ return importedMethodRef;
}
- if (declaringTypeSig is GenericInstanceTypeSignature declaringTypeGenericSig)
+ // Look for matching methods
+ foreach (var method in declaringTypeDef.Methods)
{
- // convert generic type arguments in method parameters to their index form (!0)
- var f = declaringTypeGenericSig.TypeArguments.FirstOrDefault(x => x.FullName == parameter.FullName);
- if (f != null)
- newParams.Add(new GenericParameterSignature(GenericParameterType.Type,
- declaringTypeGenericSig.TypeArguments.IndexOf(f)));
- else
+ if (method.IsStatic == data.IsStatic
+ && method.Name == data.Name
+ && method.GenericParameters.Count == data.GenericParameters.Length)
{
- // convert generic return type to its index form (!0)
- f = declaringTypeGenericSig.TypeArguments.FirstOrDefault(x => x.FullName == returnType.FullName);
- if (f != null)
- returnType = new GenericParameterSignature(GenericParameterType.Type,
- declaringTypeGenericSig.TypeArguments.IndexOf(f)).ToTypeDefOrRef();
+ // Skip past ThisParameter when comparing method parameters
+ var skip = 0;
+ if (method.Parameters.ThisParameter != null
+ && data.Parameters.Length > 0
+ && SignatureComparer.Default.Equals(method.Parameters.ThisParameter.ParameterType, parameters[0]))
+ skip++;
+
+ // Check method parameters
+ if (method.Parameters.Count == parameters.Length
+ || method.Parameters.Count == parameters.Length - skip
+ && method.Parameters.Zip(parameters.Skip(skip)).All(x => SignatureComparer.Default.Equals(x.First.ParameterType, x.Second)))
+ {
+ // Found a potential match, now build signatures from both the real method and vm method data and compare them to ensure we have the correct method
+
+ // Build signature from vm method data
+ var vmGenericParameters = data.GenericParameters.Select(x => ResolveType(x)?.ToTypeSignature()!)
+ .ToArray();
+ var vmMethodSig = (data.IsStatic
+ ? MethodSignature.CreateStatic(returnType.ToTypeSignature(), vmGenericParameters.Length,
+ parameters)
+ : MethodSignature.CreateInstance(returnType.ToTypeSignature(),
+ vmGenericParameters.Length, parameters))
+ .InstantiateGenericTypes(declaringTypeGenericContext);
+
+ // Instantiate generic types for real method from vm method data
+ var realParameters = method.Parameters.Select(x => x.ParameterType).ToArray();
+ for (var i = 0; i < parameters.Length; i++)
+ if (GetBaseTypeOfTypeSignature(parameters[i]) is GenericParameterSignature)
+ realParameters[i] = vmGenericParameters[i];
+
+ var realReturnType = method.Signature!.ReturnType;
+ if (GetBaseTypeOfTypeSignature(realReturnType) is GenericParameterSignature)
+ realReturnType = returnType.ToTypeSignature();
+
+ var realMethodSignature = (method.IsStatic
+ ? MethodSignature.CreateStatic(realReturnType, vmGenericParameters.Length,
+ realParameters)
+ : MethodSignature.CreateInstance(realReturnType, vmGenericParameters.Length,
+ realParameters))
+ .InstantiateGenericTypes(declaringTypeGenericContext);
+
+ // Compare instantiated real method signature with our signature built from vm method data
+ if (!SignatureComparer.Default.Equals(realMethodSignature, vmMethodSig))
+ continue;
+
+ if (data.HasGenericParameters)
+ {
+
+ var genericInstanceMethod = declaringTypeDefOrRef.CreateMemberReference(method.Name, method.Signature)
+ .MakeGenericInstanceMethod(vmGenericParameters).ImportWith(Ctx.Importer);
+ Cache.Add(position, genericInstanceMethod);
+ return genericInstanceMethod;
+ }
+
+ var importedMethod = declaringTypeDefOrRef.CreateMemberReference(method.Name, method.Signature).ImportWith(Ctx.Importer);
+ Cache.Add(position, importedMethod);
+ return importedMethod;
+ }
}
}
- else
- newParams.Add(parameter);
}
-
- var memberRef = declaringTypeDefOrRef
- .CreateMemberReference(data.Name, data.IsStatic
- ? MethodSignature.CreateStatic(
- returnType.ToTypeSignature(), data.GenericParameters.Length,
- newParams)
- : MethodSignature.CreateInstance(
- returnType.ToTypeSignature(), data.GenericParameters.Length,
- newParams));
-
- if (data.HasGenericParameters)
- {
- var memberRefGenerics = memberRef.MakeGenericInstanceMethod(genericArgs).ImportWith(Ctx.Importer);
- Cache.Add(position, memberRefGenerics);
- return memberRefGenerics;
- }
-
- var memberRefImported = memberRef.ImportWith(Ctx.Importer);
- Cache.Add(position, memberRefImported);
- return memberRefImported;
+
+ var signature = data.IsStatic
+ ? MethodSignature.CreateStatic(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters)
+ : MethodSignature.CreateInstance(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters);
+
+ var methodDefOrRef = (IMethodDefOrRef)declaringTypeDefOrRef.CreateMemberReference(data.Name, signature).ImportWith(Ctx.Importer);
+ Cache.Add(position, methodDefOrRef);
+ return methodDefOrRef;
}
public IMemberDescriptor? ResolveToken(int position)
diff --git a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs
index aa26faa..e277da3 100644
--- a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs
+++ b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs
@@ -61,4 +61,12 @@ internal record DevirtualizationOptions
/// This only matters if you're using the Save Anyway option
///
public bool OnlySaveDevirted { get; init; }
+
+ ///
+ /// Require dependencies when resolving generic methods
+ ///
+ ///
+ /// If this is disabled, methods utilizing generics (type or method args) may not have proper signatures if dependencies aren't able to be resolved
+ ///
+ public bool RequireDepsForGenericMethods { get; init; }
}
\ No newline at end of file
diff --git a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs
index 88127c5..226c524 100644
--- a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs
+++ b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs
@@ -13,10 +13,11 @@ internal class DevirtualizationOptionsBinder : BinderBase _keepTypesOption;
private readonly Option _saveAnywayOption;
private readonly Option _onlySaveDevirtedOption;
+ private readonly Option _requireDepsForGenericMethods;
public DevirtualizationOptionsBinder(Argument assemblyArgument, Argument outputPathArgument,
Option verbosityOption, Option preserveAllOption, Option noVerifyOption, Option keepTypesOption, Option saveAnywayOption,
- Option onlySaveDevirtedOption)
+ Option onlySaveDevirtedOption, Option requireDepsForGenericMethods)
{
_assemblyArgument = assemblyArgument;
_outputPathArgument = outputPathArgument;
@@ -26,6 +27,7 @@ public DevirtualizationOptionsBinder(Argument assemblyArgument, Argume
_keepTypesOption = keepTypesOption;
_saveAnywayOption = saveAnywayOption;
_onlySaveDevirtedOption = onlySaveDevirtedOption;
+ _requireDepsForGenericMethods = requireDepsForGenericMethods;
}
protected override DevirtualizationOptions GetBoundValue(BindingContext bindingContext) =>
@@ -38,6 +40,7 @@ protected override DevirtualizationOptions GetBoundValue(BindingContext bindingC
NoVerify = bindingContext.ParseResult.GetValueForOption(_noVerifyOption),
KeepTypes = bindingContext.ParseResult.GetValueForOption(_keepTypesOption),
SaveAnyway = bindingContext.ParseResult.GetValueForOption(_saveAnywayOption),
- OnlySaveDevirted = bindingContext.ParseResult.GetValueForOption(_onlySaveDevirtedOption)
+ OnlySaveDevirted = bindingContext.ParseResult.GetValueForOption(_onlySaveDevirtedOption),
+ RequireDepsForGenericMethods = bindingContext.ParseResult.GetValueForOption(_requireDepsForGenericMethods)
};
}
\ No newline at end of file
diff --git a/src/EazyDevirt/Program.cs b/src/EazyDevirt/Program.cs
index 7941761..4a98a6b 100644
--- a/src/EazyDevirt/Program.cs
+++ b/src/EazyDevirt/Program.cs
@@ -88,6 +88,10 @@ private static Parser BuildParser()
var onlySaveDevirtedOption = new Option(new[] { "--only-save-devirted"}, "Only saves successfully devirtualized methods (This option only matters if you use the save anyway option)");
onlySaveDevirtedOption.SetDefaultValue(false);
+ var requireDepsForGenerics = new Option(new[] { "--require-deps-for-generics"}, "Require dependencies when resolving generic methods for accuracy");
+ requireDepsForGenerics.SetDefaultValue(true);
+
+
var rootCommand = new RootCommand("is an open-source tool that automatically restores the original IL code " +
"from an assembly virtualized with Eazfuscator.NET")
{
@@ -98,12 +102,14 @@ private static Parser BuildParser()
noVerifyOption,
keepTypesOption,
saveAnywayOption,
- onlySaveDevirtedOption
+ onlySaveDevirtedOption,
+ requireDepsForGenerics
};
-
- rootCommand.SetHandler(Run,
+
+ rootCommand.SetHandler(Run,
new DevirtualizationOptionsBinder(inputArgument, outputArgument, verbosityOption,
- preserveAllOption, noVerifyOption, keepTypesOption, saveAnywayOption, onlySaveDevirtedOption));
+ preserveAllOption, noVerifyOption, keepTypesOption, saveAnywayOption, onlySaveDevirtedOption,
+ requireDepsForGenerics));
return new CommandLineBuilder(rootCommand)
.UseDefaults()