diff --git a/plugins/CashShop/PassRewardPresentation.cs b/plugins/CashShop/PassRewardPresentation.cs new file mode 100644 index 0000000..0ad4c08 --- /dev/null +++ b/plugins/CashShop/PassRewardPresentation.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using BD2.GameNames; +using HarmonyLib; + +namespace Bd2CashShop; + +// Native pass callbacks save rewards but discard their presentation list. +// Capture that list after the native save and feed it to the native popup. +internal static class PassRewardPresentation +{ + private const BindingFlags All = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + [ThreadStatic] private static Capture current; + private static Batch batch; + + private sealed class Capture + { + internal Capture Previous; + internal readonly List Rewards = new List(); + } + private sealed class Batch + { + internal readonly List Rewards = new List(); + } + + internal static void Install(Harmony harmony) + { + var receiver = typeof(MissionPassPacket).GetNestedTypes(All) + .SelectMany(type => type.GetMethods(All | BindingFlags.DeclaredOnly)) + .Single(method => method.IsGameMethod("b__1") && method.ReturnType == typeof(bool) && + method.GetParameters().Select(parameter => parameter.ParameterType) + .SequenceEqual(new[] { typeof(byte[]), typeof(int), typeof(int) })); + harmony.Patch(receiver, + prefix: new HarmonyMethod(typeof(PassRewardPresentation), nameof(BeginReceive)), + postfix: new HarmonyMethod(typeof(PassRewardPresentation), nameof(EndReceive)), + finalizer: new HarmonyMethod(typeof(PassRewardPresentation), nameof(FinishReceive))); + var addRewards = typeof(DataManager).GetGameMethod("AddRewardInfoBundle", All, null, + new[] { typeof(Proto.Net.RewardDBInfoBundle), typeof(List).MakeByRefType(), typeof(bool) }, null); + if (addRewards == null) throw new MissingMethodException("DataManager.AddRewardInfoBundle"); + harmony.Patch(addRewards, postfix: new HarmonyMethod(typeof(PassRewardPresentation), nameof(CaptureRewards))); + var receiveAll = typeof(PassRootUI).GetGameMethod("ReceiveAllReward", All); + if (receiveAll == null) throw new MissingMethodException("PassRootUI.ReceiveAllReward"); + harmony.Patch(receiveAll, postfix: new HarmonyMethod(typeof(PassRewardPresentation), nameof(WrapReceiveAll))); + var popup = typeof(MissionPassPacket).GetGameMethod("ShowRewardPopup", All); + if (popup == null) throw new MissingMethodException("MissionPassPacket.ShowRewardPopup"); + harmony.Patch(popup, prefix: new HarmonyMethod(typeof(PassRewardPresentation), nameof(MergeBatch))); + } + + private static void BeginReceive(out Capture __state) + { + __state = new Capture { Previous = current }; + current = __state; + } + private static void CaptureRewards(List __1, bool __2) + { + if (current != null && __2 && __1 != null) current.Rewards.AddRange(__1); + } + private static void EndReceive(bool __result, int __2, Capture __state) + { + current = __state.Previous; + if (!__result || __2 != 0 || __state.Rewards.Count == 0) return; + if (batch != null) batch.Rewards.AddRange(__state.Rewards); + else MissionPassPacket.ShowRewardPopup(__state.Rewards, ""); + } + private static Exception FinishReceive(Exception __exception, Capture __state) + { + if (__state != null) current = __state.Previous; + return __exception; + } + private static void WrapReceiveAll(ref IEnumerator __result) => __result = ReceiveAll(__result); + private static IEnumerator ReceiveAll(IEnumerator original) + { + var owner = new Batch(); + var previous = batch; + batch = owner; + try + { + while (original.MoveNext()) yield return original.Current; + } + finally + { + try { (original as IDisposable)?.Dispose(); } + finally { batch = previous; } + } + } + private static void MergeBatch(ref List __0) + { + if (batch == null || batch.Rewards.Count == 0) return; + var combined = __0 == null ? new List() : new List(__0); + combined.AddRange(batch.Rewards); + batch.Rewards.Clear(); + __0 = combined; + } +} diff --git a/plugins/CashShop/Plugin.cs b/plugins/CashShop/Plugin.cs index c6ffb04..8dde385 100644 --- a/plugins/CashShop/Plugin.cs +++ b/plugins/CashShop/Plugin.cs @@ -86,6 +86,7 @@ public sealed class Plugin : BaseUnityPlugin Patch(typeof(GachaProductButtonElement), "Set", nameof(GachaPrice), true, 6); PatchGeneratedReceiver("b__0", nameof(LoginPassRewards)); PatchGeneratedReceiver("b__0", nameof(EventRewards)); + PassRewardPresentation.Install(harmony); MethodInfo attendance = typeof(EventPacket).GetGameMethod("RecvAttendanceResponse", All, null, new[] { typeof(IMessage), typeof(int), typeof(int) }, null); if (attendance == null) throw new MissingMethodException("EventPacket.RecvAttendanceResponse(IMessage,int,int)");