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

Open
opened 2026-08-17 01:19:18 -05:00 by AraHaan · 0 comments
AraHaan commented 2026-08-17 01:19:18 -05:00 (Migrated from github.com)

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;
        }
    }
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: ```cs 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: ```cs // 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; } } ```
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-jbevain/cecil#983
No description provided.