using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using SharedUpgradesPlus.Configuration; using SharedUpgradesPlus.Models; using SharedUpgradesPlus.Services; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("Vippy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.4.2.0")] [assembly: AssemblyInformationalVersion("1.4.2+5c18fd22269d6d7c1e3e137162f6425d54660de2")] [assembly: AssemblyProduct("SharedUpgradesPlus")] [assembly: AssemblyTitle("SharedUpgradesPlus")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.4.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 SharedUpgradesPlus { internal static class BuildInfo { public const string Version = "1.4.2"; } [BepInPlugin("Vippy.SharedUpgradesPlus", "SharedUpgradesPlus", "1.4.2")] public class SharedUpgradesPlus : BaseUnityPlugin { internal static SharedUpgradesPlus Instance { get; private set; } internal static ManualLogSource Logger => Instance.BaseLogger; private ManualLogSource BaseLogger => ((BaseUnityPlugin)this).Logger; internal Harmony? Harmony { get; set; } private void Awake() { Instance = this; ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; PluginConfig.Init(((BaseUnityPlugin)this).Config); Patch(); Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded! LogLevel={PluginConfig.LoggingLevel.Value}"); } internal static void LogAlways(string msg) { Logger.LogInfo((object)msg); } internal static void LogInfo(string msg) { if (ConfigService.IsDebugLoggingEnabled()) { Logger.LogInfo((object)msg); } } internal static void LogVerbose(string msg) { if (ConfigService.IsVerboseLoggingEnabled()) { Logger.LogDebug((object)msg); } } internal void Patch() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0025: Expected O, but got Unknown if (Harmony == null) { Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); Harmony val2 = val; Harmony = val; } Harmony.PatchAll(); } } } namespace SharedUpgradesPlus.Services { public static class ConfigService { private static readonly Dictionary> upgradeToggles = new Dictionary>(); private static readonly Dictionary> limitSliders = new Dictionary>(); private static readonly HashSet _disabledByDefault = new HashSet { "playerUpgradeObjectValue", "playerUpgradeObjectDurability" }; public static bool IsSharedUpgradesEnabled() { return PluginConfig.EnableSharedUpgrades.Value; } public static bool IsModdedUpgradesEnabled() { return PluginConfig.EnableModdedUpgrades.Value; } public static bool IsLateJoinSyncEnabled() { return PluginConfig.EnableLateJoinSync.Value; } public static bool IsSharedUpgradeHealEnabled() { return PluginConfig.EnableSharedUpgradeHeal.Value; } public static bool IsShareNotificationEnabled() { return PluginConfig.EnableShareNotification.Value; } public static int SharedUpgradesChancePercentage() { return PluginConfig.SharedUpgradeChance.Value; } public static bool RollSharedUpgradesChance() { return Roll(PluginConfig.SharedUpgradeChance.Value); } private static bool Roll(int chance) { return Random.Range(0, 100) < chance; } public static bool IsDebugLoggingEnabled() { return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Debug; } public static bool IsVerboseLoggingEnabled() { return PluginConfig.LoggingLevel.Value >= VerbosityLevel.Verbose; } public static bool IsUpgradeEnabled(string upgradeKey) { if (upgradeToggles.TryGetValue(upgradeKey, out ConfigEntry value)) { return value.Value; } return true; } public static int UpgradeShareLimit(string upgradeKey) { if (limitSliders.TryGetValue(upgradeKey, out ConfigEntry value)) { return value.Value; } return 0; } public static void LoadModsIntoConfig() { if (PluginConfig.ConfigFile != null) { RegisterToggles(RegistryService.Instance.VanillaUpgrades, "Vanilla Upgrades"); RegisterToggles(RegistryService.Instance.ModdedUpgrades, "Modded Upgrades"); } } private static void RegisterToggles(IEnumerable upgrades, string section) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown foreach (Upgrade upgrade in upgrades) { if (!upgradeToggles.ContainsKey(upgrade.Name)) { upgradeToggles[upgrade.Name] = PluginConfig.ConfigFile.Bind(section, upgrade.CleanName, !_disabledByDefault.Contains(upgrade.Name), "Enable sharing for " + upgrade.CleanName); limitSliders[upgrade.Name] = PluginConfig.ConfigFile.Bind(section, upgrade.CleanName + " Share Limit", 0, new ConfigDescription("Others won't receive this upgrade past this level (0 = unlimited)", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); } } } } public static class DiscoveryService { public static DiscoveredUpgradesResult DiscoveredUpgrades(StatsManager statsManager) { HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); foreach (string item in statsManager.dictionaryOfDictionaries.Keys.Where((string key) => key.StartsWith("playerUpgrade"))) { if (AccessTools.Field(typeof(StatsManager), item) != null) { hashSet.Add(item); } else { hashSet2.Add(item); } } foreach (string moddedUpgradeKey in RepoLibInterop.GetModdedUpgradeKeys()) { if (!hashSet.Contains(moddedUpgradeKey)) { hashSet2.Add(moddedUpgradeKey); } } return new DiscoveredUpgradesResult(hashSet, hashSet2); } } public static class DistributionService { public static bool IsDistributing { get; private set; } public static void DistributeUpgrade(UpgradeContext context, string upgradeKey, int difference) { SharedUpgradesPlus.LogVerbose($"[Distribute] {context.PlayerName} bought {upgradeKey} (+{difference})"); int num = ConfigService.UpgradeShareLimit(upgradeKey); PhotonView component = ((Component)PunManager.instance).GetComponent(); if ((Object)(object)component == (Object)null) { SharedUpgradesPlus.Logger.LogWarning((object)"[Distribute] PhotonView not found on PunManager, can't distribute."); return; } bool flag = RegistryService.Instance.IsVanilla(upgradeKey); if (!flag && !ConfigService.IsModdedUpgradesEnabled()) { SharedUpgradesPlus.LogInfo("[Distribute] " + upgradeKey + " is modded and modded upgrades are off, skipping."); return; } if (!ConfigService.IsUpgradeEnabled(upgradeKey)) { SharedUpgradesPlus.LogInfo("[Distribute] " + upgradeKey + " is disabled in config, skipping."); return; } string text = (flag ? new Upgrade(upgradeKey).CleanName : null); List list = SemiFunc.PlayerGetAll(); int num2 = ConfigService.SharedUpgradesChancePercentage(); SharedUpgradesPlus.LogVerbose($"[Distribute] {upgradeKey} (+{difference}): {list.Count} player(s), limit={num}, chance={num2}%"); IsDistributing = true; int num3 = 0; int num4 = 0; try { foreach (PlayerAvatar item in list) { if ((Object)(object)item == (Object)null || (Object)(object)item.photonView == (Object)null || item.photonView.ViewID == context.ViewID) { continue; } string steamID = item.steamID; if (string.IsNullOrEmpty(steamID)) { continue; } int value = 0; if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(upgradeKey, out var value2)) { value2.TryGetValue(steamID, out value); } SharedUpgradesPlus.LogVerbose($"[Distribute] {item.playerName}: level={value}, limit={num}"); if (num > 0 && num <= value) { SharedUpgradesPlus.LogInfo($"[Distribute] {item.playerName} hit share limit ({num}), skipping."); num4++; continue; } if (!ConfigService.RollSharedUpgradesChance()) { SharedUpgradesPlus.LogInfo($"[Distribute] {item.playerName} roll failed ({num2}%), skipping."); num4++; continue; } int num5 = value + difference; SharedUpgradesPlus.LogAlways($"[Distribute] {item.playerName}: {value} -> {num5} (+{difference})"); if (flag) { component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, text, difference }); } else { component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { upgradeKey, steamID, num5 }); } num3++; } } catch (Exception ex) { SharedUpgradesPlus.Logger.LogError((object)("[Distribute] exception distributing " + upgradeKey + " for " + context.PlayerName + ": " + ex.Message)); } finally { IsDistributing = false; } SharedUpgradesPlus.LogVerbose($"[Distribute] done {upgradeKey}: sent={num3}, skipped={num4}"); HealBuyer(context, upgradeKey, difference); } private static void HealBuyer(UpgradeContext context, string upgradeKey, int difference) { if (upgradeKey != "playerUpgradeHealth" || !ConfigService.IsSharedUpgradeHealEnabled()) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(context.SteamID); if (!((Object)(object)val == (Object)null)) { int num = val.playerHealth.maxHealth + 20 * difference - val.playerHealth.health; SharedUpgradesPlus.LogVerbose($"[Distribute] healing {context.PlayerName}: max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}"); if (num > 0) { val.playerHealth.HealOther(num, false); } } } } public class NetworkCallbackService : MonoBehaviourPunCallbacks { [CompilerGenerated] private sealed class d__9 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerAvatar avatar; public string steamID; public Dictionary teamSnapshot; public NetworkCallbackService <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; NetworkCallbackService networkCallbackService = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = SyncService.ApplyTeamSnapshot(avatar, steamID, teamSnapshot); <>1__state = 1; return true; case 1: <>1__state = -1; networkCallbackService._pendingSync.Remove(avatar.photonView.Owner); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly HashSet _pendingSync = new HashSet(); public static NetworkCallbackService? Instance { get; private set; } public override void OnJoinedRoom() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0038: Expected O, but got Unknown SharedUpgradesPlus.LogVerbose($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient})"); try { if (PhotonNetwork.IsMasterClient) { Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"su__v1", (object)"1.4.2"); Hashtable val2 = val; PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); SharedUpgradesPlus.LogVerbose("Set room property: su__v1=1.4.2"); } } catch (Exception ex) { SharedUpgradesPlus.Logger.LogError((object)("Couldn't set room properties: " + ex.Message)); } } public override void OnPlayerEnteredRoom(Player newPlayer) { SharedUpgradesPlus.LogVerbose($"OnPlayerEnteredRoom: {newPlayer.NickName} (isMaster={SemiFunc.IsMasterClientOrSingleplayer()} lateJoin={ConfigService.IsLateJoinSyncEnabled()})"); if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer()) { _pendingSync.Add(newPlayer); SharedUpgradesPlus.LogAlways($"Deferred sync: {newPlayer.NickName} joined, queued. ({_pendingSync.Count} pending)"); } } public override void OnPlayerLeftRoom(Player otherPlayer) { bool flag = _pendingSync.Remove(otherPlayer); SharedUpgradesPlus.LogVerbose($"OnPlayerLeftRoom: {otherPlayer.NickName} (was pending: {flag}, pending count: {_pendingSync.Count})"); } public static bool IsPlayerPendingSync(Player player) { if ((Object)(object)Instance != (Object)null) { return Instance._pendingSync.Contains(player); } return false; } [IteratorStateMachine(typeof(d__9))] public IEnumerator LateSyncPlayer(PlayerAvatar avatar, string steamID, Dictionary teamSnapshot) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(0) { <>4__this = this, avatar = avatar, steamID = steamID, teamSnapshot = teamSnapshot }; } private void Awake() { Instance = this; CatchUpExistingPlayers(); } private void CatchUpExistingPlayers() { if (ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled() && SemiFunc.IsMasterClientOrSingleplayer()) { Player[] playerListOthers = PhotonNetwork.PlayerListOthers; foreach (Player val in playerListOthers) { _pendingSync.Add(val); SharedUpgradesPlus.LogAlways($"Catch-up sync: {val.NickName} was already in room, queued. ({_pendingSync.Count} pending)"); } } } } public sealed class RegistryService { private readonly HashSet vanillaUpgrades; private readonly HashSet moddedUpgrades; private static readonly RegistryService instance = new RegistryService(); public IReadOnlyCollection VanillaUpgrades => vanillaUpgrades; public IReadOnlyCollection ModdedUpgrades => moddedUpgrades; public static RegistryService Instance => instance; private RegistryService() { vanillaUpgrades = new HashSet(); moddedUpgrades = new HashSet(); } public void RegisterAll(DiscoveredUpgradesResult result) { vanillaUpgrades.UnionWith(result.Vanilla.Select(MakeUpgradeFromKey)); moddedUpgrades.UnionWith(result.Modded.Select(MakeUpgradeFromKey)); SharedUpgradesPlus.Logger.LogInfo((object)$"Discovered {vanillaUpgrades.Count} vanilla and {moddedUpgrades.Count} modded upgrade(s)."); if (result.Vanilla.Count > 0) { SharedUpgradesPlus.LogVerbose("Vanilla: " + string.Join(", ", result.Vanilla)); } if (result.Modded.Count > 0) { SharedUpgradesPlus.LogVerbose("Modded: " + string.Join(", ", result.Modded)); } } public void Clear() { SharedUpgradesPlus.LogVerbose($"Registry cleared ({vanillaUpgrades.Count} vanilla, {moddedUpgrades.Count} modded)."); vanillaUpgrades.Clear(); moddedUpgrades.Clear(); } public bool IsVanilla(string key) { string key2 = key; return vanillaUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2)); } public bool IsRegistered(string key) { string key2 = key; if (!IsVanilla(key2)) { return moddedUpgrades.Any((Upgrade upgrade) => upgrade.Name.Equals(key2)); } return true; } private Upgrade MakeUpgradeFromKey(string key) { return new Upgrade(key); } } internal static class RepoLibInterop { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static bool _resolved; private static PropertyInfo? _playerUpgradesProp; private static PropertyInfo? _upgradeIdProp; public static HashSet GetModdedUpgradeKeys() { HashSet hashSet = new HashSet(); if (!ResolveReflection()) { return hashSet; } try { if (!(_playerUpgradesProp.GetValue(null) is IEnumerable enumerable)) { return hashSet; } foreach (object item in enumerable) { if (item != null && _upgradeIdProp.GetValue(item) is string text && !string.IsNullOrEmpty(text)) { hashSet.Add("playerUpgrade" + text); } } } catch (Exception ex) { SharedUpgradesPlus.LogVerbose("[RepoLibInterop] reflection failed: " + ex.Message); } return hashSet; } private static bool ResolveReflection() { if (_resolved) { if (_playerUpgradesProp != null) { return _upgradeIdProp != null; } return false; } _resolved = true; Type type = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); if (type == null) { return false; } _playerUpgradesProp = type.GetProperty("PlayerUpgrades", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (_playerUpgradesProp == null) { return false; } Type type2 = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); if (type2 == null) { return false; } _upgradeIdProp = type2.GetProperty("UpgradeId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return _upgradeIdProp != null; } } public static class SnapshotService { public static Dictionary SnapshotPlayerStats(string steamID) { string steamID2 = steamID; if (string.IsNullOrEmpty(steamID2) || StatsManager.instance == null) { return new Dictionary(); } Dictionary dictionary = StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair> kvp) => RegistryService.Instance.IsRegistered(kvp.Key)).ToDictionary((KeyValuePair> kvp) => kvp.Key, (KeyValuePair> kvp) => kvp.Value.GetValueOrDefault(steamID2, 0)); SharedUpgradesPlus.LogVerbose($"[Snapshot] Player snapshot for {steamID2}: {dictionary.Count} upgrade(s)."); return dictionary; } public static Dictionary SnapshotTeamMaxLevels(string? excludeSteamID = null) { string excludeSteamID2 = excludeSteamID; Dictionary dictionary = new Dictionary(); if (StatsManager.instance == null) { return dictionary; } foreach (KeyValuePair> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair> k) => RegistryService.Instance.IsRegistered(k.Key))) { IEnumerable enumerable; if (!string.IsNullOrEmpty(excludeSteamID2)) { enumerable = from p in item.Value where p.Key != excludeSteamID2 select p.Value; } else { IEnumerable values = item.Value.Values; enumerable = values; } IEnumerable source = enumerable; dictionary[item.Key] = source.DefaultIfEmpty(0).Max(); } SharedUpgradesPlus.LogVerbose(string.Format("[Snapshot] Team snapshot (exclude={0}): {1} upgrade(s).", excludeSteamID2 ?? "none", dictionary.Count)); return dictionary; } } public static class SyncService { [CompilerGenerated] private sealed class d__0 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerAvatar player; public Dictionary teamSnapshot; public string steamID; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__0(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { if (<>1__state != 0) { return false; } <>1__state = -1; if ((Object)(object)StatsManager.instance == (Object)null || (Object)(object)PunManager.instance == (Object)null) { return false; } PhotonView component = ((Component)PunManager.instance).GetComponent(); if ((Object)(object)component == (Object)null) { SharedUpgradesPlus.Logger.LogWarning((object)"[LateJoin] PhotonView not found on PunManager, skipping sync."); return false; } string playerName = player.playerName; int num = ConfigService.SharedUpgradesChancePercentage(); SharedUpgradesPlus.LogAlways($"[LateJoin] syncing {playerName}: {teamSnapshot.Count} upgrade(s), chance={num}%"); int num2 = 0; int num3 = 0; foreach (KeyValuePair item in teamSnapshot) { int num4 = ConfigService.UpgradeShareLimit(item.Key); bool flag = RegistryService.Instance.IsVanilla(item.Key); SharedUpgradesPlus.LogVerbose($"[LateJoin] {item.Key}: teamMax={item.Value}, isVanilla={flag}, limit={num4}"); if (!flag && !ConfigService.IsModdedUpgradesEnabled()) { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": skipped (modded upgrades disabled)."); num3++; continue; } if (!ConfigService.IsUpgradeEnabled(item.Key)) { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": skipped (disabled in config)."); num3++; continue; } Dictionary value; int num5 = (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(item.Key, out value) ? value.GetValueOrDefault(steamID, 0) : 0); if (num4 > 0 && num4 <= num5) { SharedUpgradesPlus.LogInfo($"[LateJoin] {item.Key}: {playerName} hit share limit ({num4}), skipping."); num3++; continue; } int value2 = item.Value; int num6 = value2 - num5; if (num4 > 0) { num6 = Math.Min(num6, num4 - num5); } SharedUpgradesPlus.LogVerbose($"[LateJoin] {item.Key}: level={num5}, teamMax={item.Value}, diff={num6} (pre-roll)"); num6 = SimulateRealisticLevelling(num6); value2 = num5 + num6; if (num6 <= 0) { SharedUpgradesPlus.LogInfo("[LateJoin] " + item.Key + ": rolled 0 after chance simulation, skipping."); num3++; continue; } if (flag) { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": sending TesterUpgradeCommandRPC to " + playerName); component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamID, new Upgrade(item.Key).CleanName, num6 }); } else { SharedUpgradesPlus.LogVerbose("[LateJoin] " + item.Key + ": sending UpdateStatRPC to " + playerName); component.RPC("UpdateStatRPC", (RpcTarget)0, new object[3] { item.Key, steamID, value2 }); } SharedUpgradesPlus.LogVerbose($"[LateJoin] sent {item.Key} (+{num6}) to {playerName}."); num2++; } SharedUpgradesPlus.LogAlways($"[LateJoin] done {playerName}: sent={num2}, skipped={num3}"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [IteratorStateMachine(typeof(d__0))] public static IEnumerator ApplyTeamSnapshot(PlayerAvatar player, string steamID, Dictionary teamSnapshot) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__0(0) { player = player, steamID = steamID, teamSnapshot = teamSnapshot }; } private static int SimulateRealisticLevelling(int value) { int num = ConfigService.SharedUpgradesChancePercentage(); if (num >= 100 || value <= 0) { return value; } int num2 = 0; for (int i = 0; i < value; i++) { if (ConfigService.RollSharedUpgradesChance()) { num2++; } } SharedUpgradesPlus.LogVerbose($"[LateJoin] roll simulation: input={value}, chance={num}%, result={num2}"); return num2; } } internal class WatermarkService : MonoBehaviour { [CompilerGenerated] private sealed class d__10 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public WatermarkService <>4__this; private float 5__2; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__10(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown int num = <>1__state; WatermarkService watermarkService = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; 5__2 += 1f; try { if (PhotonNetwork.InRoom) { if (string.IsNullOrEmpty(OwnerID)) { goto IL_0171; } PlayerAvatar val = null; foreach (PlayerAvatar item in SemiFunc.PlayerGetAll()) { if ((Object)(object)item?.photonView != (Object)null && item.photonView.IsMine) { val = item; break; } } if (!((Object)(object)val == (Object)null)) { if (val.steamID != OwnerID) { goto IL_0171; } bool isMasterClient = PhotonNetwork.IsMasterClient; bool flag = ((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).ContainsKey((object)"su__v1"); if (isMasterClient || flag) { if (((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"su__v1", out object value)) { Version = (value as string) ?? "UNKNOWN"; } watermarkService.show = !isMasterClient && flag; goto IL_0171; } } } } catch { } } else { <>1__state = -1; watermarkService.polling = true; 5__2 = 0f; } if (5__2 < 10f) { <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } goto IL_0171; IL_0171: watermarkService.polling = false; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal const string RoomKey = "su__v1"; private static string Version = "UNKNOWN"; private static readonly string? OwnerID = LoadOwnerID(); private bool show; private bool polling; private Object? lastLevel; private string? lastRoom; private GUIStyle? style; private static string? LoadOwnerID() { try { string path = Path.Combine(Paths.ConfigPath, "SharedUpgradesPlus.owner"); if (!File.Exists(path)) { return null; } return File.ReadAllText(path).Trim(); } catch { return null; } } private void Update() { if ((Object)(object)RunManager.instance == (Object)null) { return; } Level levelCurrent = RunManager.instance.levelCurrent; Room currentRoom = PhotonNetwork.CurrentRoom; string text = ((currentRoom != null) ? currentRoom.Name : null); if ((Object)(object)levelCurrent == lastLevel && text == lastRoom) { return; } lastLevel = (Object?)(object)levelCurrent; lastRoom = text; if (text == null) { show = false; } if ((Object)(object)levelCurrent == (Object)(object)RunManager.instance.levelLobbyMenu) { show = false; if (!polling) { ((MonoBehaviour)this).StartCoroutine(Poll()); } } } [IteratorStateMachine(typeof(d__10))] private IEnumerator Poll() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__10(0) { <>4__this = this }; } private void OnGUI() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (!show) { return; } try { if (style == null) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 24 }; val.normal.textColor = new Color(1f, 1f, 1f, 0.15f); style = val; } GUI.Label(new Rect(6f, (float)(Screen.height - 72), 160f, 56f), "SUP: " + Version, style); } catch { } } } } namespace SharedUpgradesPlus.Patches { [HarmonyPatch(typeof(PunManager), "UpdateStatRPC")] internal class ModdedUpgradesPatch { [HarmonyPrefix] public static void Prefix(string dictionaryName, string key, out int __state) { __state = 0; if (!((Object)(object)StatsManager.instance == (Object)null) && StatsManager.instance.dictionaryOfDictionaries.TryGetValue(dictionaryName, out var value)) { value.TryGetValue(key, out __state); } } [HarmonyPostfix] public static void Postfix(string dictionaryName, string key, int value, int __state) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) if (!ConfigService.IsSharedUpgradesEnabled() || !ConfigService.IsModdedUpgradesEnabled() || !RegistryService.Instance.IsRegistered(dictionaryName) || RegistryService.Instance.IsVanilla(dictionaryName) || !ConfigService.IsUpgradeEnabled(dictionaryName)) { return; } PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(key); int num = value - __state; SharedUpgradesPlus.LogVerbose(string.Format("[ModdedPatch] {0} ({1}): {2} -> {3} (+{4}), player={5}, distributing={6}", dictionaryName, key, __state, value, num, val?.playerName ?? "not found", DistributionService.IsDistributing)); if ((Object)(object)val != (Object)null && ConfigService.IsShareNotificationEnabled()) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] running effects for " + val.playerName); if (val.isLocal) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] local player, triggering StatsUI + CameraGlitch."); StatsUI.instance.Fetch(); StatsUI.instance.ShowStats(); CameraGlitch.Instance.PlayUpgrade(); } else { SharedUpgradesPlus.LogVerbose("[ModdedPatch] remote player, shaking camera."); GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f); } if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] applying upgrade material effect to " + val.playerName + "."); val.playerHealth.MaterialEffectOverride((Effect)0); } } if (num > 0 && SemiFunc.IsMasterClientOrSingleplayer()) { if (DistributionService.IsDistributing) { SharedUpgradesPlus.LogVerbose("[ModdedPatch] already distributing, skipping " + dictionaryName + "."); } else if ((Object)(object)val == (Object)null || (Object)(object)val.photonView == (Object)null) { SharedUpgradesPlus.Logger.LogWarning((object)("[ModdedPatch] no PlayerAvatar found for " + key + ", can't distribute " + dictionaryName + ".")); } else { string playerName = val.playerName; SharedUpgradesPlus.LogAlways($"[ModdedPatch] {playerName} bought {dictionaryName}: {__state} -> {value} (+{num}), distributing..."); DistributionService.DistributeUpgrade(new UpgradeContext(key, val.photonView.ViewID, playerName, new Dictionary()), dictionaryName, num); } } } } [HarmonyPatch(typeof(PlayerTumble), "SetupDone")] internal class PlayerTumbleSetupDonePatch { [HarmonyPostfix] public static void Postfix(PlayerTumble __instance) { if (SemiFunc.IsMasterClientOrSingleplayer() && !string.IsNullOrEmpty(__instance.playerAvatar.steamID) && NetworkCallbackService.IsPlayerPendingSync(__instance.playerAvatar.photonView.Owner) && ConfigService.IsLateJoinSyncEnabled() && ConfigService.IsSharedUpgradesEnabled()) { SharedUpgradesPlus.LogAlways("[LateJoin] SetupDone fired for " + __instance.playerAvatar.steamID + ", triggering sync."); Dictionary teamSnapshot = SnapshotService.SnapshotTeamMaxLevels(__instance.playerAvatar.steamID); if ((Object)(object)NetworkCallbackService.Instance == (Object)null) { SharedUpgradesPlus.Logger.LogError((object)"NetworkCallbackService instance is null. Cannot sync player stats."); } else { ((MonoBehaviour)NetworkCallbackService.Instance).StartCoroutine(NetworkCallbackService.Instance.LateSyncPlayer(__instance.playerAvatar, __instance.playerAvatar.steamID, teamSnapshot)); } } } } [HarmonyPatch(typeof(PunManager), "TesterUpgradeCommandRPC")] internal class PlayerUpgradeEffectPatch { [HarmonyPostfix] public static void Postfix(string _steamID, string upgradeName, int upgradeNum, PhotonMessageInfo _info) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) bool flag = upgradeName == "Health"; PlayerAvatar val = SemiFunc.PlayerAvatarGetFromSteamID(_steamID); if ((Object)(object)val == (Object)null) { SharedUpgradesPlus.Logger.LogError((object)("[Effects] TesterUpgradeCommandRPC fired for " + _steamID + " but no PlayerAvatar found, skipping effects.")); return; } SharedUpgradesPlus.LogVerbose($"[Effects] {val.playerName} got {upgradeName} x{upgradeNum} (local={val.isLocal})"); if (ConfigService.IsShareNotificationEnabled()) { if (val.isLocal) { SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " is local, StatsUI + CameraGlitch."); StatsUI.instance.Fetch(); StatsUI.instance.ShowStats(); CameraGlitch.Instance.PlayUpgrade(); } else { SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " is remote, camera shake."); GameDirector.instance.CameraImpact.ShakeDistance(5f, 1f, 6f, ((Component)val).transform.position, 0.2f); } if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient) { SharedUpgradesPlus.LogVerbose("[Effects] applying upgrade material effect to " + val.playerName + "."); val.playerHealth.MaterialEffectOverride((Effect)0); } } SharedUpgradesPlus.LogVerbose("[Effects] " + val.playerName + " effects done."); if (flag && SemiFunc.IsMasterClientOrSingleplayer() && ConfigService.IsSharedUpgradeHealEnabled()) { int num = val.playerHealth.maxHealth + 20 * upgradeNum - val.playerHealth.health; SharedUpgradesPlus.LogVerbose($"[Effects] healing {val.playerName}: max={val.playerHealth.maxHealth}, current={val.playerHealth.health}, healing={num}"); if (num > 0) { val.playerHealth.HealOther(num, false); } } } } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] internal class REPOLibSyncPatch { private static readonly Type _upgradeType = AccessTools.TypeByName("REPOLib.Modules.Upgrades"); private static readonly Type _playerUpgradeType = AccessTools.TypeByName("REPOLib.Modules.PlayerUpgrade"); private static readonly FieldInfo? _playerDictionaryField = ((_playerUpgradeType != null) ? AccessTools.Field(_playerUpgradeType, "PlayerDictionary") : null); private static readonly FieldInfo? _playerUpgradesField = ((_upgradeType != null) ? AccessTools.Field(_upgradeType, "_playerUpgrades") : null); [HarmonyPostfix] [HarmonyPriority(0)] public static void Postfix() { if (_playerDictionaryField == null || _playerUpgradesField == null || (Object)(object)StatsManager.instance == (Object)null || !(_playerUpgradesField.GetValue(null) is IDictionary dictionary)) { return; } SharedUpgradesPlus.LogVerbose($"Syncing {dictionary.Count} REPOLib upgrade(s) to StatsManager."); int num = 0; foreach (DictionaryEntry item in dictionary) { if (item.Value != null) { string text = $"playerUpgrade{item.Key}"; if (StatsManager.instance.dictionaryOfDictionaries.TryGetValue(text, out var value)) { _playerDictionaryField.SetValue(item.Value, value); SharedUpgradesPlus.LogInfo("Synced PlayerDictionary for " + text + "."); num++; } } } SharedUpgradesPlus.LogVerbose($"REPOLib sync done: {num}/{dictionary.Count} upgrade(s)."); } } [HarmonyPatch(typeof(ItemUpgrade), "PlayerUpgrade")] internal class SharedUpgradesPatch { private static PlayerAvatar? GetUpgradePlayer(ItemUpgrade instance, out int viewID) { viewID = 0; ItemToggle itemToggle = instance.itemToggle; if (itemToggle == null || !itemToggle.toggleState) { return null; } viewID = instance.itemToggle.playerTogglePhotonID; return SemiFunc.PlayerAvatarGetFromPhotonID(viewID); } [HarmonyPrefix] public static void Prefix(ItemUpgrade __instance, out UpgradeContext? __state) { __state = null; if (!ConfigService.IsSharedUpgradesEnabled() || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } int viewID; PlayerAvatar upgradePlayer = GetUpgradePlayer(__instance, out viewID); if (upgradePlayer == null) { SharedUpgradesPlus.LogVerbose("[Purchase] upgrade interaction fired but couldn't find a player, skipping."); return; } string steamID = upgradePlayer.steamID; if (!string.IsNullOrEmpty(steamID)) { string text = null; ItemAttributes itemAttributes = __instance.itemAttributes; if (itemAttributes != null && (Object)(object)itemAttributes.item != (Object)null) { text = ((Object)itemAttributes.item).name; } SharedUpgradesPlus.LogVerbose("[Purchase] " + upgradePlayer.playerName + " is buying '" + text + "'"); __state = new UpgradeContext(steamID, playerName: upgradePlayer.playerName, viewID: viewID, levelsBefore: SnapshotService.SnapshotPlayerStats(steamID), itemName: text); } } [HarmonyPostfix] public static void Postfix(UpgradeContext? __state) { if (!SemiFunc.IsMasterClientOrSingleplayer() || __state == null) { return; } SharedUpgradesPlus.LogVerbose("[Purchase] checking what " + __state.PlayerName + " just bought (item='" + __state.ItemName + "')"); bool flag = false; foreach (KeyValuePair> item in StatsManager.instance.dictionaryOfDictionaries.Where((KeyValuePair> key) => RegistryService.Instance.IsRegistered(key.Key))) { item.Value.TryGetValue(__state.SteamID, out var value); __state.LevelsBefore.TryGetValue(item.Key, out var value2); SharedUpgradesPlus.LogVerbose($"[Purchase] {item.Key}: {value2} -> {value}"); if (value > value2) { int num = value - value2; flag = true; SharedUpgradesPlus.LogAlways($"[Purchase] {__state.PlayerName} bought {item.Key} (+{num}), distributing..."); DistributionService.DistributeUpgrade(__state, item.Key, num); } } SharedUpgradesPlus.LogVerbose($"[Purchase] vanilla scan done, distributed={flag}"); if (!flag && __state.ItemName != null && ConfigService.IsModdedUpgradesEnabled()) { SharedUpgradesPlus.LogVerbose("[Purchase] no vanilla upgrades changed, checking modded match for '" + __state.ItemName + "'"); string text = MatchItemNameToModdedUpgrade(__state.ItemName); if (text != null) { SharedUpgradesPlus.LogInfo("[Purchase] " + __state.PlayerName + " (" + __state.SteamID + ") bought modded " + text + " (+1), distributing..."); DistributionService.DistributeUpgrade(__state, text, 1); } else { SharedUpgradesPlus.LogVerbose("[Purchase] no match for '" + __state.ItemName + "', nothing to distribute."); } } } private static string? MatchItemNameToModdedUpgrade(string itemName) { string text = itemName.Replace(" ", ""); foreach (Upgrade moddedUpgrade in RegistryService.Instance.ModdedUpgrades) { string value = moddedUpgrade.CleanName.Replace(" ", ""); if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase)) { return moddedUpgrade.Name; } } return null; } } [HarmonyPatch(typeof(StatsManager), "Start")] internal class StatsManagerPatch { private static NetworkCallbackService? _callbackService; [HarmonyPostfix] public static void Postfix(StatsManager __instance) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) //IL_003e: Expected O, but got Unknown SharedUpgradesPlus.LogVerbose("StatsManager.Start: initial discovery."); RefreshRegistry(__instance); if ((Object)(object)_callbackService == (Object)null) { GameObject val = new GameObject("SharedUpgradesPlus_Services"); _callbackService = val.AddComponent(); val.AddComponent(); Object.DontDestroyOnLoad((Object)val); SharedUpgradesPlus.LogVerbose("Created NetworkCallbackService and WatermarkService."); } else { SharedUpgradesPlus.LogVerbose("NetworkCallbackService already exists, skipping."); } } internal static void RefreshRegistry(StatsManager statsManager) { DiscoveredUpgradesResult discoveredUpgradesResult = DiscoveryService.DiscoveredUpgrades(statsManager); SharedUpgradesPlus.LogVerbose($"Found {discoveredUpgradesResult.Vanilla.Count} vanilla and {discoveredUpgradesResult.Modded.Count} modded upgrade(s)."); RegistryService.Instance.Clear(); RegistryService.Instance.RegisterAll(discoveredUpgradesResult); ConfigService.LoadModsIntoConfig(); } } [HarmonyPatch(typeof(StatsManager), "RunStartStats")] [HarmonyAfter(new string[] { "REPOLib" })] internal class StatsManagerRunStartStatsPatch { [HarmonyPostfix] public static void Postfix(StatsManager __instance) { SharedUpgradesPlus.LogVerbose("StatsManager.RunStartStats: re-discovering after REPOLib."); StatsManagerPatch.RefreshRegistry(__instance); } } [HarmonyPatch(typeof(StatsManager), "LoadGame")] internal class StatsManagerLoadGamePatch { [HarmonyPostfix] public static void Postfix(StatsManager __instance) { SharedUpgradesPlus.LogVerbose("StatsManager.LoadGame: re-discovering after save load."); StatsManagerPatch.RefreshRegistry(__instance); } } } namespace SharedUpgradesPlus.Models { public sealed class DiscoveredUpgradesResult { public HashSet Vanilla { get; } public HashSet Modded { get; } public DiscoveredUpgradesResult(HashSet vanilla, HashSet modded) { Vanilla = vanilla; Modded = modded; base..ctor(); } } public sealed class Upgrade : IEquatable { public string Name { get; } public string CleanName { get { if (!Name.StartsWith("playerUpgrade")) { return Name; } string name = Name; int length = "playerUpgrade".Length; return name.Substring(length, name.Length - length); } } public Upgrade(string Name) { this.Name = Name; base..ctor(); } public bool Equals(Upgrade? other) { if (other != null) { return other.Name == Name; } return false; } public override bool Equals(object? obj) { return Equals(obj as Upgrade); } public override int GetHashCode() { return Name.GetHashCode(); } } public sealed class UpgradeContext { public string SteamID { get; } public int ViewID { get; } public string PlayerName { get; } public Dictionary LevelsBefore { get; } public string? ItemName { get; } public UpgradeContext(string steamID, int viewID, string playerName, Dictionary levelsBefore, string? itemName = null) { SteamID = steamID; ViewID = viewID; PlayerName = playerName; LevelsBefore = levelsBefore; ItemName = itemName; base..ctor(); } } } namespace SharedUpgradesPlus.Configuration { public enum VerbosityLevel { Off, Debug, Verbose } internal static class PluginConfig { public static ConfigEntry EnableSharedUpgrades; public static ConfigEntry SharedUpgradeChance; public static ConfigEntry EnableLateJoinSync; public static ConfigEntry EnableModdedUpgrades; public static ConfigEntry EnableSharedUpgradeHeal; public static ConfigEntry EnableShareNotification; public static ConfigEntry LoggingLevel; public static ConfigFile? ConfigFile; public static void Init(ConfigFile config) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown ConfigFile = config; EnableSharedUpgrades = config.Bind("General", "EnableSharedUpgrades", true, "Enable or disable all upgrade sharing"); SharedUpgradeChance = config.Bind("General", "SharedUpgradesChance", 100, new ConfigDescription("Chance per upgrade level to be shared with each player", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); EnableLateJoinSync = config.Bind("General", "LateJoinSync", true, "Sync upgrades to players who join mid-run"); EnableModdedUpgrades = config.Bind("General", "EnableModdedUpgrades", true, "Sync upgrades added by other mods"); EnableSharedUpgradeHeal = config.Bind("Effects", "EnableSharedUpgradeHeal", false, "Heal players to full HP when receiving a shared health upgrade"); EnableShareNotification = config.Bind("Effects", "EnableShareNotification", true, "Provide a visual effect when upgrades are shared with you"); LoggingLevel = config.Bind("General", "LogLevel", VerbosityLevel.Off, "Off: key events only (sync start/result, purchases). Debug: per-player distribution results and skips. Verbose: full trace of every step."); } } }