Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ImporterOptions.TryToUseExistingAssemblyRefs option #422

Merged
merged 2 commits into from
Dec 5, 2021

Conversation

wwh1004
Copy link
Contributor

@wwh1004 wwh1004 commented Dec 5, 2021

Add ImporterOptions.TryToUseExistingAssemblyRefs option to use already existing AssemblyRefs whenever possible

if .net 2.0 3.5 assembly is loaded in .net 4.x runtime, and we use importer to import something, that will cause both v2.0 reference assemblies and v4.0 reference assemblies appear in AssemblyRef table in saved assembly

@wwh1004
Copy link
Contributor Author

wwh1004 commented Dec 5, 2021

could you open issues or discussions in dnlib reop? if someone want to introduce improvements or anything else may cause breaking change, there should be a place to discuss it.

@CreateAndInject
Copy link
Contributor

I reported this issue so many years ago(at least 5 years).
dnlib didn't resolve it, but now it's not a problem, you can easily resolve it by ImportMapper rather than modify dnlib.
See my solution:

using System;
using System.Reflection;
using System.Text;
using dnlib.DotNet;

public class AssemblyVersionMapper : ImportMapper
{
    readonly ModuleDef module;
    readonly Assembly asm;

    public AssemblyVersionMapper(ModuleDef module, Assembly asm = null)
    {
        this.module = module;
        this.asm = asm;
    }

    public override TypeRef Map(Type source)
    {
        if (source.Assembly == asm)
            return null;
        if (GetBestAssemblyRef(source.Assembly) is not AssemblyRef asmRef)
            return null;
        GetTypeNamespaceAndName_TypeDefOrRef(source, out var @namespace, out var name);
        if (source.IsNested)
            return new TypeRefUser(module, @namespace, name, Map(source.DeclaringType));
        return new TypeRefUser(module, @namespace, name, asmRef);
    }

    public static void GetTypeNamespaceAndName_TypeDefOrRef(Type type, out string @namespace, out string name)
    {
        name = Unescape(type.Name) ?? string.Empty;
        if (!type.IsNested)
            @namespace = type.Namespace ?? string.Empty;
        else
        {
            string declTypeFullName = Unescape(type.DeclaringType.FullName);
            string typeFullName = Unescape(type.FullName);
            if (declTypeFullName.Length + 1 + name.Length == typeFullName.Length)
                @namespace = string.Empty;
            else
                @namespace = typeFullName.Substring(declTypeFullName.Length + 1, typeFullName.Length - declTypeFullName.Length - 1 - name.Length - 1);
        }
    }

    internal static string Unescape(string name)
    {
        if (string.IsNullOrEmpty(name) || name.IndexOf('\\') < 0)
            return name;
        var sb = new StringBuilder(name.Length);
        for (int i = 0; i < name.Length; i++)
        {
            if (name[i] == '\\' && i < name.Length - 1 && IsReservedTypeNameChar(name[i + 1]))
                sb.Append(name[++i]);
            else
                sb.Append(name[i]);
        }
        return sb.ToString();
    }

    static bool IsReservedTypeNameChar(char c) => c switch
    {
        ',' or '+' or '&' or '*' or '[' or ']' or '\\' => true,
        _ => false,
    };

    AssemblyRef GetBestAssemblyRef(Assembly asm)
    {
        foreach (var asmRef in module.GetAssemblyRefs())
        {
            if (asmRef.FullName == asm.FullName)
                return asmRef;
        }

        string publicKeyToken = asm.FullName.Substring(asm.FullName.LastIndexOf('=') + 1);
        if (publicKeyToken is "b77a5c561934e089" or "b03f5f7f11d50a3a" or "31bf3856ad364e35" or "cc7b13ffcd2ddd51" or "7cec85d7bea7798e" or "adb9793829ddae60" or "0a613f4dd989e8ae")
        {
            foreach (var asmRef in module.GetAssemblyRefs())
            {
                if (asmRef.Name == asm.GetName().Name && asmRef.Version.ToString() is "2.0.0.0" or "3.0.0.0" or "3.5.0.0" or "4.0.0.0")
                    return asmRef;
            }
        }

        return module.GetAssemblyRef(asm.GetName().Name); ;
    }
}

I didn't use module.GetAssemblyRef directly, instead, I used GetBestAssemblyRef, since some modified ConfuserEx may add some invalid assembly refs

src/DotNet/Importer.cs Outdated Show resolved Hide resolved
src/DotNet/Importer.cs Outdated Show resolved Hide resolved
@wtfsck
Copy link
Contributor

wtfsck commented Dec 5, 2021

could you open issues or discussions in dnlib reop? if someone want to introduce improvements or anything else may cause breaking change, there should be a place to discuss it.

Opened.

@wwh1004
Copy link
Contributor Author

wwh1004 commented Dec 5, 2021

Done.

@wtfsck wtfsck merged commit 1386325 into 0xd4d:master Dec 5, 2021
@wtfsck
Copy link
Contributor

wtfsck commented Dec 5, 2021

Thanks, merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants