using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Networking;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.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.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace CaptainAudio
{
public static class AudioLoader
{
private class LoadTask
{
public string FolderName;
public string ClipName;
public byte[] AudioData;
public bool IsCompleted;
public AudioClip LoadedClip;
}
[CompilerGenerated]
private sealed class <>c__DisplayClass5_0
{
public string resourcePrefix;
internal bool <LoadEmbeddedResourcesParallel>b__0(string name)
{
return name.StartsWith(resourcePrefix);
}
}
[CompilerGenerated]
private sealed class <>c__DisplayClass6_0
{
public AudioClip clip;
public bool success;
internal void <LoadSingleClipWithRetry>b__0(AudioClip loadedClip)
{
clip = loadedClip;
success = (Object)(object)loadedClip != (Object)null;
}
}
[CompilerGenerated]
private sealed class <InitializeAsync>d__4 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <InitializeAsync>d__4(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
if (_isInitialized)
{
return false;
}
Plugin.CustomMusic.Clear();
Plugin.CustomAmbient.Clear();
Plugin.CustomSFX.Clear();
Plugin.CustomMusicList.Clear();
Plugin.CustomAmbientList.Clear();
Plugin.CustomSFXList.Clear();
<>2__current = LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.Music", Plugin.CustomMusic, Plugin.CustomMusicList);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<>2__current = LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.Ambient", Plugin.CustomAmbient, Plugin.CustomAmbientList);
<>1__state = 2;
return true;
case 2:
<>1__state = -1;
<>2__current = LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.SFX", Plugin.CustomSFX, Plugin.CustomSFXList);
<>1__state = 3;
return true;
case 3:
<>1__state = -1;
LoadExternalAudioFiles();
StringMatchCache.Initialize();
_isInitialized = true;
Plugin.Log.LogInfo((object)$"<color=#00BFFF>Loaded: {Plugin.CustomMusicList.Count} folders, {Plugin.CustomMusic.Count} clips</color>");
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <LoadAudioClipFromBytes>d__7 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public byte[] audioData;
public string clipName;
public Action<AudioClip> onComplete;
private string <tempPath>5__2;
private UnityWebRequest <www>5__3;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <LoadAudioClipFromBytes>d__7(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<tempPath>5__2 = null;
<www>5__3 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0129: Unknown result type (might be due to invalid IL or missing references)
//IL_012f: Invalid comparison between Unknown and I4
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: 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)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
//IL_0165: Unknown result type (might be due to invalid IL or missing references)
//IL_016b: Invalid comparison between Unknown and I4
//IL_020a: Unknown result type (might be due to invalid IL or missing references)
try
{
switch (<>1__state)
{
default:
return false;
case 0:
{
<>1__state = -1;
string text = DetectAudioFormat(audioData);
<tempPath>5__2 = Path.Combine(Application.temporaryCachePath, $"temp_audio_{clipName}_{Guid.NewGuid()}{text}");
try
{
Directory.CreateDirectory(Path.GetDirectoryName(<tempPath>5__2));
File.WriteAllBytes(<tempPath>5__2, audioData);
}
catch
{
onComplete?.Invoke(null);
return false;
}
AudioType audioType = GetAudioType(text);
string text2 = "file:///" + <tempPath>5__2.Replace("\\", "/");
<www>5__3 = UnityWebRequestMultimedia.GetAudioClip(text2, audioType);
<>1__state = -3;
DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)<www>5__3.downloadHandler;
val.streamAudio = false;
val.compressed = false;
<www>5__3.timeout = 15;
<>2__current = <www>5__3.SendWebRequest();
<>1__state = 1;
return true;
}
case 1:
<>1__state = -3;
if ((int)<www>5__3.result == 1)
{
AudioClip content = DownloadHandlerAudioClip.GetContent(<www>5__3);
if ((Object)(object)content != (Object)null && content.length > 0f && content.samples > 0 && (int)content.loadState == 2)
{
((Object)content).name = clipName;
onComplete?.Invoke(content);
}
else
{
Plugin.Log.LogWarning((object)$"Invalid clip: {clipName} (length={((content != null) ? new float?(content.length) : null)}, samples={((content != null) ? new int?(content.samples) : null)}, state={((content != null) ? new AudioDataLoadState?(content.loadState) : null)})");
onComplete?.Invoke(null);
}
}
else
{
Plugin.Log.LogWarning((object)("Failed to load: " + clipName + " - " + <www>5__3.error));
onComplete?.Invoke(null);
}
<>m__Finally1();
<www>5__3 = null;
if (File.Exists(<tempPath>5__2))
{
try
{
File.Delete(<tempPath>5__2);
}
catch
{
}
}
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<www>5__3 != null)
{
((IDisposable)<www>5__3).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <LoadEmbeddedResourcesParallel>d__5 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string resourcePrefix;
public Dictionary<string, Dictionary<string, AudioClip>> folderDict;
public Dictionary<string, AudioClip> singleDict;
private List<LoadTask> <tasks>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <LoadEmbeddedResourcesParallel>d__5(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<tasks>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
{
<>1__state = -1;
<>c__DisplayClass5_0 CS$<>8__locals0 = new <>c__DisplayClass5_0
{
resourcePrefix = resourcePrefix
};
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string[] array = (from name in executingAssembly.GetManifestResourceNames()
where name.StartsWith(CS$<>8__locals0.resourcePrefix)
select name).ToArray();
if (array.Length == 0)
{
return false;
}
<tasks>5__2 = new List<LoadTask>();
string[] array2 = array;
foreach (string text in array2)
{
Stream manifestResourceStream = executingAssembly.GetManifestResourceStream(text);
if (manifestResourceStream != null)
{
byte[] array3 = new byte[manifestResourceStream.Length];
manifestResourceStream.Read(array3, 0, array3.Length);
manifestResourceStream.Dispose();
var (folderName, text2) = ParseResourcePath(text.Substring(CS$<>8__locals0.resourcePrefix.Length + 1));
if (!string.IsNullOrEmpty(text2))
{
LoadTask loadTask = new LoadTask
{
FolderName = folderName,
ClipName = text2,
AudioData = array3,
IsCompleted = false,
LoadedClip = null
};
<tasks>5__2.Add(loadTask);
((MonoBehaviour)Plugin.instance).StartCoroutine(LoadSingleClipWithRetry(loadTask));
}
}
}
break;
}
case 1:
<>1__state = -1;
break;
}
if (<tasks>5__2.Any((LoadTask t) => !t.IsCompleted))
{
<>2__current = null;
<>1__state = 1;
return true;
}
foreach (LoadTask item in <tasks>5__2)
{
if ((Object)(object)item.LoadedClip == (Object)null)
{
continue;
}
if (!string.IsNullOrEmpty(item.FolderName))
{
if (!folderDict.ContainsKey(item.FolderName))
{
folderDict[item.FolderName] = new Dictionary<string, AudioClip>();
}
folderDict[item.FolderName][item.ClipName] = item.LoadedClip;
}
else
{
singleDict[item.ClipName] = item.LoadedClip;
}
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <LoadSingleClipWithRetry>d__6 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public LoadTask task;
private <>c__DisplayClass6_0 <>8__1;
private int <retryCount>5__2;
private int <attempt>5__3;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <LoadSingleClipWithRetry>d__6(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>8__1 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<retryCount>5__2 = Plugin.LoadRetryCount.Value;
<attempt>5__3 = 0;
break;
case 1:
<>1__state = -1;
if (<>8__1.success)
{
task.LoadedClip = <>8__1.clip;
task.IsCompleted = true;
return false;
}
if (<attempt>5__3 < <retryCount>5__2 - 1)
{
<>2__current = (object)new WaitForSeconds(0.5f);
<>1__state = 2;
return true;
}
goto IL_0104;
case 2:
{
<>1__state = -1;
goto IL_0104;
}
IL_0104:
<>8__1 = null;
<attempt>5__3++;
break;
}
if (<attempt>5__3 < <retryCount>5__2)
{
<>8__1 = new <>c__DisplayClass6_0();
<>8__1.success = false;
<>8__1.clip = null;
<>2__current = LoadAudioClipFromBytes(task.AudioData, task.ClipName, delegate(AudioClip loadedClip)
{
<>8__1.clip = loadedClip;
<>8__1.success = (Object)(object)loadedClip != (Object)null;
});
<>1__state = 1;
return true;
}
task.IsCompleted = true;
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static bool _isInitialized = false;
private const int MAX_RETRY_COUNT = 3;
private const float RETRY_DELAY = 0.5f;
private static HashSet<string> _externalOverrideFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
[IteratorStateMachine(typeof(<InitializeAsync>d__4))]
public static IEnumerator InitializeAsync()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <InitializeAsync>d__4(0);
}
[IteratorStateMachine(typeof(<LoadEmbeddedResourcesParallel>d__5))]
private static IEnumerator LoadEmbeddedResourcesParallel(string resourcePrefix, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <LoadEmbeddedResourcesParallel>d__5(0)
{
resourcePrefix = resourcePrefix,
singleDict = singleDict,
folderDict = folderDict
};
}
[IteratorStateMachine(typeof(<LoadSingleClipWithRetry>d__6))]
private static IEnumerator LoadSingleClipWithRetry(LoadTask task)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <LoadSingleClipWithRetry>d__6(0)
{
task = task
};
}
[IteratorStateMachine(typeof(<LoadAudioClipFromBytes>d__7))]
private static IEnumerator LoadAudioClipFromBytes(byte[] audioData, string clipName, Action<AudioClip> onComplete)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <LoadAudioClipFromBytes>d__7(0)
{
audioData = audioData,
clipName = clipName,
onComplete = onComplete
};
}
private static void LoadExternalAudioFiles()
{
string text = Path.Combine(Paths.PluginPath, "CaptainAudio");
if (Directory.Exists(text))
{
_externalOverrideFolders.Clear();
ScanExternalFolders(text);
ApplyExternalOverrides();
if (Directory.Exists(Path.Combine(text, "Music")))
{
CollectAudioFiles(Path.Combine(text, "Music"), Plugin.CustomMusic, Plugin.CustomMusicList);
}
if (Directory.Exists(Path.Combine(text, "SFX")))
{
CollectAudioFiles(Path.Combine(text, "SFX"), Plugin.CustomSFX, Plugin.CustomSFXList);
}
if (Directory.Exists(Path.Combine(text, "Ambient")))
{
CollectAudioFiles(Path.Combine(text, "Ambient"), Plugin.CustomAmbient, Plugin.CustomAmbientList);
}
}
}
private static void ScanExternalFolders(string externalPath)
{
string[] array = new string[3] { "Music", "Ambient", "SFX" };
foreach (string text in array)
{
string path = Path.Combine(externalPath, text);
if (!Directory.Exists(path))
{
continue;
}
string[] directories = Directory.GetDirectories(path);
foreach (string path2 in directories)
{
string fileName = Path.GetFileName(path2);
if (Directory.GetFiles(path2).Any((string file) => IsValidAudioFile(file)))
{
string item = text + ":" + fileName;
_externalOverrideFolders.Add(item);
Plugin.Log.LogInfo((object)("<color=#FFA500>[Override] " + text + "/" + fileName + ": 외부 음악 감지됨 → 내장 음악 대체</color>"));
}
}
}
}
private static bool IsValidAudioFile(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
return false;
}
string text = Path.GetExtension(filePath).ToLower();
if (!(text == ".ogg") && !(text == ".wav"))
{
return text == ".mp3";
}
return true;
}
private static void ApplyExternalOverrides()
{
foreach (string externalOverrideFolder in _externalOverrideFolders)
{
string[] array = externalOverrideFolder.Split(':');
if (array.Length != 2)
{
continue;
}
string text = array[0];
string folderName = array[1];
Dictionary<string, Dictionary<string, AudioClip>> dictionary = null;
switch (text)
{
case "Music":
dictionary = Plugin.CustomMusicList;
break;
case "Ambient":
dictionary = Plugin.CustomAmbientList;
break;
case "SFX":
dictionary = Plugin.CustomSFXList;
break;
}
if (dictionary != null)
{
string text2 = dictionary.Keys.FirstOrDefault((string k) => string.Equals(k, folderName, StringComparison.OrdinalIgnoreCase));
if (text2 != null)
{
int count = dictionary[text2].Count;
dictionary[text2].Clear();
Plugin.Log.LogInfo((object)$"<color=#FFA500>[Override] {text}/{folderName}: 내장 음악 {count}개 제거됨</color>");
}
}
}
}
private static void CollectAudioFiles(string path, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict)
{
string[] files = Directory.GetFiles(path);
for (int i = 0; i < files.Length; i++)
{
LoadExternalClip(files[i], singleDict);
}
files = Directory.GetDirectories(path);
foreach (string path2 in files)
{
string fileName = Path.GetFileName(path2);
folderDict[fileName] = new Dictionary<string, AudioClip>();
string[] files2 = Directory.GetFiles(path2);
for (int j = 0; j < files2.Length; j++)
{
LoadExternalClip(files2[j], folderDict[fileName]);
}
}
}
private static void LoadExternalClip(string path, Dictionary<string, AudioClip> dict)
{
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: 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_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Expected O, but got Unknown
//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
//IL_00ce: Invalid comparison between Unknown and I4
if (path.EndsWith(".txt") || !path.Contains("."))
{
return;
}
string text = Path.GetExtension(path).ToLower();
if (text != ".ogg" && text != ".wav" && text != ".mp3")
{
return;
}
string text2 = "file:///" + path.Replace("\\", "/");
AudioType audioType = GetAudioType(text);
UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(text2, audioType);
DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)audioClip.downloadHandler;
val.streamAudio = false;
val.compressed = false;
audioClip.SendWebRequest();
float num = 10f;
float num2 = 0f;
while (!audioClip.isDone && num2 < num)
{
Thread.Sleep(10);
num2 += 0.01f;
}
if ((int)audioClip.result == 1)
{
AudioClip val2 = ((val != null) ? val.audioClip : null);
if ((Object)(object)val2 != (Object)null && val2.length > 0f && val2.samples > 0)
{
string key = (((Object)val2).name = Path.GetFileNameWithoutExtension(path));
dict[key] = val2;
}
else
{
Plugin.Log.LogWarning((object)("Invalid external clip: " + path));
}
}
else
{
Plugin.Log.LogWarning((object)("Failed to load external: " + path + " - " + audioClip.error));
}
audioClip.Dispose();
}
private static (string folderName, string clipName) ParseResourcePath(string fileName)
{
string item = null;
string item2 = null;
if (fileName.Contains("\\"))
{
string[] array = fileName.Split('\\');
if (array.Length >= 2)
{
item = array[0];
item2 = Path.GetFileNameWithoutExtension(array[^1]);
}
}
else
{
string[] array2 = fileName.Split('.');
if (array2.Length >= 3)
{
item = array2[0];
item2 = string.Join(".", array2.Skip(1).Take(array2.Length - 2));
}
else if (array2.Length >= 2)
{
item = null;
item2 = array2[0];
}
}
return (item, item2);
}
private static string DetectAudioFormat(byte[] audioData)
{
if (audioData.Length < 4)
{
return ".ogg";
}
if (audioData[0] == 82 && audioData[1] == 73 && audioData[2] == 70 && audioData[3] == 70)
{
return ".wav";
}
if ((audioData[0] == 73 && audioData[1] == 68 && audioData[2] == 51) || (audioData[0] == byte.MaxValue && (audioData[1] & 0xE0) == 224))
{
return ".mp3";
}
return ".ogg";
}
private static AudioType GetAudioType(string extension)
{
if (!(extension == ".wav"))
{
if (extension == ".mp3")
{
return (AudioType)13;
}
return (AudioType)14;
}
return (AudioType)20;
}
}
[HarmonyPatch(typeof(MusicMan), "Awake")]
public static class MusicMan_Awake_Patch
{
private static void Postfix(MusicMan __instance)
{
if (!Plugin.ModEnabled.Value)
{
return;
}
for (int i = 0; i < __instance.m_music.Count; i++)
{
NamedMusic instance = __instance.m_music[i];
string value = ReflectionCache.GetValue<string>(instance, "m_name");
if (Plugin.CustomMusicList.ContainsKey(value))
{
AudioClip[] value2 = Plugin.CustomMusicList[value].Values.ToArray();
ReflectionCache.SetValue(instance, "m_clips", value2);
continue;
}
string text = StringMatchCache.FindMatch(value);
if (text != null && Plugin.CustomMusicList.ContainsKey(text))
{
AudioClip[] value3 = Plugin.CustomMusicList[text].Values.ToArray();
ReflectionCache.SetValue(instance, "m_clips", value3);
continue;
}
AudioClip[] value4 = ReflectionCache.GetValue<AudioClip[]>(instance, "m_clips");
if (value4 == null)
{
continue;
}
for (int j = 0; j < value4.Length; j++)
{
if ((Object)(object)value4[j] != (Object)null && Plugin.CustomMusic.ContainsKey(((Object)value4[j]).name))
{
value4[j] = Plugin.CustomMusic[((Object)value4[j]).name];
}
}
}
}
}
[HarmonyPatch(typeof(MusicMan), "UpdateMusic")]
public static class MusicMan_UpdateMusic_Patch
{
private static void Prefix(ref object ___m_queuedMusic, AudioSource ___m_musicSource)
{
if (!Plugin.ModEnabled.Value)
{
return;
}
if (___m_queuedMusic != null)
{
ReflectionCache.SetValue(___m_queuedMusic, "m_volume", Plugin.MusicVolume.Value);
}
if ((Object)(object)___m_musicSource != (Object)null && ___m_musicSource.loop)
{
if (!___m_musicSource.isPlaying)
{
___m_musicSource.loop = false;
}
else if ((Object)(object)___m_musicSource.clip != (Object)null && ___m_musicSource.clip.length - ___m_musicSource.time < 0.5f)
{
___m_musicSource.loop = false;
}
}
}
}
[HarmonyPatch(typeof(MusicLocation), "Awake")]
public static class MusicLocation_Awake_Patch
{
private static void Postfix(ref AudioSource ___m_audioSource, ref float ___m_baseVolume)
{
if (!Plugin.ModEnabled.Value || (Object)(object)___m_audioSource == (Object)null || (Object)(object)___m_audioSource.clip == (Object)null)
{
return;
}
string name = ((Object)___m_audioSource.clip).name;
if (!Plugin.CustomMusic.ContainsKey(name))
{
return;
}
AudioClip val = Plugin.CustomMusic[name];
if (!((Object)(object)val == (Object)null) && !(val.length <= 0f))
{
bool isPlaying = ___m_audioSource.isPlaying;
if (isPlaying)
{
___m_audioSource.Stop();
}
___m_audioSource.clip = val;
___m_audioSource.time = 0f;
___m_baseVolume *= Plugin.LocationVolMultiplier.Value;
if (isPlaying)
{
___m_audioSource.Play();
}
}
}
}
[HarmonyPatch(typeof(AudioMan), "Awake")]
public static class AudioMan_Awake_Patch
{
private static void Postfix(AudioMan __instance, IList ___m_randomAmbients, AudioSource ___m_oceanAmbientSource, AudioSource ___m_windLoopSource)
{
if (Plugin.ModEnabled.Value)
{
for (int i = 0; i < ___m_randomAmbients.Count; i++)
{
object? obj = ___m_randomAmbients[i];
string value = ReflectionCache.GetValue<string>(obj, "m_name");
ReplaceAmbientClips(obj, "m_randomAmbientClips");
ReplaceAmbientClips(obj, "m_randomAmbientClipsDay");
ReplaceAmbientClips(obj, "m_randomAmbientClipsNight");
ReplaceAmbientList(obj, value, "_day", "m_randomAmbientClipsDay");
ReplaceAmbientList(obj, value, "_night", "m_randomAmbientClipsNight");
ReplaceAmbientList(obj, value, "", "m_randomAmbientClips");
}
if (Plugin.CustomAmbient.ContainsKey("ocean"))
{
___m_oceanAmbientSource.clip = Plugin.CustomAmbient["ocean"];
}
if (Plugin.CustomAmbient.ContainsKey("wind"))
{
___m_windLoopSource.clip = Plugin.CustomAmbient["wind"];
}
}
}
private static void ReplaceAmbientClips(object ambient, string fieldName)
{
IList value = ReflectionCache.GetValue<IList>(ambient, fieldName);
if (value == null)
{
return;
}
for (int i = 0; i < value.Count; i++)
{
object? obj = value[i];
AudioClip val = (AudioClip)((obj is AudioClip) ? obj : null);
if ((Object)(object)val != (Object)null && Plugin.CustomAmbient.ContainsKey(((Object)val).name))
{
value[i] = Plugin.CustomAmbient[((Object)val).name];
}
}
}
private static void ReplaceAmbientList(object ambient, string ambientName, string suffix, string fieldName)
{
string key = ambientName + suffix;
if (!Plugin.CustomAmbientList.ContainsKey(key))
{
return;
}
IList value = ReflectionCache.GetValue<IList>(ambient, fieldName);
if (value == null)
{
return;
}
List<AudioClip> list = Plugin.CustomAmbientList[key].Values.ToList();
value.Clear();
foreach (AudioClip item in list)
{
value.Add(item);
}
}
}
[HarmonyPatch(typeof(AudioMan), "QueueAmbientLoop")]
public static class AudioMan_QueueAmbientLoop_Patch
{
private static void Prefix(ref float ___m_queuedAmbientVol, ref float ___m_ambientVol, ref float vol)
{
if (Plugin.ModEnabled.Value)
{
vol = Plugin.AmbientVolume.Value;
___m_ambientVol = Plugin.AmbientVolume.Value;
___m_queuedAmbientVol = Plugin.AmbientVolume.Value;
}
}
}
[HarmonyPatch(typeof(ZSFX), "Awake")]
public static class ZSFX_Awake_Patch
{
private static void Postfix(ZSFX __instance)
{
if (!Plugin.ModEnabled.Value)
{
return;
}
string zSFXName = AudioUtils.GetZSFXName(__instance);
if (Plugin.CustomSFXList.TryGetValue(zSFXName, out var value))
{
__instance.m_audioClips = (from x in value
orderby x.Key
select x.Value).ToArray();
}
else
{
if (__instance.m_audioClips == null)
{
return;
}
for (int i = 0; i < __instance.m_audioClips.Length; i++)
{
if ((Object)(object)__instance.m_audioClips[i] != (Object)null && Plugin.CustomSFX.ContainsKey(((Object)__instance.m_audioClips[i]).name))
{
__instance.m_audioClips[i] = Plugin.CustomSFX[((Object)__instance.m_audioClips[i]).name];
}
}
}
}
}
[HarmonyPatch(typeof(TeleportWorld), "Awake")]
public static class TeleportWorld_Awake_Patch
{
private static void Postfix(TeleportWorld __instance)
{
if (!Plugin.ModEnabled.Value || !Plugin.CustomSFX.ContainsKey("portal"))
{
return;
}
AudioSource componentInChildren = ((Component)__instance).GetComponentInChildren<AudioSource>();
if (!((Object)(object)componentInChildren != (Object)null))
{
return;
}
AudioClip val = Plugin.CustomSFX["portal"];
if (!((Object)(object)val == (Object)null) && !(val.length <= 0f))
{
bool isPlaying = componentInChildren.isPlaying;
componentInChildren.Stop();
componentInChildren.clip = val;
componentInChildren.time = 0f;
if (isPlaying || componentInChildren.playOnAwake)
{
componentInChildren.Play();
}
}
}
}
[HarmonyPatch(typeof(Fireplace), "Start")]
public static class Fireplace_Start_Patch
{
private static void Postfix(Fireplace __instance)
{
if (!Plugin.ModEnabled.Value)
{
return;
}
string name = ((Object)__instance).name;
AudioSource val = null;
AudioClip val2 = null;
if (name.Contains("groundtorch") && Plugin.CustomSFX.ContainsKey("groundtorch"))
{
GameObject enabledObject = __instance.m_enabledObject;
val = ((enabledObject != null) ? enabledObject.GetComponentInChildren<AudioSource>() : null);
val2 = Plugin.CustomSFX["groundtorch"];
}
else if (name.Contains("walltorch") && Plugin.CustomSFX.ContainsKey("walltorch"))
{
GameObject enabledObjectHigh = __instance.m_enabledObjectHigh;
val = ((enabledObjectHigh != null) ? enabledObjectHigh.GetComponentInChildren<AudioSource>() : null);
if ((Object)(object)val == (Object)null)
{
GameObject enabledObject2 = __instance.m_enabledObject;
val = ((enabledObject2 != null) ? enabledObject2.GetComponentInChildren<AudioSource>() : null);
}
val2 = Plugin.CustomSFX["walltorch"];
}
else if (name.Contains("fire_pit") && Plugin.CustomSFX.ContainsKey("fire_pit"))
{
GameObject enabledObjectHigh2 = __instance.m_enabledObjectHigh;
val = ((enabledObjectHigh2 != null) ? enabledObjectHigh2.GetComponentInChildren<AudioSource>() : null);
val2 = Plugin.CustomSFX["fire_pit"];
}
else if (name.Contains("bonfire") && Plugin.CustomSFX.ContainsKey("bonfire"))
{
GameObject enabledObjectHigh3 = __instance.m_enabledObjectHigh;
val = ((enabledObjectHigh3 != null) ? enabledObjectHigh3.GetComponentInChildren<AudioSource>() : null);
val2 = Plugin.CustomSFX["bonfire"];
}
else if (name.Contains("hearth") && Plugin.CustomSFX.ContainsKey("hearth"))
{
GameObject enabledObjectHigh4 = __instance.m_enabledObjectHigh;
val = ((enabledObjectHigh4 != null) ? enabledObjectHigh4.GetComponentInChildren<AudioSource>() : null);
val2 = Plugin.CustomSFX["hearth"];
}
if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null && val2.length > 0f)
{
SafeReplaceClip(val, val2);
}
}
private static void SafeReplaceClip(AudioSource source, AudioClip newClip)
{
bool isPlaying = source.isPlaying;
source.Stop();
source.clip = newClip;
source.time = 0f;
if (isPlaying || source.playOnAwake)
{
source.Play();
}
}
}
[HarmonyPatch(typeof(EnvMan), "Awake")]
public static class EnvMan_Awake_Patch
{
private static void Postfix(EnvMan __instance)
{
if (!Plugin.ModEnabled.Value)
{
return;
}
for (int i = 0; i < __instance.m_environments.Count; i++)
{
string name = __instance.m_environments[i].m_name;
foreach (string key in Plugin.CustomMusicList.Keys)
{
string text = name.ToLower();
string text2 = key.ToLower();
if (text == text2 || text.Contains(text2) || text2.Contains(text))
{
string name2 = __instance.m_environments[i].m_name;
__instance.m_environments[i].m_name = key;
AddMusicToEnvironment(__instance, i, "");
__instance.m_environments[i].m_name = name2;
break;
}
}
AddMusicToEnvironment(__instance, i, "Morning");
AddMusicToEnvironment(__instance, i, "Day");
AddMusicToEnvironment(__instance, i, "Evening");
AddMusicToEnvironment(__instance, i, "Night");
}
}
private static void AddMusicToEnvironment(EnvMan envMan, int index, string timeOfDay)
{
string text = envMan.m_environments[index].m_name + timeOfDay;
if (Plugin.CustomMusicList.ContainsKey(text))
{
switch (timeOfDay)
{
case "Morning":
envMan.m_environments[index].m_musicMorning = text;
break;
case "Day":
envMan.m_environments[index].m_musicDay = text;
break;
case "Evening":
envMan.m_environments[index].m_musicEvening = text;
break;
case "Night":
envMan.m_environments[index].m_musicNight = text;
break;
}
object obj = Activator.CreateInstance(((object)MusicMan.instance.m_music[0]).GetType());
ReflectionCache.SetValue(obj, "m_name", text);
ReflectionCache.SetValue(obj, "m_clips", (from x in Plugin.CustomMusicList[text]
orderby x.Key
select x.Value).ToArray());
ReflectionCache.SetValue(obj, "m_loop", true);
ReflectionCache.SetValue(obj, "m_ambientMusic", true);
ReflectionCache.SetValue(obj, "m_resume", true);
((IList)MusicMan.instance.m_music).Add(obj);
}
}
}
[HarmonyPatch(typeof(Terminal), "InputText")]
public static class Terminal_InputText_Patch
{
private static bool Prefix(Terminal __instance)
{
if (!Plugin.ModEnabled.Value)
{
return true;
}
FieldInfo field = ((object)__instance).GetType().GetField("m_input");
if (field == null)
{
return true;
}
object value = field.GetValue(__instance);
if (value == null)
{
return true;
}
PropertyInfo property = value.GetType().GetProperty("text");
if (property == null)
{
return true;
}
string text = (string)property.GetValue(value);
switch (text.ToLower())
{
case "captainaudio reset":
((BaseUnityPlugin)Plugin.instance).Config.Reload();
((BaseUnityPlugin)Plugin.instance).Config.Save();
AddOutput(__instance, text);
AddOutput(__instance, "<color=#00BFFF>Captain Audio config reloaded</color>");
return false;
case "captainaudio music":
AddOutput(__instance, text);
if ((Object)(object)EnvMan.instance != (Object)null)
{
Player localPlayer = Player.m_localPlayer;
string arg = ((localPlayer != null && localPlayer.IsSafeInHome()) ? "home" : EnvMan.instance.GetAmbientMusic());
AddOutput(__instance, $"<color=#00BFFF>Current: {arg} | Folders: {Plugin.CustomMusicList.Count} | Clips: {Plugin.CustomMusic.Count}</color>");
}
return false;
case "captainaudio env":
AddOutput(__instance, text);
if ((Object)(object)EnvMan.instance != (Object)null)
{
AddOutput(__instance, "<color=#00BFFF>Environment: " + EnvMan.instance.GetCurrentEnvironment().m_name + "</color>");
}
else
{
AddOutput(__instance, "<color=#00BFFF>Must be called in-game</color>");
}
return false;
default:
return true;
}
}
private static void AddOutput(Terminal terminal, string text)
{
Traverse.Create((object)terminal).Method("AddString", new object[1] { text }).GetValue();
}
}
[BepInPlugin("captain.CaptainAudio", "Captain Audio", "1.2.2")]
public class Plugin : BaseUnityPlugin
{
public static Plugin instance;
public static ConfigEntry<bool> ModEnabled;
public static ConfigEntry<float> MusicVolume;
public static ConfigEntry<float> AmbientVolume;
public static ConfigEntry<float> LocationVolMultiplier;
public static ConfigEntry<int> LoadRetryCount;
public static ConfigEntry<bool> EnableFallback;
public static Dictionary<string, AudioClip> CustomMusic = new Dictionary<string, AudioClip>();
public static Dictionary<string, Dictionary<string, AudioClip>> CustomMusicList = new Dictionary<string, Dictionary<string, AudioClip>>();
public static Dictionary<string, AudioClip> CustomAmbient = new Dictionary<string, AudioClip>();
public static Dictionary<string, Dictionary<string, AudioClip>> CustomAmbientList = new Dictionary<string, Dictionary<string, AudioClip>>();
public static Dictionary<string, AudioClip> CustomSFX = new Dictionary<string, AudioClip>();
public static Dictionary<string, Dictionary<string, AudioClip>> CustomSFXList = new Dictionary<string, Dictionary<string, AudioClip>>();
public static ManualLogSource Log { get; private set; }
private void Awake()
{
instance = this;
Log = ((BaseUnityPlugin)this).Logger;
ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable this mod");
MusicVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MusicVolume", 0.6f, "Music volume (0.0 - 1.0)");
AmbientVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "AmbientVolume", 0.3f, "Ambient volume (0.0 - 1.0)");
LocationVolMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LocationVolumeMultiplier", 5f, "Location music volume multiplier");
LoadRetryCount = ((BaseUnityPlugin)this).Config.Bind<int>("Advanced", "LoadRetryCount", 3, "Number of retries when audio loading fails");
EnableFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Advanced", "EnableFallback", true, "Use fallback to vanilla music on load failure");
if (!ModEnabled.Value)
{
return;
}
try
{
Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
((MonoBehaviour)this).StartCoroutine(AudioLoader.InitializeAsync());
Log.LogInfo((object)"<color=#00BFFF>Captain Audio v1.2.2 loaded</color>");
}
catch (Exception ex)
{
Log.LogError((object)("<color=#00BFFF>Critical error: " + ex.Message + "</color>"));
}
}
}
public static class AudioUtils
{
public static string GetZSFXName(ZSFX zsfx)
{
string name = ((Object)zsfx).name;
char[] anyOf = new char[2] { '(', ' ' };
int num = name.IndexOfAny(anyOf);
if (num == -1)
{
return name;
}
return name.Remove(num);
}
public static void SafeSetVolume(AudioSource source, float volume)
{
if ((Object)(object)source != (Object)null)
{
source.volume = Mathf.Clamp01(volume);
}
}
}
public static class ReflectionCache
{
private static readonly Dictionary<string, FieldInfo> _fieldCache = new Dictionary<string, FieldInfo>();
private static readonly Dictionary<string, PropertyInfo> _propertyCache = new Dictionary<string, PropertyInfo>();
public static FieldInfo GetField(Type type, string fieldName)
{
string key = type.FullName + "." + fieldName;
if (!_fieldCache.ContainsKey(key))
{
_fieldCache[key] = type.GetField(fieldName);
}
return _fieldCache[key];
}
public static void SetValue(object instance, string fieldName, object value)
{
GetField(instance.GetType(), fieldName)?.SetValue(instance, value);
}
public static T GetValue<T>(object instance, string fieldName)
{
FieldInfo field = GetField(instance.GetType(), fieldName);
if (!(field != null))
{
return default(T);
}
return (T)field.GetValue(instance);
}
}
public static class StringMatchCache
{
private static readonly Dictionary<string, string> _lowerCaseCache = new Dictionary<string, string>();
private static readonly Dictionary<string, string> _musicMappingCache = new Dictionary<string, string>();
private static bool _initialized = false;
public static string GetLowerCase(string input)
{
if (string.IsNullOrEmpty(input))
{
return string.Empty;
}
if (!_lowerCaseCache.ContainsKey(input))
{
_lowerCaseCache[input] = input.ToLower();
}
return _lowerCaseCache[input];
}
public static void Initialize()
{
if (!_initialized)
{
AddMapping("menu", "menu", "start", "intro", "mainmenu", "main_menu", "title");
AddMapping("meadows", "meadow", "meadows");
AddMapping("blackforest", "forest", "dark", "blackforest");
AddMapping("swamp", "swamp");
_initialized = true;
}
}
private static void AddMapping(string target, params string[] sources)
{
foreach (string text in sources)
{
_musicMappingCache[text + "->" + target] = target;
}
}
public static string FindMatch(string musicName)
{
if (string.IsNullOrEmpty(musicName))
{
return null;
}
if (Plugin.CustomMusicList.ContainsKey(musicName))
{
return musicName;
}
if (_musicMappingCache.ContainsKey(musicName))
{
return _musicMappingCache[musicName];
}
string lowerCase = GetLowerCase(musicName);
foreach (string key in Plugin.CustomMusicList.Keys)
{
string lowerCase2 = GetLowerCase(key);
if (lowerCase.Contains(lowerCase2) || lowerCase2.Contains(lowerCase))
{
_musicMappingCache[musicName] = key;
return key;
}
}
return null;
}
}
}