Skip to content

Commit

Permalink
#377 Fix game updates compatibility issues
Browse files Browse the repository at this point in the history
Minimum supported game version is 643502.
  • Loading branch information
polycone committed Nov 26, 2024
1 parent 02bb5e8 commit a2986ad
Show file tree
Hide file tree
Showing 13 changed files with 72 additions and 54 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ src/Assembly-CSharp
src/*/bin/
src/*/obj/
src/*/lib/
/lib/exposed
59 changes: 38 additions & 21 deletions src/MultiplayerMod/Core/Patch/PatchTargetResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ public class PatchTargetResolver {

private static readonly Logging.Logger log = LoggerFactory.GetLogger<PatchTargetResolver>();

private readonly Dictionary<Type, List<string>> targets;
private readonly Dictionary<Type, List<MemberReference>> targets;
private readonly IEnumerable<Type> 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<Type, List<string>> targets,
Dictionary<Type, List<MemberReference>> targets,
IEnumerable<Type> baseTypes,
bool checkArgumentsSerializable
) {
Expand Down Expand Up @@ -53,51 +55,51 @@ public IEnumerable<MethodBase> 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;

if (interfaceType == null)
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;
}

Expand Down Expand Up @@ -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<Type, List<string>> targets = new();
private readonly Dictionary<Type, List<MemberReference>> targets = new();
private readonly List<Type> baseTypes = new();
private bool checkArgumentsSerializable;

private List<string> GetTargets(Type type) {
private List<MemberReference> GetTargets(Type type) {
if (targets.TryGetValue(type, out var methods))
return methods;

methods = new List<string>();
methods = new List<MemberReference>();
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;
}

Expand Down
5 changes: 5 additions & 0 deletions src/MultiplayerMod/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@
<TargetRule>Type, Field</TargetRule>
<VisibilityRule>Public</VisibilityRule>
</ExposureRules>
<ExposureRules Include="Protected properties setters and getters">
<PatternRule>protected.*?(get|set)_</PatternRule>
<TargetRule>Method</TargetRule>
<VisibilityRule>Public</VisibilityRule>
</ExposureRules>
</ItemGroup>
<ExposeAssembly SourceAssemblies="@(ExposeAssemblies)" Rules="@(ExposureRules)"/>
</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public record ComponentEventsArgs(
ComponentReference Target,
Type MethodType,
string MethodName,
Type[] Parameters,
object[] Args
);
14 changes: 12 additions & 2 deletions src/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MethodBase, Type[]> methodParametersCache = new();

public static event Action<ComponentEventsArgs>? ComponentMethodCalled;
public static event Action<StateMachineEventsArgs>? 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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
)
);
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion src/MultiplayerMod/Game/UI/Tools/Events/BuildEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -149,7 +149,7 @@ ref Chore? choreWithIdCollision
var globalChores =
Object.FindObjectsOfType<ChoreConsumer>()
.SelectMany(
consumer => consumer.GetProviders()
consumer => consumer.providers
.SelectMany(provider => provider.choreWorldMap.Values.SelectMany(x => x)).ToArray()
).ToArray();

Expand Down
14 changes: 8 additions & 6 deletions src/MultiplayerMod/Multiplayer/Commands/Gameplay/CallMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private class SerializableSchedule {
1,
a.Name,
a.description,
a.uiColor,
a.notificationTooltip,
a.allowedTypes,
a.alarm
Expand Down
2 changes: 1 addition & 1 deletion src/MultiplayerMod/Multiplayer/UI/Notifications.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>(T* objects) where T : unmanaged {
Expand Down
2 changes: 1 addition & 1 deletion src/MultiplayerMod/MultiplayerMod.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</PropertyGroup>
<PropertyGroup>
<OniSupportedContent>VANILLA_ID</OniSupportedContent>
<OniMinimumSupportedBuild>577063</OniMinimumSupportedBuild>
<OniMinimumSupportedBuild>643502</OniMinimumSupportedBuild>
<OniApiVersion>2</OniApiVersion>
</PropertyGroup>
<ItemGroup>
Expand Down

0 comments on commit a2986ad

Please sign in to comment.