Skip to content

Commit d3c7e3d

Browse files
authored
Merge pull request #278 from ifBars/agent/fix-273-il2cpp-contract-delegate
fix(npc): bridge contract events on IL2CPP
2 parents bf0ad41 + 81e957b commit d3c7e3d

2 files changed

Lines changed: 69 additions & 17 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#if IL2CPPMELON
2+
using S1Quests = Il2CppScheduleOne.Quests;
3+
#else
4+
using S1Quests = ScheduleOne.Quests;
5+
#endif
6+
7+
using System.Reflection;
8+
using S1API.Entities;
9+
using UnityEngine.Events;
10+
11+
namespace S1API.Tests.Entities;
12+
13+
public sealed class NpcCustomerContractAssignedBridgeTests
14+
{
15+
[Fact]
16+
public void BridgeMatchesRuntimeUnityEventSignature()
17+
{
18+
Type customerType = typeof(NPCCustomer);
19+
FieldInfo bridgeField = customerType.GetField(
20+
"_contractAssignedBridge",
21+
BindingFlags.Instance | BindingFlags.NonPublic)!;
22+
FieldInfo eventField = customerType.GetField(
23+
"_contractAssignedUnityEvent",
24+
BindingFlags.Instance | BindingFlags.NonPublic)!;
25+
MethodInfo handler = customerType.GetMethod(
26+
"HandleContractAssigned",
27+
BindingFlags.Instance | BindingFlags.NonPublic)!;
28+
29+
Assert.Equal(
30+
typeof(UnityAction<S1Quests.Contract>),
31+
bridgeField.FieldType);
32+
Assert.Equal(
33+
typeof(UnityEvent<S1Quests.Contract>),
34+
eventField.FieldType);
35+
Assert.Equal(
36+
typeof(S1Quests.Contract),
37+
Assert.Single(handler.GetParameters()).ParameterType);
38+
}
39+
40+
[Fact]
41+
public void UnityActionDelegateInheritanceMatchesRuntime()
42+
{
43+
bool derivesFromManagedDelegate = typeof(Delegate).IsAssignableFrom(
44+
typeof(UnityAction<S1Quests.Contract>));
45+
46+
#if IL2CPPMELON
47+
Assert.False(derivesFromManagedDelegate);
48+
#else
49+
Assert.True(derivesFromManagedDelegate);
50+
#endif
51+
}
52+
}

S1API/Entities/NPCCustomer.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
using S1API.Internal.Abstraction;
4343
using S1API.Internal.Utils;
4444
#if (IL2CPPMELON)
45+
using Il2CppInterop.Runtime;
4546
using Il2CppFishNet;
4647
using Il2CppFishNet.Managing;
4748
using Il2CppFishNet.Managing.Object;
@@ -782,20 +783,21 @@ private bool EnsureContractAssignedHook()
782783

783784
try
784785
{
785-
var evt = Utils.ReflectionUtils.TryGetFieldOrProperty(Component, "onContractAssigned");
786+
UnityEvent<S1Quests.Contract>? evt = Component.onContractAssigned;
786787
if (evt == null)
787788
return false;
788789

789-
var contractType = typeof(S1Quests.Contract);
790-
var unityActionType = typeof(UnityAction<>).MakeGenericType(contractType);
791-
var method = GetType().GetMethod(nameof(HandleContractAssigned), BindingFlags.NonPublic | BindingFlags.Instance);
792-
if (method == null)
793-
return false;
794-
795-
var del = Delegate.CreateDelegate(unityActionType, this, method);
796-
var addListener = evt.GetType().GetMethod("AddListener", new[] { unityActionType });
797-
addListener?.Invoke(evt, new object[] { del });
798-
_contractAssignedBridge = del;
790+
#if IL2CPPMELON
791+
_contractAssignedBridge =
792+
DelegateSupport.ConvertDelegate<UnityAction<S1Quests.Contract>>(
793+
new Action<S1Quests.Contract>(HandleContractAssigned))
794+
?? throw new InvalidOperationException(
795+
"Could not create the native contract-assigned listener.");
796+
#else
797+
_contractAssignedBridge =
798+
new UnityAction<S1Quests.Contract>(HandleContractAssigned);
799+
#endif
800+
evt.AddListener(_contractAssignedBridge);
799801
_contractAssignedUnityEvent = evt;
800802
return true;
801803
}
@@ -813,9 +815,7 @@ private void TryUnhookContractAssignedEvent()
813815

814816
try
815817
{
816-
var unityActionType = _contractAssignedBridge.GetType();
817-
var removeListener = _contractAssignedUnityEvent.GetType().GetMethod("RemoveListener", new[] { unityActionType });
818-
removeListener?.Invoke(_contractAssignedUnityEvent, new object[] { _contractAssignedBridge });
818+
_contractAssignedUnityEvent.RemoveListener(_contractAssignedBridge);
819819
}
820820
catch (Exception ex)
821821
{
@@ -829,11 +829,11 @@ private void TryUnhookContractAssignedEvent()
829829
}
830830

831831
private Action<float, int, int, int>? _onContractAssigned;
832-
private Delegate? _contractAssignedBridge;
833-
private object? _contractAssignedUnityEvent;
832+
private UnityAction<S1Quests.Contract>? _contractAssignedBridge;
833+
private UnityEvent<S1Quests.Contract>? _contractAssignedUnityEvent;
834834

835835
// Maps Contract to safe primitives for modders
836-
private void HandleContractAssigned(object contract)
836+
private void HandleContractAssigned(S1Quests.Contract contract)
837837
{
838838
try
839839
{

0 commit comments

Comments
 (0)