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.SetWrite((Action)delegate(Writer w, MoneySyncBroadcast v) { w.WriteInt32(v.Money); }); GenericReader.SetRead((Func)((Reader r) => new MoneySyncBroadcast { Money = r.ReadInt32() })); GenericWriter.SetWrite((Action)delegate(Writer w, ModHandshakeBroadcast v) { w.WriteInt32(v.Version); }); GenericReader.SetRead((Func)((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((Action)OnHandshakeFromClient, true); Plugin.Log.LogDebug((object)"Registered server broadcast handler."); } if (!_clientRegistered && (Object)(object)InstanceFinder.ClientManager != (Object)null) { _clientRegistered = true; InstanceFinder.ClientManager.RegisterBroadcast((Action)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(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(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 list = new List(); 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 StartingMoney; public static ConfigEntry WarnInChat; public static ConfigEntry 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("General", "StartingMoney", 0, "How much money a player who has never played this save starts with."); WarnInChat = ((BaseUnityPlugin)this).Config.Bind("General", "WarnInChat", true, "Host only: announce in chat when a connected player is missing the mod."); UnclaimedSalesGoToHost = ((BaseUnityPlugin)this).Config.Bind("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), "k__BackingField"); flag &= Require(_moneyBackingField, "MoneyManager.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 _wallets = new Dictionary(); private static readonly Dictionary _lastSent = new Dictionary(); private static readonly HashSet _moddedClientIds = new HashSet(); private static readonly Dictionary _firstSeen = new Dictionary(); private static readonly HashSet _warnedClientIds = new HashSet(); 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 list = new List(); 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 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("[NoSharedMoney] " + text + ""); } } } } public static void Save() { if (!_loaded || string.IsNullOrEmpty(_saveName)) { return; } WalletFile walletFile = new WalletFile(); foreach (KeyValuePair 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 Wallets = new List(); } 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(File.ReadAllText(path)); if (walletFile == null) { return null; } if (walletFile.Wallets == null) { walletFile.Wallets = new List(); } 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)); } } } }