From d05fbe843a4ca92b2b1f2932a2c13e6e1190259f Mon Sep 17 00:00:00 2001
From: ds5678 <49847914+ds5678@users.noreply.github.com>
Date: Tue, 21 Jul 2026 14:35:21 -0700
Subject: [PATCH] Support overriding default values for method parameters
This also includes:
* Improvements related to field constant values
* Fixes for Diffable C# and stable renaming
* A bump to C# 14 (necessary to use the `field` keyword)
* Performance boost to AsmResolverConstants
---
Cpp2IL.Core/Cpp2IL.Core.csproj | 2 +-
.../ConcreteGenericFieldAnalysisContext.cs | 1 +
...ConcreteGenericParameterAnalysisContext.cs | 3 ++
.../Model/Contexts/FieldAnalysisContext.cs | 4 +-
.../Contexts/InjectedFieldAnalysisContext.cs | 6 ++-
.../InjectedParameterAnalysisContext.cs | 7 +++-
.../Contexts/ParameterAnalysisContext.cs | 39 +++++++++++++++----
.../OutputFormats/DiffableCsOutputFormat.cs | 12 +++---
.../StableRenamingProcessingLayer.cs | 2 +-
.../AsmResolverAssemblyPopulator.cs | 16 ++------
.../Utils/AsmResolver/AsmResolverConstants.cs | 16 ++++----
11 files changed, 67 insertions(+), 41 deletions(-)
diff --git a/Cpp2IL.Core/Cpp2IL.Core.csproj b/Cpp2IL.Core/Cpp2IL.Core.csproj
index 88214b1c1..2711e0de4 100644
--- a/Cpp2IL.Core/Cpp2IL.Core.csproj
+++ b/Cpp2IL.Core/Cpp2IL.Core.csproj
@@ -7,7 +7,7 @@
Reverses Unity's IL2CPP Build Process
true
true
- 13
+ 14
enable
true
Samboy063.Cpp2IL.Core
diff --git a/Cpp2IL.Core/Model/Contexts/ConcreteGenericFieldAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/ConcreteGenericFieldAnalysisContext.cs
index 636b54419..2ae1abf65 100644
--- a/Cpp2IL.Core/Model/Contexts/ConcreteGenericFieldAnalysisContext.cs
+++ b/Cpp2IL.Core/Model/Contexts/ConcreteGenericFieldAnalysisContext.cs
@@ -10,6 +10,7 @@ public class ConcreteGenericFieldAnalysisContext : FieldAnalysisContext
public override FieldAttributes DefaultAttributes => BaseFieldContext.DefaultAttributes;
public override FieldAttributes? OverrideAttributes { get => BaseFieldContext.OverrideAttributes; set => BaseFieldContext.OverrideAttributes = value; }
public override object? DefaultConstantValue => BaseFieldContext.DefaultConstantValue;
+ public override bool UseOverrideConstantValue { get => BaseFieldContext.UseOverrideConstantValue; set => BaseFieldContext.UseOverrideConstantValue = value; }
public override object? OverrideConstantValue { get => BaseFieldContext.OverrideConstantValue; set => BaseFieldContext.OverrideConstantValue = value; }
public override int DefaultOffset => BaseFieldContext.DefaultOffset;
public override int? OverrideOffset { get => BaseFieldContext.OverrideOffset; set => BaseFieldContext.OverrideOffset = value; }
diff --git a/Cpp2IL.Core/Model/Contexts/ConcreteGenericParameterAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/ConcreteGenericParameterAnalysisContext.cs
index edf576e75..0019339ab 100644
--- a/Cpp2IL.Core/Model/Contexts/ConcreteGenericParameterAnalysisContext.cs
+++ b/Cpp2IL.Core/Model/Contexts/ConcreteGenericParameterAnalysisContext.cs
@@ -10,6 +10,9 @@ public class ConcreteGenericParameterAnalysisContext : ParameterAnalysisContext
public override ParameterAttributes? OverrideAttributes { get => BaseParameterContext.OverrideAttributes; set => BaseParameterContext.OverrideAttributes = value; }
public override string DefaultName => BaseParameterContext.DefaultName;
public override string? OverrideName { get => BaseParameterContext.OverrideName; set => BaseParameterContext.OverrideName = value; }
+ public override object? OriginalDefaultValue => BaseParameterContext.OriginalDefaultValue;
+ public override bool UseOverrideDefaultValue { get => BaseParameterContext.UseOverrideDefaultValue; set => BaseParameterContext.UseOverrideDefaultValue = value; }
+ public override object? OverrideDefaultValue { get => BaseParameterContext.OverrideDefaultValue; set => BaseParameterContext.OverrideDefaultValue = value; }
protected override int CustomAttributeIndex => -1;
public ConcreteGenericParameterAnalysisContext(ParameterAnalysisContext baseParameter, TypeAnalysisContext parameterType, ConcreteGenericMethodAnalysisContext declaringMethod) : base(null, baseParameter.ParameterIndex, declaringMethod)
diff --git a/Cpp2IL.Core/Model/Contexts/FieldAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/FieldAnalysisContext.cs
index ab945771b..939ba1254 100644
--- a/Cpp2IL.Core/Model/Contexts/FieldAnalysisContext.cs
+++ b/Cpp2IL.Core/Model/Contexts/FieldAnalysisContext.cs
@@ -45,11 +45,13 @@ public FieldAttributes Attributes
public virtual object? DefaultConstantValue => BackingData?.Field.DefaultValue?.Value;
+ public virtual bool UseOverrideConstantValue { get; set; } = false;
+
public virtual object? OverrideConstantValue { get; set; }
public object? ConstantValue
{
- get => OverrideConstantValue ?? DefaultConstantValue;
+ get => UseOverrideConstantValue ? OverrideConstantValue : DefaultConstantValue;
set => OverrideConstantValue = value;
}
diff --git a/Cpp2IL.Core/Model/Contexts/InjectedFieldAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/InjectedFieldAnalysisContext.cs
index eb5be7403..fd45e6581 100644
--- a/Cpp2IL.Core/Model/Contexts/InjectedFieldAnalysisContext.cs
+++ b/Cpp2IL.Core/Model/Contexts/InjectedFieldAnalysisContext.cs
@@ -7,13 +7,17 @@ public class InjectedFieldAnalysisContext : FieldAnalysisContext
public override TypeAnalysisContext DefaultFieldType { get; }
public override string DefaultName { get; }
public override FieldAttributes DefaultAttributes { get; }
+ public override int DefaultOffset { get; }
+ public override object? DefaultConstantValue { get; }
protected override bool IsInjected => true;
- public InjectedFieldAnalysisContext(string name, TypeAnalysisContext type, FieldAttributes attributes, TypeAnalysisContext parent) : base(null, parent)
+ public InjectedFieldAnalysisContext(string name, TypeAnalysisContext type, FieldAttributes attributes, TypeAnalysisContext parent, int offset = -1, object? constantValue = null) : base(null, parent)
{
DefaultName = name;
DefaultAttributes = attributes;
DefaultFieldType = type;
+ DefaultOffset = offset;
+ DefaultConstantValue = constantValue;
}
}
diff --git a/Cpp2IL.Core/Model/Contexts/InjectedParameterAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/InjectedParameterAnalysisContext.cs
index f2e3eb32e..146edce1f 100644
--- a/Cpp2IL.Core/Model/Contexts/InjectedParameterAnalysisContext.cs
+++ b/Cpp2IL.Core/Model/Contexts/InjectedParameterAnalysisContext.cs
@@ -9,13 +9,16 @@ public class InjectedParameterAnalysisContext : ParameterAnalysisContext
public override TypeAnalysisContext DefaultParameterType { get; }
public override ParameterAttributes DefaultAttributes { get; }
-
+
+ public override object? OriginalDefaultValue { get; }
+
protected override bool IsInjected => true;
- public InjectedParameterAnalysisContext(string? name, TypeAnalysisContext typeContext, ParameterAttributes attributes, int parameterIndex, MethodAnalysisContext declaringMethod) : base(null, parameterIndex, declaringMethod)
+ public InjectedParameterAnalysisContext(string? name, TypeAnalysisContext typeContext, ParameterAttributes attributes, int parameterIndex, MethodAnalysisContext declaringMethod, object? defaultValue = null) : base(null, parameterIndex, declaringMethod)
{
DefaultName = name ?? $"param_{parameterIndex}";
DefaultParameterType = typeContext;
DefaultAttributes = attributes;
+ OriginalDefaultValue = defaultValue;
}
}
diff --git a/Cpp2IL.Core/Model/Contexts/ParameterAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/ParameterAnalysisContext.cs
index c501c833c..2f99d4947 100644
--- a/Cpp2IL.Core/Model/Contexts/ParameterAnalysisContext.cs
+++ b/Cpp2IL.Core/Model/Contexts/ParameterAnalysisContext.cs
@@ -55,10 +55,38 @@ public ParameterAttributes Attributes
///
public bool IsRef => ParameterType is ByRefTypeAnalysisContext || Attributes.HasFlag(ParameterAttributes.Out);
+ public virtual object? OriginalDefaultValue
+ {
+ get
+ {
+ if (DefaultAttributes.HasFlag(ParameterAttributes.HasDefault))
+ {
+ var index = Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(DeclaringMethod.Definition!.parameterStart.Value + ParameterIndex);
+ return AppContext.Metadata.GetParameterDefaultValueFromIndex(index)?.ContainedDefaultValue;
+ }
+ return null;
+ }
+ }
+
+ public virtual bool UseOverrideDefaultValue { get; set; } = false;
+ public virtual object? OverrideDefaultValue
+ {
+ get;
+ set
+ {
+ UseOverrideDefaultValue = true;
+ field = value;
+ }
+ }
+
///
- /// The default value data for this parameter. Null if, and only if, the parameter has no default value. If it has a default value of literally null, this will be non-null and have a data index of -1.
+ /// The default value data for this parameter.
///
- public Il2CppParameterDefaultValue? DefaultValue { get; }
+ public object? DefaultValue
+ {
+ get => UseOverrideDefaultValue ? OverrideDefaultValue : OriginalDefaultValue;
+ set => OverrideDefaultValue = value;
+ }
public virtual TypeAnalysisContext DefaultParameterType => AppContext.ResolveIl2CppType(Definition?.RawType) ?? throw new("Subclasses of ParameterAnalysisContext must provide a parameter type");
@@ -79,11 +107,6 @@ public ParameterAnalysisContext(Il2CppParameterDefinition? definition, int param
if (Definition != null)
{
InitCustomAttributeData();
-
- if (Attributes.HasFlag(ParameterAttributes.HasDefault))
- {
- DefaultValue = AppContext.Metadata.GetParameterDefaultValueFromIndex(Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(declaringMethod.Definition!.parameterStart.Value + parameterIndex))!;
- }
}
}
@@ -111,7 +134,7 @@ public override string ToString()
if (Attributes.HasFlag(ParameterAttributes.HasDefault))
{
- var defaultValue = DefaultValue!.ContainedDefaultValue;
+ var defaultValue = DefaultValue;
if (defaultValue is string stringDefaultValue)
defaultValue = $"\"{stringDefaultValue}\"";
else if (defaultValue is bool boolDefaultValue)
diff --git a/Cpp2IL.Core/OutputFormats/DiffableCsOutputFormat.cs b/Cpp2IL.Core/OutputFormats/DiffableCsOutputFormat.cs
index 81b7af886..0c8ea99e8 100644
--- a/Cpp2IL.Core/OutputFormats/DiffableCsOutputFormat.cs
+++ b/Cpp2IL.Core/OutputFormats/DiffableCsOutputFormat.cs
@@ -120,7 +120,7 @@ private static void WriteType(IndentedTextWriter writer, TypeAnalysisContext typ
{
writer.Write(enumValue.Name);
writer.Write(" = ");
- writer.Write(InvariantValue(enumValue.BackingData!.DefaultValue));
+ writer.Write(InvariantValue(enumValue.ConstantValue));
writer.WriteLine(',');
}
}
@@ -190,24 +190,24 @@ private static void WriteField(IndentedTextWriter writer, FieldAnalysisContext f
}
}
- if (field.BackingData?.DefaultValue is { } defaultValue)
+ if (field.Attributes.HasFlag(FieldAttributes.HasDefault))
{
writer.Write(" = ");
- if (defaultValue is string stringDefaultValue)
+ if (field.ConstantValue is string stringDefaultValue)
{
writer.Write('"');
writer.Write(stringDefaultValue);
writer.Write('"');
}
- else if (defaultValue is char charDefaultValue)
+ else if (field.ConstantValue is char charDefaultValue)
{
writer.Write("'\\u");
writer.Write(((int)charDefaultValue).ToString("X"));
writer.Write("'");
}
else
- writer.Write(InvariantValue(defaultValue));
+ writer.Write(InvariantValue(field.ConstantValue));
}
writer.Write("; //Field offset: 0x");
@@ -407,5 +407,5 @@ private static void WriteCustomAttributes(IndentedTextWriter writer, HasCustomAt
=> CsFileUtils.WriteCustomAttributeStrings(owner, writer, true, true);
private static string InvariantValue(object? value)
- => value is null ? "" : value is IFormattable f ? f.ToString(null, CultureInfo.InvariantCulture) : value.ToString() ?? "";
+ => value is null ? "null" : value is IFormattable f ? f.ToString(null, CultureInfo.InvariantCulture) : value.ToString() ?? "";
}
diff --git a/Cpp2IL.Core/ProcessingLayers/StableRenamingProcessingLayer.cs b/Cpp2IL.Core/ProcessingLayers/StableRenamingProcessingLayer.cs
index 3de4da215..518e23028 100644
--- a/Cpp2IL.Core/ProcessingLayers/StableRenamingProcessingLayer.cs
+++ b/Cpp2IL.Core/ProcessingLayers/StableRenamingProcessingLayer.cs
@@ -131,7 +131,7 @@ public override void Process(ApplicationAnalysisContext appContext, Action IntegerCache = new();
- private static readonly Dictionary ByteCache = new();
+ private static readonly Constant[] IntegerCache = new Constant[16];
+ private static readonly Constant[] ByteCache = new Constant[16];
private static readonly Constant SingleZero = Constant.FromValue(0.0F);
@@ -27,10 +26,11 @@ static AsmResolverConstants()
}
}
- public static Constant GetOrCreateConstant(object from)
+ public static Constant GetOrCreateConstant(object? from)
{
return from switch
{
+ null => Null,
string s => new(ElementType.String, new(Encoding.Unicode.GetBytes(s))),
bool b => b ? BoolTrue : BoolFalse,
byte and >= 0 and < 16 => ByteCache[(byte)@from],
@@ -45,10 +45,8 @@ private static Constant CreateNewConstant(IConvertible from)
return new(GetElementTypeFromConstant(from), new(MiscUtils.RawBytes(from)));
}
- private static ElementType GetElementTypeFromConstant(object? primitive)
- => primitive is null
- ? ElementType.Object
- : primitive switch
+ private static ElementType GetElementTypeFromConstant(IConvertible primitive)
+ => primitive switch
{
sbyte => ElementType.I1,
byte => ElementType.U1,