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.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using S1API.Internal.Abstraction;
using S1API.Saveables;
using ScheduleOne;
using ScheduleOne.DevUtilities;
using ScheduleOne.Messaging;
using ScheduleOne.NPCs;
using ScheduleOne.Networking;
using ScheduleOne.Persistence.Datas;
using ScheduleOne.UI;
using ScheduleOne.UI.Phone;
using ScheduleOne.UI.Phone.Messages;
using SteamNetworkLib;
using SteamNetworkLib.Core;
using SteamNetworkLib.Events;
using SteamNetworkLib.Models;
using SteamNetworkLib.Utilities;
using Steamworks;
using TMPro;
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_Mono")]
[assembly: AssemblyConfiguration("Mono")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("TextYourFriends_Mono")]
[assembly: AssemblyTitle("TextYourFriends_Mono")]
[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_000a: Unknown result type (might be due to invalid IL or missing references)
Formatting val = (Formatting)(indented ? 1 : 0);
return JsonConvert.SerializeObject(value, 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 obj = new PlayerMessage
{
MessageId = (((object)val["messageId"])?.ToString() ?? Guid.NewGuid().ToString()),
Content = (((object)val["content"])?.ToString() ?? string.Empty),
SenderName = (((object)val["senderName"])?.ToString() ?? "Unknown"),
MessageType = (((object)val["messageType"])?.ToString() ?? "player_message")
};
JToken obj2 = val["shouldNotify"];
obj.ShouldNotify = obj2 == null || obj2.ToObject<bool>();
PlayerMessage playerMessage = obj;
if (ulong.TryParse(((object)val["senderId"])?.ToString(), out var result))
{
playerMessage.SenderId = new CSteamID(result);
}
if (ulong.TryParse(((object)val["recipientId"])?.ToString(), out var result2))
{
playerMessage.RecipientId = new CSteamID(result2);
}
string text = ((object)val["timestamp"])?.ToString();
if (DateTime.TryParse(text, null, DateTimeStyles.AdjustToUniversal, out var result3))
{
playerMessage.Timestamp = result3;
}
else
{
ModLogger.Warning("Failed to parse timestamp '" + text + "' for message " + playerMessage.MessageId + ", using current time");
playerMessage.Timestamp = DateTime.UtcNow;
}
playerMessage.IsFromLocalPlayer = playerMessage.SenderId == SteamUser.GetSteamID();
ModLogger.Debug($"MessageJsonSerializer.DeserializeMessage: Success - id={playerMessage.MessageId}, sender={playerMessage.SenderId.m_SteamID}, recipient={playerMessage.RecipientId.m_SteamID}, type={playerMessage.MessageType}");
return playerMessage;
}
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++)
{
PlayerMessage playerMessage = DeserializeMessage(((object)val[i])?.ToString() ?? 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_010d: 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_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00c1: Expected O, but got Unknown
//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: 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, 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(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
{
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 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);
}
}
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);
}
}
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);
}
}
}
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);
}
}
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);
}
}
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_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: 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(array);
val2.Apply();
return Sprite.Create(val2, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f));
}
private static Sprite ResolveFallbackSprite()
{
Sprite val = PlayerSingleton<MessagesApp>.Instance?.BlankAvatarSprite;
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 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 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 (KeyValuePair<string, TextYourFriendsSaveData> kvp in playerData)
{
if (ulong.TryParse(kvp.Key, out var steamId))
{
CSteamID targetSteamId = new CSteamID(steamId);
TextYourFriendsSaveData saveData = kvp.Value;
SaveSyncDataMessage dataMessage = new SaveSyncDataMessage
{
DataId = Guid.NewGuid().ToString(),
TargetPlayerId = kvp.Key,
ConversationData = saveData.GetJson(),
ConversationCount = saveData.Conversations.Count,
SavedAt = saveData.LastSaved
};
ModLogger.Debug($"Sending {saveData.Conversations.Count} conversations to player {kvp.Key}");
if (!(await _networkClient.SendMessageToPlayerAsync(targetSteamId, (P2PMessage)(object)dataMessage)))
{
ModLogger.Warning("Failed to send conversation data to player " + kvp.Key);
}
}
else
{
ModLogger.Warning("Invalid Steam ID in player data: " + kvp.Key);
}
}
return true;
}
catch (Exception ex)
{
ModLogger.Error("Error sending conversation data to clients", ex);
return false;
}
}
private void OnSaveRequestMessageReceived(SaveSyncRequestMessage request, CSteamID senderId)
{
//IL_0182: Unknown result type (might be due to invalid IL or missing references)
//IL_012b: Unknown result type (might be due to invalid IL or missing references)
ModLogger.Debug("Received save request " + request.RequestId + " from host for operation: " + request.Operation);
try
{
SaveSyncResponseMessage saveSyncResponseMessage = new SaveSyncResponseMessage
{
RequestId = request.RequestId,
Success = true
};
if (request.Operation == "SAVE")
{
List<PlayerConversation> allConversations = _messagingService.GetAllConversations();
ModLogger.Debug($"Client has {allConversations.Count} current conversations before sync");
if (allConversations.Count > 0)
{
_persistenceService.SaveConversations(allConversations);
ModLogger.Debug("Client synchronized conversations to persistence service before responding");
}
TextYourFriendsSaveData currentSaveData = _persistenceService.GetCurrentSaveData();
saveSyncResponseMessage.ConversationData = currentSaveData.GetJson();
saveSyncResponseMessa