Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SoloChat v0.1.1
SoloChat.dll
Decompiled 8 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SoloChat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+11e95d11e76704deee643ca1c94491a02f3e0905")] [assembly: AssemblyProduct("SoloChat")] [assembly: AssemblyTitle("SoloChat")] [assembly: AssemblyVersion("0.1.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SoloChat { [HarmonyPatch(typeof(ChatManager), "Update")] [HarmonyPriority(0)] internal static class ChatManagerUpdatePatch { private const int ExpectedGuardCount = 2; private static readonly MethodInfo VanillaMultiplayerCheck = AccessTools.Method(typeof(SemiFunc), "IsMultiplayer", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo SoloChatEnabledCheck = AccessTools.Method(typeof(ChatManagerUpdatePatch), "ChatEnabledForThisUpdate", Type.EmptyTypes, (Type[])null); internal static bool InstalledSuccessfully { get; private set; } internal static string FailureReason { get; private set; } = "The ChatManager.Update transpiler did not run."; private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); List<int> list2 = new List<int>(); if (VanillaMultiplayerCheck == null || SoloChatEnabledCheck == null) { FailureReason = "Required chat compatibility methods could not be resolved."; ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)FailureReason); } return list; } for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.Calls(list[i], VanillaMultiplayerCheck)) { list2.Add(i); } } if (list2.Count != 2) { FailureReason = $"Expected {2} SemiFunc.IsMultiplayer guards in ChatManager.Update, " + $"but found {list2.Count}."; ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)FailureReason); } return list; } foreach (int item in list2) { list[item].opcode = OpCodes.Call; list[item].operand = SoloChatEnabledCheck; } InstalledSuccessfully = true; FailureReason = null; ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"Replaced {list2.Count} singleplayer chat guards in ChatManager.Update."); } return list; } private static bool ChatEnabledForThisUpdate() { return true; } } [BepInPlugin("mathe.solochat", "Solo Chat", "0.1.1")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "mathe.solochat"; public const string PluginName = "Solo Chat"; public const string PluginVersion = "0.1.1"; private Harmony _harmony; private SoloTtsController _ttsController; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { _harmony = new Harmony("mathe.solochat"); _harmony.PatchAll(); if (!ChatManagerUpdatePatch.InstalledSuccessfully) { throw new InvalidOperationException(ChatManagerUpdatePatch.FailureReason); } _ttsController = ((Component)this).gameObject.AddComponent<SoloTtsController>(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Vanilla ChatManager and native TTS are enabled in singleplayer. The configured base-game chat key and vanilla mod chat calls are active."); } catch (Exception ex) { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; ((BaseUnityPlugin)this).Logger.LogError((object)("Solo Chat could not safely recognize the current ChatManager.Update implementation. No chat behavior was changed. " + ex)); } } private void OnDestroy() { if ((Object)(object)_ttsController != (Object)null) { _ttsController.Shutdown(); _ttsController = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; } } internal sealed class SoloTtsController : MonoBehaviour { private static readonly FieldInfo TtsPlayerAvatarField = AccessTools.Field(typeof(TTSVoice), "playerAvatar"); private static readonly FieldInfo PlayerAvatarVoiceChatField = AccessTools.Field(typeof(PlayerAvatar), "voiceChat"); private TTSVoice _voice; private AudioSource _audioSource; private PlayerAvatar _boundAvatar; private bool _creating; private bool _ready; private bool _resourceFailureLogged; private string _pendingMessage; private bool _pendingCrouching; internal static SoloTtsController Instance { get; private set; } internal static TTSVoice Voice { get { if (!((Object)(object)Instance != (Object)null)) { return null; } return Instance._voice; } } internal static bool IsSpeaking { get { if ((Object)(object)Instance != (Object)null && Instance._ready && (Object)(object)Instance._audioSource != (Object)null) { return Instance._audioSource.isPlaying; } return false; } } private void Awake() { Instance = this; } private void Update() { if (SemiFunc.IsMultiplayer()) { DestroySoloVoice(); } else if (!((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated && Object.op_Implicit((Object)(object)PlayerAvatar.instance)) { if (Object.op_Implicit((Object)(object)_voice) && (Object)(object)_boundAvatar != (Object)(object)PlayerAvatar.instance) { BindAvatar(PlayerAvatar.instance); } if (!Object.op_Implicit((Object)(object)_voice) && !_creating && !_resourceFailureLogged) { ((MonoBehaviour)this).StartCoroutine(CreateNativeVoice(PlayerAvatar.instance)); } } } internal static bool HasExistingVanillaVoice(PlayerAvatar avatar) { if (!Object.op_Implicit((Object)(object)avatar) || PlayerAvatarVoiceChatField == null) { return false; } object? value = PlayerAvatarVoiceChatField.GetValue(avatar); PlayerVoiceChat val = (PlayerVoiceChat)((value is PlayerVoiceChat) ? value : null); if (Object.op_Implicit((Object)(object)val)) { return Object.op_Implicit((Object)(object)val.ttsVoice); } return false; } internal static bool TrySpeak(PlayerAvatar avatar, string message, bool crouching) { if ((Object)(object)Instance == (Object)null || !Object.op_Implicit((Object)(object)avatar) || string.IsNullOrEmpty(message)) { return false; } return Instance.QueueOrSpeak(avatar, message, crouching); } internal static bool Owns(TTSVoice voice) { if ((Object)(object)Instance != (Object)null) { return (Object)(object)Instance._voice == (Object)(object)voice; } return false; } internal void Shutdown() { ((MonoBehaviour)this).StopAllCoroutines(); DestroySoloVoice(); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private bool QueueOrSpeak(PlayerAvatar avatar, string message, bool crouching) { if (SemiFunc.IsMultiplayer()) { return false; } if (_resourceFailureLogged) { return false; } if (Object.op_Implicit((Object)(object)_voice) && (Object)(object)_boundAvatar != (Object)(object)avatar) { BindAvatar(avatar); } if (_ready && Object.op_Implicit((Object)(object)_voice)) { _voice.TTSSpeakNow(message, crouching); return true; } _pendingMessage = message; _pendingCrouching = crouching; if (!_creating) { ((MonoBehaviour)this).StartCoroutine(CreateNativeVoice(avatar)); } return true; } private IEnumerator CreateNativeVoice(PlayerAvatar avatar) { _creating = true; if (!TryCreateNativeVoice(avatar)) { _creating = false; yield break; } yield return null; _creating = false; if (Object.op_Implicit((Object)(object)_voice)) { _ready = true; Plugin.Log.LogInfo((object)"Native singleplayer TTS voice is ready."); if (!string.IsNullOrEmpty(_pendingMessage)) { string pendingMessage = _pendingMessage; bool pendingCrouching = _pendingCrouching; _pendingMessage = null; _voice.TTSSpeakNow(pendingMessage, pendingCrouching); } } } private bool TryCreateNativeVoice(PlayerAvatar avatar) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if (TtsPlayerAvatarField == null) { LogResourceFailure("TTSVoice.playerAvatar could not be resolved."); return false; } PlayerVoiceChat val = null; GameObject val2 = Resources.Load<GameObject>("Voice"); if (Object.op_Implicit((Object)(object)val2)) { val = val2.GetComponent<PlayerVoiceChat>(); } if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.TTSprefab)) { PlayerVoiceChat[] array = Resources.FindObjectsOfTypeAll<PlayerVoiceChat>(); foreach (PlayerVoiceChat val3 in array) { if (Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val3.TTSprefab)) { val = val3; break; } } } if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.TTSprefab)) { LogResourceFailure("The vanilla Voice prefab or its TTS prefab could not be found."); return false; } GameObject val4 = Object.Instantiate<GameObject>(val.TTSprefab, ((Component)this).transform); ((Object)val4).name = "Solo Chat Vanilla TTS"; val4.transform.localPosition = Vector3.zero; val4.transform.localRotation = Quaternion.identity; if (!val4.activeSelf) { val4.SetActive(true); } _voice = val4.GetComponent<TTSVoice>(); _audioSource = val4.GetComponent<AudioSource>(); if (!Object.op_Implicit((Object)(object)_voice) || !Object.op_Implicit((Object)(object)_audioSource)) { Object.Destroy((Object)(object)val4); _voice = null; _audioSource = null; LogResourceFailure("The vanilla TTS prefab is missing TTSVoice or AudioSource."); return false; } _audioSource.outputAudioMixerGroup = val.mixerTTSSound; _audioSource.spatialBlend = 0f; _audioSource.volume = 1f; BindAvatar(avatar); return true; } private void BindAvatar(PlayerAvatar avatar) { _boundAvatar = avatar; if (Object.op_Implicit((Object)(object)_voice) && TtsPlayerAvatarField != null) { TtsPlayerAvatarField.SetValue(_voice, avatar); } } private void DestroySoloVoice() { _pendingMessage = null; _creating = false; _ready = false; _boundAvatar = null; _audioSource = null; if (Object.op_Implicit((Object)(object)_voice)) { Object.Destroy((Object)(object)((Component)_voice).gameObject); _voice = null; } } private void LogResourceFailure(string reason) { if (!_resourceFailureLogged) { _resourceFailureLogged = true; Plugin.Log.LogError((object)("Native singleplayer TTS could not initialize. " + reason)); } } } [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSpeak")] internal static class PlayerAvatarChatMessageSpeakPatch { private static bool Prefix(PlayerAvatar __instance, string _message, bool crouching) { if (SemiFunc.IsMultiplayer() || SoloTtsController.HasExistingVanillaVoice(__instance)) { return true; } return !SoloTtsController.TrySpeak(__instance, _message, crouching); } } [HarmonyPatch(typeof(TTSVoice), "Update")] internal static class SoloTtsVoiceUpdatePatch { private static bool Prefix(TTSVoice __instance, ref bool ___isSpeaking, AudioSource ___audioSource) { if (!SoloTtsController.Owns(__instance)) { return true; } ___isSpeaking = Object.op_Implicit((Object)(object)___audioSource) && ___audioSource.isPlaying; return false; } } [HarmonyPatch(typeof(LobbyChatUI), "Update")] internal static class LobbyChatUiUpdatePatch { private static void Prefix(LobbyChatUI __instance, ref TTSVoice ___ttsVoice) { if (!SemiFunc.IsMultiplayer() && __instance.isGameplay && Object.op_Implicit((Object)(object)SoloTtsController.Voice)) { ___ttsVoice = SoloTtsController.Voice; } } } [HarmonyPatch(typeof(ChatManager), "PossessChatCustomLogic")] internal static class ChatManagerPossessVoicePatch { private static void Postfix(ref float ___isSpeakingTimer) { if (!SemiFunc.IsMultiplayer() && SoloTtsController.IsSpeaking && ___isSpeakingTimer < 0.2f) { ___isSpeakingTimer = 0.2f; } } } }