using 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 Transpiler(IEnumerable instructions) { List list = new List(instructions); List list2 = new List(); 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(); ((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("Voice"); if (Object.op_Implicit((Object)(object)val2)) { val = val2.GetComponent(); } if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.TTSprefab)) { PlayerVoiceChat[] array = Resources.FindObjectsOfTypeAll(); 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(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(); _audioSource = val4.GetComponent(); 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; } } } }