From 933b706615e77dab0286e7dbe8a74d01b7e6a4ed Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:17:52 -0700 Subject: [PATCH 01/14] replace annotations in MathF3x transformer --- .../GodotMonoDecomp/RemoveMathF3x.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs b/godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs index 0df97f996..e99749ca3 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs +++ b/godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs @@ -18,6 +18,7 @@ using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.TypeSystem; @@ -34,17 +35,24 @@ public void Run(AstNode rootNode, TransformContext context) { rootNode.AcceptVisitor(this); } - public override void VisitMemberReferenceExpression(MemberReferenceExpression methodInvocation) + + public static void ReplaceAndCopyAnnotations(Expression expression, Expression newExpression) + { + newExpression.CopyAnnotationsFrom(expression); + expression.ReplaceWith(newExpression); + } + public override void VisitMemberReferenceExpression(MemberReferenceExpression mre) { - string name = methodInvocation.ToString(); - bool isPi = name.EndsWith("MathF.PI"); - if (isPi || name.EndsWith("MathF.E")) + if (mre.Target.ToString() == "MathF" && (mre.MemberName == "PI" || mre.MemberName == "E")) { - string newMember = isPi ? "Pi" : "E"; - MemberReferenceExpression newExpression = new MemberReferenceExpression(new IdentifierExpression("Mathf"), newMember); - methodInvocation.ReplaceWith(newExpression); + var mathf = new IdentifierExpression("Mathf"); + ReplaceAndCopyAnnotations(mre.Target, mathf); + if (mre.MemberName == "PI") + { + mre.MemberName = "Pi"; + } return; } - base.VisitMemberReferenceExpression(methodInvocation); + base.VisitMemberReferenceExpression(mre); } } From a7a68c15a8f8010b839c3742d41aa1d55cc03ba4 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:23:37 -0700 Subject: [PATCH 02/14] Move transformers to Transforms namespace --- godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs | 3 ++- .../{ => Transforms}/FixSwitchExpressionCasts.cs | 2 +- .../{ => Transforms}/LiftCollectionInitializers.cs | 2 +- .../{ => Transforms}/RemoveBogusBaseConstructorCalls.cs | 2 +- .../{ => Transforms}/RemoveEmbeddedAttributes.cs | 4 +--- .../{ => Transforms}/RemoveGeneratedExceptionThrows.cs | 4 +--- .../{ => Transforms}/RemoveJsonSourceGenerationClassBody.cs | 3 +-- .../GodotMonoDecomp/{ => Transforms}/RemoveMathF3x.cs | 4 +--- .../{ => Transforms}/RestoreGeneratedRegexMethods.cs | 5 +---- 9 files changed, 10 insertions(+), 19 deletions(-) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/FixSwitchExpressionCasts.cs (98%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/LiftCollectionInitializers.cs (99%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/RemoveBogusBaseConstructorCalls.cs (97%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/RemoveEmbeddedAttributes.cs (94%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/RemoveGeneratedExceptionThrows.cs (96%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/RemoveJsonSourceGenerationClassBody.cs (98%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/RemoveMathF3x.cs (94%) rename godot-mono-decomp/GodotMonoDecomp/{ => Transforms}/RestoreGeneratedRegexMethods.cs (94%) diff --git a/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs b/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs index 4917c8e99..b5ba33d79 100644 --- a/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs +++ b/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs @@ -28,6 +28,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using GodotMonoDecomp.Transforms; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; @@ -306,7 +307,7 @@ public virtual CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); decompiler.AstTransforms.Add(new RemoveCLSCompliantAttribute()); decompiler.AstTransforms.Add(new RemoveGodotScriptPathAttribute()); - decompiler.AstTransforms.Add(new GodotMonoDecomp.RemoveEmbeddedAttributes()); + decompiler.AstTransforms.Add(new GodotMonoDecomp.Transforms.RemoveEmbeddedAttributes()); decompiler.AstTransforms.Add(new RestoreGeneratedRegexMethods()); decompiler.AstTransforms.Add(new RemoveGeneratedExceptionThrows()); if (Settings.EnableCollectionInitializerLifting) diff --git a/godot-mono-decomp/GodotMonoDecomp/FixSwitchExpressionCasts.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/FixSwitchExpressionCasts.cs similarity index 98% rename from godot-mono-decomp/GodotMonoDecomp/FixSwitchExpressionCasts.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/FixSwitchExpressionCasts.cs index 796c55b51..03d8d5eb1 100644 --- a/godot-mono-decomp/GodotMonoDecomp/FixSwitchExpressionCasts.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/FixSwitchExpressionCasts.cs @@ -3,7 +3,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.TypeSystem; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// /// Intended to fix switch expressions that do not have enough context to determine the best type as a result of a parent member reference expression. diff --git a/godot-mono-decomp/GodotMonoDecomp/LiftCollectionInitializers.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs similarity index 99% rename from godot-mono-decomp/GodotMonoDecomp/LiftCollectionInitializers.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs index 281f7fff2..ad64e49fb 100644 --- a/godot-mono-decomp/GodotMonoDecomp/LiftCollectionInitializers.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs @@ -1,7 +1,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// /// Lifts a narrow set of constructor prelude initializers back to declaration initializers. diff --git a/godot-mono-decomp/GodotMonoDecomp/RemoveBogusBaseConstructorCalls.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveBogusBaseConstructorCalls.cs similarity index 97% rename from godot-mono-decomp/GodotMonoDecomp/RemoveBogusBaseConstructorCalls.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveBogusBaseConstructorCalls.cs index c516cae36..ff49efc49 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RemoveBogusBaseConstructorCalls.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveBogusBaseConstructorCalls.cs @@ -1,7 +1,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// /// Removes erroneous base._002Ector() calls that sometimes appear at the end of diff --git a/godot-mono-decomp/GodotMonoDecomp/RemoveEmbeddedAttributes.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveEmbeddedAttributes.cs similarity index 94% rename from godot-mono-decomp/GodotMonoDecomp/RemoveEmbeddedAttributes.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveEmbeddedAttributes.cs index db257837e..c269ea932 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RemoveEmbeddedAttributes.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveEmbeddedAttributes.cs @@ -1,10 +1,8 @@ -using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.Semantics; -using ICSharpCode.Decompiler.TypeSystem; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// diff --git a/godot-mono-decomp/GodotMonoDecomp/RemoveGeneratedExceptionThrows.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveGeneratedExceptionThrows.cs similarity index 96% rename from godot-mono-decomp/GodotMonoDecomp/RemoveGeneratedExceptionThrows.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveGeneratedExceptionThrows.cs index cf83b2217..5bce918b6 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RemoveGeneratedExceptionThrows.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveGeneratedExceptionThrows.cs @@ -16,12 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; -using ICSharpCode.Decompiler.TypeSystem; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// /// A hack to remove compiler-generated exception throws caused by switch expressions being compiled as imperative code. diff --git a/godot-mono-decomp/GodotMonoDecomp/RemoveJsonSourceGenerationClassBody.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveJsonSourceGenerationClassBody.cs similarity index 98% rename from godot-mono-decomp/GodotMonoDecomp/RemoveJsonSourceGenerationClassBody.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveJsonSourceGenerationClassBody.cs index 0cba4cf89..9ee21cd77 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RemoveJsonSourceGenerationClassBody.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveJsonSourceGenerationClassBody.cs @@ -1,10 +1,9 @@ -using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// /// Restores source-level shape for System.Text.Json source-generation context classes. diff --git a/godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveMathF3x.cs similarity index 94% rename from godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveMathF3x.cs index e99749ca3..cc6682be3 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RemoveMathF3x.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/RemoveMathF3x.cs @@ -18,11 +18,9 @@ using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; -using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Transforms; -using ICSharpCode.Decompiler.TypeSystem; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// ///```c# diff --git a/godot-mono-decomp/GodotMonoDecomp/RestoreGeneratedRegexMethods.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/RestoreGeneratedRegexMethods.cs similarity index 94% rename from godot-mono-decomp/GodotMonoDecomp/RestoreGeneratedRegexMethods.cs rename to godot-mono-decomp/GodotMonoDecomp/Transforms/RestoreGeneratedRegexMethods.cs index fe411abc1..d969ea238 100644 --- a/godot-mono-decomp/GodotMonoDecomp/RestoreGeneratedRegexMethods.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/RestoreGeneratedRegexMethods.cs @@ -1,10 +1,7 @@ -using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; -using ICSharpCode.Decompiler.Semantics; -using ICSharpCode.Decompiler.TypeSystem; -namespace GodotMonoDecomp; +namespace GodotMonoDecomp.Transforms; /// /// Restores source-level shape for methods generated by the Regex source generator. From db7cadc45d7be2168360985015627fb65092c0da Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:24:31 -0700 Subject: [PATCH 03/14] update --- .../Transforms/LiftCollectionInitializers.cs | 94 ++++++++++++++++--- .../TestStatementAnnotationLiftCoverage.cs | 36 +++++++ .../CollectionExamplesValidation.csproj | 1 + .../validation/validate_collection_lifting.sh | 7 +- 4 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 godot-mono-decomp/collection_examples/original/TestStatementAnnotationLiftCoverage.cs diff --git a/godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs b/godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs index ad64e49fb..2b4f6d08e 100644 --- a/godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs +++ b/godot-mono-decomp/GodotMonoDecomp/Transforms/LiftCollectionInitializers.cs @@ -1,3 +1,4 @@ +using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; @@ -344,20 +345,52 @@ private static bool TryApplyInitializer( { return false; } - variable.Initializer = initializer.Clone(); + variable.Initializer = CopyInstructionAnnotationsFromSource(initializer.Clone(), initializer); return true; case PropertyDeclaration property: if (!property.IsAutomaticProperty) { return false; } - property.Initializer = initializer.Clone(); + property.Initializer = CopyInstructionAnnotationsFromSource(initializer.Clone(), initializer); return true; default: return false; } } + private static T CopyInstructionAnnotationsFromSource(T target, AstNode source) + where T : AstNode + { + target.CopyInstructionsFrom(source); + return target; + } + + private static T CopyInstructionAnnotationsFromSources( + T target, + IEnumerable? statementSources = null, + IEnumerable? expressionSources = null) + where T : AstNode + { + if (statementSources != null) + { + foreach (var statement in statementSources) + { + target.CopyInstructionsFrom(statement); + } + } + + if (expressionSources != null) + { + foreach (var expression in expressionSources) + { + target.CopyInstructionsFrom(expression); + } + } + + return target; + } + private static bool IsStaticMember(EntityDeclaration member) { return (member.Modifiers & Modifiers.Static) == Modifiers.Static; @@ -463,6 +496,10 @@ private static bool TryMatchConditionalTempAssignment( ifElse.Condition.Clone(), trueInitializer.Clone(), falseInitializer.Clone()); + CopyInstructionAnnotationsFromSources( + conditionalInitializer, + new List { statements[startIndex], statements[startIndex + 1], statements[startIndex + 2] }, + new[] { ifElse.Condition, trueInitializer, falseInitializer }); match = new ConditionalTempAssignmentMatch( memberName, conditionalInitializer, @@ -532,7 +569,10 @@ private static bool TryRewriteInitializerWithRecoveredLocals( continue; } - identifier.ReplaceWith(replacement.Clone()); + var replacementClone = CopyInstructionAnnotationsFromSources( + replacement.Clone(), + expressionSources: new[] { identifier, replacement }); + identifier.ReplaceWith(replacementClone); changed = true; } @@ -552,14 +592,19 @@ private static Expression SimplifyRedundantReadOnlyListCollectionWrappers(Expres while (expression is ObjectCreateExpression rootCreate && TryUnwrapReadOnlyListOfListCollection(rootCreate, out var rootReplacement)) { - expression = rootReplacement.Clone(); + expression = CopyInstructionAnnotationsFromSources( + rootReplacement.Clone(), + expressionSources: new[] { rootCreate, rootReplacement }); } foreach (var objectCreate in expression.Descendants.OfType().ToArray()) { if (TryUnwrapReadOnlyListOfListCollection(objectCreate, out var replacement)) { - objectCreate.ReplaceWith(replacement.Clone()); + var replacementClone = CopyInstructionAnnotationsFromSources( + replacement.Clone(), + expressionSources: new[] { objectCreate, replacement }); + objectCreate.ReplaceWith(replacementClone); } } @@ -832,10 +877,15 @@ private static bool TryMatchListPrelude( return false; } + var arrayInitializer = CopyInstructionAnnotationsFromSources( + new ArrayInitializerExpression(values), + matchedStatements, + values); var collectionInitializer = new ObjectCreateExpression(listDecl.Type.Clone()) { - Initializer = new ArrayInitializerExpression(values) + Initializer = arrayInitializer }; + CopyInstructionAnnotationsFromSources(collectionInitializer, matchedStatements, values); match = new ListPreludeMatch( listVarName, targetMemberName, @@ -913,7 +963,7 @@ private static bool TryMatchListAddRangeSpreadPrelude( return false; } - if (!TryBuildCollectionFromSpreadSegments(listDecl.Type, segments, out var collectionInitializer)) + if (!TryBuildCollectionFromSpreadSegments(listDecl.Type, segments, matchedStatements, out var collectionInitializer)) { return false; } @@ -995,7 +1045,7 @@ private static bool TryMatchHashSetForeachSpreadPrelude( return false; } - if (!TryBuildCollectionFromSpreadSegments(setDecl.Type, segments, out var collectionInitializer)) + if (!TryBuildCollectionFromSpreadSegments(setDecl.Type, segments, matchedStatements, out var collectionInitializer)) { return false; } @@ -1071,7 +1121,7 @@ private static bool TryMatchListSpreadBuilderWrapperAssignment( return false; } - if (!TryBuildCollectionFromSpreadSegments(listDecl.Type, segments, out var listBuilderInitializer)) + if (!TryBuildCollectionFromSpreadSegments(listDecl.Type, segments, matchedStatements, out var listBuilderInitializer)) { return false; } @@ -1163,6 +1213,7 @@ private static bool TryMatchHashSetSpreadForeach( private static bool TryBuildCollectionFromSpreadSegments( AstType collectionType, IReadOnlyList segments, + IReadOnlyList matchedStatements, out Expression initializer) { initializer = Expression.Null; @@ -1184,7 +1235,9 @@ private static bool TryBuildCollectionFromSpreadSegments( var collectionExpression = new ArrayInitializerExpression(elements); collectionExpression.AddAnnotation(CollectionExpressionArrayAnnotation.Instance); + CopyInstructionAnnotationsFromSources(collectionExpression, matchedStatements, segments.Select(segment => segment.Expression)); initializer = new ObjectCreateExpression(collectionType.Clone(), collectionExpression); + CopyInstructionAnnotationsFromSources(initializer, matchedStatements, segments.Select(segment => segment.Expression)); return true; } @@ -1434,10 +1487,15 @@ private static bool TryMatchObjectMemberListPrelude( if (TryMatchTerminalObjectMemberAssignment(statement, listVarName, objectVariableName, out memberName)) { matchedStatements.Add(statement); + var arrayInitializer = CopyInstructionAnnotationsFromSources( + new ArrayInitializerExpression(values), + matchedStatements, + values); initializerValue = new ObjectCreateExpression(listDecl.Type.Clone()) { - Initializer = new ArrayInitializerExpression(values) + Initializer = arrayInitializer }; + CopyInstructionAnnotationsFromSources(initializerValue, matchedStatements, values); nextIndex = i + 1; return true; } @@ -1549,7 +1607,9 @@ private static bool TryMatchRefSlotAssignment(Statement statement, string slotVa private static void AddObjectMemberInitializer(ObjectCreateExpression objectValue, string memberName, Expression memberValue) { objectValue.Initializer ??= new ArrayInitializerExpression(); - objectValue.Initializer.Elements.Add(new NamedExpression(memberName, memberValue.Clone())); + var namedInitializer = new NamedExpression(memberName, memberValue.Clone()); + CopyInstructionAnnotationsFromSource(namedInitializer, memberValue); + objectValue.Initializer.Elements.Add(namedInitializer); } private static bool IsAllowedPreludeNoise(Statement statement) @@ -1808,14 +1868,18 @@ private static bool TryMatchCtorArgumentListPrelude( return false; } - initializer = new ObjectCreateExpression(listDecl.Type.Clone()) - { - Initializer = new ArrayInitializerExpression(values) - }; for (int i = startIndex; i < boundaryIndex; i++) { matchedStatements.Add(statements[i]); } + initializer = new ObjectCreateExpression(listDecl.Type.Clone()) + { + Initializer = CopyInstructionAnnotationsFromSources( + new ArrayInitializerExpression(values), + matchedStatements, + values) + }; + CopyInstructionAnnotationsFromSources(initializer, matchedStatements, values); return true; } diff --git a/godot-mono-decomp/collection_examples/original/TestStatementAnnotationLiftCoverage.cs b/godot-mono-decomp/collection_examples/original/TestStatementAnnotationLiftCoverage.cs new file mode 100644 index 000000000..0f4ef0aae --- /dev/null +++ b/godot-mono-decomp/collection_examples/original/TestStatementAnnotationLiftCoverage.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace ConsoleApp2; + +public static class TestStatementAnnotationLiftCoverage +{ + public class Nested + { + public List Numbers { get; set; } = []; + + public string Name { get; set; } = string.Empty; + } + + public class CoverageClass + { + public List Values { get; set; } = [1, 2, 3]; + + public Nested nested = new Nested + { + Numbers = [4, 5, 6], + Name = "nested" + }; + + public readonly List CtorArgs; + + public CoverageClass() + : this([7, 8, 9]) + { + } + + public CoverageClass(List ctorArgs) + { + CtorArgs = ctorArgs; + } + } +} diff --git a/godot-mono-decomp/collection_examples/validation/CollectionExamplesValidation.csproj b/godot-mono-decomp/collection_examples/validation/CollectionExamplesValidation.csproj index 3ec47c364..10b1b202c 100644 --- a/godot-mono-decomp/collection_examples/validation/CollectionExamplesValidation.csproj +++ b/godot-mono-decomp/collection_examples/validation/CollectionExamplesValidation.csproj @@ -14,5 +14,6 @@ + diff --git a/godot-mono-decomp/collection_examples/validation/validate_collection_lifting.sh b/godot-mono-decomp/collection_examples/validation/validate_collection_lifting.sh index 0e213634d..4f915a11e 100755 --- a/godot-mono-decomp/collection_examples/validation/validate_collection_lifting.sh +++ b/godot-mono-decomp/collection_examples/validation/validate_collection_lifting.sh @@ -59,6 +59,7 @@ if $UPDATE_FIXTURES; then "$OUTPUT_DIR/TestFuncInitializer.cs" \ "$OUTPUT_DIR/TestCtorBoundaryCoverage.cs" \ "$OUTPUT_DIR/TestInterleavedStaticCollectionInit.cs" \ + "$OUTPUT_DIR/TestStatementAnnotationLiftCoverage.cs" \ "$OUTPUT_DIR/TestNestedCollectionExpressionInitializers.cs" \ "$OUTPUT_DIR/CollectionExamplesValidation.Decompiled.csproj" \ "$OUTPUT_DIR/CollectionExamplesValidation.Decompiled.sln" @@ -77,6 +78,7 @@ test -f "$OUTPUT_DIR/TestFuncInitializer.cs" test -f "$OUTPUT_DIR/TestNestedCollectionExpressionInitializers.cs" test -f "$OUTPUT_DIR/TestCtorBoundaryCoverage.cs" test -f "$OUTPUT_DIR/TestInterleavedStaticCollectionInit.cs" +test -f "$OUTPUT_DIR/TestStatementAnnotationLiftCoverage.cs" test -f "$OUTPUT_DIR/CollectionExamplesValidation.Decompiled.csproj" echo "[5/7] Asserting expected lifted and preserved markers" @@ -97,6 +99,8 @@ grep -q "public static readonly List staticStringListField = new List strings = new List" "$OUTPUT_DIR/TestInterleavedStaticCollectionInit.cs" grep -q "public List ListProp1 { get; set; } = new List" "$OUTPUT_DIR/TestInterleavedStaticCollectionInit.cs" grep -q "private Func filter = (string s) => s.Length > 3;" "$OUTPUT_DIR/TestFuncInitializer.cs" +grep -q "= new List { 1, 2, 3 };" "$OUTPUT_DIR/TestStatementAnnotationLiftCoverage.cs" +grep -q ": this(new List { 7, 8, 9 })" "$OUTPUT_DIR/TestStatementAnnotationLiftCoverage.cs" grep -Fq "public static readonly HashSet strings = new HashSet([..stringListConst1" "$OUTPUT_DIR/TestCollectionInitWithSpread.cs" grep -Fq "public static readonly List strings = new List([..stringListConst1" "$OUTPUT_DIR/TestCollectionInitWithSpread.cs" grep -Fq "public static readonly IReadOnlySet strings = new HashSet([..stringListConst1" "$OUTPUT_DIR/TestCollectionInitWithSpread.cs" @@ -130,7 +134,8 @@ if grep -q "_002Ector(" "$OUTPUT_DIR/TestCollectionExpressionInitializers.cs" \ || grep -q "_002Ector(" "$OUTPUT_DIR/TestFuncInitializer.cs" \ || grep -q "_002Ector(" "$OUTPUT_DIR/TestNestedCollectionExpressionInitializers.cs" \ || grep -q "_002Ector(" "$OUTPUT_DIR/TestCtorBoundaryCoverage.cs" \ - || grep -q "_002Ector(" "$OUTPUT_DIR/TestInterleavedStaticCollectionInit.cs"; then + || grep -q "_002Ector(" "$OUTPUT_DIR/TestInterleavedStaticCollectionInit.cs" \ + || grep -q "_002Ector(" "$OUTPUT_DIR/TestStatementAnnotationLiftCoverage.cs"; then echo "Found unexpected _002Ector(...) artifact in decompiled output." exit 1 fi From 1a9be9343df6f4fc7e69916e525f051730d5e9b5 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:59:46 -0700 Subject: [PATCH 04/14] get native components in DotNetDepInfo --- .../GodotMonoDecomp/DotNetDepInfo.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index e91248dc9..58b90b60c 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -27,6 +27,7 @@ public enum HashMatchesNugetOrg public readonly bool Serviceable; public readonly DotNetCoreDepInfo[] deps; public readonly string[] runtimeComponents; + public readonly string[] nativeComponents; public HashMatchesNugetOrg HashMatchesNugetOrgStatus { get; private set; } = HashMatchesNugetOrg.Unknown; public AssemblyNameReference AssemblyRef => AssemblyNameReference.Parse($"{Name}, Version={GetCorrectVersion(Version)}, Culture=neutral, PublicKeyToken=null"); @@ -50,7 +51,9 @@ public DotNetCoreDepInfo( bool serviceable, string path, string sha512, - DotNetCoreDepInfo[] deps, string[] runtimeComponents) + DotNetCoreDepInfo[] deps, + string[] runtimeComponents, + string[] nativeComponents) { var parts = fullName.Split('/'); this.Name = parts[0]; @@ -70,6 +73,7 @@ public DotNetCoreDepInfo( this.deps = deps; this.runtimeComponents = runtimeComponents; + this.nativeComponents = nativeComponents; } static DotNetCoreDepInfo CreateFromJson(string fullName, string version, string target, JObject blob) @@ -118,8 +122,20 @@ static DotNetCoreDepInfo Create(string fullName, string version, string target, } } + string[] nativeComponents = Array.Empty(); + var nativeBlob = blob["targets"]?[target]?[Name + "/" + Version]?["native"] as JObject; + if (nativeBlob != null) + { + nativeComponents = new string[nativeBlob.Count]; + int i = 0; + foreach (var prop in nativeBlob.Properties()) + { + nativeComponents[i] = prop.Name; + i++; + } + } var deps = getDeps(Name, Version, target, blob, _deps); - return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents); + return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents, nativeComponents); } From dc9327ec78fc21a976d3afbc01145cf82759a586 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:28:39 -0700 Subject: [PATCH 05/14] mono: Exclude assembly references if they appear in the runtimepack depinfo --- godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs | 7 +++++-- .../GodotMonoDecomp/ProjectFileWriterGodotStyle.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index 58b90b60c..7d95795d4 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -31,6 +31,9 @@ public enum HashMatchesNugetOrg public HashMatchesNugetOrg HashMatchesNugetOrgStatus { get; private set; } = HashMatchesNugetOrg.Unknown; public AssemblyNameReference AssemblyRef => AssemblyNameReference.Parse($"{Name}, Version={GetCorrectVersion(Version)}, Culture=neutral, PublicKeyToken=null"); + public bool IsAvailableOnNuget => Serviceable && HashMatchesNugetOrgStatus != HashMatchesNugetOrg.NoMatch; + + public bool IsRuntimePack => Type == "runtimepack"; static string GetCorrectVersion(string ver) { @@ -185,14 +188,14 @@ static DotNetCoreDepInfo[] getDeps(string Name, string Version, string target, J public bool HasDep(string name, string? type, bool serviceableAndNuGetOnly = false) { - if (runtimeComponents.Contains(name) && !((!string.IsNullOrEmpty(type) && Type != type) || (serviceableAndNuGetOnly && (!Serviceable || HashMatchesNugetOrgStatus == HashMatchesNugetOrg.NoMatch)))) + if (runtimeComponents.Contains(name) && !((!string.IsNullOrEmpty(type) && Type != type) || (serviceableAndNuGetOnly && !IsAvailableOnNuget))) { return true; } for (int i = 0; i < deps.Length; i++) { if ((!string.IsNullOrEmpty(type) && deps[i].Type != type) || - (serviceableAndNuGetOnly && (!deps[i].Serviceable || deps[i].HashMatchesNugetOrgStatus == HashMatchesNugetOrg.NoMatch))) + (serviceableAndNuGetOnly && !deps[i].IsAvailableOnNuget)) { // skip non-package dependencies if parent is a package continue; diff --git a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs index eae6ccf1a..2f1388841 100644 --- a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs +++ b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs @@ -623,12 +623,16 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec HashSet seenRefs = new HashSet(); + var runtimepackComponents = deps?.deps.Where(d => d.IsRuntimePack).SelectMany(d => d.runtimeComponents).ToHashSet() ?? []; + var nonruntimepackComponents = deps?.deps.Where(d => !d.IsRuntimePack).SelectMany(d => d.runtimeComponents).ToHashSet() ?? []; + runtimepackComponents = [.. runtimepackComponents.Where(c => !nonruntimepackComponents.Contains(c))]; + foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name))) { - if (isNetCoreApp && + if (isNetCoreApp && (runtimepackComponents.Contains(reference.Name) || ( project.AssemblyReferenceClassifier.IsSharedAssembly(reference, out string? runtimePack) && - targetPacks.Contains(runtimePack)) + targetPacks.Contains(runtimePack)))) { continue; } From 2772c0eeec4a6af4e2bb65c26c586260e3ba2774 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:40:49 -0700 Subject: [PATCH 06/14] Add RuntimeComponentInfo to DotNetCoreDepInfo --- .../GodotMonoDecomp/DotNetDepInfo.cs | 123 +++++++++++++++--- .../ProjectFileWriterGodotStyle.cs | 2 +- 2 files changed, 109 insertions(+), 16 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index 7d95795d4..ea4c233dc 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -8,9 +8,52 @@ namespace GodotMonoDecomp; -public class DotNetCoreDepInfo +public class DotNetCoreDepInfo : IEquatable { + public struct RuntimeComponentInfo : IEquatable, IEquatable { + public readonly string Name; + + public readonly string Extension; + + public readonly string? Directory; + + public readonly Version? AssemblyVersion; + + public readonly Version? FileVersion; + + public readonly AssemblyNameReference AssemblyRef => AssemblyNameReference.Parse($"{Name}, Version={AssemblyVersion?.ToString(4) ?? "1.0.0.0"}, Culture=neutral, PublicKeyToken=null"); + + public RuntimeComponentInfo(string name, string extension, string? directory, Version? assemblyVersion, Version? fileVersion) + { + Name = name; + Extension = extension; + Directory = directory; + AssemblyVersion = assemblyVersion; + FileVersion = fileVersion; + } + + public readonly bool Matches(AssemblyReference? reference) + { + return reference != null && Name == reference.Name && AssemblyVersion == reference.Version; + } + public readonly bool Matches(AssemblyNameReference? other) + { + return other != null && Name == other.Name && AssemblyVersion == other.Version; + } + + public readonly bool Equals(RuntimeComponentInfo other) + { + return Name == other.Name && Extension == other.Extension && Directory == other.Directory && AssemblyVersion == other.AssemblyVersion && FileVersion == other.FileVersion; + } + + public readonly bool Equals(RuntimeComponentInfo? other) + { + return other != null && Equals(other.Value); + } + + } + public enum HashMatchesNugetOrg { // This enum is used to determine if the SHA512 hash matches the package downloaded from nuget.org. @@ -26,19 +69,28 @@ public enum HashMatchesNugetOrg public readonly string Sha512; public readonly bool Serviceable; public readonly DotNetCoreDepInfo[] deps; - public readonly string[] runtimeComponents; + public readonly RuntimeComponentInfo[] runtimeComponents; public readonly string[] nativeComponents; + public readonly RuntimeComponentInfo? ThisRuntimeComponent; public HashMatchesNugetOrg HashMatchesNugetOrgStatus { get; private set; } = HashMatchesNugetOrg.Unknown; - public AssemblyNameReference AssemblyRef => AssemblyNameReference.Parse($"{Name}, Version={GetCorrectVersion(Version)}, Culture=neutral, PublicKeyToken=null"); + public AssemblyNameReference AssemblyRef => ThisRuntimeComponent?.AssemblyRef ?? AssemblyNameReference.Parse($"{Name}, Version={ConvertToAssemblyVersion(Version)}, Culture=neutral, PublicKeyToken=null"); public bool IsAvailableOnNuget => Serviceable && HashMatchesNugetOrgStatus != HashMatchesNugetOrg.NoMatch; public bool IsRuntimePack => Type == "runtimepack"; - static string GetCorrectVersion(string ver) + static string ConvertToAssemblyVersion(string ver) { - // if it contains less than 4 parts, add ".0" to the end - var parts = ver.Split('.').ToList(); + var parts = ver.TrimStart('v').Split('-')[0].Split('+')[0].Trim().Split('.').ToList(); + for (int i = 0; i < parts.Count; i++){ + if (!UInt64.TryParse(parts[i], out _)){ + parts = parts.Take(i).ToList(); + break; + } + } + if (parts.Count == 0){ + return "1.0.0.0"; + } while (parts.Count < 4) { parts.Add("0"); @@ -55,8 +107,9 @@ public DotNetCoreDepInfo( string path, string sha512, DotNetCoreDepInfo[] deps, - string[] runtimeComponents, - string[] nativeComponents) + RuntimeComponentInfo[] runtimeComponents, + string[] nativeComponents, + RuntimeComponentInfo? thisRuntimeComponent) { var parts = fullName.Split('/'); this.Name = parts[0]; @@ -77,6 +130,7 @@ public DotNetCoreDepInfo( this.deps = deps; this.runtimeComponents = runtimeComponents; this.nativeComponents = nativeComponents; + this.ThisRuntimeComponent = thisRuntimeComponent; } static DotNetCoreDepInfo CreateFromJson(string fullName, string version, string target, JObject blob) @@ -84,6 +138,16 @@ static DotNetCoreDepInfo CreateFromJson(string fullName, string version, string return Create(fullName, version, target, blob, []); } + static System.Version? TryParseVersionOrDefault(string? version, string? defaultVersion) + { + if (string.IsNullOrEmpty(version) || !System.Version.TryParse(version, out var versionResult)){ + if(string.IsNullOrEmpty(defaultVersion) || !System.Version.TryParse(defaultVersion, out versionResult)){ + return null; + } + } + return versionResult; + } + static DotNetCoreDepInfo Create(string fullName, string version, string target, JObject blob, Dictionary _deps) { @@ -112,16 +176,30 @@ static DotNetCoreDepInfo Create(string fullName, string version, string target, sha512 = libraryBlob["sha512"]?.ToString() ?? ""; } - string[] runtimeComponents = Array.Empty(); + var runtimeComponents = new List(); var runtimeBlob = blob["targets"]?[target]?[Name + "/" + Version]?["runtime"] as JObject; + RuntimeComponentInfo? thisRuntimeComponent = null; if (runtimeBlob != null) { - runtimeComponents = new string[runtimeBlob.Count]; - int i = 0; foreach (var prop in runtimeBlob.Properties()) { - runtimeComponents[i] = System.IO.Path.GetFileNameWithoutExtension(prop.Name); - i++; + var name = System.IO.Path.GetFileNameWithoutExtension(prop.Name); + bool isThisAssembly = name == Name; + var directory = System.IO.Path.GetDirectoryName(prop.Name); + var extension = System.IO.Path.GetExtension(prop.Name); + var assemblyVersion = TryParseVersionOrDefault( + prop.Value["assemblyVersion"]?.ToString(), + isThisAssembly ? ConvertToAssemblyVersion(Version) : null + ); + var fileVersion = TryParseVersionOrDefault( + prop.Value["fileVersion"]?.ToString(), + isThisAssembly ? ConvertToAssemblyVersion(Version) : null + ); + var runtimeComponent = new RuntimeComponentInfo(name, extension, directory, assemblyVersion, fileVersion); + runtimeComponents.Add(runtimeComponent); + if (isThisAssembly){ + thisRuntimeComponent = runtimeComponent; + } } } @@ -138,7 +216,7 @@ static DotNetCoreDepInfo Create(string fullName, string version, string target, } } var deps = getDeps(Name, Version, target, blob, _deps); - return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents, nativeComponents); + return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents.ToArray(), nativeComponents, thisRuntimeComponent); } @@ -186,9 +264,24 @@ static DotNetCoreDepInfo[] getDeps(string Name, string Version, string target, J return result.ToArray(); } + public bool Equals(DotNetCoreDepInfo? other) + { + return other != null + && Name == other.Name + && Version == other.Version + && Type == other.Type + && Serviceable == other.Serviceable + && Path == other.Path + && Sha512 == other.Sha512 + && deps.SequenceEqual(other.deps) + && runtimeComponents.SequenceEqual(other.runtimeComponents) + && nativeComponents.SequenceEqual(other.nativeComponents) + && (ThisRuntimeComponent?.Equals(other.ThisRuntimeComponent) ?? other.ThisRuntimeComponent == null); + } + public bool HasDep(string name, string? type, bool serviceableAndNuGetOnly = false) { - if (runtimeComponents.Contains(name) && !((!string.IsNullOrEmpty(type) && Type != type) || (serviceableAndNuGetOnly && !IsAvailableOnNuget))) + if (runtimeComponents.Any(c => c.Name == name) && !((!string.IsNullOrEmpty(type) && Type != type) || (serviceableAndNuGetOnly && !IsAvailableOnNuget))) { return true; } diff --git a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs index 2f1388841..0d2e0ba29 100644 --- a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs +++ b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs @@ -630,7 +630,7 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name))) { - if (isNetCoreApp && (runtimepackComponents.Contains(reference.Name) || ( + if (isNetCoreApp && (runtimepackComponents.Any(c => c.Matches(reference)) || ( project.AssemblyReferenceClassifier.IsSharedAssembly(reference, out string? runtimePack) && targetPacks.Contains(runtimePack)))) { From b1da42e7957e6a09340fdfb6c3d67832ad46c5d3 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:00:16 -0700 Subject: [PATCH 07/14] add NativeComponentInfo --- .../GodotMonoDecomp/DotNetDepInfo.cs | 77 +++++++++++++++---- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index ea4c233dc..7059c072e 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -11,7 +11,8 @@ namespace GodotMonoDecomp; public class DotNetCoreDepInfo : IEquatable { - public struct RuntimeComponentInfo : IEquatable, IEquatable { + public struct RuntimeComponentInfo : IEquatable, IEquatable + { public readonly string Name; public readonly string Extension; @@ -22,6 +23,10 @@ public struct RuntimeComponentInfo : IEquatable, IEquatabl public readonly Version? FileVersion; + public readonly string Path => System.IO.Path.Combine(Directory ?? "", Name + "." + Extension); + + public readonly string FileName => Name + "." + Extension; + public readonly AssemblyNameReference AssemblyRef => AssemblyNameReference.Parse($"{Name}, Version={AssemblyVersion?.ToString(4) ?? "1.0.0.0"}, Culture=neutral, PublicKeyToken=null"); public RuntimeComponentInfo(string name, string extension, string? directory, Version? assemblyVersion, Version? fileVersion) @@ -54,6 +59,37 @@ public readonly bool Equals(RuntimeComponentInfo? other) } + public struct NativeComponentInfo : IEquatable, IEquatable + { + public readonly string Name; + public readonly string Extension; + public readonly string? Directory; + + public readonly string Path => System.IO.Path.Combine(Directory ?? "", Name + "." + Extension); + + public readonly string FileName => Name + "." + Extension; + + public readonly Version? FileVersion; + + public NativeComponentInfo(string name, string extension, string? directory, Version? fileVersion) + { + Name = name; + Extension = extension; + Directory = directory; + FileVersion = fileVersion; + } + + public readonly bool Equals(NativeComponentInfo other) + { + return Name == other.Name && Extension == other.Extension && Directory == other.Directory && FileVersion == other.FileVersion; + } + + public readonly bool Equals(NativeComponentInfo? other) + { + return other != null && Equals(other.Value); + } + } + public enum HashMatchesNugetOrg { // This enum is used to determine if the SHA512 hash matches the package downloaded from nuget.org. @@ -70,7 +106,7 @@ public enum HashMatchesNugetOrg public readonly bool Serviceable; public readonly DotNetCoreDepInfo[] deps; public readonly RuntimeComponentInfo[] runtimeComponents; - public readonly string[] nativeComponents; + public readonly NativeComponentInfo[] nativeComponents; public readonly RuntimeComponentInfo? ThisRuntimeComponent; public HashMatchesNugetOrg HashMatchesNugetOrgStatus { get; private set; } = HashMatchesNugetOrg.Unknown; public AssemblyNameReference AssemblyRef => ThisRuntimeComponent?.AssemblyRef ?? AssemblyNameReference.Parse($"{Name}, Version={ConvertToAssemblyVersion(Version)}, Culture=neutral, PublicKeyToken=null"); @@ -82,13 +118,16 @@ public enum HashMatchesNugetOrg static string ConvertToAssemblyVersion(string ver) { var parts = ver.TrimStart('v').Split('-')[0].Split('+')[0].Trim().Split('.').ToList(); - for (int i = 0; i < parts.Count; i++){ - if (!UInt64.TryParse(parts[i], out _)){ + for (int i = 0; i < parts.Count; i++) + { + if (!UInt64.TryParse(parts[i], out _)) + { parts = parts.Take(i).ToList(); break; } } - if (parts.Count == 0){ + if (parts.Count == 0) + { return "1.0.0.0"; } while (parts.Count < 4) @@ -108,7 +147,7 @@ public DotNetCoreDepInfo( string sha512, DotNetCoreDepInfo[] deps, RuntimeComponentInfo[] runtimeComponents, - string[] nativeComponents, + NativeComponentInfo[] nativeComponents, RuntimeComponentInfo? thisRuntimeComponent) { var parts = fullName.Split('/'); @@ -140,8 +179,10 @@ static DotNetCoreDepInfo CreateFromJson(string fullName, string version, string static System.Version? TryParseVersionOrDefault(string? version, string? defaultVersion) { - if (string.IsNullOrEmpty(version) || !System.Version.TryParse(version, out var versionResult)){ - if(string.IsNullOrEmpty(defaultVersion) || !System.Version.TryParse(defaultVersion, out versionResult)){ + if (string.IsNullOrEmpty(version) || !System.Version.TryParse(version, out var versionResult)) + { + if (string.IsNullOrEmpty(defaultVersion) || !System.Version.TryParse(defaultVersion, out versionResult)) + { return null; } } @@ -197,26 +238,32 @@ static DotNetCoreDepInfo Create(string fullName, string version, string target, ); var runtimeComponent = new RuntimeComponentInfo(name, extension, directory, assemblyVersion, fileVersion); runtimeComponents.Add(runtimeComponent); - if (isThisAssembly){ + if (isThisAssembly) + { thisRuntimeComponent = runtimeComponent; } } } - string[] nativeComponents = Array.Empty(); + // string[] nativeComponents = Array.Empty(); var nativeBlob = blob["targets"]?[target]?[Name + "/" + Version]?["native"] as JObject; + var nativeComponents = new List(); if (nativeBlob != null) { - nativeComponents = new string[nativeBlob.Count]; - int i = 0; foreach (var prop in nativeBlob.Properties()) { - nativeComponents[i] = prop.Name; - i++; + var name = System.IO.Path.GetFileNameWithoutExtension(prop.Name); + var extension = System.IO.Path.GetExtension(prop.Name); + var directory = System.IO.Path.GetDirectoryName(prop.Name); + var fileVersion = TryParseVersionOrDefault( + prop.Value["fileVersion"]?.ToString(), + null + ); + nativeComponents.Add(new NativeComponentInfo(name, extension, directory, fileVersion)); } } var deps = getDeps(Name, Version, target, blob, _deps); - return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents.ToArray(), nativeComponents, thisRuntimeComponent); + return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents.ToArray(), nativeComponents.ToArray(), thisRuntimeComponent); } From 3161daa45fb35679bc089709960c2dcf43832081 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:03:03 -0700 Subject: [PATCH 08/14] Add hashPath to DotNetDepInfo --- .../GodotMonoDecomp/DotNetDepInfo.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index 7059c072e..149e35b85 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -101,8 +101,8 @@ public enum HashMatchesNugetOrg public readonly string Name; public readonly string Version; public readonly string Type; - public readonly string Path; - public readonly string Sha512; + public readonly string? Path; + public readonly string? Sha512; public readonly bool Serviceable; public readonly DotNetCoreDepInfo[] deps; public readonly RuntimeComponentInfo[] runtimeComponents; @@ -143,8 +143,9 @@ public DotNetCoreDepInfo( string version, string type, bool serviceable, - string path, string sha512, + string? path, + string? hashPath, DotNetCoreDepInfo[] deps, RuntimeComponentInfo[] runtimeComponents, NativeComponentInfo[] nativeComponents, @@ -204,17 +205,19 @@ static DotNetCoreDepInfo Create(string fullName, string version, string target, Version = version; } - var type = "runtimedll"; - var serviceable = false; - var path = ""; - var sha512 = ""; + string type = "runtimedll"; + bool serviceable = false; + string sha512 = ""; + string? path = null; + string? hashPath = null; var libraryBlob = blob["libraries"]?[Name + "/" + Version] as JObject; if (libraryBlob != null) { type = libraryBlob["type"]?.ToString() ?? type; serviceable = libraryBlob["serviceable"]?.Value() ?? serviceable; - path = libraryBlob["path"]?.ToString() ?? ""; sha512 = libraryBlob["sha512"]?.ToString() ?? ""; + path = libraryBlob["path"]?.ToString(); + hashPath = libraryBlob["hashPath"]?.ToString(); } var runtimeComponents = new List(); @@ -263,7 +266,7 @@ static DotNetCoreDepInfo Create(string fullName, string version, string target, } } var deps = getDeps(Name, Version, target, blob, _deps); - return new DotNetCoreDepInfo(Name, Version, type, serviceable, path, sha512, deps, runtimeComponents.ToArray(), nativeComponents.ToArray(), thisRuntimeComponent); + return new DotNetCoreDepInfo(Name, Version, type, serviceable, sha512, path, hashPath, deps, runtimeComponents.ToArray(), nativeComponents.ToArray(), thisRuntimeComponent); } From 8eb12292a0c5cefc24c1befa257d300eea085685 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:34:19 -0700 Subject: [PATCH 09/14] more dotnetdepinfo stuff --- .../GodotMonoDecomp/DotNetDepInfo.cs | 44 +++++++------------ 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index 149e35b85..3de339acb 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -38,14 +38,10 @@ public RuntimeComponentInfo(string name, string extension, string? directory, Ve FileVersion = fileVersion; } - public readonly bool Matches(AssemblyReference? reference) + public readonly bool Matches(IAssemblyReference? reference) { return reference != null && Name == reference.Name && AssemblyVersion == reference.Version; } - public readonly bool Matches(AssemblyNameReference? other) - { - return other != null && Name == other.Name && AssemblyVersion == other.Version; - } public readonly bool Equals(RuntimeComponentInfo other) { @@ -101,14 +97,16 @@ public enum HashMatchesNugetOrg public readonly string Name; public readonly string Version; public readonly string Type; - public readonly string? Path; public readonly string? Sha512; + public readonly string? Path; + public readonly string? HashPath; public readonly bool Serviceable; public readonly DotNetCoreDepInfo[] deps; public readonly RuntimeComponentInfo[] runtimeComponents; public readonly NativeComponentInfo[] nativeComponents; public readonly RuntimeComponentInfo? ThisRuntimeComponent; public HashMatchesNugetOrg HashMatchesNugetOrgStatus { get; private set; } = HashMatchesNugetOrg.Unknown; + public System.Version AssemblyVersion => ThisRuntimeComponent?.AssemblyVersion ?? System.Version.Parse(ConvertToAssemblyVersion(Version)); public AssemblyNameReference AssemblyRef => ThisRuntimeComponent?.AssemblyRef ?? AssemblyNameReference.Parse($"{Name}, Version={ConvertToAssemblyVersion(Version)}, Culture=neutral, PublicKeyToken=null"); public bool IsAvailableOnNuget => Serviceable && HashMatchesNugetOrgStatus != HashMatchesNugetOrg.NoMatch; @@ -165,6 +163,7 @@ public DotNetCoreDepInfo( this.Type = type; this.Serviceable = serviceable; this.Path = path; + this.HashPath = hashPath; this.Sha512 = sha512; this.deps = deps; @@ -322,6 +321,7 @@ public bool Equals(DotNetCoreDepInfo? other) && Type == other.Type && Serviceable == other.Serviceable && Path == other.Path + && HashPath == other.HashPath && Sha512 == other.Sha512 && deps.SequenceEqual(other.deps) && runtimeComponents.SequenceEqual(other.runtimeComponents) @@ -329,33 +329,19 @@ public bool Equals(DotNetCoreDepInfo? other) && (ThisRuntimeComponent?.Equals(other.ThisRuntimeComponent) ?? other.ThisRuntimeComponent == null); } - public bool HasDep(string name, string? type, bool serviceableAndNuGetOnly = false) + public bool Matches(IAssemblyReference? reference) { - if (runtimeComponents.Any(c => c.Name == name) && !((!string.IsNullOrEmpty(type) && Type != type) || (serviceableAndNuGetOnly && !IsAvailableOnNuget))) + return reference != null && reference.Name == Name && reference.Version == AssemblyVersion; + } + + public bool HasDep(IAssemblyReference reference, string? type, bool serviceableAndNuGetOnly = false) + { + bool ShouldFilter(DotNetCoreDepInfo dep) => !((string.IsNullOrEmpty(type) || dep.Type == type) && (!serviceableAndNuGetOnly || dep.IsAvailableOnNuget)); + if (runtimeComponents.Any(c => c.Matches(reference)) && !ShouldFilter(this)) { return true; } - for (int i = 0; i < deps.Length; i++) - { - if ((!string.IsNullOrEmpty(type) && deps[i].Type != type) || - (serviceableAndNuGetOnly && !deps[i].IsAvailableOnNuget)) - { - // skip non-package dependencies if parent is a package - continue; - } - - if (deps[i].Name == name) - { - return true; - } - - if (deps[i].HasDep(name, null, false)) - { - return true; - } - } - - return false; + return deps.Any(d => !ShouldFilter(d) && (d.Matches(reference) || d.HasDep(reference, type, serviceableAndNuGetOnly))); } public static string GetDepPath(string assemblyPath) From a945db2380cd4142c18948de42e10143319a5ff0 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:49:40 -0700 Subject: [PATCH 10/14] fixes --- .../GodotMonoDecomp/DotNetDepInfo.cs | 49 ++++++++++++++++--- .../ProjectFileWriterGodotStyle.cs | 26 +++++----- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index 3de339acb..272bece1b 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -23,9 +23,9 @@ public struct RuntimeComponentInfo : IEquatable, IEquatabl public readonly Version? FileVersion; - public readonly string Path => System.IO.Path.Combine(Directory ?? "", Name + "." + Extension); + public readonly string FileName => Name + Extension; - public readonly string FileName => Name + "." + Extension; + public readonly string Path => System.IO.Path.Combine(Directory ?? "", FileName); public readonly AssemblyNameReference AssemblyRef => AssemblyNameReference.Parse($"{Name}, Version={AssemblyVersion?.ToString(4) ?? "1.0.0.0"}, Culture=neutral, PublicKeyToken=null"); @@ -61,9 +61,9 @@ public struct NativeComponentInfo : IEquatable, IEquatable< public readonly string Extension; public readonly string? Directory; - public readonly string Path => System.IO.Path.Combine(Directory ?? "", Name + "." + Extension); + public readonly string FileName => Name + Extension; - public readonly string FileName => Name + "." + Extension; + public readonly string Path => System.IO.Path.Combine(Directory ?? "", FileName); public readonly Version? FileVersion; @@ -113,6 +113,10 @@ public enum HashMatchesNugetOrg public bool IsRuntimePack => Type == "runtimepack"; + public bool IsProject => Type == "project"; + + public bool HasNoRuntimeComponent => runtimeComponents == null || runtimeComponents.Length == 0; + static string ConvertToAssemblyVersion(string ver) { var parts = ver.TrimStart('v').Split('-')[0].Split('+')[0].Trim().Split('.').ToList(); @@ -329,19 +333,50 @@ public bool Equals(DotNetCoreDepInfo? other) && (ThisRuntimeComponent?.Equals(other.ThisRuntimeComponent) ?? other.ThisRuntimeComponent == null); } + private List GetAllDeps(bool followProjectReferences, HashSet? seen) + { + var result = new HashSet(); + result.AddRange(deps); + var unseenDeps = deps.Where(d => !seen?.Contains(d) ?? true); + if (seen == null) + { + seen = [this, .. deps]; + } + foreach (var dep in unseenDeps) + { + if (!followProjectReferences && dep.Type == "project") + { + continue; + } + var found = dep.GetAllDeps(followProjectReferences, seen); + result.AddRange(found); + seen.AddRange(found); + } + return result.ToList(); + } + + public List GetAllDeps(bool followProjectReferences = false) { + return GetAllDeps(followProjectReferences, null); + } + + public bool Matches(IAssemblyReference? reference) { return reference != null && reference.Name == Name && reference.Version == AssemblyVersion; } - public bool HasDep(IAssemblyReference reference, string? type, bool serviceableAndNuGetOnly = false) + public bool HasDep(IAssemblyReference reference, string? type, bool serviceableAndNuGetOnly = false, bool dontFollowProjectReferences = false) { bool ShouldFilter(DotNetCoreDepInfo dep) => !((string.IsNullOrEmpty(type) || dep.Type == type) && (!serviceableAndNuGetOnly || dep.IsAvailableOnNuget)); - if (runtimeComponents.Any(c => c.Matches(reference)) && !ShouldFilter(this)) + if (runtimeComponents.Any(c => c.Matches(reference) && !c.Equals(ThisRuntimeComponent)) && !ShouldFilter(this)) { return true; } - return deps.Any(d => !ShouldFilter(d) && (d.Matches(reference) || d.HasDep(reference, type, serviceableAndNuGetOnly))); + return deps.Any( + d => !ShouldFilter(d) && + (d.Matches(reference) || + ((!dontFollowProjectReferences || d.Type != "project") + && d.HasDep(reference, type, serviceableAndNuGetOnly, dontFollowProjectReferences)))); } public static string GetDepPath(string assemblyPath) diff --git a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs index 0d2e0ba29..5c2d5d663 100644 --- a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs +++ b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs @@ -212,7 +212,7 @@ static void WritePackageReferences(XmlTextWriter xml, MetadataFile module, IProj // We do not want to include source generator packages in the project file because they will attempt // to generate code which we already have decompiled and create build errors. // Check if it's a possible source generator package; it will have no runtime components - if (dep.runtimeComponents == null || dep.runtimeComponents.Length == 0) + if (dep.HasNoRuntimeComponent) { // double check to see if the module has a reference to this if (module.AssemblyReferences.Any(r => r.Name == dep.Name)) @@ -615,16 +615,20 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec } } - List godotSharpRefs = new List(); + List godotSharpRefs = new List(); - List packageReferences = new List(); + List packageReferences = new List(); - List projectReferences = new List(); + List projectReferences = new List(); HashSet seenRefs = new HashSet(); + bool NotProjectRef(DotNetCoreDepInfo dep) => !settings.CreateAdditionalProjectsForProjectReferences || !dep.IsProject; - var runtimepackComponents = deps?.deps.Where(d => d.IsRuntimePack).SelectMany(d => d.runtimeComponents).ToHashSet() ?? []; - var nonruntimepackComponents = deps?.deps.Where(d => !d.IsRuntimePack).SelectMany(d => d.runtimeComponents).ToHashSet() ?? []; + List RefsToWrite = module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name)).Select(IAssemblyReference (r) => r).ToList(); + + var allDeps = deps?.GetAllDeps(!settings.CreateAdditionalProjectsForProjectReferences) ?? []; + var runtimepackComponents = allDeps.Where(d => d.IsRuntimePack).SelectMany(d => d.runtimeComponents).ToHashSet() ?? []; + var nonruntimepackComponents = allDeps.Where(d => !d.IsRuntimePack && NotProjectRef(d)).SelectMany(d => d.runtimeComponents).ToHashSet() ?? []; runtimepackComponents = [.. runtimepackComponents.Where(c => !nonruntimepackComponents.Contains(c))]; @@ -698,14 +702,14 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec "The following references were not added to the project file because they are part of the project references above."); } - bool IsProjectReference(AssemblyReference reference) + bool IsProjectReference(IAssemblyReference reference) { - return settings.CreateAdditionalProjectsForProjectReferences && deps != null && deps.HasDep(reference.Name, "project", false); + return settings.CreateAdditionalProjectsForProjectReferences && deps != null && deps.HasDep(reference, "project", false); } - bool DepExistsInPackages(AssemblyReference reference) + bool DepExistsInPackages(IAssemblyReference reference) { - return settings.WriteNuGetPackageReferences && deps != null && deps.HasDep(reference.Name, "package", true); + return settings.WriteNuGetPackageReferences && deps != null && deps.HasDep(reference, "package", true); } string GetNewRefOutputPath(string path) @@ -791,7 +795,7 @@ void CopyRef(MetadataFile asembly, string outputPath) } } - void WriteRef(XmlTextWriter newXml, AssemblyReference reference, bool realRef, string? nameOverride = null) + void WriteRef(XmlTextWriter newXml, IAssemblyReference reference, bool realRef, string? nameOverride = null) { newXml.WriteStartElement("Reference"); newXml.WriteAttributeString("Include", nameOverride ?? reference.Name); From e0b2b2895801b082ca089185e453c669d386e18a Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:15:13 -0700 Subject: [PATCH 11/14] Write additional references in comments, copy them --- .../GodotMonoDecomp/DotNetDepInfo.cs | 5 ++ .../ProjectFileWriterGodotStyle.cs | 48 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs index 272bece1b..c97c7c3d2 100644 --- a/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs +++ b/godot-mono-decomp/GodotMonoDecomp/DotNetDepInfo.cs @@ -379,6 +379,11 @@ public bool HasDep(IAssemblyReference reference, string? type, bool serviceableA && d.HasDep(reference, type, serviceableAndNuGetOnly, dontFollowProjectReferences)))); } + public DotNetCoreDepInfo? GetDep(IAssemblyReference reference) + { + return GetAllDeps().FirstOrDefault(d => d.Matches(reference)); + } + public static string GetDepPath(string assemblyPath) { return System.IO.Path.ChangeExtension(assemblyPath, ".deps.json"); diff --git a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs index 5c2d5d663..bc35fd143 100644 --- a/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs +++ b/godot-mono-decomp/GodotMonoDecomp/ProjectFileWriterGodotStyle.cs @@ -17,12 +17,14 @@ // DEALINGS IN THE SOFTWARE. using System.Reflection.PortableExecutable; +using System.Runtime.Loader; using System.Xml; using GodotMonoDecomp; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Util; +using NuGet.Configuration; public interface IGodotProjectWithSettingsProvider : IProjectInfoProvider @@ -615,11 +617,11 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec } } - List godotSharpRefs = new List(); + HashSet godotSharpRefs = new HashSet(); - List packageReferences = new List(); + HashSet packageReferences = new HashSet(); - List projectReferences = new List(); + HashSet projectReferences = new HashSet(); HashSet seenRefs = new HashSet(); bool NotProjectRef(DotNetCoreDepInfo dep) => !settings.CreateAdditionalProjectsForProjectReferences || !dep.IsProject; @@ -632,7 +634,7 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec runtimepackComponents = [.. runtimepackComponents.Where(c => !nonruntimepackComponents.Contains(c))]; - foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name))) + foreach (var reference in RefsToWrite) { if (isNetCoreApp && (runtimepackComponents.Any(c => c.Matches(reference)) || ( project.AssemblyReferenceClassifier.IsSharedAssembly(reference, out string? runtimePack) && @@ -661,6 +663,11 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec continue; } + var referenceDep = deps?.GetDep(reference); + if (referenceDep?.HasNoRuntimeComponent ?? false) { + xml.WriteComment($"Reference '{reference.Name}' has no runtime components, but the assembly makes a reference to it. This may be a source generator package."); + } + WriteRef(xml, reference, true); // if the reference is GodotSharp and there is no GodotSharpEditor reference, we have to add it manually if (reference.Name.Equals("GodotSharp", StringComparison.OrdinalIgnoreCase) && !module.AssemblyReferences.Any(r => r.Name.Equals("GodotSharpEditor", StringComparison.OrdinalIgnoreCase))) @@ -701,6 +708,18 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IGodotProjec }, "The following references were not added to the project file because they are part of the project references above."); } + HashSet AdditionalRefs = GetAdditionalRefsToWrite(deps); + if (AdditionalRefs.Count > 0) { + writeBlockComment(xml, (newXml) => { + foreach (var reference in AdditionalRefs) + { + // realRef is true because we want to copy the file to the output directory if necessary, but not include it in the assembly references + WriteRef(newXml, reference, true); + } + }, + "The following references were found in the dependency manifest but the assembly makes no reference to them."); + } + bool IsProjectReference(IAssemblyReference reference) { @@ -712,6 +731,27 @@ bool DepExistsInPackages(IAssemblyReference reference) return settings.WriteNuGetPackageReferences && deps != null && deps.HasDep(reference, "package", true); } + HashSet GetAdditionalRefsToWrite(DotNetCoreDepInfo? deps){ + if (deps == null){ + return []; + } + var depsToWrite = deps?.deps.Where(d => module.AssemblyReferences.Any(r => d.Matches(r))).ToHashSet() ?? []; + bool ShouldNotFilter(DotNetCoreDepInfo d){ + return !( + DepExistsInPackages(d.AssemblyRef) || + IsProjectReference(d.AssemblyRef) || + IsImplicitReference(d.Name) || + d.HasNoRuntimeComponent || + depsToWrite.Contains(d) || + depsToWrite.Any(d2 => d2.HasDep(d.AssemblyRef, null))); + } + return deps?.deps + .Where(ShouldNotFilter) + .Select(d => d.AssemblyRef as IAssemblyReference) + .Where(ar => project.AssemblyResolver.Resolve(ar) != null) + .ToHashSet() ?? []; + } + string GetNewRefOutputPath(string path) { return Path.Combine(copyToDir, Path.GetFileName(path)); From 7fdb0093da815af51224ad1a9bfc654575b34d57 Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:34:52 -0700 Subject: [PATCH 12/14] Add IL comment annotation feature to C# output --- .../CollectionExpressionOutputVisitor.cs | 212 +++++++++++++++++- .../GodotMonoDecomp/GodotModuleDecompiler.cs | 4 +- .../GodotMonoDecompSettings.cs | 7 + .../GodotMonoDecomp/GodotProjectDecompiler.cs | 4 +- .../GodotMonoDecompCLI/Program.cs | 4 + .../GodotMonoDecompNativeAOT/Lib.cs | 2 + 6 files changed, 226 insertions(+), 7 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs b/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs index 43351cdf9..db9db0e3f 100644 --- a/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs +++ b/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs @@ -1,7 +1,18 @@ +using System.Collections.Generic; using System.IO; -using ICSharpCode.Decompiler.CSharp; +using System.Linq; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Disassembler; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpyX.Extensions; +using SequencePoint = ICSharpCode.Decompiler.DebugInfo.SequencePoint; +using System.Reflection.Metadata; namespace GodotMonoDecomp; @@ -22,12 +33,207 @@ private CollectionExpressionSpreadElementAnnotation() { } } +public class ProxyOutput: ITextOutput +{ + public ITextOutput realOutput; + public ProxyOutput(ITextOutput realOutput) + { + this.realOutput = realOutput; + } + public string IndentationString { + get { return realOutput.IndentationString; } + set { realOutput.IndentationString = value; } + } + public void Indent() + { + realOutput.Indent(); + } + public void Unindent() + { + realOutput.Unindent(); + } + public void Write(char ch) + { + realOutput.Write(ch); + } + public void Write(string text) + { + realOutput.Write(text); + } + public void WriteLine() + { + realOutput.WriteLine(); + } + public void WriteReference(OpCodeInfo opCode, bool omitSuffix = false) + { + realOutput.WriteReference(opCode, omitSuffix); + } + public void WriteReference(MetadataFile metadata, Handle handle, string text, string protocol = "decompile", bool isDefinition = false) + { + realOutput.WriteReference(metadata, handle, text, protocol, isDefinition); + } + public void WriteReference(IType type, string text, bool isDefinition = false) + { + realOutput.WriteReference(type, text, isDefinition); + } + public void WriteReference(IMember member, string text, bool isDefinition = false) + { + realOutput.WriteReference(member, text, isDefinition); + } + public void WriteLocalReference(string text, object reference, bool isDefinition = false) + { + realOutput.WriteLocalReference(text, reference, isDefinition); + } + + public void MarkFoldStart(string collapsedText = "...", bool defaultCollapsed = false, bool isDefinition = false) + { + realOutput.MarkFoldStart(collapsedText, defaultCollapsed, isDefinition); + } + public void MarkFoldEnd() + { + realOutput.MarkFoldEnd(); + } +} + +public class GodotLineDisassembler: MethodBodyDisassembler +{ + + ITextOutput fakeOutput; + ITextOutput realOutput; + + ProxyOutput proxyOutput; + IList sequencePoints; + public GodotLineDisassembler(ITextOutput output, CancellationToken cancellationToken, List sequencePointsToDecompile) : this(new ProxyOutput(new PlainTextOutput()), output, cancellationToken, sequencePointsToDecompile) + { + } + + private GodotLineDisassembler(ProxyOutput proxyOutput, ITextOutput output, CancellationToken cancellationToken, List sequencePointsToDecompile) : base(proxyOutput, cancellationToken){ + this.fakeOutput = proxyOutput.realOutput; + this.proxyOutput = proxyOutput; + this.sequencePoints = sequencePointsToDecompile; + this.realOutput = output; + } + + public override void Disassemble(MetadataFile module, System.Reflection.Metadata.MethodDefinitionHandle handle) + { + base.Disassemble(module, handle); + } + + protected override void WriteInstruction(ITextOutput _, MetadataFile metadataFile, System.Reflection.Metadata.MethodDefinitionHandle methodHandle, ref System.Reflection.Metadata.BlobReader blob, int methodRva) + { + int offset = blob.Offset; + if (sequencePoints.Any(seq => (seq.Offset <= offset && seq.EndOffset > offset))) + { + this.proxyOutput.realOutput = realOutput; + base.WriteInstruction(realOutput, metadataFile, methodHandle, ref blob, methodRva); + } + else + { + this.proxyOutput.realOutput = fakeOutput; + base.WriteInstruction(fakeOutput, metadataFile, methodHandle, ref blob, methodRva); + } + this.proxyOutput.realOutput = fakeOutput; + } +} public class GodotCSharpOutputVisitor : CSharpOutputVisitor { - public GodotCSharpOutputVisitor(TextWriter w, CSharpFormattingOptions formattingOptions) - : base(w, formattingOptions) + private readonly bool emitILAnnotationComments; + private readonly TokenWriter commentWriter; + + private readonly GodotMonoDecompSettings settings; + + private Dictionary> sequencePoints = []; + + private Dictionary>>> startLineToSequencePoints = []; + + private readonly CSharpDecompiler? decompiler; + + private int lastStartLine = 0; + + public GodotCSharpOutputVisitor(TextWriter w, GodotMonoDecompSettings settings, bool emitILAnnotationComments = false, CSharpDecompiler? decompiler = null) + : this(new TextWriterTokenWriter(w), settings, emitILAnnotationComments, decompiler) + { + } + + private GodotCSharpOutputVisitor(TokenWriter w, GodotMonoDecompSettings settings, + bool emitILAnnotationComments, CSharpDecompiler? decompiler) : base(w, settings.CSharpFormattingOptions) { + this.decompiler = decompiler; + this.emitILAnnotationComments = emitILAnnotationComments; + this.settings = settings; + this.commentWriter = w; + } + + // we have to create another visitor and output to a fake writer in order + // for the output visitor to annotate all the nodes with line/column information + // So that we can use this to generate sequence points for the IL instructions + static void WriteCode(TextWriter output, GodotMonoDecompSettings settings, SyntaxTree syntaxTree, IDecompilerTypeSystem typeSystem) + { + syntaxTree.AcceptVisitor(new InsertParenthesesVisitor { InsertParenthesesForReadability = true }); + TokenWriter tokenWriter = new TextWriterTokenWriter(output) { IndentationString = settings.CSharpFormattingOptions.IndentationString }; + tokenWriter = TokenWriter.WrapInWriterThatSetsLocationsInAST(tokenWriter); + syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(tokenWriter, settings, false, null)); + } + + public override void VisitSyntaxTree(SyntaxTree syntaxTree) + { + if (emitILAnnotationComments && decompiler != null) { + var fakeWriter = new StringWriter(); + WriteCode(fakeWriter, settings, syntaxTree, decompiler.TypeSystem); + sequencePoints = decompiler.CreateSequencePoints(syntaxTree); + + // create a + var startLines = sequencePoints.Where(kvp => kvp.Value.Count > 0).SelectMany(kvp => kvp.Value.Select(seq => seq.StartLine)).Distinct().OrderBy(l => l).ToList(); + foreach (var startLine in startLines) + { + var sequencePointsForLine = sequencePoints.Select(kvp => { + return new KeyValuePair>(kvp.Key, kvp.Value.Where(seq => seq.StartLine == startLine).ToList()); + }).Where(kvp => kvp.Value.Count > 0).ToList(); + startLineToSequencePoints[startLine] = sequencePointsForLine; + } + } + base.VisitSyntaxTree(syntaxTree); + } + + protected override void StartNode(AstNode node) + { + var startLine = node.StartLocation.Line; + if (emitILAnnotationComments && startLine > lastStartLine && startLineToSequencePoints.ContainsKey(startLine)) { + var sequencePointsForLine = startLineToSequencePoints[startLine]; + // transform it into a list of (methodHandle, sequencePoint) + var sps = sequencePointsForLine + .SelectMany(kvp => kvp.Value.Select(sp => (Method: kvp.Key, SequencePoint: sp))) + .OrderBy(sp => sp.SequencePoint.Offset) + .ToList(); + foreach (var (Method, SequencePoint) in sps) { + + MethodDefinitionHandle? methodHandle = (MethodDefinitionHandle)Method.Method?.MetadataToken; + if (methodHandle == null) { + continue; + } + + var output = new PlainTextOutput(); + + var methodDisassembler = new GodotLineDisassembler(output, default(CancellationToken), [SequencePoint]); + var metadataFile = Method.Method.ParentModule?.MetadataFile; + if (metadataFile != null) { + methodDisassembler.Disassemble(metadataFile, methodHandle.Value); + var text = output.ToString(); + if (text.Length > 0) { + commentWriter.NewLine(); + foreach (var line in output.ToString().Split('\n')) { + if (line.Length > 0) { + commentWriter.WriteComment(CommentType.SingleLine, line); + } + } + } + } + + } + } + lastStartLine = startLine; + base.StartNode(node); } public override void VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression) diff --git a/godot-mono-decomp/GodotMonoDecomp/GodotModuleDecompiler.cs b/godot-mono-decomp/GodotMonoDecomp/GodotModuleDecompiler.cs index cb0276050..76f77c4ab 100644 --- a/godot-mono-decomp/GodotMonoDecomp/GodotModuleDecompiler.cs +++ b/godot-mono-decomp/GodotMonoDecomp/GodotModuleDecompiler.cs @@ -494,7 +494,7 @@ public string DecompileIndividualFile(string file) var decompiler = module.CreateCSharpDecompilerWithPartials(types); var tree = decompiler.DecompileTypes(types); var stringWriter = new StringWriter(); - tree.AcceptVisitor(new GodotCSharpOutputVisitor(stringWriter, Settings.CSharpFormattingOptions)); + tree.AcceptVisitor(new GodotCSharpOutputVisitor(stringWriter, Settings, Settings.EmitILAnnotationComments, decompiler)); return stringWriter.ToString(); } @@ -679,7 +679,7 @@ private string GetPathForType(ITypeDefinition? typeDef){ syntaxTree = decompiler.DecompileTypes(types); } StringWriter stringWriter = new StringWriter(); - syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(stringWriter, Settings.CSharpFormattingOptions)); + syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(stringWriter, Settings, Settings.EmitILAnnotationComments, decompiler)); var scriptText = stringWriter.ToString(); var scriptInfo = new GodotScriptInfo( diff --git a/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs b/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs index d54d9dbef..0bcd8574c 100644 --- a/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs +++ b/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs @@ -50,6 +50,12 @@ public class GodotMonoDecompSettings : DecompilerSettings /// public bool EnableCollectionInitializerLifting { get; set; } = true; + /// + /// Emit ILInstruction annotations as comments for statement/expression nodes. + /// Intended for debug verification of annotation propagation. + /// + public bool EmitILAnnotationComments { get; set; } = false; + private void InitializeDefaultSettings() { UseNestedDirectoriesForNamespaces = true; @@ -78,6 +84,7 @@ public GodotMonoDecompSettings(LanguageVersion languageVersion) : base(languageV settings.OverrideLanguageVersion = OverrideLanguageVersion; settings.GodotVersionOverride = GodotVersionOverride; settings.EnableCollectionInitializerLifting = EnableCollectionInitializerLifting; + settings.EmitILAnnotationComments = EmitILAnnotationComments; return settings; } diff --git a/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs b/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs index b5ba33d79..0366ef0ce 100644 --- a/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs +++ b/godot-mono-decomp/GodotMonoDecomp/GodotProjectDecompiler.cs @@ -343,7 +343,7 @@ IEnumerable WriteAssemblyInfo(DecompilerTypeSystem ts, Cancella string assemblyInfo = Path.Combine(prop, "AssemblyInfo.cs"); using (var w = CreateFile(Path.Combine(TargetDirectory, assemblyInfo))) { - syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); + syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(w, Settings, Settings.EmitILAnnotationComments, decompiler)); } return new[] { new ProjectItemInfo("Compile", assemblyInfo) }; } @@ -469,7 +469,7 @@ void ProcessFiles(List> files) var path = Path.Combine(TargetDirectory, file.Key); using StreamWriter w = new StreamWriter(path); - syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); + syntaxTree.AcceptVisitor(new GodotCSharpOutputVisitor(w, Settings, Settings.EmitILAnnotationComments, decompiler)); } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { diff --git a/godot-mono-decomp/GodotMonoDecompCLI/Program.cs b/godot-mono-decomp/GodotMonoDecompCLI/Program.cs index a9e177156..f02c16d2c 100644 --- a/godot-mono-decomp/GodotMonoDecompCLI/Program.cs +++ b/godot-mono-decomp/GodotMonoDecompCLI/Program.cs @@ -44,6 +44,7 @@ int Main(string[] args) settings.VerifyNuGetPackageIsFromNugetOrg = result.Value.VerifyNuGetPackages; settings.GodotVersionOverride = result.Value.GodotVersion == null ? null : GodotStuff.ParseGodotVersionFromString(result.Value.GodotVersion); settings.EnableCollectionInitializerLifting = !result.Value.DisableCollectionInitializerLifting || result.Value.EnableCollectionInitializerLifting; + settings.EmitILAnnotationComments = result.Value.EmitILAnnotationComments; // get the current time var startTime = DateTime.Now; // call the DecompileProject function @@ -113,6 +114,9 @@ public class Options [Option("disable-collection-initializer-lifting", Required = false, HelpText = "Disable LiftCollectionInitializers and run RemoveBogusBaseConstructorCalls instead.")] public bool DisableCollectionInitializerLifting { get; set; } + [Option("emit-il-annotation-comments", Required = false, HelpText = "Emit ILInstruction annotations as C# comments for statement/expression nodes.")] + public bool EmitILAnnotationComments { get; set; } + [Option("write-script-info", Required = false, HelpText = "Write script info to a JSON file in the output directory.")] public bool WriteScriptInfo { get; set; } // dump strings option diff --git a/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs b/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs index 39ed410d2..e8d2e59c7 100644 --- a/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs +++ b/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs @@ -109,6 +109,8 @@ struct AOTGodotModuleDecompilerProgress : IProgress { private delegate int ProgressFunction(IntPtr userData, int current, int total, IntPtr status); + private event Action? OnProgress = null; + private readonly ProgressFunction? progressFunction; private readonly IntPtr userData; From 29dd2b7cea60e8eaa77bfaef8a7a7f87aae887ef Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:37:19 -0700 Subject: [PATCH 13/14] Add caching for disassembly lines in C# output visitor --- .../CollectionExpressionOutputVisitor.cs | 95 +++++++++++++++---- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs b/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs index db9db0e3f..f67135a59 100644 --- a/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs +++ b/godot-mono-decomp/GodotMonoDecomp/CollectionExpressionOutputVisitor.cs @@ -150,6 +150,7 @@ public class GodotCSharpOutputVisitor : CSharpOutputVisitor private readonly CSharpDecompiler? decompiler; private int lastStartLine = 0; + private readonly Dictionary<(MetadataFile Module, MethodDefinitionHandle MethodHandle, string SequenceKey), string[]> disassemblyLineCache = []; public GodotCSharpOutputVisitor(TextWriter w, GodotMonoDecompSettings settings, bool emitILAnnotationComments = false, CSharpDecompiler? decompiler = null) : this(new TextWriterTokenWriter(w), settings, emitILAnnotationComments, decompiler) @@ -179,6 +180,10 @@ static void WriteCode(TextWriter output, GodotMonoDecompSettings settings, Synta public override void VisitSyntaxTree(SyntaxTree syntaxTree) { if (emitILAnnotationComments && decompiler != null) { + lastStartLine = 0; + startLineToSequencePoints.Clear(); + sequencePoints.Clear(); + disassemblyLineCache.Clear(); var fakeWriter = new StringWriter(); WriteCode(fakeWriter, settings, syntaxTree, decompiler.TypeSystem); sequencePoints = decompiler.CreateSequencePoints(syntaxTree); @@ -206,36 +211,90 @@ protected override void StartNode(AstNode node) .SelectMany(kvp => kvp.Value.Select(sp => (Method: kvp.Key, SequencePoint: sp))) .OrderBy(sp => sp.SequencePoint.Offset) .ToList(); - foreach (var (Method, SequencePoint) in sps) { + var methodHandles = new Dictionary(); + foreach (var sp in sps) + { + if (methodHandles.ContainsKey(sp.Method)) + { + continue; + } - MethodDefinitionHandle? methodHandle = (MethodDefinitionHandle)Method.Method?.MetadataToken; - if (methodHandle == null) { + if (TryGetMethodDefinitionHandle(sp.Method, out var metadataFile, out var methodHandle)) + { + methodHandles[sp.Method] = (metadataFile, methodHandle); + } + } + + var emittedSequencePoints = new HashSet<(MetadataFile Module, MethodDefinitionHandle MethodHandle, int Offset, int EndOffset)>(); + foreach (var sp in sps) + { + if (!methodHandles.TryGetValue(sp.Method, out var resolvedMethod)) + { continue; } - var output = new PlainTextOutput(); - - var methodDisassembler = new GodotLineDisassembler(output, default(CancellationToken), [SequencePoint]); - var metadataFile = Method.Method.ParentModule?.MetadataFile; - if (metadataFile != null) { - methodDisassembler.Disassemble(metadataFile, methodHandle.Value); - var text = output.ToString(); - if (text.Length > 0) { - commentWriter.NewLine(); - foreach (var line in output.ToString().Split('\n')) { - if (line.Length > 0) { - commentWriter.WriteComment(CommentType.SingleLine, line); - } - } - } + var emitKey = (resolvedMethod.Module, resolvedMethod.MethodHandle, sp.SequencePoint.Offset, sp.SequencePoint.EndOffset); + if (!emittedSequencePoints.Add(emitKey)) + { + continue; + } + + var lines = GetDisassemblyLines(resolvedMethod.Module, resolvedMethod.MethodHandle, [sp.SequencePoint]); + if (lines.Length == 0) + { + continue; } + commentWriter.NewLine(); + foreach (var line in lines) + { + commentWriter.WriteComment(CommentType.SingleLine, line); + } } } lastStartLine = startLine; base.StartNode(node); } + private string[] GetDisassemblyLines(MetadataFile metadataFile, MethodDefinitionHandle methodHandle, List sequencePointList) + { + var sequenceKey = string.Join(";", sequencePointList.Select(sp => $"{sp.Offset:x4}-{sp.EndOffset:x4}")); + var cacheKey = (metadataFile, methodHandle, sequenceKey); + if (disassemblyLineCache.TryGetValue(cacheKey, out var cachedLines)) + { + return cachedLines; + } + + var output = new PlainTextOutput(); + var methodDisassembler = new GodotLineDisassembler(output, default(CancellationToken), sequencePointList); + methodDisassembler.Disassemble(metadataFile, methodHandle); + var lines = output + .ToString() + .Split('\n') + .Where(line => line.Length > 0) + .ToArray(); + disassemblyLineCache[cacheKey] = lines; + return lines; + } + + private static bool TryGetMethodDefinitionHandle(ILFunction function, out MetadataFile metadataFile, out MethodDefinitionHandle methodHandle) + { + metadataFile = null!; + methodHandle = default; + + var method = function.Method; + var token = method?.MetadataToken ?? default; + var module = method?.ParentModule?.MetadataFile; + if (module == null || token.IsNil || token.Kind != HandleKind.MethodDefinition) + { + return false; + } + + metadataFile = module; + methodHandle = (MethodDefinitionHandle)token; + return true; + } + public override void VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression) { if (arrayInitializerExpression.Annotation() == null) From f5ddb55d1003a562ab79001d1997f09dfbc4381a Mon Sep 17 00:00:00 2001 From: nikitalita <69168929+nikitalita@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:51:16 -0700 Subject: [PATCH 14/14] Plumb csharp settings to UI --- .../GodotMonoDecomp/GodotMonoDecompSettings.cs | 1 + godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs | 6 ++++++ .../include/godot_mono_decomp.h | 3 +++ utility/gdre_config.cpp | 15 +++++++++++++++ utility/godot_mono_decomp_wrapper.cpp | 9 +++++++++ utility/godot_mono_decomp_wrapper.h | 3 +++ 6 files changed, 37 insertions(+) diff --git a/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs b/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs index 0bcd8574c..bca21b277 100644 --- a/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs +++ b/godot-mono-decomp/GodotMonoDecomp/GodotMonoDecompSettings.cs @@ -83,6 +83,7 @@ public GodotMonoDecompSettings(LanguageVersion languageVersion) : base(languageV settings.CreateAdditionalProjectsForProjectReferences = CreateAdditionalProjectsForProjectReferences; settings.OverrideLanguageVersion = OverrideLanguageVersion; settings.GodotVersionOverride = GodotVersionOverride; + settings.RemoveGeneratedJsonContextBody = RemoveGeneratedJsonContextBody; settings.EnableCollectionInitializerLifting = EnableCollectionInitializerLifting; settings.EmitILAnnotationComments = EmitILAnnotationComments; return settings; diff --git a/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs b/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs index e8d2e59c7..6aa980132 100644 --- a/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs +++ b/godot-mono-decomp/GodotMonoDecompNativeAOT/Lib.cs @@ -58,6 +58,9 @@ public static IntPtr AOTCreateGodotModuleDecompiler( bool verifyNuGetPackageIsFromNugetOrg, bool copyOutOfTreeReferences, bool createAdditionalProjectsForProjectReferences, + bool removeGeneratedJsonContextBody, + bool enableCollectionInitializerLifting, + bool emitILAnnotationComments, int OverrideLanguageVersion ) { @@ -71,6 +74,9 @@ int OverrideLanguageVersion VerifyNuGetPackageIsFromNugetOrg = verifyNuGetPackageIsFromNugetOrg, CopyOutOfTreeReferences = copyOutOfTreeReferences, CreateAdditionalProjectsForProjectReferences = createAdditionalProjectsForProjectReferences, + RemoveGeneratedJsonContextBody = removeGeneratedJsonContextBody, + EnableCollectionInitializerLifting = enableCollectionInitializerLifting, + EmitILAnnotationComments = emitILAnnotationComments, OverrideLanguageVersion = OverrideLanguageVersion == 0 ? null : (LanguageVersion)OverrideLanguageVersion, GodotVersionOverride = godotVersionOverrideStr == null ? null : GodotStuff.ParseGodotVersionFromString(godotVersionOverrideStr) }; diff --git a/godot-mono-decomp/GodotMonoDecompNativeAOT/include/godot_mono_decomp.h b/godot-mono-decomp/GodotMonoDecompNativeAOT/include/godot_mono_decomp.h index 5f21f06f5..1407d5914 100644 --- a/godot-mono-decomp/GodotMonoDecompNativeAOT/include/godot_mono_decomp.h +++ b/godot-mono-decomp/GodotMonoDecompNativeAOT/include/godot_mono_decomp.h @@ -48,6 +48,9 @@ void* GodotMonoDecomp_CreateGodotModuleDecompiler( bool verifyNuGetPackageIsFromNugetOrg, bool copyOutOfTreeReferences, bool createAdditionalProjectsForProjectReferences, + bool removeGeneratedJsonContextBody, + bool enableCollectionInitializerLifting, + bool emitILAnnotationComments, LanguageVersion OverrideLanguageVersion ); diff --git a/utility/gdre_config.cpp b/utility/gdre_config.cpp index 616b919df..827c25d11 100644 --- a/utility/gdre_config.cpp +++ b/utility/gdre_config.cpp @@ -291,6 +291,21 @@ Vector> GDREConfig::_init_default_settings() { "Create additional projects for project references", "If a project reference is detected, create an additional project and add it to the solution.", true)), + memnew(GDREConfigSetting( + "CSharp/remove_generated_json_context_body", + "Remove generated Json context body", + "Remove the body of generated JsonSourceGeneration context classes.", + false)), + memnew(GDREConfigSetting( + "CSharp/enable_collection_initializer_lifting", + "Enable collection initializer lifting", + "Enable the LiftCollectionInitializers transform.\nIf disabled, the legacy RemoveBogusBaseConstructorCalls transform is used.", + true)), + memnew(GDREConfigSetting( + "CSharp/emit_il_annotation_comments", + "Emit IL annotation comments", + "Emit ILInstruction annotations as comments for statement/expression nodes.\nIntended for debug verification of annotation propagation.", + false)), memnew(GDREConfigSetting_CSharpForceLanguageVersion()), memnew(GDREConfigSetting( "CSharp/compile_after_decompile", diff --git a/utility/godot_mono_decomp_wrapper.cpp b/utility/godot_mono_decomp_wrapper.cpp index e73e097ef..03726098c 100644 --- a/utility/godot_mono_decomp_wrapper.cpp +++ b/utility/godot_mono_decomp_wrapper.cpp @@ -49,6 +49,9 @@ Error GodotMonoDecompWrapper::_load(const String &p_assembly_path, const Vector< p_settings.VerifyNuGetPackageIsFromNugetOrg, p_settings.CopyOutOfTreeReferences, p_settings.CreateAdditionalProjectsForProjectReferences, + p_settings.RemoveGeneratedJsonContextBody, + p_settings.EnableCollectionInitializerLifting, + p_settings.EmitILAnnotationComments, (LanguageVersion)p_settings.OverrideLanguageVersion); delete[] originalProjectFiles_c_array; if (new_decompiler_handle == nullptr) { @@ -304,6 +307,9 @@ GodotMonoDecompWrapper::GodotMonoDecompSettings GodotMonoDecompWrapper::GodotMon settings.VerifyNuGetPackageIsFromNugetOrg = GDREConfig::get_singleton()->get_setting("CSharp/verify_nuget_package_is_from_nuget_org", false); settings.CopyOutOfTreeReferences = GDREConfig::get_singleton()->get_setting("CSharp/copy_out_of_tree_references", true); settings.CreateAdditionalProjectsForProjectReferences = GDREConfig::get_singleton()->get_setting("CSharp/create_additional_projects_for_project_references", true); + settings.RemoveGeneratedJsonContextBody = GDREConfig::get_singleton()->get_setting("CSharp/remove_generated_json_context_body", false); + settings.EnableCollectionInitializerLifting = GDREConfig::get_singleton()->get_setting("CSharp/enable_collection_initializer_lifting", true); + settings.EmitILAnnotationComments = GDREConfig::get_singleton()->get_setting("CSharp/emit_il_annotation_comments", false); settings.OverrideLanguageVersion = GDREConfig::get_singleton()->get_setting("CSharp/force_language_version", 0); return settings; } @@ -313,6 +319,9 @@ bool GodotMonoDecompWrapper::GodotMonoDecompSettings::operator==(const GodotMono VerifyNuGetPackageIsFromNugetOrg == p_other.VerifyNuGetPackageIsFromNugetOrg && CopyOutOfTreeReferences == p_other.CopyOutOfTreeReferences && CreateAdditionalProjectsForProjectReferences == p_other.CreateAdditionalProjectsForProjectReferences && + RemoveGeneratedJsonContextBody == p_other.RemoveGeneratedJsonContextBody && + EnableCollectionInitializerLifting == p_other.EnableCollectionInitializerLifting && + EmitILAnnotationComments == p_other.EmitILAnnotationComments && OverrideLanguageVersion == p_other.OverrideLanguageVersion && GodotVersionOverride == p_other.GodotVersionOverride; } diff --git a/utility/godot_mono_decomp_wrapper.h b/utility/godot_mono_decomp_wrapper.h index c9e237b95..6293d7ba1 100644 --- a/utility/godot_mono_decomp_wrapper.h +++ b/utility/godot_mono_decomp_wrapper.h @@ -29,6 +29,9 @@ class GodotMonoDecompWrapper : public RefCounted { bool VerifyNuGetPackageIsFromNugetOrg = false; bool CopyOutOfTreeReferences = true; bool CreateAdditionalProjectsForProjectReferences = true; + bool RemoveGeneratedJsonContextBody = false; + bool EnableCollectionInitializerLifting = true; + bool EmitILAnnotationComments = false; int OverrideLanguageVersion = 0; String GodotVersionOverride; static GodotMonoDecompSettings get_default_settings();