Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RepoMusicPlayer v1.2.0
BepInEx/plugins/MusicPlayer/MusicPlayer.dll
Decompiled 9 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Photon.Voice.Unity; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyVersion("0.0.0.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 RepoMusicPlayer { internal static class FlacDecoder { public sealed class Result { public float[] Samples; public int Channels; public int SampleRate; public int BitsPerSample; public int FrameCount; } public static Result Decode(byte[] data) { if (data == null || data.Length < 8) { throw new InvalidOperationException("文件太小"); } int num = 0; if (data[0] != 102 || data[1] != 76 || data[2] != 97 || data[3] != 67) { if (data.Length <= 10 || data[0] != 73 || data[1] != 68 || data[2] != 51) { throw new InvalidOperationException("不是 FLAC 文件"); } int num2 = ((data[6] & 0x7F) << 21) | ((data[7] & 0x7F) << 14) | ((data[8] & 0x7F) << 7) | (data[9] & 0x7F); num = 10 + num2; if (num + 4 > data.Length || data[num] != 102 || data[num + 1] != 76 || data[num + 2] != 97 || data[num + 3] != 67) { throw new InvalidOperationException("不是 FLAC 文件"); } } num += 4; int num3 = 44100; int channels = 2; int bitsPerSample = 16; while (num + 4 <= data.Length) { byte b = data[num]; bool flag = (b & 0x80) != 0; int num4 = b & 0x7F; int num5 = (data[num + 1] << 16) | (data[num + 2] << 8) | data[num + 3]; num += 4; if (num4 == 0 && num5 >= 34 && num + 34 <= data.Length) { int num6 = num + 10; byte b2 = data[num6]; byte b3 = data[num6 + 1]; byte b4 = data[num6 + 2]; byte b5 = data[num6 + 3]; num3 = (b2 << 12) | (b3 << 4) | (b4 >> 4); channels = ((b4 >> 1) & 7) + 1; bitsPerSample = (((b4 & 1) << 4) | (b5 >> 4)) + 1; } num += num5; if (flag) { break; } } if (num >= data.Length) { throw new InvalidOperationException("没有音频帧"); } if (channels <= 0 || channels > 8) { throw new InvalidOperationException($"声道数异常:{channels}"); } BitReader bitReader = new BitReader(data, num); List<float[]>[] array = new List<float[]>[channels]; for (int i = 0; i < channels; i++) { array[i] = new List<float[]>(); } int num7 = 0; while (bitReader.RemainingBits > 32 && TryDecodeFrame(bitReader, ref channels, array, bitsPerSample, num3)) { num7++; } int num8 = 0; foreach (float[] item in array[0]) { num8 += item.Length; } if (num8 == 0) { throw new InvalidOperationException("没有解出音频数据"); } float[] array2 = new float[num8 * channels]; int num9 = 0; for (int j = 0; j < array[0].Count; j++) { int num10 = array[0][j].Length; for (int k = 0; k < num10; k++) { for (int l = 0; l < channels; l++) { array2[(num9 + k) * channels + l] = array[l][j][k]; } } num9 += num10; } return new Result { Samples = array2, Channels = channels, SampleRate = num3, BitsPerSample = bitsPerSample, FrameCount = num7 }; } private static bool TryDecodeFrame(BitReader reader, ref int channels, List<float[]>[] chunks, int bitsPerSample, int streamSampleRate) { int num = reader.ReadBits(14); if (num != 16382) { return false; } reader.SkipBits(1); reader.ReadBits(1); int num2 = reader.ReadBits(4); int num3 = reader.ReadBits(4); int num4 = reader.ReadBits(4); int code = reader.ReadBits(3); reader.SkipBits(1); if (num2 == 0 || num3 == 15 || num4 > 10) { return false; } ReadUtf8Number(reader); int num5 = ResolveBlockSize(reader, num2); ResolveSampleRate(reader, num3, streamSampleRate); int num6 = ResolveBitsPerSample(code, bitsPerSample); reader.SkipBits(8); int num7 = ((num4 < 8) ? (num4 + 1) : 2); if (num7 != channels) { return false; } int[][] array = new int[num7][]; for (int i = 0; i < num7; i++) { int num8 = num6; if ((num4 == 8 && i == 1) || (num4 == 9 && i == 0) || (num4 == 10 && i == 1)) { num8++; } array[i] = DecodeSubframe(reader, num5, num8); if (array[i] == null) { return false; } } reader.AlignToByte(); reader.SkipBits(16); ApplyChannelDecorrelation(num4, array, num5); float num9 = 1f / (float)(1 << num6 - 1); for (int j = 0; j < num7; j++) { float[] array2 = new float[num5]; int[] array3 = array[j]; for (int k = 0; k < num5; k++) { float num10 = (float)array3[k] * num9; array2[k] = ((num10 > 1f) ? 1f : ((num10 < -1f) ? (-1f) : num10)); } chunks[j].Add(array2); } return true; } private static int[] DecodeSubframe(BitReader reader, int blockSize, int bitsPerSample) { reader.SkipBits(1); int num = reader.ReadBits(6); int num2 = reader.ReadBits(1); int num3 = 0; if (num2 != 0) { num3 = reader.ReadUnary() + 1; } int num4 = bitsPerSample - num3; if (num4 <= 0 || num4 > 32) { return null; } int[] array; if (num == 0) { int num5 = reader.ReadSignedBits(num4); array = new int[blockSize]; for (int i = 0; i < blockSize; i++) { array[i] = num5; } } else if (num == 1) { array = new int[blockSize]; for (int j = 0; j < blockSize; j++) { array[j] = reader.ReadSignedBits(num4); } } else if (num >= 8 && num <= 12) { int num6 = num - 8; array = new int[blockSize]; for (int k = 0; k < num6; k++) { array[k] = reader.ReadSignedBits(num4); } int[] array2 = DecodeResidual(reader, blockSize, num6); if (array2 == null) { return null; } for (int l = num6; l < blockSize; l++) { array[l] = (int)(num6 switch { 0 => array2[l], 1 => (long)array[l - 1] + (long)array2[l], 2 => 2L * (long)array[l - 1] - array[l - 2] + array2[l], 3 => 3L * (long)array[l - 1] - 3L * (long)array[l - 2] + array[l - 3] + array2[l], _ => 4L * (long)array[l - 1] - 6L * (long)array[l - 2] + 4L * (long)array[l - 3] - array[l - 4] + array2[l], }); } } else { if (num < 32) { return null; } int num7 = (num & 0x1F) + 1; array = new int[blockSize]; for (int m = 0; m < num7; m++) { array[m] = reader.ReadSignedBits(num4); } int num8 = reader.ReadBits(4) + 1; if (num8 == 16) { return null; } int num9 = reader.ReadSignedBits(5); int[] array3 = new int[num7]; for (int n = 0; n < num7; n++) { array3[n] = reader.ReadSignedBits(num8); } int[] array4 = DecodeResidual(reader, blockSize, num7); if (array4 == null) { return null; } for (int num10 = num7; num10 < blockSize; num10++) { long num11 = 0L; for (int num12 = 0; num12 < num7; num12++) { num11 += (long)array3[num12] * (long)array[num10 - 1 - num12]; } array[num10] = (int)((num11 >> num9) + array4[num10]); } } if (num3 > 0) { for (int num13 = 0; num13 < blockSize; num13++) { array[num13] <<= num3; } } return array; } private static int[] DecodeResidual(BitReader reader, int blockSize, int predictorOrder) { int num = reader.ReadBits(2); if (num > 1) { return null; } int count = ((num == 0) ? 4 : 5); int num2 = ((num == 0) ? 15 : 31); int num3 = reader.ReadBits(4); int num4 = 1 << num3; int num5 = blockSize >> num3; if (num5 == 0) { return null; } int[] array = new int[blockSize]; int num6 = predictorOrder; for (int i = 0; i < num4; i++) { int num7 = num5; if (i == 0) { num7 -= predictorOrder; } int num8 = reader.ReadBits(count); if (num8 == num2) { int count2 = reader.ReadBits(5); for (int j = 0; j < num7; j++) { array[num6++] = reader.ReadSignedBits(count2); } } else { for (int k = 0; k < num7; k++) { array[num6++] = ReadRice(reader, num8); } } } return array; } private static int ReadRice(BitReader reader, int parameter) { int num = reader.ReadUnary(); int num2 = ((parameter > 0) ? reader.ReadBits(parameter) : 0); int num3 = (num << parameter) | num2; return (num3 >> 1) ^ -(num3 & 1); } private static void ApplyChannelDecorrelation(int assignment, int[][] subframes, int blockSize) { if (assignment < 8 || subframes.Length < 2) { return; } int[] array = subframes[0]; int[] array2 = subframes[1]; switch (assignment) { case 8: { for (int j = 0; j < blockSize; j++) { int num3 = array[j]; int num4 = array2[j]; array[j] = num3; array2[j] = num3 - num4; } break; } case 9: { for (int k = 0; k < blockSize; k++) { int num5 = array[k]; int num6 = array2[k]; array[k] = num6 + num5; array2[k] = num6; } break; } case 10: { for (int i = 0; i < blockSize; i++) { int num = array[i]; int num2 = array2[i]; num = (num << 1) | (num2 & 1); array[i] = num + num2 >> 1; array2[i] = num - num2 >> 1; } break; } } } private static int ResolveBlockSize(BitReader reader, int code) { if (code == 1) { return 192; } if (code >= 2 && code <= 5) { return 576 << code - 2; } return code switch { 6 => reader.ReadBits(8) + 1, 7 => reader.ReadBits(16) + 1, _ => 256 << code - 8, }; } private static int ResolveSampleRate(BitReader reader, int code, int streamRate) { return code switch { 0 => streamRate, 1 => 88200, 2 => 176400, 3 => 192000, 4 => 8000, 5 => 16000, 6 => 22050, 7 => 24000, 8 => 32000, 9 => 44100, 10 => 48000, 11 => 96000, 12 => reader.ReadBits(8) * 1000, 13 => reader.ReadBits(16), _ => reader.ReadBits(16) * 10, }; } private static int ResolveBitsPerSample(int code, int streamBits) { return code switch { 1 => 8, 2 => 12, 4 => 16, 5 => 20, 6 => 24, 7 => 32, _ => streamBits, }; } private static void ReadUtf8Number(BitReader reader) { int num = reader.ReadBits(8); if ((num & 0x80) != 0) { int num2 = (((num & 0xE0) == 192) ? 1 : (((num & 0xF0) == 224) ? 2 : (((num & 0xF8) == 240) ? 3 : (((num & 0xFC) == 248) ? 4 : (((num & 0xFE) != 252) ? 6 : 5))))); for (int i = 0; i < num2; i++) { reader.SkipBits(8); } } } } internal sealed class BitReader { private readonly byte[] _data; private int _bitPosition; public int RemainingBits => _data.Length * 8 - _bitPosition; public BitReader(byte[] data, int byteOffset) { _data = data; _bitPosition = byteOffset * 8; } public int ReadBits(int count) { if (count <= 0) { return 0; } int num = 0; int num2 = count; while (num2 > 0) { int num3 = _bitPosition >> 3; if (num3 >= _data.Length) { throw new InvalidOperationException("FLAC 数据意外结束"); } int num4 = _bitPosition & 7; int num5 = 8 - num4; int num6 = ((num5 < num2) ? num5 : num2); int num7 = num5 - num6; int num8 = (1 << num6) - 1; int num9 = (_data[num3] >> num7) & num8; num = (num << num6) | num9; _bitPosition += num6; num2 -= num6; } return num; } public int ReadSignedBits(int count) { if (count <= 0) { return 0; } int num = ReadBits(count); int num2 = 1 << count - 1; return ((num & num2) != 0) ? (num - (num2 << 1)) : num; } public int ReadUnary() { int num = 0; int num4; while (true) { int num2 = _bitPosition >> 3; if (num2 >= _data.Length) { throw new InvalidOperationException("FLAC 数据意外结束"); } int num3 = _bitPosition & 7; num4 = (_data[num2] & (255 >> num3)) << num3; if (num4 != 0) { break; } int num5 = 8 - num3; num += num5; _bitPosition += num5; } int num6 = 0; while ((num4 & 0x80) == 0) { num6++; num4 <<= 1; } _bitPosition += num6 + 1; return num + num6; } public void SkipBits(int count) { _bitPosition += Math.Max(0, count); int num = _data.Length * 8; if (_bitPosition > num) { _bitPosition = num; } } public void AlignToByte() { int num = _bitPosition & 7; if (num != 0) { _bitPosition += 8 - num; } } } internal static class Playlist { private static readonly string[] Extensions = new string[4] { ".mp3", ".ogg", ".wav", ".flac" }; private const string Header = "# R.E.P.O. 音乐播放器播放列表\n# 每行一个本地文件或 http(s) 直链;相对路径相对于本文件同目录下的 music 文件夹。\n# 支持格式:mp3 / ogg / wav / flac。\n# 例:\n# song.mp3\n# D:\\Music\\album\\track01.ogg\n# https://example.com/radio.mp3\n#\n# 列表为空时会自动扫描 music 文件夹里的音频文件。\n"; public static string Root { get; } = Path.Combine(Paths.ConfigPath, "RepoMusicPlayer"); public static string FilePath { get; } = Path.Combine(Root, "playlist.txt"); public static string MusicDir { get; } = Path.Combine(Root, "music"); public static List<string> Load() { List<string> list = new List<string>(); try { Directory.CreateDirectory(Root); Directory.CreateDirectory(MusicDir); if (!File.Exists(FilePath)) { File.WriteAllText(FilePath, "# R.E.P.O. 音乐播放器播放列表\n# 每行一个本地文件或 http(s) 直链;相对路径相对于本文件同目录下的 music 文件夹。\n# 支持格式:mp3 / ogg / wav / flac。\n# 例:\n# song.mp3\n# D:\\Music\\album\\track01.ogg\n# https://example.com/radio.mp3\n#\n# 列表为空时会自动扫描 music 文件夹里的音频文件。\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } string[] array = File.ReadAllLines(FilePath, Encoding.UTF8); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length != 0 && text2[0] != '#') { string text3 = Resolve(text2); if (text3 != null) { list.Add(text3); } } } if (list.Count == 0) { string[] files = Directory.GetFiles(MusicDir); foreach (string text4 in files) { string value = Path.GetExtension(text4).ToLowerInvariant(); if (Array.IndexOf(Extensions, value) >= 0) { list.Add(text4); } } list.Sort(StringComparer.OrdinalIgnoreCase); } } catch (Exception ex) { MusicPlayerPlugin.Log.LogError((object)("读取播放列表失败:" + ex.Message)); } return list; } private static string Resolve(string line) { if (line.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || line.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { return line; } try { string text = (Path.IsPathRooted(line) ? line : Path.Combine(MusicDir, line)); return File.Exists(text) ? text : null; } catch { return null; } } } [BepInPlugin("xingKong.RepoMusicPlayer", "REPO Music Player", "1.0.0")] [BepInProcess("REPO.exe")] public class MusicPlayerPlugin : BaseUnityPlugin { public const string PluginGuid = "xingKong.RepoMusicPlayer"; public const string PluginName = "REPO Music Player"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; internal static MusicConfig Cfg; private readonly List<string> _tracks = new List<string>(); private AudioSource _source; private Coroutine _loadRoutine; private UnityWebRequest _request; private int _index = -1; private bool _loading; private bool _playing; private bool _paused; private string _toastText = string.Empty; private float _toastUntil; private GUIStyle _style; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Cfg = new MusicConfig(((BaseUnityPlugin)this).Config); _source = ((Component)this).gameObject.AddComponent<AudioSource>(); _source.playOnAwake = false; _source.loop = false; _source.spatialBlend = 0f; _source.volume = Mathf.Clamp01(Cfg.Volume.Value); ReloadPlaylist(); Log.LogInfo((object)string.Format("{0} v{1} 已加载:{2} 首,热键 F7/F8/F9,音量 F6/F10。", "REPO Music Player", "1.0.0", _tracks.Count)); Log.LogInfo((object)("播放列表:" + Playlist.FilePath)); } private void Update() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (Cfg != null && Cfg.Enabled.Value) { if (Input.GetKeyDown(Cfg.KeyNext.Value)) { Next(); } else if (Input.GetKeyDown(Cfg.KeyPrevious.Value)) { Previous(); } else if (Input.GetKeyDown(Cfg.KeyPlayPause.Value)) { TogglePause(); } else if (Input.GetKeyDown(Cfg.KeyVolumeUp.Value)) { ChangeVolume(0.05f); } else if (Input.GetKeyDown(Cfg.KeyVolumeDown.Value)) { ChangeVolume(-0.05f); } else if (Input.GetKeyDown(Cfg.KeyReload.Value)) { ReloadPlaylist(); Next(); } if (_playing && !_paused && !_loading && (Object)(object)_source != (Object)null && !_source.isPlaying) { OnTrackEnded(); } } } private void OnDestroy() { VoiceBroadcast.Stop(); } private void OnGUI() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) if (Cfg == null || !Cfg.Enabled.Value) { return; } bool flag = _toastUntil > Time.unscaledTime; if (flag || Cfg.AlwaysShow.Value) { if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = 20, alignment = (TextAnchor)4, richText = false }; } _style.normal.textColor = Color.white; string text = (flag ? _toastText : CurrentStatus()); if (!string.IsNullOrEmpty(text)) { Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width / 2f - 400f, 36f, 800f, 32f); GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text, ShadowStyle()); GUI.Label(val, text, _style); } } } private GUIStyle ShadowStyle() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(_style); val.normal.textColor = new Color(0f, 0f, 0f, 0.75f); return val; } private string CurrentStatus() { if (_tracks.Count == 0) { return "♪ 播放列表为空"; } string arg = Title(_tracks[Mathf.Clamp(_index, 0, _tracks.Count - 1)]); string arg2 = (_loading ? "加载中" : (_paused ? "已暂停" : (_playing ? "播放中" : "已停止"))); return $"♪ {arg} [{arg2}] {Mathf.RoundToInt(_source.volume * 100f)}%"; } public void Next() { if (_tracks.Count == 0) { Toast("播放列表为空,见 BepInEx/config/RepoMusicPlayer/playlist.txt"); return; } CancelLoad(); if (Cfg.Shuffle.Value) { int num = _index; if (_tracks.Count > 1) { while (num == _index) { num = Random.Range(0, _tracks.Count); } } _index = num; } else { _index = (_index + 1) % _tracks.Count; } _loadRoutine = ((MonoBehaviour)this).StartCoroutine(LoadTrack(_tracks[_index])); } public void Previous() { if (_tracks.Count != 0) { CancelLoad(); _index = (_index - 1 + _tracks.Count) % _tracks.Count; _loadRoutine = ((MonoBehaviour)this).StartCoroutine(LoadTrack(_tracks[_index])); } } public void TogglePause() { if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null) { Next(); } else if (_paused) { _source.UnPause(); _paused = false; if ((Object)(object)_source.clip != (Object)null) { VoiceBroadcast.Start(_source.clip); } Toast("继续播放"); } else if (_source.isPlaying) { _source.Pause(); _paused = true; VoiceBroadcast.Stop(); Toast("已暂停"); } else { Next(); } } private void ChangeVolume(float delta) { float num = Mathf.Clamp01(Cfg.Volume.Value + delta); Cfg.Volume.Value = num; if ((Object)(object)_source != (Object)null) { _source.volume = num; } Toast($"音量 {Mathf.RoundToInt(num * 100f)}%"); } private void OnTrackEnded() { if (!Cfg.LoopPlaylist.Value) { _playing = false; VoiceBroadcast.Stop(); Toast("播放结束"); } else { Next(); } } private void ReloadPlaylist() { _tracks.Clear(); _tracks.AddRange(Playlist.Load()); _index = -1; Log.LogInfo((object)$"[音乐] 播放列表已加载:{_tracks.Count} 首。"); } private void CancelLoad() { VoiceBroadcast.Stop(); if (_loadRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_loadRoutine); _loadRoutine = null; } try { UnityWebRequest request = _request; if (request != null) { request.Dispose(); } } catch { } _request = null; _loading = false; } private IEnumerator LoadTrack(string track) { _loading = true; _playing = false; _paused = false; Toast("加载中:" + Title(track)); bool isUrl = track.Contains("://"); string ext = Path.GetExtension(track).ToLowerInvariant(); if (ext == ".flac") { yield return LoadFlac(track, isUrl); yield break; } string uri = (isUrl ? track : new Uri(track).AbsoluteUri); AudioType type = (AudioType)((ext == ".ogg") ? 14 : ((ext == ".wav") ? 20 : 13)); UnityWebRequest request = (_request = UnityWebRequestMultimedia.GetAudioClip(uri, type)); yield return request.SendWebRequest(); _loading = false; _loadRoutine = null; _request = null; if ((int)request.result != 1) { string error = request.error; request.Dispose(); Toast("加载失败:" + error); Log.LogWarning((object)("[音乐] 加载失败 " + track + ":" + error)); yield break; } AudioClip clip = DownloadHandlerAudioClip.GetContent(request); request.Dispose(); if ((Object)(object)clip == (Object)null) { Toast("无法解码该音频"); } else { Play(clip, track); } } private IEnumerator LoadFlac(string track, bool isUrl) { Toast("解码 FLAC:" + Title(track)); byte[] bytes; if (isUrl) { UnityWebRequest request = UnityWebRequest.Get(track); try { _request = request; yield return request.SendWebRequest(); _request = null; if ((int)request.result != 1) { _loading = false; _loadRoutine = null; Toast("加载失败:" + request.error); Log.LogWarning((object)("[音乐] FLAC 下载失败 " + track + ":" + request.error)); yield break; } bytes = request.downloadHandler.data; } finally { ((IDisposable)request)?.Dispose(); } } else { try { bytes = File.ReadAllBytes(track); } catch (Exception ex) { Exception e = ex; _loading = false; _loadRoutine = null; Toast("读取失败:" + e.Message); yield break; } } FlacDecoder.Result decoded = null; Exception failure = null; Task task = Task.Run(delegate { try { decoded = FlacDecoder.Decode(bytes); } catch (Exception ex2) { failure = ex2; } }); while (!task.IsCompleted) { yield return null; } _loading = false; _loadRoutine = null; if (failure != null || decoded == null || decoded.Samples == null || decoded.Samples.Length == 0) { Toast("FLAC 解码失败:" + (failure?.Message ?? "无音频数据")); Log.LogWarning((object)$"[音乐] FLAC 解码失败 {track}:{failure}"); yield break; } int frames = decoded.Samples.Length / decoded.Channels; if (frames <= 0) { Toast("FLAC 没有音频帧"); yield break; } if ((long)frames * (long)decoded.Channels > 60000000) { Toast("FLAC 文件过大,已跳过"); Log.LogWarning((object)$"[音乐] FLAC 过大跳过 {track}:{frames} 帧 × {decoded.Channels} 声道"); yield break; } AudioClip clip = AudioClip.Create(Title(track), frames, decoded.Channels, decoded.SampleRate, false); clip.SetData(decoded.Samples, 0); Log.LogInfo((object)$"[音乐] FLAC 解码完成:{clip.frequency}Hz / {clip.channels}ch / {clip.length:F0}s"); Play(clip, track); } private void Play(AudioClip clip, string track) { _source.Stop(); _source.clip = clip; _source.volume = Mathf.Clamp01(Cfg.Volume.Value); _source.Play(); VoiceBroadcast.Start(clip); _playing = true; _paused = false; Toast("♪ " + Title(track)); Log.LogInfo((object)("[音乐] 播放:" + Title(track))); } private static string Title(string track) { if (string.IsNullOrEmpty(track)) { return "<空>"; } if (track.Contains("://")) { return track; } return Path.GetFileNameWithoutExtension(track); } private void Toast(string text) { _toastText = text; _toastUntil = Time.unscaledTime + 3.5f; } } internal sealed class MusicConfig { public readonly ConfigEntry<bool> Enabled; public readonly ConfigEntry<float> Volume; public readonly ConfigEntry<bool> Shuffle; public readonly ConfigEntry<bool> LoopPlaylist; public readonly ConfigEntry<bool> AlwaysShow; public readonly ConfigEntry<bool> Broadcast; public readonly ConfigEntry<bool> HostOnlyBroadcast; public readonly ConfigEntry<KeyCode> KeyNext; public readonly ConfigEntry<KeyCode> KeyPrevious; public readonly ConfigEntry<KeyCode> KeyPlayPause; public readonly ConfigEntry<KeyCode> KeyVolumeUp; public readonly ConfigEntry<KeyCode> KeyVolumeDown; public readonly ConfigEntry<KeyCode> KeyReload; public MusicConfig(ConfigFile cfg) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown Enabled = cfg.Bind<bool>("1. 通用", "启用", true, "总开关。"); Volume = cfg.Bind<float>("2. 播放", "音量", 0.6f, new ConfigDescription("0 ~ 1,热键可实时调整。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); Shuffle = cfg.Bind<bool>("2. 播放", "随机播放", false, "下一首时随机选曲。"); LoopPlaylist = cfg.Bind<bool>("2. 播放", "循环播放", true, "一首播完自动下一首;关闭则播完停止。"); AlwaysShow = cfg.Bind<bool>("2. 播放", "常驻显示状态", false, "在屏幕上方一直显示当前曲目与音量。"); Broadcast = cfg.Bind<bool>("4. 广播", "广播给所有玩家", true, "借用游戏语音通道,把当前歌曲当成麦克风播出去,其它玩家不装插件也能听到。关闭则只在自己客户端播放。"); HostOnlyBroadcast = cfg.Bind<bool>("4. 广播", "仅房主可广播", true, "开启时只有主机(MasterClient)会广播,避免多个客户端同时抢占语音通道。"); KeyNext = cfg.Bind<KeyCode>("3. 热键", "下一首", (KeyCode)290, "切到下一首。"); KeyPrevious = cfg.Bind<KeyCode>("3. 热键", "上一首", (KeyCode)288, "切到上一首。"); KeyPlayPause = cfg.Bind<KeyCode>("3. 热键", "暂停/继续", (KeyCode)289, "暂停、继续,或没在播放时开始。"); KeyVolumeUp = cfg.Bind<KeyCode>("3. 热键", "音量 +", (KeyCode)291, "每次 +5%。"); KeyVolumeDown = cfg.Bind<KeyCode>("3. 热键", "音量 -", (KeyCode)287, "每次 -5%。"); KeyReload = cfg.Bind<KeyCode>("3. 热键", "重载列表", (KeyCode)286, "重新读取 playlist.txt 并播放。"); } } internal static class VoiceBroadcast { private static readonly FieldInfo InstanceField = typeof(PlayerVoiceChat).GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RecorderField = typeof(PlayerVoiceChat).GetField("recorder", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static Recorder _recorder; private static bool _active; private static bool _saved; private static InputSourceType _originalSource; private static bool _originalTransmit; private static bool _originalVoiceDetection; private static AudioClip _originalClip; private static bool _originalLoop; private static bool _warned; public static bool IsActive => _active; public static bool CanBroadcast() { if (!MusicPlayerPlugin.Cfg.Broadcast.Value) { return false; } if (MusicPlayerPlugin.Cfg.HostOnlyBroadcast.Value) { try { if (!SemiFunc.IsMasterClientOrSingleplayer()) { return false; } } catch { return false; } } return true; } public static void Start(AudioClip clip) { if ((Object)(object)clip == (Object)null || !CanBroadcast()) { return; } Recorder recorder = GetRecorder(); if ((Object)(object)recorder == (Object)null) { return; } try { if (!_active) { SaveOriginals(recorder); } recorder.SourceType = (InputSourceType)1; recorder.AudioClip = clip; recorder.LoopAudioClip = true; recorder.VoiceDetection = false; recorder.TransmitEnabled = true; recorder.RestartRecording(); _active = true; MusicPlayerPlugin.Log.LogInfo((object)"[音乐] 已通过语音通道广播当前歌曲(其它玩家无需插件)。"); } catch (Exception ex) { MusicPlayerPlugin.Log.LogWarning((object)("[音乐] 广播失败:" + ex.Message)); Restore(); } } public static void Stop() { if (_active) { Restore(); MusicPlayerPlugin.Log.LogInfo((object)"[音乐] 已停止广播,恢复麦克风。"); } } private static void Restore() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_recorder != (Object)null && _saved) { _recorder.SourceType = _originalSource; _recorder.AudioClip = _originalClip; _recorder.LoopAudioClip = _originalLoop; _recorder.VoiceDetection = _originalVoiceDetection; _recorder.TransmitEnabled = _originalTransmit; _recorder.RestartRecording(); } } catch (Exception ex) { MusicPlayerPlugin.Log.LogDebug((object)("[音乐] 恢复麦克风失败:" + ex.Message)); } _active = false; _saved = false; } private static void SaveOriginals(Recorder recorder) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) _recorder = recorder; _originalSource = recorder.SourceType; _originalClip = recorder.AudioClip; _originalLoop = recorder.LoopAudioClip; _originalVoiceDetection = recorder.VoiceDetection; _originalTransmit = recorder.TransmitEnabled; _saved = true; } private static Recorder GetRecorder() { try { object? obj = InstanceField?.GetValue(null); PlayerVoiceChat val = (PlayerVoiceChat)((obj is PlayerVoiceChat) ? obj : null); if ((Object)(object)val == (Object)null) { if (!_warned) { _warned = true; MusicPlayerPlugin.Log.LogInfo((object)"[音乐] 未找到 PlayerVoiceChat,广播模式不可用(仅本地播放)。"); } return null; } object? obj2 = RecorderField?.GetValue(val); Recorder val2 = (Recorder)((obj2 is Recorder) ? obj2 : null); if ((Object)(object)val2 == (Object)null && !_warned) { _warned = true; MusicPlayerPlugin.Log.LogInfo((object)"[音乐] PlayerVoiceChat 上没有 Recorder,广播模式不可用(仅本地播放)。"); } return val2; } catch (Exception ex) { MusicPlayerPlugin.Log.LogDebug((object)("[音乐] 获取 Recorder 失败:" + ex.Message)); return null; } } } }