Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of TextYourFriends Il2Cpp v1.0.1
Mods/TextYourFriends_Il2cpp.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using HarmonyLib; using Il2Cpp; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppNewtonsoft.Json; using Il2CppNewtonsoft.Json.Linq; using Il2CppScheduleOne; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.Persistence.Datas; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Phone; using Il2CppScheduleOne.UI.Phone.Messages; using Il2CppSteamworks; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using S1API.Internal.Abstraction; using S1API.Saveables; using SteamNetworkLib; using SteamNetworkLib.Core; using SteamNetworkLib.Events; using SteamNetworkLib.Models; using SteamNetworkLib.Utilities; using TextYourFriends; using TextYourFriends.Events; using TextYourFriends.GameIntegration; using TextYourFriends.Models; using TextYourFriends.Models.Persistence; using TextYourFriends.Models.Sync; using TextYourFriends.Persistence; using TextYourFriends.Services; using TextYourFriends.UI; using TextYourFriends.Utils; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "TextYourFriends", "1.0.0", "Bars", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TextYourFriends_Il2cpp")] [assembly: AssemblyConfiguration("Il2cpp")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("TextYourFriends_Il2cpp")] [assembly: AssemblyTitle("TextYourFriends_Il2cpp")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.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 TextYourFriends { public class Core : MelonMod { private ISteamLobbyService? _steamLobbyService; private IPlayerMessagePersistenceService? _persistenceService; private IPlayerMessagingService? _messagingService; private PlayerConversationManager? _conversationManager; private bool _isInitialized = false; private MessagesApp? _messagesAppInstance; public static Core? Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; ModConfig.Initialize(); ModLogger.LogInitialization(); try { HarmonyPatches.SetModInstance(this); ModLogger.Info("TextYourFriends mod initialized successfully"); } catch (Exception exception) { ModLogger.Error("Failed to initialize TextYourFriends mod", exception); } } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { try { ModLogger.Debug($"Scene initialized: {sceneName} (index: {buildIndex})"); if (sceneName.Contains("Main")) { ModLogger.Debug("Main game scene detected, initializing TextYourFriends services..."); InitializeServices(); } } catch (Exception exception) { ModLogger.Error("Error during scene initialization", exception); } } public override void OnUpdate() { try { if (_isInitialized && _messagingService != null) { _messagingService.Update(); } _steamLobbyService?.Update(); } catch (Exception exception) { ModLogger.Error("Error during update", exception); } } public override void OnApplicationQuit() { try { ModLogger.LogShutdown(); Cleanup(); Instance = null; } catch (Exception exception) { ModLogger.Error("Error during shutdown", exception); } } private void InitializeServices() { if (_isInitialized) { return; } try { ModLogger.Debug("Initializing TextYourFriends services..."); _steamLobbyService = new SteamLobbyService(); if (!_steamLobbyService.Initialize()) { ModLogger.Error("Failed to initialize Steam lobby service"); return; } _persistenceService = new PlayerMessagePersistenceService(); if (!_persistenceService.Initialize()) { ModLogger.Error("Failed to initialize persistence service"); return; } _messagingService = new PlayerMessagingService(_steamLobbyService, _persistenceService); if (!_messagingService.Initialize()) { ModLogger.Error("Failed to initialize messaging service"); return; } SubscribeToMessagingEvents(); _isInitialized = true; ModLogger.Debug("TextYourFriends services initialized successfully"); } catch (Exception exception) { ModLogger.Error("Failed to initialize services", exception); } } private void SubscribeToMessagingEvents() { if (_messagingService != null) { _messagingService.OnMessageReceived += OnMessageReceived; _messagingService.OnMessageSent += OnMessageSent; _messagingService.OnConversationUpdated += OnConversationUpdated; } } private void OnMessageReceived(object? sender, PlayerMessageEventArgs e) { try { ModLogger.Debug("Core.OnMessageReceived: Message received from " + e.Message.SenderName + ": " + e.Message.Content); if ((Object)(object)_conversationManager != (Object)null) { _conversationManager.AddReceivedMessage(e.Conversation, e.Message); } else { ModLogger.Warning("_conversationManager is null, cannot add received message to UI"); } } catch (Exception exception) { ModLogger.Error("Error handling received message", exception); } } private void OnMessageSent(object? sender, PlayerMessageEventArgs e) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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) try { if ((Object)(object)_conversationManager != (Object)null) { CSteamID steamId = e.Conversation.OtherPlayer.SteamId; PlayerMSGConversation playerMSGConversation = _conversationManager.GetPlayerMSGConversation(steamId); if (playerMSGConversation != null) { playerMSGConversation.PlayerConversation = e.Conversation; playerMSGConversation.AddSentMessage(e.Message); } else { ModLogger.Warning("No MSGConversation found for " + e.Conversation.OtherPlayer.DisplayName + ", creating new one"); _conversationManager.AddPlayerConversation(e.Conversation); } } } catch (Exception exception) { ModLogger.Error("Error handling sent message", exception); } } private void OnConversationUpdated(object? sender, PlayerConversation e) { try { if ((Object)(object)_conversationManager != (Object)null) { _conversationManager.AddPlayerConversation(e); } } catch (Exception exception) { ModLogger.Error("Error handling conversation update", exception); } } internal void OnMessagesAppUpdate(MessagesApp messagesApp) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) try { if (_messagingService != null && (Object)(object)_conversationManager != (Object)null) { _conversationManager.UpdateConversationList(_messagingService.Conversations); } if (_messagingService == null || !_messagingService.IsMessagingAvailable || _messagingService.Conversations.Count != 0) { return; } ISteamLobbyService steamLobbyService = _steamLobbyService; if (steamLobbyService == null || !steamLobbyService.IsInLobby || steamLobbyService.PlayerCount <= 1) { return; } List<PlayerInfo> lobbyPlayers = steamLobbyService.GetLobbyPlayers(); foreach (PlayerInfo item in lobbyPlayers) { if (!item.IsLocalPlayer) { _messagingService.GetOrCreateConversation(item.SteamId); } } if (_messagingService.Conversations.Count > 0) { UpdatePlayerConversationsUI(); } } catch (Exception exception) { ModLogger.Error("Error handling MessagesApp update", exception); } } internal void OnMessagesAppReturnButtonClicked(MessagesApp messagesApp) { try { } catch (Exception exception) { ModLogger.Error("Error handling return button click", exception); } } internal void OnLobbyChatMessageReceived(string message, CSteamID sender) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) try { ModLogger.Debug($"Core.OnLobbyChatMessageReceived: Entry - sender={sender.m_SteamID}, messageLength={message?.Length ?? 0}"); if (_steamLobbyService is SteamLobbyService steamLobbyService) { ModLogger.Debug("Core.OnLobbyChatMessageReceived: Triggering SteamLobbyService.TriggerLobbyChatMessage"); steamLobbyService.TriggerLobbyChatMessage(message); return; } ModLogger.Warning("Could not trigger lobby chat message - service is not SteamLobbyService type"); ModLogger.Debug("Core.OnLobbyChatMessageReceived: Falling back to ProcessInterceptedMessage"); if (_messagingService != null) { ProcessInterceptedMessage(message, sender); } } catch (Exception exception) { ModLogger.Error("Error handling lobby chat message", exception); } } private void ProcessInterceptedMessage(string lobbyMessage, CSteamID sender) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) try { ModLogger.Debug($"Core.ProcessInterceptedMessage: Entry - sender={sender.m_SteamID}, rawLength={lobbyMessage?.Length ?? 0}"); PlayerMessage playerMessage = MessageJsonSerializer.TryParseFromLobbyMessage(lobbyMessage); if (playerMessage == null) { ModLogger.Warning("Could not parse intercepted message"); ModLogger.Debug("Core.ProcessInterceptedMessage: Parse failed, raw preview: " + lobbyMessage?.Substring(0, Math.Min(100, lobbyMessage?.Length ?? 0)) + "..."); return; } ModLogger.Debug($"Core.ProcessInterceptedMessage: Parsed - senderId={playerMessage.SenderId.m_SteamID}, recipientId={playerMessage.RecipientId.m_SteamID}, type={playerMessage.MessageType}"); if (_steamLobbyService != null && playerMessage.SenderId == _steamLobbyService.LocalPlayerId) { ModLogger.Debug("Core.ProcessInterceptedMessage: Skipping message from self"); return; } if (_steamLobbyService != null && playerMessage.RecipientId != _steamLobbyService.LocalPlayerId && playerMessage.RecipientId != CSteamID.Nil) { ModLogger.Debug($"Core.ProcessInterceptedMessage: Message not for us (recipient={playerMessage.RecipientId.m_SteamID}), skipping"); return; } ModLogger.Debug("Processing intercepted message from " + playerMessage.SenderName + ": " + playerMessage.Content); PlayerConversation orCreateConversation = _messagingService.GetOrCreateConversation(playerMessage.SenderId); if (playerMessage.IsTextMessage) { orCreateConversation.AddMessage(playerMessage); if ((Object)(object)_conversationManager != (Object)null) { _conversationManager.AddReceivedMessage(orCreateConversation, playerMessage); } ModLogger.Debug("Core.ProcessInterceptedMessage: Successfully processed text message from " + playerMessage.SenderName); } else { ModLogger.Debug($"Core.ProcessInterceptedMessage: Message was not a text message (isText={playerMessage.IsTextMessage}), skipping UI update"); } } catch (Exception exception) { ModLogger.Error("Error processing intercepted message", exception); } } internal void OnConversationOpened(MSGConversation conversation) { try { } catch (Exception exception) { ModLogger.Error("Error handling conversation opened", exception); } } internal void OnConversationClosed(MSGConversation conversation) { try { } catch (Exception exception) { ModLogger.Error("Error handling conversation closed", exception); } } public IPlayerMessagingService? GetMessagingService() { return _messagingService; } public IPlayerMessagePersistenceService? GetPersistenceService() { return _persistenceService; } public bool ForceSave() { try { ModLogger.Debug("=== Force Save Triggered ==="); bool flag = false; if (_messagingService is PlayerMessagingService playerMessagingService) { flag = playerMessagingService.ForceSave(); ModLogger.Debug($"Messaging service force save result: {flag}"); } if (_persistenceService != null && _persistenceService is PlayerMessagePersistenceService playerMessagePersistenceService) { playerMessagePersistenceService.SaveToDisk(); } else { Saveable.RequestGameSave(false); } return flag; } catch (Exception exception) { ModLogger.Error("Error in ForceSave", exception); return false; } } public Dictionary<string, object> GetFullStatistics() { Dictionary<string, object> dictionary = _messagingService?.GetStatistics() ?? new Dictionary<string, object>(); if (_persistenceService != null) { Dictionary<string, object> saveStatistics = _persistenceService.GetSaveStatistics(); foreach (KeyValuePair<string, object> item in saveStatistics) { dictionary["Persistence_" + item.Key] = item.Value; } } TextYourFriendsSave instance = TextYourFriendsSave.Instance; dictionary["SaveSystem_S1API"] = instance != null; dictionary["SaveSystem_HasChanges"] = _persistenceService?.HasUnsavedChanges ?? false; return dictionary; } private void Cleanup() { try { _conversationManager?.Cleanup(); _messagingService?.Dispose(); _persistenceService?.Dispose(); _steamLobbyService?.Dispose(); _conversationManager = null; _messagingService = null; _persistenceService = null; _steamLobbyService = null; _messagesAppInstance = null; _isInitialized = false; } catch (Exception exception) { ModLogger.Error("Error during cleanup", exception); } } public void OnMessagesAppStarted(MessagesApp messagesApp) { try { if ((Object)(object)messagesApp == (Object)null) { ModLogger.Warning("MessagesApp is null in OnMessagesAppStarted"); return; } _messagesAppInstance = messagesApp; InitializeUIService(messagesApp); IPlayerMessagingService? messagingService = _messagingService; if (messagingService != null && messagingService.Conversations?.Count > 0) { UpdatePlayerConversationsUI(); } ModLogger.Debug("MessagesApp integration initialized successfully"); } catch (Exception exception) { ModLogger.Error("Error in OnMessagesAppStarted", exception); } } private void InitializeUIService(MessagesApp messagesApp) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown try { GameObject val = new GameObject("PlayerConversationManager"); _conversationManager = val.AddComponent<PlayerConversationManager>(); _conversationManager.Initialize(messagesApp); ModLogger.Debug("Player Conversation Manager initialized successfully"); } catch (Exception exception) { ModLogger.Error("Error initializing player conversation manager", exception); } } private void UpdatePlayerConversationsUI() { if (_messagingService == null) { ModLogger.Warning("Messaging service is null, cannot update player conversations UI"); return; } try { PlayerConversationManager instance = PlayerConversationManager.Instance; if ((Object)(object)instance == (Object)null) { ModLogger.Warning("PlayerConversationManager instance is null, UI not yet initialized"); return; } IReadOnlyList<PlayerConversation> conversations = _messagingService.Conversations; ModLogger.Debug($"Updating UI with {conversations.Count} player conversations"); foreach (PlayerConversation item in conversations) { ModLogger.Debug($"Adding conversation to UI: {item.OtherPlayer.DisplayName} ({item.Messages.Count} messages)"); instance.AddPlayerConversation(item); } ModLogger.Debug($"Successfully updated UI with {conversations.Count} player conversations"); } catch (Exception exception) { ModLogger.Error("Error updating player conversations UI", exception); } } public void CheckSaveSystemStatus() { try { ModLogger.Debug("=== TextYourFriends Save System Status (S1API) ==="); TextYourFriendsSave instance = TextYourFriendsSave.Instance; ModLogger.Debug($"S1API Saveable Instance: {instance != null}"); if (instance != null) { ModLogger.Debug($"Saved Conversations: {(instance.GetData()?.Conversations?.Count).GetValueOrDefault()}"); } ModLogger.Debug($"Persistence Service Available: {_persistenceService != null}"); ModLogger.Debug($"Messaging Service Available: {_messagingService != null}"); if (_messagingService != null) { List<PlayerConversation> allConversations = _messagingService.GetAllConversations(); ModLogger.Debug($"Current Conversations: {allConversations.Count}"); foreach (PlayerConversation item in allConversations) { ModLogger.Debug($" - {item.OtherPlayer.DisplayName}: {item.Messages.Count} messages"); } } Dictionary<string, object> fullStatistics = GetFullStatistics(); ModLogger.Debug("=== Full Statistics ==="); foreach (KeyValuePair<string, object> item2 in fullStatistics) { ModLogger.Debug($"{item2.Key}: {item2.Value}"); } ModLogger.Debug("=== End Save System Status ==="); } catch (Exception ex) { ModLogger.Error("Error checking save system status: " + ex.Message, ex); } } public static T? GetService<T>() where T : class { if (Instance == null) { return null; } if (typeof(T) == typeof(IPlayerMessagingService)) { return Instance._messagingService as T; } if (typeof(T) == typeof(IPlayerMessagePersistenceService)) { return Instance._persistenceService as T; } if (typeof(T) == typeof(ISteamLobbyService)) { return Instance._steamLobbyService as T; } return null; } } } namespace TextYourFriends.Utils { public static class Constants { public const string MOD_NAME = "TextYourFriends"; public const string MOD_VERSION = "1.0.0"; public const string MOD_AUTHOR = "Bars"; public const string MESSAGE_PREFIX = "TYF_MSG:"; public const string MESSAGE_TYPE_PLAYER = "player_message"; public const string MESSAGE_TYPE_TYPING = "typing_indicator"; public const string MESSAGE_TYPE_READ_RECEIPT = "read_receipt"; public const string LOBBY_KEY_MOD_VERSION = "tyf_version"; public const string MEMBER_KEY_DISPLAY_NAME = "tyf_display_name"; public const string MEMBER_KEY_STATUS = "tyf_status"; public const int MAX_MESSAGE_LENGTH = 500; public const int MAX_CONVERSATION_HISTORY = 50; public const int MAX_TYPING_INDICATOR_TIME_MS = 3000; public const string PLAYER_CONVERSATION_CATEGORY = "Players"; public const float MESSAGE_SEND_COOLDOWN_MS = 100f; public const string ERROR_STEAM_NOT_INITIALIZED = "Steam is not initialized"; public const string ERROR_NOT_IN_LOBBY = "Player is not in a lobby"; public const string ERROR_MESSAGE_TOO_LONG = "Message exceeds maximum length"; public const string ERROR_PLAYER_NOT_FOUND = "Target player not found in lobby"; public const string SAVE_FILE_NAME = "TextYourFriends_SaveData.json"; public const string SAVE_FOLDER_NAME = "TextYourFriends"; public const int SAVE_VERSION = 1; public const int AUTO_SAVE_INTERVAL_SECONDS = 30; public const int MAX_SAVE_RETRIES = 3; public const bool ENABLE_AUTO_SAVE = true; public const bool ENABLE_BACKUP_SAVES = true; public const int MAX_BACKUP_COUNT = 5; public const string BACKUP_FILE_PREFIX = "TextYourFriends_Backup_"; public const string TEMP_FILE_SUFFIX = ".tmp"; public const string BACKUP_FOLDER_NAME = "Backups"; } internal static class JsonUtils { public static string Serialize(object value, bool indented = false) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Formatting val = (Formatting)(indented ? 1 : 0); return JsonConvert.SerializeObject((Object)((value is Object) ? value : null), val); } } public static class MessageJsonSerializer { public static string SerializeMessage(PlayerMessage message) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown if (message == null) { throw new ArgumentNullException("message"); } string text = ((message.Timestamp.Kind == DateTimeKind.Utc) ? message.Timestamp : message.Timestamp.ToUniversalTime()).ToString("O"); JObject val = new JObject { ["messageId"] = JToken.op_Implicit(message.MessageId), ["senderId"] = JToken.op_Implicit(message.SenderId.m_SteamID.ToString()), ["recipientId"] = JToken.op_Implicit(message.RecipientId.m_SteamID.ToString()), ["senderName"] = JToken.op_Implicit(message.SenderName), ["content"] = JToken.op_Implicit(message.Content), ["timestamp"] = JToken.op_Implicit(text), ["messageType"] = JToken.op_Implicit(message.MessageType), ["shouldNotify"] = JToken.op_Implicit(message.ShouldNotify) }; return ((Object)val).ToString(); } public static PlayerMessage? DeserializeMessage(string json) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(json)) { return null; } try { JObject val = JObject.Parse(json); if (val == null) { return null; } PlayerMessage playerMessage = new PlayerMessage(); JToken obj = val["messageId"]; playerMessage.MessageId = ((obj != null) ? ((Object)obj).ToString() : null) ?? Guid.NewGuid().ToString(); JToken obj2 = val["content"]; playerMessage.Content = ((obj2 != null) ? ((Object)obj2).ToString() : null) ?? string.Empty; JToken obj3 = val["senderName"]; playerMessage.SenderName = ((obj3 != null) ? ((Object)obj3).ToString() : null) ?? "Unknown"; JToken obj4 = val["messageType"]; playerMessage.MessageType = ((obj4 != null) ? ((Object)obj4).ToString() : null) ?? "player_message"; JToken obj5 = val["shouldNotify"]; playerMessage.ShouldNotify = obj5 == null || obj5.ToObject<bool>(); PlayerMessage playerMessage2 = playerMessage; JToken obj6 = val["senderId"]; if (ulong.TryParse((obj6 != null) ? ((Object)obj6).ToString() : null, out var result)) { playerMessage2.SenderId = new CSteamID(result); } JToken obj7 = val["recipientId"]; if (ulong.TryParse((obj7 != null) ? ((Object)obj7).ToString() : null, out var result2)) { playerMessage2.RecipientId = new CSteamID(result2); } JToken obj8 = val["timestamp"]; string text = ((obj8 != null) ? ((Object)obj8).ToString() : null); if (DateTime.TryParse(text, null, DateTimeStyles.AdjustToUniversal, out var result3)) { playerMessage2.Timestamp = result3; } else { ModLogger.Warning("Failed to parse timestamp '" + text + "' for message " + playerMessage2.MessageId + ", using current time"); playerMessage2.Timestamp = DateTime.UtcNow; } playerMessage2.IsFromLocalPlayer = playerMessage2.SenderId == SteamUser.GetSteamID(); ModLogger.Debug($"MessageJsonSerializer.DeserializeMessage: Success - id={playerMessage2.MessageId}, sender={playerMessage2.SenderId.m_SteamID}, recipient={playerMessage2.RecipientId.m_SteamID}, type={playerMessage2.MessageType}"); return playerMessage2; } catch (Exception ex) { ModLogger.Error("Failed to deserialize message: " + ex.Message); ModLogger.Debug("MessageJsonSerializer.DeserializeMessage: Exception - " + ex.Message + ", json preview: " + json?.Substring(0, Math.Min(80, json?.Length ?? 0)) + "..."); return null; } } public static string CreateLobbyMessage(PlayerMessage message) { string text = SerializeMessage(message); string text2 = "TYF_MSG:" + text; ModLogger.Debug($"MessageJsonSerializer.CreateLobbyMessage: Created lobby message, jsonLength={text?.Length ?? 0}, totalLength={text2?.Length ?? 0}, messageId={message?.MessageId}"); return text2; } public static PlayerMessage? TryParseFromLobbyMessage(string lobbyMessage) { //IL_0098: Unknown result type (might be due to invalid IL or missing references) ModLogger.Debug(string.Format("MessageJsonSerializer.TryParseFromLobbyMessage: Entry - length={0}, hasPrefix={1}", lobbyMessage?.Length ?? 0, lobbyMessage?.StartsWith("TYF_MSG:") ?? false)); if (string.IsNullOrEmpty(lobbyMessage) || !lobbyMessage.StartsWith("TYF_MSG:")) { ModLogger.Debug("MessageJsonSerializer.TryParseFromLobbyMessage: Early exit - null, empty, or missing prefix"); return null; } string json = lobbyMessage.Substring("TYF_MSG:".Length); PlayerMessage playerMessage = DeserializeMessage(json); ModLogger.Debug("MessageJsonSerializer.TryParseFromLobbyMessage: Deserialize result=" + ((playerMessage != null) ? $"id={playerMessage.MessageId}, sender={playerMessage.SenderId.m_SteamID}" : "null")); return playerMessage; } public static bool IsTextYourFriendsMessage(string lobbyMessage) { return !string.IsNullOrEmpty(lobbyMessage) && lobbyMessage.StartsWith("TYF_MSG:"); } public static string SerializeMessages(IEnumerable<PlayerMessage> messages) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown if (messages == null) { throw new ArgumentNullException("messages"); } JArray val = new JArray(); foreach (PlayerMessage message in messages) { DateTime dateTime = ((message.Timestamp.Kind == DateTimeKind.Utc) ? message.Timestamp : message.Timestamp.ToUniversalTime()); JObject val2 = new JObject { ["messageId"] = JToken.op_Implicit(message.MessageId), ["senderId"] = JToken.op_Implicit(message.SenderId.m_SteamID.ToString()), ["recipientId"] = JToken.op_Implicit(message.RecipientId.m_SteamID.ToString()), ["senderName"] = JToken.op_Implicit(message.SenderName), ["content"] = JToken.op_Implicit(message.Content), ["timestamp"] = JToken.op_Implicit(dateTime.ToString("O")), ["messageType"] = JToken.op_Implicit(message.MessageType), ["shouldNotify"] = JToken.op_Implicit(message.ShouldNotify) }; val.Add((JToken)(object)val2); } return ((Object)val).ToString(); } public static List<PlayerMessage> DeserializeMessages(string json) { List<PlayerMessage> list = new List<PlayerMessage>(); if (string.IsNullOrEmpty(json)) { return list; } try { JArray val = JArray.Parse(json); if (val == null) { return list; } for (int i = 0; i < ((JContainer)val).Count; i++) { JToken obj = val[i]; PlayerMessage playerMessage = DeserializeMessage(((obj != null) ? ((Object)obj).ToString() : null) ?? string.Empty); if (playerMessage != null) { list.Add(playerMessage); } } } catch (Exception ex) { ModLogger.Error("Failed to deserialize messages: " + ex.Message); } return list; } } public static class ModConfig { private const string CategoryName = "TextYourFriends"; private static MelonPreferences_Category? _category; private static MelonPreferences_Entry<bool>? _debugLoggingEnabled; public static bool DebugLoggingEnabled => _debugLoggingEnabled?.Value ?? false; public static void Initialize() { _category = MelonPreferences.CreateCategory("TextYourFriends"); _debugLoggingEnabled = _category.CreateEntry<bool>("DebugLogging", false, "Debug Logging", "Enable verbose debug logging for troubleshooting", false, false, (ValueValidator)null, (string)null); _category.SaveToFile(false); ModLogger.Info($"Configuration initialized. Debug logging: {DebugLoggingEnabled}"); } public static void Reload() { MelonPreferences_Category? category = _category; if (category != null) { category.LoadFromFile(true); } } public static void SetDebugLogging(bool enabled) { if (_debugLoggingEnabled != null) { _debugLoggingEnabled.Value = enabled; MelonPreferences_Category? category = _category; if (category != null) { category.SaveToFile(false); } } } } public static class ModLogger { public static void Info(string message) { MelonLogger.Msg(message); } public static void Warning(string message) { MelonLogger.Warning(message); } public static void Error(string message) { MelonLogger.Error(message); } public static void Error(string message, Exception exception) { MelonLogger.Error(message + ": " + exception.Message); MelonLogger.Error("Stack trace: " + exception.StackTrace); } public static void Debug(string message) { if (ModConfig.DebugLoggingEnabled) { MelonLogger.Msg("[DEBUG] " + message); } } public static void LogInitialization() { Info("Initializing TextYourFriends v1.0.0 by Bars"); } public static void LogShutdown() { Info("TextYourFriends shutting down"); } } public static class SteamAvatarLoader { public static Sprite? LoadSteamAvatar(CSteamID steamId) { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) try { if (!SteamManager.Initialized) { ModLogger.Warning("Steamworks not initialized"); return null; } int mediumFriendAvatar = SteamFriends.GetMediumFriendAvatar(steamId); if (mediumFriendAvatar == 0) { return null; } uint num = default(uint); uint num2 = default(uint); if (!SteamUtils.GetImageSize(mediumFriendAvatar, ref num, ref num2) || num == 0 || num2 == 0) { ModLogger.Warning($"Couldn't get avatar size for {steamId}"); return null; } byte[] array = new byte[num * num2 * 4]; if (!SteamUtils.GetImageRGBA(mediumFriendAvatar, Il2CppStructArray<byte>.op_Implicit(array), (int)(num * num2 * 4))) { ModLogger.Warning($"Couldn't get avatar data for {steamId}"); return null; } Texture2D val = new Texture2D((int)num, (int)num2, (TextureFormat)4, false, false); val.LoadRawTextureData(Il2CppStructArray<byte>.op_Implicit(array)); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, (float)num, (float)num2), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { ModLogger.Error($"Error loading Steam avatar for {steamId}: {ex.Message}", ex); return null; } } public static Sprite? LoadLocalPlayerAvatar() { //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_0023: Unknown result type (might be due to invalid IL or missing references) if (!SteamManager.Initialized) { ModLogger.Warning("Steamworks not initialized"); return null; } CSteamID steamID = SteamUser.GetSteamID(); return LoadSteamAvatar(steamID); } public static bool HasAvatar(CSteamID steamId) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) try { if (!SteamManager.Initialized) { return false; } int mediumFriendAvatar = SteamFriends.GetMediumFriendAvatar(steamId); return mediumFriendAvatar != 0; } catch { return false; } } public static void TestSteamAvatarLoading() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) try { ModLogger.Debug("=== Steam Avatar Loading Test ==="); if (!SteamManager.Initialized) { ModLogger.Warning("Steam is not initialized - cannot test avatar loading"); return; } CSteamID steamID = SteamUser.GetSteamID(); string personaName = SteamFriends.GetPersonaName(); ModLogger.Debug($"Local Player: {personaName} ({steamID})"); Sprite val = LoadSteamAvatar(steamID); Rect rect; if ((Object)(object)val != (Object)null) { string name = ((Object)val).name; rect = val.rect; object arg = ((Rect)(ref rect)).width; rect = val.rect; ModLogger.Debug($"✓ Successfully loaded local player avatar: {name} ({arg}x{((Rect)(ref rect)).height})"); } else { ModLogger.Warning("✗ Failed to load local player avatar"); } bool flag = HasAvatar(steamID); ModLogger.Debug($"Local player has avatar: {flag}"); CSteamID steamId = default(CSteamID); ((CSteamID)(ref steamId))..ctor(76561197960287930uL); Sprite val2 = LoadSteamAvatar(steamId); if ((Object)(object)val2 != (Object)null) { string name2 = ((Object)val2).name; rect = val2.rect; object arg2 = ((Rect)(ref rect)).width; rect = val2.rect; ModLogger.Debug($"✓ Successfully loaded Steam official avatar: {name2} ({arg2}x{((Rect)(ref rect)).height})"); } else { ModLogger.Warning("✗ Failed to load Steam official avatar"); } ModLogger.Debug("=== Steam Avatar Loading Test Complete ==="); } catch (Exception ex) { ModLogger.Error("Error during Steam avatar loading test: " + ex.Message, ex); } } } } namespace TextYourFriends.UI { [RegisterTypeInIl2Cpp] public class PlayerConversationManager : MonoBehaviour { private MessagesApp _messagesApp; private readonly Dictionary<CSteamID, PlayerMSGConversation> _playerConversations = new Dictionary<CSteamID, PlayerMSGConversation>(); private readonly Dictionary<CSteamID, PlayerConversation> _activeConversations = new Dictionary<CSteamID, PlayerConversation>(); public static PlayerConversationManager? Instance { get; private set; } public PlayerConversationManager(IntPtr ptr) : base(ptr) { } public void Initialize(MessagesApp messagesApp) { if ((Object)(object)messagesApp == (Object)null) { ModLogger.Error("MessagesApp is null, cannot initialize PlayerConversationManager"); return; } try { _messagesApp = messagesApp; Instance = this; ModLogger.Debug("Player Conversation Manager initialized successfully"); } catch (Exception exception) { ModLogger.Error("Failed to initialize PlayerConversationManager", exception); } } [HideFromIl2Cpp] public void AddPlayerConversation(PlayerConversation conversation) { //IL_002d: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (conversation?.OtherPlayer == null) { ModLogger.Warning("Invalid conversation or player data"); return; } try { CSteamID steamId = conversation.OtherPlayer.SteamId; _activeConversations[steamId] = conversation; if (_playerConversations.TryGetValue(steamId, out var value)) { UpdateExistingConversation(value, conversation); return; } PlayerMSGConversation playerMSGConversation = new PlayerMSGConversation(conversation.OtherPlayer); playerMSGConversation.PlayerConversation = conversation; _playerConversations[steamId] = playerMSGConversation; playerMSGConversation.EnsureUIWithPlayerInput(); if (conversation.Messages.Count > 0) { ModLogger.Debug($"Loading {conversation.Messages.Count} existing messages for {conversation.OtherPlayer.DisplayName}"); playerMSGConversation.LoadExistingMessages(); } playerMSGConversation.OnMessageSent += OnPlayerMessageSent; ModLogger.Debug("Created new PlayerMSGConversation for " + conversation.OtherPlayer.DisplayName); } catch (Exception ex) { ModLogger.Error("Error adding player conversation: " + ex.Message, ex); } } public void RemovePlayerConversation(CSteamID playerId) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) try { if (_playerConversations.TryGetValue(playerId, out var value)) { value.OnMessageSent -= OnPlayerMessageSent; value.Cleanup(); if (MessagesApp.ActiveConversations.Contains((MSGConversation)(object)value)) { MessagesApp.ActiveConversations.Remove((MSGConversation)(object)value); } _playerConversations.Remove(playerId); } _activeConversations.Remove(playerId); ModLogger.Debug($"Removed player conversation for {playerId}"); } catch (Exception ex) { ModLogger.Error("Error removing player conversation: " + ex.Message, ex); } } [HideFromIl2Cpp] private void UpdateExistingConversation(PlayerMSGConversation msgConversation, PlayerConversation conversation) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) try { int valueOrDefault = (msgConversation.PlayerConversation?.Messages?.Count).GetValueOrDefault(); PlayerConversation playerConversation = msgConversation.PlayerConversation; _activeConversations[conversation.OtherPlayer.SteamId] = conversation; msgConversation.PlayerConversation = conversation; int num = conversation.Messages?.Count ?? 0; if (num <= valueOrDefault) { return; } ModLogger.Debug($"Conversation for {conversation.OtherPlayer.DisplayName} has new messages ({valueOrDefault} -> {num}), adding new messages"); HashSet<string> hashSet = new HashSet<string>(); if (playerConversation?.Messages != null) { foreach (PlayerMessage message in playerConversation.Messages) { hashSet.Add(message.MessageId); } } ModLogger.Debug("Reloading all messages for " + conversation.OtherPlayer.DisplayName + " to maintain chronological order"); msgConversation.LoadExistingMessages(clearExisting: true); ModLogger.Debug("Reloaded all messages in chronological order"); } catch (Exception ex) { ModLogger.Error("Error updating existing conversation: " + ex.Message, ex); try { ModLogger.Debug("Falling back to full message reload due to error"); msgConversation.LoadExistingMessages(); } catch (Exception ex2) { ModLogger.Error("Fallback reload also failed: " + ex2.Message, ex2); } } } [HideFromIl2Cpp] public void AddReceivedMessage(PlayerConversation conversation, PlayerMessage message) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (conversation?.OtherPlayer == null || message == null) { ModLogger.Warning("AddReceivedMessage called with null conversation or message"); return; } try { CSteamID steamId = conversation.OtherPlayer.SteamId; ModLogger.Debug($"PlayerConversationManager.AddReceivedMessage: Adding message from {conversation.OtherPlayer.DisplayName} (ID: {steamId})"); if (!_playerConversations.ContainsKey(steamId)) { ModLogger.Debug($"Player conversation doesn't exist for {steamId}, creating it"); AddPlayerConversation(conversation); } if (_playerConversations.TryGetValue(steamId, out var value)) { ModLogger.Debug($"Found MSGConversation for {steamId}, calling AddReceivedMessage"); value.AddReceivedMessage(message); ModLogger.Debug("Successfully added received message from " + conversation.OtherPlayer.DisplayName + ": " + message.Content); } else { ModLogger.Error($"Could not find MSGConversation for player {steamId} after creation attempt"); } } catch (Exception ex) { ModLogger.Error("Error adding received message: " + ex.Message, ex); } } [HideFromIl2Cpp] public void UpdateConversationList(IEnumerable<PlayerConversation> conversations) { try { foreach (PlayerConversation conversation in conversations) { AddPlayerConversation(conversation); } if ((Object)(object)_messagesApp != (Object)null) { _messagesApp.RepositionEntries(); } } catch (Exception ex) { ModLogger.Error("Error updating conversation list: " + ex.Message, ex); } } [HideFromIl2Cpp] private void OnPlayerMessageSent(object? sender, string messageContent) { //IL_0037: 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_00cc: Unknown result type (might be due to invalid IL or missing references) try { if (!(sender is PlayerMSGConversation playerMSGConversation) || playerMSGConversation.PlayerConversation?.OtherPlayer == null) { return; } CSteamID steamId = playerMSGConversation.PlayerConversation.OtherPlayer.SteamId; Core instance = Core.Instance; if (instance != null) { object obj = ((object)instance).GetType().GetField("_messagingService", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(instance); if (obj != null) { MethodInfo method = obj.GetType().GetMethod("SendMessage", new Type[2] { typeof(CSteamID), typeof(string) }); if (method != null) { method.Invoke(obj, new object[2] { steamId, messageContent }); ModLogger.Debug("Successfully sent message via messaging service"); } else { ModLogger.Warning("SendMessage method not found on messaging service"); } } } ModLogger.Debug("Player sent message to " + playerMSGConversation.PlayerConversation.OtherPlayer.DisplayName + ": " + messageContent); } catch (Exception ex) { ModLogger.Error("Error handling sent message: " + ex.Message, ex); } } public PlayerMSGConversation? GetPlayerMSGConversation(CSteamID playerId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) _playerConversations.TryGetValue(playerId, out var value); return value; } public void RefreshAllConversations() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) try { ModLogger.Debug($"Refreshing all conversations - {_activeConversations.Count} active conversations"); foreach (KeyValuePair<CSteamID, PlayerConversation> item in _activeConversations.ToList()) { CSteamID key = item.Key; PlayerConversation value = item.Value; if (_playerConversations.TryGetValue(key, out var value2)) { ModLogger.Debug($"Refreshing conversation with {value.OtherPlayer.DisplayName} ({value.Messages.Count} messages)"); value2.PlayerConversation = value; if (value.Messages.Count > 0) { value2.LoadExistingMessages(); } } else { ModLogger.Warning("No MSGConversation found for " + value.OtherPlayer.DisplayName + ", recreating"); AddPlayerConversation(value); } } ModLogger.Debug("Conversation refresh completed"); } catch (Exception ex) { ModLogger.Error("Error refreshing conversations: " + ex.Message, ex); } } public void Cleanup() { try { foreach (KeyValuePair<CSteamID, PlayerMSGConversation> playerConversation in _playerConversations) { PlayerMSGConversation value = playerConversation.Value; value.OnMessageSent -= OnPlayerMessageSent; value.Cleanup(); if (MessagesApp.ActiveConversations.Contains((MSGConversation)(object)value)) { MessagesApp.ActiveConversations.Remove((MSGConversation)(object)value); } } _playerConversations.Clear(); _activeConversations.Clear(); Instance = null; ModLogger.Debug("PlayerConversationManager cleaned up successfully"); } catch (Exception ex) { ModLogger.Error("Error during cleanup: " + ex.Message, ex); } } private void OnDestroy() { Cleanup(); } } } namespace TextYourFriends.Services { public interface IPlayerMessagePersistenceService : IDisposable { bool IsInitialized { get; } bool HasUnsavedChanges { get; } event EventHandler<TextYourFriendsSaveData>? OnDataLoaded; event EventHandler<bool>? OnDataSaved; event EventHandler? OnDataChanged; bool Initialize(); void SaveConversation(PlayerConversation conversation); void SaveConversations(IEnumerable<PlayerConversation> conversations); List<PlayerConversation> LoadConversations(IPlayerMessagingService messagingService); bool SaveToDisk(); bool LoadData(); void ClearAllData(); void CleanupInvalidConversations(); Dictionary<string, object> GetSaveStatistics(); TextYourFriendsSaveData GetCurrentSaveData(); void LoadFromExternalData(TextYourFriendsSaveData externalData); void LoadSaveData(TextYourFriendsSaveData saveData); void MarkAsSaved(); } public interface IPlayerMessagingService : IDisposable { bool IsInitialized { get; } bool IsMessagingAvailable { get; } IReadOnlyList<PlayerConversation> Conversations { get; } int UnreadConversationCount { get; } int TotalUnreadMessageCount { get; } event EventHandler<PlayerMessageEventArgs>? OnMessageReceived; event EventHandler<PlayerMessageEventArgs>? OnMessageSent; event EventHandler<MessageReadEventArgs>? OnMessageRead; event EventHandler<PlayerConversation>? OnConversationUpdated; bool Initialize(); bool SendMessage(CSteamID recipientId, string content); void MarkMessageAsRead(string messageId, CSteamID senderId); PlayerConversation? GetConversation(CSteamID playerId); PlayerConversation GetOrCreateConversation(CSteamID playerId); void MarkConversationAsRead(CSteamID playerId); List<PlayerConversation> GetConversationsWithUnreadMessages(); (bool IsValid, string ErrorMessage) ValidateMessage(string content, CSteamID recipientId); void Update(); void ClearAllConversations(); Dictionary<string, object> GetStatistics(); List<PlayerConversation> GetAllConversations(); void AddLoadedConversation(PlayerConversation conversation); } public interface ISteamLobbyService : IDisposable { SteamNetworkClient NetworkClient { get; } bool IsInitialized { get; } bool IsInLobby { get; } bool IsHost { get; } CSteamID LocalPlayerId { get; } int PlayerCount { get; } event EventHandler<PlayerJoinedEventArgs>? OnPlayerJoined; event EventHandler<PlayerLeftEventArgs>? OnPlayerLeft; event EventHandler<LobbyStateChangedEventArgs>? OnLobbyStateChanged; event EventHandler<string>? OnLobbyChatMessage; bool Initialize(); List<PlayerInfo> GetLobbyPlayers(); PlayerInfo? GetPlayer(CSteamID steamId); bool SendLobbyMessage(string message); void SetModLobbyData(string key, string value); string? GetModLobbyData(string key); void Update(); bool IsSteamInitialized(); void TriggerLobbyChatMessage(string message); } internal sealed class PlayerConversationAvatarService { private readonly PlayerInfo _playerInfo; public PlayerConversationAvatarService(PlayerInfo playerInfo) { _playerInfo = playerInfo; } public Sprite ResolvePlayerMugshot() { //IL_0017: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) try { CSteamID? val = _playerInfo?.SteamId; CSteamID nil = CSteamID.Nil; if (!val.HasValue || val.GetValueOrDefault() != nil) { Sprite val2 = SteamAvatarLoader.LoadSteamAvatar(_playerInfo.SteamId); if ((Object)(object)val2 != (Object)null) { return val2; } } return ResolveFallbackSprite(); } catch (Exception ex) { ModLogger.Error("Error loading player mugshot: " + ex.Message, ex); return ResolveFallbackSprite(); } } public Sprite ResolveForSenderFallback() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) Sprite val = ResolvePlayerMugshot(); if ((Object)(object)val != (Object)null) { return val; } Texture2D val2 = new Texture2D(64, 64); Color32[] array = (Color32[])(object)new Color32[4096]; for (int i = 0; i < array.Length; i++) { array[i] = new Color32((byte)128, (byte)128, (byte)128, byte.MaxValue); } val2.SetPixels32(Il2CppStructArray<Color32>.op_Implicit(array)); val2.Apply(); return Sprite.Create(val2, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f)); } private static Sprite ResolveFallbackSprite() { MessagesApp instance = PlayerSingleton<MessagesApp>.Instance; Sprite val = ((instance != null) ? instance.BlankAvatarSprite : null); if ((Object)(object)val != (Object)null) { return val; } return Resources.Load<Sprite>("UI/BlankAvatar"); } } internal sealed class PlayerConversationMessageService { private readonly int _maxMessageHistory; public PlayerConversationMessageService(int maxMessageHistory) { _maxMessageHistory = maxMessageHistory; } public bool CanSend(string value, int maxMessageLength) { return !string.IsNullOrWhiteSpace(value) && value.Length <= maxMessageLength; } public string? NormalizeOutgoingText(string rawText) { if (string.IsNullOrWhiteSpace(rawText)) { return null; } return rawText.Trim(); } public bool ContainsMessage(List<Message> messageHistory, string content, ESenderType senderType) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) return messageHistory.Any((Message m) => m.text == content && m.sender == senderType); } public bool ContainsMessage(List<Message> messageHistory, string content, ESenderType senderType) { //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) for (int i = 0; i < messageHistory.Count; i++) { Message val = messageHistory[i]; if (val.text == content && val.sender == senderType) { return true; } } return false; } public void TrimHistory(List<Message> messageHistory) { while (messageHistory.Count > _maxMessageHistory) { messageHistory.RemoveAt(0); } } public void TrimHistory(List<Message> messageHistory) { while (messageHistory.Count > _maxMessageHistory) { messageHistory.RemoveAt(0); } } public HashSet<string> BuildRenderedSet(List<Message> messageHistory) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(); foreach (Message item in messageHistory) { hashSet.Add(BuildUniqueKey(item.text, item.sender)); } return hashSet; } public HashSet<string> BuildRenderedSet(List<Message> messageHistory) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(); Enumerator<Message> enumerator = messageHistory.GetEnumerator(); while (enumerator.MoveNext()) { Message current = enumerator.Current; hashSet.Add(BuildUniqueKey(current.text, current.sender)); } return hashSet; } public string BuildUniqueKey(string content, ESenderType senderType) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return $"{content}_{senderType}"; } } public class PlayerMessagePersistenceService : IPlayerMessagePersistenceService, IDisposable { private TextYourFriendsSaveData _currentSaveData; private bool _hasUnsavedChanges; private bool _isInitialized; private bool _isDisposed; public bool IsInitialized => _isInitialized && !_isDisposed; public bool HasUnsavedChanges => _hasUnsavedChanges; public event EventHandler<TextYourFriendsSaveData>? OnDataLoaded; public event EventHandler<bool>? OnDataSaved; public event EventHandler? OnDataChanged; public PlayerMessagePersistenceService() { TextYourFriendsSaveData textYourFriendsSaveData = TextYourFriendsSave.Instance?.GetData(); _currentSaveData = ((textYourFriendsSaveData != null && textYourFriendsSaveData.IsValid()) ? textYourFriendsSaveData : TextYourFriendsSaveData.CreateDefault()); } public bool Initialize() { if (_isInitialized || _isDisposed) { return _isInitialized; } try { ModLogger.Debug("Initializing PlayerMessagePersistenceService (game save integration mode)..."); CleanupInvalidConversations(); _isInitialized = true; ModLogger.Debug("PlayerMessagePersistenceService initialized successfully"); return true; } catch (Exception exception) { ModLogger.Error("Failed to initialize PlayerMessagePersistenceService", exception); return false; } } public void SaveConversation(PlayerConversation conversation) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized || conversation?.OtherPlayer == null) { return; } try { string playerId = conversation.OtherPlayer.SteamId.m_SteamID.ToString(); int num = _currentSaveData.Conversations.FindIndex((PlayerConversationData c) => c.OtherPlayerId == playerId); PlayerConversationData playerConversationData = PlayerConversationData.FromPlayerConversation(conversation); if (num >= 0) { _currentSaveData.Conversations[num] = playerConversationData; } else { _currentSaveData.Conversations.Add(playerConversationData); } _hasUnsavedChanges = true; SyncToSaveable(); this.OnDataChanged?.Invoke(this, EventArgs.Empty); ModLogger.Debug("Marked conversation with " + conversation.OtherPlayer.DisplayName + " for saving"); } catch (Exception ex) { ModLogger.Error("Error saving conversation: " + ex.Message, ex); } } public void SaveConversations(IEnumerable<PlayerConversation> conversations) { if (!IsInitialized || conversations == null) { return; } foreach (PlayerConversation conversation in conversations) { SaveConversation(conversation); } } public List<PlayerConversation> LoadConversations(IPlayerMessagingService messagingService) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized || messagingService == null) { return new List<PlayerConversation>(); } List<PlayerConversation> list = new List<PlayerConversation>(); try { CSteamID val = default(CSteamID); foreach (PlayerConversationData conversation in _currentSaveData.Conversations) { if (!conversation.IsValid()) { ModLogger.Warning("Invalid conversation data for player " + conversation.OtherPlayerId); continue; } if (!ulong.TryParse(conversation.OtherPlayerId, out var result)) { ModLogger.Warning("Invalid player ID: " + conversation.OtherPlayerId); continue; } ((CSteamID)(ref val))..ctor(result); if (val == SteamUser.GetSteamID()) { ModLogger.Warning("Skipping invalid conversation with local player: " + conversation.OtherPlayerName); continue; } PlayerInfo otherPlayer = new PlayerInfo(val, conversation.OtherPlayerName); PlayerConversation playerConversation = conversation.ToPlayerConversation(otherPlayer); list.Add(playerConversation); ModLogger.Debug($"Loaded conversation with {playerConversation.OtherPlayer.DisplayName} ({playerConversation.Messages.Count} messages)"); } ModLogger.Debug($"Loaded {list.Count} conversations from save data"); } catch (Exception exception) { ModLogger.Error("Error loading conversations", exception); } return list; } public bool SaveToDisk() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized) { return false; } try { ModLogger.Debug("Marking TextYourFriends data for save (handled by game save system)..."); _currentSaveData.LastSaved = DateTime.UtcNow.ToString("O"); if (SteamAPI.IsSteamRunning()) { _currentSaveData.LocalPlayerId = SteamUser.GetSteamID().m_SteamID.ToString(); } TextYourFriendsSave.Instance?.SetData(_currentSaveData); Saveable.RequestGameSave(false); _hasUnsavedChanges = false; this.OnDataSaved?.Invoke(this, e: true); return true; } catch (Exception exception) { ModLogger.Error("Failed to mark TextYourFriends data for save", exception); this.OnDataSaved?.Invoke(this, e: false); return false; } } public bool LoadData() { try { ModLogger.Debug("LoadData called - using default data (loading handled by game save system)"); if (_currentSaveData == null || !_currentSaveData.IsValid()) { _currentSaveData = TextYourFriendsSaveData.CreateDefault(); } _hasUnsavedChanges = false; this.OnDataLoaded?.Invoke(this, _currentSaveData); return true; } catch (Exception exception) { ModLogger.Error("Failed to initialize default data", exception); _currentSaveData = TextYourFriendsSaveData.CreateDefault(); this.OnDataLoaded?.Invoke(this, _currentSaveData); return false; } } public void ClearAllData() { if (!IsInitialized) { return; } try { _currentSaveData.Conversations.Clear(); _hasUnsavedChanges = true; this.OnDataChanged?.Invoke(this, EventArgs.Empty); ModLogger.Debug("Cleared all conversation data"); } catch (Exception exception) { ModLogger.Error("Error clearing conversation data", exception); } } public void CleanupInvalidConversations() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized) { return; } try { string localPlayerId = SteamUser.GetSteamID().m_SteamID.ToString(); int count = _currentSaveData.Conversations.Count; _currentSaveData.Conversations.RemoveAll((PlayerConversationData c) => c.OtherPlayerId == localPlayerId || !c.IsValid() || string.IsNullOrEmpty(c.OtherPlayerId) || c.OtherPlayerId == "0"); int num = count - _currentSaveData.Conversations.Count; if (num > 0) { _hasUnsavedChanges = true; ModLogger.Debug($"Cleaned up {num} invalid conversations"); } } catch (Exception exception) { ModLogger.Error("Error cleaning up invalid conversations", exception); } } private void SyncToSaveable() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) try { _currentSaveData.LastSaved = DateTime.UtcNow.ToString("O"); if (SteamAPI.IsSteamRunning()) { _currentSaveData.LocalPlayerId = SteamUser.GetSteamID().m_SteamID.ToString(); } TextYourFriendsSave.Instance?.SetData(_currentSaveData); } catch (Exception ex) { ModLogger.Error("Failed to sync to Saveable: " + ex.Message, ex); } } public void MarkAsSaved() { _hasUnsavedChanges = false; ModLogger.Debug("Marked TextYourFriends data as saved"); } public TextYourFriendsSaveData GetCurrentSaveData() { return _currentSaveData ?? TextYourFriendsSaveData.CreateDefault(); } public Dictionary<string, object> GetSaveStatistics() { if (!IsInitialized) { return new Dictionary<string, object>(); } return new Dictionary<string, object> { ["SavedConversations"] = _currentSaveData.Conversations.Count, ["TotalMessages"] = _currentSaveData.Conversations.Sum((PlayerConversationData c) => c.Messages.Count), ["LastSaved"] = _currentSaveData.LastSaved, ["HasUnsavedChanges"] = _hasUnsavedChanges, ["SaveMode"] = "GameSaveIntegration", ["SchemaVersion"] = _currentSaveData.SchemaVersion }; } public void LoadFromExternalData(TextYourFriendsSaveData saveData) { try { if (saveData?.Conversations == null) { ModLogger.Warning("Cannot load from null or invalid save data"); return; } ModLogger.Debug($"Loading {saveData.Conversations.Count} conversations from external save data"); _currentSaveData = saveData; CleanupInvalidConversations(); _hasUnsavedChanges = false; ModLogger.Debug($"Successfully loaded {_currentSaveData.Conversations.Count} conversations from external data"); this.OnDataLoaded?.Invoke(this, saveData); } catch (Exception exception) { ModLogger.Error("Failed to load from external save data", exception); } } public void LoadSaveData(TextYourFriendsSaveData saveData) { LoadFromExternalData(saveData); } public void Dispose() { if (_isDisposed) { return; } try { ModLogger.Debug("Disposing PlayerMessagePersistenceService..."); if (_hasUnsavedChanges) { ModLogger.Debug("Marking unsaved changes for game save system before disposal..."); this.OnDataChanged?.Invoke(this, EventArgs.Empty); } _isInitialized = false; _isDisposed = true; ModLogger.Debug("PlayerMessagePersistenceService disposed"); } catch (Exception exception) { ModLogger.Error("Error disposing persistence service", exception); } } } public class PlayerMessagingService : IPlayerMessagingService, IDisposable { private readonly ISteamLobbyService _lobbyService; private readonly IPlayerMessagePersistenceService _persistenceService; private bool _isInitialized = false; private bool _isDisposed = false; private readonly Dictionary<CSteamID, PlayerConversation> _conversations = new Dictionary<CSteamID, PlayerConversation>(); private DateTime _lastMessageSent = DateTime.MinValue; private int _totalMessagesSent = 0; private int _totalMessagesReceived = 0; public bool IsInitialized => _isInitialized && !_isDisposed; public bool IsMessagingAvailable => IsInitialized && _lobbyService.IsInLobby && _lobbyService.PlayerCount > 1; public IReadOnlyList<PlayerConversation> Conversations => _conversations.Values.ToList(); public int UnreadConversationCount => _conversations.Values.Count((PlayerConversation c) => c.HasUnreadMessages); public int TotalUnreadMessageCount => _conversations.Values.Sum((PlayerConversation c) => c.UnreadCount); public event EventHandler<PlayerMessageEventArgs>? OnMessageReceived; public event EventHandler<PlayerMessageEventArgs>? OnMessageSent; public event EventHandler<MessageReadEventArgs>? OnMessageRead; public event EventHandler<PlayerConversation>? OnConversationUpdated; public PlayerMessagingService(ISteamLobbyService lobbyService, IPlayerMessagePersistenceService persistenceService) { _lobbyService = lobbyService ?? throw new ArgumentNullException("lobbyService"); _persistenceService = persistenceService ?? throw new ArgumentNullException("persistenceService"); } public bool Initialize() { if (_isInitialized || _isDisposed) { return _isInitialized; } try { if (!_lobbyService.IsInitialized && !_lobbyService.Initialize()) { ModLogger.Error("Failed to initialize Steam lobby service"); return false; } if (!_persistenceService.Initialize()) { ModLogger.Error("Failed to initialize persistence service"); return false; } _lobbyService.OnPlayerJoined += OnPlayerJoined; _lobbyService.OnPlayerLeft += OnPlayerLeft; _lobbyService.OnLobbyStateChanged += OnLobbyStateChanged; _lobbyService.OnLobbyChatMessage += OnLobbyChatMessage; _persistenceService.OnDataLoaded += OnPersistenceDataLoaded; _persistenceService.OnDataSaved += OnPersistenceDataSaved; _isInitialized = true; ModLogger.Debug("Player messaging service initialized successfully"); LoadSavedConversations(); CreateConversationsForExistingPlayers(); return true; } catch (Exception exception) { ModLogger.Error("Failed to initialize player messaging service", exception); return false; } } public bool SendMessage(CSteamID recipientId, string content) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) ModLogger.Debug($"PlayerMessagingService.SendMessage: Entry - recipientId={recipientId.m_SteamID}, contentLength={content?.Length ?? 0}"); (bool, string) tuple = ValidateMessage(content, recipientId); if (!tuple.Item1) { ModLogger.Warning("Message validation failed: " + tuple.Item2); ModLogger.Debug("PlayerMessagingService.SendMessage: Validation failed - " + tuple.Item2); return false; } DateTime utcNow = DateTime.UtcNow; if ((utcNow - _lastMessageSent).TotalMilliseconds < 100.0) { ModLogger.Warning("Message send cooldown active"); ModLogger.Debug($"PlayerMessagingService.SendMessage: Cooldown active, {(utcNow - _lastMessageSent).TotalMilliseconds}ms since last send"); return false; } try { string personaName = SteamFriends.GetPersonaName(); PlayerMessage playerMessage = new PlayerMessage(_lobbyService.LocalPlayerId, recipientId, personaName, content) { IsFromLocalPlayer = true }; ModLogger.Debug($"PlayerMessagingService.SendMessage: Created PlayerMessage id={playerMessage.MessageId}, sender={personaName}, recipient={recipientId.m_SteamID}"); string text = MessageJsonSerializer.CreateLobbyMessage(playerMessage); ModLogger.Debug($"PlayerMessagingService.SendMessage: Lobby message created, length={text?.Length ?? 0}, calling SendLobbyMessage"); if (!_lobbyService.SendLobbyMessage(text)) { ModLogger.Error("Failed to send lobby message - running diagnostics..."); return false; } PlayerConversation orCreateConversation = GetOrCreateConversation(recipientId); orCreateConversation.AddMessage(playerMessage); _persistenceService.SaveConversation(orCreateConversation); _lastMessageSent = utcNow; _totalMessagesSent++; ModLogger.Debug($"PlayerMessagingService.SendMessage: Success - message sent, totalSent={_totalMessagesSent}"); this.OnMessageSent?.Invoke(this, new PlayerMessageEventArgs(playerMessage, orCreateConversation, isOutgoing: true)); this.OnConversationUpdated?.Invoke(this, orCreateConversation); return true; } catch (Exception exception) { ModLogger.Error("Failed to send message", exception); return false; } } public void MarkMessageAsRead(string messageId, CSteamID senderId) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) if (!IsMessagingAvailable || string.IsNullOrEmpty(messageId)) { return; } try { PlayerConversation conversation = GetConversation(senderId); if (conversation == null) { return; } PlayerMessage playerMessage = conversation.Messages.FirstOrDefault((PlayerMessage m) => m.MessageId == messageId); if (playerMessage != null && !playerMessage.IsRead) { playerMessage.MarkAsRead(); PlayerMessage message = PlayerMessage.CreateReadReceipt(_lobbyService.LocalPlayerId, senderId, messageId); string message2 = MessageJsonSerializer.CreateLobbyMessage(message); ModLogger.Debug($"PlayerMessagingService.MarkMessageAsRead: Sending read receipt for messageId={messageId} to sender={senderId.m_SteamID}"); _lobbyService.SendLobbyMessage(message2); PlayerInfo player = _lobbyService.GetPlayer(_lobbyService.LocalPlayerId); if (player != null) { this.OnMessageRead?.Invoke(this, new MessageReadEventArgs(messageId, player, conversation)); } this.OnConversationUpdated?.Invoke(this, conversation); } } catch (Exception exception) { ModLogger.Error("Failed to mark message as read", exception); } } public PlayerConversation? GetConversation(CSteamID playerId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) PlayerConversation value; return _conversations.TryGetValue(playerId, out value) ? value : null; } public PlayerConversation GetOrCreateConversation(CSteamID playerId) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (playerId == _lobbyService.LocalPlayerId) { ModLogger.Error($"Attempted to create conversation with local player {playerId}. This should not happen!"); throw new InvalidOperationException("Cannot create conversation with local player"); } if (_conversations.TryGetValue(playerId, out var value)) { return value; } PlayerInfo playerInfo = _lobbyService.GetPlayer(playerId); if (playerInfo == null) { string friendPersonaName = SteamFriends.GetFriendPersonaName(playerId); playerInfo = new PlayerInfo(playerId, friendPersonaName); } PlayerConversation playerConversation = new PlayerConversation(playerInfo); _conversations[playerId] = playerConversation; _persistenceService.SaveConversation(playerConversation); this.OnConversationUpdated?.Invoke(this, playerConversation); return playerConversation; } public void MarkConversationAsRead(CSteamID playerId) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) PlayerConversation conversation = GetConversation(playerId); if (conversation == null) { return; } List<PlayerMessage> list = conversation.Messages.Where((PlayerMessage m) => !m.IsRead && !m.IsFromLocalPlayer).ToList(); foreach (PlayerMessage item in list) { MarkMessageAsRead(item.MessageId, playerId); } } public List<PlayerConversation> GetConversationsWithUnreadMessages() { return _conversations.Values.Where((PlayerConversation c) => c.HasUnreadMessages).ToList(); } public (bool IsValid, string ErrorMessage) ValidateMessage(string content, CSteamID recipientId) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (!IsMessagingAvailable) { return (false, "Player is not in a lobby"); } if (string.IsNullOrWhiteSpace(content)) { return (false, "Message cannot be empty"); } if (content.Length > 500) { return (false, "Message exceeds maximum length"); } if (recipientId == CSteamID.Nil) { return (false, "Invalid recipient"); } if (recipientId == _lobbyService.LocalPlayerId) { return (false, "Cannot send message to yourself"); } PlayerInfo player = _lobbyService.GetPlayer(recipientId); if (player == null) { return (false, "Target player not found in lobby"); } return (true, string.Empty); } public void Update() { if (!IsInitialized) { return; } try { _lobbyService.Update(); } catch (Exception exception) { ModLogger.Error("Error updating messaging service", exception); } } public void ClearAllConversations() { _conversations.Clear(); ModLogger.Debug("All conversations cleared"); } public Dictionary<string, object> GetStatistics() { Dictionary<string, object> dictionary = new Dictionary<string, object> { ["TotalMessagesSent"] = _totalMessagesSent, ["TotalMessagesReceived"] = _totalMessagesReceived, ["ActiveConversations"] = _conversations.Count, ["UnreadConversations"] = UnreadConversationCount, ["TotalUnreadMessages"] = TotalUnreadMessageCount, ["IsInLobby"] = _lobbyService.IsInLobby, ["PlayerCount"] = _lobbyService.PlayerCount }; if (_persistenceService != null) { Dictionary<string, object> saveStatistics = _persistenceService.GetSaveStatistics(); foreach (KeyValuePair<string, object> item in saveStatistics) { dictionary["Persistence_" + item.Key] = item.Value; } } return dictionary; } public List<PlayerConversation> GetAllConversations() { return _conversations.Values.ToList(); } public bool ForceSave() { if (!IsInitialized || _persistenceService == null) { return false; } try { _persistenceService.SaveConversations(_conversations.Values); return _persistenceService.SaveToDisk(); } catch (Exception exception) { ModLogger.Error("Error forcing save", exception); return false; } } public IPlayerMessagePersistenceService? GetPersistenceService() { return _persistenceService; } private void OnPlayerJoined(object? sender, PlayerJoinedEventArgs e) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) ModLogger.Debug("Player joined lobby: " + e.Player.DisplayName); if (!e.Player.IsLocalPlayer) { GetOrCreateConversation(e.Player.SteamId); } } private void OnPlayerLeft(object? sender, PlayerLeftEventArgs e) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) ModLogger.Debug("Player left lobby: " + e.Player.DisplayName); PlayerConversation conversation = GetConversation(e.Player.SteamId); if (conversation != null) { conversation.UpdatePlayerStatus(isOnline: false); this.OnConversationUpdated?.Invoke(this, conversation); } } private void OnLobbyStateChanged(object? sender, LobbyStateChangedEventArgs e) { if (!e.IsInLobby) { ClearAllConversations(); } else { CreateConversationsForExistingPlayers(); } } private void OnLobbyChatMessage(object? sender, string lobbyMessage) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) try { ModLogger.Debug($"PlayerMessagingService.OnLobbyChatMessage: Received lobby message, length={lobbyMessage?.Length ?? 0}, isTYF={MessageJsonSerializer.IsTextYourFriendsMessage(lobbyMessage)}"); if (!MessageJsonSerializer.IsTextYourFriendsMessage(lobbyMessage)) { ModLogger.Debug("PlayerMessagingService.OnLobbyChatMessage: Not a TextYourFriends message, ignoring"); return; } PlayerMessage playerMessage = MessageJsonSerializer.TryParseFromLobbyMessage(lobbyMessage); if (playerMessage == null) { ModLogger.Warning("Failed to parse TextYourFriends message"); ModLogger.Debug("PlayerMessagingService.OnLobbyChatMessage: Parse failed for: " + lobbyMessage?.Substring(0, Math.Min(150, lobbyMessage?.Length ?? 0)) + "..."); return; } ModLogger.Debug($"PlayerMessagingService.OnLobbyChatMessage: Parsed message - sender={playerMessage.SenderId.m_SteamID}, recipient={playerMessage.RecipientId.m_SteamID}, type={playerMessage.MessageType}, isText={playerMessage.IsTextMessage}, isReadReceipt={playerMessage.IsReadReceipt}"); if (playerMessage.SenderId == _lobbyService.LocalPlayerId) { ModLogger.Debug("PlayerMessagingService.OnLobbyChatMessage: Ignoring message from self"); return; } if (playerMessage.RecipientId != _lobbyService.LocalPlayerId && playerMessage.RecipientId != CSteamID.Nil) { ModLogger.Debug($"PlayerMessagingService.OnLobbyChatMessage: Message not for us (recipient={playerMessage.RecipientId.m_SteamID}, local={_lobbyService.LocalPlayerId.m_SteamID}), ignoring"); return; } ModLogger.Debug("Processing received message from " + playerMessage.SenderName + ": " + playerMessage.Content); HandleReceivedMessage(playerMessage); } catch (Exception exception) { ModLogger.Error("Error processing lobby chat message", exception); } } private void CreateConversationsForExistingPlayers() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (!_lobbyService.IsInLobby) { return; } List<PlayerInfo> lobbyPlayers = _lobbyService.GetLobbyPlayers(); ModLogger.Debug($"Creating conversations for {lobbyPlayers.Count} existing lobby players"); foreach (PlayerInfo item in lobbyPlayers) { if (!item.IsLocalPlayer) { GetOrCreateConversation(item.SteamId); } } ModLogger.Debug($"Created {_conversations.Count} conversations for existing players"); } private void HandleReceivedMessage(PlayerMessage message) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) ModLogger.Debug($"PlayerMessagingService.HandleReceivedMessage: Entry - sender={message.SenderId.m_SteamID}, isText={message.IsTextMessage}, isReadReceipt={message.IsReadReceipt}"); PlayerConversation orCreateConversation = GetOrCreateConversation(message.SenderId); if (message.IsTextMessage) { orCreateConversation.AddMessage(message); _totalMessagesReceived++; ModLogger.Debug($"PlayerMessagingService.HandleReceivedMessage: Added text message, totalReceived={_totalMessagesReceived}, conversationMessages={orCreateConversation.Messages.Count}"); _persistenceService.SaveConversation(orCreateConversation); this.OnMessageReceived?.Invoke(this, new PlayerMessageEventArgs(message, orCreateConversation, isOutgoing: false)); this.OnConversationUpdated?.Invoke(this, orCreateConversation); } else if (message.IsReadReceipt) { string messageId = message.Content; ModLogger.Debug("PlayerMessagingService.HandleReceivedMessage: Processing read receipt for messageId=" + messageId); PlayerMessage playerMessage = orCreateConversation.Messages.FirstOrDefault((PlayerMessage m) => m.MessageId == messageId); if (playerMessage != null && !playerMessage.IsRead) { playerMessage.MarkAsRead(); ModLogger.Debug("PlayerMessagingService.HandleReceivedMessage: Marked message " + messageId + " as read"); this.OnMessageRead?.Invoke(this, new MessageReadEventArgs(messageId, orCreateConversation.OtherPlayer, orCreateConversation)); } else { ModLogger.Debug("PlayerMessagingService.HandleReceivedMessage: Read receipt - message not found or already read (messageId=" + messageId + ")"); } } } private void LoadSavedConversations() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) try { List<PlayerConversation> list = _persistenceService.LoadConversations(this); foreach (PlayerConversation item in list) { if (item?.OtherPlayer != null) { _conversations[item.OtherPlayer.SteamId] = item; } } ModLogger.Debug($"Loaded {list.Count} saved conversations"); } catch (Exception exception) { ModLogger.Error("Error loading saved conversations", exception); } } private void OnPersistenceDataLoaded(object? sender, TextYourFriendsSaveData saveData) { ModLogger.Debug($"Persistence data loaded - {saveData.Conversations.Count} conversations"); } private void OnPersistenceDataSaved(object? sender, bool success) { if (!success) { ModLogger.Warning("Failed to save persistence data"); } } public void AddLoadedConversation(PlayerConversation conversation) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) try { if (conversation?.OtherPlayer == null) { ModLogger.Warning("Cannot add null or invalid conversation"); return; } CSteamID steamId = conversation.OtherPlayer.SteamId; if (steamId == _lobbyService.LocalPlayerId) { ModLogger.Warning($"Skipping conversation with local player {steamId}"); return; } ModLogger.Debug($"Adding loaded conversation with {conversation.OtherPlayer.DisplayName} ({conversation.Messages.Count} messages)"); _conversations[steamId] = conversation; this.OnConversationUpdated?.Invoke(this, conversation); } catch (Exception exception) { ModLogger.Error("Error adding loaded conversation", exception); } } public void Dispose() { if (_isDisposed) { return; } try { if (_lobbyService != null) { _lobbyService.OnPlayerJoined -= OnPlayerJoined; _lobbyService.OnPlayerLeft -= OnPlayerLeft; _lobbyService.OnLobbyStateChanged -= OnLobbyStateChanged; _lobbyService.OnLobbyChatMessage -= OnLobbyChatMessage; } if (_persistenceService != null) { _persistenceService.OnDataLoaded -= OnPersistenceDataLoaded; _persistenceService.OnDataSaved -= OnPersistenceDataSaved; _persistenceService.SaveConversations(_conversations.Values); _persistenceService.Dispose(); } _conversations.Clear(); _isInitialized = false; _isDisposed = true; ModLogger.Debug("Player messaging service disposed"); } catch (Exception exception) { ModLogger.Error("Error disposing messaging service", exception); } } } public class SaveSynchronizationService : IDisposable { private class SaveRequestInfo { public string RequestId { get; set; } = string.Empty; public DateTime RequestTime { get; set; } public List<CSteamID> TargetPlayers { get; set; } = new List<CSteamID>(); public List<CSteamID> ResponsesReceived { get; set; } = new List<CSteamID>(); public int TimeoutSeconds { get; set; } = 30; public Action<Dictionary<string, TextYourFriendsSaveData>>? OnCompleted { get; set; } } private readonly ISteamLobbyService _lobbyService; private readonly IPlayerMessagingService _messagingService; private readonly IPlayerMessagePersistenceService _persistenceService; private SteamNetworkClient _networkClient; private bool _isInitialized = false; private bool _isDisposed = false; private TextYourFriendsGameData? _multiplayerSaveData; private readonly Dictionary<string, SaveRequestInfo> _pendingSaveRequests = new Dictionary<string, SaveRequestInfo>(); private readonly Dictionary<string, Dictionary<string, TextYourFriendsSaveData>> _collectedSaveData = new Dictionary<string, Dictionary<string, TextYourFriendsSaveData>>(); public bool IsInitialized => _isInitialized && !_isDisposed; public bool IsHost => _lobbyService?.IsHost ?? false; public event EventHandler<SaveSyncCompletedEventArgs>? OnSaveSyncCompleted; public event EventHandler<LoadSyncCompletedEventArgs>? OnLoadSyncCompleted; public SaveSynchronizationService(ISteamLobbyService lobbyService, IPlayerMessagingService messagingService, IPlayerMessagePersistenceService persistenceService) { _lobbyService = lobbyService ?? throw new ArgumentNullException("lobbyService"); _messagingService = messagingService ?? throw new ArgumentNullException("messagingService"); _persistenceService = persistenceService ?? throw new ArgumentNullException("persistenceService"); _networkClient = lobbyService.NetworkClient; } public bool Initialize() { if (_isInitialized || _isDisposed) { return _isInitialized; } try { ModLogger.Debug("Initializing SaveSynchronizationService..."); RegisterMessageHandlers(); _isInitialized = true; ModLogger.Debug("SaveSynchronizationService initialized successfully"); return true; } catch (Exception exception) { ModLogger.Error("Failed to initialize SaveSynchronizationService", exception); return false; } } private void RegisterMessageHandlers() { _networkClient.RegisterMessageHandler<SaveSyncRequestMessage>((Action<SaveSyncRequestMessage, CSteamID>)OnSaveRequestMessageReceived); _networkClient.RegisterMessageHandler<SaveSyncResponseMessage>((Action<SaveSyncResponseMessage, CSteamID>)OnSaveResponseMessageReceived); _networkClient.RegisterMessageHandler<SaveSyncDataMessage>((Action<SaveSyncDataMessage, CSteamID>)OnSaveDataMessageReceived); _networkClient.RegisterMessageHandler<SaveSyncLoadDataRequestMessage>((Action<SaveSyncLoadDataRequestMessage, CSteamID>)OnLoadDataRequestMessageReceived); _networkClient.RegisterMessageHandler<SaveSyncLoadDataResponseMessage>((Action<SaveSyncLoadDataResponseMessage, CSteamID>)OnLoadDataResponseMessageReceived); ModLogger.Debug("Registered SaveSync message handlers with SteamNetworkClient"); } public void ProcessMessages() { } public async Task<Dictionary<string, TextYourFriendsSaveData>> RequestSaveDataFromClients(int timeoutSeconds = 30) { if (!IsHost) { ModLogger.Warning("RequestSaveDataFromClients can only be called by the host"); return new Dictionary<string, TextYourFriendsSaveData>(); } if (!IsInitialized) { ModLogger.Error("SaveSynchronizationService not initialized"); return new Dictionary<string, TextYourFriendsSaveData>(); } try { ModLogger.Debug("Requesting save data from all connected clients..."); List<PlayerInfo> lobbyPlayers = _lobbyService.GetLobbyPlayers(); List<PlayerInfo> clientPlayers = lobbyPlayers.Where((PlayerInfo p) => p.SteamId != _lobbyService.LocalPlayerId).ToList(); if (clientPlayers.Count == 0) { ModLogger.Debug("No clients connected, skipping save data request"); return new Dictionary<string, TextYourFriendsSaveData>(); } string requestId = Guid.NewGuid().ToString(); SaveRequestInfo requestInfo = new SaveRequestInfo { RequestId = requestId, RequestTime = DateTime.UtcNow, TargetPlayers = clientPlayers.Select((PlayerInfo p) => p.SteamId).ToList(), TimeoutSeconds = timeoutSeconds }; _pendingSaveRequests[requestId] = requestInfo; _collectedSaveData[requestId] = new Dictionary<string, TextYourFriendsSaveData>(); SaveSyncRequestMessage requestMessage = new SaveSyncRequestMessage { RequestId = requestId, Operation = "SAVE", TimeoutSeconds = timeoutSeconds }; foreach (PlayerInfo client in clientPlayers) { ModLogger.Debug($"Sending save request to {client.DisplayName} ({client.SteamId})"); if (!(await _networkClient.SendMessageToPlayerAsync(client.SteamId, (P2PMessage)(object)requestMessage))) { ModLogger.Warning("Failed to send save request to " + client.DisplayName); } } DateTime startTime = DateTime.UtcNow; while ((DateTime.UtcNow - startTime).TotalSeconds < (double)timeoutSeconds) { SaveRequestInfo request = _pendingSaveRequests.GetValueOrDefault(requestId); if (request != null && request.ResponsesReceived.Count >= request.TargetPlayers.Count) { break; } await Task.Delay(100); } Dictionary<string, TextYourFriendsSaveData> results = _collectedSaveData.GetValueOrDefault(requestId) ?? new Dictionary<string, TextYourFriendsSaveData>(); _pendingSaveRequests.Remove(requestId); _collectedSaveData.Remove(requestId); ModLogger.Debug($"Save data collection completed. Received data from {results.Count}/{clientPlayers.Count} clients"); return results; } catch (Exception ex) { ModLogger.Error("Error requesting save data from clients", ex); return new Dictionary<string, TextYourFriendsSaveData>(); } } public void SetMultiplayerSaveData(TextYourFriendsGameData gameData) { if (!IsHost) { ModLogger.Warning("SetMultiplayerSaveData can only be called by the host"); return; } _multiplayerSaveData = gameData; ModLogger.Debug($"Stored multiplayer save data for {(gameData?.AllPlayersData?.Count).GetValueOrDefault()} players"); } public async Task<bool> RequestSavedDataFromHost(int timeoutSeconds = 10) { if (IsHost) { ModLogger.Warning("RequestSavedDataFromHost should only be called by clients"); return false; } if (!IsInitialized) { ModLogger.Error("SaveSynchronizationService not initialized"); return false; } try { string localPlayerId = _lobbyService.LocalPlayerId.m_SteamID.ToString(); ModLogger.Debug("Client " + localPlayerId + " requesting saved data from host..."); SaveSyncLoadDataRequestMessage requestMessage = new SaveSyncLoadDataRequestMessage { RequestingPlayerId = localPlayerId }; if (_lobbyService.IsHost) { ModLogger.Error("Client tried to request data from host, but we are the host!"); return false; } List<PlayerInfo> lobbyPlayers = _lobbyService.GetLobbyPlayers(); List<PlayerInfo> otherPlayers = lobbyPlayers.Where((PlayerInfo p) => p.SteamId != _lobbyService.LocalPlayerId).ToList(); if (otherPlayers.Count == 0) { ModLogger.Error("Cannot find any other players to request data from"); return false; } CSteamID hostSteamId = otherPlayers[0].SteamId; ModLogger.Debug($"Sending load data request to assumed host ({hostSteamId})"); if (!(await _networkClient.SendMessageToPlayerAsync(hostSteamId, (P2PMessage)(object)requestMessage))) { ModLogger.Error("Failed to send load data request to host"); return false; } await Task.Delay(timeoutSeconds * 1000); ModLogger.Debug("Load data request sent successfully"); return true; } catch (Exception ex) { ModLogger.Error("Error requesting saved data from host", ex); return false; } } public async Task<bool> SendConversationDataToClients(Dictionary<string, TextYourFriendsSaveData> playerData) { if (!IsHost) { ModLogger.Warning("SendConversationDataToClients can only be called by the host"); return false; } if (!IsInitialized) { ModLogger.Error("SaveSynchronizationService not initialized"); return false; } try { ModLogger.Debug($"Sending conversation data to {playerData.Count} clients..."); foreach (KeyV