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.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Photon.Pun;
using Photon.Voice.Unity;
using REPOSoundBoard.Config;
using REPOSoundBoard.Hotkeys;
using REPOSoundBoard.Patches;
using REPOSoundBoard.Sound;
using UnityEngine;
using UnityEngine.Networking;
[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("REPOSoundBoard")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+d5cf5ba6d1d300a00d41a7ff3192bca516660c18")]
[assembly: AssemblyProduct("REPOSoundBoard")]
[assembly: AssemblyTitle("REPOSoundBoard")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace REPOSoundBoard
{
[BepInPlugin("com.moli.repo-soundboard", "REPOSoundBoard", "0.1.1")]
public class REPOSoundBoard : BaseUnityPlugin
{
private const string GUID = "com.moli.repo-soundboard";
private const string NAME = "REPOSoundBoard";
private const string VERSION = "0.1.1";
private static Harmony _harmony = new Harmony("com.moli.repo-soundboard");
public HotkeyManager HotkeyManager;
public SoundBoard SoundBoard;
public static REPOSoundBoard Instance { get; private set; }
public ManualLogSource LOG => ((BaseUnityPlugin)this).Logger;
public AppConfig Config { get; private set; }
private void Awake()
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Expected O, but got Unknown
if (!((Object)(object)Instance != (Object)null))
{
Instance = this;
Config = AppConfig.LoadConfig();
GameObject val = new GameObject("REPOSoundBoardMod");
Object.DontDestroyOnLoad((Object)(object)val);
((Object)val).hideFlags = (HideFlags)61;
HotkeyManager = val.AddComponent<HotkeyManager>();
SoundBoard = val.AddComponent<SoundBoard>();
SoundBoard.LoadConfig(Config.SoundBoard);
_harmony.PatchAll(typeof(PlayerVoiceChatPatch));
}
}
}
}
namespace REPOSoundBoard.Sound
{
public static class AudioExtractor
{
private static bool IsFfmpegInstalled()
{
try
{
using Process process = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = "-version",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
});
if (process == null)
{
return false;
}
process.WaitForExit(2000);
return process.ExitCode == 0 || process.ExitCode == 1;
}
catch
{
return false;
}
}
public static bool ExtractAudioFromVideo(string videoPath, string outputAudioPath)
{
if (!IsFfmpegInstalled())
{
REPOSoundBoard.Instance.LOG.LogError((object)"Cannot extract audio from video: ffmpeg is not installed. You can download ffmpeg from https://www.ffmpeg.org/download.html");
return false;
}
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = "ffmpeg";
processStartInfo.Arguments = "-i \"" + videoPath + "\" -vn -acodec pcm_s16le -ar 48000 -ac 1 \"" + outputAudioPath + "\"";
processStartInfo.UseShellExecute = false;
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
ProcessStartInfo startInfo = processStartInfo;
try
{
using Process process = Process.Start(startInfo);
process.WaitForExit();
return process.ExitCode == 0;
}
catch (Exception ex)
{
REPOSoundBoard.Instance.LOG.LogError((object)("Failed to extract audio with ffmpeg: " + ex.Message));
return false;
}
}
}
public class MediaClip
{
[CompilerGenerated]
private sealed class <Load>d__18 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public MediaClip <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <Load>d__18(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
int num = <>1__state;
MediaClip mediaClip = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
if (mediaClip.IsVideoFile())
{
<>2__current = mediaClip.LoadVideo();
<>1__state = 1;
return true;
}
<>2__current = mediaClip.LoadAudio("file:///" + mediaClip._source);
<>1__state = 2;
return true;
case 1:
<>1__state = -1;
break;
case 2:
<>1__state = -1;
break;
}
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 <LoadAudio>d__20 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string source;
public MediaClip <>4__this;
private UnityWebRequest <www>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <LoadAudio>d__20(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();
}
}
<www>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: Unknown result type (might be due to invalid IL or missing references)
//IL_00a5: Invalid comparison between Unknown and I4
bool result;
try
{
int num = <>1__state;
MediaClip mediaClip = <>4__this;
switch (num)
{
default:
result = false;
break;
case 0:
{
<>1__state = -1;
AudioType audioTypeFromExtension = GetAudioTypeFromExtension(source);
if ((int)audioTypeFromExtension == 0)
{
REPOSoundBoard.Instance.LOG.LogError((object)("Unsupported media format for file: " + mediaClip._source));
result = false;
break;
}
<www>5__2 = UnityWebRequestMultimedia.GetAudioClip(source, audioTypeFromExtension);
<>1__state = -3;
<>2__current = <www>5__2.SendWebRequest();
<>1__state = 1;
result = true;
break;
}
case 1:
<>1__state = -3;
if ((int)<www>5__2.result == 1)
{
AudioClip content = DownloadHandlerAudioClip.GetContent(<www>5__2);
((Object)content).name = Path.GetFileName(mediaClip._source);
AudioClip val = SoundConverter.ConvertStereoToMono(content);
if ((Object)(object)val == (Object)null)
{
mediaClip.IsLoaded = false;
mediaClip.FailedToLoad = true;
result = false;
<>m__Finally1();
break;
}
mediaClip.AudioClip = val;
mediaClip.IsLoaded = true;
mediaClip.FailedToLoad = false;
}
else
{
REPOSoundBoard.Instance.LOG.LogError((object)("Failed to load media: " + <www>5__2.error + ". Path: " + source));
mediaClip.IsLoaded = false;
mediaClip.FailedToLoad = true;
}
<>m__Finally1();
<www>5__2 = null;
result = false;
break;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
return result;
}
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__2 != null)
{
((IDisposable)<www>5__2).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <LoadVideo>d__19 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public MediaClip <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <LoadVideo>d__19(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
int num = <>1__state;
MediaClip mediaClip = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
if (!File.Exists(mediaClip._videoAudioPath))
{
REPOSoundBoard.Instance.LOG.LogInfo((object)("Found video. Extracting audio to " + mediaClip._videoAudioPath + "..."));
if (!AudioExtractor.ExtractAudioFromVideo(mediaClip._source, mediaClip._videoAudioPath))
{
mediaClip.IsLoaded = false;
mediaClip.FailedToLoad = true;
return false;
}
REPOSoundBoard.Instance.LOG.LogInfo((object)"Audio successfully extracted");
}
<>2__current = mediaClip.LoadAudio("file:///" + mediaClip._videoAudioPath);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
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 readonly string _source;
private static readonly string[] VideoExtensions = new string[5] { ".mp4", ".webm", ".avi", ".mov", ".ogg" };
private string _videoAudioPath;
public bool IsLoaded { get; private set; }
public bool FailedToLoad { get; private set; }
public AudioClip AudioClip { get; private set; }
public MediaClip(string source)
{
_source = source;
IsLoaded = false;
FailedToLoad = false;
_videoAudioPath = Path.Combine(Path.GetDirectoryName(_source), "audio_" + Path.GetFileName(_source) + ".wav");
}
private static AudioType GetAudioTypeFromExtension(string file)
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
return (AudioType)(Path.GetExtension(file).ToLowerInvariant() switch
{
".wav" => 20,
".mp3" => 13,
".aiff" => 2,
_ => 0,
});
}
private bool IsVideoFile()
{
string value = Path.GetExtension(_source).ToLowerInvariant();
return VideoExtensions.Contains(value);
}
[IteratorStateMachine(typeof(<Load>d__18))]
public IEnumerator Load()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <Load>d__18(0)
{
<>4__this = this
};
}
[IteratorStateMachine(typeof(<LoadVideo>d__19))]
private IEnumerator LoadVideo()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <LoadVideo>d__19(0)
{
<>4__this = this
};
}
[IteratorStateMachine(typeof(<LoadAudio>d__20))]
private IEnumerator LoadAudio(string source)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <LoadAudio>d__20(0)
{
<>4__this = this,
source = source
};
}
}
public class SoundBoard : MonoBehaviour
{
[CompilerGenerated]
private sealed class <Play>d__11 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public SoundBoard <>4__this;
public SoundButton soundButton;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <Play>d__11(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0103: Unknown result type (might be due to invalid IL or missing references)
//IL_010d: Expected O, but got Unknown
int num = <>1__state;
SoundBoard soundBoard = <>4__this;
switch (num)
{
default:
return false;
case 0:
<>1__state = -1;
if ((Object)(object)soundBoard._recorder == (Object)null || (Object)(object)soundBoard._audioSource == (Object)null || !soundButton.Clip.IsLoaded)
{
return false;
}
if (soundBoard._isPlaying)
{
soundBoard.StopCurrent();
}
soundBoard._isPlaying = true;
soundBoard._currentSoundButton = soundButton;
soundBoard._recorder.TransmitEnabled = false;
soundBoard._recorder.SourceType = (InputSourceType)1;
soundBoard._recorder.AudioClip = soundButton.Clip.AudioClip;
soundBoard._recorder.TransmitEnabled = true;
soundBoard._audioSource.clip = soundButton.Clip.AudioClip;
soundBoard._audioSource.volume = soundButton.Volume;
soundBoard._audioSource.Play();
<>2__current = (object)new WaitForSeconds(soundButton.Clip.AudioClip.length);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
soundBoard.StopCurrent();
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 List<SoundButton> _soundButtons = new List<SoundButton>();
private Recorder _recorder;
private AudioSource _audioSource;
private bool _isPlaying;
private SoundButton _currentSoundButton;
private Coroutine _playCoroutine;
public void LoadConfig(SoundBoardConfig config)
{
config.StopHotkey.OnPressed(StopCurrent);
REPOSoundBoard.Instance.HotkeyManager.RegisterHotkey(config.StopHotkey);
foreach (SoundBoardConfig.SoundButtonConfig soundButton2 in config.SoundButtons)
{
if (soundButton2.Hotkey != null && soundButton2.Path != null)
{
MediaClip mediaClip = new MediaClip(soundButton2.Path);
((MonoBehaviour)this).StartCoroutine(mediaClip.Load());
SoundButton soundButton = new SoundButton(mediaClip, soundButton2.Hotkey, soundButton2.Volume);
AddSoundButton(soundButton);
}
}
}
public void AddSoundButton(SoundButton soundButton)
{
soundButton.Hotkey.OnPressed(delegate
{
_playCoroutine = ((MonoBehaviour)this).StartCoroutine(Play(soundButton));
});
REPOSoundBoard.Instance.HotkeyManager.RegisterHotkey(soundButton.Hotkey);
_soundButtons.Add(soundButton);
}
public void RemoveSoundButton(SoundButton soundButton)
{
_soundButtons.Remove(soundButton);
REPOSoundBoard.Instance.HotkeyManager.UnregisterHotkey(soundButton.Hotkey);
}
public void ChangeRecorder(Recorder recorder)
{
StopCurrent();
_recorder = recorder;
}
public void ChangeAudioSource(AudioSource source)
{
StopCurrent();
_audioSource = source;
}
[IteratorStateMachine(typeof(<Play>d__11))]
private IEnumerator Play(SoundButton soundButton)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <Play>d__11(0)
{
<>4__this = this,
soundButton = soundButton
};
}
private void StopCurrent()
{
if (!((Object)(object)_recorder == (Object)null) && _isPlaying)
{
if (_playCoroutine != null)
{
((MonoBehaviour)this).StopCoroutine(_playCoroutine);
_playCoroutine = null;
}
_recorder.SourceType = (InputSourceType)0;
_recorder.AudioClip = null;
_recorder.TransmitEnabled = true;
if ((Object)(object)_audioSource != (Object)null)
{
_audioSource.Stop();
}
_isPlaying = false;
_currentSoundButton = null;
}
}
}
public class SoundButton
{
public MediaClip Clip { get; private set; }
public Hotkey Hotkey { get; private set; }
public float Volume { get; private set; }
public SoundButton(MediaClip clip, Hotkey hotkey, float volume)
{
Clip = clip;
Hotkey = hotkey;
Volume = volume;
}
}
public class SoundConverter
{
public static AudioClip ConvertStereoToMono(AudioClip stereoClip)
{
if (stereoClip.channels == 1)
{
return stereoClip;
}
float[] array = new float[stereoClip.samples * stereoClip.channels];
stereoClip.GetData(array, 0);
int samples = stereoClip.samples;
float[] array2 = new float[samples];
for (int i = 0; i < samples; i++)
{
array2[i] = (array[i * 2] + array[i * 2 + 1]) * 0.5f;
}
try
{
AudioClip obj = AudioClip.Create(((Object)stereoClip).name + " (Mono)", samples, 1, stereoClip.frequency, false);
obj.SetData(array2, 0);
return obj;
}
catch (Exception ex)
{
REPOSoundBoard.Instance.LOG.LogWarning((object)("Failed to convert clip " + ((Object)stereoClip).name + " to mono. Error: " + ex.Message));
return null;
}
}
}
}
namespace REPOSoundBoard.Patches
{
public class PlayerVoiceChatPatch
{
[HarmonyPatch(typeof(PlayerVoiceChat), "Start")]
[HarmonyPostfix]
public static void PostStart(PlayerVoiceChat __instance, ref Recorder ___recorder, ref PhotonView ___photonView)
{
if (___photonView.IsMine)
{
AudioSource source = ((Component)__instance).gameObject.AddComponent<AudioSource>();
REPOSoundBoard.Instance.SoundBoard.ChangeRecorder(___recorder);
REPOSoundBoard.Instance.SoundBoard.ChangeAudioSource(source);
}
}
}
}
namespace REPOSoundBoard.Hotkeys
{
public class Hotkey
{
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
public List<KeyCode> Keys { get; set; } = new List<KeyCode>();
[CanBeNull]
[JsonIgnore]
private Action Callback { get; set; }
[JsonIgnore]
public bool IsPressed { get; set; }
public Hotkey()
{
}
public Hotkey(KeyCode key, [CanBeNull] Action callback)
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
Keys.Add(key);
Callback = callback;
}
public Hotkey(List<KeyCode> keys, [CanBeNull] Action callback)
{
Keys = keys;
}
public Hotkey OnPressed(Action callback)
{
Callback = callback;
return this;
}
public void Trigger()
{
if (Callback != null)
{
Callback();
}
}
}
public class HotkeyManager : MonoBehaviour
{
private List<Hotkey> _hotkeys = new List<Hotkey>();
public void RegisterHotkey(Hotkey hotkey)
{
_hotkeys.Add(hotkey);
}
public void UnregisterHotkey(Hotkey hotkey)
{
_hotkeys.Remove(hotkey);
}
public void Update()
{
foreach (Hotkey hotkey in _hotkeys)
{
if (hotkey.IsPressed)
{
HandlePressedHotkey(hotkey);
}
else
{
HandleReleasedHotkey(hotkey);
}
}
}
private static void HandlePressedHotkey(Hotkey hotkey)
{
if (hotkey.Keys.Any((KeyCode key) => !Input.GetKey(key)))
{
hotkey.IsPressed = false;
}
}
private static void HandleReleasedHotkey(Hotkey hotkey)
{
if (hotkey.Keys.All((KeyCode key) => Input.GetKey(key)))
{
hotkey.IsPressed = true;
hotkey.Trigger();
}
}
}
}
namespace REPOSoundBoard.Config
{
public class AppConfig
{
private static string ConfigFilePath;
public SoundBoardConfig SoundBoard { get; set; }
public AppConfig()
{
SoundBoard = new SoundBoardConfig();
}
public static AppConfig LoadConfig()
{
ConfigFilePath = Path.Combine(Paths.ConfigPath, "Moli.REPOSoundBoard.json");
AppConfig result = new AppConfig();
try
{
result = ConfigSerializer.DeserializeConfig(File.ReadAllText(ConfigFilePath));
return result;
}
catch (Exception ex)
{
REPOSoundBoard.Instance.LOG.LogWarning((object)("Failed to read config file" + ex.Message));
return result;
}
}
public void SaveToFile()
{
string contents = ConfigSerializer.SerializeConfig(this);
File.WriteAllText(ConfigFilePath, contents);
}
}
public class ConfigSerializer
{
public static string SerializeConfig(AppConfig config)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Expected O, but got Unknown
JsonSerializerSettings val = new JsonSerializerSettings
{
Formatting = (Formatting)1,
NullValueHandling = (NullValueHandling)1
};
return JsonConvert.SerializeObject((object)config, val);
}
public static AppConfig DeserializeConfig(string json)
{
return JsonConvert.DeserializeObject<AppConfig>(json);
}
}
public class SoundBoardConfig
{
public class SoundButtonConfig
{
public string Path { get; set; }
public float Volume { get; set; }
public Hotkey Hotkey { get; set; }
}
public Hotkey StopHotkey { get; set; }
public List<SoundButtonConfig> SoundButtons { get; set; }
public SoundBoardConfig()
{
SoundButtons = new List<SoundButtonConfig>();
StopHotkey = new Hotkey((KeyCode)104, null);
}
}
}