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.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using R2API.Utils;
using RiskOfOptions;
using RiskOfOptions.OptionConfigs;
using RiskOfOptions.Options;
using RoR2;
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("AudioOverlapFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0")]
[assembly: AssemblyInformationalVersion("1.0.0+be36210b70f9e3bb8e57752ca0f402c40f91a6d0")]
[assembly: AssemblyProduct("AudioOverlapFix")]
[assembly: AssemblyTitle("AudioOverlapFix")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.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 AudioOverlapFix
{
internal static class Log
{
internal static ManualLogSource _logSource;
internal static void Init(ManualLogSource logSource)
{
_logSource = logSource;
}
private static string getLogPrefix(string callerPath, string callerMemberName, int callerLineNumber)
{
int num = callerPath.LastIndexOf("AudioOverlapFix");
if (num >= 0)
{
callerPath = callerPath.Substring(num + "AudioOverlapFix".Length + 1);
}
return $"{callerPath}:{callerLineNumber} ({callerMemberName}) ";
}
internal static void Error(object data, [CallerFilePath] string callerPath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1)
{
_logSource.LogError((object)(getLogPrefix(callerPath, callerMemberName, callerLineNumber) + data));
}
internal static void Error_NoCallerPrefix(object data)
{
_logSource.LogError(data);
}
internal static void Fatal(object data, [CallerFilePath] string callerPath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1)
{
_logSource.LogFatal((object)(getLogPrefix(callerPath, callerMemberName, callerLineNumber) + data));
}
internal static void Fatal_NoCallerPrefix(object data)
{
_logSource.LogFatal(data);
}
internal static void Info(object data, [CallerFilePath] string callerPath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1)
{
_logSource.LogInfo((object)(getLogPrefix(callerPath, callerMemberName, callerLineNumber) + data));
}
internal static void Info_NoCallerPrefix(object data)
{
_logSource.LogInfo(data);
}
internal static void Message(object data, [CallerFilePath] string callerPath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1)
{
_logSource.LogMessage((object)(getLogPrefix(callerPath, callerMemberName, callerLineNumber) + data));
}
internal static void Message_NoCallerPrefix(object data)
{
_logSource.LogMessage(data);
}
internal static void Warning(object data, [CallerFilePath] string callerPath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1)
{
_logSource.LogWarning((object)(getLogPrefix(callerPath, callerMemberName, callerLineNumber) + data));
}
internal static void Warning_NoCallerPrefix(object data)
{
_logSource.LogWarning(data);
}
}
[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
[BepInPlugin("Gorakh.AudioOverlapFix", "AudioOverlapFix", "1.2.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Main : BaseUnityPlugin
{
public const string PluginGUID = "Gorakh.AudioOverlapFix";
public const string PluginAuthor = "Gorakh";
public const string PluginName = "AudioOverlapFix";
public const string PluginVersion = "1.2.1";
internal static ConfigEntry<float> DuplicateSoundCooldown;
internal static ConfigEntry<bool> ExcludeMithrixPizzaSound;
private static readonly HashSet<TimeStampedSoundEvent> _trackedSoundEvents = new HashSet<TimeStampedSoundEvent>(TimeStampedSoundEvent.EventIDComparer);
internal static Main Instance { get; private set; }
private void Awake()
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Expected O, but got Unknown
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Expected O, but got Unknown
Stopwatch stopwatch = Stopwatch.StartNew();
Log.Init(((BaseUnityPlugin)this).Logger);
Instance = this;
DuplicateSoundCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Sound Cooldown", 0f, new ConfigDescription("How many seconds to keep track of sounds and prevent it from playing again. Set to 0 to only prevent duplicate sounds within the same frame", (AcceptableValueBase)null, Array.Empty<object>()));
ExcludeMithrixPizzaSound = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Exclude Mithrix Pizza Attack", true, new ConfigDescription("Excludes Mithrix's pizza attack sound from the mod. The sound was seemingly designed with overlap in mind and will be very low volume if this is turned off.", (AcceptableValueBase)null, Array.Empty<object>()));
if (RiskOfOptionsCompat.Active)
{
RiskOfOptionsCompat.Init();
}
SoundEnginePatcher.OverridePostEvent += SoundEnginePatcher_OverridePostEvent;
SoundEventLibrary.Init();
stopwatch.Stop();
Log.Info_NoCallerPrefix($"Initialized in {stopwatch.Elapsed.TotalSeconds:F2} seconds");
}
private void LateUpdate()
{
if (DuplicateSoundCooldown != null)
{
float soundCooldown = DuplicateSoundCooldown.Value;
_trackedSoundEvents.RemoveWhere((TimeStampedSoundEvent evnt) => evnt.TimeSince > soundCooldown);
}
}
private void OnDestroy()
{
SoundEnginePatcher.OverridePostEvent -= SoundEnginePatcher_OverridePostEvent;
_trackedSoundEvents.Clear();
}
private static bool excludeSound(uint eventID)
{
if (SoundEventLibrary.IsInitialized && ExcludeMithrixPizzaSound.Value && eventID == SoundEventLibrary.Play_moonBrother_blueWall_explode)
{
return true;
}
return false;
}
private static bool tryPlaySound(uint eventID)
{
if (!excludeSound(eventID))
{
return _trackedSoundEvents.Add(new TimeStampedSoundEvent(eventID, Time.unscaledTime));
}
return true;
}
private static void SoundEnginePatcher_OverridePostEvent(uint eventID, ref bool post)
{
if (post && !tryPlaySound(eventID))
{
post = false;
}
}
}
internal static class RiskOfOptionsCompat
{
public static bool Active => Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions");
public static void Init()
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Expected O, but got Unknown
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Expected O, but got Unknown
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Expected O, but got Unknown
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Expected O, but got Unknown
//IL_00de: Unknown result type (might be due to invalid IL or missing references)
//IL_00e4: Expected O, but got Unknown
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
ModSettingsManager.SetModDescription("Mod options for Audio Overlap Fix", "Gorakh.AudioOverlapFix", "Audio Overlap Fix");
ModSettingsManager.AddOption((BaseOption)new StepSliderOption(Main.DuplicateSoundCooldown, new StepSliderConfig
{
formatString = "{0:F2}s",
min = 0f,
max = 2f,
increment = 0.01f
}), "Gorakh.AudioOverlapFix", "Audio Overlap Fix");
ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(Main.ExcludeMithrixPizzaSound, new CheckBoxConfig()), "Gorakh.AudioOverlapFix", "Audio Overlap Fix");
FileInfo fileInfo = findPluginIconFile();
if (fileInfo == null || !fileInfo.Exists)
{
return;
}
using FileStream fileStream = fileInfo.OpenRead();
byte[] array = new byte[fileStream.Length];
try
{
fileStream.Read(array, 0, array.Length);
}
catch (Exception arg)
{
Log.Error($"Exception reading icon file {fileInfo.FullName}: {arg}", "X:\\Git\\RoR2\\AudioOverlapFix\\AudioOverlapFix\\RiskOfOptionsCompat.cs", "Init", 46);
return;
}
Texture2D val = new Texture2D(1, 1);
if (ImageConversion.LoadImage(val, array))
{
ModSettingsManager.SetModIcon(Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), Vector2.zero), "Gorakh.AudioOverlapFix", "Audio Overlap Fix");
}
}
private static FileInfo findPluginIconFile()
{
try
{
return findPluginFileRecursive(new DirectoryInfo(Path.GetDirectoryName(((BaseUnityPlugin)Main.Instance).Info.Location)));
}
catch (SecurityException arg)
{
Log.Error($"Unable to find icon file. Encountered Exception: {arg}", "X:\\Git\\RoR2\\AudioOverlapFix\\AudioOverlapFix\\RiskOfOptionsCompat.cs", "findPluginIconFile", 74);
return null;
}
static FileInfo findPluginFileRecursive(DirectoryInfo directory)
{
if (directory == null || !directory.Exists)
{
return null;
}
return directory.EnumerateFiles("icon.png", SearchOption.TopDirectoryOnly).FirstOrDefault() ?? findPluginFileRecursive(directory.Parent);
}
}
}
public static class SoundEnginePatcher
{
public delegate void OverridePostEventDelegate(uint eventID, ref bool post);
private static bool _hasAppliedPatches;
private static event OverridePostEventDelegate _overridePostEvent;
public static event OverridePostEventDelegate OverridePostEvent
{
add
{
_overridePostEvent += value;
tryApplyPatches();
}
remove
{
_overridePostEvent -= value;
}
}
private static bool shouldPostEvent(uint eventID)
{
bool post = true;
SoundEnginePatcher._overridePostEvent?.Invoke(eventID, ref post);
return post;
}
private static bool shouldPostEvent(string eventName)
{
return shouldPostEvent(AkSoundEngine.GetIDFromString(eventName));
}
private static uint tryPostEvent(uint playingID, bool shouldPost)
{
if (!shouldPost && playingID != 0)
{
AkSoundEngine.StopPlayingID(playingID);
}
return playingID;
}
private static void tryApplyPatches()
{
//IL_0152: Unknown result type (might be due to invalid IL or missing references)
//IL_015c: Expected O, but got Unknown
//IL_0157: Unknown result type (might be due to invalid IL or missing references)
if (_hasAppliedPatches)
{
return;
}
MethodInfo[] methods = typeof(AkSoundEngine).GetMethods(BindingFlags.Static | BindingFlags.Public);
foreach (MethodInfo methodInfo in methods)
{
if (!string.Equals(methodInfo.Name, "PostEvent"))
{
continue;
}
if (methodInfo.ReturnType != typeof(uint))
{
Log.Warning("Unhandled return type '" + GeneralExtensions.FullDescription(methodInfo.ReturnType) + "'", "X:\\Git\\RoR2\\AudioOverlapFix\\AudioOverlapFix\\SoundEnginePatcher.cs", "tryApplyPatches", 67);
continue;
}
ParameterInfo[] parameters = methodInfo.GetParameters();
ParameterInfo eventIdOrNameParameter = null;
ParameterInfo[] array = parameters;
foreach (ParameterInfo parameterInfo in array)
{
if (parameterInfo.ParameterType == typeof(uint))
{
if (string.Equals(parameterInfo.Name, "in_eventID", StringComparison.OrdinalIgnoreCase))
{
eventIdOrNameParameter = parameterInfo;
break;
}
}
else if (parameterInfo.ParameterType == typeof(string) && string.Equals(parameterInfo.Name, "in_pszEventName", StringComparison.OrdinalIgnoreCase))
{
eventIdOrNameParameter = parameterInfo;
break;
}
}
if (eventIdOrNameParameter == null)
{
Log.Warning("Failed to find eventId or eventName parameter on method " + GeneralExtensions.FullDescription((MethodBase)methodInfo), "X:\\Git\\RoR2\\AudioOverlapFix\\AudioOverlapFix\\SoundEnginePatcher.cs", "tryApplyPatches", 96);
}
else
{
new ILHook((MethodBase)methodInfo, new Manipulator(tryPostEventManipulator));
}
void tryPostEventManipulator(ILContext il)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Expected O, but got Unknown
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
ILCursor val = new ILCursor(il);
while (val.TryGotoNext((MoveType)0, new Func<Instruction, bool>[1]
{
(Instruction x) => ILPatternMatchingExt.MatchRet(x)
}))
{
val.Emit(OpCodes.Ldarg, eventIdOrNameParameter.Position);
if (eventIdOrNameParameter.ParameterType == typeof(string))
{
val.EmitDelegate<Func<string, bool>>((Func<string, bool>)shouldPostEvent);
}
else
{
if (!(eventIdOrNameParameter.ParameterType == typeof(uint)))
{
throw new NotImplementedException("Event parameter type '" + GeneralExtensions.FullDescription(eventIdOrNameParameter.ParameterType) + "' is not implemented");
}
val.EmitDelegate<Func<uint, bool>>((Func<uint, bool>)shouldPostEvent);
}
val.EmitDelegate<Func<uint, bool, uint>>((Func<uint, bool, uint>)tryPostEvent);
int index = val.Index;
val.Index = index + 1;
}
}
}
_hasAppliedPatches = true;
}
}
public static class SoundEventLibrary
{
public static bool IsInitialized { get; private set; }
public static uint Play_moonBrother_blueWall_explode { get; private set; }
public static void Init()
{
if (AkSoundEngine.IsInitialized())
{
setupEventIDs();
}
else
{
RoR2Application.onUpdate += update;
}
static void update()
{
if (AkSoundEngine.IsInitialized())
{
setupEventIDs();
RoR2Application.onUpdate -= update;
}
}
}
private static void setupEventIDs()
{
IsInitialized = true;
Play_moonBrother_blueWall_explode = AkSoundEngine.GetIDFromString("Play_moonBrother_blueWall_explode");
}
}
public readonly struct TimeStampedSoundEvent
{
private sealed class _EventIDComparer : IEqualityComparer<TimeStampedSoundEvent>
{
public bool Equals(TimeStampedSoundEvent x, TimeStampedSoundEvent y)
{
return x.EventID == y.EventID;
}
public int GetHashCode(TimeStampedSoundEvent obj)
{
return obj.EventID.GetHashCode();
}
}
public readonly uint EventID;
public readonly float TimeStamp;
public static readonly IEqualityComparer<TimeStampedSoundEvent> EventIDComparer = new _EventIDComparer();
public float TimeSince => Time.unscaledTime - TimeStamp;
public TimeStampedSoundEvent(uint EventID, float TimeStamp)
{
this.EventID = EventID;
this.TimeStamp = TimeStamp;
}
}
}