From 6624e66cfef8a34bd7c72a58fc2a7465058533bd Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 3 Nov 2026 11:38:13 -0500 Subject: [PATCH 1/4] fix: Private nested types in NetworkVariables in CoreCLR --- .../Editor/CodeGen/CodeGenHelpers.cs | 30 +++++++++++++++++++ .../Editor/CodeGen/NetworkBehaviourILPP.cs | 20 +++++++++++++ 2 files changed, 50 insertions(+) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index 41d4a8c570..a134933f1c 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -208,6 +208,36 @@ public static bool HasInterface(this TypeReference typeReference, string interfa } } + /// + /// Collects every type that assembly scoped code cannot access when it uses . + /// This is each private or protected nested type in its nesting chain, or in the chain of its generic arguments. + /// + /// The type the generated code uses. + /// Receives each inaccessible type, without duplicates. + public static void CollectTypesHiddenFromAssembly(this TypeReference type, List hidden) + { + if (type is GenericInstanceType genericInstance) + { + foreach (var argument in genericInstance.GenericArguments) + { + argument.CollectTypesHiddenFromAssembly(hidden); + } + } + else if (type is TypeSpecification specification) + { + specification.ElementType.CollectTypesHiddenFromAssembly(hidden); + return; + } + + for (var definition = type.Resolve(); definition != null && definition.IsNested; definition = definition.DeclaringType) + { + if ((definition.IsNestedPrivate || definition.IsNestedFamily || definition.IsNestedFamilyAndAssembly) && !hidden.Contains(definition)) + { + hidden.Add(definition); + } + } + } + /// /// Checks a generic method instantiation against the generic constraints of the method it instantiates. /// Mono does not enforce these on the calls codegen emits, but CoreCLR does, and it rejects the entire diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index d8ab346df4..04da32c334 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -437,6 +437,26 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, continue; } + // CoreCLR enforces member access on the generic calls emitted below; Mono does not. + var hiddenTypes = new List(); + type.CollectTypesHiddenFromAssembly(hiddenTypes); + if (hiddenTypes.Count > 0) + { +#if ENABLE_CORECLR + foreach (var hiddenType in hiddenTypes) + { + m_Diagnostics.AddError($"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); + } + continue; +#else + // Log a warning in Mono so we can warn users who haven't yet switched their projects over. + foreach (var hiddenType in hiddenTypes) + { + m_Diagnostics.AddWarning($"NetworkVariable serialization for {type.Name} will fail in CoreCLR! With CoreCLR generated code cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); + } +#endif + } + if (serializeMethod != null) { instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod))); From bb3347e379c497d730a9ccac6d97ab852265908b Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 3 Nov 2026 11:53:43 -0500 Subject: [PATCH 2/4] Update CHANGELOG and upgrade-guide --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Documentation~/upgrade-guide.md | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7251c0de71..7a1f7be9c2 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -25,6 +25,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Ensured a useful and human readable error is logged when a NetworkVariable is trying to serialize a private or protected type. (#4239) - Issue where `NetworkTime` reported a `TickOffset` of almost a whole tick, instead of zero, for some times that land on a tick (for example 0.3 at a tick rate of 10), which put `TickWithPartial` one tick ahead or, in long sessions, `Tick` one tick behind. Times between minus one tick and zero now report tick -1 with a positive `TickOffset`, as other negative times do. (#4228) - Issue where bits written with `BitWriter` were lost when a later `BitWriter.TryBeginWriteBits` call in the same bitwise context grew the `FastBufferWriter`. The bytes written earlier in that context were replaced with zeros or uninitialized memory. (#4226) - Issue where a growable `FastBufferWriter` created with a size of 0 never returned from its first write or `TryBeginWrite` call that needed the buffer to grow. (#4224) diff --git a/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md b/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md index 217a8c0c2e..1729babf7c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md +++ b/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md @@ -123,6 +123,14 @@ In version 2.x, the `Lerp` and `SmoothDampening` interpolation types smooth by d Version 3.x introduces the foundations of the unified GameObject layer for Netcode for Entities, known as the hybrid prefab concept. Version 3.x doesn't enable this integration by default for standard GameObject workflows. Standard `NetworkBehaviour`, `NetworkVariable`, and RPC workflows work the same as in version 2.x. +## CoreCLR compilation changes + +CoreCLR introduces a new compilation pipeline that changes some custom serialization generation. + +## NetworkVariable serialization + +The Mono compilation pipeline was capable of generating serialization for private and protected types. With CoreCLR, this is no longer possible and will fail the compilation. Ensure any types used inside NetworkVariables in your project are declared as `public` or `internal`. + ## Additional resources - [Install Netcode for GameObjects](install.md) From 366fc35150fc65648bb335629b34b6edeeee8cd3 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 5 Nov 2026 11:18:12 -0500 Subject: [PATCH 3/4] Update com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md Co-authored-by: Amy Reeve --- com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md b/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md index 1729babf7c..8124d6c7bc 100644 --- a/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md +++ b/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md @@ -127,7 +127,7 @@ Version 3.x introduces the foundations of the unified GameObject layer for Netco CoreCLR introduces a new compilation pipeline that changes some custom serialization generation. -## NetworkVariable serialization +### NetworkVariable serialization The Mono compilation pipeline was capable of generating serialization for private and protected types. With CoreCLR, this is no longer possible and will fail the compilation. Ensure any types used inside NetworkVariables in your project are declared as `public` or `internal`. From 4de1e345508f102842aa2fab9a912f9d20834286 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 5 Nov 2026 10:30:32 -0500 Subject: [PATCH 4/4] Fix U-PR comments --- .../Editor/CodeGen/CodeGenHelpers.cs | 33 ++++++++++++++++--- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 16 +++++---- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index a134933f1c..626e6492b1 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -211,33 +211,58 @@ public static bool HasInterface(this TypeReference typeReference, string interfa /// /// Collects every type that assembly scoped code cannot access when it uses . /// This is each private or protected nested type in its nesting chain, or in the chain of its generic arguments. + /// A protected internal type is only hidden when it is declared in an assembly whose internals cannot see. /// /// The type the generated code uses. + /// The assembly the generated code is emitted into. /// Receives each inaccessible type, without duplicates. - public static void CollectTypesHiddenFromAssembly(this TypeReference type, List hidden) + public static void CollectTypesHiddenFromAssembly(this TypeReference type, AssemblyDefinition consumer, List hidden) { if (type is GenericInstanceType genericInstance) { foreach (var argument in genericInstance.GenericArguments) { - argument.CollectTypesHiddenFromAssembly(hidden); + argument.CollectTypesHiddenFromAssembly(consumer, hidden); } } else if (type is TypeSpecification specification) { - specification.ElementType.CollectTypesHiddenFromAssembly(hidden); + specification.ElementType.CollectTypesHiddenFromAssembly(consumer, hidden); return; } for (var definition = type.Resolve(); definition != null && definition.IsNested; definition = definition.DeclaringType) { - if ((definition.IsNestedPrivate || definition.IsNestedFamily || definition.IsNestedFamilyAndAssembly) && !hidden.Contains(definition)) + var isHidden = definition.IsNestedPrivate || definition.IsNestedFamily || definition.IsNestedFamilyAndAssembly || + (definition.IsNestedFamilyOrAssembly && !consumer.CanSeeInternalsOf(definition.Module.Assembly)); + if (isHidden && !hidden.Contains(definition)) { hidden.Add(definition); } } } + /// + /// Checks whether code in can access the internal members of . + /// + /// The assembly that accesses the members. + /// The assembly that declares the members. + /// True if both are the same assembly, or grants InternalsVisibleTo. + private static bool CanSeeInternalsOf(this AssemblyDefinition consumer, AssemblyDefinition owner) + { + if (owner.Name.Name == consumer.Name.Name) + { + return true; + } + + // InternalsVisibleTo takes "Name" or "Name, PublicKey=..."; only the simple name matters here. + return owner.CustomAttributes.Any(attribute => + attribute.AttributeType.FullName == typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute).FullName && + attribute.ConstructorArguments.Count == 1 && + attribute.ConstructorArguments[0].Value is string friend && + friend.Split(',')[0].Trim() == consumer.Name.Name); + } + /// /// Checks a generic method instantiation against the generic constraints of the method it instantiates. /// Mono does not enforce these on the calls codegen emits, but CoreCLR does, and it rejects the entire diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 04da32c334..5c9c0bddbb 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -439,22 +439,24 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, // CoreCLR enforces member access on the generic calls emitted below; Mono does not. var hiddenTypes = new List(); - type.CollectTypesHiddenFromAssembly(hiddenTypes); + type.CollectTypesHiddenFromAssembly(assembly, hiddenTypes); if (hiddenTypes.Count > 0) { -#if ENABLE_CORECLR - foreach (var hiddenType in hiddenTypes) + // Use the target assembly's defines: a Mono editor can build a CoreCLR player. + if (assemblyDefines.Contains("ENABLE_CORECLR")) { - m_Diagnostics.AddError($"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); + foreach (var hiddenType in hiddenTypes) + { + m_Diagnostics.AddError($"{type} cannot be used in a network variable - Cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); + } + continue; } - continue; -#else + // Log a warning in Mono so we can warn users who haven't yet switched their projects over. foreach (var hiddenType in hiddenTypes) { m_Diagnostics.AddWarning($"NetworkVariable serialization for {type.Name} will fail in CoreCLR! With CoreCLR generated code cannot access the {hiddenType.Name} type to generate serialization as it is {(hiddenType.IsNestedPrivate ? "private" : "protected")}. To fix this, change the access modifier to internal or public."); } -#endif } if (serializeMethod != null)