From 646a7559cca8834c6567d6bce1cd2e637a3432ba Mon Sep 17 00:00:00 2001 From: Zuev Vladimir Date: Fri, 11 Aug 2023 17:53:42 +0200 Subject: [PATCH] #207 Fix `Room is not serializable`. (#214) Also added validation for all other arguments for their serialization. --- .../Core/Patch/PatchTargetResolver.cs | 63 ++++++++++++++++++- .../Game/Mechanics/Objects/ObjectEvents.cs | 4 +- .../Messaging/Surrogates/RoomSurrogate.cs | 31 +++++++++ .../Surrogates/SerializationSurrogates.cs | 1 + 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/RoomSurrogate.cs diff --git a/MultiplayerMod/Core/Patch/PatchTargetResolver.cs b/MultiplayerMod/Core/Patch/PatchTargetResolver.cs index f1c0b761..262c8c8a 100644 --- a/MultiplayerMod/Core/Patch/PatchTargetResolver.cs +++ b/MultiplayerMod/Core/Patch/PatchTargetResolver.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using MultiplayerMod.Core.Logging; +using MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; +using UnityEngine; namespace MultiplayerMod.Core.Patch; @@ -13,10 +16,16 @@ public class PatchTargetResolver { private readonly Dictionary> targets; private readonly IEnumerable baseTypes; private readonly Assembly assembly = Assembly.GetAssembly(typeof(global::Game)); + private bool checkArgumentsSerializable; - public PatchTargetResolver(Dictionary> targets, IEnumerable baseTypes) { + private PatchTargetResolver( + Dictionary> targets, + IEnumerable baseTypes, + bool checkArgumentsSerializable + ) { this.targets = targets; this.baseTypes = baseTypes; + this.checkArgumentsSerializable = checkArgumentsSerializable; } public IEnumerable Resolve() { @@ -58,8 +67,11 @@ public IEnumerable Resolve() { private MethodBase GetMethodOrSetter(Type type, string methodName, Type? interfaceType) { var methodInfo = GetMethod(type, methodName, interfaceType); - if (methodInfo != null) + if (methodInfo != null) { + if (checkArgumentsSerializable) + ValidateArguments(methodInfo); return methodInfo; + } var property = GetSetter(type, methodName, interfaceType); if (property != null) @@ -112,10 +124,50 @@ private List GetImplementedInterfaces(IEnumerable interfaceTypes, Ty .Where(interfaceType => interfaceType.IsAssignableFrom(type)) .ToList(); + private void ValidateArguments(MethodBase? methodBase) { + if (methodBase == null) return; + + var parameters = methodBase.GetParameters(); + foreach (var parameterInfo in parameters) { + var paramType = parameterInfo.ParameterType; + ValidateTypeIsSerializable(methodBase, paramType); + } + } + + private void ValidateTypeIsSerializable(MethodBase methodBase, Type checkType) { + if (checkType.IsInterface) { + var implementations = assembly.GetTypes() + .Where( + type => type.IsClass && checkType.IsAssignableFrom(type) + ).ToList(); + foreach (var implementation in implementations) { + ValidateTypeIsSerializable(methodBase, implementation); + } + return; + } + if (checkType.IsEnum) { + return; + } + var isTypeSerializable = checkType.IsDefined(typeof(SerializableAttribute), false); + var isSurrogateExists = SerializationSurrogates.Selector.GetSurrogate( + checkType, + new StreamingContext(StreamingContextStates.All), + out ISurrogateSelector _ + ) != null; + var gameObjectOrKMono = + checkType.IsSubclassOf(typeof(GameObject)) || checkType.IsSubclassOf(typeof(KMonoBehaviour)); + if (isTypeSerializable || isSurrogateExists || gameObjectOrKMono) return; + + var message = $"{checkType} is not serializable (method {methodBase}."; + log.Error(message); + throw new Exception(message); + } + public class Builder { private readonly Dictionary> targets = new(); private readonly List baseTypes = new(); + private bool checkArgumentsSerializable; private List GetTargets(Type type) { if (targets.TryGetValue(type, out var methods)) @@ -136,7 +188,12 @@ public Builder AddBaseType(Type type) { return this; } - public PatchTargetResolver Build() => new(targets, baseTypes); + public Builder CheckArgumentsSerializable(bool check) { + checkArgumentsSerializable = check; + return this; + } + + public PatchTargetResolver Build() => new(targets, baseTypes, checkArgumentsSerializable); } diff --git a/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs b/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs index 6177715b..3ee1c753 100644 --- a/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs +++ b/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs @@ -85,7 +85,8 @@ public static class ObjectEvents { ) .AddMethods(typeof(ISidescreenButtonControl), nameof(ISidescreenButtonControl.OnSidescreenButtonPressed)) .AddMethods(typeof(IUserControlledCapacity), nameof(IUserControlledCapacity.UserMaxCapacity)) - .AddMethods(typeof(Assignable), nameof(Assignable.Assign), nameof(Assignable.Unassign)).AddMethods( + .AddMethods(typeof(Assignable), nameof(Assignable.Assign), nameof(Assignable.Unassign)) + .AddMethods( typeof(AccessControl), nameof(AccessControl.SetPermission), nameof(AccessControl.ClearPermission), @@ -116,6 +117,7 @@ public static class ObjectEvents { // ) .AddBaseType(typeof(KMonoBehaviour)) .AddBaseType(typeof(StateMachine.Instance)) + .CheckArgumentsSerializable(true) .Build(); // ReSharper disable once UnusedMember.Local diff --git a/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/RoomSurrogate.cs b/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/RoomSurrogate.cs new file mode 100644 index 00000000..a4b0e79c --- /dev/null +++ b/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/RoomSurrogate.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates; + +public class RoomSurrogate : ISerializationSurrogate, ISurrogateType { + + public Type Type => typeof(Room); + + public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { + var room = obj as Room; + var allIds = new List(room!.primary_buildings); + allIds.AddRange(room.buildings); + allIds.AddRange(room.plants); + var firstGo = allIds.First().gameObject!; + var cell = Grid.PosToCell(firstGo); + info.AddValue("cell", cell); + } + + public object? SetObjectData( + object obj, + SerializationInfo info, + StreamingContext context, + ISurrogateSelector selector + ) { + var cell = info.GetInt32("cell"); + return global::Game.Instance.roomProber.GetCavityForCell(cell)?.room; + } +} diff --git a/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/SerializationSurrogates.cs b/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/SerializationSurrogates.cs index eda3d134..0cf9c119 100644 --- a/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/SerializationSurrogates.cs +++ b/MultiplayerMod/Platform/Steam/Network/Messaging/Surrogates/SerializationSurrogates.cs @@ -17,6 +17,7 @@ static SerializationSurrogates() { Selector.Add(new CarePackageInstanceDataSurrogate()); Selector.Add(new ComplexRecipeSurrogate()); Selector.Add(new MinionStartingStatsSurrogate()); + Selector.Add(new RoomSurrogate()); Selector.Add(new SpaceDestinationSurrogate()); Selector.Add(new SpiceGrinderSurrogate()); }