Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Ephemeral Coins Multiplayer Patch v1.0.1
TheEphemeralCoinsMultiplayerPatch.dll
Decompiled a day agousing 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 <ApplyAffordabilityFix>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<EphemeralCoinSaveEntry> _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<string, object> dict) { if (ShouldPreserveRestoredCoins) { ApplySavedEntries(_lastRestored, fromSnapshotReapply: true); } EndPreserveWindow(); List<EphemeralCoinSaveEntry> list = new List<EphemeralCoinSaveEntry>(); 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<EphemeralCoinSaveEntry> list = save.TryGetModdedData<List<EphemeralCoinSaveEntry>>("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<EphemeralCoinSaveEntry> 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<EphemeralCoinSaveEntry> CloneList(List<EphemeralCoinSaveEntry> source) { List<EphemeralCoinSaveEntry> list = new List<EphemeralCoinSaveEntry>(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<Material>(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<GameObject> EnumerateCoinPrefabs(PickupDef coinDef) { HashSet<int> seen = new HashSet<int>(); 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<GameObject>(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<MeshFilter>(); MeshRenderer component2 = ((Component)val).GetComponent<MeshRenderer>(); 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<GameObject>(path); if (!Object.op_Implicit((Object)(object)val)) { val = LoadAddressable<GameObject>(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<PurchaseInteraction>(); 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<FrogController>(); if ((Object)(object)component2 != (Object)null && _frogMaxPetsField != null) { _frogMaxPetsField.SetValue(component2, (int)GetConfigFloat("FrogPets", 1f)); } } } private static Mesh LoadCoinMesh(string[] keys) { Mesh val = LoadFirstAddressable<Mesh>(keys); if (Object.op_Implicit((Object)(object)val)) { return val; } GameObject val2 = LoadFirstAddressable<GameObject>(keys); if (!Object.op_Implicit((Object)(object)val2)) { return null; } MeshFilter component = val2.GetComponent<MeshFilter>(); if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.sharedMesh)) { return component.sharedMesh; } component = val2.GetComponentInChildren<MeshFilter>(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<Material>(assetName); } private static T LoadAddressable<T>(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<T>((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<T>(params string[] keys) where T : Object { for (int i = 0; i < keys.Length; i++) { T val = LoadAddressable<T>(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<MeshFilter>(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<PatchedCoinEntry> Entries = new List<PatchedCoinEntry>(); } [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<SyncPatchedCoinStorage>(); 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; } } }