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
privatewhen viewed with ILSpy, compiler generated classes (async state machine code, iterator code, etc) managed to change fromprivate->internalbut I expected it to be madepublic(this is to publicize the Assembly-CSharp to a Unity game). Also some of theMoveNext()methods in iterators did not get replaced withthrow null;as I would expect.this is what ILSpy would show on that
MoveNext()in a compiler generated iterator type:The code I use currently to publicize an assembly: