Skip to content

Problem when trying to publicize everything in an assembly. #983

Description

@AraHaan

I have an issue with Mono.Cecil where I want to publicize everything in an assembly. However, some fields still remain private when viewed with ILSpy, compiler generated classes (async state machine code, iterator code, etc) managed to change from private -> internal but I expected it to be made public (this is to publicize the Assembly-CSharp to a Unity game). Also some of the MoveNext() methods in iterators did not get replaced with throw null; as I would expect.

this is what ILSpy would show on that MoveNext() in a compiler generated iterator type:

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

The code I use currently to publicize an assembly:

    // First we must publicize all properties, methods, fields,
    // events, and types in the input assembly.
    // 
    // Then once it is fully publicized it must
    // be made reference only by replacing all code within all
    // method/property bodies with "throw null;".
    // 
    // If everything succeeded the return value is true; false
    // otherwise.
    private static bool PublicizeAssembly(string outputFolder, string assemblyFile, out string? TargetFramework)
    {
        var asm = AssemblyDefinition.ReadAssembly(assemblyFile);
        if (asm == null)
        {
            TargetFramework = null;
            return false;
        }

        TargetFramework = GetTargetFramework(asm);
        _ = Directory.CreateDirectory(Path.Join(outputFolder, TargetFramework));
        ArgumentException.ThrowIfNullOrEmpty(assemblyFile);
        try
        {
            foreach (var type in asm.MainModule.Types)
            {
                PublicizeType(type);
                StripMethodBodies(type);

                // for nested types we must publicize and strip their method bodies as well.
                foreach (var nested in type.NestedTypes)
                {
                    PublicizeType(nested);
                    StripMethodBodies(nested);
                }
            }

            asm.Write(Path.Join(outputFolder, TargetFramework, "Assembly-CSharp.dll"));
            return true;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Error publicizing assembly {Path.GetFileName(assemblyFile)}: {ex.Message}\n{ex.StackTrace}");
            return false;
        }
    }

    private static string? GetTargetFramework(AssemblyDefinition asm)
    {
        var attr = asm.CustomAttributes
            .FirstOrDefault(static a => a.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute");
        if (attr == null)
        {
            return null;
        }

        // The constructor argument contains the framework string
        if (attr.ConstructorArguments.Count > 0)
        {
            // Use Unsafe.As<T> because why the fuck not? 😂
            var raw = Unsafe.As<string>(attr.ConstructorArguments[0].Value);
            raw = raw.Trim();
            var version = raw.Split('v').Last();
            if (raw.StartsWith(".NETFramework")) // .NET Framework Mono based Unity Game.
            {
                return "net" + version.Replace(".", "");
            }
            else if (raw.StartsWith(".NETStandard")) // .NET Standard Mono based Unity Game.
            {
                return "netstandard" + version;
            }
            else if (raw.StartsWith(".NETCoreApp")) // .NET Core/.NET Mono or IL2CPP based Unity Game.
            {
                return "net" + version;
            }
        }

        return null;
    }

    private static void PublicizeType(TypeDefinition type)
    {
        if (!type.IsPublic && type.IsNotPublic)
        {
            type.IsPublic = true;
            type.IsNotPublic = false;
        }

        // if the type is nested private we must set it to nested public.
        if (!type.IsNestedPublic && (
            type.IsNestedPrivate ||
            type.IsNestedAssembly ||
            type.IsSpecialName ||
            type.IsNestedFamily ||
            type.IsNestedFamilyOrAssembly ||
            type.IsNestedFamilyAndAssembly))
        {
            type.IsNestedPublic = true;
            type.IsNestedPrivate = false;

            // clear "internal".
            type.IsNestedAssembly = false;

            type.IsNestedFamily = false;
            type.IsNestedFamilyOrAssembly = false;
            type.IsNestedFamilyAndAssembly = false;
        }

        if (!type.IsPublic) // expected nothing to print here as everything should be public.
        {
            Console.WriteLine($"{type.FullName}'s attributes: {type.Attributes}");
        }

        // We need to publicize all of these next.
        foreach (var method in type.Methods)
        {
            PublicizeMethod(method);
        }

        foreach (var _field in type.Fields)
        {
            PublicizeField(_field);
        }

        foreach (var _event in type.Events)
        {
            if (_event.AddMethod != null)
            {
                PublicizeMethod(_event.AddMethod);
            }

            if (_event.RemoveMethod != null)
            {
                PublicizeMethod(_event.RemoveMethod);
            }

            // Probably a good idea to publicize these as well.
            if (_event.HasOtherMethods)
            {
                foreach (var method in _event.OtherMethods)
                {
                    PublicizeMethod(method);
                }
            }
        }

        foreach (var property in type.Properties)
        {
            if (property.GetMethod != null)
            {
                PublicizeMethod(property.GetMethod);
            }

            if (property.SetMethod != null)
            {
                PublicizeMethod(property.SetMethod);
            }
        }
    }

    private static void PublicizeMethod(MethodDefinition method)
    {
        if (!method.IsPublic && (
            method.IsPrivate ||
            method.IsAssembly ||
            method.IsSpecialName ||
            method.IsCompilerControlled ||
            method.IsFamily ||
            method.IsFamilyOrAssembly ||
            method.IsFamilyAndAssembly))
        {
            method.IsPublic = true;
            method.IsPrivate = false;

            // this is for "internal" methods seen only in the assembly?
            method.IsAssembly = false;

            // remove "protected".
            method.IsFamily = false;
            method.IsFamilyOrAssembly = false;
            method.IsFamilyAndAssembly = false;
        }

        if (!method.IsPublic) // expected nothing to print here as everything should be public.
        {
            Console.WriteLine($"{method.FullName}'s attributes: {method.Attributes}");
        }
    }

    private static void PublicizeField(FieldDefinition _field)
    {
        if (!_field.IsPublic && (
            _field.IsPrivate ||
            _field.IsAssembly ||
            _field.IsSpecialName ||
            _field.IsCompilerControlled ||
            _field.IsFamily ||
            _field.IsFamilyOrAssembly ||
            _field.IsFamilyAndAssembly))
        {
            _field.IsPublic = true;
            _field.IsPrivate = false;

            // this is for "internal" fields seen only in the assembly?
            _field.IsAssembly = false;

            // remove "protected".
            _field.IsFamily = false;
            _field.IsFamilyOrAssembly = false;
            _field.IsFamilyAndAssembly = false;
        }

        if (!_field.IsPublic) // expected nothing to print here as everything should be public.
        {
            Console.WriteLine($"{_field.FullName}'s attributes: {_field.Attributes}");
        }
    }

    private static void StripMethodBodies(TypeDefinition type)
    {
        foreach (var method in type.Methods)
        {
            // Skip abstract methods — they have no body
            if (method.IsAbstract)
            {
                continue;
            }

            // Skip P/Invoke and extern methods
            if (method.IsPInvokeImpl || method.IsInternalCall)
            {
                continue;
            }

            // Skip property/event accessors that are abstract
            if (!method.HasBody)
            {
                continue;
            }

            // Create a new empty body
            method.Body = new MethodBody(method);
            var il = method.Body.GetILProcessor();

            // Insert: ldnull; throw
            il.Append(il.Create(OpCodes.Ldnull));
            il.Append(il.Create(OpCodes.Throw));

            // Clear exception handlers (important!)
            method.Body.ExceptionHandlers.Clear();

            // Clear variables
            method.Body.Variables.Clear();

            // Ensure max stack is correct
            method.Body.MaxStackSize = 1;
        }
    }

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions