From ee04a666777a91f0a629f6147b2c834beb7b8251 Mon Sep 17 00:00:00 2001 From: Mikhail Domasevich Date: Wed, 3 Dec 2025 21:24:35 +0900 Subject: [PATCH 1/4] fix: #4070 Command null sender connection for server-owned objects --- .../Weaver/Processors/CommandProcessor.cs | 6 ++-- Assets/Mirror/Editor/Weaver/WeaverTypes.cs | 2 ++ .../Editor/NetworkServer/NetworkServerTest.cs | 28 +++++++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs index ad9912df5b..c5eb89f898 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs @@ -91,10 +91,8 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write ParameterDefinition param = md.Parameters[i]; if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command)) { - // load 'this.' - worker.Emit(OpCodes.Ldarg_0); - // call get_connectionToClient - worker.Emit(OpCodes.Call, weaverTypes.NetworkBehaviourConnectionToClientReference); + // Load NetworkServer.localConnection + worker.Emit(OpCodes.Call, weaverTypes.NetworkServerLocalConnectionReference); } else { diff --git a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs index 62d654bebd..27c3b7ba30 100644 --- a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs +++ b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs @@ -17,6 +17,7 @@ public class WeaverTypes public MethodReference GetWriterReference; public MethodReference ReturnWriterReference; + public MethodReference NetworkServerLocalConnectionReference; public MethodReference NetworkClientConnectionReference; public MethodReference RemoteCallDelegateConstructor; @@ -86,6 +87,7 @@ public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFail TypeReference NetworkServerType = Import(typeof(NetworkServer)); NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed); + NetworkServerLocalConnectionReference = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localConnection", ref WeavingFailed); TypeReference NetworkClientType = Import(typeof(NetworkClient)); NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed); diff --git a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs index 819e98e198..0bb6c0362a 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs @@ -851,10 +851,9 @@ public void SendCommand_RequiresAuthority() Assert.That(serverComponent.called, Is.EqualTo(0)); } - // [Command] with NetworkConnectionToClient parameter on host should be automatically set to .connectionToClient. - // this is what happens in server-only mode too. + // Command with NetworkConnectionToClient arg expects a non-null connection arg when called on the client owned object. [Test] - public void SendCommand_ConnectionToClient_HostMode() + public void SendCommand_ClientOwnedObject_ConnectionNotNull() { // listen & connect NetworkServer.Listen(1); @@ -875,6 +874,29 @@ public void SendCommand_ConnectionToClient_HostMode() Assert.That(comp.called, Is.EqualTo(1)); } + // Command with NetworkConnectionToClient arg expects a non-null connection arg when called on the server owned object. + [Test] + public void SendCommand_ServerOwnedObject_ConnectionNotNull() + { + // listen & connect + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // add an identity with two networkbehaviour components + // spawned, otherwise command handler won't find it in .spawned. + // WITH OWNER = WITH AUTHORITY + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out CommandWithConnectionToClientNetworkBehaviour comp, ownerConnection: null); + + // call the command, which has a 'NetworkConnectionToClient = null' default parameter + comp.TestCommand(); + ProcessMessages(); + + // on server it should be != null + Assert.That(comp.conn, !Is.Null); + Assert.That(comp.conn, Is.EqualTo(NetworkServer.localConnection)); + Assert.That(comp.called, Is.EqualTo(1)); + } + [Test] public void SendToAll() { From 9eed36880c89587430f4c616a6e4ff0ef4fc74a7 Mon Sep 17 00:00:00 2001 From: Mikhail Domasevich Date: Thu, 4 Dec 2025 17:12:25 +0900 Subject: [PATCH 2/4] Fix comment of the test Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs index 0bb6c0362a..1ae910611f 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs @@ -884,7 +884,7 @@ public void SendCommand_ServerOwnedObject_ConnectionNotNull() // add an identity with two networkbehaviour components // spawned, otherwise command handler won't find it in .spawned. - // WITH OWNER = WITH AUTHORITY + // WITHOUT OWNER (SERVER-OWNED OBJECT) CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out CommandWithConnectionToClientNetworkBehaviour comp, ownerConnection: null); // call the command, which has a 'NetworkConnectionToClient = null' default parameter From 46272b9b84305d27c0847ca5d24f85b766d9a6db Mon Sep 17 00:00:00 2001 From: Mikhail Domasevich Date: Wed, 24 Dec 2025 16:46:36 +0900 Subject: [PATCH 3/4] Clarify Command sender selection for host --- Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs | 2 ++ Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs index c5eb89f898..2034e87f66 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs @@ -91,6 +91,8 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write ParameterDefinition param = md.Parameters[i]; if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command)) { + // Server-owned object => no owner connection (connectionToClient is null). + // Host calling [Command] should use NetworkServer.localConnection as sender. // Load NetworkServer.localConnection worker.Emit(OpCodes.Call, weaverTypes.NetworkServerLocalConnectionReference); } diff --git a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs index 1ae910611f..ecc40ee97a 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs @@ -870,6 +870,7 @@ public void SendCommand_ClientOwnedObject_ConnectionNotNull() // on server it should be != null Assert.That(comp.conn, !Is.Null); + // Host player-owned object: sender == connectionToClient (host's localConnection). Assert.That(comp.conn, Is.EqualTo(comp.connectionToClient)); Assert.That(comp.called, Is.EqualTo(1)); } @@ -893,6 +894,7 @@ public void SendCommand_ServerOwnedObject_ConnectionNotNull() // on server it should be != null Assert.That(comp.conn, !Is.Null); + // Host mode + server-owned object: connectionToClient is null, so sender must be localConnection. Assert.That(comp.conn, Is.EqualTo(NetworkServer.localConnection)); Assert.That(comp.called, Is.EqualTo(1)); } From a74662d489c97d7d90334142121ff09cc81d1c57 Mon Sep 17 00:00:00 2001 From: mischa <16416509+miwarnec@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:48:33 +0100 Subject: [PATCH 4/4] Update CommandProcessor.cs --- Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs index 2034e87f66..0e40564ac6 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs @@ -94,6 +94,7 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write // Server-owned object => no owner connection (connectionToClient is null). // Host calling [Command] should use NetworkServer.localConnection as sender. // Load NetworkServer.localConnection + // Fixes: https://github.com/MirrorNetworking/Mirror/issues/4070 worker.Emit(OpCodes.Call, weaverTypes.NetworkServerLocalConnectionReference); } else