Decompiled source of NoSharedMoney v1.0.0

BepInEx/plugins/NoSharedMoney.dll

Decompiled 23 minutes ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
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: AssemblyCompany("NoSharedMoney")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("NoSharedMoney")]
[assembly: AssemblyTitle("NoSharedMoney")]
[assembly: AssemblyVersion("1.0.0.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 NoSharedMoney
{
	public static class ClientMoney
	{
		public static int Money { get; private set; }

		public static void Apply(int money)
		{
			int num = money - Money;
			Money = money;
			Refl.SetStaticMoney(money);
			Refl.RaiseOnItemSold();
			PlayerUI.SetMoney(money, (num < 0) ? (-num) : num, num > 0);
		}

		public static void Reset()
		{
			Money = 0;
			Refl.SetStaticMoney(0);
		}
	}
	public struct MoneySyncBroadcast : IBroadcast
	{
		public int Money;
	}
	public struct ModHandshakeBroadcast : IBroadcast
	{
		public int Version;
	}
	public static class Net
	{
		public const int ProtocolVersion = 1;

		private static bool _serializersRegistered;

		private static bool _serverRegistered;

		private static bool _clientRegistered;

		public static void RegisterSerializers()
		{
			if (!_serializersRegistered)
			{
				_serializersRegistered = true;
				GenericWriter<MoneySyncBroadcast>.SetWrite((Action<Writer, MoneySyncBroadcast>)delegate(Writer w, MoneySyncBroadcast v)
				{
					w.WriteInt32(v.Money);
				});
				GenericReader<MoneySyncBroadcast>.SetRead((Func<Reader, MoneySyncBroadcast>)((Reader r) => new MoneySyncBroadcast
				{
					Money = r.ReadInt32()
				}));
				GenericWriter<ModHandshakeBroadcast>.SetWrite((Action<Writer, ModHandshakeBroadcast>)delegate(Writer w, ModHandshakeBroadcast v)
				{
					w.WriteInt32(v.Version);
				});
				GenericReader<ModHandshakeBroadcast>.SetRead((Func<Reader, ModHandshakeBroadcast>)((Reader r) => new ModHandshakeBroadcast
				{
					Version = r.ReadInt32()
				}));
			}
		}

		public static void TryRegisterHandlers()
		{
			if (!((Object)(object)InstanceFinder.NetworkManager == (Object)null))
			{
				RegisterSerializers();
				if (!_serverRegistered && (Object)(object)InstanceFinder.ServerManager != (Object)null)
				{
					_serverRegistered = true;
					InstanceFinder.ServerManager.RegisterBroadcast<ModHandshakeBroadcast>((Action<NetworkConnection, ModHandshakeBroadcast, Channel>)OnHandshakeFromClient, true);
					Plugin.Log.LogDebug((object)"Registered server broadcast handler.");
				}
				if (!_clientRegistered && (Object)(object)InstanceFinder.ClientManager != (Object)null)
				{
					_clientRegistered = true;
					InstanceFinder.ClientManager.RegisterBroadcast<MoneySyncBroadcast>((Action<MoneySyncBroadcast, Channel>)OnMoneyFromServer);
					Plugin.Log.LogDebug((object)"Registered client broadcast handler.");
				}
			}
		}

		private static void OnHandshakeFromClient(NetworkConnection conn, ModHandshakeBroadcast msg, Channel channel)
		{
			Wallets.MarkModded(conn, msg.Version);
		}

		private static void OnMoneyFromServer(MoneySyncBroadcast msg, Channel channel)
		{
			ClientMoney.Apply(msg.Money);
		}

		public static void SendHandshake()
		{
			if (!InstanceFinder.IsClientStarted || (Object)(object)InstanceFinder.ClientManager == (Object)null)
			{
				return;
			}
			try
			{
				InstanceFinder.ClientManager.Broadcast<ModHandshakeBroadcast>(new ModHandshakeBroadcast
				{
					Version = 1
				}, (Channel)0);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to send handshake: " + ex));
			}
		}

		public static void SendMoney(NetworkConnection conn, int money)
		{
			if (!InstanceFinder.IsServerStarted || (Object)(object)InstanceFinder.ServerManager == (Object)null || conn == (NetworkConnection)null || !conn.IsActive)
			{
				return;
			}
			try
			{
				InstanceFinder.ServerManager.Broadcast<MoneySyncBroadcast>(conn, new MoneySyncBroadcast
				{
					Money = money
				}, true, (Channel)0);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to send money to client " + conn.ClientId + ": " + ex));
			}
		}
	}
	public static class PurchaseContext
	{
		public static Player Buyer;

		private static int _depth;

		public static bool Active
		{
			get
			{
				if (_depth > 0)
				{
					return (Object)(object)Buyer != (Object)null;
				}
				return false;
			}
		}

		public static void Enter(Player buyer)
		{
			_depth++;
			if ((Object)(object)buyer != (Object)null)
			{
				Buyer = buyer;
			}
		}

		public static void Exit()
		{
			_depth--;
			if (_depth <= 0)
			{
				_depth = 0;
				Buyer = null;
			}
		}
	}
	public static class Patches
	{
		private static readonly string[] SpendMethods = new string[8] { "BuyItem", "BuyBait", "BuyAttachment", "BuyBulletUpgrade", "BuySharpnessUpgrade", "BuyBoatMotor", "BuyBoatRadar", "UnlockPocket" };

		public static void ApplyAll(Harmony harmony)
		{
			PatchMoneyManager(harmony);
			PatchSaveManager(harmony);
			PatchSpendSites(harmony);
		}

		private static void PatchMoneyManager(Harmony harmony)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected O, but got Unknown
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Expected O, but got Unknown
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			Type typeFromHandle = typeof(MoneyManager);
			Type typeFromHandle2 = typeof(Patches);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "AddMoney", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "AddMoneyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "RemoveMoney", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "RemoveMoneyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "SellItem", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "SellItemPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "CanAfford", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "CanAffordPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnChangeMoney", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle2, "SkipPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnStartServer", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle2, "OnStartServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnStartClient", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle2, "OnStartClientPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeFromHandle, "OnStopClient", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle2, "OnStopClientPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void PatchSaveManager(Harmony harmony)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_0049: Expected O, but got Unknown
			harmony.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "SaveServer", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Patches), "SaveServerPrefix", (Type[])null), new HarmonyMethod(typeof(Patches), "SaveServerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void PatchSpendSites(Harmony harmony)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			HarmonyMethod val = new HarmonyMethod(typeof(Patches), "SpendPrefix", (Type[])null);
			HarmonyMethod val2 = new HarmonyMethod(typeof(Patches), "SpendFinalizer", (Type[])null);
			MethodInfo[] methods = typeof(Server).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			List<string> list = new List<string>();
			string[] spendMethods = SpendMethods;
			foreach (string text in spendMethods)
			{
				string text2 = "RpcLogic___" + text + "___";
				bool flag = false;
				MethodInfo[] array = methods;
				foreach (MethodInfo methodInfo in array)
				{
					if (methodInfo.Name.StartsWith(text2, StringComparison.Ordinal))
					{
						harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null);
						list.Add(text);
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					Plugin.Log.LogError((object)("Could not find Server." + text2 + "* - purchases through '" + text + "' will be charged to the wrong wallet."));
				}
			}
			Plugin.Log.LogInfo((object)string.Format("Hooked {0}/{1} purchase paths: {2}", list.Count, SpendMethods.Length, string.Join(", ", list.ToArray())));
		}

		private static bool SkipPrefix()
		{
			return false;
		}

		private static bool AddMoneyPrefix(int amount, Player player)
		{
			if (!InstanceFinder.IsServerStarted)
			{
				return false;
			}
			Wallets.Add(player, amount);
			Refl.PlayMoneySounds(increase: true, player);
			return false;
		}

		private static bool RemoveMoneyPrefix(int amount, Player player)
		{
			if (!InstanceFinder.IsServerStarted)
			{
				return false;
			}
			Wallets.Remove(player, amount);
			Refl.PlayMoneySounds(increase: false, player);
			return false;
		}

		private static bool SellItemPrefix(Item item)
		{
			if (!InstanceFinder.IsServerStarted || (Object)(object)item == (Object)null)
			{
				return false;
			}
			Player val = item.LastHolder;
			if ((Object)(object)val == (Object)null)
			{
				if (!Plugin.UnclaimedSalesGoToHost.Value)
				{
					Plugin.Log.LogInfo((object)$"Sold '{item.GetName()}' for {item.TotalWorth} but nobody had held it; income discarded.");
					return false;
				}
				val = Player.LocalPlayer;
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				Plugin.Log.LogInfo((object)$"Sold '{item.GetName()}' for {item.TotalWorth} but nobody had held it; credited the host.");
			}
			Wallets.Add(val, item.TotalWorth);
			Refl.PlayMoneySounds(increase: true, val);
			return false;
		}

		private static bool CanAffordPrefix(int cost, ref bool __result)
		{
			if (InstanceFinder.IsServerStarted && PurchaseContext.Active)
			{
				__result = cost <= Wallets.Get(PurchaseContext.Buyer);
				return false;
			}
			return true;
		}

		private static void SpendPrefix(object[] __args)
		{
			PurchaseContext.Enter(ResolveBuyer(__args));
		}

		private static void SpendFinalizer()
		{
			PurchaseContext.Exit();
		}

		private static Player ResolveBuyer(object[] args)
		{
			if (args == null)
			{
				return null;
			}
			object[] array = args;
			foreach (object obj in array)
			{
				Player val = (Player)((obj is Player) ? obj : null);
				if (val != null)
				{
					return val;
				}
			}
			array = args;
			foreach (object obj2 in array)
			{
				Item val2 = (Item)((obj2 is Item) ? obj2 : null);
				if (val2 != null && (Object)(object)val2 != (Object)null)
				{
					Player val3 = val2.Holder;
					if ((Object)(object)val3 == (Object)null)
					{
						val3 = val2.SyncedHolder;
					}
					if ((Object)(object)val3 == (Object)null)
					{
						val3 = val2.LastHolder;
					}
					if ((Object)(object)val3 != (Object)null)
					{
						return val3;
					}
				}
			}
			Plugin.Log.LogWarning((object)"Could not work out who is buying; the purchase will be refused.");
			return null;
		}

		private static void SaveServerPrefix(out int __state)
		{
			__state = Refl.GetStaticMoney();
			if (InstanceFinder.IsServerStarted)
			{
				Refl.SetStaticMoney(Wallets.Total());
			}
		}

		private static void SaveServerPostfix(int __state)
		{
			Refl.SetStaticMoney(__state);
			if (InstanceFinder.IsServerStarted)
			{
				Wallets.Save();
			}
		}

		private static void OnStartServerPostfix()
		{
			Wallets.LoadForServer();
		}

		private static void OnStartClientPostfix()
		{
			ClientMoney.Reset();
			Net.SendHandshake();
		}

		private static void OnStopClientPostfix()
		{
			ClientMoney.Reset();
			if (!InstanceFinder.IsServerStarted)
			{
				Wallets.Save();
				Wallets.OnServerStopped();
			}
		}
	}
	[BepInPlugin("dazed.howtofish.nosharedmoney", "NoSharedMoney", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "dazed.howtofish.nosharedmoney";

		public const string Name = "NoSharedMoney";

		public const string Version = "1.0.0";

		public static ManualLogSource Log;

		public static ConfigEntry<int> StartingMoney;

		public static ConfigEntry<bool> WarnInChat;

		public static ConfigEntry<bool> UnclaimedSalesGoToHost;

		private Harmony _harmony;

		private float _nextReconcile;

		private void Awake()
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			StartingMoney = ((BaseUnityPlugin)this).Config.Bind<int>("General", "StartingMoney", 0, "How much money a player who has never played this save starts with.");
			WarnInChat = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "WarnInChat", true, "Host only: announce in chat when a connected player is missing the mod.");
			UnclaimedSalesGoToHost = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UnclaimedSalesGoToHost", true, "If something is sold that no player ever held, credit the host instead of discarding the money.");
			if (!Refl.Resolve())
			{
				Log.LogError((object)"Aborting: the game's money internals do not look the way this mod expects.");
				return;
			}
			Net.RegisterSerializers();
			_harmony = new Harmony("dazed.howtofish.nosharedmoney");
			Patches.ApplyAll(_harmony);
			Server.OnServerStopped += OnServerStopped;
			Log.LogInfo((object)"NoSharedMoney 1.0.0 loaded. Money is now per-player; every player needs this mod installed.");
		}

		private void OnDestroy()
		{
			Server.OnServerStopped -= OnServerStopped;
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private static void OnServerStopped()
		{
			Wallets.Save();
			Wallets.OnServerStopped();
		}

		private void Update()
		{
			Net.TryRegisterHandlers();
			if (!(Time.unscaledTime < _nextReconcile))
			{
				_nextReconcile = Time.unscaledTime + 0.25f;
				if (InstanceFinder.IsServerStarted)
				{
					Wallets.Reconcile();
				}
			}
		}
	}
	public static class Refl
	{
		private static FieldInfo _moneyBackingField;

		private static FieldInfo _onItemSoldField;

		private static MethodInfo _moneySound;

		private static MethodInfo _observerMoneySound;

		public static bool Resolve()
		{
			bool flag = true;
			_moneyBackingField = AccessTools.Field(typeof(MoneyManager), "<Money>k__BackingField");
			flag &= Require(_moneyBackingField, "MoneyManager.<Money>k__BackingField");
			_onItemSoldField = AccessTools.Field(typeof(MoneyManager), "OnItemSold");
			flag &= Require(_onItemSoldField, "MoneyManager.OnItemSold");
			_moneySound = AccessTools.Method(typeof(MoneyManager), "MoneySound", new Type[2]
			{
				typeof(bool),
				typeof(Player)
			}, (Type[])null);
			flag &= Require(_moneySound, "MoneyManager.MoneySound");
			_observerMoneySound = AccessTools.Method(typeof(MoneyManager), "ObserverMoneySound", new Type[2]
			{
				typeof(bool),
				typeof(Player)
			}, (Type[])null);
			return flag & Require(_observerMoneySound, "MoneyManager.ObserverMoneySound");
		}

		private static bool Require(MemberInfo member, string name)
		{
			if (member != null)
			{
				return true;
			}
			Plugin.Log.LogError((object)("Could not find " + name + ". The game has probably updated; NoSharedMoney will not work correctly."));
			return false;
		}

		public static void SetStaticMoney(int value)
		{
			_moneyBackingField?.SetValue(null, value);
		}

		public static int GetStaticMoney()
		{
			if (_moneyBackingField == null)
			{
				return 0;
			}
			return (int)_moneyBackingField.GetValue(null);
		}

		public static void RaiseOnItemSold()
		{
			(_onItemSoldField?.GetValue(null) as Action)?.Invoke();
		}

		public static void PlayMoneySounds(bool increase, Player player)
		{
			MoneyManager instance = MoneyManager.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)player == (Object)null)
			{
				return;
			}
			object[] parameters = new object[2] { increase, player };
			try
			{
				_observerMoneySound?.Invoke(instance, parameters);
				_moneySound?.Invoke(instance, parameters);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Money sound failed: " + ex.Message));
			}
		}
	}
	public static class Wallets
	{
		private const ulong SyntheticKeyBase = 18446462598732840960uL;

		private static readonly Dictionary<ulong, int> _wallets = new Dictionary<ulong, int>();

		private static readonly Dictionary<ulong, int> _lastSent = new Dictionary<ulong, int>();

		private static readonly HashSet<int> _moddedClientIds = new HashSet<int>();

		private static readonly Dictionary<int, float> _firstSeen = new Dictionary<int, float>();

		private static readonly HashSet<int> _warnedClientIds = new HashSet<int>();

		private static string _saveName;

		private static int _unassignedPot;

		private static bool _loaded;

		public static bool IsModded(NetworkConnection conn)
		{
			if (conn != (NetworkConnection)null)
			{
				return _moddedClientIds.Contains(conn.ClientId);
			}
			return false;
		}

		public static void LoadForServer()
		{
			_wallets.Clear();
			_lastSent.Clear();
			_moddedClientIds.Clear();
			_firstSeen.Clear();
			_warnedClientIds.Clear();
			_unassignedPot = 0;
			_loaded = true;
			_saveName = ((SaveManager.CurServerSave != null) ? SaveManager.CurServerSave.Name : null);
			WalletFile walletFile = WalletStore.Load(_saveName);
			if (walletFile != null)
			{
				foreach (WalletEntry wallet in walletFile.Wallets)
				{
					if (ulong.TryParse(wallet.SteamID, out var result) && result != 0L)
					{
						_wallets[result] = Mathf.Max(0, wallet.Money);
					}
				}
				Plugin.Log.LogInfo((object)$"Loaded {_wallets.Count} individual wallet(s) totalling {Total()} for save '{_saveName}'.");
			}
			else
			{
				MigrateSharedPot();
				Save();
			}
		}

		private static void MigrateSharedPot()
		{
			int num = ((SaveManager.CurServerSave != null) ? SaveManager.CurServerSave.Money : 0);
			if (num <= 0)
			{
				Plugin.Log.LogInfo((object)"No shared balance to migrate; everyone starts from the configured amount.");
				return;
			}
			List<ulong> list = new List<ulong>();
			if (SaveManager.CurServerSave != null && SaveManager.CurServerSave.Players != null)
			{
				foreach (SavedPlayer player in SaveManager.CurServerSave.Players)
				{
					if (player != null && player.SteamID != 0L && !list.Contains(player.SteamID))
					{
						list.Add(player.SteamID);
					}
				}
			}
			if (list.Count == 0)
			{
				_unassignedPot = num;
				Plugin.Log.LogInfo((object)$"Migrating shared balance of {num}: no player records in the save, so it goes to the first player who joins.");
				return;
			}
			int num2 = num / list.Count;
			int num3 = num - num2 * list.Count;
			for (int i = 0; i < list.Count; i++)
			{
				_wallets[list[i]] = num2 + ((i == 0) ? num3 : 0);
			}
			Plugin.Log.LogInfo((object)$"Migrated shared balance of {num} evenly across {list.Count} saved player(s) ({num2} each, remainder {num3} to the first).");
		}

		public static void OnServerStopped()
		{
			_loaded = false;
			_wallets.Clear();
			_lastSent.Clear();
			_moddedClientIds.Clear();
			_firstSeen.Clear();
			_warnedClientIds.Clear();
			_saveName = null;
			_unassignedPot = 0;
		}

		private static ulong KeyFor(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return 0uL;
			}
			ulong steamID = player.SteamID;
			if (steamID != 0L)
			{
				return steamID;
			}
			NetworkConnection owner = ((NetworkBehaviour)player).Owner;
			if (owner != (NetworkConnection)null && owner.ClientId >= 0)
			{
				return (ulong)(-281474976710656L | (uint)owner.ClientId);
			}
			return 0uL;
		}

		private static bool IsSynthetic(ulong key)
		{
			return (key & 0xFFFF000000000000uL) == 18446462598732840960uL;
		}

		public static int Get(Player player)
		{
			ulong num = KeyFor(player);
			if (num == 0L)
			{
				return 0;
			}
			return EnsureWallet(num);
		}

		private static int EnsureWallet(ulong key)
		{
			if (_wallets.TryGetValue(key, out var value))
			{
				return value;
			}
			int num = Plugin.StartingMoney.Value;
			if (_unassignedPot > 0)
			{
				num += _unassignedPot;
				Plugin.Log.LogInfo((object)$"Handed the migrated shared balance of {_unassignedPot} to the first joining player.");
				_unassignedPot = 0;
			}
			num = Mathf.Max(0, num);
			_wallets[key] = num;
			return num;
		}

		public static void Add(Player player, int amount)
		{
			Set(player, Get(player) + Mathf.Abs(amount));
		}

		public static void Remove(Player player, int amount)
		{
			Set(player, Get(player) - Mathf.Abs(amount));
		}

		public static void Set(Player player, int value)
		{
			ulong num = KeyFor(player);
			if (num == 0L)
			{
				Plugin.Log.LogWarning((object)"Tried to change the wallet of a player with no identity; ignoring.");
				return;
			}
			EnsureWallet(num);
			_wallets[num] = Mathf.Clamp(value, 0, int.MaxValue);
			Push(player);
		}

		public static int Total()
		{
			int num = 0;
			foreach (int value in _wallets.Values)
			{
				num += value;
			}
			return num;
		}

		public static void Push(Player player)
		{
			ulong num = KeyFor(player);
			if (num == 0L || !_wallets.TryGetValue(num, out var value) || (_lastSent.TryGetValue(num, out var value2) && value2 == value))
			{
				return;
			}
			_lastSent[num] = value;
			if ((Object)(object)Player.LocalPlayer == (Object)(object)player)
			{
				ClientMoney.Apply(value);
				return;
			}
			NetworkConnection owner = ((NetworkBehaviour)player).Owner;
			if (!(owner == (NetworkConnection)null) && IsModded(owner))
			{
				Net.SendMoney(owner, value);
			}
		}

		public static void MarkModded(NetworkConnection conn, int version)
		{
			if (conn == (NetworkConnection)null)
			{
				return;
			}
			if (version != 1)
			{
				Plugin.Log.LogWarning((object)$"Client {conn.ClientId} runs NoSharedMoney protocol {version}, this server is {1}. Wallet sync may misbehave; use matching mod versions.");
			}
			if (_moddedClientIds.Add(conn.ClientId))
			{
				Plugin.Log.LogInfo((object)$"Client {conn.ClientId} has NoSharedMoney installed.");
			}
			foreach (Player player in PlayerManager.Players)
			{
				if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner == conn)
				{
					_lastSent.Remove(KeyFor(player));
					Push(player);
					break;
				}
			}
		}

		public static void Reconcile()
		{
			if (!_loaded || !InstanceFinder.IsServerStarted)
			{
				return;
			}
			List<Player> players = PlayerManager.Players;
			if (players == null)
			{
				return;
			}
			for (int i = 0; i < players.Count; i++)
			{
				Player val = players[i];
				if ((Object)(object)val == (Object)null || ((NetworkBehaviour)val).IsDeinitializing)
				{
					continue;
				}
				ulong num = KeyFor(val);
				if (num == 0L)
				{
					continue;
				}
				EnsureWallet(num);
				Push(val);
				NetworkConnection owner = ((NetworkBehaviour)val).Owner;
				if (owner == (NetworkConnection)null || (Object)(object)val == (Object)(object)Player.LocalPlayer)
				{
					continue;
				}
				if (!_firstSeen.ContainsKey(owner.ClientId))
				{
					_firstSeen[owner.ClientId] = Time.time;
				}
				if (!IsModded(owner) && Time.time - _firstSeen[owner.ClientId] > 10f && _warnedClientIds.Add(owner.ClientId))
				{
					string text = "'" + val.SteamName + "' does not have NoSharedMoney installed. Their money display will be wrong and they will not be able to buy anything.";
					Plugin.Log.LogWarning((object)text);
					if (Plugin.WarnInChat.Value)
					{
						ChatManager.ChatMessage("<i>[NoSharedMoney] " + text + "</i>");
					}
				}
			}
		}

		public static void Save()
		{
			if (!_loaded || string.IsNullOrEmpty(_saveName))
			{
				return;
			}
			WalletFile walletFile = new WalletFile();
			foreach (KeyValuePair<ulong, int> wallet in _wallets)
			{
				if (!IsSynthetic(wallet.Key))
				{
					walletFile.Wallets.Add(new WalletEntry
					{
						SteamID = wallet.Key.ToString(),
						Money = wallet.Value
					});
				}
			}
			WalletStore.Save(_saveName, walletFile);
			Plugin.Log.LogInfo((object)$"Saved {walletFile.Wallets.Count} wallet(s) totalling {Total()} for save '{_saveName}'.");
		}
	}
	[Serializable]
	public class WalletEntry
	{
		public string SteamID;

		public int Money;
	}
	[Serializable]
	public class WalletFile
	{
		public List<WalletEntry> Wallets = new List<WalletEntry>();
	}
	public static class WalletStore
	{
		private static string Folder => Path.Combine(Application.persistentDataPath, "NoSharedMoney");

		private static string PathFor(string saveName)
		{
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char oldChar in invalidFileNameChars)
			{
				saveName = saveName.Replace(oldChar, '_');
			}
			return Path.Combine(Folder, saveName + ".json");
		}

		public static WalletFile Load(string saveName)
		{
			if (string.IsNullOrEmpty(saveName))
			{
				return null;
			}
			try
			{
				string path = PathFor(saveName);
				if (!File.Exists(path))
				{
					return null;
				}
				WalletFile walletFile = JsonConvert.DeserializeObject<WalletFile>(File.ReadAllText(path));
				if (walletFile == null)
				{
					return null;
				}
				if (walletFile.Wallets == null)
				{
					walletFile.Wallets = new List<WalletEntry>();
				}
				return walletFile;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to read wallets: " + ex));
				return null;
			}
		}

		public static void Save(string saveName, WalletFile file)
		{
			if (string.IsNullOrEmpty(saveName))
			{
				return;
			}
			try
			{
				Directory.CreateDirectory(Folder);
				string text = JsonConvert.SerializeObject((object)file, (Formatting)1);
				if (string.IsNullOrEmpty(text) || text == "{}")
				{
					Plugin.Log.LogError((object)"Wallet serialization produced nothing; refusing to overwrite the file.");
				}
				else
				{
					File.WriteAllText(PathFor(saveName), text);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Failed to write wallets: " + ex));
			}
		}
	}
}