From a2986adb6ec161af2d817043110f9fc6e5d37bd7 Mon Sep 17 00:00:00 2001 From: Denis Pakhorukov Date: Tue, 26 Nov 2024 19:30:32 +0200 Subject: [PATCH] #377 Fix game updates compatibility issues Minimum supported game version is 643502. --- .gitignore | 1 + .../Core/Patch/PatchTargetResolver.cs | 59 ++++++++++++------- src/MultiplayerMod/Directory.Build.targets | 5 ++ .../Mechanics/Objects/ComponentEventsArgs.cs | 1 + .../Game/Mechanics/Objects/ObjectEvents.cs | 14 ++++- .../ScheduleConditionalUpdatePatch.cs | 17 ------ .../Game/UI/Tools/Events/BuildEvents.cs | 2 +- .../Commands/Chores/FindNextChore.cs | 4 +- .../Commands/Gameplay/CallMethod.cs | 14 +++-- .../Screens/Schedule/ChangeSchedulesList.cs | 1 + .../Multiplayer/UI/Notifications.cs | 2 +- .../World/Debug/WorldDebugSnapshot.cs | 4 +- src/MultiplayerMod/MultiplayerMod.csproj | 2 +- 13 files changed, 72 insertions(+), 54 deletions(-) delete mode 100644 src/MultiplayerMod/Game/Mechanics/Schedules/ScheduleConditionalUpdatePatch.cs diff --git a/.gitignore b/.gitignore index 6593ff74..4a6868d9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ src/Assembly-CSharp src/*/bin/ src/*/obj/ src/*/lib/ +/lib/exposed diff --git a/src/MultiplayerMod/Core/Patch/PatchTargetResolver.cs b/src/MultiplayerMod/Core/Patch/PatchTargetResolver.cs index 262c8c8a..39fbe0fe 100644 --- a/src/MultiplayerMod/Core/Patch/PatchTargetResolver.cs +++ b/src/MultiplayerMod/Core/Patch/PatchTargetResolver.cs @@ -13,13 +13,15 @@ public class PatchTargetResolver { private static readonly Logging.Logger log = LoggerFactory.GetLogger(); - private readonly Dictionary> targets; + private readonly Dictionary> targets; private readonly IEnumerable baseTypes; private readonly Assembly assembly = Assembly.GetAssembly(typeof(global::Game)); private bool checkArgumentsSerializable; + private const BindingFlags instanceBindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; + private PatchTargetResolver( - Dictionary> targets, + Dictionary> targets, IEnumerable baseTypes, bool checkArgumentsSerializable ) { @@ -53,40 +55,40 @@ public IEnumerable Resolve() { .SelectMany( type => { if (classTypes.Contains(type)) - return targets[type].Select(methodName => GetMethodOrSetter(type, methodName, null)); + return targets[type].Select(it => GetMethodOrSetter(type, it, null)); var implementedInterfaces = GetImplementedInterfaces(interfaceTypes, type); return implementedInterfaces.SelectMany( implementedInterface => targets[implementedInterface].Select( - methodName => GetMethodOrSetter(type, methodName, implementedInterface) + it => GetMethodOrSetter(type, it, implementedInterface) ) ); } ).ToList(); } - private MethodBase GetMethodOrSetter(Type type, string methodName, Type? interfaceType) { - var methodInfo = GetMethod(type, methodName, interfaceType); + private MethodBase GetMethodOrSetter(Type type, MemberReference reference, Type? interfaceType) { + var methodInfo = GetMethod(type, reference, interfaceType); if (methodInfo != null) { if (checkArgumentsSerializable) ValidateArguments(methodInfo); return methodInfo; } - var property = GetSetter(type, methodName, interfaceType); + var property = GetSetter(type, reference.Name, interfaceType); if (property != null) return property; - var message = $"Method {type}.{methodName} ({interfaceType}) not found"; + var message = $"Method {type}.{reference.Name} ({interfaceType}) not found"; log.Error(message); throw new Exception(message); } - private MethodBase? GetMethod(Type type, string methodName, Type? interfaceType) { - var methodInfo = type.GetMethod( - methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance - ); + private MethodBase? GetMethod(Type type, MemberReference reference, Type? interfaceType) { + var methodInfo = reference.Parameters == null + ? type.GetMethod(reference.Name, instanceBindingFlags) + : type.GetMethod(reference.Name, instanceBindingFlags, null, reference.Parameters, null); + if (methodInfo != null) return methodInfo; @@ -94,10 +96,10 @@ private MethodBase GetMethodOrSetter(Type type, string methodName, Type? interfa return null; // Some overrides names prefixed by interface e.g. Clinic#ISliderControl.SetSliderValue - methodInfo = type.GetMethod( - interfaceType.Name + "." + methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance - ); + methodInfo = reference.Parameters == null + ? type.GetMethod(interfaceType.Name + "." + reference.Name, instanceBindingFlags) + : type.GetMethod(interfaceType.Name + "." + reference.Name, instanceBindingFlags, null, reference.Parameters, null); + return methodInfo; } @@ -163,23 +165,38 @@ out ISurrogateSelector _ throw new Exception(message); } + public class MemberReference { + public string Name { get; } + public Type[]? Parameters { get; } + + public MemberReference(string name, Type[]? parameters) { + Name = name; + Parameters = parameters; + } + } + public class Builder { - private readonly Dictionary> targets = new(); + private readonly Dictionary> targets = new(); private readonly List baseTypes = new(); private bool checkArgumentsSerializable; - private List GetTargets(Type type) { + private List GetTargets(Type type) { if (targets.TryGetValue(type, out var methods)) return methods; - methods = new List(); + methods = new List(); targets[type] = methods; return methods; } public Builder AddMethods(Type type, params string[] methods) { - GetTargets(type).AddRange(methods); + GetTargets(type).AddRange(methods.Select(name => new MemberReference(name, null))); + return this; + } + + public Builder AddMethods(Type type, params MemberReference[] references) { + GetTargets(type).AddRange(references); return this; } diff --git a/src/MultiplayerMod/Directory.Build.targets b/src/MultiplayerMod/Directory.Build.targets index 48ba890c..68ccf2aa 100644 --- a/src/MultiplayerMod/Directory.Build.targets +++ b/src/MultiplayerMod/Directory.Build.targets @@ -107,6 +107,11 @@ Type, Field Public + + protected.*?(get|set)_ + Method + Public + diff --git a/src/MultiplayerMod/Game/Mechanics/Objects/ComponentEventsArgs.cs b/src/MultiplayerMod/Game/Mechanics/Objects/ComponentEventsArgs.cs index 3ca5f49a..50210dc6 100644 --- a/src/MultiplayerMod/Game/Mechanics/Objects/ComponentEventsArgs.cs +++ b/src/MultiplayerMod/Game/Mechanics/Objects/ComponentEventsArgs.cs @@ -7,5 +7,6 @@ public record ComponentEventsArgs( ComponentReference Target, Type MethodType, string MethodName, + Type[] Parameters, object[] Args ); diff --git a/src/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs b/src/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs index 96a8f433..ea052f0d 100644 --- a/src/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs +++ b/src/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs @@ -2,22 +2,26 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using HarmonyLib; using MultiplayerMod.Core.Patch; using MultiplayerMod.ModRuntime.Context; using MultiplayerMod.ModRuntime.StaticCompatibility; using MultiplayerMod.Multiplayer.Objects; using UnityEngine; +using static MultiplayerMod.Core.Patch.PatchTargetResolver; namespace MultiplayerMod.Game.Mechanics.Objects; [HarmonyPatch] public static class ObjectEvents { + private static readonly ConditionalWeakTable methodParametersCache = new(); + public static event Action? ComponentMethodCalled; public static event Action? StateMachineMethodCalled; - private static readonly PatchTargetResolver targets = new PatchTargetResolver.Builder() + private static readonly PatchTargetResolver targets = new Builder() .AddMethods(typeof(Filterable), nameof(Filterable.SelectedTag)) .AddMethods( typeof(TreeFilterable), @@ -86,7 +90,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(typeof(Assignable), new MemberReference(nameof(Assignable.Assign), new[] { typeof(IAssignableIdentity) } )) + .AddMethods(typeof(Assignable), nameof(Assignable.Unassign)) .AddMethods( typeof(AccessControl), nameof(AccessControl.SetPermission), @@ -148,11 +153,16 @@ private static void ProcessObjectEvent(object __instance, MethodBase __originalM ).ToArray(); switch (__instance) { case KMonoBehaviour kMonoBehaviour: + if (!methodParametersCache.TryGetValue(__originalMethod, out var parameters)) { + parameters = __originalMethod.GetParameters().Select(it => it.ParameterType).ToArray(); + methodParametersCache.Add(__originalMethod, parameters); + } ComponentMethodCalled?.Invoke( new ComponentEventsArgs( kMonoBehaviour.GetReference(), __originalMethod.DeclaringType!, __originalMethod.Name, + parameters, args ) ); diff --git a/src/MultiplayerMod/Game/Mechanics/Schedules/ScheduleConditionalUpdatePatch.cs b/src/MultiplayerMod/Game/Mechanics/Schedules/ScheduleConditionalUpdatePatch.cs deleted file mode 100644 index 5c19f0a1..00000000 --- a/src/MultiplayerMod/Game/Mechanics/Schedules/ScheduleConditionalUpdatePatch.cs +++ /dev/null @@ -1,17 +0,0 @@ -using HarmonyLib; - -namespace MultiplayerMod.Game.Mechanics.Schedules; - -[HarmonyPatch(typeof(Schedule))] -public static class ScheduleConditionalUpdatePatch { - - [HarmonyPrefix] - [HarmonyPatch(nameof(Schedule.SetGroup))] - private static bool SetGroupPrefix(Schedule __instance, int idx, ScheduleGroup group) { - if (idx < 0 || idx >= __instance.blocks.Count) - return false; - - return __instance.blocks[idx].GroupId != group.Id; - } - -} diff --git a/src/MultiplayerMod/Game/UI/Tools/Events/BuildEvents.cs b/src/MultiplayerMod/Game/UI/Tools/Events/BuildEvents.cs index 6ec65d51..240d7e06 100644 --- a/src/MultiplayerMod/Game/UI/Tools/Events/BuildEvents.cs +++ b/src/MultiplayerMod/Game/UI/Tools/Events/BuildEvents.cs @@ -39,7 +39,7 @@ ILGenerator generator result.Add(new CodeInstruction(OpCodes.Ldc_I4_0)); result.Add(new CodeInstruction(OpCodes.Stloc_S, asyncReplace)); - result.AddConditional(source, it => it.IsStoreToLocal(4)); + result.AddConditional(source, it => it.IsStoreToLocal(5)); // replaced = true; result.Add(new CodeInstruction(OpCodes.Ldc_I4_1)); diff --git a/src/MultiplayerMod/Multiplayer/Commands/Chores/FindNextChore.cs b/src/MultiplayerMod/Multiplayer/Commands/Chores/FindNextChore.cs index 1d591394..dd44dc97 100644 --- a/src/MultiplayerMod/Multiplayer/Commands/Chores/FindNextChore.cs +++ b/src/MultiplayerMod/Multiplayer/Commands/Chores/FindNextChore.cs @@ -127,7 +127,7 @@ ref choreWithIdCollision string serverChoreType, ref Chore? choreWithIdCollision ) { - var chores = instance.GetProviders() + var chores = instance.providers .SelectMany(provider => provider.choreWorldMap.Values.SelectMany(x => x)) .ToArray(); return FindFullMatch( @@ -149,7 +149,7 @@ ref Chore? choreWithIdCollision var globalChores = Object.FindObjectsOfType() .SelectMany( - consumer => consumer.GetProviders() + consumer => consumer.providers .SelectMany(provider => provider.choreWorldMap.Values.SelectMany(x => x)).ToArray() ).ToArray(); diff --git a/src/MultiplayerMod/Multiplayer/Commands/Gameplay/CallMethod.cs b/src/MultiplayerMod/Multiplayer/Commands/Gameplay/CallMethod.cs index eb629366..8faf453f 100644 --- a/src/MultiplayerMod/Multiplayer/Commands/Gameplay/CallMethod.cs +++ b/src/MultiplayerMod/Multiplayer/Commands/Gameplay/CallMethod.cs @@ -12,12 +12,17 @@ public class CallMethod : MultiplayerCommand { private readonly StateMachineReference? stateMachineTarget; private readonly Type methodType; private readonly string methodName; + private readonly Type[]? parameters; private readonly object[] args; + private const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | + BindingFlags.DeclaredOnly; + public CallMethod(ComponentEventsArgs eventArgs) { componentTarget = eventArgs.Target; methodType = eventArgs.MethodType; methodName = eventArgs.MethodName; + parameters = eventArgs.Parameters; args = eventArgs.Args; } @@ -29,12 +34,9 @@ public CallMethod(StateMachineEventsArgs eventArgs) { } public override void Execute(MultiplayerCommandContext context) { - var method = methodType - .GetMethod( - methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | - BindingFlags.DeclaredOnly - ); + var method = parameters == null + ? methodType.GetMethod(methodName, flags) + : methodType.GetMethod(methodName, flags, null, parameters, null); var args = this.args.Select( arg => arg switch { diff --git a/src/MultiplayerMod/Multiplayer/Commands/Screens/Schedule/ChangeSchedulesList.cs b/src/MultiplayerMod/Multiplayer/Commands/Screens/Schedule/ChangeSchedulesList.cs index f2fcdc0b..496a9cce 100644 --- a/src/MultiplayerMod/Multiplayer/Commands/Screens/Schedule/ChangeSchedulesList.cs +++ b/src/MultiplayerMod/Multiplayer/Commands/Screens/Schedule/ChangeSchedulesList.cs @@ -63,6 +63,7 @@ private class SerializableSchedule { 1, a.Name, a.description, + a.uiColor, a.notificationTooltip, a.allowedTypes, a.alarm diff --git a/src/MultiplayerMod/Multiplayer/UI/Notifications.cs b/src/MultiplayerMod/Multiplayer/UI/Notifications.cs index 5789a1cb..369e5b44 100644 --- a/src/MultiplayerMod/Multiplayer/UI/Notifications.cs +++ b/src/MultiplayerMod/Multiplayer/UI/Notifications.cs @@ -21,7 +21,7 @@ private void OnConnectionLost(ConnectionLostEvent @event) { screen.AddPlainText("Connection has been lost. Further play can not be synced"); screen.AddOption( "OK", - _ => PauseScreen.Instance.OnQuitConfirm() + _ => PauseScreen.Instance.OnQuitConfirm(true) ); } } diff --git a/src/MultiplayerMod/Multiplayer/World/Debug/WorldDebugSnapshot.cs b/src/MultiplayerMod/Multiplayer/World/Debug/WorldDebugSnapshot.cs index cb1fe715..5082fe11 100644 --- a/src/MultiplayerMod/Multiplayer/World/Debug/WorldDebugSnapshot.cs +++ b/src/MultiplayerMod/Multiplayer/World/Debug/WorldDebugSnapshot.cs @@ -71,9 +71,7 @@ private static int Hash(Chore chore) { hash = CombineHashCodes(hash, chore.masterPriority.priority_class.GetHashCode()); hash = CombineHashCodes(hash, chore.masterPriority.priority_value.GetHashCode()); hash = CombineHashCodes(hash, chore.IsPreemptable.GetHashCode()); - hash = CombineHashCodes(hash, chore.priorityMod.GetHashCode()); - hash = CombineHashCodes(hash, chore.addToDailyReport.GetHashCode()); - return CombineHashCodes(hash, chore.reportType.GetHashCode()); + return CombineHashCodes(hash, chore.priorityMod.GetHashCode()); } private static unsafe int[] HashBatches(T* objects) where T : unmanaged { diff --git a/src/MultiplayerMod/MultiplayerMod.csproj b/src/MultiplayerMod/MultiplayerMod.csproj index 8cce1a12..977969a2 100644 --- a/src/MultiplayerMod/MultiplayerMod.csproj +++ b/src/MultiplayerMod/MultiplayerMod.csproj @@ -31,7 +31,7 @@ VANILLA_ID - 577063 + 643502 2