using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using On.RoR2; using ProperSave; using R2API.Networking; using R2API.Networking.Interfaces; using R2API.Utils; using RoR2; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TheEphemeralCoinsMultiplayerPatch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+34c48a2bac01ab92d5a7d4b2a16bb0164d553963")] [assembly: AssemblyProduct("TheEphemeralCoinsMultiplayerPatch")] [assembly: AssemblyTitle("TheEphemeralCoinsMultiplayerPatch")] [assembly: AssemblyVersion("1.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EphemeralCoinsMultiplayerPatch { internal static class CoinStoragePatch { private static Type _ephemeralType; private static Type _hooksType; private static object _ephemeralInstance; private static PropertyInfo _artifactEnabledProp; private static FieldInfo _coinCountsField; private static MethodInfo _delayedStartingCoins; public static void Apply(Harmony harmony) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Expected O, but got Unknown _ephemeralType = AccessTools.TypeByName("EphemeralCoins.EphemeralCoins") ?? throw new InvalidOperationException("Could not find EphemeralCoins.EphemeralCoins"); _hooksType = AccessTools.TypeByName("EphemeralCoins.Hooks") ?? throw new InvalidOperationException("Could not find EphemeralCoins.Hooks"); _ephemeralInstance = AccessTools.Field(_ephemeralType, "instance")?.GetValue(null) ?? throw new InvalidOperationException("EphemeralCoins.instance was null"); _artifactEnabledProp = AccessTools.Property(_ephemeralType, "artifactEnabled"); _coinCountsField = AccessTools.Field(_ephemeralType, "coinCounts"); _delayedStartingCoins = AccessTools.Method(_ephemeralType, "DelayedStartingLunarCoins", (Type[])null, (Type[])null); harmony.Patch((MethodBase)AccessTools.Method(_ephemeralType, "SetupCoinStorage", (Type[])null, (Type[])null), new HarmonyMethod(typeof(CoinStoragePatch), "SetupCoinStoragePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(_ephemeralType, "giveCoinsToUser", (Type[])null, (Type[])null), new HarmonyMethod(typeof(CoinStoragePatch), "GiveCoinsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(_ephemeralType, "takeCoinsFromUser", (Type[])null, (Type[])null), new HarmonyMethod(typeof(CoinStoragePatch), "TakeCoinsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(_ephemeralType, "getCoinsFromUser", (Type[])null, (Type[])null), new HarmonyMethod(typeof(CoinStoragePatch), "GetCoinsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(_hooksType, "Run_Start", (Type[])null, (Type[])null), new HarmonyMethod(typeof(CoinStoragePatch), "HooksRunStartPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool ArtifactEnabled() { if (_artifactEnabledProp != null) { return (bool)_artifactEnabledProp.GetValue(_ephemeralInstance); } return false; } public static void ApplySyncedPlayer(NetworkUserId userId, string name, uint count) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) PatchedCoinEntry patchedCoinEntry = FindEntry(userId, name) ?? EnsureEntry(userId, name, syncToClients: false); patchedCoinEntry.userId = userId; if (!string.IsNullOrEmpty(name)) { patchedCoinEntry.name = name; } patchedCoinEntry.ephemeralCoinCount = count; TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)("Sync coin entry: " + (patchedCoinEntry.name ?? "?") + " -> " + count)); } public static void SyncAllEntriesToClients() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active) { return; } foreach (PatchedCoinEntry entry in PatchCoinState.Entries) { if (entry != null) { NetMessageExtensions.Send((INetMessage)(object)new SyncPatchedCoinStorage(entry.userId, entry.name, entry.ephemeralCoinCount), (NetworkDestination)1); } } } private static PatchedCoinEntry FindEntry(NetworkUserId userId, string name) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) foreach (PatchedCoinEntry entry in PatchCoinState.Entries) { if (entry != null && ((NetworkUserId)(ref entry.userId)).Equals(userId)) { return entry; } } if (!string.IsNullOrEmpty(name)) { foreach (PatchedCoinEntry entry2 in PatchCoinState.Entries) { if (entry2 != null && string.Equals(entry2.name, name, StringComparison.Ordinal)) { return entry2; } } } return null; } private static PatchedCoinEntry FindEntry(NetworkUser user) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)user == (Object)null) { return null; } return FindEntry(user.id, user.userName); } private static PatchedCoinEntry EnsureEntry(NetworkUser user, bool syncToClients) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)user == (Object)null) { return null; } PatchedCoinEntry patchedCoinEntry = FindEntry(user); if (patchedCoinEntry != null) { patchedCoinEntry.userId = user.id; if (string.IsNullOrEmpty(patchedCoinEntry.name)) { patchedCoinEntry.name = user.userName; } return patchedCoinEntry; } return EnsureEntry(user.id, user.userName, syncToClients); } private static PatchedCoinEntry EnsureEntry(NetworkUserId userId, string name, bool syncToClients) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) PatchedCoinEntry patchedCoinEntry = FindEntry(userId, name); if (patchedCoinEntry != null) { patchedCoinEntry.userId = userId; if (string.IsNullOrEmpty(patchedCoinEntry.name)) { patchedCoinEntry.name = name; } return patchedCoinEntry; } PatchedCoinEntry patchedCoinEntry2 = new PatchedCoinEntry { userId = userId, name = name, ephemeralCoinCount = 0u }; PatchCoinState.Entries.Add(patchedCoinEntry2); if (syncToClients && NetworkServer.active) { NetMessageExtensions.Send((INetMessage)(object)new SyncPatchedCoinStorage(patchedCoinEntry2.userId, patchedCoinEntry2.name, patchedCoinEntry2.ephemeralCoinCount), (NetworkDestination)1); } return patchedCoinEntry2; } private static void SetupPatchedStorage(bool newRun) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (newRun) { PatchCoinState.Entries.Clear(); ProperSavePatch.ClearRestoredFlag(); } if (newRun && _coinCountsField != null && _coinCountsField.GetValue(_ephemeralInstance) is IList list) { list.Clear(); } foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList) { if ((Object)(object)readOnlyInstances == (Object)null) { continue; } PatchedCoinEntry patchedCoinEntry = FindEntry(readOnlyInstances); if (patchedCoinEntry != null) { patchedCoinEntry.userId = readOnlyInstances.id; if (string.IsNullOrEmpty(patchedCoinEntry.name)) { patchedCoinEntry.name = readOnlyInstances.userName; } } else { EnsureEntry(readOnlyInstances, syncToClients: true); TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)(readOnlyInstances.userName + " added to patched CoinStorage.")); } } SyncAllEntriesToClients(); } private static bool SetupCoinStoragePrefix(bool NewRun) { SetupPatchedStorage(NewRun); TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)"Patched SetupCoinStorage finished."); return false; } private static bool GiveCoinsPrefix(NetworkUser user, uint count) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (ProperSavePatch.IsLoading()) { return false; } PatchedCoinEntry patchedCoinEntry = EnsureEntry(user, syncToClients: false); if (patchedCoinEntry == null) { return false; } patchedCoinEntry.ephemeralCoinCount += count; if (NetworkServer.active) { NetMessageExtensions.Send((INetMessage)(object)new SyncPatchedCoinStorage(patchedCoinEntry.userId, patchedCoinEntry.name, patchedCoinEntry.ephemeralCoinCount), (NetworkDestination)1); } TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)("giveCoinsToUser: " + user.userName + " " + count + " -> " + patchedCoinEntry.ephemeralCoinCount)); return false; } private static bool TakeCoinsPrefix(NetworkUser user, uint count) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (ProperSavePatch.IsLoading()) { return false; } PatchedCoinEntry patchedCoinEntry = EnsureEntry(user, syncToClients: false); if (patchedCoinEntry == null) { return false; } if (count > patchedCoinEntry.ephemeralCoinCount) { patchedCoinEntry.ephemeralCoinCount = 0u; } else { patchedCoinEntry.ephemeralCoinCount -= count; } if (NetworkServer.active) { NetMessageExtensions.Send((INetMessage)(object)new SyncPatchedCoinStorage(patchedCoinEntry.userId, patchedCoinEntry.name, patchedCoinEntry.ephemeralCoinCount), (NetworkDestination)1); } TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)("takeCoinsFromUser: " + user.userName + " " + count + " -> " + patchedCoinEntry.ephemeralCoinCount)); return false; } private static bool GetCoinsPrefix(NetworkUser user, ref uint __result) { __result = 0u; if ((Object)(object)Run.instance == (Object)null || (Object)(object)user == (Object)null) { return false; } PatchedCoinEntry patchedCoinEntry = FindEntry(user); if (patchedCoinEntry != null) { __result = patchedCoinEntry.ephemeralCoinCount; } return false; } private static bool HooksRunStartPrefix(orig_Start orig, Run self) { orig.Invoke(self); if (ArtifactEnabled()) { SpawnPrefabPatch.SafePrefabSetup(set: true); } if (NetworkServer.active && ArtifactEnabled()) { bool num = !ProperSavePatch.IsLoading() && !ProperSavePatch.ShouldPreserveRestoredCoins; if (num) { ProperSavePatch.ClearRestoredFlag(); PatchCoinState.Entries.Clear(); } SetupPatchedStorage(num); if (num && _delayedStartingCoins != null) { object ephemeralInstance = _ephemeralInstance; MonoBehaviour val = (MonoBehaviour)((ephemeralInstance is MonoBehaviour) ? ephemeralInstance : null); if (val != null) { val.StartCoroutine((IEnumerator)_delayedStartingCoins.Invoke(_ephemeralInstance, null)); } } } return false; } } internal static class CostTypePatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static IsAffordableDelegate <>9__5_0; internal bool b__5_0(CostTypeDef costTypeDef, IsAffordableContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) NetworkUser val = Util.LookUpBodyNetworkUser(((Component)context.activator).gameObject); if (!Object.op_Implicit((Object)(object)val)) { return false; } if (ArtifactEnabled()) { return GetCoins(val) >= context.cost; } return val.lunarCoins >= context.cost; } } private static object _ephemeralInstance; private static PropertyInfo _artifactEnabledProp; public static void Apply() { Type type = AccessTools.TypeByName("EphemeralCoins.EphemeralCoins"); _ephemeralInstance = AccessTools.Field(type, "instance").GetValue(null); _artifactEnabledProp = AccessTools.Property(type, "artifactEnabled"); RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(ApplyAffordabilityFix)); } private static bool ArtifactEnabled() { if (_artifactEnabledProp != null) { return (bool)_artifactEnabledProp.GetValue(_ephemeralInstance); } return false; } private static uint GetCoins(NetworkUser user) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)user == (Object)null) { return 0u; } foreach (PatchedCoinEntry entry in PatchCoinState.Entries) { if (entry != null && ((NetworkUserId)(ref entry.userId)).Equals(user.id)) { return entry.ephemeralCoinCount; } } string userName = user.userName; if (!string.IsNullOrEmpty(userName)) { foreach (PatchedCoinEntry entry2 in PatchCoinState.Entries) { if (entry2 != null && string.Equals(entry2.name, userName, StringComparison.Ordinal)) { return entry2.ephemeralCoinCount; } } } return 0u; } private static void ApplyAffordabilityFix() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown try { CostTypeDef costTypeDef = CostTypeCatalog.GetCostTypeDef((CostTypeIndex)3); if (costTypeDef == null) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)"LunarCoin CostTypeDef missing; shop affordability patch skipped."); return; } object obj = <>c.<>9__5_0; if (obj == null) { IsAffordableDelegate val = delegate(CostTypeDef val3, IsAffordableContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) NetworkUser val2 = Util.LookUpBodyNetworkUser(((Component)context.activator).gameObject); if (!Object.op_Implicit((Object)(object)val2)) { return false; } return ArtifactEnabled() ? (GetCoins(val2) >= context.cost) : (val2.lunarCoins >= context.cost); }; <>c.<>9__5_0 = val; obj = (object)val; } costTypeDef.isAffordable = (IsAffordableDelegate)obj; TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)"Patched LunarCoin isAffordable delegate."); } catch (Exception ex) { TheEphemeralCoinsMultiplayerPatch.Log.LogError((object)("Failed to patch LunarCoin affordability (load will continue): " + ex)); } } } internal static class HooksInitPatch { public static void Apply(Harmony harmony) { } } internal static class NetworkUserIdAccess { private static readonly FieldInfo ValueField = AccessTools.Field(typeof(NetworkUserId), "value"); private static readonly FieldInfo StrValueField = AccessTools.Field(typeof(NetworkUserId), "strValue"); private static readonly FieldInfo SubIdField = AccessTools.Field(typeof(NetworkUserId), "subId"); private static readonly PropertyInfo ValueProp = AccessTools.Property(typeof(NetworkUserId), "value"); private static readonly PropertyInfo StrValueProp = AccessTools.Property(typeof(NetworkUserId), "strValue"); private static readonly PropertyInfo SubIdProp = AccessTools.Property(typeof(NetworkUserId), "subId"); public static ulong GetValue(NetworkUserId id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) object obj = id; if (ValueField != null) { return (ulong)ValueField.GetValue(obj); } if (ValueProp != null) { return (ulong)ValueProp.GetValue(obj); } return 0uL; } public static string GetStrValue(NetworkUserId id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) object obj = id; if (StrValueField != null) { return StrValueField.GetValue(obj) as string; } if (StrValueProp != null) { return StrValueProp.GetValue(obj) as string; } return null; } public static byte GetSubId(NetworkUserId id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) object obj = id; if (SubIdField != null) { return (byte)SubIdField.GetValue(obj); } if (SubIdProp != null) { return (byte)SubIdProp.GetValue(obj); } return 0; } } internal static class ProperSavePatch { [Serializable] public class EphemeralCoinSaveEntry { public ulong idValue; public string idStr; public byte idSub; public string name; public uint count; } public const string SaveDataKey = "EphemeralCoins.coinCounts"; private static List _lastRestored; private static int _preserveLoadId; private static int _loadId; private static int _delayedReapplyLoadId; private static bool _hooksInstalled; public static bool RestoredFromSave { get; private set; } internal static bool ShouldPreserveRestoredCoins { get { if (_preserveLoadId != 0 && _preserveLoadId == _loadId) { return _lastRestored != null; } return false; } } public static void Apply() { if (!Chainloader.PluginInfos.ContainsKey("com.KingEnderBrine.ProperSave")) { TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)"ProperSave not installed; coin save/load skipped."); } else { Setup(); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void Setup() { if (!_hooksInstalled) { _hooksInstalled = true; SaveFile.OnGatherSaveData += OnGatherSaveData; Loading.OnLoadingEnded += OnLoadingEnded; Stage.onStageStartGlobal += OnStageStartGlobal; Run.onRunDestroyGlobal += OnRunDestroyGlobal; TheEphemeralCoinsMultiplayerPatch.Log.LogInfo((object)"ProperSave compatibility enabled (ephemeral coin save/load)."); } } [MethodImpl(MethodImplOptions.NoInlining)] internal static bool IsLoading() { try { return Loading.IsLoading; } catch { return false; } } private static void OnRunDestroyGlobal(Run run) { ClearRestoredFlag(); PatchCoinState.Entries.Clear(); TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)"ProperSave: cleared coin state on run destroy."); } [MethodImpl(MethodImplOptions.NoInlining)] private static void OnGatherSaveData(Dictionary dict) { if (ShouldPreserveRestoredCoins) { ApplySavedEntries(_lastRestored, fromSnapshotReapply: true); } EndPreserveWindow(); List list = new List(); foreach (PatchedCoinEntry entry in PatchCoinState.Entries) { if (entry != null) { list.Add(CloneEntry(entry)); } } dict["EphemeralCoins.coinCounts"] = list; TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)("ProperSave: gathered " + list.Count + " ephemeral coin entries.")); } [MethodImpl(MethodImplOptions.NoInlining)] private static void OnLoadingEnded(SaveFile save) { if (save == null) { return; } try { List list = save.TryGetModdedData>("EphemeralCoins.coinCounts"); if (list == null) { TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)"ProperSave: no ephemeral coin data in save (starting at 0)."); return; } _loadId++; _preserveLoadId = _loadId; _delayedReapplyLoadId = _loadId; _lastRestored = CloneList(list); ApplySavedEntries(_lastRestored); StartPostLoadReapplyCoroutine(_loadId); } catch (Exception ex) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)("ProperSave: failed to restore ephemeral coins: " + ex)); } } private static void OnStageStartGlobal(Stage stage) { if (ShouldPreserveRestoredCoins) { ApplySavedEntries(_lastRestored, fromSnapshotReapply: true); } } private static void StartPostLoadReapplyCoroutine(int loadId) { MonoBehaviour instance = (MonoBehaviour)(object)TheEphemeralCoinsMultiplayerPatch.Instance; if (!((Object)(object)instance == (Object)null)) { instance.StartCoroutine(PostLoadReapplyCoroutine(loadId)); } } private static IEnumerator PostLoadReapplyCoroutine(int loadId) { yield return (object)new WaitForSeconds(1f); if (loadId == _delayedReapplyLoadId && _lastRestored != null) { ApplySavedEntries(_lastRestored, fromSnapshotReapply: true); TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)"ProperSave: re-applied ephemeral coin snapshot after load settle."); if (_preserveLoadId == 0) { _lastRestored = null; } } } private static void EndPreserveWindow() { _preserveLoadId = 0; } internal static void ApplySavedEntries(List saved, bool fromSnapshotReapply = false) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) PatchCoinState.Entries.Clear(); if (saved == null) { RestoredFromSave = false; return; } foreach (EphemeralCoinSaveEntry item2 in saved) { if (item2 != null) { NetworkUserId userId = ((!string.IsNullOrEmpty(item2.idStr)) ? NetworkUserId.FromIp(item2.idStr, item2.idSub) : NetworkUserId.FromId(item2.idValue, item2.idSub)); PatchedCoinEntry item = new PatchedCoinEntry { userId = userId, name = item2.name, ephemeralCoinCount = item2.count }; PatchCoinState.Entries.Add(item); } } RestoredFromSave = true; if (NetworkServer.active) { foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList) { if ((Object)(object)readOnlyInstances == (Object)null) { continue; } foreach (PatchedCoinEntry entry in PatchCoinState.Entries) { if (entry != null && (((NetworkUserId)(ref entry.userId)).Equals(readOnlyInstances.id) || (!string.IsNullOrEmpty(entry.name) && string.Equals(entry.name, readOnlyInstances.userName, StringComparison.Ordinal)))) { entry.userId = readOnlyInstances.id; entry.name = readOnlyInstances.userName; break; } } } CoinStoragePatch.SyncAllEntriesToClients(); } if (!fromSnapshotReapply) { TheEphemeralCoinsMultiplayerPatch.Log.LogInfo((object)("ProperSave: restored " + PatchCoinState.Entries.Count + " ephemeral coin entr" + ((PatchCoinState.Entries.Count == 1) ? "y" : "ies") + ".")); } } internal static void ClearRestoredFlag() { _loadId++; _preserveLoadId = 0; _delayedReapplyLoadId = 0; _lastRestored = null; RestoredFromSave = false; } private static EphemeralCoinSaveEntry CloneEntry(PatchedCoinEntry player) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) return new EphemeralCoinSaveEntry { idValue = NetworkUserIdAccess.GetValue(player.userId), idStr = (NetworkUserIdAccess.GetStrValue(player.userId) ?? ""), idSub = NetworkUserIdAccess.GetSubId(player.userId), name = player.name, count = player.ephemeralCoinCount }; } private static List CloneList(List source) { List list = new List(source.Count); foreach (EphemeralCoinSaveEntry item in source) { if (item != null) { list.Add(new EphemeralCoinSaveEntry { idValue = item.idValue, idStr = item.idStr, idSub = item.idSub, name = item.name, count = item.count }); } } return list; } } internal static class SpawnPrefabPatch { private static readonly string[] LunarInteractables = new string[5] { "RoR2/Base/Interactables/LunarRecycler/LunarRecycler.prefab", "RoR2/Base/Interactables/LunarChest/LunarChest.prefab", "RoR2/Base/Interactables/LunarShopTerminal/LunarShopTerminal.prefab", "RoR2/Base/Scenes/bazaar/SeerStation.prefab", "RoR2/Base/Scenes/moon/Natural/Prefabs/FrogInteractable.prefab" }; private static readonly string[] PickupLunarCoinKeys = new string[2] { "RoR2/Base/LunarCoin/PickupLunarCoin.prefab", "RoR2/Base/MiscPickups/LunarCoin/PickupLunarCoin.prefab" }; private static readonly string[] LunarCoinMeshKeys = new string[2] { "RoR2/Base/Common/VFX/mdlLunarCoin.fbx", "RoR2/Base/Common/VFX/Coins/mdlLunarCoin.fbx" }; private static readonly string[] LunarCoinWithHoleMeshKeys = new string[2] { "RoR2/Base/Common/VFX/mdlLunarCoinWithHole.fbx", "RoR2/Base/Common/VFX/Coins/mdlLunarCoinWithHole.fbx" }; private static readonly string[] LunarCoinPlaceholderMatKeys = new string[2] { "RoR2/Base/Common/VFX/matLunarCoinPlaceholder.mat", "RoR2/Base/Common/VFX/Coins/matLunarCoinPlaceholder.mat" }; private static Type _artifactManagerType; private static Type _assetsType; private static Type _bepConfigType; private static FieldInfo _frogMaxPetsField; public static void Apply(Harmony harmony) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown _artifactManagerType = AccessTools.TypeByName("EphemeralCoins.NewMoonArtifactManager") ?? throw new InvalidOperationException("Could not find NewMoonArtifactManager"); _assetsType = AccessTools.TypeByName("EphemeralCoins.Assets"); _bepConfigType = AccessTools.TypeByName("EphemeralCoins.BepConfig"); _frogMaxPetsField = AccessTools.Field(typeof(FrogController), "maxPets"); MethodInfo methodInfo = AccessTools.Method(_artifactManagerType, "PrefabSetup", new Type[1] { typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(_artifactManagerType, "Run_Start", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SpawnPrefabPatch), "ArtifactRunStartPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SpawnPrefabPatch), "PrefabSetupPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool ArtifactRunStartPrefix(orig_Start orig, Run self) { orig.Invoke(self); SafePrefabSetup(set: true); return false; } private static bool PrefabSetupPrefix(bool set) { SafePrefabSetup(set); return false; } internal static void SafePrefabSetup(bool set) { try { ApplyCoinVisuals(set); } catch (Exception ex) { TheEphemeralCoinsMultiplayerPatch.Log.LogError((object)("Patched PrefabSetup coin visuals failed (run init will continue): " + ex)); } try { ApplyInteractableCosts(); } catch (Exception ex2) { TheEphemeralCoinsMultiplayerPatch.Log.LogError((object)("Patched PrefabSetup interactable costs failed (run init will continue): " + ex2)); } } private static void ApplyCoinVisuals(bool set) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) TheEphemeralCoinsMultiplayerPatch.Log.LogInfo((object)("Patched PrefabSetup coin visuals " + set)); PickupDef lunarCoinPickupDef = GetLunarCoinPickupDef(); if (lunarCoinPickupDef == null) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)"Patched PrefabSetup: LunarCoin.Coin0 pickup def not ready yet; will retry on run start."); } else { lunarCoinPickupDef.nameToken = (set ? "Ephemeral Coin" : "PICKUP_LUNAR_COIN"); lunarCoinPickupDef.interactContextToken = (set ? "Pick up Ephemeral Coin" : "LUNAR_COIN_PICKUP_CONTEXT"); lunarCoinPickupDef.baseColor = Color32.op_Implicit(set ? new Color32((byte)96, (byte)254, byte.MaxValue, byte.MaxValue) : new Color32((byte)48, (byte)127, byte.MaxValue, byte.MaxValue)); lunarCoinPickupDef.darkColor = Color32.op_Implicit(set ? new Color32((byte)152, (byte)168, byte.MaxValue, byte.MaxValue) : new Color32((byte)76, (byte)84, (byte)144, byte.MaxValue)); } Mesh val = LoadCoinMesh(set ? LunarCoinMeshKeys : LunarCoinWithHoleMeshKeys); Material val2 = (set ? LoadBundleMaterial("matEphemeralCoin") : LoadFirstAddressable(LunarCoinPlaceholderMatKeys)); if (!Object.op_Implicit((Object)(object)val)) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)"Patched PrefabSetup: failed to load replacement lunar coin mesh."); } if (!Object.op_Implicit((Object)(object)val2)) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)"Patched PrefabSetup: failed to load lunar coin material."); } int num = 0; foreach (GameObject item in EnumerateCoinPrefabs(lunarCoinPickupDef)) { if (TryApplyMeshAndMaterial(item, val, val2)) { num++; } } if (num == 0) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)"Patched PrefabSetup: could not apply mesh/material to any lunar coin prefab."); } else { TheEphemeralCoinsMultiplayerPatch.Log.LogInfo((object)("Patched PrefabSetup: applied coin mesh/material to " + num + " prefab(s).")); } } private static PickupDef GetLunarCoinPickupDef() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) PickupIndex val = PickupCatalog.FindPickupIndex("LunarCoin.Coin0"); PickupDef pickupDef = PickupCatalog.GetPickupDef(val); if (pickupDef != null) { return pickupDef; } object? obj = AccessTools.PropertyGetter(typeof(PickupIndex), "pickupDef")?.Invoke(val, null); return (PickupDef)((obj is PickupDef) ? obj : null); } private static IEnumerable EnumerateCoinPrefabs(PickupDef coinDef) { HashSet seen = new HashSet(); GameObject val = ((coinDef != null) ? GetDisplayPrefab(coinDef) : null); if (Object.op_Implicit((Object)(object)val) && seen.Add(((Object)val).GetInstanceID())) { yield return val; } string[] pickupLunarCoinKeys = PickupLunarCoinKeys; for (int i = 0; i < pickupLunarCoinKeys.Length; i++) { GameObject val2 = LoadAddressable(pickupLunarCoinKeys[i]); if (Object.op_Implicit((Object)(object)val2) && seen.Add(((Object)val2).GetInstanceID())) { yield return val2; } } } private static GameObject GetDisplayPrefab(PickupDef coinDef) { if (coinDef == null) { return null; } FieldInfo fieldInfo = AccessTools.Field(typeof(PickupDef), "displayPrefab"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(coinDef); return (GameObject)((value is GameObject) ? value : null); } object? obj = AccessTools.Property(typeof(PickupDef), "displayPrefab")?.GetValue(coinDef); return (GameObject)((obj is GameObject) ? obj : null); } private static bool TryApplyMeshAndMaterial(GameObject theCoin, Mesh replacementMesh, Material replacementMat) { Transform val = FindCoinMesh(theCoin); if (!Object.op_Implicit((Object)(object)theCoin) || !Object.op_Implicit((Object)(object)val)) { return false; } bool result = false; MeshFilter component = ((Component)val).GetComponent(); MeshRenderer component2 = ((Component)val).GetComponent(); if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)replacementMesh)) { component.sharedMesh = replacementMesh; component.mesh = replacementMesh; result = true; } if (Object.op_Implicit((Object)(object)component2) && Object.op_Implicit((Object)(object)replacementMat)) { ((Renderer)component2).sharedMaterial = replacementMat; ((Renderer)component2).material = replacementMat; result = true; } return result; } private static void ApplyInteractableCosts() { string[] lunarInteractables = LunarInteractables; foreach (string text in lunarInteractables) { try { ApplyInteractableCost(text); } catch (Exception ex) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)("Patched PrefabSetup: interactable update failed for " + text + ": " + ex.Message)); } } } private static void ApplyInteractableCost(string path) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) GameObject val = LoadAddressable(path); if (!Object.op_Implicit((Object)(object)val)) { val = LoadAddressable(path.Replace("RoR2/Base/Interactables/", "RoR2/Base/").Replace("RoR2/Base/Scenes/bazaar/", "RoR2/Base/bazaar/").Replace("RoR2/Base/Scenes/moon/Natural/Prefabs/", "RoR2/Base/moon/")); } if (!Object.op_Implicit((Object)(object)val)) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)("Patched PrefabSetup: failed to load lunarInteractable " + path)); return; } int num = ResolveCost(((Object)val).name); PurchaseInteraction component = val.GetComponent(); if (!Object.op_Implicit((Object)(object)component)) { return; } component.cost = num; if (num == 0) { component.costType = (CostTypeIndex)0; } if (((Object)val).name == "FrogInteractable") { FrogController component2 = val.GetComponent(); if ((Object)(object)component2 != (Object)null && _frogMaxPetsField != null) { _frogMaxPetsField.SetValue(component2, (int)GetConfigFloat("FrogPets", 1f)); } } } private static Mesh LoadCoinMesh(string[] keys) { Mesh val = LoadFirstAddressable(keys); if (Object.op_Implicit((Object)(object)val)) { return val; } GameObject val2 = LoadFirstAddressable(keys); if (!Object.op_Implicit((Object)(object)val2)) { return null; } MeshFilter component = val2.GetComponent(); if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.sharedMesh)) { return component.sharedMesh; } component = val2.GetComponentInChildren(true); if (!Object.op_Implicit((Object)(object)component)) { return null; } return component.sharedMesh; } private static int ResolveCost(string prefabName) { return prefabName switch { "LunarRecycler" => (int)GetConfigFloat("RerollCost", 0f), "LunarChest" => (int)GetConfigFloat("PodCost", 0f), "LunarShopTerminal" => (int)GetConfigFloat("ShopCost", 1f), "SeerStation" => (int)GetConfigFloat("SeerCost", 1f), "FrogInteractable" => (int)GetConfigFloat("FrogCost", 0f), _ => 0, }; } private static float GetConfigFloat(string fieldName, float fallback) { if (_bepConfigType == null) { return fallback; } object obj = AccessTools.Field(_bepConfigType, fieldName)?.GetValue(null); if (obj == null) { return fallback; } PropertyInfo propertyInfo = AccessTools.Property(obj.GetType(), "Value"); if (propertyInfo == null) { return fallback; } object value = propertyInfo.GetValue(obj); if (value is float) { return (float)value; } if (value is int num) { return num; } return fallback; } private static Material LoadBundleMaterial(string assetName) { if (_assetsType == null) { return null; } object? obj = AccessTools.Field(_assetsType, "mainBundle")?.GetValue(null); AssetBundle val = (AssetBundle)((obj is AssetBundle) ? obj : null); if (!Object.op_Implicit((Object)(object)val)) { TheEphemeralCoinsMultiplayerPatch.Log.LogWarning((object)"Patched PrefabSetup: EphemeralCoins.Assets.mainBundle is null."); return null; } return val.LoadAsset(assetName); } private static T LoadAddressable(string key) where T : Object { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { return Addressables.LoadAssetAsync((object)key).WaitForCompletion(); } catch (Exception ex) { TheEphemeralCoinsMultiplayerPatch.Log.LogDebug((object)("Patched PrefabSetup: Addressables key failed (" + key + "): " + ex.Message)); return default(T); } } private static T LoadFirstAddressable(params string[] keys) where T : Object { for (int i = 0; i < keys.Length; i++) { T val = LoadAddressable(keys[i]); if (Object.op_Implicit((Object)(object)val)) { return val; } } return default(T); } private static Transform FindCoinMesh(GameObject coinPrefab) { if (!Object.op_Implicit((Object)(object)coinPrefab)) { return null; } Transform val = coinPrefab.transform.Find("Coin5Mesh"); if (Object.op_Implicit((Object)(object)val)) { return val; } val = coinPrefab.transform.Find("LunarCoinMesh"); if (Object.op_Implicit((Object)(object)val)) { return val; } MeshFilter componentInChildren = coinPrefab.GetComponentInChildren(true); if (!Object.op_Implicit((Object)(object)componentInChildren)) { return null; } return ((Component)componentInChildren).transform; } } public class SyncPatchedCoinStorage : INetMessage, ISerializableObject { public NetworkUserId userId; public string name; public uint ephemeralCoinCount; public SyncPatchedCoinStorage() { } public SyncPatchedCoinStorage(NetworkUserId userId, string name, uint ephemeralCoinCount) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) this.userId = userId; this.name = name; this.ephemeralCoinCount = ephemeralCoinCount; } public void Serialize(NetworkWriter writer) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) writer.WritePackedUInt64(NetworkUserIdAccess.GetValue(userId)); writer.Write(NetworkUserIdAccess.GetStrValue(userId) ?? ""); writer.Write(NetworkUserIdAccess.GetSubId(userId)); writer.Write(name ?? ""); writer.WritePackedUInt32(ephemeralCoinCount); } public void Deserialize(NetworkReader reader) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) ulong num = reader.ReadPackedUInt64(); string text = reader.ReadString(); byte b = reader.ReadByte(); userId = ((!string.IsNullOrEmpty(text)) ? NetworkUserId.FromIp(text, b) : NetworkUserId.FromId(num, b)); name = reader.ReadString(); ephemeralCoinCount = reader.ReadPackedUInt32(); } public void OnReceived() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active) { CoinStoragePatch.ApplySyncedPlayer(userId, name, ephemeralCoinCount); } } } internal class PatchedCoinEntry { public NetworkUserId userId; public string name; public uint ephemeralCoinCount; } internal static class PatchCoinState { internal static readonly List Entries = new List(); } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.willcola.EphemeralCoinsMultiplayerPatch", "Ephemeral_Coins_Multiplayer_Patch", "1.0.1")] public class TheEphemeralCoinsMultiplayerPatch : BaseUnityPlugin { public const string PluginGuid = "com.willcola.EphemeralCoinsMultiplayerPatch"; public const string PluginName = "Ephemeral_Coins_Multiplayer_Patch"; public const string PluginVersion = "1.0.1"; public const string EphemeralCoinsGuid = "com.Varna.EphemeralCoins"; public static readonly Version TargetedEphemeralCoinsVersion = new Version(2, 3, 7); internal static TheEphemeralCoinsMultiplayerPatch Instance { get; private set; } internal static Harmony Harmony { get; private set; } internal static bool PatchesApplied { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Harmony = new Harmony("com.willcola.EphemeralCoinsMultiplayerPatch"); if (!TryGetEphemeralCoinsVersion(out var version)) { ((BaseUnityPlugin)this).Logger.LogError((object)"Ephemeral Coins is required but was not found. Patch not applied."); return; } if (version > TargetedEphemeralCoinsVersion) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Ephemeral Coins {version} is newer than targeted {TargetedEphemeralCoinsVersion}. Patch idle."); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Ephemeral Coins {version} detected (<= {TargetedEphemeralCoinsVersion}). Applying multiplayer patch."); try { NetworkingAPI.RegisterMessageType(); CoinStoragePatch.Apply(Harmony); SpawnPrefabPatch.Apply(Harmony); HooksInitPatch.Apply(Harmony); CostTypePatch.Apply(); ProperSavePatch.Apply(); PatchesApplied = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Ephemeral Coins Multiplayer Patch applied."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to apply Ephemeral Coins Multiplayer Patch: " + ex)); } } private static bool TryGetEphemeralCoinsVersion(out Version version) { version = null; if (!Chainloader.PluginInfos.TryGetValue("com.Varna.EphemeralCoins", out var value) || ((value != null) ? value.Metadata : null) == null) { return false; } version = value.Metadata.Version; return version != null; } } }