using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using Cysharp.Threading.Tasks;
using FishNet.Object.Synchronizing;
using HarmonyLib;
using MelonLoader;
using MimesisPersistence;
using Mimic.Voice.SpeechSystem;
using ReluProtocol;
using ReluProtocol.Enum;
using ReluReplay;
using ReluReplay.Data;
using ReluReplay.Serializer;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(MimesisPersistenceMod), "MimesisPersistence", "0.3.1", "JoanR", null)]
[assembly: MelonGame(null, null)]
[assembly: AssemblyTitle("MimesisPersistence")]
[assembly: AssemblyDescription("Persistence mod for Mimesis - saves SpeechEvents and Replay data")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MimesisPersistence")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b6cf6602-9625-476b-a64e-bbd27bbbda35")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace MimesisPersistence
{
public class MimesisPersistenceMod : MelonMod
{
private static Harmony _harmony;
private const string HarmonyId = "MimesisPersistence";
public override void OnInitializeMelon()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Expected O, but got Unknown
try
{
_harmony = new Harmony("MimesisPersistence");
_harmony.PatchAll(typeof(MimesisPersistenceMod).Assembly);
((MelonBase)this).LoggerInstance.Msg("Persistence enabled (host only). Patches applied.");
}
catch (Exception arg)
{
((MelonBase)this).LoggerInstance.Error($"Failed to apply patches: {arg}");
}
}
public override void OnUpdate()
{
SpeechEventPoolManager.ProcessDeferredUpdates();
}
public override void OnDeinitializeMelon()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
public static class MimesisSaveManager
{
private const string MimesisDataFolder = "MimesisData";
private const string SlotPrefix = "Slot";
private const string SpeechEventsFile = "speech_events.bin";
private const string ReplayPlayFile = "replay_play.bin";
private const string ReplayVoiceFile = "replay_voice.bin";
private const string MetadataFile = "metadata.json";
private const int MetadataVersion = 2;
private const string BackupSuffix = ".bak";
private const string TempSuffix = ".tmp";
private static readonly FieldInfo CompressedAudioDataField = typeof(SpeechEvent).GetField("CompressedAudioData", BindingFlags.Instance | BindingFlags.Public);
private static void SafeWriteAllBytes(string filePath, byte[] data)
{
string text = filePath + ".tmp";
string destFileName = filePath + ".bak";
File.WriteAllBytes(text, data);
if (File.Exists(filePath))
{
try
{
File.Copy(filePath, destFileName, overwrite: true);
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] Backup failed for " + Path.GetFileName(filePath) + ": " + ex.Message);
}
}
if (File.Exists(filePath))
{
File.Delete(filePath);
}
File.Move(text, filePath);
}
private static void SafeWriteAllText(string filePath, string text)
{
SafeWriteAllBytes(filePath, Encoding.UTF8.GetBytes(text));
}
private static byte[] SafeReadAllBytes(string filePath)
{
if (File.Exists(filePath))
{
try
{
byte[] array = File.ReadAllBytes(filePath);
if (array != null && array.Length != 0)
{
return array;
}
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] Main file read failed (" + Path.GetFileName(filePath) + "): " + ex.Message);
}
}
string path = filePath + ".bak";
if (File.Exists(path))
{
try
{
byte[] array2 = File.ReadAllBytes(path);
if (array2 != null && array2.Length != 0)
{
MelonLogger.Warning("[MimesisPersistence] Recovered from backup: " + Path.GetFileName(path));
return array2;
}
}
catch (Exception ex2)
{
MelonLogger.Error("[MimesisPersistence] Backup also failed (" + Path.GetFileName(path) + "): " + ex2.Message);
}
}
return null;
}
public static void SafeWritePlayerMapping(string filePath, string json)
{
SafeWriteAllText(filePath, json);
}
public static string SafeReadPlayerMapping(string filePath)
{
if (File.Exists(filePath))
{
try
{
string text = File.ReadAllText(filePath);
if (!string.IsNullOrEmpty(text))
{
return text;
}
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] Player mapping read failed: " + ex.Message);
}
}
string path = filePath + ".bak";
if (File.Exists(path))
{
try
{
string text2 = File.ReadAllText(path);
if (!string.IsNullOrEmpty(text2))
{
MelonLogger.Warning("[MimesisPersistence] Recovered player mapping from backup");
return text2;
}
}
catch (Exception ex2)
{
MelonLogger.Error("[MimesisPersistence] Player mapping backup also failed: " + ex2.Message);
}
}
return null;
}
public static string GetMimesisSlotPath(int slotId)
{
PlatformMgr instance = MonoSingleton<PlatformMgr>.Instance;
if ((Object)(object)instance == (Object)null)
{
return null;
}
string saveFileFolderPath = instance.GetSaveFileFolderPath();
if (string.IsNullOrEmpty(saveFileFolderPath))
{
return null;
}
return Path.Combine(saveFileFolderPath, "MimesisData", "Slot" + slotId);
}
public static bool IsHost()
{
if (!IsHostViaNetwork())
{
return IsHostViaVWorld();
}
return true;
}
private static bool IsHostViaNetwork()
{
try
{
Type type = Type.GetType("FishNet.Managing.NetworkManager, FishNet.Runtime");
if (type == null)
{
return false;
}
PropertyInfo property = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);
if (property == null)
{
return false;
}
object value = property.GetValue(null);
if (value == null)
{
return false;
}
PropertyInfo property2 = type.GetProperty("IsServer", BindingFlags.Instance | BindingFlags.Public);
return property2 != null && (bool)property2.GetValue(value);
}
catch
{
return false;
}
}
private static bool IsHostViaVWorld()
{
try
{
object hubMember = GetHubMember("vworld");
if (hubMember == null)
{
return false;
}
object obj = hubMember.GetType().GetField("_sessionManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(hubMember);
if (obj == null)
{
return false;
}
FieldInfo field = obj.GetType().GetField("_hostSessionContext", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
return false;
}
object value = field.GetValue(obj);
if (value == null)
{
return false;
}
object obj2 = value.GetType().GetField("_vPlayer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(value);
if (obj2 == null)
{
return false;
}
PropertyInfo property = obj2.GetType().GetProperty("IsHost", BindingFlags.Instance | BindingFlags.Public);
return property != null && (bool)property.GetValue(obj2);
}
catch
{
return false;
}
}
public static List<SpeechEvent> CollectAllSpeechEvents()
{
List<SpeechEvent> list = new List<SpeechEvent>();
try
{
HashSet<long> hashSet = new HashSet<long>();
SpeechEventArchive[] array = Object.FindObjectsOfType<SpeechEventArchive>();
if (array != null)
{
FieldInfo field = typeof(SpeechEventArchive).GetField("events", BindingFlags.Instance | BindingFlags.Public);
if (field != null)
{
SpeechEventArchive[] array2 = array;
foreach (SpeechEventArchive obj in array2)
{
object value = field.GetValue(obj);
if (value == null)
{
continue;
}
PropertyInfo property = value.GetType().GetProperty("Count", BindingFlags.Instance | BindingFlags.Public);
PropertyInfo property2 = value.GetType().GetProperty("Item", new Type[1] { typeof(int) });
if (property == null || property2 == null)
{
continue;
}
int num = (int)property.GetValue(value);
for (int j = 0; j < num; j++)
{
object? value2 = property2.GetValue(value, new object[1] { j });
SpeechEvent val = (SpeechEvent)((value2 is SpeechEvent) ? value2 : null);
if (val != null && hashSet.Add(val.Id))
{
list.Add(val);
}
}
}
}
}
int count = list.Count;
List<SpeechEvent> disconnectedEvents = SpeechEventPoolManager.GetDisconnectedEvents();
int num2 = 0;
foreach (SpeechEvent item in disconnectedEvents)
{
if (item != null && hashSet.Add(item.Id))
{
list.Add(item);
num2++;
}
}
List<SpeechEvent> pendingEvents = SpeechEventPoolManager.GetPendingEvents();
int num3 = 0;
foreach (SpeechEvent item2 in pendingEvents)
{
if (item2 != null && hashSet.Add(item2.Id))
{
list.Add(item2);
num3++;
}
}
if (num2 > 0 || num3 > 0)
{
MelonLogger.Msg("[MimesisPersistence] CollectAllSpeechEvents: " + $"{count} live + {num2} disconnected + {num3} pending (absent players) = {list.Count} total");
}
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] CollectAllSpeechEvents: " + ex.Message);
}
return list;
}
private static object GetReplayData()
{
try
{
object hubMember = GetHubMember("replayManager");
if (hubMember == null)
{
return null;
}
FieldInfo field = typeof(ReplayManager).GetField("_recorder", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
return null;
}
object value = field.GetValue(hubMember);
return value?.GetType().GetField("_replayData", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(value);
}
catch
{
return null;
}
}
public static int GetCurrentSaveSlotId()
{
try
{
object hubMember = GetHubMember("vworld");
if (hubMember == null)
{
return -1;
}
PropertyInfo property = hubMember.GetType().GetProperty("SaveSlotID", BindingFlags.Instance | BindingFlags.Public);
if (property == null)
{
return -1;
}
return (int)property.GetValue(hubMember);
}
catch
{
return -1;
}
}
public static object GetHubMember(string name)
{
if ((Object)(object)Hub.s == (Object)null)
{
return null;
}
Type typeFromHandle = typeof(Hub);
FieldInfo field = typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field.GetValue(Hub.s);
}
PropertyInfo property = typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.CanRead)
{
return property.GetValue(Hub.s);
}
return null;
}
private static List<MsgWithTime> GetPlayDataFromReplayData(object replayData)
{
if (replayData == null)
{
return null;
}
try
{
return replayData.GetType().GetField("_playData", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(replayData) as List<MsgWithTime>;
}
catch
{
return null;
}
}
private static List<SndWithTime> GetVoiceDataFromReplayData(object replayData)
{
if (replayData == null)
{
return null;
}
try
{
return replayData.GetType().GetField("_voiceData", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(replayData) as List<SndWithTime>;
}
catch
{
return null;
}
}
public static void SaveMimesisData(int slotId)
{
if (!IsHost())
{
MelonLogger.Msg("[MimesisPersistence] Skip save: not host.");
return;
}
string mimesisSlotPath = GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
MelonLogger.Warning("[MimesisPersistence] Skip save: slot path unavailable.");
return;
}
try
{
Directory.CreateDirectory(mimesisSlotPath);
List<SpeechEvent> list = CollectAllSpeechEvents();
if (list != null && list.Count > 0)
{
using MemoryStream memoryStream = new MemoryStream();
using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
List<(byte[], byte[])> list2 = new List<(byte[], byte[])>();
foreach (SpeechEvent item2 in list)
{
byte[] dataFromSndEvent = ReplayableSndEvent.GetDataFromSndEvent(item2);
if (dataFromSndEvent != null && dataFromSndEvent.Length != 0)
{
byte[] item = item2.CompressedAudioData ?? Array.Empty<byte>();
list2.Add((dataFromSndEvent, item));
}
}
binaryWriter.Write(list2.Count);
long num = 0L;
foreach (var (array, array2) in list2)
{
binaryWriter.Write(array.Length);
binaryWriter.Write(array);
binaryWriter.Write(array2.Length);
binaryWriter.Write(array2);
num += array2.Length;
}
SafeWriteAllBytes(Path.Combine(mimesisSlotPath, "speech_events.bin"), memoryStream.ToArray());
MelonLogger.Msg($"[MimesisPersistence] Serialized {list2.Count} SpeechEvents, audio={num / 1024}KB");
}
object replayData = GetReplayData();
List<MsgWithTime> playDataFromReplayData = GetPlayDataFromReplayData(replayData);
if (playDataFromReplayData != null && playDataFromReplayData.Count > 0)
{
string text = Path.Combine(mimesisSlotPath, "replay_play.bin");
ReplaySerializer.SerializeMsgToFile(ref playDataFromReplayData, ref text);
}
List<SndWithTime> voiceDataFromReplayData = GetVoiceDataFromReplayData(replayData);
if (voiceDataFromReplayData != null && voiceDataFromReplayData.Count > 0)
{
string text2 = Path.Combine(mimesisSlotPath, "replay_voice.bin");
ReplaySerializer.SerializeSndToFile(ref voiceDataFromReplayData, ref text2);
}
SpeechEventPoolManager.SavePlayerMapping(slotId);
int num2 = list?.Count ?? 0;
int num3 = playDataFromReplayData?.Count ?? 0;
int num4 = voiceDataFromReplayData?.Count ?? 0;
SafeWriteAllText(Path.Combine(mimesisSlotPath, "metadata.json"), $"{{\"version\":{2},\"timestamp\":\"{DateTime.UtcNow:O}\",\"speechCount\":{num2},\"playCount\":{num3},\"voiceCount\":{num4}}}");
MelonLogger.Msg($"[MimesisPersistence] Saved slot {slotId}. Speech={num2}, Play={num3}, Voice={num4}");
}
catch (Exception arg)
{
MelonLogger.Error($"[MimesisPersistence] SaveMimesisData: {arg}");
}
}
public static List<SpeechEvent> LoadSpeechEvents(int slotId)
{
string mimesisSlotPath = GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
return null;
}
string text = Path.Combine(mimesisSlotPath, "speech_events.bin");
if (!File.Exists(text) && !File.Exists(text + ".bak"))
{
return null;
}
try
{
byte[] array = SafeReadAllBytes(text);
if (array == null || array.Length < 4)
{
return null;
}
List<SpeechEvent> list = new List<SpeechEvent>();
int num = 0;
using (MemoryStream memoryStream = new MemoryStream(array))
{
using BinaryReader binaryReader = new BinaryReader(memoryStream);
num = binaryReader.ReadInt32();
if (num <= 0 || num > 100000)
{
return LoadSpeechEventsOldFormat(array);
}
long num2 = 0L;
for (int i = 0; i < num; i++)
{
if (memoryStream.Position >= array.Length)
{
break;
}
int num3 = binaryReader.ReadInt32();
if (num3 <= 0 || memoryStream.Position + num3 > array.Length)
{
continue;
}
byte[] metaData = binaryReader.ReadBytes(num3);
byte[] audioData = null;
if (memoryStream.Position + 4 <= array.Length)
{
int num4 = binaryReader.ReadInt32();
if (num4 >= 0 && memoryStream.Position + num4 <= array.Length)
{
audioData = ((num4 > 0) ? binaryReader.ReadBytes(num4) : null);
num2 += num4;
}
else
{
memoryStream.Position -= 4L;
}
}
SpeechEvent val = DeserializeSingleSpeechEvent(metaData, audioData);
if (val != null)
{
list.Add(val);
}
}
MelonLogger.Msg($"[MimesisPersistence] Loaded {list.Count}/{num} SpeechEvents from slot {slotId}, audio={num2 / 1024}KB");
}
return (list.Count > 0) ? list : null;
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] LoadSpeechEvents: " + ex.Message);
return null;
}
}
private static List<SpeechEvent> LoadSpeechEventsOldFormat(byte[] data)
{
try
{
Type type = FindMemoryPackSerializerType();
if (type == null)
{
return null;
}
MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(delegate(MethodInfo m)
{
if (m.Name != "Deserialize")
{
return false;
}
ParameterInfo[] parameters = m.GetParameters();
return parameters.Length >= 2 && parameters[0].ParameterType == typeof(Type) && parameters[1].ParameterType == typeof(byte[]);
});
if (methodInfo == null)
{
return null;
}
List<SpeechEvent> list = (List<SpeechEvent>)methodInfo.Invoke(null, new object[3]
{
typeof(List<SpeechEvent>),
data,
null
});
if (list != null && list.Count > 0)
{
MelonLogger.Msg($"[MimesisPersistence] Loaded {list.Count} SpeechEvents (legacy format)");
return list;
}
}
catch
{
}
return null;
}
private static SpeechEvent DeserializeSingleSpeechEvent(byte[] metaData, byte[] audioData = null)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
if (metaData == null || metaData.Length == 0)
{
return null;
}
try
{
SpeechEvent sndEvent = new ReplayableSndEvent((SndEventType)1, 0, 0L, 0L, metaData, (byte[])null).GetSndEvent();
if (sndEvent != null && audioData != null && audioData.Length != 0 && CompressedAudioDataField != null)
{
CompressedAudioDataField.SetValue(sndEvent, audioData);
}
return sndEvent;
}
catch
{
return null;
}
}
private static Type FindMemoryPackSerializerType()
{
Type type = Type.GetType("MemoryPack.MemoryPackSerializer, MemoryPack");
if (type != null)
{
return type;
}
type = Type.GetType("MemoryPack.MemoryPackSerializer, MemoryPack.Core");
if (type != null)
{
return type;
}
type = Type.GetType("MemoryPack.MemoryPackSerializer, MemoryPack.Runtime");
if (type != null)
{
return type;
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
try
{
type = assembly.GetType("MemoryPack.MemoryPackSerializer");
if (type != null)
{
return type;
}
}
catch
{
}
}
return null;
}
public static List<MsgWithTime> LoadReplayPlayData(int slotId)
{
//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_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: 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)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
string mimesisSlotPath = GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
return null;
}
string path = Path.Combine(mimesisSlotPath, "replay_play.bin");
if (!File.Exists(path))
{
return null;
}
try
{
using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
UniTask<List<MsgWithTime>> val = ReplaySerializer.DeserializeMsgFromFileAsync(fileStream);
val.GetAwaiter().GetResult();
return val.GetAwaiter().GetResult();
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] LoadReplayPlayData: " + ex.Message);
return null;
}
}
public static List<SndWithTime> LoadReplayVoiceData(int slotId)
{
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: 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_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
string mimesisSlotPath = GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
return null;
}
string text = Path.Combine(mimesisSlotPath, "replay_voice.bin");
if (!File.Exists(text))
{
return null;
}
try
{
UniTask<List<SndWithTime>> val = ReplaySerializer.DeserializeSndFromFileAsync(text);
val.GetAwaiter().GetResult();
return val.GetAwaiter().GetResult();
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] LoadReplayVoiceData: " + ex.Message);
return null;
}
}
public static bool HasMimesisData(int slotId)
{
string mimesisSlotPath = GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
return false;
}
if (!File.Exists(Path.Combine(mimesisSlotPath, "speech_events.bin")) && !File.Exists(Path.Combine(mimesisSlotPath, "speech_events.bin.bak")) && !File.Exists(Path.Combine(mimesisSlotPath, "replay_play.bin")))
{
return File.Exists(Path.Combine(mimesisSlotPath, "replay_voice.bin"));
}
return true;
}
public static void DeleteMimesisData(int slotId)
{
string mimesisSlotPath = GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath) || !Directory.Exists(mimesisSlotPath))
{
return;
}
try
{
Directory.Delete(mimesisSlotPath, recursive: true);
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] DeleteMimesisData: " + ex.Message);
}
}
}
public static class SpeechEventPoolManager
{
public enum EventState
{
Pending,
Injected,
Fallback
}
private static readonly Dictionary<long, (SpeechEvent ev, EventState state, string originalPlayerName)> _pool = new Dictionary<long, (SpeechEvent, EventState, string)>();
private static readonly Dictionary<string, List<long>> _byPlayerName = new Dictionary<string, List<long>>();
private static readonly Dictionary<ulong, string> _steamToDissonance = new Dictionary<ulong, string>();
private static int _loadedSlotId = -1;
private static SpeechEventArchive _localArchive;
private static readonly List<(SpeechEventArchive archive, List<SpeechEvent> events)> _deferredNameUpdates = new List<(SpeechEventArchive, List<SpeechEvent>)>();
private static readonly List<SpeechEventArchive> _deferredInjectionArchives = new List<SpeechEventArchive>();
private static readonly Dictionary<long, SpeechEvent> _disconnectedCache = new Dictionary<long, SpeechEvent>();
private static readonly Dictionary<ulong, string> _disconnectedPlayerMappings = new Dictionary<ulong, string>();
private static readonly FieldInfo RecordedTimeField = typeof(SpeechEvent).GetField("RecordedTime", BindingFlags.Instance | BindingFlags.Public);
private static readonly FieldInfo LastPlayedTimeField = typeof(SpeechEvent).GetField("LastPlayedTime", BindingFlags.Instance | BindingFlags.Public);
private const string PlayerMappingFile = "player_mapping.json";
public static int TotalCount => _pool.Count;
public static bool IsLoaded
{
get
{
if (_loadedSlotId >= 0)
{
return _pool.Count > 0;
}
return false;
}
}
public static int DisconnectedCacheCount => _disconnectedCache.Count;
public static void LoadForSlot(int slotId)
{
if (slotId == _loadedSlotId && _pool.Count > 0)
{
return;
}
Reset();
_loadedSlotId = slotId;
List<SpeechEvent> list = MimesisSaveManager.LoadSpeechEvents(slotId);
if (list == null || list.Count == 0)
{
MelonLogger.Msg("[SpeechEventPoolManager] No events to load for slot " + slotId);
return;
}
foreach (SpeechEvent item in list)
{
if (item != null && !_pool.ContainsKey(item.Id))
{
string text = item.PlayerName ?? "";
_pool[item.Id] = (item, EventState.Pending, text);
if (!_byPlayerName.TryGetValue(text, out var value))
{
value = new List<long>();
_byPlayerName[text] = value;
}
value.Add(item.Id);
}
}
LoadPlayerMapping(slotId);
MelonLogger.Msg($"[SpeechEventPoolManager] Loaded {_pool.Count} events for slot {slotId} " + $"({_steamToDissonance.Count} SteamID mappings)");
}
public static List<SpeechEvent> ClaimEventsForArchive(string playerId, long playerUID, bool isLocal = false, SpeechEventArchive archive = null)
{
List<SpeechEvent> list = new List<SpeechEvent>();
if (_pool.Count == 0)
{
return list;
}
MelonLogger.Msg("[SpeechEventPoolManager] ClaimEventsForArchive: PlayerId='" + playerId + "', " + $"PlayerUID={playerUID}, isLocal={isLocal}, " + string.Format("pool has {0} events, playerNames in pool: [{1}]", _pool.Count, string.Join(", ", _byPlayerName.Keys)));
HashSet<string> hashSet = new HashSet<string>();
if (!string.IsNullOrEmpty(playerId) && _byPlayerName.ContainsKey(playerId))
{
hashSet.Add(playerId);
MelonLogger.Msg("[SpeechEventPoolManager] Level 1 match: DissonanceID '" + playerId + "' found directly");
}
ulong steamIdForPlayerUID = GetSteamIdForPlayerUID(playerUID, isLocal);
if (steamIdForPlayerUID != 0L)
{
if (_steamToDissonance.TryGetValue(steamIdForPlayerUID, out var value))
{
if (_byPlayerName.ContainsKey(value))
{
hashSet.Add(value);
MelonLogger.Msg($"[SpeechEventPoolManager] Level 2 match: SteamID {steamIdForPlayerUID} " + "-> old DissonanceID '" + value + "' (new: '" + playerId + "')");
}
}
else
{
MelonLogger.Msg($"[SpeechEventPoolManager] Level 2: SteamID {steamIdForPlayerUID} not in mapping " + $"({_steamToDissonance.Count} entries)");
}
}
else
{
MelonLogger.Msg("[SpeechEventPoolManager] Level 2: Could not resolve SteamID for " + $"PlayerUID={playerUID}");
}
foreach (string item in hashSet)
{
if (!_byPlayerName.TryGetValue(item, out var value2))
{
continue;
}
foreach (long item2 in value2)
{
if (_pool.TryGetValue(item2, out (SpeechEvent, EventState, string) value3) && value3.Item2 == EventState.Pending)
{
_pool[item2] = (value3.Item1, EventState.Injected, value3.Item3);
list.Add(value3.Item1);
}
}
}
if (list.Count > 0)
{
if (!string.IsNullOrEmpty(playerId))
{
foreach (SpeechEvent item3 in list)
{
item3.PlayerName = playerId;
}
MelonLogger.Msg($"[SpeechEventPoolManager] Claimed {list.Count} events, " + "PlayerName updated to '" + playerId + "'");
}
else if ((Object)(object)archive != (Object)null)
{
_deferredNameUpdates.Add((archive, new List<SpeechEvent>(list)));
MelonLogger.Msg($"[SpeechEventPoolManager] Claimed {list.Count} events, " + "PlayerId empty -> deferred PlayerName update registered");
}
}
else
{
var (num, num2, num3) = GetCounts();
MelonLogger.Msg("[SpeechEventPoolManager] No events claimed for PlayerId='" + playerId + "' (matched names: [" + string.Join(", ", hashSet) + "], " + $"pool: {num}P/{num2}I/{num3}F)");
}
return list;
}
private static ulong GetSteamIdForPlayerUID(long playerUID, bool isLocal = false)
{
if (playerUID != 0L)
{
try
{
object hubMember = MimesisSaveManager.GetHubMember("pdata");
if (hubMember != null)
{
FieldInfo field = hubMember.GetType().GetField("actorUIDToSteamID", BindingFlags.Instance | BindingFlags.Public);
if (field != null && field.GetValue(hubMember) is Dictionary<long, ulong> dictionary && dictionary.TryGetValue(playerUID, out var value))
{
MelonLogger.Msg($"[SpeechEventPoolManager] Resolved PlayerUID {playerUID} -> SteamID {value} (from actorUIDToSteamID)");
return value;
}
}
}
catch (Exception ex)
{
MelonLogger.Warning("[SpeechEventPoolManager] actorUIDToSteamID lookup error: " + ex.Message);
}
}
if (isLocal)
{
ulong localSteamId = GetLocalSteamId();
if (localSteamId != 0L)
{
MelonLogger.Msg($"[SpeechEventPoolManager] Resolved host SteamID={localSteamId} (from PlatformMgr, PlayerUID was {playerUID})");
return localSteamId;
}
}
MelonLogger.Warning($"[SpeechEventPoolManager] Could not resolve SteamID for PlayerUID={playerUID} (isLocal={isLocal})");
return 0uL;
}
private static ulong GetLocalSteamId()
{
try
{
PlatformMgr instance = MonoSingleton<PlatformMgr>.Instance;
if ((Object)(object)instance == (Object)null)
{
return 0uL;
}
FieldInfo field = typeof(PlatformMgr).GetField("_uniqueUserPath", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
return 0uL;
}
string text = field.GetValue(instance) as string;
if (!string.IsNullOrEmpty(text) && ulong.TryParse(text, out var result))
{
return result;
}
}
catch (Exception ex)
{
MelonLogger.Warning("[SpeechEventPoolManager] GetLocalSteamId error: " + ex.Message);
}
return 0uL;
}
public static List<SpeechEvent> ForceFallbackToLocal()
{
List<SpeechEvent> list = new List<SpeechEvent>();
foreach (long item in _pool.Keys.ToList())
{
(SpeechEvent, EventState, string) tuple = _pool[item];
if (tuple.Item2 == EventState.Pending)
{
_pool[item] = (tuple.Item1, EventState.Fallback, tuple.Item3);
list.Add(tuple.Item1);
}
}
if (list.Count > 0)
{
MelonLogger.Msg($"[SpeechEventPoolManager] Forced {list.Count} events to FALLBACK");
}
return list;
}
public static void RegisterDeferredInjection(SpeechEventArchive archive)
{
if ((Object)(object)archive == (Object)null)
{
return;
}
foreach (SpeechEventArchive deferredInjectionArchive in _deferredInjectionArchives)
{
if ((Object)(object)deferredInjectionArchive == (Object)(object)archive)
{
return;
}
}
_deferredInjectionArchives.Add(archive);
MelonLogger.Msg("[SpeechEventPoolManager] Registered archive for deferred injection " + $"(waiting for SyncVars, {_deferredInjectionArchives.Count} pending)");
}
public static void ProcessDeferredUpdates()
{
if (_deferredInjectionArchives.Count > 0)
{
for (int num = _deferredInjectionArchives.Count - 1; num >= 0; num--)
{
SpeechEventArchive val = _deferredInjectionArchives[num];
if ((Object)(object)val == (Object)null)
{
_deferredInjectionArchives.RemoveAt(num);
continue;
}
string playerId;
long playerUID;
bool isLocal;
try
{
playerId = val.PlayerId;
playerUID = val.PlayerUID;
isLocal = val.IsLocal;
}
catch
{
continue;
}
if (string.IsNullOrEmpty(playerId) && playerUID == 0L)
{
continue;
}
_deferredInjectionArchives.RemoveAt(num);
MelonLogger.Msg("[SpeechEventPoolManager] Deferred injection: SyncVars ready for " + $"PlayerId='{playerId}', PlayerUID={playerUID}");
SyncList<SpeechEvent> events = val.events;
if (events == null)
{
continue;
}
float currentSessionTime = GetCurrentSessionTime();
HashSet<long> hashSet = new HashSet<long>();
for (int i = 0; i < events.Count; i++)
{
hashSet.Add(events[i].Id);
}
int num2 = 0;
if (HasPending())
{
List<SpeechEvent> list = ClaimEventsForArchive(playerId, playerUID, isLocal, val);
if (list != null)
{
foreach (SpeechEvent item in list)
{
if (item != null && !hashSet.Contains(item.Id))
{
FixEventTiming(item, currentSessionTime);
events.Add(item);
hashSet.Add(item.Id);
num2++;
}
}
}
}
if (_disconnectedCache.Count > 0)
{
List<SpeechEvent> list2 = ClaimDisconnectedEventsForArchive(playerId, playerUID, isLocal);
if (list2 != null)
{
foreach (SpeechEvent item2 in list2)
{
if (item2 != null && !hashSet.Contains(item2.Id))
{
FixEventTiming(item2, currentSessionTime);
events.Add(item2);
hashSet.Add(item2.Id);
num2++;
}
}
}
}
if (num2 > 0)
{
(int, int, int) counts = GetCounts();
MelonLogger.Msg($"[SpeechEventPoolManager] Deferred injection: {num2} events into " + $"PlayerId='{playerId}' [pool: {counts.Item1}P/{counts.Item2}I/{counts.Item3}F, " + $"disconnected cache: {_disconnectedCache.Count}]");
}
}
}
if (_deferredNameUpdates.Count == 0)
{
return;
}
for (int num3 = _deferredNameUpdates.Count - 1; num3 >= 0; num3--)
{
var (val2, list3) = _deferredNameUpdates[num3];
if ((Object)(object)val2 == (Object)null)
{
_deferredNameUpdates.RemoveAt(num3);
continue;
}
string playerId2;
try
{
playerId2 = val2.PlayerId;
}
catch
{
continue;
}
if (string.IsNullOrEmpty(playerId2))
{
continue;
}
foreach (SpeechEvent item3 in list3)
{
if (item3 != null)
{
item3.PlayerName = playerId2;
}
}
MelonLogger.Msg($"[SpeechEventPoolManager] Deferred update: {list3.Count} events " + "PlayerName updated to '" + playerId2 + "'");
_deferredNameUpdates.RemoveAt(num3);
}
}
public static SpeechEventArchive GetLocalArchive()
{
return _localArchive;
}
public static void SetLocalArchive(SpeechEventArchive archive)
{
_localArchive = archive;
}
public static (int pending, int injected, int fallback) GetCounts()
{
int num = 0;
int num2 = 0;
int num3 = 0;
using (Dictionary<long, (SpeechEvent, EventState, string)>.ValueCollection.Enumerator enumerator = _pool.Values.GetEnumerator())
{
while (enumerator.MoveNext())
{
switch (enumerator.Current.Item2)
{
case EventState.Pending:
num++;
break;
case EventState.Injected:
num2++;
break;
case EventState.Fallback:
num3++;
break;
}
}
}
return (num, num2, num3);
}
public static List<SpeechEvent> GetPendingEvents()
{
List<SpeechEvent> list = new List<SpeechEvent>();
foreach (var value in _pool.Values)
{
if (value.state == EventState.Pending)
{
list.Add(value.ev);
}
}
return list;
}
public static bool HasPending()
{
foreach (var value in _pool.Values)
{
if (value.state == EventState.Pending)
{
return true;
}
}
return false;
}
public static void FixEventTiming(SpeechEvent ev, float currentTime)
{
if (RecordedTimeField != null)
{
RecordedTimeField.SetValue(ev, currentTime);
}
if (LastPlayedTimeField != null)
{
LastPlayedTimeField.SetValue(ev, currentTime);
}
}
public static float GetCurrentSessionTime()
{
try
{
object hubMember = MimesisSaveManager.GetHubMember("timeutil");
if (hubMember != null)
{
MethodInfo method = hubMember.GetType().GetMethod("GetCurrentTickSec", BindingFlags.Instance | BindingFlags.Public);
if (method != null)
{
return (int)method.Invoke(hubMember, null);
}
}
}
catch
{
}
return 0f;
}
public static void CacheEventsFromArchive(SpeechEventArchive archive)
{
if ((Object)(object)archive == (Object)null)
{
return;
}
try
{
string text = null;
long num = 0L;
bool flag = false;
try
{
text = archive.PlayerId;
num = archive.PlayerUID;
flag = archive.IsLocal;
}
catch
{
}
if (flag)
{
return;
}
FieldInfo field = typeof(SpeechEventArchive).GetField("events", BindingFlags.Instance | BindingFlags.Public);
if (field == null)
{
return;
}
object value = field.GetValue(archive);
if (value == null)
{
return;
}
PropertyInfo property = value.GetType().GetProperty("Count", BindingFlags.Instance | BindingFlags.Public);
PropertyInfo property2 = value.GetType().GetProperty("Item", new Type[1] { typeof(int) });
if (property == null || property2 == null)
{
return;
}
int num2 = (int)property.GetValue(value);
int num3 = 0;
for (int i = 0; i < num2; i++)
{
object? value2 = property2.GetValue(value, new object[1] { i });
SpeechEvent val = (SpeechEvent)((value2 is SpeechEvent) ? value2 : null);
if (val != null && !_disconnectedCache.ContainsKey(val.Id))
{
_disconnectedCache[val.Id] = val;
num3++;
}
}
ulong steamIdForPlayerUID = GetSteamIdForPlayerUID(num);
if (steamIdForPlayerUID != 0L && !string.IsNullOrEmpty(text))
{
_disconnectedPlayerMappings[steamIdForPlayerUID] = text;
MelonLogger.Msg($"[SpeechEventPoolManager] Cached player mapping: SteamID {steamIdForPlayerUID} -> '{text}'");
}
MelonLogger.Msg($"[SpeechEventPoolManager] Cached {num3} events from disconnected player " + $"'{text}' (UID={num}). Total cached: {_disconnectedCache.Count}");
}
catch (Exception ex)
{
MelonLogger.Warning("[SpeechEventPoolManager] CacheEventsFromArchive error: " + ex.Message);
}
}
public static List<SpeechEvent> ClaimDisconnectedEventsForArchive(string playerId, long playerUID, bool isLocal)
{
List<SpeechEvent> list = new List<SpeechEvent>();
if (_disconnectedCache.Count == 0)
{
return list;
}
HashSet<string> hashSet = new HashSet<string>();
if (!string.IsNullOrEmpty(playerId))
{
hashSet.Add(playerId);
}
ulong steamIdForPlayerUID = GetSteamIdForPlayerUID(playerUID, isLocal);
if (steamIdForPlayerUID != 0L && _disconnectedPlayerMappings.TryGetValue(steamIdForPlayerUID, out var value))
{
hashSet.Add(value);
MelonLogger.Msg($"[SpeechEventPoolManager] Disconnected cache match: SteamID {steamIdForPlayerUID} -> old DissonanceID '{value}'");
}
if (hashSet.Count == 0)
{
return list;
}
List<long> list2 = new List<long>();
foreach (KeyValuePair<long, SpeechEvent> item2 in _disconnectedCache)
{
string item = item2.Value.PlayerName ?? "";
if (hashSet.Contains(item))
{
if (!string.IsNullOrEmpty(playerId))
{
item2.Value.PlayerName = playerId;
}
list.Add(item2.Value);
list2.Add(item2.Key);
}
}
foreach (long item3 in list2)
{
_disconnectedCache.Remove(item3);
}
if (steamIdForPlayerUID != 0L && list.Count > 0)
{
_disconnectedPlayerMappings.Remove(steamIdForPlayerUID);
}
if (list.Count > 0)
{
MelonLogger.Msg($"[SpeechEventPoolManager] Reclaimed {list.Count} events from disconnected cache " + $"for PlayerId='{playerId}' (remaining cache: {_disconnectedCache.Count})");
}
return list;
}
public static List<SpeechEvent> GetDisconnectedEvents()
{
return new List<SpeechEvent>(_disconnectedCache.Values);
}
public static Dictionary<ulong, string> GetDisconnectedPlayerMappings()
{
return new Dictionary<ulong, string>(_disconnectedPlayerMappings);
}
public static void Reset()
{
_pool.Clear();
_byPlayerName.Clear();
_steamToDissonance.Clear();
_deferredNameUpdates.Clear();
_deferredInjectionArchives.Clear();
_disconnectedCache.Clear();
_disconnectedPlayerMappings.Clear();
_loadedSlotId = -1;
_localArchive = null;
}
public static void SavePlayerMapping(int slotId)
{
string mimesisSlotPath = MimesisSaveManager.GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
MelonLogger.Warning("[SpeechEventPoolManager] SavePlayerMapping: slot path is null/empty!");
return;
}
try
{
SpeechEventArchive[] array = Object.FindObjectsOfType<SpeechEventArchive>();
if (array == null || array.Length == 0)
{
MelonLogger.Warning("[SpeechEventPoolManager] SavePlayerMapping: no SpeechEventArchive found!");
return;
}
MelonLogger.Msg($"[SpeechEventPoolManager] SavePlayerMapping: found {array.Length} archives");
Dictionary<ulong, string> dictionary = new Dictionary<ulong, string>();
SpeechEventArchive[] array2 = array;
foreach (SpeechEventArchive val in array2)
{
try
{
string playerId = val.PlayerId;
long playerUID = val.PlayerUID;
bool isLocal = val.IsLocal;
if (string.IsNullOrEmpty(playerId))
{
MelonLogger.Msg($"[SpeechEventPoolManager] Archive skipped: empty PlayerId (UID={playerUID})");
continue;
}
ulong steamIdForPlayerUID = GetSteamIdForPlayerUID(playerUID, isLocal);
MelonLogger.Msg($"[SpeechEventPoolManager] Archive: PlayerId='{playerId}', PlayerUID={playerUID}, IsLocal={isLocal}, SteamID={steamIdForPlayerUID}");
if (steamIdForPlayerUID != 0L)
{
dictionary[steamIdForPlayerUID] = playerId;
}
else
{
MelonLogger.Warning($"[SpeechEventPoolManager] Could not resolve SteamID for PlayerUID={playerUID}");
}
}
catch (Exception ex)
{
MelonLogger.Warning("[SpeechEventPoolManager] Archive error: " + ex.Message);
}
}
Dictionary<ulong, string> disconnectedPlayerMappings = GetDisconnectedPlayerMappings();
int num = 0;
foreach (KeyValuePair<ulong, string> item in disconnectedPlayerMappings)
{
if (!dictionary.ContainsKey(item.Key))
{
dictionary[item.Key] = item.Value;
num++;
}
}
if (num > 0)
{
MelonLogger.Msg($"[SpeechEventPoolManager] Added {num} disconnected player mappings");
}
List<string> list = new List<string>();
foreach (KeyValuePair<ulong, string> item2 in dictionary)
{
string arg = EscapeJsonString(item2.Value);
list.Add($"\"{item2.Key}\":\"{arg}\"");
}
string json = "{" + string.Join(",", list) + "}";
Directory.CreateDirectory(mimesisSlotPath);
string text = Path.Combine(mimesisSlotPath, "player_mapping.json");
MimesisSaveManager.SafeWritePlayerMapping(text, json);
MelonLogger.Msg($"[SpeechEventPoolManager] Saved player mapping: {dictionary.Count} entries ({num} from cache) -> {text}");
}
catch (Exception arg2)
{
MelonLogger.Error($"[SpeechEventPoolManager] SavePlayerMapping FAILED: {arg2}");
}
}
private static void LoadPlayerMapping(int slotId)
{
_steamToDissonance.Clear();
string mimesisSlotPath = MimesisSaveManager.GetMimesisSlotPath(slotId);
if (string.IsNullOrEmpty(mimesisSlotPath))
{
return;
}
string text = Path.Combine(mimesisSlotPath, "player_mapping.json");
string text2 = MimesisSaveManager.SafeReadPlayerMapping(text);
if (string.IsNullOrEmpty(text2))
{
MelonLogger.Msg("[SpeechEventPoolManager] No player_mapping.json at " + text);
return;
}
try
{
foreach (KeyValuePair<ulong, string> item in ParseSteamToDissonanceJson(text2))
{
_steamToDissonance[item.Key] = item.Value;
}
MelonLogger.Msg($"[SpeechEventPoolManager] Loaded player mapping: {_steamToDissonance.Count} entries");
foreach (KeyValuePair<ulong, string> item2 in _steamToDissonance)
{
MelonLogger.Msg($"[SpeechEventPoolManager] SteamID {item2.Key} -> DissonanceID '{item2.Value}'");
}
}
catch (Exception ex)
{
MelonLogger.Warning("[SpeechEventPoolManager] LoadPlayerMapping: " + ex.Message);
}
}
private static Dictionary<ulong, string> ParseSteamToDissonanceJson(string json)
{
Dictionary<ulong, string> dictionary = new Dictionary<ulong, string>();
if (string.IsNullOrEmpty(json))
{
return dictionary;
}
json = json.Trim();
if (json.StartsWith("{"))
{
json = json.Substring(1);
}
if (json.EndsWith("}"))
{
json = json.Substring(0, json.Length - 1);
}
json = json.Trim();
if (string.IsNullOrEmpty(json))
{
return dictionary;
}
int num = 0;
while (num < json.Length)
{
int num2 = json.IndexOf('"', num);
if (num2 < 0)
{
break;
}
int num3 = json.IndexOf('"', num2 + 1);
if (num3 < 0)
{
break;
}
string s = json.Substring(num2 + 1, num3 - num2 - 1);
int num4 = json.IndexOf(':', num3 + 1);
if (num4 < 0)
{
break;
}
int num5 = json.IndexOf('"', num4 + 1);
if (num5 < 0)
{
break;
}
int num6 = json.IndexOf('"', num5 + 1);
if (num6 < 0)
{
break;
}
string value = json.Substring(num5 + 1, num6 - num5 - 1);
if (ulong.TryParse(s, out var result))
{
dictionary[result] = value;
}
num = num6 + 1;
int num7 = json.IndexOf(',', num);
num = ((num7 >= 0) ? (num7 + 1) : json.Length);
}
return dictionary;
}
private static string EscapeJsonString(string s)
{
if (s == null)
{
return "";
}
return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
}
}
namespace MimesisPersistence.Patches
{
[HarmonyPatch(typeof(MaintenanceRoom), "SaveGameData")]
public static class MaintenanceRoomPatches
{
[HarmonyPostfix]
public static void Postfix(int saveSlotID, List<string> playerNames, bool isAutoSave, MsgErrorCode __result)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
if ((int)__result == 0 && MimesisSaveManager.IsHost())
{
MimesisSaveManager.SaveMimesisData((!isAutoSave) ? saveSlotID : 0);
}
}
}
[HarmonyPatch(typeof(PlatformMgr), "Delete")]
public static class PlatformMgrPatches
{
[HarmonyPostfix]
public static void Postfix(string fileName)
{
try
{
if (!string.IsNullOrEmpty(fileName) && fileName.StartsWith("MMGameData", StringComparison.OrdinalIgnoreCase) && int.TryParse(Path.GetFileNameWithoutExtension(fileName).Replace("MMGameData", ""), out var result) && MMSaveGameData.CheckSaveSlotID(result, true))
{
MimesisSaveManager.DeleteMimesisData(result);
}
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] PlatformMgr.Delete: " + ex.Message);
}
}
}
[HarmonyPatch(typeof(SpeechEventArchive), "OnStartClient")]
public static class SpeechEventArchivePatches
{
private static int _poolLoadedForSlot = -999;
[HarmonyPostfix]
public static void Postfix(SpeechEventArchive __instance)
{
try
{
if (!MimesisSaveManager.IsHost())
{
return;
}
int currentSaveSlotId = MimesisSaveManager.GetCurrentSaveSlotId();
if (!MMSaveGameData.CheckSaveSlotID(currentSaveSlotId, true))
{
return;
}
if (currentSaveSlotId != _poolLoadedForSlot)
{
_poolLoadedForSlot = currentSaveSlotId;
SpeechEventPoolManager.Reset();
if (MimesisSaveManager.HasMimesisData(currentSaveSlotId))
{
SpeechEventPoolManager.LoadForSlot(currentSaveSlotId);
}
}
if (__instance.IsLocal)
{
SpeechEventPoolManager.SetLocalArchive(__instance);
}
string text = null;
long num = 0L;
bool flag = false;
try
{
text = __instance.PlayerId;
num = __instance.PlayerUID;
flag = __instance.IsLocal;
}
catch
{
}
if (!flag && string.IsNullOrEmpty(text) && num == 0L)
{
if (SpeechEventPoolManager.HasPending() || SpeechEventPoolManager.DisconnectedCacheCount > 0)
{
SpeechEventPoolManager.RegisterDeferredInjection(__instance);
}
return;
}
SyncList<SpeechEvent> events = __instance.events;
if (events == null)
{
return;
}
float currentSessionTime = SpeechEventPoolManager.GetCurrentSessionTime();
HashSet<long> hashSet = new HashSet<long>();
for (int i = 0; i < events.Count; i++)
{
hashSet.Add(events[i].Id);
}
int num2 = 0;
if (SpeechEventPoolManager.HasPending())
{
List<SpeechEvent> list = SpeechEventPoolManager.ClaimEventsForArchive(text, num, flag, __instance);
if (list != null && list.Count > 0)
{
foreach (SpeechEvent item in list)
{
if (item != null && !hashSet.Contains(item.Id))
{
SpeechEventPoolManager.FixEventTiming(item, currentSessionTime);
events.Add(item);
hashSet.Add(item.Id);
num2++;
}
}
}
}
if (SpeechEventPoolManager.DisconnectedCacheCount > 0)
{
List<SpeechEvent> list2 = SpeechEventPoolManager.ClaimDisconnectedEventsForArchive(text, num, flag);
if (list2 != null && list2.Count > 0)
{
foreach (SpeechEvent item2 in list2)
{
if (item2 != null && !hashSet.Contains(item2.Id))
{
SpeechEventPoolManager.FixEventTiming(item2, currentSessionTime);
events.Add(item2);
hashSet.Add(item2.Id);
num2++;
}
}
MelonLogger.Msg($"[MimesisPersistence] Restored {list2.Count} events from disconnected cache " + "into archive PlayerId=" + text);
}
}
if (num2 > 0)
{
(int, int, int) counts = SpeechEventPoolManager.GetCounts();
MelonLogger.Msg($"[MimesisPersistence] Injected {num2} total events into archive " + $"PlayerId={text} (time={currentSessionTime:F1}) " + $"[pool: {counts.Item1}P/{counts.Item2}I/{counts.Item3}F, " + $"disconnected cache: {SpeechEventPoolManager.DisconnectedCacheCount}]");
}
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] SpeechEventArchive inject: " + ex.Message);
}
}
public static int InjectEventsIntoArchive(SpeechEventArchive archive, List<SpeechEvent> events)
{
if ((Object)(object)archive == (Object)null || events == null || events.Count == 0)
{
return 0;
}
SyncList<SpeechEvent> events2 = archive.events;
if (events2 == null)
{
return 0;
}
float currentSessionTime = SpeechEventPoolManager.GetCurrentSessionTime();
HashSet<long> hashSet = new HashSet<long>();
for (int i = 0; i < events2.Count; i++)
{
hashSet.Add(events2[i].Id);
}
int num = 0;
foreach (SpeechEvent @event in events)
{
if (@event != null && !hashSet.Contains(@event.Id))
{
SpeechEventPoolManager.FixEventTiming(@event, currentSessionTime);
events2.Add(@event);
hashSet.Add(@event.Id);
num++;
}
}
return num;
}
public static void ResetInjectedSlot()
{
_poolLoadedForSlot = -999;
SpeechEventPoolManager.Reset();
}
}
[HarmonyPatch(typeof(VoiceManager), "GetRandomOtherSpeechEventArchive")]
public static class VoiceManagerHallucinationPatch
{
[HarmonyPostfix]
public static void Postfix(ref SpeechEventArchive __result)
{
try
{
if (!((Object)(object)__result != (Object)null))
{
SpeechEventArchive localArchive = SpeechEventPoolManager.GetLocalArchive();
if (!((Object)(object)localArchive == (Object)null) && localArchive.RandomPoolSize > 0)
{
__result = localArchive;
}
}
}
catch (Exception ex)
{
MelonLogger.Warning("[MimesisPersistence] Hallucination fallback: " + ex.Message);
}
}
}
}