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 BepInEx;
using BepInEx.Logging;
using CustomSounds.Networking;
using CustomSounds.Patches;
using HarmonyLib;
using LCSoundTool;
using Unity.Netcode;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("CustomSounds")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomSounds")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("9e086160-a7fd-4721-ba09-3e8534cb7011")]
[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")]
internal class <Module>
{
static <Module>()
{
}
}
namespace CustomSounds
{
[BepInPlugin("CustomSounds", "Custom Sounds", "2.3.1")]
public class Plugin : BaseUnityPlugin
{
public struct SoundData
{
public string SoundName;
public float? RandomPercentage;
public string CustomName;
public string FilePath;
public string FileExtension;
public string PackName;
public string AudioSource;
public string DirectoryPath;
public string RelativeDirectoryPath;
}
public class FolderTree
{
public Dictionary<string, FolderTree> SubFolders { get; set; }
public List<SoundData> Files { get; set; }
public FolderTree()
{
SubFolders = new Dictionary<string, FolderTree>();
Files = new List<SoundData>();
}
}
public static class SoundDataProcessor
{
public static FolderTree BuildFolderTree(List<SoundData> soundDataList)
{
FolderTree folderTree = new FolderTree();
foreach (SoundData soundData in soundDataList)
{
string relativeDirectoryPath = soundData.RelativeDirectoryPath;
string[] array = relativeDirectoryPath.Split(Path.DirectorySeparatorChar, '\u0001');
FolderTree folderTree2 = folderTree;
string[] array2 = array;
foreach (string key in array2)
{
if (!folderTree2.SubFolders.ContainsKey(key))
{
folderTree2.SubFolders[key] = new FolderTree();
}
folderTree2 = folderTree2.SubFolders[key];
}
folderTree2.Files.Add(soundData);
}
return folderTree;
}
public static string DisplayTree(bool isListing, FolderTree tree, int indent = 0, bool isRoot = true, int soundCount = 0)
{
StringBuilder stringBuilder = new StringBuilder();
if (isRoot)
{
soundCount = CountSounds(tree);
string text = (isListing ? "Listing all currently loaded custom sounds:" : "Customsounds reloaded.");
stringBuilder.AppendLine(text + $" ({soundCount} sounds)");
}
foreach (KeyValuePair<string, FolderTree> subFolder in tree.SubFolders)
{
if (isRoot)
{
stringBuilder.Append("\n");
}
string text2 = subFolder.Key;
if (text2.EndsWith("-AS"))
{
text2 = subFolder.Key.Substring(0, subFolder.Key.Length - 3) + " (AudioSource)";
}
stringBuilder.AppendLine(new string(' ', indent * 2) + ((indent > 0) ? "∟ " : "") + text2 + " :");
stringBuilder.Append(DisplayTree(isListing, subFolder.Value, indent + 1, isRoot: false));
}
foreach (SoundData file in tree.Files)
{
string text3 = ((!file.RandomPercentage.HasValue) ? "" : $" (Random: {file.RandomPercentage * 100f}%)");
string text4 = ((file.CustomName == "") ? "" : (" [" + file.CustomName + "]"));
stringBuilder.AppendLine(new string(' ', indent * 2) + "- " + file.SoundName + text3 + text4 + " [" + file.FileExtension.ToUpper() + "]");
}
return stringBuilder.ToString();
}
private static int CountSounds(FolderTree tree)
{
int num = tree.Files.Count;
foreach (KeyValuePair<string, FolderTree> subFolder in tree.SubFolders)
{
num += CountSounds(subFolder.Value);
}
return num;
}
}
private const string PLUGIN_GUID = "CustomSounds";
private const string PLUGIN_NAME = "Custom Sounds";
private const string PLUGIN_VERSION = "2.3.1";
public static Plugin Instance;
internal ManualLogSource logger;
private Harmony harmony;
public HashSet<string> currentSounds = new HashSet<string>();
public HashSet<string> oldSounds = new HashSet<string>();
public HashSet<string> modifiedSounds = new HashSet<string>();
public Dictionary<string, string> soundHashes = new Dictionary<string, string>();
public Dictionary<string, string> soundPacks = new Dictionary<string, string>();
public static bool hasAcceptedSync = false;
public static List<SoundData> soundDataList = new List<SoundData>();
public static bool Initialized { get; private set; }
private void Awake()
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
if (!((Object)(object)Instance == (Object)null))
{
return;
}
Instance = this;
logger = Logger.CreateLogSource("CustomSounds");
harmony = new Harmony("CustomSounds");
harmony.PatchAll(typeof(TerminalParsePlayerSentencePatch));
modifiedSounds = new HashSet<string>();
string customSoundsFolderPath = GetCustomSoundsFolderPath();
if (!Directory.Exists(customSoundsFolderPath))
{
logger.LogInfo((object)"\"CustomSounds\" folder not found. Creating it now.");
Directory.CreateDirectory(customSoundsFolderPath);
}
Type[] types = Assembly.GetExecutingAssembly().GetTypes();
Type[] array = types;
foreach (Type type in array)
{
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo[] array2 = methods;
foreach (MethodInfo methodInfo in array2)
{
object[] customAttributes = methodInfo.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), inherit: false);
if (customAttributes.Length != 0)
{
methodInfo.Invoke(null, null);
}
}
}
logger.LogInfo((object)"Plugin CustomSounds is loaded!");
}
internal void Start()
{
Initialize();
}
internal void OnDestroy()
{
Initialize();
}
internal void Initialize()
{
if (!Initialized)
{
Initialized = true;
ReloadSounds();
}
}
private void OnApplicationQuit()
{
}
public GameObject LoadNetworkPrefabFromEmbeddedResource()
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string name = "CustomSounds.Bundle.audionetworkhandler";
using Stream stream = executingAssembly.GetManifestResourceStream(name);
if (stream == null)
{
Debug.LogError((object)"Asset bundle not found in embedded resources.");
return null;
}
byte[] array = new byte[stream.Length];
stream.Read(array, 0, array.Length);
AssetBundle val = AssetBundle.LoadFromMemory(array);
if ((Object)(object)val == (Object)null)
{
Debug.LogError((object)"Failed to load AssetBundle from memory.");
return null;
}
return val.LoadAsset<GameObject>("audionetworkhandler");
}
public string GetCustomSoundsFolderPath()
{
return Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)Instance).Info.Location), "CustomSounds");
}
public void RevertSounds()
{
if (currentSounds == null || currentSounds.Count == 0)
{
logger.LogInfo((object)"No sounds to revert.");
return;
}
HashSet<string> hashSet = new HashSet<string>();
foreach (string currentSound in currentSounds)
{
string text = currentSound;
if (currentSound.Contains("-"))
{
text = currentSound.Substring(0, currentSound.IndexOf("-"));
}
if (!hashSet.Contains(text))
{
logger.LogInfo((object)(text + " restored."));
SoundTool.RestoreAudioClip(text);
hashSet.Add(text);
}
}
logger.LogInfo((object)"Original game sounds restored.");
}
public void ReloadSounds()
{
oldSounds = new HashSet<string>(currentSounds);
currentSounds.Clear();
modifiedSounds.Clear();
soundDataList.Clear();
string directoryName = Path.GetDirectoryName(Paths.PluginPath);
ProcessDirectory(directoryName);
}
private string GetRelativePathToCustomSounds(string filePath)
{
Debug.Log((object)("FilePath: " + filePath));
string[] array = filePath.Split(new char[1] { Path.DirectorySeparatorChar });
int num = Array.IndexOf(array, "CustomSounds");
if (num == -1 || num == array.Length - 1)
{
return "";
}
string[] paths = array.Skip(num + 1).ToArray();
return Path.Combine(paths);
}
private void ProcessDirectory(string directoryPath)
{
string[] directories = Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories);
foreach (string text in directories)
{
string fileName = Path.GetFileName(text);
ProcessSoundFiles(text, fileName);
}
}
private void ProcessSoundFiles(string directoryPath, string packName)
{
string[] array = new string[3] { "*.wav", "*.ogg", "*.mp3" };
string[] array2 = array;
foreach (string searchPattern in array2)
{
string[] files = Directory.GetFiles(directoryPath, searchPattern);
foreach (string text in files)
{
if (text.Contains("CustomSounds"))
{
ProcessSingleFile(text, packName);
}
}
}
}
private void ProcessSingleFile(string file, string packName)
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
(string soundName, float? percentage, string customName) tuple = ParseSoundFileName(fileNameWithoutExtension);
string item = tuple.soundName;
float? item2 = tuple.percentage;
string item3 = tuple.customName;
string fileExtension = Path.GetExtension(file).TrimStart(new char[1] { '.' }).ToLower();
string relativePathToCustomSounds = GetRelativePathToCustomSounds(file);
string fileName = Path.GetFileName(Path.GetDirectoryName(file));
string text = (fileName.EndsWith("-AS") ? fileName.Substring(0, fileName.Length - 3) : null);
SoundData soundData = default(SoundData);
soundData.SoundName = item;
soundData.RandomPercentage = item2;
soundData.CustomName = item3;
soundData.FilePath = file;
soundData.FileExtension = fileExtension;
soundData.PackName = packName;
soundData.AudioSource = text;
soundData.DirectoryPath = Path.GetDirectoryName(file);
soundData.RelativeDirectoryPath = Path.GetDirectoryName(relativePathToCustomSounds);
SoundData item4 = soundData;
soundDataList.Add(item4);
string text2 = "Sound replaced: " + item;
if (item4.RandomPercentage.HasValue)
{
text2 += $" (Percentage = {item4.RandomPercentage.Value * 100f}%)";
}
if (!string.IsNullOrEmpty(item4.CustomName))
{
text2 = text2 + " (Custom Name = " + item4.CustomName + ")";
}
text2 = text2 + " (File Extension = " + item4.FileExtension + ")";
Debug.Log((object)text2);
AudioClip audioClip = SoundTool.GetAudioClip(Path.GetDirectoryName(file), file);
((Object)audioClip).name = item;
currentSounds.Add(item4.SoundName);
if (item4.RandomPercentage.HasValue)
{
if (item4.AudioSource != null)
{
SoundTool.ReplaceAudioClip(item4.SoundName, audioClip, item4.RandomPercentage.Value, item4.AudioSource);
}
else
{
SoundTool.ReplaceAudioClip(item4.SoundName, audioClip, item4.RandomPercentage.Value);
}
}
else if (text != null)
{
SoundTool.ReplaceAudioClip(item4.SoundName, audioClip, item4.AudioSource);
}
else
{
SoundTool.ReplaceAudioClip(item4.SoundName, audioClip);
}
}
private (string soundName, float? percentage, string customName) ParseSoundFileName(string fullSoundName)
{
string[] array = fullSoundName.Split(new char[1] { '-' });
string s = array[^1].TrimEnd('.', 'w', 'a', 'v', 'o', 'g', 'm', 'p', '3');
if (int.TryParse(s, out var result))
{
string item = array[0];
string item2 = string.Join(" ", array.Skip(1).Take(array.Length - 2));
float value = (float)result / 100f;
return (item, value, item2);
}
return (array[0], null, string.Join(" ", array.Skip(1)));
}
}
}
namespace CustomSounds.Patches
{
[HarmonyPatch]
public class NetworkObjectManager
{
private static GameObject networkPrefab;
private static GameObject networkHandlerHost;
[HarmonyPatch(typeof(GameNetworkManager), "Start")]
[HarmonyPrefix]
public static void Init()
{
if (!((Object)(object)networkPrefab != (Object)null))
{
networkPrefab = Plugin.Instance.LoadNetworkPrefabFromEmbeddedResource();
networkPrefab.AddComponent<AudioNetworkHandler>();
NetworkManager.Singleton.AddNetworkPrefab(networkPrefab);
Plugin.Instance.logger.LogInfo((object)"Created AudioNetworkHandler prefab");
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(StartOfRound), "Awake")]
private static void SpawnNetworkHandler()
{
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
try
{
if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer)
{
Plugin.Instance.logger.LogInfo((object)"Spawning network handler");
networkHandlerHost = Object.Instantiate<GameObject>(networkPrefab, Vector3.zero, Quaternion.identity);
if (networkHandlerHost.GetComponent<NetworkObject>().IsSpawned)
{
Debug.Log((object)"NetworkObject is spawned and active.");
}
else
{
Debug.Log((object)"Failed to spawn NetworkObject.");
}
networkHandlerHost.GetComponent<NetworkObject>().Spawn(true);
if ((Object)(object)AudioNetworkHandler.Instance != (Object)null)
{
Debug.Log((object)"Successfully accessed AudioNetworkHandler instance.");
}
else
{
Debug.Log((object)"AudioNetworkHandler instance is null.");
}
}
}
catch
{
Plugin.Instance.logger.LogError((object)"Failed to spawned network handler");
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")]
private static void DestroyNetworkHandler()
{
try
{
if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer)
{
Plugin.Instance.logger.LogInfo((object)"Destroying network handler");
Object.Destroy((Object)(object)networkHandlerHost);
networkHandlerHost = null;
}
}
catch
{
Plugin.Instance.logger.LogError((object)"Failed to destroy network handler");
}
}
}
[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
public static class TerminalParsePlayerSentencePatch
{
public static bool Prefix(Terminal __instance, ref TerminalNode __result)
{
string[] array = __instance.screenText.text.Split(new char[1] { '\n' });
if (array.Length == 0)
{
return true;
}
string[] array2 = array.Last().Trim().ToLower()
.Split(new char[1] { ' ' });
if (array2.Length == 0 || (array2[0] != "customsounds" && array2[0] != "cs"))
{
return true;
}
Plugin.Instance.logger.LogInfo((object)("Received terminal command: " + string.Join(" ", array2)));
if (array2.Length > 1 && (array2[0] == "customsounds" || array2[0] == "cs"))
{
switch (array2[1])
{
case "reload":
case "rl":
Plugin.Instance.RevertSounds();
Plugin.Instance.ReloadSounds();
__result = CreateTerminalNode(Plugin.SoundDataProcessor.DisplayTree(isListing: false, Plugin.SoundDataProcessor.BuildFolderTree(Plugin.soundDataList)));
return false;
case "revert":
case "rv":
Plugin.Instance.RevertSounds();
__result = CreateTerminalNode("Game sounds reverted to original.\n\n");
return false;
case "list":
case "l":
__result = CreateTerminalNode(Plugin.SoundDataProcessor.DisplayTree(isListing: true, Plugin.SoundDataProcessor.BuildFolderTree(Plugin.soundDataList)));
return false;
case "help":
case "h":
if (NetworkManager.Singleton.IsHost)
{
__result = CreateTerminalNode("CustomSounds commands \n(Can also be used with 'CS' as an alias).\n\n>CUSTOMSOUNDS LIST/L\nTo display all currently loaded sounds\n\n>CUSTOMSOUNDS RELOAD/RL\nTo reload and apply sounds from the 'CustomSounds' folder and its subfolders.\n\n>CUSTOMSOUNDS REVERT/RV\nTo unload all custom sounds and restore original game sounds\n\n");
}
else
{
__result = CreateTerminalNode("CustomSounds commands \n(Can also be used with 'CS' as an alias).\n\n>CUSTOMSOUNDS LIST/L\nTo display all currently loaded sounds\n\n>CUSTOMSOUNDS RELOAD/RL\nTo reload and apply sounds from the 'CustomSounds' folder and its subfolders.\n\n>CUSTOMSOUNDS REVERT/RV\nTo unload all custom sounds and restore original game sounds\n\n");
}
return false;
case "sync":
case "s":
__result = CreateTerminalNode("/!\\ ERROR /!\\ \nThis command is not supported in this version of CustomSounds\n\n");
return false;
case "unsync":
case "u":
__result = CreateTerminalNode("/!\\ ERROR /!\\ \nThis command is not supported in this version of CustomSounds\n\n");
return false;
case "fu":
case "force-unsync":
__result = CreateTerminalNode("/!\\ ERROR /!\\ \nThis command is not supported in this version of CustomSounds\n\n");
return false;
default:
__result = CreateTerminalNode("Unknown customsounds command.\n\n");
return false;
}
}
return true;
}
private static TerminalNode CreateTerminalNode(string message)
{
TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>();
val.displayText = message;
val.clearPreviousText = true;
return val;
}
}
}
namespace CustomSounds.Networking
{
public class AudioNetworkHandler : NetworkBehaviour
{
public static AudioNetworkHandler Instance { get; private set; }
protected override void __initializeVariables()
{
((NetworkBehaviour)this).__initializeVariables();
}
protected internal override string __getTypeName()
{
return "AudioNetworkHandler";
}
}
}