Decompiled source of PeakMusic v1.4.4
plugins/PeakMusic/PeakMusic.Core.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.WebSockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using PeakMusic.Core.Netease; using PeakMusic.Core.Sources; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("PeakMusic.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("PeakMusic.Core")] [assembly: AssemblyTitle("PeakMusic.Core")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [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 PeakMusic.Core { public sealed class LrcDocument { private static readonly Regex TagRegex = new Regex("\\[(\\d{1,3}):(\\d{1,2})(?:[.:](\\d{1,3}))?\\]", RegexOptions.Compiled); private static readonly Regex MetaRegex = new Regex("^\\[(ar|ti|al|by|offset|re|ve|length|kana|ti|tool):", RegexOptions.IgnoreCase | RegexOptions.Compiled); public readonly List<LrcLine> Lines = new List<LrcLine>(); public int OffsetMs; public static LrcDocument Parse(string lrc) { LrcDocument lrcDocument = new LrcDocument(); if (string.IsNullOrEmpty(lrc)) { return lrcDocument; } string[] array = lrc.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0 || text[0] == '{') { continue; } if (MetaRegex.IsMatch(text)) { if (text.StartsWith("[offset:", StringComparison.OrdinalIgnoreCase) && int.TryParse(text.Substring(8).TrimEnd(new char[1] { ']' }), out var result)) { lrcDocument.OffsetMs = result; } continue; } MatchCollection matchCollection = TagRegex.Matches(text); if (matchCollection.Count == 0) { continue; } string text2 = TagRegex.Replace(text, "").Trim(); if (text2.Length == 0) { continue; } foreach (Match item in matchCollection) { int minutes = int.Parse(item.Groups[1].Value, CultureInfo.InvariantCulture); int seconds = int.Parse(item.Groups[2].Value, CultureInfo.InvariantCulture); int milliseconds = 0; if (item.Groups[3].Success) { string text3 = item.Groups[3].Value; while (text3.Length < 3) { text3 += "0"; } if (text3.Length > 3) { text3 = text3.Substring(0, 3); } milliseconds = int.Parse(text3, CultureInfo.InvariantCulture); } lrcDocument.Lines.Add(new LrcLine { Time = new TimeSpan(0, 0, minutes, seconds, milliseconds), Text = text2 }); } } lrcDocument.Lines.Sort((LrcLine a, LrcLine b) => a.Time.CompareTo(b.Time)); return lrcDocument; } public int IndexAt(float seconds) { if (Lines.Count == 0) { return -1; } double num = (double)seconds * 1000.0 + (double)OffsetMs; int num2 = 0; int num3 = Lines.Count - 1; int result = -1; while (num2 <= num3) { int num4 = (num2 + num3) / 2; if (Lines[num4].Time.TotalMilliseconds <= num) { result = num4; num2 = num4 + 1; } else { num3 = num4 - 1; } } return result; } public string TextAt(float seconds) { int num = IndexAt(seconds); if (num >= 0) { return Lines[num].Text; } return ""; } } public sealed class LrcLine { public TimeSpan Time; public string Text = ""; } public sealed class MusicTrack { public long id { get; set; } public string name { get; set; } public string ar { get; set; } public string al { get; set; } public string picUrl { get; set; } public int dt { get; set; } public long fee { get; set; } public int st { get; set; } public TimeSpan Duration => TimeSpan.FromMilliseconds(Math.Max(0, dt)); public override string ToString() { if (!string.IsNullOrEmpty(ar)) { return name + " - " + ar; } return name; } } public sealed class PlayUrlResult { public string Url { get; set; } public int Bitrate { get; set; } public string Type { get; set; } public int Code { get; set; } public bool IsTrialOnly { get; set; } public bool IsPlayable { get { if (Code == 200 && !IsTrialOnly) { return !string.IsNullOrEmpty(Url); } return false; } } } public sealed class Throttle : IDisposable { private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1); private readonly Random _rng = new Random(Environment.TickCount * 31); private readonly int _minDelayMs; private readonly int _maxDelayMs; private bool _disposed; public int Completed { get; private set; } public Throttle(int minDelayMs = 200, int maxDelayMs = 500) { if (minDelayMs < 0) { minDelayMs = 0; } if (maxDelayMs < minDelayMs) { maxDelayMs = minDelayMs; } _minDelayMs = minDelayMs; _maxDelayMs = maxDelayMs; } public async Task<T> RunAsync<T>(Func<Task<T>> work, CancellationToken ct = default(CancellationToken)) { if (work == null) { throw new ArgumentNullException("work"); } await _gate.WaitAsync(ct).ConfigureAwait(continueOnCapturedContext: false); try { int num; lock (_rng) { num = _rng.Next(_minDelayMs, _maxDelayMs + 1); } if (num > 0) { await Task.Delay(num, ct).ConfigureAwait(continueOnCapturedContext: false); } T result = await work().ConfigureAwait(continueOnCapturedContext: false); Completed++; return result; } finally { _gate.Release(); } } public Task RunAsync(Func<Task> work, CancellationToken ct = default(CancellationToken)) { if (work == null) { throw new ArgumentNullException("work"); } return RunAsync(async delegate { await work().ConfigureAwait(continueOnCapturedContext: false); return true; }, ct); } public void Dispose() { if (!_disposed) { _disposed = true; _gate.Dispose(); } } } } namespace PeakMusic.Core.Sources { public static class LibraryStore { private const char Sep = '\u0001'; private const int MaxHistory = 20; public static string Dir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PeakMusic"); private static string HistoryPath => Path.Combine(Dir, "history.txt"); private static string FavoritesPath => Path.Combine(Dir, "favorites.txt"); public static List<string> LoadHistory() { List<string> list = new List<string>(); try { if (!File.Exists(HistoryPath)) { return list; } string[] array = File.ReadAllLines(HistoryPath, Encoding.UTF8); for (int i = 0; i < array.Length; i++) { string text = (array[i] ?? "").Trim(); if (text.Length != 0 && !list.Contains(text)) { list.Add(text); } } return list; } catch { return list; } } public static void AddHistory(string keyword) { if (string.IsNullOrEmpty(keyword)) { return; } string i = keyword.Trim(); if (i.Length == 0) { return; } List<string> list = LoadHistory(); list.RemoveAll((string x) => string.Equals(x, i, StringComparison.OrdinalIgnoreCase)); list.Insert(0, i); if (list.Count > 20) { list.RemoveRange(20, list.Count - 20); } try { Directory.CreateDirectory(Dir); File.WriteAllLines(HistoryPath, list.ToArray(), Encoding.UTF8); } catch { } } public static List<SongInfo> LoadFavorites() { List<SongInfo> list = new List<SongInfo>(); try { if (!File.Exists(FavoritesPath)) { return list; } string[] array = File.ReadAllLines(FavoritesPath, Encoding.UTF8); for (int i = 0; i < array.Length; i++) { SongInfo songInfo = ParseLine(array[i]); if (songInfo != null) { list.Add(songInfo); } } return list; } catch { return list; } } public static void AddFavorite(SongInfo song) { if (song != null) { List<SongInfo> list = LoadFavorites(); list.RemoveAll((SongInfo x) => x.Key == song.Key); list.Insert(0, song); SaveFavorites(list); } } public static void RemoveFavorite(string key) { List<SongInfo> list = LoadFavorites(); list.RemoveAll((SongInfo x) => x.Key == key); SaveFavorites(list); } public static bool IsFavorite(string key) { foreach (SongInfo item in LoadFavorites()) { if (item.Key == key) { return true; } } return false; } private static void SaveFavorites(List<SongInfo> list) { try { Directory.CreateDirectory(Dir); List<string> list2 = new List<string>(); foreach (SongInfo item in list) { string text = Serialize(item); if (text != null) { list2.Add(text); } } File.WriteAllLines(FavoritesPath, list2.ToArray(), Encoding.UTF8); } catch { } } private static string Serialize(SongInfo s) { if (s == null) { return null; } string text = (string.IsNullOrEmpty(s.SourceId) ? s.Id.ToString() : s.SourceId); return string.Join('\u0001'.ToString(), Platforms.ToInt(s.Platform).ToString(), text, Safe(s.Name), Safe(s.Artists), Safe(s.Album), s.DurationMs.ToString()); } private static SongInfo ParseLine(string raw) { if (string.IsNullOrEmpty(raw)) { return null; } string[] array = raw.Split(new char[1] { '\u0001' }); if (array.Length < 6) { return null; } try { int i = int.Parse(array[0]); long durationMs = long.Parse(array[5]); SongInfo songInfo = new SongInfo { Platform = Platforms.FromInt(i), Name = array[2], Artists = array[3], Album = array[4], DurationMs = durationMs }; if (long.TryParse(array[1], out var result)) { songInfo.Id = result; } else { songInfo.SourceId = array[1]; } return songInfo; } catch { return null; } } private static string Safe(string v) { return (v ?? "").Replace('\u0001', ' ').Replace("\r", " ").Replace("\n", " "); } } internal sealed class MqttWsClient : IDisposable { private const int SubPacketId = 1; private ClientWebSocket _ws; private readonly List<byte> _rxAcc = new List<byte>(); private BlockingCollection<byte[]> _packets; private Task _rxLoop; private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); private volatile bool _disposed; public async Task<string> ConnectAsync(string host, int port, string path, IDictionary<string, string> headers, string clientId, IDictionary<string, string> connectUserProps, CancellationToken ct) { string cur = path; ClientWebSocket ws; string serverRef; for (int attempt = 0; attempt < 4; Shutdown(ws), cur = BuildRedirectPath(cur, serverRef), attempt++) { ws = new ClientWebSocket(); try { ws.Options.AddSubProtocol("mqtt"); } catch { } if (headers != null) { foreach (KeyValuePair<string, string> header in headers) { try { ws.Options.SetRequestHeader(header.Key, header.Value); } catch { } } } await ws.ConnectAsync(new Uri("wss://" + host + ":" + port + cur), ct).ConfigureAwait(continueOnCapturedContext: false); _packets = new BlockingCollection<byte[]>(64); _rxAcc.Clear(); StartRxLoop(ws); await SendAsync(ws, BuildConnect(clientId, connectUserProps), ct).ConfigureAwait(continueOnCapturedContext: false); byte[] array = TakePacket(20000); if (array == null) { Shutdown(ws); throw new InvalidOperationException("MQTT CONNACK 超时"); } int reason; string text = ParseConnAckProps(array, out reason, out serverRef); switch (reason) { case 0: _ws = ws; return cur; case 156: case 157: if (!string.IsNullOrEmpty(serverRef)) { continue; } break; } Shutdown(ws); throw new InvalidOperationException("MQTT 连接被拒 reason=0x" + reason.ToString("X2") + text); } throw new InvalidOperationException("MQTT 重定向次数超限"); } public async Task SubscribeAsync(string topic, IDictionary<string, string> subUserProps, CancellationToken ct) { ClientWebSocket ws = _ws; if (ws == null) { throw new InvalidOperationException("MQTT 未连接"); } await SendAsync(ws, BuildSubscribe(topic, subUserProps), ct).ConfigureAwait(continueOnCapturedContext: false); DateTime dateTime = DateTime.UtcNow.AddSeconds(10.0); while (DateTime.UtcNow < dateTime) { byte[] array = TakePacket(2000); if (array == null || ((array[0] >> 4) & 0xF) != 9) { continue; } int pos = 1; ReadVarint(array, ref pos); pos += 2; int num = ReadVarint(array, ref pos); pos += num; for (int i = pos; i < array.Length; i++) { if (array[i] >= 128) { throw new InvalidOperationException("MQTT SUBACK 拒绝 reason=0x" + array[i].ToString("X2")); } } return; } throw new InvalidOperationException("MQTT SUBACK 超时"); } public MqttWsMessage ReceiveMessage(TimeSpan wait) { DateTime dateTime = DateTime.UtcNow + wait; byte[] array; do { int num = (int)(dateTime - DateTime.UtcNow).TotalMilliseconds; if (num <= 0) { return null; } array = TakePacket(num); if (array == null) { return null; } } while (((array[0] >> 4) & 0xF) != 3); return ParsePublish(array); } public Task PingAsync(CancellationToken ct) { ClientWebSocket ws = _ws; if (ws == null) { throw new InvalidOperationException("MQTT 未连接"); } return SendAsync(ws, new byte[2] { 192, 0 }, ct); } private void StartRxLoop(ClientWebSocket ws) { _rxLoop = Task.Run(async delegate { byte[] buf = new byte[8192]; try { while (!_disposed) { WebSocketReceiveResult webSocketReceiveResult = await ws.ReceiveAsync(new ArraySegment<byte>(buf), CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false); if (webSocketReceiveResult.MessageType == WebSocketMessageType.Close) { break; } for (int i = 0; i < webSocketReceiveResult.Count; i++) { _rxAcc.Add(buf[i]); } byte[] item; while ((item = TryTakePacket()) != null) { if (_packets != null && !_packets.IsAddingCompleted) { try { _packets.Add(item); } catch { return; } } } } } catch { } }); } private byte[] TakePacket(int timeoutMs) { BlockingCollection<byte[]> packets = _packets; if (packets == null) { return null; } if (!packets.TryTake(out var item, timeoutMs)) { return null; } return item; } private byte[] TryTakePacket() { if (_rxAcc.Count < 2) { return null; } int num = 1; int num2 = 0; int i = 1; bool flag = false; for (; i < _rxAcc.Count && i <= 4; i++) { int num3 = _rxAcc[i]; num2 += (num3 & 0x7F) * num; num *= 128; if ((num3 & 0x80) == 0) { flag = true; break; } } if (!flag) { return null; } int num4 = i + 1; if (_rxAcc.Count < num4 + num2) { return null; } byte[] array = new byte[num4 + num2]; _rxAcc.CopyTo(0, array, 0, array.Length); _rxAcc.RemoveRange(0, array.Length); return array; } private void Shutdown(ClientWebSocket ws) { try { if (_packets != null) { _packets.CompleteAdding(); } } catch { } try { ws.Dispose(); } catch { } } private Task SendAsync(ClientWebSocket ws, byte[] packet, CancellationToken ct) { return _sendLock.WaitAsync(ct).ContinueWith((Func<Task, Task>)async delegate { try { await ws.SendAsync(new ArraySegment<byte>(packet), WebSocketMessageType.Binary, endOfMessage: true, ct).ConfigureAwait(continueOnCapturedContext: false); } finally { try { _sendLock.Release(); } catch { } } }, CancellationToken.None).Unwrap(); } private static byte[] BuildConnect(string clientId, IDictionary<string, string> userProps) { List<byte> list = new List<byte>(); list.AddRange(new byte[10] { 0, 4, 77, 81, 84, 84, 5, 2, 0, 45 }); List<byte> list2 = new List<byte>(); list2.Add(21); AddUtf8(list2, "pass"); if (userProps != null) { foreach (KeyValuePair<string, string> userProp in userProps) { list2.Add(38); AddUtf8(list2, userProp.Key); AddUtf8(list2, userProp.Value); } } AddVarint(list, list2.Count); list.AddRange(list2); AddUtf8(list, clientId); return Wrap(16, list); } private static byte[] BuildSubscribe(string topic, IDictionary<string, string> userProps) { List<byte> list = new List<byte>(); list.Add(0); list.Add(1); List<byte> list2 = new List<byte>(); if (userProps != null) { foreach (KeyValuePair<string, string> userProp in userProps) { list2.Add(38); AddUtf8(list2, userProp.Key); AddUtf8(list2, userProp.Value); } } AddVarint(list, list2.Count); list.AddRange(list2); AddUtf8(list, topic); list.Add(0); return Wrap(130, list); } private static byte[] Wrap(int typeFlags, List<byte> body) { List<byte> obj = new List<byte> { (byte)typeFlags }; AddVarint(obj, body.Count); obj.AddRange(body); return obj.ToArray(); } private static void AddUtf8(List<byte> buf, string s) { byte[] bytes = Encoding.UTF8.GetBytes(s ?? ""); buf.Add((byte)(bytes.Length >> 8)); buf.Add((byte)(bytes.Length & 0xFF)); buf.AddRange(bytes); } private static void AddVarint(List<byte> buf, int value) { do { int num = value % 128; value /= 128; if (value > 0) { num |= 0x80; } buf.Add((byte)num); } while (value > 0); } private static string ParseConnAckProps(byte[] ack, out int reason, out string serverRef) { serverRef = null; reason = -1; if (ack.Length < 3) { return null; } int pos = 1; ReadVarint(ack, ref pos); if (pos + 2 > ack.Length) { return null; } pos++; reason = ack[pos++]; int num = ReadVarint(ack, ref pos); string text = ""; int num2 = Math.Min(ack.Length, pos + num); while (pos < num2) { switch (ack[pos++]) { case 19: pos += 2; break; case 28: serverRef = ReadUtf8(ack, ref pos); break; case 31: text = text + " reason=" + ReadUtf8(ack, ref pos); break; case 38: { string text2 = ReadUtf8(ack, ref pos); string text3 = ReadUtf8(ack, ref pos); text = text + " " + text2 + "=" + text3; break; } default: pos = num2; break; } } return text; } private static MqttWsMessage ParsePublish(byte[] pkt) { int num = ((pkt[0] & 0xF) >> 1) & 3; int pos = 1; ReadVarint(pkt, ref pos); MqttWsMessage mqttWsMessage = new MqttWsMessage { Topic = ReadUtf8(pkt, ref pos) }; if (num > 0) { pos += 2; } int num2 = ReadVarint(pkt, ref pos); int num3 = Math.Min(pkt.Length, pos + num2); while (pos < num3) { switch (pkt[pos++]) { case 1: pos++; break; case 2: case 17: case 24: case 39: pos += 4; break; case 3: case 8: case 18: case 21: case 26: case 28: case 31: ReadUtf8(pkt, ref pos); break; case 9: case 22: { int num4 = (pkt[pos] << 8) | pkt[pos + 1]; pos += 2 + num4; break; } case 11: ReadVarint(pkt, ref pos); break; case 19: case 33: case 34: case 35: pos += 2; break; case 23: case 25: case 36: case 37: case 40: case 41: case 42: pos++; break; case 38: { string key = ReadUtf8(pkt, ref pos); string value = ReadUtf8(pkt, ref pos); mqttWsMessage.UserProps[key] = value; break; } default: pos = num3; break; } } if (pos < pkt.Length) { mqttWsMessage.Payload = Encoding.UTF8.GetString(pkt, pos, pkt.Length - pos); } return mqttWsMessage; } private static int ReadVarint(byte[] buf, ref int pos) { int num = 1; int num2 = 0; for (int i = 0; i < 4; i++) { if (pos >= buf.Length) { break; } int num3 = buf[pos++]; num2 += (num3 & 0x7F) * num; num *= 128; if ((num3 & 0x80) == 0) { break; } } return num2; } private static string ReadUtf8(byte[] buf, ref int pos) { if (pos + 2 > buf.Length) { pos = buf.Length; return ""; } int num = (buf[pos] << 8) | buf[pos + 1]; pos += 2; if (pos + num > buf.Length) { num = buf.Length - pos; } string result = Encoding.UTF8.GetString(buf, pos, num); pos += num; return result; } private static string BuildRedirectPath(string path, string serverRef) { string text = path.TrimEnd(new char[1] { '/' }); string[] array = text.Split(new char[1] { '/' }); if (array.Length != 0 && array[^1].Contains(":")) { array[^1] = serverRef; return string.Join("/", array); } return text + "/" + serverRef; } public void Dispose() { _disposed = true; try { if (_packets != null) { _packets.CompleteAdding(); } } catch { } try { if (_ws != null) { _ws.Dispose(); } } catch { } _ws = null; } } internal sealed class MqttWsMessage { public string Topic = ""; public Dictionary<string, string> UserProps = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public string Payload = ""; } public sealed class MusicSources : IDisposable { private readonly Throttle _throttle; public NeteaseClient Netease { get; private set; } public QqClient QQ { get; private set; } public MusicSources() { _throttle = new Throttle(); Netease = new NeteaseClient(_throttle); QQ = new QqClient(_throttle); } public string LoginLine(Platform p) { if (p == Platform.QQ) { if (!QQ.HasLogin) { return "QQ音乐未登录(付费曲会取不到)"; } return "QQ音乐已登录"; } if (!Netease.HasLogin) { return "网易云未登录(会员曲会 -110)"; } if (!Netease.VipKnown) { Netease.RefreshAccount(); } if (Netease.VipKnown && !Netease.AccountValid) { return "网易云登录态已失效(面板里退出后重新扫码)"; } if (Netease.VipKnown && Netease.HasVip) { return "网易云已登录 · " + ((Netease.VipType == 11) ? "黑胶VIP" : "会员"); } if (Netease.VipKnown) { return "网易云已登录 · 非会员(会员曲只能试听)"; } return "网易云已登录"; } public Task<List<SongInfo>> SearchAsync(Platform platform, string keyword, int limit, int offset, CancellationToken ct = default(CancellationToken)) { if (platform == Platform.QQ) { return QQ.SearchAsync(keyword, limit, offset, ct); } return Netease.SearchAsync(keyword, limit, offset, ct); } public Task<SongUrlResult> GetUrlAsync(SongInfo song, CancellationToken ct = default(CancellationToken)) { if (song == null) { return Task.FromResult(new SongUrlResult { Code = -1, Message = "song 为空" }); } if (song.Platform == Platform.QQ) { return QQ.GetSongUrlAsync(song, ct); } return Netease.GetSongUrlAsync(song.Id, "exhigh", ct); } public Task<LyricResult> GetLyricAsync(SongInfo song, CancellationToken ct = default(CancellationToken)) { if (song == null) { return Task.FromResult(new LyricResult()); } if (song.Platform == Platform.QQ) { return QQ.GetLyricAsync(song, ct); } return Netease.GetLyricAsync(song.Id, ct); } public void Dispose() { try { if (Netease != null) { Netease.Dispose(); } } catch { } try { if (QQ != null) { QQ.Dispose(); } } catch { } try { _throttle.Dispose(); } catch { } } } public enum Platform { Netease, QQ } public static class Platforms { public static readonly Platform[] All = new Platform[2] { Platform.Netease, Platform.QQ }; public static string DisplayName(Platform p) { if (p == Platform.QQ) { return "QQ音乐"; } return "网易云"; } public static int BrandRgb(Platform p) { if (p == Platform.QQ) { return 3261052; } return 15288135; } public static string BrandHex(Platform p) { return "#" + BrandRgb(p).ToString("X6"); } public static void BrandColor(Platform p, out float r, out float g, out float b) { int num = BrandRgb(p); r = (float)((num >> 16) & 0xFF) / 255f; g = (float)((num >> 8) & 0xFF) / 255f; b = (float)(num & 0xFF) / 255f; } public static Platform FromInt(int i) { if (i < 0 || i >= All.Length) { return Platform.Netease; } return All[i]; } public static int ToInt(Platform p) { for (int i = 0; i < All.Length; i++) { if (All[i] == p) { return i; } } return 0; } } public sealed class PlaylistInfo { public string Id = ""; public string Name = ""; public string CoverUrl = ""; public int TrackCount; public string Creator = ""; public bool IsFavorites; public Platform Platform; public int DirId; public string Key => Platform.ToString() + ":" + Id; public string Subtitle { get { string text = ((TrackCount > 0) ? (TrackCount + " 首") : "歌单"); if (!string.IsNullOrEmpty(Creator)) { return text + " · " + Creator; } return text; } } public override string ToString() { return Platform.ToString() + "/" + Name + "(" + Id + ")"; } } public static class PngDecoder { public sealed class Image { public int Width; public int Height; public byte[] Pixels; } public static Image Decode(byte[] png) { if (png == null || png.Length < 8) { return null; } if (png[0] != 137 || png[1] != 80 || png[2] != 78 || png[3] != 71) { return null; } int num = 8; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; byte[] array = null; byte[] array2 = null; byte[] array3 = null; int num6; int num7; for (; num + 8 <= png.Length; num = num7 + num6 + 4) { num6 = ReadInt32(png, num); string text = new string(new char[4] { (char)png[num + 4], (char)png[num + 5], (char)png[num + 6], (char)png[num + 7] }); num7 = num + 8; switch (text) { case "IHDR": num2 = ReadInt32(png, num7); num3 = ReadInt32(png, num7 + 4); num4 = png[num7 + 8]; num5 = png[num7 + 9]; continue; case "IDAT": array = Append(array, png, num7, num6); continue; case "PLTE": array2 = new byte[num6]; Array.Copy(png, num7, array2, 0, num6); continue; case "tRNS": array3 = new byte[num6]; Array.Copy(png, num7, array3, 0, num6); continue; default: continue; case "IEND": break; } break; } if (num2 <= 0 || num3 <= 0 || array == null) { return null; } int num8; switch (num5) { case 0: num8 = 1; break; case 2: num8 = 3; break; case 3: num8 = 1; break; case 4: num8 = 2; break; case 6: num8 = 4; break; default: return null; } if (num4 != 1 && num4 != 2 && num4 != 4 && num4 != 8 && num4 != 16) { return null; } int num9 = ((num4 < 8) ? 1 : (num8 * num4 / 8)); int num10 = ((num4 >= 8) ? (num2 * num9) : ((num2 * num8 * num4 + 7) / 8)); int num11 = 0; if (array.Length >= 2 && (array[0] & 0xF) == 8 && ((array[0] << 8) + array[1]) % 31 == 0) { num11 = 2; } byte[] array4; using (MemoryStream stream = new MemoryStream(array, num11, array.Length - num11)) { using DeflateStream s = new DeflateStream(stream, CompressionMode.Decompress); array4 = ReadAll(s, (num10 + 1) * num3); } if (array4 == null || array4.Length < (num10 + 1) * num3) { return null; } byte[] array5 = new byte[num10 * num3]; int num12 = 0; for (int i = 0; i < num3; i++) { int num13 = array4[num12++]; int num14 = i * num10; for (int j = 0; j < num10; j++) { int num15 = ((j >= num9) ? array5[num14 + j - num9] : 0); int num16 = ((i > 0) ? array5[num14 - num10 + j] : 0); int c = ((j >= num9 && i > 0) ? array5[num14 - num10 + j - num9] : 0); int num17 = array4[num12++]; switch (num13) { case 1: num17 = (num17 + num15) & 0xFF; break; case 2: num17 = (num17 + num16) & 0xFF; break; case 3: num17 = (num17 + (num15 + num16 >> 1)) & 0xFF; break; case 4: num17 = (num17 + Paeth(num15, num16, c)) & 0xFF; break; } array5[num14 + j] = (byte)num17; } } byte[] array6 = new byte[num2 * num3 * 4]; int num18 = 0; for (int k = 0; k < num3; k++) { for (int l = 0; l < num2; l++) { int num19 = k * num2 + l; byte b = byte.MaxValue; byte b2; byte b3; byte b4; if (num4 < 8) { int num20 = Sample(array5, k * num10, l * num8, num4); if (num5 == 3) { if (array2 != null && num20 * 3 + 2 < array2.Length) { b2 = array2[num20 * 3]; b3 = array2[num20 * 3 + 1]; b4 = array2[num20 * 3 + 2]; } else { b2 = (b3 = (b4 = 0)); } if (array3 != null && num20 < array3.Length) { b = array3[num20]; } } else { int num21 = (1 << num4) - 1; b2 = (b3 = (b4 = (byte)((num21 > 0) ? ((byte)(num20 * 255 / num21)) : 0))); } } else if (num5 == 3) { int num22 = array5[num19 * num8]; if (array2 != null && num22 * 3 + 2 < array2.Length) { b2 = array2[num22 * 3]; b3 = array2[num22 * 3 + 1]; b4 = array2[num22 * 3 + 2]; } else { b2 = (b3 = (b4 = 0)); } if (array3 != null && num22 < array3.Length) { b = array3[num22]; } } else if (num8 >= 3) { int num23 = num19 * num8 * (num4 / 8); b2 = array5[num23]; b3 = array5[num23 + num4 / 8]; b4 = array5[num23 + 2 * (num4 / 8)]; if (num8 == 4) { b = array5[num23 + 3 * (num4 / 8)]; } } else { int num24 = (k * num10 + l * num8) * (num4 / 8); b2 = (b3 = (b4 = array5[num24])); if (num8 == 2) { b = array5[num24 + num4 / 8]; } } array6[num18++] = b2; array6[num18++] = b3; array6[num18++] = b4; array6[num18++] = b; } } return new Image { Width = num2, Height = num3, Pixels = array6 }; } private static int Sample(byte[] data, int rowStart, int col, int bitDepth) { int num = rowStart * 8 + col * bitDepth; int num2 = num >> 3; int num3 = 8 - bitDepth - (num & 7); return (data[num2] >> num3) & ((1 << bitDepth) - 1); } private static int Paeth(int a, int b, int c) { int num = a + b - c; int num2 = Math.Abs(num - a); int num3 = Math.Abs(num - b); int num4 = Math.Abs(num - c); if (num2 <= num3 && num2 <= num4) { return a; } if (num3 <= num4) { return b; } return c; } private static int ReadInt32(byte[] buf, int off) { return (buf[off] << 24) | (buf[off + 1] << 16) | (buf[off + 2] << 8) | buf[off + 3]; } private static byte[] Append(byte[] dst, byte[] src, int off, int len) { if (dst == null) { dst = new byte[0]; } byte[] array = new byte[dst.Length + len]; Array.Copy(dst, 0, array, 0, dst.Length); Array.Copy(src, off, array, dst.Length, len); return array; } private static byte[] ReadAll(Stream s, int expected) { using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int count; while ((count = s.Read(array, 0, array.Length)) > 0) { memoryStream.Write(array, 0, count); if (memoryStream.Length > expected * 2) { break; } } return memoryStream.ToArray(); } } public sealed class QqClient : IDisposable { private sealed class RedirectResult { public string Location; public string Body; } private const string SearchUrl = "https://c.y.qq.com/soso/fcgi-bin/client_search_cp"; private const string FcgUrl = "https://u.y.qq.com/cgi-bin/musicu.fcg"; private const string LyricUrl = "https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg"; private const string CoverUrl = "https://y.gtimg.cn/music/photo_new/T002R300x300M000"; private const string PtXLogin = "https://xui.ptlogin2.qq.com/cgi-bin/xlogin"; private const string PtQrShow = "https://ssl.ptlogin2.qq.com/ptqrshow"; private const string PtQrLogin = "https://ssl.ptlogin2.qq.com/ptqrlogin"; private const string Mp3Prefix = "M500"; private const string Mp3PrefixHi = "M800"; private const string Referer = "https://y.qq.com/portal/player.html"; private const string PtReferer = "https://xui.ptlogin2.qq.com/"; private string _lastSetCookie = ""; private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; private HttpClient _http; private HttpClient _cookielessHttp; private HttpClientHandler _handler; private CookieContainer _cookies = new CookieContainer(); private readonly Throttle _throttle; private readonly Random _rng = new Random(Environment.TickCount * 17); private string _savedCookie; private string _uin = ""; private string _guid = ""; private readonly Dictionary<string, string> _mediaMidCache = new Dictionary<string, string>(StringComparer.Ordinal); private string _qrsig = ""; private string _loginSig = ""; private string _lastOauthTrace = ""; private string _appQrId = ""; private MqttWsClient _appMqtt; private CancellationTokenSource _appCts; private readonly object _appLock = new object(); private QqLoginResult _appState = new QqLoginResult { State = QqLoginResult.QqLoginState.Unknown }; private int _loginType = 2; private string _lastDesktopError = ""; private string _encryptUin; public string CookieText => HarvestCookies() ?? _savedCookie ?? ""; public bool HasLogin { get { if (string.IsNullOrEmpty(_uin)) { return false; } if (!string.IsNullOrEmpty(LookupCookie("qm_keyst"))) { return true; } if (!string.IsNullOrEmpty(LookupCookie("qqmusic_key"))) { return true; } return !string.IsNullOrEmpty(LookupCookie("musickey")); } } public string Uin { get { if (!string.IsNullOrEmpty(_uin)) { return _uin; } return "0"; } } private string Authst { get { string text = LookupCookie("qqmusic_key"); if (string.IsNullOrEmpty(text)) { text = LookupCookie("qm_keyst"); } if (string.IsNullOrEmpty(text)) { text = LookupCookie("musickey"); } return text ?? ""; } } public QqClient(Throttle throttle = null) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown NewSession(); _throttle = throttle ?? new Throttle(); HttpClient val = new HttpClient((HttpMessageHandler)new HttpClientHandler { AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate), UseCookies = false }); val.Timeout = TimeSpan.FromSeconds(20.0); _cookielessHttp = val; ((HttpHeaders)_cookielessHttp.DefaultRequestHeaders).TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); ((HttpHeaders)_cookielessHttp.DefaultRequestHeaders).TryAddWithoutValidation("Referer", "https://y.qq.com/portal/player.html"); _savedCookie = EnsureComplete(QqCookieStore.Load()); if (!string.IsNullOrEmpty(_savedCookie) && _savedCookie != QqCookieStore.Load()) { QqCookieStore.Save(_savedCookie); } LoadCookiesIntoContainer(_savedCookie); RefreshUin(); string text = LookupKey(); _loginType = ((!string.IsNullOrEmpty(text) && text.StartsWith("W_X", StringComparison.Ordinal)) ? 1 : 2); EnsureGuid(); } private void NewSession() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown HttpClient http = _http; HttpClientHandler val = new HttpClientHandler(); val.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; val.UseCookies = true; val.CookieContainer = new CookieContainer(); HttpClientHandler val2 = (_handler = val); _cookies = val2.CookieContainer; HttpClient val3 = new HttpClient((HttpMessageHandler)val2) { Timeout = TimeSpan.FromSeconds(20.0) }; ((HttpHeaders)val3.DefaultRequestHeaders).TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); ((HttpHeaders)val3.DefaultRequestHeaders).TryAddWithoutValidation("Referer", "https://y.qq.com/portal/player.html"); _http = val3; if (http != null) { try { ((HttpMessageInvoker)http).Dispose(); } catch { } } } private string LookupCookie(string name) { if (!QqCookieStore.Parse(CookieText).TryGetValue(name, out var value)) { return null; } return value; } private void RefreshUin() { _uin = Digits(LookupCookie("uin") ?? LookupCookie("p_uin") ?? LookupCookie("wxuin") ?? ""); } private static string Digits(string raw) { string text = (raw ?? "").Trim(); if (text.Length == 0) { return ""; } if (text[0] == 'o' || text[0] == 'O') { text = text.Substring(1); } text = text.TrimStart(new char[1] { '0' }); if (text.Length != 0) { return text; } return "0"; } private static string OFormat(string digits) { if (!string.IsNullOrEmpty(digits)) { return "o0" + digits; } return ""; } private static string EnsureComplete(string cookieText) { Dictionary<string, string> dictionary = QqCookieStore.Parse(cookieText); if (dictionary.Count == 0) { return cookieText; } string value = null; string[] array = new string[3] { "qm_keyst", "qqmusic_key", "musickey" }; for (int i = 0; i < array.Length; i++) { if (dictionary.TryGetValue(array[i], out var value2) && !string.IsNullOrEmpty(value2)) { value = value2; break; } } if (!string.IsNullOrEmpty(value)) { dictionary["qm_keyst"] = value; dictionary["qqmusic_key"] = value; dictionary["musickey"] = value; } string raw = null; string[] array2 = new string[3] { "uin", "p_uin", "wxuin" }; for (int j = 0; j < array2.Length; j++) { if (dictionary.TryGetValue(array2[j], out var value3) && !string.IsNullOrEmpty(value3)) { raw = value3; break; } } string text = Digits(raw); if (!string.IsNullOrEmpty(text)) { dictionary["uin"] = OFormat(text); dictionary["p_uin"] = OFormat(text); dictionary["pt2gguin"] = OFormat(text); dictionary["qqmusic_uin"] = text; } return QqCookieStore.Normalize(dictionary); } private string EnsureGuid() { if (!string.IsNullOrEmpty(_guid)) { return _guid; } try { string path = Path.Combine(QqCookieStore.Dir, "qq_guid.txt"); if (File.Exists(path)) { string text = File.ReadAllText(path, Encoding.UTF8).Trim(); if (text.Length >= 6) { _guid = text; return _guid; } } _guid = _rng.Next(100000000, 2000000000).ToString(); Directory.CreateDirectory(QqCookieStore.Dir); File.WriteAllText(path, _guid, Encoding.UTF8); } catch { _guid = "10000"; } return _guid; } public void SetCookie(string cookieText) { cookieText = EnsureComplete(cookieText); ClearCookieContainer(); LoadCookiesIntoContainer(cookieText); QqCookieStore.Save(QqCookieStore.Normalize(QqCookieStore.Parse(cookieText))); _savedCookie = QqCookieStore.Load(); RefreshUin(); EnsureGuid(); } private void ClearCookieContainer() { try { foreach (Cookie cookie in _cookies.GetCookies(new Uri("https://y.qq.com"))) { cookie.Expired = true; } foreach (Cookie cookie2 in _cookies.GetCookies(new Uri("https://u.y.qq.com"))) { cookie2.Expired = true; } } catch { } } public void Logout() { QqCookieStore.Clear(); _savedCookie = ""; _uin = ""; _qrsig = ""; _loginSig = ""; ClearCookieContainer(); try { foreach (Cookie cookie in _cookies.GetCookies(new Uri("https://ptlogin2.qq.com/"))) { cookie.Expired = true; } } catch { } } public Task<List<SongInfo>> SearchAsync(string keyword, int limit = 40, int offset = 0, CancellationToken ct = default(CancellationToken)) { return _throttle.RunAsync(async delegate { int num = offset / Math.Max(1, limit) + 1; string url = "https://c.y.qq.com/soso/fcgi-bin/client_search_cp?format=json&t=0&p=" + num + "&n=" + limit + "&w=" + Uri.EscapeDataString(keyword ?? ""); return ParseSearch(await GetAsync(url, ct).ConfigureAwait(continueOnCapturedContext: false)); }, ct); } private static List<SongInfo> ParseSearch(string json) { List<SongInfo> list = new List<SongInfo>(); Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(json)); if (dictionary == null) { return list; } Dictionary<string, object> dictionary2 = MiniJson.Dict(dictionary, "data"); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "song") : null); List<object> list2 = ((dictionary3 != null) ? MiniJson.List(dictionary3, "list") : null); if (list2 == null) { return list; } foreach (object item in list2) { Dictionary<string, object> dictionary4 = MiniJson.AsObject(item); if (dictionary4 == null) { continue; } string text = MiniJson.Str(dictionary4, "songmid", ""); if (string.IsNullOrEmpty(text)) { continue; } SongInfo songInfo = new SongInfo { Platform = Platform.QQ, SourceId = text, Id = StableId(text), Name = MiniJson.Str(dictionary4, "songname", "?"), Album = MiniJson.Str(dictionary4, "albumname", ""), DurationMs = MiniJson.Lng(dictionary4, "interval", 0L) * 1000 }; string text2 = MiniJson.Str(dictionary4, "media_mid", ""); songInfo.MediaMid = (string.IsNullOrEmpty(text2) ? text : text2); List<object> list3 = MiniJson.List(dictionary4, "singer"); if (list3 != null) { List<string> list4 = new List<string>(); foreach (object item2 in list3) { Dictionary<string, object> dictionary5 = MiniJson.AsObject(item2); string text3 = ((dictionary5 != null) ? MiniJson.Str(dictionary5, "name", "") : ""); if (!string.IsNullOrEmpty(text3)) { list4.Add(text3); } } songInfo.Artists = string.Join(" / ", list4.ToArray()); } string text4 = MiniJson.Str(dictionary4, "albummid", ""); if (!string.IsNullOrEmpty(text4)) { songInfo.AlbumPicUrl = "https://y.gtimg.cn/music/photo_new/T002R300x300M000" + text4 + ".jpg?max_age=2592000"; } Dictionary<string, object> dictionary6 = MiniJson.Dict(dictionary4, "pay"); int num = (int)((dictionary6 != null) ? MiniJson.Lng(dictionary6, "payplay", 0L) : 0); songInfo.Fee = ((num != 0) ? 1 : 0); songInfo.PrivilegeSt = 0; list.Add(songInfo); } return list; } private void LoadCookiesIntoContainer(string cookieText) { foreach (KeyValuePair<string, string> item in QqCookieStore.Parse(cookieText)) { try { _cookies.Add(new Uri("https://y.qq.com/"), new Cookie(item.Key, item.Value) { Domain = ".qq.com", Path = "/" }); } catch { } } } private string HarvestCookies() { try { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); Uri[] array = new Uri[6] { new Uri("https://y.qq.com/"), new Uri("https://qq.com/"), new Uri("https://ptlogin2.qq.com/"), new Uri("https://ssl.ptlogin2.qq.com/"), new Uri("https://ssl.ptlogin2.graph.qq.com/"), new Uri("https://graph.qq.com/") }; foreach (Uri uri in array) { foreach (Cookie cookie in _cookies.GetCookies(uri)) { if (!string.IsNullOrEmpty(cookie.Value)) { dictionary[cookie.Name] = cookie.Value; } } } string text = QqCookieStore.Normalize(dictionary); if (string.IsNullOrEmpty(text)) { return null; } if (text != _savedCookie && QqCookieStore.Parse(text).ContainsKey("qqmusic_key")) { QqCookieStore.Save(text); _savedCookie = text; } return text; } catch { return null; } } private static int Hash33(string s) { return Hash33(s, 0); } private static int Hash33(string s, int seed) { int num = seed; if (string.IsNullOrEmpty(s)) { return num & 0x7FFFFFFF; } for (int i = 0; i < s.Length; i++) { num = (num << 5) + num + s[i]; } return num & 0x7FFFFFFF; } public Task<byte[]> QrBeginAsync(CancellationToken ct = default(CancellationToken)) { return _throttle.RunAsync(async delegate { NewSession(); _qrsig = ""; _loginSig = ""; string url = "https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&daid=383&style=33&login_text=授权并登录&hide_title_bar=1&hide_border=1&target=self&s_url=" + Uri.EscapeDataString(OAuthAuthorizeUrl()) + "&pt_3rd_aid=100497308&pt_feedback_link=" + Uri.EscapeDataString("https://support.qq.com/products/77942"); try { await GetWithCaptureAsync(url, ct).ConfigureAwait(continueOnCapturedContext: false); string text = ExtractSetCookie(_lastSetCookie, "pt_login_sig"); if (!string.IsNullOrEmpty(text)) { _loginSig = text; } } catch { } if (string.IsNullOrEmpty(_loginSig)) { _loginSig = LookupCookie("pt_login_sig") ?? ""; } string text2 = _rng.NextDouble().ToString("R"); string url2 = "https://ssl.ptlogin2.qq.com/ptqrshow?appid=716027609&e=2&l=M&s=3&d=72&v=4&t=" + text2 + "&daid=383&pt_3rd_aid=100497308"; byte[] array = await GetWithCaptureAsync(url2, ct).ConfigureAwait(continueOnCapturedContext: false); string text3 = ExtractSetCookie(_lastSetCookie, "qrsig"); _qrsig = ((!string.IsNullOrEmpty(text3)) ? text3 : (LookupCookie("qrsig") ?? "")); if (array == null || array.Length == 0) { throw new InvalidOperationException("ptqrshow 没返回二维码图片"); } return array; }, ct); } public Task<QqLoginResult> QrPollAsync(CancellationToken ct = default(CancellationToken)) { return _throttle.RunAsync(async delegate { QqLoginResult r = new QqLoginResult(); int num = Hash33(_qrsig); string stringToEscape = "0-0-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); string loginSig = (string.IsNullOrEmpty(_loginSig) ? "" : _loginSig); string url = "https://ssl.ptlogin2.qq.com/ptqrlogin?u1=" + Uri.EscapeDataString(OAuthAuthorizeUrl()) + "&ptqrtoken=" + num + "&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=" + Uri.EscapeDataString(stringToEscape) + "&js_ver=20102616&js_type=1&login_sig=" + Uri.EscapeDataString(loginSig) + "&pt_uistyle=40&aid=716027609&daid=383&pt_3rd_aid=100497308"; string text; try { text = await GetRawAsync(url, ct, "https://xui.ptlogin2.qq.com/").ConfigureAwait(continueOnCapturedContext: false); } catch (Exception ex) { r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = ex.Message; return r; } string state = ""; string url2 = ""; string msg = ""; ParsePtuiCb(text, ref state, ref url2, ref msg); switch (state) { case "0": { string text2 = await AuthorizeQq(url2, ct).ConfigureAwait(continueOnCapturedContext: false); if (string.IsNullOrEmpty(text2)) { r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = "登录成功但没拿到 OAuth code。轨迹:" + _lastOauthTrace; return r; } string value = await MusicuLogin(text2, ct).ConfigureAwait(continueOnCapturedContext: false); if (string.IsNullOrEmpty(value)) { r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = "OAuth code 拿到了但没换到 musickey"; return r; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<string, string> item in QqCookieStore.Parse(HarvestCookies() ?? "")) { dictionary[item.Key] = item.Value; } dictionary["qqmusic_key"] = value; dictionary["musickey"] = value; if (!dictionary.ContainsKey("uin") && !string.IsNullOrEmpty(_uin)) { dictionary["uin"] = _uin; } string text3 = QqCookieStore.Normalize(dictionary); if (!string.IsNullOrEmpty(text3)) { SetCookie(text3); } r.State = QqLoginResult.QqLoginState.Success; r.CookieText = text3; return r; } case "66": r.State = QqLoginResult.QqLoginState.Waiting; return r; case "67": r.State = QqLoginResult.QqLoginState.Scanned; return r; case "65": r.State = QqLoginResult.QqLoginState.Expired; return r; default: r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = (string.IsNullOrEmpty(text) ? ("ptqrlogin 空响应(qrsig " + _qrsig.Length + " 字符,login_sig " + loginSig.Length + " 字符;为 0 说明二维码会话没建立,请点「换一张」重拉)") : ("ptqrlogin 未知响应[" + ((state.Length > 0) ? state : "?") + "] " + Summarize(text))); return r; } }, ct); } private static string OAuthAuthorizeUrl() { return "https://graph.qq.com/oauth2.0/authorize?client_id=100497308&response_type=code&redirect_uri=" + Uri.EscapeDataString("https://y.qq.com/portal/wx_redirect.html") + "&scope=" + Uri.EscapeDataString("get_user_basic,get_vip_info,get_user_detail") + "&state=peak"; } private static void ParsePtuiCb(string text, ref string state, ref string url, ref string msg) { if (string.IsNullOrEmpty(text)) { return; } int num = text.IndexOf("ptuiCB", StringComparison.Ordinal); if (num < 0) { return; } int num2 = text.IndexOf('(', num); int num3 = text.LastIndexOf(')'); if (num2 < 0 || num3 <= num2) { return; } string text2 = text.Substring(num2 + 1, num3 - num2 - 1); List<string> list = new List<string>(); int num4 = 0; while (num4 < text2.Length && list.Count < 8) { char c = text2[num4]; if ("'\"‘“".IndexOf(c) < 0) { num4++; continue; } string text3 = text2; int num5 = text3.IndexOf(c switch { '‘' => '’', '“' => '”', _ => c, }, num4 + 1); if (num5 < 0) { break; } list.Add(text2.Substring(num4 + 1, num5 - num4 - 1)); num4 = num5 + 1; } if (list.Count > 0) { state = list[0] ?? ""; } if (list.Count > 2) { url = list[2] ?? ""; } if (list.Count > 4) { msg = list[4] ?? ""; } } private static string Summarize(string text) { if (string.IsNullOrEmpty(text)) { return "(空)"; } string text2 = text.Replace("\r", " ").Replace("\n", " ").Trim(); if (text2.Length > 150) { return text2.Substring(0, 150) + "…"; } return text2; } private async Task<string> AuthorizeQq(string checkSigUrl, CancellationToken ct) { StringBuilder trace = new StringBuilder(); try { string text = ExtractParam(checkSigUrl, "ptsigx"); string text2 = ExtractParam(checkSigUrl, "uin"); if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { _lastOauthTrace = "check_sig URL 里缺 ptsigx/uin:" + ShortHost(checkSigUrl); return ""; } trace.Append("ptsigx/uin ✓ "); string url = "https://ssl.ptlogin2.graph.qq.com/check_sig?uin=" + text2 + "&pttype=1&service=ptqrlogin&nodirect=0&ptsigx=" + Uri.EscapeDataString(text) + "&s_url=" + Uri.EscapeDataString("https://graph.qq.com/oauth2.0/login_jump") + "&ptlang=2052&ptredirect=100&aid=716027609&daid=383&j_later=0&low_login_hour=0®master=0&pt_login_type=3&pt_aid=0&pt_aaid=16&pt_light=0&pt_3rd_aid=100497308"; await GetRawAsync(url, ct, "https://xui.ptlogin2.qq.com/").ConfigureAwait(continueOnCapturedContext: false); if (!FlattenCookies().TryGetValue("p_skey", out var value) || string.IsNullOrEmpty(value)) { _lastOauthTrace = trace?.ToString() + "check_sig 后没拿到 p_skey cookie"; return ""; } trace.Append("p_skey ✓(len=").Append(value.Length).Append(") "); string value2 = Hash33(value, 5381).ToString(); trace.Append("gtk=").Append(value2).Append(' '); Dictionary<string, string> form = new Dictionary<string, string> { { "response_type", "code" }, { "client_id", "100497308" }, { "redirect_uri", "https://y.qq.com/portal/wx_redirect.html?login_type=1&surl=https://y.qq.com/" }, { "scope", "get_user_info,get_app_friends" }, { "state", "state" }, { "switch", "" }, { "from_ptlogin", "1" }, { "src", "1" }, { "update_auth", "1" }, { "openapi", "1010_1030" }, { "g_tk", value2 }, { "auth_time", ((long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds).ToString() }, { "ui", Guid.NewGuid().ToString() } }; List<string> list = new List<string>(FlattenCookies().Keys); list.Sort(StringComparer.Ordinal); trace.Append("cookies[").Append(string.Join(",", list.ToArray())).Append("] "); RedirectResult redirectResult = await PostFormRedirectAsync("https://graph.qq.com/oauth2.0/authorize", form, "https://graph.qq.com/", ct).ConfigureAwait(continueOnCapturedContext: false); string text3 = ExtractCodeParam(redirectResult.Location) ?? ExtractCodeParam(redirectResult.Body); if (!string.IsNullOrEmpty(text3)) { trace.Append("authorize→code ✓"); _lastOauthTrace = trace.ToString(); return text3; } _lastOauthTrace = trace?.ToString() + "authorize 没回 code,Location=" + (((redirectResult.Location ?? "(无)").Length > 220) ? redirectResult.Location.Substring(0, 220) : (redirectResult.Location ?? "(无)")) + " 正文前80字=" + ((redirectResult.Body != null && redirectResult.Body.Length > 80) ? redirectResult.Body.Substring(0, 80).Replace("\n", " ") : redirectResult.Body); return ""; } catch (Exception ex) { _lastOauthTrace = trace?.ToString() + "异常:" + ex.Message; return ""; } } private static string ExtractParam(string url, string name) { if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) { return ""; } int num = url.IndexOf(name + "=", StringComparison.Ordinal); if (num < 0) { return ""; } int num2 = num + name.Length + 1; int num3 = url.IndexOf('&', num2); return Uri.UnescapeDataString((num3 < 0) ? url.Substring(num2) : url.Substring(num2, num3 - num2)); } private static string ShortHost(string url) { if (string.IsNullOrEmpty(url)) { return "(空)"; } int num = url.IndexOf("://", StringComparison.Ordinal); string text = ((num >= 0) ? url.Substring(num + 3) : url); int num2 = text.IndexOf('/'); string obj = ((num2 > 0) ? text.Substring(0, num2) : text); string text2 = ((num2 > 0) ? text.Substring(num2, Math.Min(40, text.Length - num2)) : ""); return obj + text2; } private static string ExtractCodeParam(string text) { if (string.IsNullOrEmpty(text)) { return ""; } int num = text.IndexOf("code=", StringComparison.Ordinal); if (num < 0) { return ""; } int num2 = num + "code=".Length; int i; for (i = num2; i < text.Length; i++) { char c = text[i]; if (c == '&' || c == '\'' || c == '"' || c == ',' || c == ')' || c == ']' || char.IsWhiteSpace(c)) { break; } } if (i <= num2) { return ""; } return text.Substring(num2, i - num2); } private async Task<string> MusicuLogin(string code, CancellationToken ct) { try { string text = _rng.Next(100000000, 999999999).ToString(); string body = "{\"req_0\":{\"module\":\"QQConnectLogin.LoginServer\",\"method\":\"QQLogin\",\"param\":{\"code\":\"" + code + "\",\"strAppid\":\"100497308\"}},\"comm\":{\"ct\":11,\"cv\":14090008,\"v\":14090008,\"chid\":\"10003505\",\"tmeAppID\":\"qqmusic\",\"QIMEI\":\"\",\"QIMEI36\":\"\",\"OpenUDID\":\"" + text + "\",\"udid\":\"" + text + "\",\"OpenUDID2\":\"" + text + "\",\"aid\":\"" + text + "\",\"os_ver\":\"10\",\"phonetype\":\"MI 6\",\"devicelevel\":\"29\",\"newdevicelevel\":\"29\",\"tmeLoginType\":2}}"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await PostJsonAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", body, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary2 = ((dictionary != null) ? MiniJson.Dict(dictionary, "req_0") : null); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "data") : null); if (dictionary3 != null) { string text2 = MiniJson.Str(dictionary3, "musickey", ""); if (!string.IsNullOrEmpty(text2)) { return text2; } } return LookupCookie("musickey") ?? ""; } catch { return ""; } } public Task<byte[]> AppQrBeginAsync(CancellationToken ct = default(CancellationToken)) { return _throttle.RunAsync(async delegate { AppQrStop(); string body = "{\"req_0\":{\"module\":\"music.login.LoginServer\",\"method\":\"CreateQRCode\",\"param\":{\"tmeAppID\":\"qqmusic\",\"ct\":11,\"cv\":20090008}},\"comm\":{\"ct\":23,\"cv\":0,\"tmeAppID\":\"qqmusic\"}}"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await PostJsonAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", body, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary2 = ((dictionary != null) ? MiniJson.Dict(dictionary, "req_0") : null); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "data") : null); string text = ((dictionary3 != null) ? MiniJson.Str(dictionary3, "qrcode", "") : ""); string text2 = ((dictionary3 != null) ? MiniJson.Str(dictionary3, "qrcodeID", "") : ""); if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { throw new InvalidOperationException("CreateQRCode 没返回二维码"); } _appQrId = text2; SetAppState(new QqLoginResult { State = QqLoginResult.QqLoginState.Waiting }); int num = text.IndexOf(','); byte[] result = Convert.FromBase64String((num >= 0) ? text.Substring(num + 1) : text); CancellationTokenSource cts = new CancellationTokenSource(); lock (_appLock) { _appCts = cts; } string id = text2; Task.Run(() => RunAppMqttAsync(id, cts.Token)); return result; }, ct); } public QqLoginResult AppQrPoll() { lock (_appLock) { return new QqLoginResult { State = _appState.State, CookieText = _appState.CookieText, RawMessage = _appState.RawMessage }; } } public void AppQrStop() { CancellationTokenSource appCts; MqttWsClient appMqtt; lock (_appLock) { appCts = _appCts; _appCts = null; appMqtt = _appMqtt; _appMqtt = null; } try { appCts?.Cancel(); } catch { } try { appMqtt?.Dispose(); } catch { } } private void SetAppState(QqLoginResult r) { lock (_appLock) { _appState = r; } } private async Task RunAppMqttAsync(string qrId, CancellationToken ct) { _ = 3; try { MqttWsClient mqtt = new MqttWsClient(); lock (_appLock) { _appMqtt = mqtt; } Dictionary<string, string> headers = new Dictionary<string, string> { { "Origin", "https://y.qq.com" }, { "Referer", "https://y.qq.com/" }, { "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } }; Dictionary<string, string> connectUserProps = new Dictionary<string, string> { { "tmeAppID", "qqmusic" }, { "business", "management" }, { "hashTag", qrId }, { "clientTag", "management.user" }, { "userID", qrId } }; string clientId = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() + _rng.Next(1000, 10000); await mqtt.ConnectAsync("mu.y.qq.com", 443, "/ws/handshake", headers, clientId, connectUserProps, ct).ConfigureAwait(continueOnCapturedContext: false); Dictionary<string, string> subUserProps = new Dictionary<string, string> { { "authorization", "tmelogin" }, { "pubsub", "unicast" } }; await mqtt.SubscribeAsync("management.qrcode_login/" + qrId, subUserProps, ct).ConfigureAwait(continueOnCapturedContext: false); while (!ct.IsCancellationRequested) { MqttWsMessage mqttWsMessage = mqtt.ReceiveMessage(TimeSpan.FromSeconds(15.0)); if (mqttWsMessage == null) { try { await mqtt.PingAsync(ct).ConfigureAwait(continueOnCapturedContext: false); } catch { break; } continue; } string value = ""; mqttWsMessage.UserProps.TryGetValue("type", out value); switch (value) { case "scanned": SetAppState(new QqLoginResult { State = QqLoginResult.QqLoginState.Scanned }); break; case "canceled": SetAppState(new QqLoginResult { State = QqLoginResult.QqLoginState.Failed, RawMessage = "手机上取消了登录" }); return; case "timeout": SetAppState(new QqLoginResult { State = QqLoginResult.QqLoginState.Expired }); return; case "loginFailed": { string text = mqttWsMessage.Payload ?? ""; SetAppState(new QqLoginResult { State = QqLoginResult.QqLoginState.Failed, RawMessage = "手机端登录失败" + ((text.Length > 0) ? (":" + ((text.Length > 60) ? text.Substring(0, 60) : text)) : "") }); return; } case "cookies": SetAppState(await FinishAppLoginAsync(qrId, mqttWsMessage.Payload ?? "", ct).ConfigureAwait(continueOnCapturedContext: false)); return; } } } catch (OperationCanceledException) { } catch (Exception ex2) { SetAppState(new QqLoginResult { State = QqLoginResult.QqLoginState.Failed, RawMessage = "App 扫码通道异常:" + ex2.Message }); } } private async Task<QqLoginResult> FinishAppLoginAsync(string qrId, string payload, CancellationToken ct) { QqLoginResult r = new QqLoginResult(); try { Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(payload)); Dictionary<string, object> dictionary2 = ((dictionary != null) ? MiniJson.Dict(dictionary, "cookies") : null); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "qqmusic_uin") : null); Dictionary<string, object> dictionary4 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "qqmusic_key") : null); string text = ((dictionary3 != null) ? MiniJson.Str(dictionary3, "value", "") : ""); string text2 = ((dictionary4 != null) ? MiniJson.Str(dictionary4, "value", "") : ""); Dictionary<string, object> dictionary5 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "euin") : null); string euin = ((dictionary5 != null) ? MiniJson.Str(dictionary5, "value", "") : ""); if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = "cookies 推送里缺 qqmusic_uin / qqmusic_key"; return r; } string digits2 = text.Trim(); if (digits2.StartsWith("o") || digits2.StartsWith("O")) { digits2 = digits2.Substring(1); } digits2 = digits2.TrimStart(new char[1] { '0' }); if (digits2.Length == 0) { digits2 = "0"; } string musickey = text2; string loginKey = ""; try { string[] bodies = new string[2] { "{\"req_0\":{\"module\":\"music.login.LoginServer\",\"method\":\"Login\",\"param\":{\"musicid\":" + digits2 + ",\"qrCodeID\":\"" + qrId + "\",\"token\":\"" + text2 + "\",\"loginType\":6,\"needCookie\":1}},\"comm\":{\"ct\":11,\"cv\":20090008,\"tmeAppID\":\"qqmusic\",\"tmeLoginType\":6}}", "{\"req_0\":{\"module\":\"music.login.LoginServer\",\"method\":\"Login\",\"param\":{\"musicid\":" + digits2 + ",\"qrCodeID\":\"" + qrId + "\",\"token\":\"" + text2 + "\"}},\"comm\":{\"ct\":11,\"cv\":20090008,\"tmeAppID\":\"qqmusic\",\"tmeLoginType\":6}}" }; int bi; for (bi = 0; bi < bodies.Length; bi++) { Dictionary<string, object> dictionary6 = MiniJson.AsObject(MiniJson.Parse(await _throttle.RunAsync(() => PostJsonAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", bodies[bi], ct), ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary7 = ((dictionary6 != null) ? MiniJson.Dict(dictionary6, "req_0") : null); Dictionary<string, object> dictionary8 = ((dictionary7 != null) ? MiniJson.Dict(dictionary7, "data") : null); string text3 = ((dictionary8 != null) ? MiniJson.Str(dictionary8, "musickey", "") : ""); if (!string.IsNullOrEmpty(text3)) { musickey = text3; loginKey = text3; } long num = ((dictionary8 != null) ? MiniJson.Lng(dictionary8, "loginType", 0L) : 0); if (num > 0 && num < 10) { _loginType = (int)num; } if (!string.IsNullOrEmpty(loginKey)) { break; } } } catch { } string text4 = QqCookieStore.Normalize(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["uin"] = OFormat(digits2), ["p_uin"] = OFormat(digits2), ["pt2gguin"] = OFormat(digits2), ["qqmusic_uin"] = digits2, ["euin"] = euin, ["qqmusic_key"] = musickey, ["qm_keyst"] = musickey, ["musickey"] = musickey }); if (!string.IsNullOrEmpty(text4)) { SetCookie(text4); } try { string checkBody = "{\"comm\":{\"ct\":19,\"cv\":2201,\"uin\":\"" + digits2 + "\",\"authst\":\"" + musickey + "\",\"g_tk\":" + Hash33(musickey) + ",\"tmeLoginType\":" + ((!string.IsNullOrEmpty(musickey) && musickey.StartsWith("W_X", StringComparison.Ordinal)) ? 1 : 2) + "},\"req_0\":{\"module\":\"music.UserInfo.userInfoServer\",\"method\":\"GetLoginUserInfo\",\"param\":{}}}"; Dictionary<string, object> dictionary9 = MiniJson.AsObject(MiniJson.Parse(await PostJsonWithCookieAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", checkBody, CookieText, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary10 = ((dictionary9 != null) ? MiniJson.Dict(dictionary9, "req_0") : null); long num2 = ((dictionary10 != null) ? MiniJson.Lng(dictionary10, "code", -1L) : (-1)); Dictionary<string, object> dictionary11 = ((dictionary10 != null) ? MiniJson.Dict(dictionary10, "data") : null); string value = ((dictionary11 != null) ? MiniJson.Str(dictionary11, "nick", "") : ""); if (string.IsNullOrEmpty(value) && dictionary11 != null) { Dictionary<string, object> dictionary12 = MiniJson.Dict(dictionary11, "info"); if (dictionary12 != null) { value = MiniJson.Str(dictionary12, "nick", ""); if (string.IsNullOrEmpty(value)) { Dictionary<string, object> dictionary13 = MiniJson.Dict(dictionary12, "base"); if (dictionary13 != null) { value = MiniJson.Str(dictionary13, "nick", ""); } } } } if (num2 != 0L || string.IsNullOrEmpty(value)) { await Task.Delay(900, ct).ConfigureAwait(continueOnCapturedContext: false); Dictionary<string, object> dictionary14 = MiniJson.AsObject(MiniJson.Parse(await PostJsonWithCookieAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", checkBody, CookieText, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary15 = ((dictionary14 != null) ? MiniJson.Dict(dictionary14, "req_0") : null); num2 = ((dictionary15 != null) ? MiniJson.Lng(dictionary15, "code", -1L) : (-1)); Dictionary<string, object> dictionary16 = ((dictionary15 != null) ? MiniJson.Dict(dictionary15, "data") : null); value = ((dictionary16 != null) ? MiniJson.Str(dictionary16, "nick", "") : ""); } if (num2 != 0L || string.IsNullOrEmpty(value)) { r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = "登录已提交,但服务器不认这把 key(code=" + num2 + "),请重新扫码"; r.CookieText = text4; return r; } } catch { } r.State = QqLoginResult.QqLoginState.Success; r.CookieText = text4; } catch (Exception ex) { r.State = QqLoginResult.QqLoginState.Failed; r.RawMessage = "处理 cookies 推送失败:" + ex.Message; } return r; } public async Task<SongUrlResult> GetSongUrlAsync(SongInfo song, CancellationToken ct = default(CancellationToken)) { string mid = ((song == null) ? "" : (string.IsNullOrEmpty(song.SourceId) ? song.Id.ToString() : song.SourceId)); if (string.IsNullOrEmpty(mid)) { return new SongUrlResult { Code = -1, Message = "缺少 QQ songmid" }; } long id = song?.Id ?? 0; string text = QqCookieStore.Load(); if (!string.IsNullOrEmpty(text)) { string text2 = EnsureComplete(text); if (!string.IsNullOrEmpty(text2) && text2 != text) { SetCookie(text2); } } EnsureGuid(); bool logged = HasLogin; string fileMid = ((song != null && !string.IsNullOrEmpty(song.MediaMid)) ? song.MediaMid : null); if (string.IsNullOrEmpty(fileMid)) { fileMid = await ResolveMediaMidAsync(mid, ct).ConfigureAwait(continueOnCapturedContext: false); } if (song != null && string.IsNullOrEmpty(song.MediaMid)) { song.MediaMid = fileMid; } if (logged) { try { SongUrlResult songUrlResult = await GetSongUrlDesktopAsync(song, mid, fileMid, ct).ConfigureAwait(continueOnCapturedContext: false); if (songUrlResult != null && songUrlResult.Ok) { return songUrlResult; } if (songUrlResult != null && !string.IsNullOrEmpty(songUrlResult.Message)) { _lastDesktopError = songUrlResult.Message; } } catch (Exception ex) { _lastDesktopError = "桌面通道异常:" + ex.Message; } } List<string[]> tries = new List<string[]>(); if (logged) { tries.Add(new string[3] { "M800.mp3", "320k", "320000" }); } tries.Add(new string[3] { "M500.mp3", "128k", "128000" }); tries.Add(new string[3] { "M500.mp4", "96k aac", "96000" }); tries.Add(new string[3] { "C400.m4a", "48k aac", "48000" }); SongUrlResult last = null; for (int i = 0; i < tries.Count; i++) { string[] array = tries[i]; SongUrlResult songUrlResult2 = await GetSongUrlOnceAsync(song, mid, fileMid, array[0], array[1], int.Parse(array[2]), null, ct).ConfigureAwait(continueOnCapturedContext: false); if (songUrlResult2.Ok) { return songUrlResult2; } last = songUrlResult2; if (songUrlResult2.Code == -104004) { break; } } if (last == null) { last = new SongUrlResult { Id = id, Code = -112, Message = "QQ 取流没有任何返回" }; } string probe = null; if (logged) { probe = await ProbeAccountAsync(ct).ConfigureAwait(continueOnCapturedContext: false); } string reason = last.Message ?? ""; if (logged && string.IsNullOrEmpty(probe)) { SongUrlResult songUrlResult3 = await GetSongUrlOnceAsync(song, mid, fileMid, "M500.mp3", "128k(游客)", 128000, "", ct).ConfigureAwait(continueOnCapturedContext: false); if (!songUrlResult3.Ok) { songUrlResult3 = await GetSongUrlOnceAsync(song, mid, fileMid, "M500.mp4", "96k(游客)", 96000, "", ct).ConfigureAwait(continueOnCapturedContext: false); } if (songUrlResult3.Ok) { songUrlResult3.Message = "登录态已失效,已按游客身份播放(会员曲要重新扫码登录才能放)"; return songUrlResult3; } } if (!logged) { last.Message = "QQ音乐未登录:会员曲 / 付费曲必须有登录态才能取流,扫码登录后再点这首歌"; } else if (string.IsNullOrEmpty(probe)) { last.Message = "QQ音乐没认出登录态(cookie 不完整或已过期):面板里退出登录后重新扫码;服务器返回 " + reason; } else { string text3 = (string.IsNullOrEmpty(_lastDesktopError) ? "" : (";桌面通道:" + _lastDesktopError)); last.Message = "账号 " + probe + " 取不到直链:" + reason + text3 + "(确认绿钻未过期,或这首确实无版权)"; } last.Code = -112; last.Fee = song?.Fee ?? 1; return last; } private Task<string> ResolveMediaMidAsync(string songmid, CancellationToken ct) { if (string.IsNullOrEmpty(songmid)) { return Task.FromResult(songmid ?? ""); } if (_mediaMidCache.TryGetValue(songmid, out var value)) { return Task.FromResult(value); } return _throttle.RunAsync(async delegate { try { string url = "https://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg?songmid=" + Uri.EscapeDataString(songmid) + "&format=json&platform=yqq&needNewCode=0"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await GetAsync(url, ct).ConfigureAwait(continueOnCapturedContext: false))); List<object> list = ((dictionary != null) ? MiniJson.List(dictionary, "data") : null); if (list != null && list.Count > 0) { Dictionary<string, object> dictionary2 = MiniJson.AsObject(list[0]); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "file") : null); string text = ((dictionary3 != null) ? MiniJson.Str(dictionary3, "media_mid", "") : ""); if (!string.IsNullOrEmpty(text)) { _mediaMidCache[songmid] = text; return text; } } } catch { } _mediaMidCache[songmid] = songmid; return songmid; }, ct); } private Task<SongUrlResult> GetSongUrlOnceAsync(SongInfo song, string mid, string fileMid, string suffix, string level, int br, string cookieOverride, CancellationToken ct) { string guid = EnsureGuid(); bool flag = cookieOverride != null && cookieOverride.Length == 0; string uin = (flag ? "0" : Uin); string cookie = ((cookieOverride != null) ? cookieOverride : CookieText); int loginflag = ((!flag) ? 1 : 0); return _throttle.RunAsync(async delegate { string text = suffix.Substring(0, 4) + fileMid + suffix.Substring(4); string text2 = "{\"uin\":\"" + uin + "\",\"format\":\"json\",\"ct\":\"24\",\"cv\":\"0\",\"platform\":\"yqq.json\",\"guid\":\"" + guid + "\"}"; string text3 = "{\"module\":\"CDN.SrfCdnDispatchServer\",\"method\":\"GetCdnDispatch\",\"param\":{\"guid\":\"" + guid + "\",\"calltype\":0,\"userip\":\"\"}}"; string text4 = "{\"module\":\"vkey.GetVkeyServer\",\"method\":\"CgiGetVkey\",\"param\":{\"guid\":\"" + guid + "\",\"songmid\":[\"" + mid + "\"],\"songtype\":[0],\"filename\":[\"" + text + "\"],\"uin\":\"" + uin + "\",\"loginflag\":" + loginflag + ",\"platform\":\"20\"}}"; string body = "{\"comm\":" + text2 + ",\"req\":" + text3 + ",\"req_0\":" + text4 + "}"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await PostJsonWithCookieAsync("https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&platform=yqq.json", body, cookie, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary2 = ((dictionary != null) ? MiniJson.Dict(dictionary, "req_0") : null); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "data") : null); if (dictionary3 == null) { return Fail(song, -500, "QQ 取流响应异常"); } string text5 = ""; List<object> list = MiniJson.List(dictionary3, "sip"); if (list != null && list.Count > 0) { text5 = Convert.ToString(list[0]); } string text6 = ""; long num = 0L; string text7 = ""; long num2 = 0L; List<object> list2 = MiniJson.List(dictionary3, "midurlinfo"); if (list2 != null && list2.Count > 0) { Dictionary<string, object> dictionary4 = MiniJson.AsObject(list2[0]); if (dictionary4 != null) { text6 = MiniJson.Str(dictionary4, "purl", ""); num = MiniJson.Lng(dictionary4, "result", 0L); text7 = MiniJson.Str(dictionary4, "errtype", ""); num2 = MiniJson.Lng(dictionary4, "code", 0L); } } if (string.IsNullOrEmpty(text6)) { int code = -112; string text8 = "QQ 音乐没返回直链"; switch (num) { case 104003L: code = -104003; text8 = "该账号下没有这首歌的播放权限(result=104003:无版权 / 需会员)"; break; case 104004L: code = -104004; text8 = "需要单独付费购买(result=104004)"; break; default: text8 = "QQ 音乐拒绝取流 result=" + num; break; case 0L: if (num2 != 0L) { text8 = "QQ 音乐拒绝取流 code=" + num2; } break; } if (!string.IsNullOrEmpty(text7)) { text8 = text8 + " errtype=" + text7; } return Fail(song, code, text8); } string text9 = ((text5.Length > 0) ? text5 : "http://ws.stream.qqmusic.qq.com/"); string url = NeteaseClient.ForceHttps(text9.TrimEnd(new char[1] { '/' }) + "/" + text6.TrimStart(new char[1] { '/' })); return (!(await VerifyUrlAsync(url, ct).ConfigureAwait(continueOnCapturedContext: false))) ? Fail(song, -112, "拿到直链但 CDN 拒绝下载(" + level + "),换档重试") : new SongUrlResult { Id = ((song != null) ? song.Id : 0), Url = url, Code = 200, Br = br, Level = level, Fee = ((song != null) ? song.Fee : 0) }; }, ct); } private async Task<bool> VerifyUrlAsync(string url, CancellationToken ct) { try { HttpRequestMessage val = new HttpRequestMessage(HttpMethod.Get, url); ((HttpHeaders)val.Headers).TryAddWithoutValidation("Range", "bytes=0-2048"); HttpResponseMessage val2 = await _cookielessHttp.SendAsync(val, (HttpCompletionOption)1, ct).ConfigureAwait(continueOnCapturedContext: false); try { int statusCode = (int)val2.StatusCode; return statusCode == 200 || statusCode == 206 || statusCode == 416; } finally { val2.Dispose(); } } catch { return true; } } private Task<SongUrlResult> GetSongUrlDesktopAsync(SongInfo song, string mid, string fileMid, CancellationToken ct) { string guid = EnsureGuid(); string uin = Uin; string key = Authst; string cookie = CookieText; int loginType = _loginType; if (string.IsNullOrEmpty(key) || uin == "0" || string.IsNullOrEmpty(fileMid)) { _lastDesktopError = "缺少登录态(authst/uin/media_mid)"; return Task.FromResult(Fail(song, -112, _lastDesktopError)); } int gtk = Hash33(key); return _throttle.RunAsync(async delegate { string text = "{\"ct\":19,\"cv\":2201,\"uin\":\"" + uin + "\",\"authst\":\"" + key + "\",\"g_tk\":" + gtk + ",\"tmeLoginType\":" + loginType + "}"; string text2 = "{\"module\":\"music.vkey.GetVkey\",\"method\":\"UrlGetVkey\",\"param\":{\"guid\":\"" + guid + "\",\"uin\":\"" + uin + "\",\"ctx\":0,\"filename\":[\"M500" + fileMid + ".mp3\",\"M800" + fileMid + ".mp3\"],\"songmid\":[\"" + mid + "\",\"" + mid + "\"],\"songtype\":[0,0]}}"; string body = "{\"comm\":" + text + ",\"req_0\":" + text2 + "}"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await PostJsonWithCookieAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", body, cookie, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary2 = ((dictionary != null) ? MiniJson.Dict(dictionary, "req_0") : null); Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "data") : null); if (dictionary3 == null) { long num = ((dictionary != null) ? MiniJson.Lng(dictionary, "code", 0L) : 0); long num2 = ((dictionary2 != null) ? MiniJson.Lng(dictionary2, "code", 0L) : 0); _lastDesktopError = "桌面端响应异常 code=" + ((num2 != 0L) ? num2 : num); return Fail(song, -112, _lastDesktopError); } string text3 = ""; List<object> list = MiniJson.List(dictionary3, "sip"); if (list != null && list.Count > 0) { text3 = Convert.ToString(list[0]); } string text4 = ""; string hit = ""; long num3 = 0L; List<object> list2 = MiniJson.List(dictionary3, "midurlinfo"); if (list2 != null) { string text5 = ""; string text6 = ""; foreach (object item in list2) { Dictionary<string, object> dictionary4 = MiniJson.AsObject(item); if (dictionary4 != null) { long num4 = MiniJson.Lng(dictionary4, "result", 0L); if (num4 != 0L) { num3 = num4; } else { string text7 = MiniJson.Str(dictionary4, "purl", ""); if (!string.IsNullOrEmpty(text7)) { string text8 = MiniJson.Str(dictionary4, "filename", ""); if (text8.IndexOf("M800", StringComparison.OrdinalIgnoreCase) >= 0) { text4 = text7; hit = text8; break; } if (string.IsNullOrEmpty(text5)) { text5 = text7; text6 = text8; } } } } } if (string.IsNullOrEmpty(text4) && !string.IsNullOrEmpty(text5)) { text4 = text5; hit = text6; } } if (string.IsNullOrEmpty(text4)) { switch (num3) { case 104004L: _lastDesktopError = "需要单独付费购买(result=104004)"; return Fail(song, -104004, _lastDesktopError); case 104003L: _lastDesktopError = "账号没有这首歌的播放权限(result=104003)"; return Fail(song, -104003, _lastDesktopError); default: _lastDesktopError = "桌面端没返回直链 result=" + num3; return Fail(song, -112, _lastDesktopError); } } string text9 = ((text3.Length > 0) ? text3 : "https://isure.stream.qqmusic.qq.com/"); string url = NeteaseClient.ForceHttps(text9.TrimEnd(new char[1] { '/' }) + "/" + text4.TrimStart(new char[1] { '/' })); if (!(await VerifyUrlAsync(url, ct).ConfigureAwait(continueOnCapturedContext: false))) { _lastDesktopError = "桌面端直链 CDN 拒绝下载"; return Fail(song, -112, _lastDesktopError); } bool flag = hit.IndexOf("M800", StringComparison.OrdinalIgnoreCase) >= 0; return new SongUrlResult { Id = ((song != null) ? song.Id : 0), Url = url, Code = 200, Br = (flag ? 320000 : 128000), Level = (flag ? "320k · 桌面通道" : "128k · 桌面通道"), Fee = ((song != null) ? song.Fee : 0) }; }, ct); } private Task<string> ProbeAccountAsync(CancellationToken ct) { EnsureGuid(); string uin = Uin; string key = Authst; string cookie = CookieText; int loginType = _loginType; return _throttle.RunAsync(async delegate { try { string text = "{\"ct\":19,\"cv\":2201,\"uin\":\"" + uin + "\",\"authst\":\"" + key + "\",\"g_tk\":" + Hash33(key) + ",\"tmeLoginType\":" + loginType + "}"; string text2 = "{\"module\":\"music.UserInfo.userInfoServer\",\"method\":\"GetLoginUserInfo\",\"param\":{}}"; string body = "{\"comm\":" + text + ",\"req_0\":" + text2 + "}"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await PostJsonWithCookieAsync("https://u.y.qq.com/cgi-bin/musicu.fcg", body, cookie, ct).ConfigureAwait(continueOnCapturedContext: false))); Dictionary<string, object> dictionary2 = ((dictionary != null) ? MiniJson.Dict(dictionary, "req_0") : null); long num = ((dictionary2 != null) ? MiniJson.Lng(dictionary2, "code", 0L) : 0); if (num == 1000 || num == 104400 || num == 104401) { return (string)null; } Dictionary<string, object> dictionary3 = ((dictionary2 != null) ? MiniJson.Dict(dictionary2, "data") : null); if (dictionary3 == null || dictionary3.Count == 0) { return (string)null; } string text3 = MiniJson.Str(dictionary3, "nick", ""); string text4 = MiniJson.Str(dictionary3, "str_musicid", ""); long result = MiniJson.Lng(dictionary3, "musicid", 0L); if (result == 0L && !string.IsNullOrEmpty(text4)) { long.TryParse(text4, out result); } if (result == 0L) { result = MiniJson.Lng(dictionary3, "uin", 0L); } if (result == 0L) { return (string)null; } return string.IsNullOrEmpty(text3) ? ("uin=" + result) : (text3 + "(uin=" + result + ")"); } catch { return (string)null; } }, ct); } private static SongUrlResult Fail(SongInfo song, int code, string message) { return new SongUrlResult { Id = (song?.Id ?? 0), Code = code, Fee = (song?.Fee ?? 1), Message = message }; } private static string Trim(string text, int max) { string text2 = (text ?? "").Replace("\r", " ").Replace("\n", " ").Trim(); if (text2.Length <= max) { return text2; } return text2.Substring(0, max); } public Task<LyricResult> GetLyricAsync(SongInfo song, CancellationToken ct = default(CancellationToken)) { string mid = ((song == null) ? "" : (string.IsNullOrEmpty(song.SourceId) ? song.Id.ToString() : song.SourceId)); long id = song?.Id ?? 0; if (string.IsNullOrEmpty(mid)) { return Task.FromResult(new LyricResult { Id = id }); } return _throttle.RunAsync(async delegate { string url = "https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=" + Uri.EscapeDataString(mid) + "&format=json&nobase64=0&g_tk=5381&loginUin=0&hostUin=0&platform=yqq&needNewCode=0"; Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(await GetAsync(url, ct).ConfigureAwait(continueOnCapturedContext: false))); return (dictionary == null) ? new LyricResult { Id = id } : new LyricResult { Id = id, Lrc = DecodeBase64(MiniJson.Str(dictionary, "lyric", "")), Translated = DecodeBase64(MiniJson.Str(dictionary, "trans", "")) }; }, ct); } private static string DecodeBase64(string b64) { if (string.IsNullOrEmpty(b64)) { return ""; } try { return Encoding.UTF8.GetString(Convert.FromBase64String(b64)); } catch { return (b64.IndexOf('[') >= 0) ? b64 : ""; } } private async Task<string> GetAsync(string url, CancellationToken ct) { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url); try { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Referer", "https://y.qq.com/portal/player.html"); HttpResponseMessage resp = await ((HttpMessageInvoker)_http).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } private async Task<string> GetRawAsync(string url, CancellationToken ct, string referer = null) { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url); try { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Referer", referer ?? "https://y.qq.com/portal/player.html"); HttpResponseMessage resp = await ((HttpMessageInvoker)_http).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { return await resp.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } private async Task<byte[]> GetWithCaptureAsync(string url, CancellationToken ct) { _lastSetCookie = ""; HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url); try { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Referer", "https://xui.ptlogin2.qq.com/"); HttpResponseMessage resp = await ((HttpMessageInvoker)_http).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { try { if (((HttpHeaders)resp.Headers).Contains("Set-Cookie")) { _lastSetCookie = string.Join("\n", ((HttpHeaders)resp.Headers).GetValues("Set-Cookie")); } } catch { } return await resp.Content.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } private static string ExtractSetCookie(string setCookieText, string name) { if (string.IsNullOrEmpty(setCookieText) || string.IsNullOrEmpty(name)) { return null; } string[] array = setCookieText.Split(new char[1] { '\n' }); foreach (string text in array) { int num = text.IndexOf(name + "=", StringComparison.Ordinal); if (num == 0 || (num > 0 && (text[num - 1] == ' ' || text[num - 1] == ';'))) { int num2 = num + name.Length + 1; int num3 = text.IndexOf(';', num2); string text2 = ((num3 < 0) ? text.Substring(num2) : text.Substring(num2, num3 - num2)).Trim(); if (text2.Length > 0) { return text2; } } } return null; } private async Task<byte[]> GetBinaryAsync(string url, CancellationToken ct) { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, url); try { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Referer", "https://y.qq.com/portal/player.html"); HttpResponseMessage resp = await ((HttpMessageInvoker)_http).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } private async Task<string> PostJsonAsync(string url, string body, CancellationToken ct) { StringContent content = new StringContent(body, Encoding.UTF8, "application/json"); try { HttpRequestMessage val = new HttpRequestMessage(HttpMethod.Post, url); val.Content = (HttpContent)content; HttpRequestMessage req = val; try { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Referer", "https://y.qq.com/"); HttpResponseMessage resp = await ((HttpMessageInvoker)_http).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } finally { ((IDisposable)content)?.Dispose(); } } private async Task<string> PostJsonWithCookieAsync(string url, string body, string cookieHeader, CancellationToken ct) { StringContent content = new StringContent(body, Encoding.UTF8, "application/json"); try { HttpRequestMessage val = new HttpRequestMessage(HttpMethod.Post, url); val.Content = (HttpContent)content; HttpRequestMessage req = val; try { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Referer", "https://y.qq.com/"); if (!string.IsNullOrEmpty(cookieHeader)) { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Cookie", cookieHeader); } HttpResponseMessage resp = await ((HttpMessageInvoker)_cookielessHttp).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } finally { ((IDisposable)content)?.Dispose(); } } private Dictionary<string, string> FlattenCookies() { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); Uri[] array = new Uri[7] { new Uri("https://ssl.ptlogin2.graph.qq.com/"), new Uri("https://graph.qq.com/"), new Uri("https://ssl.ptlogin2.qq.com/"), new Uri("https://ptlogin2.qq.com/"), new Uri("https://xui.ptlogin2.qq.com/"), new Uri("https://y.qq.com/"), new Uri("https://qq.com/") }; foreach (Uri uri in array) { try { foreach (Cookie cookie in _cookies.GetCookies(uri)) { if (!string.IsNullOrEmpty(cookie.Name) && !string.IsNullOrEmpty(cookie.Value) && !dictionary.ContainsKey(cookie.Name)) { dictionary[cookie.Name] = cookie.Value; } } } catch { } } return dictionary; } private string BuildCookieHeader() { StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, string> item in FlattenCookies()) { if (stringBuilder.Length > 0) { stringBuilder.Append("; "); } stringBuilder.Append(item.Key).Append('=').Append(item.Value); } return stringBuilder.ToString(); } private async Task<RedirectResult> PostFormRedirectAsync(string url, Dictionary<string, string> form, string referer, CancellationToken ct) { HttpClientHandler val = new HttpClientHandler(); val.UseCookies = false; val.AllowAutoRedirect = false; HttpClientHandler handler = val; try { HttpClient val2 = new HttpClient((HttpMessageHandler)handler); val2.Timeout = TimeSpan.FromSeconds(20.0); HttpClient client = val2; try { ((HttpHeaders)client.DefaultRequestHeaders).TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); ((HttpHeaders)client.DefaultRequestHeaders).TryAddWithoutValidation("Referer", referer); FormUrlEncodedContent content = new FormUrlEncodedContent((IEnumerable<KeyValuePair<string, string>>)form); try { HttpRequestMessage val3 = new HttpRequestMessage(HttpMethod.Post, url); val3.Content = (HttpContent)content; HttpRequestMessage req = val3; try { string text = BuildCookieHeader(); if (text.Length > 0) { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Cookie", text); } HttpResponseMessage resp = await ((HttpMessageInvoker)client).SendAsync(req, ct).ConfigureAwait(continueOnCapturedContext: false); try { RedirectResult r = new RedirectResult(); Uri location = resp.Headers.Location; if (location != null) { r.Location = (location.IsAbsoluteUri ? location.ToString() : new Uri(new Uri(url), location).ToString()); } RedirectResult redirectResult = r; redirectResult.Body = await resp.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); return r; } finally { ((IDisposable)resp)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } finally { ((IDisposable)content)?.Dispose(); } } finally { ((IDisposable)client)?.Dispose(); } } finally { ((IDisposable)handler)?.Dispose(); } } private static long StableId(string mid) { ulong num = 1469598103934665603uL; for (int i = 0; i < mid.Length; i++) { num ^= mid[i]; num *= 1099511628211L; } return (long)(num & 0x7FFFFFFFFFFFFFFFL); } public void Dispose() { try { ((HttpMessageInvoker)_http).Dispose(); } catch { } } private string DesktopComm() { string text = LookupKey(); int num = Hash33(text); return "{\"ct\":19,\"cv\":2201,\"uin\":\"" + Uin + "\",\"authst\":\"" + text + "\",\"g_tk\":" + num + ",\"tmeLoginType\":" + _loginType + "}"; } private string LookupKey() { string text = LookupCookie("qqmusic_key"); if (string.IsNullOrEmpty(text)) { text = LookupCookie("qm_keyst"); } if (string.IsNullOrEmpty(text)) { text = LookupCookie("musickey"); } return text ?? ""; } private async Task<Dictionary<string, object>> CgiAsync(string module, string method, string paramJson, CancellationToken ct) { string body = "{\"comm\":" + DesktopComm() + ",\"req_0\":{\"module\":\"" + module + "\",\"method\":\"" + method + "\",\"param\":" + paramJson + "}}"; Dictionary<string, object> dictionary = MiniJson.Dict(MiniJson.AsObject(MiniJson.Parse(await PostJsonWithCookieAsync("https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&platform=yqq.json", body, CookieText, ct).ConfigureAwait(continueOnCapturedContext: false))), "req_0"); if (dictionary == null) { return null; } long num = MiniJson.Lng(dictionary, "code", 0L); if (num != 0L) { throw new InvalidOperationException("QQ " + module + " 返回 " + num); } return MiniJson.Dict(dictionary, "data"); } public async Task<string> GetEncryptUinAsync(CancellationToken ct = default(CancellationToken)) { if (!string.IsNullOrEmpty(_encryptUin)) { return _encryptUin; } try { Dictionary<string, object> dictionary = await CgiAsync("music.UserInfo.userInfoServer", "GetLoginUserInfo", "{}", ct).ConfigureAwait(continueOnCapturedContext: false); Dictionary<string, object>[] array = new Dictionary<string, object>[3] { MiniJson.Dict(dictionary, "info"), MiniJson.Dict(MiniJson.Dict(dictionary, "info"), "base"), dictionary }; foreach (Dictionary<string, object> dictionary2 in array) { if (dictionary2 != null) { string text = MiniJson.Str(dictionary2, "encrypt_uin", ""); if (string.IsNullOrEmpty(text)) { text = MiniJson.Str(dictionary2, "encryptUin", ""); } if (!string.IsNullOrEmpty(text)) { _encryptUin = text; return text; } } } } catch { } return ""; } public async Task<List<PlaylistInfo>> GetPlaylistsAsync(CancellationToken ct = default(CancellationToken)) { List<PlaylistInfo> list = new List<PlaylistInfo>(); if (!HasLogin) { return list; } list.Add(new PlaylistInfo { Id = "201", Name = "我喜欢的音乐", IsFavorites = true, DirId = 201, Platform = Platform.QQ, Creator = "QQ音乐" }); try { List<object> list2 = MiniJson.List(await CgiAsync("music.musicasset.PlaylistBaseRead", "GetPlaylistByUin", "{\"uin\":\"" + Uin + "\"}", ct).ConfigureAwait(continueOnCapturedContext: false), "v_playlist"); if (list2 != null
plugins/PeakMusic/PeakMusic.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using Microsoft.CodeAnalysis; using PeakMusic.Core; using PeakMusic.Core.Lyrics; using PeakMusic.Core.Netease; using PeakMusic.Core.Qr; using PeakMusic.Core.Sources; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("PeakMusic")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("PeakMusic")] [assembly: AssemblyTitle("PeakMusic")] [assembly: AssemblyVersion("0.1.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 PeakMusic { internal sealed class AudioProbe : MonoBehaviour { private const string Tag = "[探针]"; private AudioSource _src; private AudioClip _clip; private UnityWebRequest _activeRequest; private readonly List<string> _urls = new List<string>(); private bool _paused; private bool _busy; private bool _showGui = true; private bool _inputSystemOk = true; private bool _legacyInputOk = true; private bool _inputBackendLogged; private string _status = "空闲 · F7 开始播放"; private long _lastMs = -1L; private GUIStyle _style; private bool _styleReady; private int _playCount; private static ManualLogSource Log => Plugin.Log; private void LogInputBackendOnce(string backend) { if (!_inputBackendLogged) { _inputBackendLogged = true; Log.LogInfo((object)("[探针] 热键实际走的是: " + backend + " —— 这个结论要写进 notes.md")); } } private void Start() { _src = ((Component)this).gameObject.AddComponent<AudioSource>(); _src.playOnAwake = false; _src.loop = true; _src.spatialBlend = 0f; _src.volume = Plugin.Volume.Value; _src.bypassReverbZones = true; _urls.Clear(); _urls.Add(Plugin.UrlA.Value); _urls.Add(Plugin.UrlB.Value); Log.LogInfo((object)("[探针] AudioSource 就绪。spatialBlend=0 loop=true volume=" + _src.volume)); Log.LogInfo((object)("[探针] 当前 AudioClip 计数(含游戏自带)= " + CountClips())); } private void Update() { if (KeyPressed((KeyCode)288, (Key)100, "F7")) { if (!_busy) { ((MonoBehaviour)this).StartCoroutine(PlayCoroutine(Plugin.UrlA.Value, "A")); } } else if (KeyPressed((KeyCode)289, (Key)101, "F8")) { if (!_busy) { ((MonoBehaviour)this).StartCoroutine(SeekCoroutine()); } } else if (KeyPressed((KeyCode)290, (Key)102, "F9")) { TogglePause(); } else if (KeyPressed((KeyCode)291, (Key)103, "F10")) { if (!_busy) { ((MonoBehaviour)this).StartCoroutine(StressCoroutine(20)); } } else if (KeyPressed((KeyCode)292, (Key)104, "F11")) { if (!_busy) { ((MonoBehaviour)this).StartCoroutine(StopCoroutineSafe()); } } else if (KeyPressed((KeyCode)287, (Key)99, "F6")) { _showGui = !_showGui; } else if (KeyPressed((KeyCode)286, (Key)98, "F5") && !_busy) { ((MonoBehaviour)this).StartCoroutine(SlowLinkCoroutine()); } } private bool KeyPressed(KeyCode legacy, Key modern, string name) { //IL_0012: 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) if (_inputSystemOk) { try { Keyboard current = Keyboard.current; if (current != null) { KeyControl val = current[modern]; int num; if (val != null) { num = (((ButtonControl)val).wasPressedThisFrame ? 1 : 0); if (num != 0) { LogInputBackendOnce("Input System"); } } else { num = 0; } return (byte)num != 0; } } catch (Exception ex) { _inputSystemOk = false; Log.LogWarning((object)("[探针] 读 Input System 失败,回退旧版 Input:" + ex.GetType().Name + " " + ex.Message)); } } if (!_legacyInputOk) { return false; } try { bool keyDown = Input.GetKeyDown(legacy); if (keyDown) { LogInputBackendOnce("旧版 Input(InputSystem 拿不到键盘)"); } return keyDown; } catch (Exception ex2) { _legacyInputOk = false; Log.LogError((object)("[探针] 旧版 Input 也不可用(Active Input Handling 可能只开了新系统)," + name + " 热键失效,需要用 InputSystem 的 action 方案:" + ex2.GetType().Name)); return false; } } private static void ApplyTimeout(UnityWebRequest req) { int value = Plugin.TimeoutSeconds.Value; req.timeout = ((value > 0) ? value : 0); } private IEnumerator SendWithProgress(UnityWebRequest req, string tag) { UnityWebRequestAsyncOperation op = req.SendWebRequest(); int lastLogged = -10; while (!((AsyncOperation)op).isDone) { float downloadProgress = req.downloadProgress; int num = ((downloadProgress < 0f) ? (-1) : Mathf.Clamp(Mathf.RoundToInt(downloadProgress * 100f), 0, 100)); long num2 = (long)(req.downloadedBytes / 1024); _status = ((num < 0) ? ("缓冲中 " + num2 + " KB") : ("缓冲 " + num + "% " + num2 + " KB")); if (num >= 0 && num - lastLogged >= 10) { lastLogged = num; Log.LogInfo((object)("[探针] 缓冲 " + num + "% " + num2 + "KB " + tag)); } yield return null; } } private IEnumerator PlayCoroutine(string url, string tag) { _busy = true; _status = "准备播放 " + tag; MusicPlayer active = MusicPlayer.Active; if ((Object)(object)active != (Object)null && active.HasClip) { active.Stop(); } yield return StopCoroutineSafeCore(); Stopwatch sw = Stopwatch.StartNew(); UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(url, (AudioType)13); DownloadHandler downloadHandler = req.downloadHandler; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val != null) { val.streamAudio = true; } ApplyTimeout(req); yield return SendWithProgress(req, "[" + tag + "]"); long elapsedMilliseconds = sw.ElapsedMilliseconds; if ((int)req.result != 1) { Log.LogError((object)("[探针] 起播失败 [" + tag + "] code=" + req.responseCode + " err=" + req.error + " 耗时=" + elapsedMilliseconds + "ms")); _status = "起播失败:" + req.error; req.Dispose(); _busy = false; yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(req); if ((Object)(object)content == (Object)null) { Log.LogError((object)"[探针] GetContent 返回 null,但请求成功了。audioType 或编码可能不匹配。"); _status = "GetContent 为 null"; req.Dispose(); _busy = false; yield break; } _clip = content; _activeRequest = req; _src.clip = content; _src.volume = Plugin.Volume.Value; _src.Play(); sw.Stop(); _lastMs = sw.ElapsedMilliseconds; _playCount++; Log.LogInfo((object)("[探针] 起播成功 [" + tag + "] 网络=" + elapsedMilliseconds + "ms 出声=" + sw.ElapsedMilliseconds + "ms len=" + content.length.ToString("F2") + "s freq=" + content.frequency + " ch=" + content.channels + " loadState=" + ((object)content.loadState/*cast due to .constrained prefix*/).ToString() + " isPlaying=" + _src.isPlaying + " clips=" + CountClips())); if (content.length <= 0.01f) { Log.LogWarning((object)"[探针] clip.length≈0 —— 流式模式下常见,seek 会因此不确定,记一笔。"); } _status = "播放中 [" + tag + "] 起播 " + sw.ElapsedMilliseconds + "ms"; _busy = false; } private void TogglePause() { if ((Object)(object)_src == (Object)null || (Object)(object)_clip == (Object)null) { Log.LogWarning((object)"[探针] 还没播任何东西。"); return; } if (_src.isPlaying) { _src.Pause(); _paused = true; Log.LogInfo((object)("[探针] 已暂停,time=" + _src.time.ToString("F2"))); } else { _src.UnPause(); _paused = false; Log.LogInfo((object)("[探针] 已恢复,time=" + _src.time.ToString("F2"))); } _status = (_paused ? "已暂停" : "播放中"); } private IEnumerator SeekCoroutine() { if ((Object)(object)_src == (Object)null || (Object)(object)_clip == (Object)null || !_src.isPlaying) { Log.LogWarning((object)"[探针] seek 需要先有正在播放的曲子(按 F7)。"); yield break; } _busy = true; float target = Plugin.SeekSeconds.Value; float length = _clip.length; if (length > 5f && target > length - 5f) { target = length - 5f; } _src.time = target; float at0 = _src.time; yield return (object)new WaitForSecondsRealtime(0.5f); float at500 = _src.time; yield return (object)new WaitForSecondsRealtime(0.5f); float time = _src.time; bool num = at500 >= target - 0.5f; bool flag = time > at500; bool flag2 = num && flag; Log.LogInfo((object)("[探针] seek 测试 目标=" + target.ToString("F1") + "s 立即=" + at0.ToString("F2") + "s +500ms=" + at500.ToString("F2") + "s +1000ms=" + time.ToString("F2") + "s => " + (flag2 ? "PASS" : "FAIL"))); if (!flag2) { Log.LogWarning((object)"[探针] seek 没落到位。streamAudio=true 时只能跳到已缓冲区间——把 AudioProbe 里 streamAudio 改成 false 再测一次(代价是等整曲下完才播)。"); } _status = (flag2 ? ("seek PASS (" + at500.ToString("F1") + "s)") : "seek FAIL"); _busy = false; } private IEnumerator SlowLinkCoroutine() { _busy = true; _status = "慢链验证中"; yield return ((MonoBehaviour)this).StartCoroutine(PlayCoroutine(Plugin.UrlSlow.Value, "慢链")); if ((Object)(object)_clip == (Object)null) { _status = "慢链起播失败"; _busy = false; yield break; } float length = _clip.length; float far = ((length > 30f) ? (length - 15f) : (length * 0.8f)); float time = _src.time; Log.LogInfo((object)("[探针] 慢链:len=" + length.ToString("F1") + "s 起播后 time=" + time.ToString("F2") + "s isPlaying=" + _src.isPlaying + "(512KB/s 下整曲约需 7.2s)")); yield return (object)new WaitForSecondsRealtime(3f); float time2 = _src.time; Log.LogInfo((object)("[探针] 慢链 3s 后 time=" + time2.ToString("F2") + "s;按 128kbps 估算,3s 内网络侧只下到约 1.5MB≈96s 音频,所以 seek 到 " + far.ToString("F0") + "s 已经超出已缓冲区间")); _src.time = far; float j0 = _src.time; yield return (object)new WaitForSecondsRealtime(0.5f); float j500 = _src.time; yield return (object)new WaitForSecondsRealtime(0.5f); float time3 = _src.time; bool flag = j500 >= far - 2f; bool flag2 = time3 > j500; Log.LogInfo((object)("[探针] 慢链远距 seek 目标=" + far.ToString("F0") + "s 立即=" + j0.ToString("F2") + "s +500ms=" + j500.ToString("F2") + "s +1000ms=" + time3.ToString("F2") + "s => " + ((flag && flag2) ? "PASS" : "FAIL"))); if (!flag) { Log.LogWarning((object)"[探针] 流式下远距 seek 不落位 —— 阶段 3 的「从第 N 秒开始播」不能只靠 AudioSource.time,必须用 Range 头重新请求(CDN 支持 206 才行)或者改整曲下载后 seek。这条要写进 notes.md。"); } _status = "慢链 seek " + ((flag && flag2) ? "PASS" : "FAIL"); _busy = false; } private IEnumerator StressCoroutine(int times) { _busy = true; int before = CountClips(); Log.LogInfo((object)("[探针] 换歌压测开始:" + times + " 次,每首整曲下载,起始 clips=" + before)); for (int i = 0; i < times; i++) { string url = _urls[i % _urls.Count]; yield return StopCoroutineSafeCore(); UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(url, (AudioType)13); DownloadHandler downloadHandler = req.downloadHandler; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val != null) { val.streamAudio = true; } ApplyTimeout(req); yield return SendWithProgress(req, "[压测 " + (i + 1) + "]"); if ((int)req.result != 1) { Log.LogError((object)("[探针] 压测第 " + (i + 1) + " 次请求失败:" + req.error)); req.Dispose(); _status = "压测中断于第 " + (i + 1) + " 次"; _busy = false; yield break; } _clip = DownloadHandlerAudioClip.GetContent(req); _activeRequest = req; _src.clip = _clip; _src.Play(); _status = "压测 " + (i + 1) + "/" + times; Log.LogInfo((object)("[探针] 压测 " + (i + 1) + "/" + times + " ok clips=" + CountClips())); yield return (object)new WaitForSecondsRealtime(0.4f); } yield return Resources.UnloadUnusedAssets(); int num = CountClips(); Log.LogInfo((object)("[探针] 换歌压测结束:clips " + before + " → " + num + ((num - before <= 3) ? " (正常)" : " (在涨,清理逻辑要查)"))); _status = "压测完成 clips=" + num; _busy = false; } private IEnumerator StopCoroutineSafe() { _busy = true; yield return StopCoroutineSafeCore(); _status = "已停止并清理"; _busy = false; } private IEnumerator StopCoroutineSafeCore() { if ((Object)(object)_src != (Object)null) { _src.Stop(); _src.clip = null; } if (_activeRequest != null) { try { _activeRequest.Abort(); } catch { } try { _activeRequest.Dispose(); } catch { } _activeRequest = null; } if ((Object)(object)_clip != (Object)null) { Object.Destroy((Object)(object)_clip); _clip = null; } _paused = false; yield return null; yield return Resources.UnloadUnusedAssets(); } private static int CountClips() { try { AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>(); return (array == null) ? (-1) : array.Length; } catch { return -1; } } private void OnGUI() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0211: Unknown result type (might be due to invalid IL or missing references) if (!_showGui) { return; } if (!_styleReady) { _styleReady = true; try { Font val = Font.CreateDynamicFontFromOSFont("Microsoft YaHei", 14); _style = new GUIStyle(GUI.skin.label) { fontSize = 14, richText = false }; if ((Object)(object)val != (Object)null) { _style.font = val; } } catch (Exception ex) { Log.LogWarning((object)("[探针] 加载中文字体失败,界面可能显示方块:" + ex.Message)); } } float num = 430f; float num2 = 108f; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(12f, 12f, num, num2); GUI.Box(val2, GUIContent.none); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 6f, num - 20f, 20f), "PeakMusic 阶段1 探针 " + _status, _style); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 30f, num - 20f, 20f), "F7 播放 F8 seek F9 暂停 F10 压测x20 F11 停止 F5 慢链", _style); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 54f, num - 20f, 20f), ((_lastMs >= 0) ? ("上次起播 " + _lastMs + "ms ") : "") + "clips=" + CountClips() + " vol=" + (((Object)(object)_src != (Object)null) ? _src.volume.ToString("F2") : "-"), _style); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 78f, num - 20f, 20f), "playCount=" + _playCount + (_busy ? " [忙]" : ""), _style); } private void OnDestroy() { try { if ((Object)(object)_src != (Object)null) { _src.Stop(); _src.clip = null; } if (_activeRequest != null) { _activeRequest.Abort(); _activeRequest.Dispose(); _activeRequest = null; } if ((Object)(object)_clip != (Object)null) { Object.Destroy((Object)(object)_clip); } Log.LogInfo((object)"[探针] AudioProbe 已销毁,资源已释放。"); } catch (Exception ex) { Log.LogWarning((object)("[探针] 销毁清理时异常:" + ex.Message)); } } } internal sealed class CoverCache : MonoBehaviour { private const int DstSize = 64; private const int DstRadius = 9; private const int MaxEntries = 64; private const int MaxConcurrent = 3; private readonly Dictionary<string, Texture2D> _done = new Dictionary<string, Texture2D>(); private readonly List<string> _lru = new List<string>(); private readonly HashSet<string> _inflight = new HashSet<string>(); private readonly HashSet<string> _failed = new HashSet<string>(); private readonly List<string> _queue = new List<string>(); private int _active; public Texture2D Get(string rawUrl) { string text = Normalize(rawUrl); if (text == null) { return null; } if (_done.TryGetValue(text, out var value)) { Touch(text); return value; } if (_inflight.Contains(text)) { return null; } if (_failed.Contains(text)) { return null; } _inflight.Add(text); _queue.Add(text); Pump(); return null; } private void Pump() { while (_active < 3 && _queue.Count > 0) { string url = _queue[0]; _queue.RemoveAt(0); _active++; ((MonoBehaviour)this).StartCoroutine(Load(url)); } } private static string Normalize(string raw) { if (string.IsNullOrEmpty(raw)) { return null; } string text = raw; int num = text.IndexOf('?'); if (num > 0) { text = text.Substring(0, num); } text = NeteaseClient.ForceHttps(text); return text + "?param=96y96"; } private IEnumerator Load(string url) { UnityWebRequest req = null; UnityWebRequestAsyncOperation op; try { req = UnityWebRequestTexture.GetTexture(url, false); req.timeout = 12; op = req.SendWebRequest(); } catch { op = null; } if (op != null) { yield return op; } Texture2D val = null; try { if (op != null && (int)req.result == 1) { val = DownloadHandlerTexture.GetContent(req); } } catch { } Texture2D val2 = (((Object)(object)val != (Object)null) ? Bake(val) : null); if ((Object)(object)val2 != (Object)null) { Store(url, val2); } else if (_failed.Count < 512) { _failed.Add(url); } if ((Object)(object)val != (Object)null) { try { Object.Destroy((Object)(object)val); } catch { } } if (req != null) { try { req.Dispose(); } catch { } } _inflight.Remove(url); _active--; Pump(); } private Texture2D Bake(Texture2D src) { //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) Texture2D val = null; try { Color32[] pixels = src.GetPixels32(); int width = ((Texture)src).width; int height = ((Texture)src).height; if (pixels == null || width <= 0 || height <= 0) { return null; } Color32[] array = (Color32[])(object)new Color32[4096]; float num = 32f; for (int i = 0; i < 64; i++) { int num2 = Mathf.Clamp(i * height / 64, 0, height - 1); for (int j = 0; j < 64; j++) { int num3 = Mathf.Clamp(j * width / 64, 0, width - 1); Color32 val2 = pixels[num2 * width + num3]; float num4 = Mathf.Abs((float)j + 0.5f - num) - (num - 9f); float num5 = Mathf.Abs((float)i + 0.5f - num) - (num - 9f); float num6 = Mathf.Max(num4, 0f); float num7 = Mathf.Max(num5, 0f); float num8 = Mathf.Sqrt(num6 * num6 + num7 * num7) + Mathf.Min(Mathf.Max(num4, num5), 0f) - 9f; float num9 = Mathf.Clamp01(0.5f - num8); val2.a = (byte)((float)(int)val2.a * num9); array[i * 64 + j] = val2; } } val = new Texture2D(64, 64, (TextureFormat)4, false); ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; ((Object)val).hideFlags = (HideFlags)52; val.SetPixels32(array); val.Apply(false, false); return val; } catch { if ((Object)(object)val != (Object)null) { try { Object.Destroy((Object)(object)val); } catch { } } return null; } } private void Store(string url, Texture2D tex) { _done[url] = tex; _lru.Add(url); while (_lru.Count > 64) { string key = _lru[0]; _lru.RemoveAt(0); if (!_done.TryGetValue(key, out var value)) { continue; } _done.Remove(key); if ((Object)(object)value != (Object)null) { try { Object.Destroy((Object)(object)value); } catch { } } } } private void Touch(string url) { int num = _lru.IndexOf(url); if (num >= 0 && num != _lru.Count - 1) { _lru.RemoveAt(num); _lru.Add(url); } } private void OnDestroy() { foreach (KeyValuePair<string, Texture2D> item in _done) { if ((Object)(object)item.Value != (Object)null) { try { Object.Destroy((Object)(object)item.Value); } catch { } } } _done.Clear(); _lru.Clear(); _queue.Clear(); _inflight.Clear(); } } internal static class LyricColors { internal struct Preset { internal readonly string Name; internal readonly string Lit; internal readonly string Rest; internal readonly string Sub; internal Preset(string name, string lit, string rest, string sub) { Name = name; Lit = lit; Rest = rest; Sub = sub; } } internal static readonly Preset[] Presets = new Preset[10] { new Preset("纯白", "FFFFFF", "FFFFFF", "FFFFFF"), new Preset("暖米", "FFF6E3", "FFF6E3", "E6D9C2"), new Preset("霓虹青", "6FF7E4", "6FF7E4", "BCEDE6"), new Preset("樱花粉", "FFB3D1", "FFB3D1", "F2DAE4"), new Preset("烈焰红", "FF6B6B", "FF6B6B", "FFC9C9"), new Preset("薄荷绿", "A6F07A", "A6F07A", "DCF3CB"), new Preset("落日橙", "FFC078", "FFC078", "FFE5C4"), new Preset("冰川蓝", "A3CBFF", "A3CBFF", "DAEAFF"), new Preset("暗夜紫", "C9A6FF", "C9A6FF", "E5D8FF"), new Preset("暖白", "F7E1D7", "F7E1D7", "FFEEDF") }; private static string _kLit; private static string _kRest; private static string _kSub; private static Color _lit = Color.white; private static Color _rest = Color.white; private static Color _sub = Color.white; internal static Color Lit { get { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Sync(); return _lit; } } internal static Color Rest { get { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Sync(); return _rest; } } internal static Color Sub { get { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Sync(); return _sub; } } internal static int MatchPreset() { string text = Norm(Get(Plugin.LyricColorLit, "FFFFFF")); string text2 = Norm(Get(Plugin.LyricColorRest, "FFFFFF")); string text3 = Norm(Get(Plugin.LyricColorSub, "FFFFFF")); for (int i = 0; i < Presets.Length; i++) { if (Norm(Presets[i].Lit) == text && Norm(Presets[i].Rest) == text2 && Norm(Presets[i].Sub) == text3) { return i; } } return -1; } internal static string CurrentName() { int num = MatchPreset(); if (num < 0) { return "自定义"; } return Presets[num].Name; } internal static int Cycle() { int num = MatchPreset(); if (num < 0 && Plugin.LyricColorPreset != null) { num = Plugin.LyricColorPreset.Value; } if (num < 0 || num >= Presets.Length) { num = -1; } int num2 = (num + 1) % Presets.Length; Preset preset = Presets[num2]; if (Plugin.LyricColorLit != null) { Plugin.LyricColorLit.Value = preset.Lit; } if (Plugin.LyricColorRest != null) { Plugin.LyricColorRest.Value = preset.Rest; } if (Plugin.LyricColorSub != null) { Plugin.LyricColorSub.Value = preset.Sub; } if (Plugin.LyricColorPreset != null) { Plugin.LyricColorPreset.Value = num2; } Sync(); return num2; } private static string Get(ConfigEntry<string> e, string fallback) { if (e == null) { return fallback; } string value = e.Value; if (!string.IsNullOrEmpty(value)) { return value; } return fallback; } private static void Sync() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) string text = Norm(Get(Plugin.LyricColorLit, "FFFFFF")); string text2 = Norm(Get(Plugin.LyricColorRest, "FFFFFF")); string text3 = Norm(Get(Plugin.LyricColorSub, "FFFFFF")); if (text != _kLit) { _kLit = text; _lit = Parse(text, Color.white); } if (text2 != _kRest) { _kRest = text2; _rest = Parse(text2, Color.white); } if (text3 != _kSub) { _kSub = text3; _sub = Parse(text3, Color.white); } } private static string Norm(string hex) { if (string.IsNullOrEmpty(hex)) { return "FFFFFF"; } string text = hex.Trim(); if (text.StartsWith("#")) { text = text.Substring(1); } return text.ToUpperInvariant(); } private static Color Parse(string hex, Color fallback) { //IL_0031: 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_0019: Unknown result type (might be due to invalid IL or missing references) string text = Norm(hex); if (text.Length != 6 && text.Length != 8) { return fallback; } Color result = default(Color); if (!ColorUtility.TryParseHtmlString("#" + text, ref result)) { return fallback; } return result; } } public sealed class LyricOverlay : MonoBehaviour { private const string Tag = "[歌词]"; private MusicPlayer _player; private readonly List<LrcLine> _lines = new List<LrcLine>(); private readonly List<LrcLine> _trans = new List<LrcLine>(); private SongInfo _song; private string _lrcRaw = ""; private WorldLyricRenderer _world; private static readonly string[] MetaPrefixes = new string[29] { "作词", "作曲", "编曲", "制作人", "出品人", "监制", "混音", "母带", "录音", "和声", "吉他", "贝斯", "键盘", "弦乐", "鼓", "统筹", "策划", "出品", "发行", "词", "曲", "OP", "SP", "Lyrics", "Composed", "Arranged", "Produced", "Mixed", "Mastered" }; public static LyricOverlay Instance { get; private set; } private bool InWorldScene { get { Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return false; } if (((Behaviour)main).enabled) { return ((Component)main).gameObject.activeInHierarchy; } return false; } } private void Awake() { Instance = this; _world = new WorldLyricRenderer(); } private void OnDestroy() { if (_world != null) { _world.Dispose(); _world = null; } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } public void SetLyrics(SongInfo song, string lrc) { SetLyrics(song, lrc, null); } public void SetLyrics(SongInfo song, string lrc, string translated) { _song = song; _lrcRaw = lrc ?? ""; _lines.Clear(); _trans.Clear(); if (string.IsNullOrEmpty(_lrcRaw)) { if (Plugin.Log != null) { Plugin.Log.LogInfo((object)"[歌词] 这首没有歌词,只显示歌名。"); } return; } FillLines(_lines, _lrcRaw); FillLines(_trans, translated); if (_world != null) { _world.SetLyrics((song != null) ? song.Key : "", _lines); } if (Plugin.Log != null) { Plugin.Log.LogInfo((object)("[歌词] 歌词就绪:" + _lines.Count + " 行" + ((_trans.Count > 0) ? ("(含翻译 " + _trans.Count + " 行)") : "") + " · " + ((song != null) ? song.Name : "?") + " · 世界空间 3D(TMP SDF,逐字动画)")); } } public void Clear() { _song = null; _lines.Clear(); _trans.Clear(); _lrcRaw = ""; if (_world != null) { _world.SetLyrics("", new List<LrcLine>()); } } private static void FillLines(List<LrcLine> dst, string raw) { if (string.IsNullOrEmpty(raw)) { return; } LrcDocument val = LrcDocument.Parse(raw); if (val == null || val.Lines == null) { return; } foreach (LrcLine line in val.Lines) { if (line != null && !string.IsNullOrEmpty(line.Text) && line.Text.Trim().Length != 0 && (Plugin.LyricFilterMeta == null || !Plugin.LyricFilterMeta.Value || !IsMetaLine(line.Text))) { dst.Add(line); } } } private static bool IsMetaLine(string text) { if (string.IsNullOrEmpty(text)) { return false; } string text2 = text.Trim(); if (text2.Length >= 2 && text2[0] == '【' && text2[text2.Length - 1] == '】') { return true; } if (text2.IndexOf("正版授权", StringComparison.Ordinal) >= 0) { return true; } for (int i = 0; i < MetaPrefixes.Length; i++) { string text3 = MetaPrefixes[i]; if (text2.StartsWith(text3, StringComparison.Ordinal) && text2.Length > text3.Length) { char c = text2[text3.Length]; if (c == ':' || c == ':' || c == ' ' || c == '\u3000') { return true; } } } return false; } private void LateUpdate() { if ((Object)(object)_player == (Object)null) { _player = MusicPlayer.Active; } if ((Object)(object)_player == (Object)null) { return; } if (_player.Current != null && _song != null && _player.Current.Key != _song.Key) { Clear(); } if (_world != null) { if (!Plugin.LyricEnabled.Value || !_player.HasClip || _lines.Count == 0 || !InWorldScene) { _world.Hide(); return; } float time = _player.Time; _world.Tick(_player, Camera.main, time); } } } internal sealed class MusicPanel : MonoBehaviour { private const string Tag = "[面板]"; private const float PanelW = 832f; private const float PanelH = 696f; private const float GripH = 26f; private const float RowH = 40f; private const int PageSize = 8; private const int FetchLimit = 40; private const float CoverSize = 30f; private const float BigCover = 52f; private const float NavW = 96f; private const float ScaleMin = 0.85f; private const float ScaleMax = 1.8f; private static readonly string[] Presets = new string[4] { "雨爱", "下雨天", "江南", "晴天" }; private bool _open; private bool _typing; private string _keyword = ""; private string _status = "面板就绪"; private string _loginLine = ""; private const string LoginUrlPrefix = "https://music.163.com/login?codekey="; private bool _loginOpen; private bool _isQqLogin; private bool _qqAppScan = true; private bool _loginAutoTried; private int _loginState; private string _loginMsg = ""; private string _qrText = ""; private string _qrKey = ""; private byte[] _qrPng; private string _qrCookie = ""; private QrState _qrResult = (QrState)(-1); private QqLoginState _qqResult = (QqLoginState)(-1); private string _qrError = ""; private Texture2D _qrTex; private Task _qrTask; private int _qrTaskKind; private float _nextPollAt; private float _loginDoneAt; private readonly List<SongInfo> _songs = new List<SongInfo>(); private int _page; private int _pendingScroll; private string _kw = ""; private int _offSearch; private bool _hasMore; private const int MoreBatch = 40; private MusicPlayer _player; private MusicSources _client; private int _view; private readonly List<SongInfo> _favorites = new List<SongInfo>(); private readonly List<string> _history = new List<string>(); private bool _libDirty = true; private bool _wasPlaying; private float _playEndGrace; private Platform _platform; private Task _task; private int _taskKind; private List<SongInfo> _pendingSongs; private SongUrlResult _pendingUrl; private SongInfo _pendingSong; private string _pendingRequester; private string _taskError; private bool _busy; private bool _queueFallback; private string _queueUrlFallback = ""; private bool _soloPlay; private Keyboard _textKb; private Vector2 _mousePos; private bool _mouseValid; private bool _clicked; private bool _held; private Vector2 _lm; private Vector2 _panelPos; private float _scale = 1f; private float _dpi = 1f; private int _screenH = -1; private bool _collapsed; private bool _dragging; private Vector2 _dragGrab; private bool _resizing; private float _resizeBaseScale; private Vector2 _resizeGrabPos; private bool _cursorOwned; private CursorLockMode _prevLock; private bool _prevVisible; private Font _font; private GUIStyle _sPanel; private GUIStyle _sPanelThin; private GUIStyle _sShadow; private GUIStyle _sCard; private GUIStyle _sField; private GUIStyle _sFieldFocus; private GUIStyle _sBar; private GUIStyle _sBtn; private GUIStyle _sBtnHi; private GUIStyle _sBtnOff; private GUIStyle _sPrimary; private GUIStyle _sPrimaryHi; private GUIStyle _sChip; private GUIStyle _sChipHi; private GUIStyle _sRowHi; private GUIStyle _sRowPlay; private GUIStyle _sBadge; private GUIStyle _sCover; private GUIStyle _h1; private GUIStyle _sub; private GUIStyle _body; private GUIStyle _bodyHi; private GUIStyle _dim; private GUIStyle _placeholder; private GUIStyle _tiny; private GUIStyle _rowText; private GUIStyle _rowTextHi; private GUIStyle _artist; private GUIStyle _dur; private GUIStyle _statusOk; private GUIStyle _statusErr; private GUIStyle _hintCenter; private GUIStyle _stateRight; private GUIStyle _timeText; private GUIStyle _barDim; private GUIStyle _btnText; private GUIStyle _btnTextOff; private GUIStyle _btnTextPrimary; private GUIStyle _btnTextSmall; private GUIStyle _chipText; private GUIStyle _badgeOk; private GUIStyle _badgeBusy; private GUIStyle _sLight; private GUIStyle _loginStatus; private GUIStyle _loginStatusHint; private Texture2D _texDivider; private Texture2D _texAccent; private Texture2D _texProgBg; private Texture2D _texProgFill; private Texture2D _texCoverEmpty; private Texture2D _texDot; private Texture2D _loginMask; private readonly List<Texture2D> _texs = new List<Texture2D>(); private bool _styleReady; private bool _lyricHotkeyDead; private bool _lyricHotkeyCollides; private string _accentHex = "#FFFFFF"; private string _faintHex = "#808080"; private Palette _pal; private int _themeIdx; private CoverCache _covers; private Rect _rPanel; private Rect _rTitle; private Rect _rArtist; private Rect _rBadge; private Rect _rTheme; private Rect _rHeaderLine; private Rect _rSearch; private Rect _rSearchBtn; private Rect _rClear; private Rect _rStatus; private Rect _rCover; private Rect _rGrip; private Rect _rClose; private Rect _rCollapse; private Rect _rZoomIn; private Rect _rZoomOut; private Rect _rResize; private Rect _rNav; private Rect _rNavLine; private Rect _rLyricPos; private Rect _rLyricColor; private Rect _rLyricSize; private Rect _rLyricHeight; private Rect _rLyricEdge; private Rect _rLyricDist; private Rect _rLyricBright; private readonly Rect[] _rNavItem = (Rect[])(object)new Rect[Platforms.All.Length]; private Rect _rList; private Rect _rPrev; private Rect _rNext; private Rect _rPageInfo; private Rect _rBar; private Rect _rProgress; private Rect _rPause; private Rect _rStop; private Rect _rVolDown; private Rect _rVolUp; private Rect _rVolText; private Rect _rTime; private Rect _rHint; private readonly Rect[] _rPreset = (Rect[])(object)new Rect[Presets.Length]; private Rect _rFavNav; private Rect _rQueueNav; private readonly Rect[] _rFavBtn = (Rect[])(object)new Rect[8]; private Rect _rLoginCard; private Rect _rLoginQr; private Rect _rLoginRefresh; private Rect _rLoginClip; private Rect _rLoginClose; private Rect _rLoginLogout; private Rect _rLoginMode; private static readonly List<SongInfo> _emptySongs = new List<SongInfo>(); private Task _lyricTask; private SongInfo _lyricSong; private LyricResult _lyricResult; private static ManualLogSource Log => Plugin.Log; private MusicSources Client { get { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown if (_client == null) { _client = new MusicSources(); } return _client; } } private float TotalH { get { if (!_collapsed) { return 722f; } return 26f; } } private string PanelKeyName { get { string text = ((Plugin.PanelKey != null) ? Plugin.PanelKey.Value : ""); if (!string.IsNullOrEmpty(text)) { return text; } return "F1/F2"; } } private string PanelKeyCfg { get { string text = ((Plugin.PanelKey != null) ? Plugin.PanelKey.Value : ""); if (!string.IsNullOrWhiteSpace(text)) { return text; } return "F1,F2"; } } private bool IsLoggedIn { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)_platform != 1) { return Client.Netease.HasLogin; } return Client.QQ.HasLogin; } } private bool CtrlDown { get { try { Keyboard current = Keyboard.current; return current != null && current.ctrlKey.isPressed; } catch { return false; } } } private int PageCount => Mathf.Max(1, Mathf.CeilToInt((float)ActiveSongs.Count / 8f)); private int PageNo => _page / 8 + 1; private bool CanPrev => _page > 0; private bool CanNext => _page + 8 < ActiveSongs.Count; private int MaxPageStart { get { int count = ActiveSongs.Count; if (count <= 8) { return 0; } return (count - 1) / 8 * 8; } } private List<SongInfo> ActiveSongs { get { if (_view == 2) { MusicSync instance = MusicSync.Instance; if (!((Object)(object)instance != (Object)null)) { return _emptySongs; } return instance.Queue; } if (_view != 1) { return _songs; } return _favorites; } } private void Awake() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) _player = ((Component)this).gameObject.AddComponent<MusicPlayer>(); _covers = ((Component)this).gameObject.AddComponent<CoverCache>(); _themeIdx = Mathf.Clamp(Plugin.Theme.Value, 0, Palettes.Count - 1); _pal = Palettes.Get(_themeIdx); _platform = Platforms.FromInt(Plugin.SourcePlatform.Value); _screenH = Screen.height; bool flag = _screenH < 480; _dpi = Mathf.Clamp((float)_screenH / 1080f, 1f, 2.4f); _scale = (flag ? Mathf.Clamp(Plugin.PanelScale.Value, 0.85f, 1.8f) : Mathf.Clamp(Plugin.PanelScale.Value * _dpi, 0.85f * _dpi, 1.8f * _dpi)); Log.LogInfo((object)("[面板] 屏幕 " + Screen.width + "x" + _screenH + " · 分辨率系数 " + _dpi.ToString("F2") + " · 面板缩放 " + _scale.ToString("F2") + "(约 " + Mathf.RoundToInt(832f * _scale) + "x" + Mathf.RoundToInt(TotalH * _scale) + " 像素)")); _collapsed = Plugin.PanelCollapsed.Value; string text = NormalizeKeys((Plugin.LyricKey != null) ? Plugin.LyricKey.Value : ""); string text2 = NormalizeKeys(PanelKeyCfg); _lyricHotkeyDead = text.Length == 0 || text == text2; _lyricHotkeyCollides = text.Length > 0 && text == text2; RefreshLibrary(); } private void HandleRemotePlay(SongInfo song, string url, float startAt) { if (!((Object)(object)_player == (Object)null)) { Log.LogInfo((object)("[面板] 一起听:同步播放 " + song.Name + " - " + song.Artists)); _status = "一起听:" + song.Name; _player.Play(song, url, startAt); StartLyricFetch(song); } } private void HandleRemotePause(bool paused) { if ((Object)(object)_player == (Object)null || !_player.HasClip) { return; } if (paused) { if (_player.IsPlaying) { _player.TogglePause(); } } else if (!_player.IsPlaying) { _player.TogglePause(); } } private void HandleRemoteStop() { if (!((Object)(object)_player == (Object)null)) { _player.Stop(); } } private void HandleRemoteSeek(float seconds) { if (!((Object)(object)_player == (Object)null) && _player.HasClip) { Log.LogInfo((object)("[面板] 一起听:进度对齐 " + seconds.ToString("F1") + "s")); } } private void HandleQueueAdd(SongInfo song, string requester, string url) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) MusicSync instance = MusicSync.Instance; if ((Object)(object)instance == (Object)null) { return; } bool flag = false; for (int i = 0; i < instance.Queue.Count; i++) { SongInfo val = instance.Queue[i]; if (val != null && val.Platform == song.Platform && val.Key == song.Key) { flag = true; break; } } if (!flag) { instance.AddQueueItem(song, requester, url); _status = "点歌:" + song.Name + "(" + requester + ")已入队"; Log.LogInfo((object)("[面板] 点歌入队:" + song.Name + "(" + requester + ")队列 " + instance.Queue.Count + " 首" + (string.IsNullOrEmpty(url) ? "〔无直链〕" : "〔带点歌人直链〕"))); instance.BroadcastQueueState(); } } private void HandleQueueState(string serialized) { MusicSync instance = MusicSync.Instance; if (!((Object)(object)instance == (Object)null)) { instance.ApplyQueueSnapshot(serialized); Log.LogInfo((object)("[面板] 一起听:队列更新(" + instance.Queue.Count + " 首)")); } } private void Start() { Log.LogInfo((object)("[面板] 面板已就绪。热键 " + PanelKeyName + " 开关;标题栏可拖动,Ctrl+滚轮或右下角拖拽缩放。")); if (_lyricHotkeyCollides) { Log.LogInfo((object)("[面板] 歌词热键与面板热键相同(" + NormalizeKeys(PanelKeyCfg) + "),已忽略歌词热键 —— 歌词默认就是开着的,放歌自动出现;要临时关掉就点面板左侧「歌词」行,或把 [歌词] Hotkey 改成别的键。")); } MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null) { instance.OnRemotePlay += HandleRemotePlay; instance.OnRemotePause += HandleRemotePause; instance.OnRemoteStop += HandleRemoteStop; instance.OnRemoteSeek += HandleRemoteSeek; instance.OnQueueAdd += HandleQueueAdd; instance.OnQueueState += HandleQueueState; Log.LogInfo((object)"[面板] 一起听指令订阅完成"); } else { Log.LogWarning((object)"[面板] 一起听:MusicSync 不存在,同步接收不可用"); } } private void Update() { if (Screen.height >= 480 && Screen.height != _screenH) { float num = ((_dpi > 0.01f) ? (_scale / _dpi) : Plugin.PanelScale.Value); _screenH = Screen.height; _dpi = Mathf.Clamp((float)_screenH / 1080f, 1f, 2.4f); _scale = Mathf.Clamp(num * _dpi, 0.85f * _dpi, 1.8f * _dpi); Log.LogInfo((object)("[面板] 分辨率变化 → " + Screen.width + "x" + _screenH + " · 分辨率系数 " + _dpi.ToString("F2") + " · 面板缩放 " + _scale.ToString("F2"))); } if (PanelKeyDown()) { Toggle(); } if (!_lyricHotkeyDead && LyricKeyDown()) { Plugin.LyricEnabled.Value = !Plugin.LyricEnabled.Value; Log.LogInfo((object)("[面板] 3D 歌词 " + (Plugin.LyricEnabled.Value ? "开" : "关") + "(已存进 cfg)")); } PollLyric(); PollLogin(); AutoAdvance(); if (_open) { ComputeLayout(); UpdateMouse(); HandleMouse(); HandleKeyboard(); PollTask(); } } private void LateUpdate() { if (_open && _cursorOwned) { ApplyCursor(); } } private void Toggle() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) _open = !_open; if (_open) { PlaceWindow(); ComputeLayout(); OwnCursor(); RefreshLoginLine(); Log.LogInfo((object)("[面板] 打开面板。" + _loginLine)); if (string.IsNullOrEmpty(_keyword)) { _keyword = ""; } bool flag = (((int)_platform == 0) ? (!HasNeteaseLogin()) : (!HasQqLogin())); if (!_loginAutoTried && flag) { _loginAutoTried = true; StartLogin(); } } else { StopTyping(); CloseLogin(); _dragging = false; _resizing = false; SaveWindow(); ReleaseCursor(); Log.LogInfo((object)"[面板] 关闭面板。"); } } private void OwnCursor() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!_cursorOwned) { try { _prevLock = Cursor.lockState; _prevVisible = Cursor.visible; } catch { } _cursorOwned = true; } ApplyCursor(); } private void ApplyCursor() { try { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } catch { } } private void ReleaseCursor() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (!_cursorOwned) { return; } _cursorOwned = false; try { Cursor.lockState = _prevLock; Cursor.visible = _prevVisible; } catch { } } private bool PanelKeyDown() { return ConfigKeyDown(PanelKeyCfg); } private bool LyricKeyDown() { string text = ((Plugin.LyricKey != null) ? Plugin.LyricKey.Value : "F4"); if (string.IsNullOrWhiteSpace(text)) { return false; } return ConfigKeyDown(text); } private bool ConfigKeyDown(string cfg) { //IL_0044: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(cfg)) { return false; } bool result = false; string[] array = cfg.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } if (Enum.TryParse<Key>(text, ignoreCase: true, out Key result2) && (int)result2 != 0) { try { Keyboard current = Keyboard.current; if (current != null) { KeyControl val = current[result2]; if (val != null && ((ButtonControl)val).wasPressedThisFrame) { result = true; } } } catch { } } else { if (!Enum.TryParse<KeyCode>(text, ignoreCase: true, out KeyCode result3) || (int)result3 == 0) { continue; } try { if (Input.GetKeyDown(result3)) { result = true; } } catch { } } } return result; } private static string NormalizeKeys(string cfg) { if (string.IsNullOrWhiteSpace(cfg)) { return ""; } string[] array = cfg.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); List<string> list = new List<string>(); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim().ToUpperInvariant(); if (text.Length > 0) { list.Add(text); } } list.Sort(StringComparer.Ordinal); return string.Join(",", list.ToArray()); } private void PlaceWindow() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) float value = Plugin.PanelX.Value; float value2 = Plugin.PanelY.Value; if (value < 0f || value2 < 0f) { CenterWindow(); } else { _panelPos = new Vector2(value, value2); } ClampWindow(); } private void CenterWindow() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) _panelPos = new Vector2(Mathf.Max(12f, ((float)Screen.width - 832f * _scale) * 0.5f), Mathf.Max(12f, ((float)Screen.height - TotalH * _scale) * 0.5f)); } private void ClampWindow() { float num = 832f * _scale; float num2 = TotalH * _scale; _panelPos.x = Mathf.Clamp(_panelPos.x, 80f - num, (float)Screen.width - 80f); _panelPos.y = Mathf.Clamp(_panelPos.y, 0f, Mathf.Max(0f, (float)Screen.height - Mathf.Min(num2, 40f))); } private void SaveWindow() { if (Screen.height < 480) { return; } try { Plugin.PanelX.Value = _panelPos.x; Plugin.PanelY.Value = _panelPos.y; Plugin.PanelScale.Value = Mathf.Clamp(_scale / ((_dpi < 0.01f) ? 1f : _dpi), 0.85f, 1.8f); Plugin.PanelCollapsed.Value = _collapsed; ((BaseUnityPlugin)Plugin.Instance).Config.Save(); } catch (Exception ex) { Log.LogWarning((object)("[面板] 保存窗口位置失败:" + ex.Message)); } } private void ZoomBy(float delta) { float scale = _scale; _scale = Mathf.Clamp(_scale + delta * _dpi, 0.85f * _dpi, 1.8f * _dpi); if (!(Mathf.Abs(scale - _scale) < 0.0005f)) { ClampWindow(); SaveWindow(); } } private bool HasNeteaseLogin() { try { return Client.Netease.HasLogin; } catch { return false; } } private bool HasQqLogin() { try { return Client.QQ.HasLogin; } catch { return false; } } private void RefreshLoginLine() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) try { _loginLine = Client.LoginLine(_platform); } catch (Exception ex) { _loginLine = "登录态读取失败:" + ex.Message; } } private void LogoutCurrent() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)_platform == 1) { Client.QQ.Logout(); Log.LogInfo((object)"[面板] 已退出 QQ 音乐登录"); } else { Client.Netease.Logout(); Log.LogInfo((object)"[面板] 已退出网易云登录"); } RefreshLoginLine(); _loginMsg = "已退出登录"; } private void StartLogin() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 _loginOpen = true; _loginState = 1; _isQqLogin = (int)_platform == 1; _loginMsg = (_isQqLogin ? "正在向 QQ 音乐要二维码…" : "正在向网易云要二维码…"); _qrText = ""; _qrKey = ""; _qrCookie = ""; _qrError = ""; if ((Object)(object)_qrTex != (Object)null) { try { Object.Destroy((Object)(object)_qrTex); } catch { } _qrTex = null; } Log.LogInfo((object)("[面板] 扫码登录:" + (_isQqLogin ? "QQ" : "网易云") + " 开始取二维码")); _qrTaskKind = 1; _qrTask = Task.Run(delegate { try { if (_isQqLogin) { byte[] qrPng = (_qqAppScan ? Client.QQ.AppQrBeginAsync(default(CancellationToken)).GetAwaiter().GetResult() : Client.QQ.QrBeginAsync(default(CancellationToken)).GetAwaiter().GetResult()); _qrPng = qrPng; } else { _qrKey = Client.Netease.QrBeginAsync(default(CancellationToken)).GetAwaiter().GetResult(); } } catch (Exception ex) { _qrError = ex.Message; } }); } private void CloseLogin() { if (!_loginOpen) { return; } _loginOpen = false; _qrPng = null; if ((Object)(object)_qrTex != (Object)null) { try { Object.Destroy((Object)(object)_qrTex); } catch { } _qrTex = null; } if (_isQqLogin) { Client.QQ.AppQrStop(); } } private void StartPoll() { string key = _qrKey; if (!_isQqLogin && string.IsNullOrEmpty(key)) { return; } _nextPollAt = Time.unscaledTime + 2f; _qrTaskKind = 2; _qrTask = Task.Run(delegate { //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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) try { if (_isQqLogin) { QqLoginResult val = (_qqAppScan ? Client.QQ.AppQrPoll() : Client.QQ.QrPollAsync(default(CancellationToken)).GetAwaiter().GetResult()); _qqResult = val.State; _qrCookie = val.CookieText ?? ""; if (!string.IsNullOrEmpty(val.RawMessage)) { _qrError = val.RawMessage; } } else { QrLoginResult result = Client.Netease.QrPollAsync(key, default(CancellationToken)).GetAwaiter().GetResult(); _qrResult = result.State; _qrCookie = result.CookieText ?? ""; if (!string.IsNullOrEmpty(result.RawMessage)) { _qrError = result.RawMessage; } } } catch (Exception ex) { _qrResult = (QrState)(-1); _qqResult = (QqLoginState)(-1); _qrError = ex.Message; } }); } private void PollLogin() { if (!_loginOpen) { return; } if (_qrTask != null && _qrTask.IsCompleted) { Task qrTask = _qrTask; _qrTask = null; try { qrTask.GetAwaiter().GetResult(); } catch { } if (_qrTaskKind == 1) { if (_isQqLogin) { if (_qrPng == null || _qrPng.Length == 0) { _loginState = 7; _loginMsg = "拿不到二维码:" + (_qrError ?? "服务端没返回图片"); Log.LogWarning((object)("[面板] 取 QQ 二维码失败:" + _loginMsg)); } else { _qrTex = PngToTexture(_qrPng); _loginState = 2; _loginMsg = "用手机 QQ 扫这上面的码(或在浏览器打开 QQ 音乐登录)"; _nextPollAt = Time.unscaledTime + 1.5f; Log.LogInfo((object)("[面板] QQ 二维码已生成:" + _qrPng.Length + " 字节," + (((Object)(object)_qrTex != (Object)null) ? (((Texture)_qrTex).width + "px") : "纹理生成失败"))); } } else if (string.IsNullOrEmpty(_qrKey)) { _loginState = 7; _loginMsg = "拿不到二维码:" + (_qrError ?? "服务端没返回 unikey"); Log.LogWarning((object)("[面板] 取 unikey 失败:" + _loginMsg)); } else { _qrText = "https://music.163.com/login?codekey=" + _qrKey; _qrTex = BuildQrTexture(_qrText); _loginState = 2; _loginMsg = "用手机网易云 App 扫这上面的码"; _nextPollAt = Time.unscaledTime + 1.5f; Log.LogInfo((object)("[面板] 二维码已生成:" + _qrText.Length + " 字节," + (((Object)(object)_qrTex != (Object)null) ? (((Texture)_qrTex).width + "px") : "纹理生成失败") + "|手机打不开 App 时可在浏览器打开:" + _qrText)); } } else if (_qrTaskKind == 2) { ApplyQrResult(); } } if ((_loginState == 2 || _loginState == 3) && _qrTask == null && Time.unscaledTime >= _nextPollAt) { StartPoll(); } if (_loginState == 4 && _qrTask == null && Time.unscaledTime - _loginDoneAt > 1.8f) { CloseLogin(); } } private void ApplyQrResult() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected I4, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (_isQqLogin) { ApplyQqResult(); return; } QrState qrResult = _qrResult; switch (qrResult - 800) { default: if ((int)qrResult == 8821) { _loginState = 6; _loginMsg = "网易风控拒绝了这次扫码(8821),换码重扫没用 —— 用「从剪贴板导入」"; Log.LogWarning((object)("[面板] 扫码被风控(8821):" + _qrError)); } else { _loginMsg = "轮询异常,继续重试:" + (_qrError ?? "未知"); } break; case 1: _loginState = 2; _loginMsg = "等手机扫码…"; break; case 2: _loginState = 3; _loginMsg = "已扫码,请在手机上点「确认登录」"; break; case 3: if (!string.IsNullOrEmpty(_qrCookie)) { Client.Netease.SetCookie(_qrCookie); } RefreshLoginLine(); _loginState = 4; _loginMsg = "登录成功 · " + _loginLine; _loginDoneAt = Time.unscaledTime; Log.LogInfo((object)("[面板] 网易云登录成功,cookie=" + CookieStore.Mask(Client.Netease.CookieText))); if (!Client.Netease.HasLogin) { _loginMsg = "扫码成功但没拿到 MUSIC_U 登录 cookie —— 用「从剪贴板导入」补登录态"; Log.LogWarning((object)("[面板] 扫码 803 成功但 cookie 缺 MUSIC_U:qr=" + CookieStore.Mask(_qrCookie ?? "") + " 现=" + CookieStore.Mask(Client.Netease.CookieText))); } break; case 0: _loginState = 5; _loginMsg = "二维码过期了,点「换一张」"; break; } } private void ApplyQqResult() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected I4, but got Unknown QqLoginState qqResult = _qqResult; switch ((int)qqResult) { case 0: _loginState = 2; _loginMsg = "等手机扫码…"; break; case 1: _loginState = 3; _loginMsg = "已扫码,请在手机上确认登录"; break; case 2: if (!string.IsNullOrEmpty(_qrCookie)) { Client.QQ.SetCookie(_qrCookie); } RefreshLoginLine(); _loginState = 4; _loginMsg = "登录成功 · " + _loginLine; _loginDoneAt = Time.unscaledTime; Log.LogInfo((object)("[面板] QQ 登录成功,cookie=" + QqCookieStore.Mask(Client.QQ.CookieText))); break; case 3: _loginState = 5; _loginMsg = "二维码过期了,点「换一张」"; break; case 4: _loginState = 7; _loginMsg = "登录失败:" + (_qrError ?? "未知"); Log.LogWarning((object)("[面板] QQ 轮询失败:" + (_qrError ?? "未知"))); break; default: _loginMsg = "轮询异常,继续重试:" + (_qrError ?? "未知"); break; } } private void ImportCookieFromClipboard() { string text = ""; try { text = GUIUtility.systemCopyBuffer; } catch { } if (_isQqLogin) { if (string.IsNullOrEmpty(text) || (text.IndexOf("qqmusic_key", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("musickey", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("qm_keyst", StringComparison.OrdinalIgnoreCase) < 0)) { _loginState = 7; _loginMsg = "剪贴板里没有 qqmusic_key / musickey。浏览器登录 y.qq.com → F12 → Application → Cookies → 复制 qqmusic_key 的值"; return; } try { Client.QQ.SetCookie(text.Trim()); RefreshLoginLine(); bool hasLogin = Client.QQ.HasLogin; _loginState = (hasLogin ? 4 : 7); _loginMsg = (hasLogin ? ("登录态已导入 · " + _loginLine) : "解析后仍然没有 qqmusic_key,检查格式"); _loginDoneAt = Time.unscaledTime; Log.LogInfo((object)("[面板] 从剪贴板导入 QQ 登录态:" + (hasLogin ? "成功" : "失败"))); return; } catch (Exception ex) { _loginState = 7; _loginMsg = "导入失败:" + ex.Message; return; } } if (string.IsNullOrEmpty(text) || text.IndexOf("MUSIC_U", StringComparison.OrdinalIgnoreCase) < 0) { _loginState = 7; _loginMsg = "剪贴板里没有 MUSIC_U。浏览器登录 music.163.com → F12 → Application → Cookies → 复制 MUSIC_U 的值"; return; } try { Client.Netease.SetCookie(text.Trim()); RefreshLoginLine(); bool hasLogin2 = Client.Netease.HasLogin; _loginState = (hasLogin2 ? 4 : 7); _loginMsg = (hasLogin2 ? ("登录态已导入 · " + _loginLine) : "解析后仍然没有 MUSIC_U,检查格式"); _loginDoneAt = Time.unscaledTime; Log.LogInfo((object)("[面板] 从剪贴板导入登录态:" + (hasLogin2 ? "成功" : "失败"))); } catch (Exception ex2) { _loginState = 7; _loginMsg = "导入失败:" + ex2.Message; } } private Texture2D BuildQrTexture(string text) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) try { QrCode val = QrEncoder.Encode(text, -1); int num = (val.Size + 6) * 5; Texture2D val2 = new Texture2D(num, num, (TextureFormat)4, false); ((Texture)val2).filterMode = (FilterMode)0; ((Texture)val2).wrapMode = (TextureWrapMode)1; ((Object)val2).hideFlags = (HideFlags)52; Color32[] array = (Color32[])(object)new Color32[num * num]; Color32 val3 = default(Color32); ((Color32)(ref val3))..ctor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); Color32 val4 = default(Color32); ((Color32)(ref val4))..ctor((byte)12, (byte)12, (byte)14, byte.MaxValue); for (int i = 0; i < array.Length; i++) { array[i] = val3; } for (int j = 0; j < val.Size; j++) { int num2 = num - 1 - (j + 3) * 5; for (int k = 0; k < val.Size; k++) { if (!val.Dark(k, j)) { continue; } int num3 = (k + 3) * 5; for (int l = 0; l < 5; l++) { int num4 = num2 - l; if (num4 >= 0 && num4 < num) { int num5 = num4 * num + num3; for (int m = 0; m < 5; m++) { array[num5 + m] = val4; } } } } } val2.SetPixels32(array); val2.Apply(false, false); ManualLogSource log = Log; string[] obj = new string[9] { "[面板] 二维码纹理 ", num.ToString(), "px(V", null, null, null, null, null, null }; int version = val.Version; obj[3] = version.ToString(); obj[4] = ","; version = val.Size; obj[5] = version.ToString(); obj[6] = " 模块,掩码 "; version = val.Mask; obj[7] = version.ToString(); obj[8] = ")"; log.LogInfo((object)string.Concat(obj)); return val2; } catch (Exception ex) { Log.LogError((object)("[面板] 生成二维码失败:" + ex.Message)); return null; } } private static Texture2D PngToTexture(byte[] png) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) try { Image val = PngDecoder.Decode(png); if (val == null || val.Pixels == null) { return null; } Texture2D val2 = new Texture2D(val.Width, val.Height, (TextureFormat)4, false); ((Object)val2).hideFlags = (HideFlags)52; ((Texture)val2).filterMode = (FilterMode)0; ((Texture)val2).wrapMode = (TextureWrapMode)1; Color32[] array = (Color32[])(object)new Color32[val.Width * val.Height]; for (int i = 0; i < array.Length; i++) { int num = i * 4; array[i] = new Color32(val.Pixels[num], val.Pixels[num + 1], val.Pixels[num + 2], val.Pixels[num + 3]); } val2.SetPixels32(array); val2.Apply(false, false); return val2; } catch (Exception ex) { Log.LogError((object)("[面板] 解码二维码 PNG 失败:" + ex.Message)); return null; } } private bool Pressed(KeyCode legacy, Key modern) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) try { Keyboard current = Keyboard.current; if (current != null) { KeyControl val = current[modern]; if (val != null && ((ButtonControl)val).wasPressedThisFrame) { return true; } return false; } } catch { } try { return Input.GetKeyDown(legacy); } catch { return false; } } private void UpdateMouse() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_00ba: 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_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) _mouseValid = false; _clicked = false; _held = false; _pendingScroll = 0; try { Mouse current = Mouse.current; if (current != null) { Vector2 val = ((InputControl<Vector2>)(object)((Pointer)current).position).ReadValue(); _mousePos = new Vector2(val.x, (float)Screen.height - val.y); _clicked = current.leftButton.wasPressedThisFrame; _held = current.leftButton.isPressed; float y = ((InputControl<Vector2>)(object)current.scroll).ReadValue().y; if (y > 0.01f) { _pendingScroll = -1; } else if (y < -0.01f) { _pendingScroll = 1; } _mouseValid = true; ToLocal(); return; } } catch { } try { Vector3 mousePosition = Input.mousePosition; _mousePos = new Vector2(mousePosition.x, (float)Screen.height - mousePosition.y); _clicked = Input.GetMouseButtonDown(0); _held = Input.GetMouseButton(0); _mouseValid = true; } catch { } ToLocal(); } private void ToLocal() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) float num = ((_scale < 0.01f) ? 1f : _scale); _lm = new Vector2((_mousePos.x - _panelPos.x) / num, (_mousePos.y - _panelPos.y) / num); } private bool Hit(Rect r) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (_mouseValid && _clicked) { return ((Rect)(ref r)).Contains(_lm); } return false; } private bool HitScreen(Rect r) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (_mouseValid && _clicked) { return ((Rect)(ref r)).Contains(_mousePos); } return false; } private void HandleMouse() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_043d: Unknown result type (might be due to invalid IL or missing references) //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_049a: Unknown result type (might be due to invalid IL or missing references) //IL_04f6: Unknown result type (might be due to invalid IL or missing references) //IL_0798: Unknown result type (might be due to invalid IL or missing references) //IL_05b2: Unknown result type (might be due to invalid IL or missing references) //IL_07ae: Unknown result type (might be due to invalid IL or missing references) //IL_07c4: Unknown result type (might be due to invalid IL or missing references) //IL_07d9: Unknown result type (might be due to invalid IL or missing references) //IL_07ee: Unknown result type (might be due to invalid IL or missing references) //IL_0819: Unknown result type (might be due to invalid IL or missing references) if (!_mouseValid) { return; } if (_dragging) { if (_held) { _panelPos = _mousePos - _dragGrab; ClampWindow(); } else { _dragging = false; SaveWindow(); } } else if (_resizing) { if (_held) { float num = _mousePos.x - _resizeGrabPos.x + (_mousePos.y - _resizeGrabPos.y); _scale = Mathf.Clamp(_resizeBaseScale + num / 900f * _dpi, 0.85f * _dpi, 1.8f * _dpi); ClampWindow(); } else { _resizing = false; SaveWindow(); } } else if (_loginOpen) { if (_clicked) { if (HitScreen(_rLoginRefresh)) { StartLogin(); } else if (HitScreen(_rLoginClip)) { ImportCookieFromClipboard(); } else if (HitScreen(_rLoginClose)) { CloseLogin(); } else if (_isQqLogin && HitScreen(_rLoginMode)) { Client.QQ.AppQrStop(); _qqAppScan = !_qqAppScan; _loginMsg = (_qqAppScan ? "已切到 QQ 音乐 App 扫码" : "已切到手机 QQ 扫码"); StartLogin(); } else if (HitScreen(_rLoginLogout) && IsLoggedIn) { LogoutCurrent(); } } } else { if (!_clicked) { return; } if (Hit(_rClose)) { Toggle(); } else if (Hit(_rCollapse)) { _collapsed = !_collapsed; ComputeLayout(); ClampWindow(); SaveWindow(); Log.LogInfo((object)("[面板]" + (_collapsed ? " 面板已折叠。" : " 面板已展开。"))); } else if (Hit(_rZoomIn)) { ZoomBy(0.08f); } else if (Hit(_rZoomOut)) { ZoomBy(-0.08f); } else if (Hit(_rGrip)) { _dragging = true; _dragGrab = _mousePos - _panelPos; } else { if (_collapsed) { return; } if (Hit(_rResize)) { _resizing = true; _resizeGrabPos = _mousePos; _resizeBaseScale = _scale; } else if (_pendingScroll != 0 && CtrlDown) { ZoomBy((_pendingScroll < 0) ? 0.06f : (-0.06f)); } else if (_pendingScroll != 0 && ((Rect)(ref _rList)).Contains(_lm)) { int pendingScroll = _pendingScroll; _pendingScroll = 0; TurnPage(pendingScroll); } else { if ((Object)(object)_player == (Object)null) { return; } if (((Rect)(ref _rSearch)).Contains(_lm)) { StartTyping(); return; } if (Hit(_rSearchBtn)) { StartSearch(); return; } if (Hit(_rClear)) { _keyword = ""; return; } if (Hit(_rTheme)) { CycleTheme(); return; } if (Hit(_rLyricPos)) { ToggleLyric(); return; } if (Hit(_rLyricSize)) { CycleLyricSize(); return; } if (Hit(_rLyricDist)) { CycleLyricDistance(); return; } if (Hit(_rLyricEdge)) { CycleLyricEdge(); return; } if (Hit(_rLyricColor)) { CycleLyricColor(); return; } if (Hit(_rLyricBright)) { CycleLyricBright(); return; } if (Hit(_rLyricHeight)) { CycleLyricHeight(); return; } for (int i = 0; i < Platforms.All.Length; i++) { if (Hit(_rNavItem[i])) { SwitchPlatform(Platforms.All[i]); return; } } if (Hit(_rFavNav)) { SwitchView((_view != 1) ? 1 : 0); return; } if (Hit(_rQueueNav)) { SwitchView((_view != 2) ? 2 : 0); return; } for (int j = 0; j < Presets.Length; j++) { string text = TagLabel(j); if (text == null) { break; } if (Hit(_rPreset[j])) { _keyword = text; StartSearch(); return; } } List<SongInfo> activeSongs = ActiveSongs; for (int k = 0; k < 8; k++) { int num2 = _page + k; if (num2 >= activeSongs.Count) { break; } if (!Hit(_rFavBtn[k])) { continue; } if (_view == 2) { MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null && instance.IsHost) { string name = activeSongs[num2].Name; instance.RemoveQueueItem(num2); instance.BroadcastQueueState(); _status = "已移出队列:" + name; Log.LogInfo((object)("[面板] " + _status)); } } else { ToggleFavorite(activeSongs[num2]); } return; } for (int l = 0; l < 8; l++) { int num3 = _page + l; if (num3 >= activeSongs.Count) { break; } if (!Hit(RowRect(l))) { continue; } MusicSync instance2 = MusicSync.Instance; if (_view == 2) { if ((Object)(object)instance2 != (Object)null && instance2.IsHost) { if (_busy) { _status = "上一个请求还没回来,等一下再批准"; return; } SongInfo val = activeSongs[num3]; string text2 = instance2.QueueUrlAt(num3); instance2.RemoveQueueItem(num3); instance2.BroadcastQueueState(); _status = "已批准播放:" + val.Name; Log.LogInfo((object)("[面板] 批准点歌:" + val.Name + " - " + val.Artists + (string.IsNullOrEmpty(text2) ? "〔无点歌人直链,房主自取〕" : "〔用点歌人直链〕"))); _queueFallback = true; _queueUrlFallback = text2; PlaySong(val); } else { _status = (((Object)(object)instance2 != (Object)null && instance2.HostHasMod) ? "等房主批准播放" : "房主没装模组,队列没人批;直接点搜索结果就能本机播放"); } return; } if ((Object)(object)instance2 != (Object)null && instance2.InRoom && !instance2.IsHost) { if (instance2.HostHasMod) { string nickName = PhotonNetwork.NickName; QueueSong(activeSongs[num3], string.IsNullOrEmpty(nickName) ? "我" : nickName); return; } _soloPlay = true; Log.LogInfo((object)("[面板] 房主未安装本模组,改为本机播放:" + activeSongs[num3].Name + "(房间内装模组 " + instance2.ModdedCount + " 人)")); } PlaySong(activeSongs[num3]); return; } if (Hit(_rPrev)) { TurnPage(-1); } else if (Hit(_rNext)) { TurnPage(1); } else if (Hit(_rPause)) { TogglePause(); } else if (Hit(_rStop)) { StopPlayback(); } else if (Hit(_rVolDown)) { _player.SetVolume(_player.Volume - 0.05f); } else if (Hit(_rVolUp)) { _player.SetVolume(_player.Volume + 0.05f); } } } } } private void TurnPage(int dir) { if (ActiveSongs.Count == 0) { _status = "还没有结果,先搜索"; return; } int page = _page; if (dir > 0) { _page = Mathf.Min(_page + 8, MaxPageStart); } else { _page = Mathf.Max(0, _page - 8); } if (dir > 0 && _page == page && _view == 0 && _page + 8 >= _songs.Count && _hasMore) { StartLoadMore(); return; } if (_page == page) { _status = ((dir > 0) ? ("已经是最后一页(第 " + PageNo + "/" + PageCount + " 页)") : "已经是第一页"); return; } _status = "第 " + PageNo + "/" + PageCount + " 页(共 " + ActiveSongs.Count + " 首)"; Log.LogInfo((object)("[面板] 翻页 → 第 " + PageNo + "/" + PageCount + " 页,起始下标 " + _page)); } private void HandleKeyboard() { if (Pressed((KeyCode)27, (Key)60)) { if (_loginOpen) { CloseLogin(); } else if (_typing) { StopTyping(); } } else { if (_loginOpen) { return; } if (!_typing) { if (Pressed((KeyCode)116, (Key)34)) { StartTyping(); } return; } if (Pressed((KeyCode)8, (Key)65) && _keyword.Length > 0) { _keyword = _keyword.Substring(0, _keyword.Length - 1); } if (Pressed((KeyCode)13, (Key)2) || Pressed((KeyCode)271, (Key)77)) { StartSearch(); } } } private void StartTyping() { if (!_typing) { _typing = true; _status = "输入中:打字 → 回车搜索 / Esc 结束输入"; try { Input.imeCompositionMode = (IMECompositionMode)1; } catch { } SubscribeTextInput(); } } private void StopTyping() { if (_typing) { _typing = false; try { Input.imeCompositionMode = (IMECompositionMode)0; } catch { } UnsubscribeTextInput(); _status = "已结束输入"; } } private void SubscribeTextInput() { try { Keyboard current = Keyboard.current; if (current != null) { if (_textKb != null && _textKb != current) { _textKb.onTextInput -= OnTextInput; } _textKb = current; current.onTextInput -= OnTextInput; current.onTextInput += OnTextInput; } } catch (Exception ex) { Log.LogWarning((object)("[面板] 订阅文本输入失败,中文可能打不进:" + ex.GetType().Name)); } } private void UnsubscribeTextInput() { try { if (_textKb != null) { _textKb.onTextInput -= OnTextInput; } } catch { } _textKb = null; } private void OnTextInput(char c) { if (_open && _typing && c >= ' ' && c != '\u007f' && _keyword.Length < 40) { _keyword += c; } } private void StartSearch() { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) if (_busy) { _status = "上一个请求还没回来,等一下"; return; } string kw = _keyword.Trim(); if (kw.Length == 0) { _status = "先输入歌名(点搜索框或按 T,也可点下面的预设词)"; return; } LibraryStore.AddHistory(kw); _libDirty = true; if (_view != 0) { _view = 0; _page = 0; } _busy = true; _taskKind = 1; _taskError = null; _pendingSongs = null; _kw = kw; _offSearch = 0; _hasMore = true; _page = 0; Platform plat = _platform; _status = "搜索中:" + Platforms.DisplayName(plat) + " · " + kw; Log.LogInfo((object)("[面板] 搜索[" + Platforms.DisplayName(plat) + "] " + kw)); _task = Task.Run(delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) try { List<SongInfo> result = Client.SearchAsync(plat, kw, 40, 0, default(CancellationToken)).GetAwaiter().GetResult(); _pendingSongs = result; _offSearch = result?.Count ?? 0; } catch (Exception ex) { _taskError = ex.GetType().Name + ": " + ex.Message; } }); } private void StartLoadMore() { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (_busy) { _status = "正在加载更多…"; return; } if (!_hasMore) { _status = "没有更多了(共 " + _songs.Count + " 首)"; return; } _busy = true; _taskKind = 3; _taskError = null; _pendingSongs = null; _status = "加载更多中…(已 " + _songs.Count + " 首)"; string kw = _kw; int off = _offSearch; Platform plat = _platform; Log.LogInfo((object)("[面板] 续拉[" + Platforms.DisplayName(plat) + "] " + kw + " off=" + off)); _task = Task.Run(delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) try { List<SongInfo> result = Client.SearchAsync(plat, kw, 40, off, default(CancellationToken)).GetAwaiter().GetResult(); _pendingSongs = result; _offSearch += result?.Count ?? 0; } catch (Exception ex) { _taskError = ex.GetType().Name + ": " + ex.Message; } }); } private void PlaySong(SongInfo song) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (_busy) { _status = "上一个请求还没回来,等一下"; return; } _busy = true; _taskKind = 2; _taskError = null; _pendingUrl = null; _pendingSong = song; _status = "取播放地址:" + song.Name; Log.LogInfo((object)("[面板] 取播放地址[" + Platforms.DisplayName(song.Platform) + "] " + song.Key + " " + song.Name + " - " + song.Artists)); LyricOverlay instance = LyricOverlay.Instance; if ((Object)(object)instance != (Object)null) { instance.Clear(); } SongInfo target = song; _task = Task.Run(delegate { try { _pendingUrl = Client.GetUrlAsync(target, default(CancellationToken)).GetAwaiter().GetResult(); } catch (Exception ex) { _taskError = ex.GetType().Name + ": " + ex.Message; } }); } private void QueueSong(SongInfo song, string requester) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (_busy) { _status = "上一个请求还没回来,等一下"; return; } _busy = true; _taskKind = 4; _taskError = null; _pendingUrl = null; _pendingSong = song; _pendingRequester = requester; _status = "点歌中(取直链):" + song.Name; Log.LogInfo((object)("[面板] 客机点歌先取流[" + Platforms.DisplayName(song.Platform) + "] " + song.Key + " " + song.Name + " - " + song.Artists)); SongInfo target = song; _task = Task.Run(delegate { try { _pendingUrl = Client.GetUrlAsync(target, default(CancellationToken)).GetAwaiter().GetResult(); } catch (Exception ex) { _taskError = ex.GetType().Name + ": " + ex.Message; } }); } private void PlaySongWithUrl(SongInfo song, string url) { if (!((Object)(object)_player == (Object)null)) { _status = "开始播放:" + song.Name; Log.LogInfo((object)("[面板] 用点歌人直链播放:" + song.Name + " - " + song.Artists)); _player.Play(song, url); StartLyricFetch(song); MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null) { instance.BroadcastPlay(song, url, 0f); } } } private void TogglePause() { if ((Object)(object)_player == (Object)null) { return; } _player.TogglePause(); MusicSync instance = MusicSync.Instance; if (!((Object)(object)instance == (Object)null)) { if (_player.HasClip && !_player.IsPlaying) { instance.BroadcastPause(paused: true); } else if (_player.IsPlaying) { instance.BroadcastPause(paused: false); } } } private void StopPlayback() { if (!((Object)(object)_player == (Object)null)) { _player.Stop(); MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null) { instance.BroadcastStop(); } } } private string ExplainFetchFail(SongInfo song, SongUrlResult r) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (r == null) { return "没有返回"; } if (r.Code == -110) { if (!((song != null && (int)song.Platform == 1) ? HasQqLogin() : HasNeteaseLogin())) { return "-110 无权限:还没登录(扫码登录有权限的账号再试)"; } return "-110 无权限:已登录,但这首是会员曲/付费曲/无版权(当前账号没有这首歌的权限)"; } return r.Explain(); } private void PollTask() { if (_task == null || !_task.IsCompleted) { return; } Task task = _task; _task = null; try { task.GetAwaiter().GetResult(); } catch (Exception ex) { if (_taskError == null) { _taskError = ex.GetType().Name + ": " + ex.Message; } } if (_taskError != null) { _status = "失败:" + _taskError; Log.LogError((object)("[面板] " + _status)); _taskError = null; _busy = false; return; } if (_taskKind == 1) { _songs.Clear(); if (_pendingSongs != null) { _songs.AddRange(_pendingSongs); } _page = 0; _hasMore = _pendingSongs != null && _pendingSongs.Count >= 40; _status = ((_songs.Count == 0) ? "没搜到(换个关键词,或者点预设词试试)" : ("搜到 " + _songs.Count + " 首 · 第 1/" + PageCount + " 页,点一行播放")); Log.LogInfo((object)("[面板] 搜索结果 " + _songs.Count + " 首,共 " + PageCount + " 页")); } else if (_taskKind == 3) { int num = 0; if (_pendingSongs != null) { HashSet<string> hashSet = new HashSet<string>(); foreach (SongInfo song in _songs) { hashSet.Add(((object)Unsafe.As<Platform, Platform>(ref song.Platform)/*cast due to .constrained prefix*/).ToString() + ":" + song.Key); } foreach (SongInfo pendingSong in _pendingSongs) { if (hashSet.Add(((object)Unsafe.As<Platform, Platform>(ref pendingSong.Platform)/*cast due to .constrained prefix*/).ToString() + ":" + pendingSong.Key)) { _songs.Add(pendingSong); num++; } } } _hasMore = _pendingSongs != null && _pendingSongs.Count >= 40; if (num == 0 && !_hasMore) { _status = "没有更多了(共 " + _songs.Count + " 首)"; } else { _status = "又加了 " + num + " 首 · 共 " + _songs.Count + " 首 · 第 " + PageNo + "/" + PageCount + " 页"; _page = Mathf.Min(_page + 8, MaxPageStart); } Log.LogInfo((object)("[面板] 续拉 +" + num + " 首,共 " + _songs.Count + " 首,hasMore=" + _hasMore)); } else if (_taskKind == 2) { if (_pendingUrl != null && _pendingUrl.Ok && (Object)(object)_player != (Object)null) { _status = (_soloPlay ? ("开始播放:" + _pendingSong.Name + "(房主没装模组,只有你能听到)") : ("开始播放:" + _pendingSong.Name)); _player.Play(_pendingSong, _pendingUrl.Url); StartLyricFetch(_pendingSong); Log.LogInfo((object)("[面板] 起播音质:" + _pendingUrl.Br / 1000 + "kbps · " + _pendingUrl.Level + " · " + _pendingSong.Name + ((_queueFallback && !string.IsNullOrEmpty(_queueUrlFallback)) ? "〔房主账号取流,未用点歌人直链〕" : ""))); MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null) { instance.BroadcastPlay(_pendingSong, _pendingUrl.Url, 0f); } } else if (_queueFallback && !string.IsNullOrEmpty(_queueUrlFallback) && _pendingSong != null) { Log.LogInfo((object)("[面板] 房主账号取不到(" + ExplainFetchFail(_pendingSong, _pendingUrl) + "),改用点歌人直链:" + _pendingSong.Name)); PlaySongWithUrl(_pendingSong, _queueUrlFallback); _queueUrlFallback = ""; } else { string text = ExplainFetchFail(_pendingSong, _pendingUrl); if (_queueFallback && _pendingUrl != null && _pendingUrl.Code == -110) { text = "这首歌是会员/付费曲:点歌人取不到直链,房主账号也没有权限(换一首,或让有会员的账号点歌)"; } _status = "拿不到播放地址:" + text; Log.LogWarning((object)("[面板] " + _status + " key=" + ((_pendingSong != null) ? _pendingSong.Key : "?"))); } } else if (_taskKind == 4) { MusicSync instance2 = MusicSync.Instance; string text2 = ((_pendingUrl != null && _pendingUrl.Ok) ? _pendingUrl.Url : ""); if ((Object)(object)instance2 != (Object)null && _pendingSong != null) { instance2.RequestQueue(_pendingSong, _pendingRequester ?? "我", text2); } if (!string.IsNullOrEmpty(text2)) { _status = "已点歌:" + _pendingSong.Name + "(等房主批准)"; } else { string text3 = ExplainFetchFail(_pendingSong, _pendingUrl); _status = "已点歌:" + _pendingSong.Name + "(你这账号取不到流," + text3 + ",房主会用他的账号再试)"; Log.LogWarning((object)("[面板] 客机点歌取流失败,发无直链入队:" + text3 + " key=" + ((_pendingSong != null) ? _pendingSong.Key : "?"))); } } _taskKind = 0; _busy = false; _queueFallback = false; _queueUrlFallback = ""; _soloPlay = false; } private void AutoAdvance() { MusicPlayer player = _player; if ((Object)(object)player == (Object)null) { _wasPlaying = false; return; } MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null && instance.InRoom && !instance.IsHost && instance.HostHasMod) { _wasPlaying = player.IsPlaying; return; } bool isPlaying = player.IsPlaying; bool hasClip = player.HasClip; if (isPlaying) { _wasPlaying = true; _playEndGrace = 0f; return; } if (!_wasPlaying) { if (_playEndGrace > 0f) { _playEndGrace -= Time.unscaledDeltaTime; } return; } if (!hasClip || !(player.Length > 1f) || !(player.Time >= player.Length - 1.2f)) { _wasPlaying = false; _playEndGrace = 0f; return; } _wasPlaying = false; if (!(_playEndGrace > 0f)) { _playEndGrace = 1f; PlayNextSong(); } } private void PlayNextSong() { MusicSync instance = MusicSync.Instance; if ((Object)(object)instance != (Object)null && instance.IsHost && instance.Queue.Count > 0) { SongInfo val = instance.Queue[0]; string text = instance.QueueUrlAt(0); instance.RemoveQueueItem(0); instance.BroadcastQueueState(); Log.LogInfo((object)("[面板] 自动接播队列:" + val.Name + " - " + val.Artists + "(剩余 " + instance.Queue.Count + " 首)" + (string.IsNullOrEmpty(text) ? "〔无点歌人直链〕" : "〔用点歌人直链〕"))); _queueFallback = true; _queueUrlFallback = text; PlaySong(val); return; } List<SongInfo> activeSongs = ActiveSongs; if (activeSongs.Count == 0) { _status = "列表空,无法自动切歌"; return; } if (_busy) { _playEndGrace = 1.5f; return; } MusicPlayer player = _player; SongInfo val2 = (((Object)(object)player != (Object)null) ? player.Current : null); if (val2 == null) { return; } int num = -1; for (int i = 0; i < activeSongs.Count; i++) { if (activeSongs[i].Key == val2.Key) { num = i; break; } } int index = ((num >= 0) ? ((num + 1) % activeSongs.Count) : 0); SongInfo val3 = activeSongs[index]; Log.LogInfo((object)("[面板] 自动切下一首:" + val3.Name + " - " + val3.Artists)); PlaySong(val3); } private void SwitchView(int v) { if (_view != v) { _view = v; _page = 0; RefreshLibrary(); string text = v switch { 2 => "一起听队列", 0 => "搜索结果", _ => "收藏", }; _status = "已切到「" + text + "」"; Log.LogInfo((object)("[面板] 视图 → " + text)); } } private void RefreshLibrary() { if (_libDirty) { _libDirty = false; _favorites.Clear(); _favorites.AddRange(LibraryStore.LoadFavorites()); _history.Clear(); _history.AddRange(LibraryStore.LoadHistory()); } } private string TagLabel(int i) { if (i < 0 || i >= Presets.Length) { return null; } if (_history.Count > 0) { if (i >= _history.Count) { return null; } return _history[i]; } return Presets[i]; } private void ToggleFavorite(SongInfo s) { if (s != null) { if (LibraryStore.IsFavorite(s.Key)) { LibraryStore.RemoveFavorite(s.Key); _status = "已取消收藏:" + s.Name; } else { LibraryStore.AddFavorite(s); _status = "已收藏:" + s.Name; } _libDirty = true; RefreshLibrary(); Log.LogInfo((object)("[面板] " + _status + "(收藏总数 " + _favorites.Count + ")")); } } private void StartLyricFetch(SongInfo song) { if (song == null || _lyricTask != null) { return; } _lyricSong = song; _lyricResult = null; SongInfo target = song; _lyricTask = Task.Run(delegate { try { _lyricResult = Client.GetLyricAsync(target, default(CancellationToken)).GetAwaiter().GetResult(); } catch (Exception ex) { Log.LogWarning((object)("[面板] 取歌词失败:" + ex.Message)); } }); } private void PollLyric() { if (_lyricTask == null || !_lyricTask.IsCompleted) { return; } _ = _lyricTask; _lyricTask = null; SongInfo lyricSong = _lyricSong; LyricResult lyricResult = _lyricResult; _lyricSong = null; _lyricResult = null; if (lyricSong == null) { return; } LyricOverlay instance = LyricOverlay.Instance; if (!((Object)(object)instance == (Object)null)) { if (lyricResult == null || !lyricResult.HasLyric) { instance.SetLyrics(lyricSong, ""); } else { instance.SetLyrics(lyricSong, lyricResult.Lrc, lyricResult.Translated); } } } private Rect RowRect(int i) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) Rect result = default(Rect); ((Rect)(ref result))..ctor(((Rect)(ref _rList)).x + 4f, ((Rect)(ref _rList)).y + 4f + (float)i * 40f, ((Rect)(ref _rList)).width - 8f, 38f); _rFavBtn[i] = new Rect(((Rect)(ref result)).xMax - 46f, ((Rect)(ref result)).y + (((Rect)(ref result)).height - 32f) * 0.5f, 38f, 32f); return result; } private vo
plugins/PeakMusic/PeakMusic.NowPlaying.exe
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web.Script.Serialization; using Microsoft.CodeAnalysis; using Windows.Media.Control; using Windows.Storage.Streams; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("PeakMusic.NowPlaying")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PeakMusic.NowPlaying")] [assembly: AssemblyTitle("PeakMusic.NowPlaying")] [assembly: AssemblyVersion("1.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 PeakMusic.NowPlaying { internal static class Compat { private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); private sealed class PlayerInfo { public IntPtr Hwnd; public string Process = ""; public string Key = "Unknown"; public string Title = ""; } private sealed class ElogInfo { public bool Found; public string Path = ""; public double PosSec = -1.0; public string PosRaw = ""; public bool Playing = true; public bool StateKnown; public long DurMs; public string Title = ""; public string Artist = ""; public long SongId; } private sealed class SmtcInfo { public string App = ""; public string Title = ""; public string Artist = ""; public string Album = ""; public long PosMs; public long DurMs; public string Status = "None"; } private sealed class Runtime { public PlayerInfo Locked; public string LastTitle = ""; public string LastArtist = ""; public string PendingTitle = ""; public DateTime PendingSince = DateTime.MinValue; public double PredictedSec; public DateTime LastTick = DateTime.Now; public bool Seeded; public double SongAge; public string ElogPosRaw = ""; public double ElogLastPos; public int ElogMissStreak; public int SmtcZeroStreak; public long SmtcLastPos = -1L; public DateTime SmtcLastAt = DateTime.MinValue; public string Lyrics = ""; public string LyricSource = "empty"; public string LyricKey = ""; public LyricProvider.Entry LyricEntry; public string LyricEntryKey = ""; public DateTime LyricTriedAt = DateTime.MinValue; public long LastDurMs; public string LastStatus = "None"; } private static volatile bool _quiet; private static string _eventLog; private static DateTime _elogProbeAt = DateTime.MinValue; private static DateTime _posLogAt = DateTime.MinValue; private static string _paKey; private static long _paPos; private static DateTime _paAt = DateTime.MinValue; private static string _elogLastRaw = ""; private static PlayerInfo _probeWin; private static string _metaKey = ""; private static ElogInfo _metaCache; [DllImport("kernel32.dll")] private static extern IntPtr GetConsoleWindow(); internal static void LogEvent(string line) { if (string.IsNullOrEmpty(_eventLog)) { return; } try { FileInfo fileInfo = new FileInfo(_eventLog); if (fileInfo.Exists && fileInfo.Length > 262144) { fileInfo.Delete(); } File.AppendAllText(_eventLog, "[" + DateTime.Now.ToString("HH:mm:ss") + "] " + line + "\r\n"); } catch { } } private static void Say(string line) { if (_quiet) { return; } try { Console.WriteLine(line); } catch { _quiet = true; } } [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc cb, IntPtr lParam); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder s, int max); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowTextLength(IntPtr hWnd); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint pid); internal static async Task<int> RunAsync(string outFile, int intervalMs, bool verbose, bool strict) { if (string.IsNullOrEmpty(outFile)) { outFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "music_progress.json"); } try { outFile = Path.GetFullPath(outFile); } catch { } try { _eventLog = Path.Combine(Path.GetDirectoryName(outFile), "music_follow.log"); } catch { } try { if (GetConsoleWindow() == IntPtr.Zero && !Console.IsOutputRedirected) { _quiet = true; } } catch { _quiet = true; } Say("[follow] 输出文件: " + outFile); Say("[follow] 轮询间隔: " + intervalMs + "ms,Ctrl+C 退出"); if (verbose) { Say("[follow] 详细日志已开启"); } bool quit = false; try { Console.CancelKeyPress += delegate(object s, ConsoleCancelEventArgs e) { quit = true; e.Cancel = true; }; } catch { } GlobalSystemMediaTransportControlsSessionManager mgr = null; TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager>); for (int i = 0; i < 6; i++) { if (mgr != null) { break; } try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionManager>(GlobalSystemMediaTransportControlsSessionManager.RequestAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; taskAwaiter = taskAwaiter2; taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager>); } GlobalSystemMediaTransportControlsSessionManager result = taskAwaiter.GetResult(); mgr = result; } catch (Exception ex) { if (verbose) { Say("[follow] SMTC 初始化失败: " + ex.Message); } } if (mgr == null) { await Task.Delay(400); } } if (mgr == null) { Say("[follow] 警告:系统媒体控制不可用,将只依赖窗口标题 + elog"); } Runtime rt = new Runtime { LastTick = DateTime.Now }; while (!quit) { DateTime t0 = DateTime.Now; try { await TickAsync(mgr, rt, outFile, verbose, strict); } catch (Exception ex2) { if (verbose) { Say("[follow] 循环异常: " + ex2.Message); } } int i = intervalMs - (int)(DateTime.Now - t0).TotalMilliseconds; if (i < 50) { i = 50; } for (int slept = 0; slept < i; slept += 50) { if (quit) { break; } await Task.Delay(Math.Min(50, i - slept)); } } Say("[follow] 已退出"); return 0; } internal static async Task<int> WatchAsync(int seconds) { GlobalSystemMediaTransportControlsSessionManager mgr = null; try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionManager>(GlobalSystemMediaTransportControlsSessionManager.RequestAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager>); taskAwaiter = taskAwaiter2; } GlobalSystemMediaTransportControlsSessionManager result = taskAwaiter.GetResult(); mgr = result; } catch { } PrintWatchHeader(); DateTime endAt = DateTime.Now.AddSeconds(seconds); TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter4 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); while (DateTime.Now < endAt) { StringBuilder sb = new StringBuilder(); sb.Append(DateTime.Now.ToString("HH:mm:ss.fff")); ElogInfo elogInfo = null; try { elogInfo = ReadElog(_metaKey); } catch { } if (elogInfo != null && elogInfo.Found) { sb.Append(" | elog "); sb.Append((!elogInfo.StateKnown) ? "状态?" : (elogInfo.Playing ? "播放" : "暂停")); sb.Append(' '); sb.Append((elogInfo.PosSec >= 0.0) ? (elogInfo.PosSec.ToString("F2") + "s") : "无进度"); if (!string.IsNullOrEmpty(elogInfo.Title)) { sb.Append(' ').Append(elogInfo.Title); } } else { sb.Append(" | elog 无"); } if (mgr != null) { try { foreach (GlobalSystemMediaTransportControlsSession session in mgr.GetSessions()) { string app = session.SourceAppUserModelId ?? ""; if (!IsMusicAppId(app)) { continue; } string st = "?"; try { GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo(); if (playbackInfo != null) { st = ((object)playbackInfo.PlaybackStatus/*cast due to .constrained prefix*/).ToString(); } } catch { } double pos = -1.0; double dur = -1.0; string upd = "-"; try { GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties = session.GetTimelineProperties(); if (timelineProperties != null) { pos = timelineProperties.Position.TotalSeconds; dur = timelineProperties.EndTime.TotalSeconds; upd = timelineProperties.LastUpdatedTime.ToString("HH:mm:ss"); } } catch { } string t = ""; string a = ""; try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter3 = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>(session.TryGetMediaPropertiesAsync()); if (!taskAwaiter3.IsCompleted) { await taskAwaiter3; taskAwaiter3 = taskAwaiter4; taskAwaiter4 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); } GlobalSystemMediaTransportControlsSessionMediaProperties result2 = taskAwaiter3.GetResult(); if (result2 != null) { t = result2.Title ?? ""; a = result2.Artist ?? ""; } } catch { } sb.Append(" | ").Append(AppShort(app)).Append(' ') .Append(st) .Append(' ') .Append((pos >= 0.0) ? (pos.ToString("F2") + "/" + dur.ToString("F0") + "s") : "-") .Append(" 更新") .Append(upd) .Append(' ') .Append(t); if (!string.IsNullOrEmpty(a)) { sb.Append(" - ").Append(a); } } } catch { } } Console.WriteLine(sb.ToString()); Console.Out.Flush(); await Task.Delay(500); } Console.WriteLine("=== 探针结束 ==="); return 0; } private static string AppShort(string app) { if (string.IsNullOrEmpty(app)) { return "?"; } int num = app.LastIndexOfAny(new char[3] { '\\', '/', '!' }); if (num < 0 || num >= app.Length - 1) { return app; } return app.Substring(num + 1); } private static void PrintWatchHeader() { Console.WriteLine("=== 探针开始:每 0.5 秒采样一次 ==="); Console.WriteLine("看三件事:①elog 的秒数(网易云内部真值)②各平台 SMTC 报的秒数 ③后面「更新」是系统这次采样的时间"); Console.WriteLine("对比方法:同一时刻,谁的数字大就是谁领先。"); Console.WriteLine(); } private static async Task TickAsync(GlobalSystemMediaTransportControlsSessionManager mgr, Runtime rt, string outFile, bool verbose, bool strict) { DateTime now = DateTime.Now; double dt = (now - rt.LastTick).TotalSeconds; if (dt < 0.0 || dt > 5.0) { dt = 0.0; } rt.LastTick = now; bool flag = rt.Locked != null && rt.Locked.Hwnd != IntPtr.Zero && GetWindowTextLength(rt.Locked.Hwnd) > 0; if (rt.Locked != null && !flag) { if (verbose) { Say("[follow] 播放器窗口已关闭"); } rt.Locked = null; rt.LastTitle = ""; rt.LastArtist = ""; rt.Lyrics = ""; rt.LyricKey = ""; rt.PredictedSec = 0.0; Program.WriteAtomic(outFile, "{\"status\":\"None\",\"title\":\"\",\"artist\":\"\",\"positionMs\":0,\"endMs\":0,\"lyrics\":\"\",\"lyricsSource\":\"empty\"}"); rt.LastStatus = "None"; return; } string text = await DetectPlayingApp(mgr); if (string.IsNullOrEmpty(text) && HasSmtcSession(mgr, "NetEase") && DateTime.Now >= _elogProbeAt) { _elogProbeAt = DateTime.Now.AddSeconds(1.0); PlayerInfo playerInfo = ((rt.Locked != null && rt.Locked.Key == "NetEase") ? rt.Locked : FindMusicWindowBy("NetEase")); if (playerInfo != null) { ElogInfo elogInfo = ReadElog(playerInfo.Title); if (elogInfo != null && elogInfo.Found) { bool flag2 = !string.IsNullOrEmpty(elogInfo.PosRaw) && elogInfo.PosRaw != _elogLastRaw; _elogLastRaw = elogInfo.PosRaw ?? ""; if (!string.IsNullOrEmpty(elogInfo.Title) && ((elogInfo.StateKnown && elogInfo.Playing) || flag2)) { text = "NetEase"; _probeWin = playerInfo; } } } } if (!string.IsNullOrEmpty(text) && (rt.Locked == null || rt.Locked.Key != text)) { PlayerInfo playerInfo2 = ((text == "NetEase" && _probeWin != null) ? _probeWin : FindMusicWindowBy(text)); if (playerInfo2 != null) { Say("[follow] 换成正在播放的播放器: " + ((rt.Locked == null) ? "无" : rt.Locked.Key) + " → " + playerInfo2.Key + " (" + playerInfo2.Title + ")"); LogEvent("切播放器 → " + playerInfo2.Key + " | " + playerInfo2.Title); ResetForNewPlayer(rt); rt.Locked = playerInfo2; } } if (rt.Locked == null || rt.Locked.Hwnd == IntPtr.Zero || GetWindowTextLength(rt.Locked.Hwnd) <= 0) { PlayerInfo playerInfo3 = (string.IsNullOrEmpty(text) ? null : FindMusicWindowBy(text)) ?? FindMusicWindow(); if (playerInfo3 != null && (rt.Locked == null || playerInfo3.Hwnd != rt.Locked.Hwnd)) { rt.Locked = playerInfo3; Say("[follow] 锁定播放器: " + playerInfo3.Key + " → " + playerInfo3.Title); } } if (rt.Locked != null && rt.Locked.Key == "NetEase" && !HasSmtcSession(mgr, "NetEase")) { PlayerInfo playerInfo4 = FindMusicWindowBy("KuGou") ?? FindMusicWindowBy("QQMusic"); if (playerInfo4 != null) { Say("[follow] 网易云无系统媒体会话,改用 " + playerInfo4.Key); LogEvent("网易云无系统媒体会话 → 改用 " + playerInfo4.Key); ResetForNewPlayer(rt); rt.Locked = playerInfo4; } } ElogInfo elog = null; if (rt.Locked != null && rt.Locked.Key == "NetEase") { elog = ReadElog((rt.Locked != null) ? rt.Locked.Title : ""); } SmtcInfo smtcInfo = await ReadSmtc(mgr, (rt.Locked != null) ? rt.Locked.Key : null); ComposeMeta(rt, elog, smtcInfo, out var title, out var artist); string text2 = ComposeStatus(rt, elog, smtcInfo); bool flag3 = ConfirmSongChange(rt, title, artist, now); long num = 0L; if (smtcInfo != null && smtcInfo.DurMs > 1000) { num = smtcInfo.DurMs; } else if (elog != null && elog.DurMs > 1000) { num = elog.DurMs; } else if (rt.LastDurMs > 1000) { num = rt.LastDurMs; } if (num > 0) { rt.LastDurMs = num; } double num2 = AdvancePosition(rt, elog, smtcInfo, text2, dt, flag3, verbose); if (flag3) { num2 = (rt.PredictedSec = rt.SongAge); rt.Seeded = true; } if (num > 0 && num2 * 1000.0 > (double)(num + 5000)) { num2 = (double)num / 1000.0; if (text2 == "Playing") { text2 = "Paused"; } } else if (num > 0 && num2 * 1000.0 > (double)num) { num2 = (double)num / 1000.0; } long songId = 0L; if (elog != null && elog.SongId > 0 && elog.Found && !string.IsNullOrEmpty(elog.Title) && (smtcInfo == null || smtcInfo.DurMs <= 1000 || elog.DurMs <= 1000 || Math.Abs(elog.DurMs - smtcInfo.DurMs) <= 3000)) { songId = elog.SongId; } MaybeFetchLyrics(rt, title, artist, num, songId, verbose); ApplyPendingLyric(rt); long num3 = (long)Math.Round(num2 * 1000.0); if (num3 < 0) { num3 = 0L; } string text3 = "{\"status\":\"" + text2 + "\",\"title\":\"" + Program.Esc(title) + "\",\"artist\":\"" + Program.Esc(artist) + "\",\"positionMs\":" + num3 + ",\"endMs\":" + ((num > 0) ? num : 0) + ",\"lyrics\":\"" + Program.Esc(rt.Lyrics) + "\",\"lyricsSource\":\"" + rt.LyricSource + "\"" + (strict ? "" : (",\"platform\":\"" + PlatformKey(rt) + "\"")) + "}"; Program.WriteAtomic(outFile, text3); if ((DateTime.Now - _posLogAt).TotalSeconds >= 5.0) { _posLogAt = DateTime.Now; LogEvent("进度 " + ((double)num3 / 1000.0).ToString("F1") + "/" + ((double)num / 1000.0).ToString("F1") + "s | smtc " + ((smtcInfo != null) ? ((double)smtcInfo.PosMs / 1000.0).ToString("F1") : "-") + "s | " + PlatformKey(rt) + " | " + text2); } if (verbose && (text2 != rt.LastStatus || flag3)) { Say("[follow] " + text2 + " | " + title + " - " + artist + " | " + num2.ToString("F1") + "/" + ((double)num / 1000.0).ToString("F1") + "s | 歌词:" + (string.IsNullOrEmpty(rt.Lyrics) ? "无" : (rt.Lyrics.Length + "字(" + rt.LyricSource + ")"))); } rt.LastStatus = text2; } private static void ComposeMeta(Runtime rt, ElogInfo elog, SmtcInfo smtc, out string title, out string artist) { title = ""; artist = ""; if (rt.Locked != null && rt.Locked.Key == "NetEase") { if (elog != null && elog.Found && !string.IsNullOrEmpty(elog.Title) && (smtc == null || smtc.DurMs <= 1000 || elog.DurMs <= 1000 || Math.Abs(elog.DurMs - smtc.DurMs) <= 3000)) { title = elog.Title; artist = elog.Artist; } else if (rt.Locked != null) { ParseWindowTitle(rt.Locked.Title, rt.Locked.Key, out title, out artist); } if (string.IsNullOrEmpty(title) && smtc != null) { title = smtc.Title; artist = smtc.Artist; } } else if (smtc != null && !string.IsNullOrEmpty(smtc.Title)) { title = smtc.Title; artist = smtc.Artist; } else if (rt.Locked != null) { ParseWindowTitle(rt.Locked.Title, rt.Locked.Key, out title, out artist); } title = CleanTitle(title); if (title == "酷狗音乐" || title == "QQ音乐" || title == "网易云音乐" || title == "酷我音乐") { title = ""; } } private static string CleanTitle(string s) { if (string.IsNullOrEmpty(s)) { return ""; } s = s.Trim(); int num = s.IndexOf("("); if (num > 0) { s = s.Substring(0, num).Trim(); } num = s.IndexOf("("); if (num > 0) { s = s.Substring(0, num).Trim(); } return s; } private static void ParseWindowTitle(string raw, string key, out string title, out string artist) { title = ""; artist = ""; if (string.IsNullOrEmpty(raw)) { return; } string text = raw.Trim(); string[] array = new string[8] { " - 酷狗音乐", " - 酷狗音", " - 酷狗", " - QQ音乐", " - 网易云音乐", " - 酷我音乐", " - CloudMusic", " - 网易云" }; foreach (string value in array) { int num = text.LastIndexOf(value, StringComparison.OrdinalIgnoreCase); if (num > 0) { text = text.Substring(0, num).Trim(); } } text = text.Replace("歌曲-", "").Replace("歌曲 - ", "").Trim(); if (text.StartsWith("乐 ")) { text = text.Substring(2).Trim(); } int num2 = 0; int startIndex = 0; while ((startIndex = text.IndexOf(" - ", startIndex, StringComparison.Ordinal)) >= 0) { num2++; startIndex += 3; } if (num2 > 1) { return; } int num3 = text.LastIndexOf(" - ", StringComparison.Ordinal); if (num3 > 0) { string text2 = text.Substring(0, num3).Trim(); string text3 = text.Substring(num3 + 3).Trim(); if (key == "KuGou") { artist = text2; title = text3; } else { title = text2; artist = text3; } } else { title = text; } if (title.Length == 0 || artist.Length == 0) { if (title.Length == 0) { title = text; } artist = ""; } } private static string ComposeStatus(Runtime rt, ElogInfo elog, SmtcInfo smtc) { if (rt.Locked == null && (smtc == null || string.IsNullOrEmpty(smtc.Title))) { return "None"; } if (rt.Locked != null && rt.Locked.Key == "NetEase" && elog != null && elog.Found && elog.StateKnown) { if (!elog.Playing) { return "Paused"; } return "Playing"; } if (smtc != null && !string.IsNullOrEmpty(smtc.Status) && smtc.Status != "None") { return smtc.Status; } return "Paused"; } private static bool ConfirmSongChange(Runtime rt, string title, string artist, DateTime now) { if (string.IsNullOrEmpty(title)) { rt.PendingTitle = ""; return false; } if (title == rt.LastTitle) { rt.PendingTitle = ""; rt.LastArtist = artist; return false; } if (rt.PendingTitle != title) { rt.PendingTitle = title; rt.PendingSince = now; return false; } double totalSeconds = (now - rt.PendingSince).TotalSeconds; double num = 1.0; if (totalSeconds < num) { return false; } rt.LastTitle = title; rt.LastArtist = artist; rt.PendingTitle = ""; rt.SongAge = totalSeconds; rt.PredictedSec = 0.0; rt.Seeded = false; rt.Lyrics = ""; rt.LyricSource = "empty"; rt.LyricKey = ""; rt.LyricEntry = null; rt.LyricEntryKey = ""; rt.SmtcZeroStreak = 0; rt.LastDurMs = 0L; LogEvent("切歌 " + title + " - " + artist); return true; } private static double AdvancePosition(Runtime rt, ElogInfo elog, SmtcInfo smtc, string status, double dt, bool songChanged, bool verbose) { if (songChanged) { rt.PredictedSec = 0.0; rt.ElogPosRaw = ""; rt.SmtcLastPos = -1L; } if (!rt.Seeded) { if (elog != null && elog.Found && elog.PosSec >= 0.0) { rt.PredictedSec = elog.PosSec; rt.ElogPosRaw = elog.PosRaw; rt.Seeded = true; } else if (smtc != null && smtc.PosMs > 0) { rt.PredictedSec = (double)smtc.PosMs / 1000.0; rt.Seeded = true; } else if (status == "Playing") { rt.Seeded = true; } } if (status == "Playing") { rt.PredictedSec += dt; } if (elog != null && elog.Found && elog.PosSec >= 0.0) { if (!string.IsNullOrEmpty(elog.PosRaw) && elog.PosRaw != rt.ElogPosRaw) { if (Math.Abs(elog.PosSec - rt.PredictedSec) > 1.2 || !rt.Seeded) { if (verbose && rt.ElogPosRaw != "") { Say("[follow] elog 跳变同步: " + rt.PredictedSec.ToString("F1") + "s → " + elog.PosSec.ToString("F1") + "s"); } rt.PredictedSec = elog.PosSec; } rt.ElogPosRaw = elog.PosRaw; rt.ElogLastPos = elog.PosSec; } rt.ElogMissStreak = 0; } else if (rt.Locked != null && rt.Locked.Key == "NetEase") { rt.ElogMissStreak++; } if (rt.Locked != null && rt.Locked.Key != "NetEase" && smtc != null) { if (smtc.PosMs > 0) { rt.SmtcZeroStreak = 0; double num = (double)smtc.PosMs / 1000.0; if (Math.Abs(num - rt.PredictedSec) > 1.5) { if (verbose) { Say("[follow] SMTC 跳变同步: " + rt.PredictedSec.ToString("F1") + "s → " + num.ToString("F1") + "s"); } rt.PredictedSec = num; } rt.SmtcLastPos = smtc.PosMs; } else if (status == "Playing") { rt.SmtcZeroStreak++; } } if (rt.PredictedSec < 0.0) { rt.PredictedSec = 0.0; } return rt.PredictedSec; } private static void MaybeFetchLyrics(Runtime rt, string title, string artist, long durMs, long songId, bool verbose) { if (string.IsNullOrEmpty(title)) { return; } string text = ((songId > 0) ? songId.ToString() : "0") + "\u0002" + title + "\u0001" + artist; if ((rt.LyricEntry != null && rt.LyricEntryKey == text && !string.IsNullOrEmpty(rt.LyricEntry.Lyric)) || (text == rt.LyricKey && (DateTime.Now - rt.LyricTriedAt).TotalSeconds < 5.0)) { return; } rt.LyricKey = text; rt.LyricTriedAt = DateTime.Now; string platform = PlatformKey(rt); long num = songId; try { rt.LyricEntry = ((num > 0) ? LyricProvider.GetById(platform, num, title, artist, durMs) : LyricProvider.Get(platform, title, artist, durMs)); rt.LyricEntryKey = text; } catch (Exception ex) { if (verbose) { Say("[follow] 歌词请求失败: " + ex.Message); } } } private static void ApplyPendingLyric(Runtime rt) { LyricProvider.Entry lyricEntry = rt.LyricEntry; if (lyricEntry == null || rt.LyricEntryKey.Length == 0 || rt.LyricEntryKey != rt.LyricKey || string.IsNullOrEmpty(lyricEntry.Lyric) || (rt.Lyrics.Length > 0 && rt.LyricSource == (string.IsNullOrEmpty(lyricEntry.Source) ? "api" : lyricEntry.Source))) { return; } rt.Lyrics = lyricEntry.Lyric; rt.LyricSource = (string.IsNullOrEmpty(lyricEntry.Source) ? "api" : lyricEntry.Source); if (rt.LastDurMs <= 1000) { long num = LastLrcMs(lyricEntry.Lyric); if (num > 1000) { rt.LastDurMs = num + 5000; } } Say("[follow] 歌词 HIT " + rt.LastTitle + " - " + rt.LastArtist + " (" + rt.LyricSource + ")"); LogEvent("歌词 HIT " + rt.LastTitle + " - " + rt.LastArtist + " (" + rt.LyricSource + ")"); } private static bool HasSmtcSession(GlobalSystemMediaTransportControlsSessionManager mgr, string key) { if (mgr == null) { return false; } try { foreach (GlobalSystemMediaTransportControlsSession session in mgr.GetSessions()) { string text = session.SourceAppUserModelId ?? ""; if (IsMusicAppId(text) && KeyOf(text.ToLowerInvariant()) == key) { return true; } } } catch { } return false; } private static async Task<string> DetectPlayingApp(GlobalSystemMediaTransportControlsSessionManager mgr) { if (mgr == null) { return null; } try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); foreach (GlobalSystemMediaTransportControlsSession s in mgr.GetSessions()) { string app = s.SourceAppUserModelId ?? ""; if (!IsMusicAppId(app)) { continue; } GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = s.GetPlaybackInfo(); if (playbackInfo == null || (int)playbackInfo.PlaybackStatus != 4) { continue; } string t = ""; try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>(s.TryGetMediaPropertiesAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; taskAwaiter = taskAwaiter2; taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); } GlobalSystemMediaTransportControlsSessionMediaProperties result = taskAwaiter.GetResult(); if (result != null) { t = result.Title ?? ""; } } catch { } if (string.IsNullOrEmpty(t)) { continue; } string text = KeyOf(app.ToLowerInvariant()); if (text == "Unknown") { continue; } long num = 0L; try { GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties = s.GetTimelineProperties(); if (timelineProperties != null) { num = (long)timelineProperties.Position.TotalMilliseconds; } } catch { } if (num > 0) { if (text == _paKey && num == _paPos) { if ((DateTime.Now - _paAt).TotalSeconds > 3.0) { continue; } } else { _paKey = text; _paPos = num; _paAt = DateTime.Now; } } return text; } } catch { } return null; } private static PlayerInfo FindMusicWindowBy(string key) { PlayerInfo found = null; EnumWindows(delegate(IntPtr h, IntPtr l) { if (!IsWindowVisible(h)) { return true; } int windowTextLength = GetWindowTextLength(h); if (windowTextLength <= 0) { return true; } StringBuilder stringBuilder = new StringBuilder(windowTextLength + 2); GetWindowText(h, stringBuilder, stringBuilder.Capacity); string text = stringBuilder.ToString().Trim(); if (text.Length == 0) { return true; } GetWindowThreadProcessId(h, out var pid); if (pid == 0) { return true; } string text2; try { text2 = Process.GetProcessById((int)pid).ProcessName.ToLowerInvariant(); } catch { return true; } if (text2 == "explorer" || text2 == "applicationframehost") { return true; } if (KeyOf(text2) != key) { return true; } found = new PlayerInfo { Hwnd = h, Process = text2, Key = key, Title = text }; return false; }, IntPtr.Zero); return found; } private static void ResetForNewPlayer(Runtime rt) { rt.LastTitle = ""; rt.LastArtist = ""; rt.PendingTitle = ""; rt.Lyrics = ""; rt.LyricSource = "empty"; rt.LyricKey = ""; rt.PredictedSec = 0.0; rt.Seeded = false; rt.LastDurMs = 0L; rt.ElogPosRaw = ""; rt.SmtcLastPos = -1L; rt.SmtcZeroStreak = 0; rt.LastStatus = ""; } private static long LastLrcMs(string lrc) { long num = 0L; int num2 = 0; while (num2 < lrc.Length) { int num3 = lrc.IndexOf('[', num2); if (num3 < 0) { break; } int num4 = lrc.IndexOf(']', num3 + 1); if (num4 < 0) { break; } int num5 = lrc.IndexOf(':', num3 + 1); if (num5 > num3 && num5 < num4) { int result = 0; string s = lrc.Substring(num3 + 1, num5 - num3 - 1).Trim(); string text = lrc.Substring(num5 + 1, num4 - num5 - 1).Trim(); int num6 = text.IndexOf('.'); string s2 = ((num6 >= 0) ? text.Substring(0, num6) : text); string text2 = ((num6 >= 0) ? text.Substring(num6 + 1) : "0"); if (int.TryParse(s, out var result2) && int.TryParse(s2, out var result3)) { if (text2.Length == 3) { int.TryParse(text2, out result); } else if (text2.Length > 0 && int.TryParse(text2, out result)) { result *= ((text2.Length == 1) ? 100 : 10); } long num7 = ((long)result2 * 60L + result3) * 1000 + result; if (num7 > num) { num = num7; } } } num2 = num4 + 1; } return num; } private static PlayerInfo FindMusicWindow() { PlayerInfo found = null; EnumWindows(delegate(IntPtr h, IntPtr l) { if (!IsWindowVisible(h)) { return true; } int windowTextLength = GetWindowTextLength(h); if (windowTextLength <= 0) { return true; } StringBuilder stringBuilder = new StringBuilder(windowTextLength + 2); GetWindowText(h, stringBuilder, stringBuilder.Capacity); string text = stringBuilder.ToString().Trim(); if (text.Length == 0) { return true; } if (text.IndexOf("Dummy", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (text.IndexOf("GDI", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (text.IndexOf("Default IME", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } GetWindowThreadProcessId(h, out var pid); if (pid == 0) { return true; } string text2; try { text2 = Process.GetProcessById((int)pid).ProcessName.ToLowerInvariant(); } catch { return true; } if (text2 == "explorer" || text2 == "applicationframehost") { return true; } string text3 = KeyOf(text2); if (text3 == "Unknown") { return true; } found = new PlayerInfo { Hwnd = h, Process = text2, Key = text3, Title = text }; return false; }, IntPtr.Zero); return found; } private static string KeyOf(string proc) { if (proc.Contains("cloudmusic") || proc.Contains("netease")) { return "NetEase"; } if (proc.Contains("qqmusic")) { return "QQMusic"; } if (proc.Contains("kugou") || proc.Contains("kgmusic")) { return "KuGou"; } if (proc.Contains("kwmusic") || proc.Contains("kuwo")) { return "KuWo"; } return "Unknown"; } private static string PlatformKey(Runtime rt) { if (rt.Locked == null) { return "none"; } return rt.Locked.Key switch { "NetEase" => "netease", "QQMusic" => "qq", "KuGou" => "kugou", "KuWo" => "kuwo", _ => "other", }; } private static async Task<SmtcInfo> ReadSmtc(GlobalSystemMediaTransportControlsSessionManager mgr, string preferKey) { if (mgr == null) { return null; } try { GlobalSystemMediaTransportControlsSession best = null; int num = -1; foreach (GlobalSystemMediaTransportControlsSession session in mgr.GetSessions()) { string text = session.SourceAppUserModelId ?? ""; if (!IsMusicAppId(text)) { continue; } string text2 = KeyOf(text.ToLowerInvariant()); int num2 = 1; if (preferKey != null && text2 == preferKey) { num2 = 3; } else if (text2 != "Unknown") { num2 = 2; } try { GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo(); if (playbackInfo != null && (int)playbackInfo.PlaybackStatus == 4) { num2 += 10; } } catch { } if (num2 > num) { num = num2; best = session; } } if (best == null) { return null; } SmtcInfo info = new SmtcInfo { App = (best.SourceAppUserModelId ?? "") }; try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>(best.TryGetMediaPropertiesAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); taskAwaiter = taskAwaiter2; } GlobalSystemMediaTransportControlsSessionMediaProperties result = taskAwaiter.GetResult(); if (result != null) { info.Title = result.Title ?? ""; info.Artist = result.Artist ?? ""; info.Album = result.AlbumTitle ?? ""; } } catch { } try { GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties = best.GetTimelineProperties(); if (timelineProperties != null) { info.PosMs = (long)timelineProperties.Position.TotalMilliseconds; info.DurMs = (long)timelineProperties.EndTime.TotalMilliseconds; } } catch { } try { GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo2 = best.GetPlaybackInfo(); if (playbackInfo2 != null) { GlobalSystemMediaTransportControlsSessionPlaybackStatus playbackStatus = playbackInfo2.PlaybackStatus; switch ((int)playbackStatus) { case 4: info.Status = "Playing"; break; case 5: info.Status = "Paused"; break; case 3: info.Status = "Paused"; break; case 0: info.Status = "None"; break; default: info.Status = "Paused"; break; } } } catch { } return info; } catch { return null; } } private static bool IsMusicAppId(string app) { if (string.IsNullOrEmpty(app)) { return false; } string text = app.ToLowerInvariant(); if (!text.Contains("cloudmusic") && !text.Contains("netease") && !text.Contains("qqmusic") && !text.Contains("kugou") && !text.Contains("kgmusic") && !text.Contains("kwmusic")) { return text.Contains("kuwo"); } return true; } private static string ElogPath() { string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); string text = Path.Combine(folderPath, "NetEase", "CloudMusic", "cloudmusic.elog"); if (File.Exists(text)) { return text; } string text2 = Path.Combine(folderPath, "NetEase", "CloudMusic", "webdata", "cloudmusic.elog"); if (File.Exists(text2)) { return text2; } return text; } private static byte[] DecodeElog(byte[] input, int count) { byte[] array = new byte[count]; for (int i = 0; i < count; i++) { byte num = input[i]; int num2 = num / 16; int num3 = num % 16; int num4 = (num2 ^ (num3 + 8)) % 16; int num5 = num / 64; int num6 = 3 - (num2 & 3); array[i] = (byte)(num4 * 16 + num5 * 4 + num6); } return array; } private static byte[] ReadTail(FileStream fs, long len, int want) { int num = (int)Math.Min(want, len); if (num <= 0) { return new byte[0]; } fs.Seek(len - num, SeekOrigin.Begin); byte[] array = new byte[num]; int i; int num2; for (i = 0; i < num; i += num2) { num2 = fs.Read(array, i, num - i); if (num2 <= 0) { break; } } if (i != num) { Array.Resize(ref array, i); } return array; } private static ElogInfo ReadElog(string windowTitle) { ElogInfo elogInfo = new ElogInfo(); string path = (elogInfo.Path = ElogPath()); if (!File.Exists(path)) { return elogInfo; } elogInfo.Found = true; try { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); long length = fileStream.Length; if (length <= 0) { return elogInfo; } byte[] array = ReadTail(fileStream, length, 524288); if (array.Length != 0) { string text = Encoding.UTF8.GetString(DecodeElog(array, array.Length)); ParsePos(text, elogInfo); ParseState(text, elogInfo); } if (_metaCache == null || string.IsNullOrEmpty(_metaKey) || windowTitle != _metaKey) { ElogInfo elogInfo2 = DeepScanMeta(fileStream, length); if (elogInfo2 != null) { _metaCache = elogInfo2; _metaKey = windowTitle; } } if (_metaCache != null) { elogInfo.Title = _metaCache.Title; elogInfo.Artist = _metaCache.Artist; elogInfo.DurMs = _metaCache.DurMs; elogInfo.SongId = _metaCache.SongId; } } catch { } return elogInfo; } private static ElogInfo DeepScanMeta(FileStream fs, long len) { long num = len; for (int i = 0; i < 6; i++) { if (num <= 0) { break; } long num2 = Math.Max(0L, num - 4194304); int num3 = (int)(num - num2); fs.Seek(num2, SeekOrigin.Begin); byte[] array = new byte[num3]; int j; int num4; for (j = 0; j < num3; j += num4) { num4 = fs.Read(array, j, num3 - j); if (num4 <= 0) { break; } } if (j <= 0) { break; } string text = Encoding.UTF8.GetString(DecodeElog(array, j)); if (text.LastIndexOf("\"checkPlayPrivilege\"", StringComparison.Ordinal) > 0) { ElogInfo elogInfo = new ElogInfo { Found = true }; ParseMeta(text, elogInfo); if (elogInfo.SongId > 0 || !string.IsNullOrEmpty(elogInfo.Title)) { return elogInfo; } } num = num2; } return null; } private static void ParsePos(string text, ElogInfo info) { int num = text.LastIndexOf("\"setPlayingPosition\"", StringComparison.Ordinal); if (num <= 0) { return; } int num2 = text.IndexOf(',', num); if (num2 <= 0) { return; } int i; for (i = num2 + 1; i < text.Length && (text[i] == ' ' || text[i] == '"'); i++) { } int j; for (j = i; j < text.Length && (char.IsDigit(text[j]) || text[j] == '.' || text[j] == '-'); j++) { } if (j > i) { string text2 = text.Substring(i, j - i); if (double.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result >= 0.0) { info.PosSec = result; info.PosRaw = text2; } } } private static void ParseState(string text, ElogInfo info) { int num = text.LastIndexOf("\"native播放state\"", StringComparison.Ordinal); if (num <= 0) { return; } int num2 = text.IndexOf(',', num); if (num2 > 0) { int i; for (i = num2 + 1; i < text.Length && (text[i] == ' ' || text[i] == '"'); i++) { } int j; for (j = i; j < text.Length && char.IsDigit(text[j]); j++) { } if (j > i && int.TryParse(text.Substring(i, j - i), out var result)) { info.Playing = result == 1; info.StateKnown = true; } } } private static void ParseMeta(string text, ElogInfo info) { int num = text.LastIndexOf("\"checkPlayPrivilege\"", StringComparison.Ordinal); if (num <= 0) { return; } int num2 = text.IndexOf("\"duration\":", num, StringComparison.Ordinal); if (num2 > 0) { int num3 = num2 + 11; int i; for (i = num3; i < text.Length && char.IsDigit(text[i]); i++) { } if (i > num3 && long.TryParse(text.Substring(num3, i - num3), out var result) && result > 1000) { info.DurMs = result; } } int num4 = text.IndexOf("\"name\":\"", num, StringComparison.Ordinal); if (num4 > 0) { int num5 = num4 + 8; int num6 = text.IndexOf('"', num5); if (num6 > num5) { info.Title = text.Substring(num5, num6 - num5); } } int num7 = text.IndexOf("\"artists\":[{", num, StringComparison.Ordinal); if (num7 > 0) { int num8 = text.IndexOf("\"name\":\"", num7, StringComparison.Ordinal); if (num8 > 0) { int num9 = num8 + 8; int num10 = text.IndexOf('"', num9); if (num10 > num9) { info.Artist = text.Substring(num9, num10 - num9); } } } int num11 = text.IndexOf("\"id\":", num, StringComparison.Ordinal); if (num11 > 0) { int j; for (j = num11 + 5; j < text.Length && (text[j] == ' ' || text[j] == '"'); j++) { } int k; for (k = j; k < text.Length && char.IsDigit(text[k]); k++) { } if (k > j && long.TryParse(text.Substring(j, k - j), out var result2) && result2 > 0) { info.SongId = result2; } } } internal static void Diagnose() { Say("=== 本机播放器诊断 ==="); Say(""); PlayerInfo playerInfo = FindMusicWindow(); if (playerInfo == null) { Say("窗口:未找到音乐播放器窗口"); } else { Say("窗口:" + playerInfo.Key + " (" + playerInfo.Process + ") → " + playerInfo.Title); } string text = ElogPath(); Say("elog 预期路径:" + text); Say("elog 是否存在:" + (File.Exists(text) ? "是" : "否")); if (File.Exists(text)) { ElogInfo elogInfo = ReadElog(""); Say(" setPlayingPosition:" + ((elogInfo.PosSec >= 0.0) ? (elogInfo.PosSec.ToString("F2") + "s (raw=" + elogInfo.PosRaw + ")") : "未找到")); Say(" native播放state:" + ((!elogInfo.StateKnown) ? "未找到" : (elogInfo.Playing ? "1 (播放)" : "0 (暂停)"))); Say(" duration:" + ((elogInfo.DurMs > 0) ? (elogInfo.DurMs + "ms") : "未找到")); Say(" checkPlayPrivilege 歌名:" + (string.IsNullOrEmpty(elogInfo.Title) ? "未找到" : elogInfo.Title)); Say(" checkPlayPrivilege 歌手:" + (string.IsNullOrEmpty(elogInfo.Artist) ? "未找到" : elogInfo.Artist)); Say(" songId:" + ((elogInfo.SongId > 0) ? elogInfo.SongId.ToString() : "未找到")); } Say(""); Say("(诊断结束。若 elog 不存在,网易云进度会退回 SMTC,可能不准。)"); } } internal static class LyricProvider { internal sealed class Entry { public string Lyric = ""; public string Trans = ""; public string Source = ""; public long DurMs; public int Fails; public long NextTryTicks; public bool Busy; } internal static bool Debug; internal static long DurMsHolder; private static readonly object Gate; private static readonly Dictionary<string, Entry> Cache; private static readonly JavaScriptSerializer Json; private static readonly string[] ArtistSep; private static void Dbg(string msg) { if (Debug) { try { Console.WriteLine("[歌词调试] " + msg); } catch { } } } static LyricProvider() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown Gate = new object(); Cache = new Dictionary<string, Entry>(); Json = new JavaScriptSerializer(); ArtistSep = new string[10] { "/", "、", "&", ",", ",", ";", ";", " feat. ", " feat ", " ft. " }; try { Json.MaxJsonLength = 8388608; } catch { } } private static string Key(string platform, string title, string artist) { return platform + "\u0002" + (title ?? "").Trim() + "\u0001" + (artist ?? "").Trim(); } internal static Entry Get(string platform, string title, string artist, long durMs) { if (string.IsNullOrEmpty(title)) { return null; } string key = Key(platform, title, artist); lock (Gate) { if (!Cache.TryGetValue(key, out var value)) { value = new Entry(); Cache[key] = value; } if (!value.Busy && string.IsNullOrEmpty(value.Lyric) && DateTime.UtcNow.Ticks >= value.NextTryTicks) { value.Busy = true; string t = title; string a = artist; string pf = platform; long d = durMs; Entry slot = value; ThreadPool.QueueUserWorkItem(delegate { string lyric = ""; string trans = ""; string source = ""; try { Fetch(pf, t, a, d, out lyric, out trans, out source); } catch { } lock (Gate) { slot.Busy = false; if (!string.IsNullOrEmpty(lyric)) { slot.Lyric = lyric; slot.Trans = trans ?? ""; slot.Source = source ?? "?"; slot.Fails = 0; if (DurMsHolder > 0) { slot.DurMs = DurMsHolder; } } else { slot.Fails++; slot.NextTryTicks = DateTime.UtcNow.AddSeconds((slot.Fails <= 2) ? 8 : 60).Ticks; } } }); } return value; } } internal static Entry GetById(string platform, long songId, string title, string artist, long durMs) { if (songId <= 0) { return Get(platform, title, artist, durMs); } string key = "id:" + songId; lock (Gate) { if (!Cache.TryGetValue(key, out var value)) { value = new Entry(); Cache[key] = value; } if (!value.Busy && string.IsNullOrEmpty(value.Lyric) && DateTime.UtcNow.Ticks >= value.NextTryTicks) { value.Busy = true; string t = title; string a = artist; string pf = platform; long d = durMs; long sid = songId; Entry slot = value; ThreadPool.QueueUserWorkItem(delegate { string lyric = ""; string trans = ""; string source = ""; try { FetchNeteaseById(sid, out lyric, out trans, out source); } catch { } if (string.IsNullOrEmpty(lyric)) { try { Fetch(pf, t, a, d, out lyric, out trans, out source); } catch { } } lock (Gate) { slot.Busy = false; if (!string.IsNullOrEmpty(lyric)) { slot.Lyric = lyric; slot.Trans = trans ?? ""; slot.Source = source ?? "?"; } else { slot.Fails++; slot.NextTryTicks = DateTime.UtcNow.AddSeconds((slot.Fails <= 2) ? 8 : 60).Ticks; } } }); } return value; } } private static void FetchNeteaseById(long songId, out string lyric, out string trans, out string source) { lyric = ""; trans = ""; source = ""; string text = Get("https://music.163.com/api/song/lyric?id=" + songId + "&lv=1&kv=1&tv=-1", "https://music.163.com/", 4000); Dbg("歌词 HTTP " + text.Length + " 字节"); Dictionary<string, object> dictionary = Obj(Json.DeserializeObject(text)); if (dictionary != null) { Dictionary<string, object> d = Obj(Get2(dictionary, "lrc")); Dictionary<string, object> d2 = Obj(Get2(dictionary, "tlyric")); lyric = S(d, "lyric"); trans = S(d2, "lyric"); source = "netease-id:" + songId; } } private static string Get(string url, string referer, int timeoutMs) { HttpWebRequest obj = (HttpWebRequest)WebRequest.Create(url); obj.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; obj.Referer = referer; obj.Timeout = timeoutMs; obj.ReadWriteTimeout = timeoutMs; obj.KeepAlive = false; using HttpWebResponse httpWebResponse = (HttpWebResponse)obj.GetResponse(); using Stream stream = httpWebResponse.GetResponseStream(); using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return streamReader.ReadToEnd(); } private static Dictionary<string, object> Obj(object o) { return o as Dictionary<string, object>; } private static string S(Dictionary<string, object> d, string k) { if (d == null) { return ""; } if (!d.TryGetValue(k, out var value) || value == null) { return ""; } return Convert.ToString(value); } private static long L(Dictionary<string, object> d, string k) { if (d == null) { return 0L; } if (!d.TryGetValue(k, out var value) || value == null) { return 0L; } if (!long.TryParse(Convert.ToString(value), out var result)) { return 0L; } return result; } private static List<object> Arr(Dictionary<string, object> d, string k) { if (d == null) { return null; } if (!d.TryGetValue(k, out var value) || value == null) { return null; } if (value is List<object> result) { return result; } if (value is string) { return null; } if (!(value is IEnumerable enumerable)) { return null; } List<object> list = new List<object>(); foreach (object item in enumerable) { list.Add(item); } return list; } private static string NormName(string s) { if (string.IsNullOrEmpty(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { if (char.IsLetterOrDigit(c) || (c >= '一' && c <= '鿿') || (c >= '\u3040' && c <= 'ヿ')) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static string NormTitle(string s) { if (string.IsNullOrEmpty(s)) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length); int num = 0; foreach (char c in s) { switch (c) { case '(': case '[': case '【': case '(': num++; break; case ')': case ']': case '】': case ')': if (num > 0) { num--; } break; default: if (num <= 0) { stringBuilder.Append(c); } break; } } return NormName(stringBuilder.ToString()); } private static bool ArtistStrict(string wanted, string got) { if (string.IsNullOrEmpty(wanted) || string.IsNullOrEmpty(got)) { return false; } string[] array = got.Split(ArtistSep, StringSplitOptions.RemoveEmptyEntries); string[] array2 = wanted.Split(ArtistSep, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array2.Length; i++) { string text = NormName(array2[i]); if (text.Length < 2) { continue; } string[] array3 = array; for (int j = 0; j < array3.Length; j++) { string text2 = NormName(array3[j]); if (text2 == text) { return true; } if (text.Length >= 3 && text2.Length >= 3 && (text2.Contains(text) || text.Contains(text2))) { return true; } } } return false; } private static int Score(string title, string artist, long durMs, string candTitle, string candArtist, long candDurMs) { string text = NormTitle(title); string text2 = NormTitle(candTitle); if (text.Length == 0 || text2.Length == 0) { return 0; } int num; if (text == text2) { num = 40; } else { if (!text.Contains(text2) && !text2.Contains(text)) { return 0; } num = 15; } if (!string.IsNullOrEmpty(artist) && !string.IsNullOrEmpty(candArtist)) { if (!ArtistStrict(artist, candArtist)) { return 0; } num += 40; } else { num += 10; } if (durMs > 0 && candDurMs > 0) { long num2 = Math.Abs(durMs - candDurMs); if (num2 <= 2000) { num += 25; } else { if (num2 > 6000) { return 0; } num += 8; } } return num; } private static void Fetch(string platform, string title, string artist, long durMs, out string lyric, out string trans, out string source) { lyric = ""; trans = ""; source = ""; bool flag = platform == "kugou"; bool flag2 = platform == "qq"; if (flag2) { try { FetchQQ(title, artist, durMs, out lyric, out source); trans = ""; } catch (Exception ex) { Dbg("QQ异常: " + ex.GetType().Name + " " + ex.Message); } Dbg("QQ结果: " + (string.IsNullOrEmpty(lyric) ? "空" : (lyric.Length + " 字 / " + source))); if (!string.IsNullOrEmpty(lyric)) { return; } } if (flag) { try { FetchKugou(title, artist, durMs, out lyric, out source); trans = ""; } catch (Exception ex2) { Dbg("酷狗异常: " + ex2.GetType().Name + " " + ex2.Message); } Dbg("酷狗结果: " + (string.IsNullOrEmpty(lyric) ? "空" : (lyric.Length + " 字 / " + source))); if (!string.IsNullOrEmpty(lyric)) { return; } } try { FetchNetease(title, artist, durMs, out lyric, out trans, out source); } catch (Exception ex3) { Dbg("网易云异常: " + ex3.GetType().Name + " " + ex3.Message); } Dbg("网易云结果: " + (string.IsNullOrEmpty(lyric) ? "空" : (lyric.Length + " 字 / " + source))); if (!string.IsNullOrEmpty(lyric)) { return; } if (!flag) { try { FetchKugou(title, artist, durMs, out lyric, out source); trans = ""; } catch (Exception ex4) { Dbg("酷狗异常: " + ex4.GetType().Name + " " + ex4.Message); } Dbg("酷狗结果: " + (string.IsNullOrEmpty(lyric) ? "空" : (lyric.Length + " 字 / " + source))); } if (string.IsNullOrEmpty(lyric) && !flag2) { try { FetchQQ(title, artist, durMs, out lyric, out source); trans = ""; } catch (Exception ex5) { Dbg("QQ异常: " + ex5.GetType().Name + " " + ex5.Message); } Dbg("QQ结果: " + (string.IsNullOrEmpty(lyric) ? "空" : (lyric.Length + " 字 / " + source))); } } private static void FetchQQ(string title, string artist, long durMs, out string lyric, out string source) { lyric = ""; source = ""; string text = Uri.EscapeDataString((title + " " + artist).Trim()); string text2 = Get("https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=10&cr=1&t=0&format=json&w=" + text, "https://y.qq.com/", 4000); if (string.IsNullOrEmpty(text2)) { return; } List<object> list = Arr(Obj(Get2(Obj(Get2(Obj(Json.DeserializeObject(text2)), "data")), "song")), "list"); if (list == null) { return; } string text3 = ""; string text4 = ""; int num = 0; foreach (object item in list) { Dictionary<string, object> dictionary = Obj(item); if (dictionary == null) { continue; } string text5 = S(dictionary, "songname"); long num2 = L(dictionary, "interval"); if (num2 > 0 && num2 < 20000) { num2 *= 1000; } string text6 = ""; List<object> list2 = Arr(dictionary, "singer"); if (list2 != null) { List<string> list3 = new List<string>(); foreach (object item2 in list2) { Dictionary<string, object> dictionary2 = Obj(item2); if (dictionary2 != null) { list3.Add(S(dictionary2, "name")); } } text6 = string.Join(" / ", list3.ToArray()); } int num3 = Score(title, artist, durMs, text5, text6, num2); if (num3 > num) { num = num3; text3 = S(dictionary, "songmid"); text4 = text5 + " - " + text6; } } Dbg("QQ候选 " + list.Count + " 首,最佳 " + text3 + " 分=" + num + " " + text4); if (string.IsNullOrEmpty(text3) || num < 60) { return; } string text7 = Get("https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=" + Uri.EscapeDataString(text3) + "&format=json&nobase64=0", "https://y.qq.com/", 4000); if (!string.IsNullOrEmpty(text7)) { int num4 = text7.IndexOf('{'); int num5 = text7.LastIndexOf('}'); if (num4 > 0 && num5 > num4) { text7 = text7.Substring(num4, num5 - num4 + 1); } string text8 = S(Obj(Json.DeserializeObject(text7)), "lyric"); if (!string.IsNullOrEmpty(text8)) { lyric = Encoding.UTF8.GetString(Convert.FromBase64String(text8)); source = "qq:" + text3 + " (" + text4 + " " + num + "分)"; } } } private static void FetchNetease(string title, string artist, long durMs, out string lyric, out string trans, out string source) { lyric = ""; trans = ""; source = ""; string text = Uri.EscapeDataString((title + " " + artist).Trim()); string text2 = Get("https://music.163.com/api/cloudsearch/pc?s=" + text + "&type=1&limit=8", "https://music.163.com/", 4000); Dbg("搜索 HTTP " + text2.Length + " 字节"); Dictionary<string, object> dictionary = Obj(Json.DeserializeObject(text2)); if (dictionary == null) { Dbg("搜索 JSON 解析为 null"); return; } Dbg("搜索 code=" + L(dictionary, "code")); if (L(dictionary, "code") == -462) { return; } List<object> list = Arr(Obj(Get2(dictionary, "result")), "songs"); if (list == null) { return; } long num = 0L; int num2 = 0; string text3 = ""; foreach (object item in list) { Dictionary<string, object> dictionary2 = Obj(item); if (dictionary2 == null) { continue; } string text4 = S(dictionary2, "name"); long num3 = L(dictionary2, "dt"); if (num3 <= 0) { num3 = L(dictionary2, "duration"); } List<object> list2 = Arr(dictionary2, "ar"); if (list2 == null) { list2 = Arr(dictionary2, "artists"); } string text5 = ""; if (list2 != null) { List<string> list3 = new List<string>(); foreach (object item2 in list2) { Dictionary<string, object> dictionary3 = Obj(item2); if (dictionary3 != null) { list3.Add(S(dictionary3, "name")); } } text5 = string.Join(" / ", list3.ToArray()); } int num4 = Score(title, artist, durMs, text4, text5, num3); if (num4 > num2) { num2 = num4; num = L(dictionary2, "id"); text3 = text4 + " - " + text5; } } Dbg("候选 " + list.Count + " 首,最佳 " + num + " 分=" + num2 + " " + text3); if (num == 0L || num2 < 60) { return; } string text6 = Get("https://music.163.com/api/song/lyric?id=" + num + "&lv=1&kv=1&tv=-1", "https://music.163.com/", 4000); Dbg("歌词 HTTP " + text6.Length + " 字节"); Dictionary<string, object> dictionary4 = Obj(Json.DeserializeObject(text6)); if (dictionary4 == null) { return; } Dictionary<string, object> d = Obj(Get2(dictionary4, "lrc")); Dictionary<string, object> d2 = Obj(Get2(dictionary4, "tlyric")); lyric = S(d, "lyric"); trans = S(d2, "lyric"); source = "netease:" + num + " (" + text3 + " " + num2 + "分)"; foreach (object item3 in list) { Dictionary<string, object> dictionary5 = Obj(item3); if (dictionary5 != null && L(dictionary5, "id") == num) { long num5 = L(dictionary5, "dt"); if (num5 <= 0) { num5 = L(dictionary5, "duration"); } if (num5 > 0) { DurMsHolder = num5; } break; } } } private static void FetchKugou(string title, string artist, long durMs, out string lyric, out string source) { lyric = ""; source = ""; string text = Uri.EscapeDataString((title + " " + artist).Trim()); string text2 = Get("https://krcs.kugou.com/search?ver=1&man=yes&client=mobi&keyword=" + text + "&duration=" + durMs, "https://www.kugou.com/", 4000); Dictionary<string, object> dictionary = Obj(Json.DeserializeObject(text2)); if (dictionary == null) { return; } List<object> list = Arr(dictionary, "candidates"); if (list == null) { return; } string text3 = ""; string stringToEscape = ""; string text4 = ""; int num = 0; foreach (object item in list) { Dictionary<string, object> dictionary2 = Obj(item); if (dictionary2 != null) { string text5 = S(dictionary2, "song"); string text6 = S(dictionary2, "singer"); long num2 = L(dictionary2, "duration"); if (num2 > 0 && num2 < 10000) { num2 *= 1000; } int num3 = Score(title, artist, durMs, text5, text6, num2); if (num3 > num) { num = num3; text3 = S(dictionary2, "id"); stringToEscape = S(dictionary2, "accesskey"); text4 = text5 + " - " + text6; } } } if (!string.IsNullOrEmpty(text3) && num >= 60) { string text7 = Get("https://lyrics.kugou.com/download?ver=1&client=pc&id=" + Uri.EscapeDataString(text3) + "&accesskey=" + Uri.EscapeDataString(stringToEscape) + "&fmt=lrc&charset=utf8", "https://www.kugou.com/", 4000); string text8 = S(Obj(Json.DeserializeObject(text7)), "content"); if (!string.IsNullOrEmpty(text8)) { lyric = Encoding.UTF8.GetString(Convert.FromBase64String(text8)); source = "kugou:" + text3 + " (" + text4 + " " + num + "分)"; } } } private static object Get2(Dictionary<string, object> d, string k) { if (d == null || !d.TryGetValue(k, out var value)) { return null; } return value; } } internal static class MemScan { private struct MEMORY_BASIC_INFORMATION { public IntPtr BaseAddress; public IntPtr AllocationBase; public uint AllocationProtect; public uint Alignment1; public IntPtr RegionSize; public uint State; public uint Protect; public uint Type; public uint Alignment2; } private sealed class Hit { public long Addr; public bool IsFloat; public double Value; public string Note = ""; } private const uint PROCESS_QUERY_INFORMATION = 1024u; private const uint PROCESS_VM_READ = 16u; private const uint MEM_COMMIT = 4096u; private const uint MEM_PRIVATE = 131072u; private const uint PAGE_GUARD = 256u; private const uint PAGE_NOACCESS = 1u; private const int CHUNK = 8388608; private const int GAP_MS = 120; [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint access, bool inherit, int pid); [DllImport("kernel32.dll", SetLastError = true)] private static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, IntPtr dwLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, IntPtr nSize, out IntPtr lpRead); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr h); internal static int Run(string procName, long durationMs) { if (durationMs <= 0) { durationMs = 600000L; } Process process = null; Process[] processesByName = Process.GetProcessesByName(procName); foreach (Process process2 in processesByName) { try { if (process == null || process2.WorkingSet64 > process.WorkingSet64) { process = process2; } } catch { } } if (process == null) { Console.WriteLine("找不到进程:" + procName); return 2; } Console.WriteLine("目标进程:" + process.ProcessName + " (pid " + process.Id + "),内存 " + process.WorkingSet64 / 1024 / 1024 + " MB"); Console.WriteLine("时长上限:" + durationMs + " ms,差分间隔 " + 120 + " ms"); Console.WriteLine(); IntPtr intPtr = OpenProcess(1040u, inherit: false, process.Id); if (intPtr == IntPtr.Zero) { Console.WriteLine("打不开进程(权限不足或被保护),错误码 " + Marshal.GetLastWin32Error()); return 3; } List<Hit> list = new List<Hit>(); long num = 0L; int num2 = 0; Stopwatch stopwatch = Stopwatch.StartNew(); try { long num3 = 65536L; long num4 = 140737488355327L; MEMORY_BASIC_INFORMATION lpBuffer; while (num3 < num4 && VirtualQueryEx(intPtr, new IntPtr(num3), out lpBuffer, new IntPtr(Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)))) != 0) { long num5 = lpBuffer.BaseAddress.ToInt64(); long num6 = lpBuffer.RegionSize.ToInt64(); if (num6 <= 0) { num3 = num5 + 4096; continue; } bool flag = (lpBuffer.Protect & 4) != 0 || (lpBuffer.Protect & 8) != 0 || (lpBuffer.Protect & 0x80) != 0 || (lpBuffer.Protect & 0x40) != 0; if (lpBuffer.State == 4096 && lpBuffer.Type == 131072 && flag && (lpBuffer.Protect & 1) == 0 && (lpBuffer.Protect & 0x100) == 0) { long num7 = num5 + num6; for (long num8 = num5; num8 < num7; num8 += 8388608) { int num9 = (int)Math.Min(8388608L, num7 - num8); if (num9 < 8) { continue; } byte[] array = new byte[num9]; byte[] array2 = new byte[num9]; if (!ReadProcessMemory(intPtr, new IntPtr(num8), array, new IntPtr(num9), out var lpRead)) { continue; } int num10 = lpRead.ToInt32(); if (num10 < num9) { num9 = num10; if (num9 < 8) { continue; } } Thread.Sleep(120); if (ReadProcessMemory(intPtr, new IntPtr(num8), array2, new IntPtr(num9), out lpRead)) { int num11 = Math.Min(num9, lpRead.ToInt32()); CompareChunk(list, array, array2, num11, num8, durationMs); num += num11; if (++num2 % 25 == 0) { Console.WriteLine(" …已扫 " + num / 1024 / 1024 + " MB / 候选 " + list.Count); } } } } long num12 = num5 + num6; if (num12 <= num3) { num12 = num3 + 4096; } num3 = num12; } Console.WriteLine("扫描完成:" + num / 1024 / 1024 + " MB,用时 " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + "s,候选 " + list.Count + " 个"); if (list.Count > 0) { Console.WriteLine("等待 2 秒做二次验证……"); Thread.Sleep(2000); List<Hit> list2 = new List<Hit>(); foreach (Hit item in list) { byte[] array3 = new byte[4]; if (ReadProcessMemory(intPtr, new IntPtr(item.Addr), array3, new IntPtr(4), out var lpRead2) && lpRead2.ToInt32() >= 4) { double num13 = (item.IsFloat ? ((double)BitConverter.ToSingle(array3, 0) * 1000.0) : ((double)BitConverter.ToInt32(array3, 0))); double num14 = num13 - item.Value; if (Math.Abs(num14 - 2000.0) <= 400.0) { item.Note = "Δ2s=" + num14.ToString("F0") + "ms"; item.Value = num13; list2.Add(item); } } } list = list2; Console.WriteLine("二次验证通过:" + list.Count + " 个"); } } finally { CloseHandle(intPtr); } Console.WriteLine(); if (list.Count == 0) { Console.WriteLine("× 没找到候选。可能原因:进程受保护 / 位置不在可读区 / 差分窗口不合适。"); return 1; } Console.WriteLine("═══ 候选地址(按值排序)═══"); list.Sort((Hit x, Hit y) => x.Value.CompareTo(y.Value)); int num15 = Math.Min(list.Count, 20); for (int num16 = 0; num16 < num15; num16++) { Hit hit = list[num16]; Console.WriteLine(" 0x" + hit.Addr.ToString("X") + " " + (hit.IsFloat ? "float " : "int32 ") + (hit.Value / 1000.0).ToString("F2") + "s " + hit.Note); } return 0; } internal static int Peek(string procName, long addr, int seconds) { Process process = null; Process[] processesByName = Process.GetProcessesByName(procName); foreach (Process process2 in processesByName) { try { if (process == null || process2.WorkingSet64 > process.WorkingSet64) { process = process2; } } catch { } } if (process == null) { Console.WriteLine("找不到进程:" + procName); return 2; } IntPtr intPtr = OpenProcess(1040u, inherit: false, process.Id); if (intPtr == IntPtr.Zero) { Console.WriteLine("打不开进程,错误码 " + Marshal.GetLastWin32Error()); return 3; } Console.WriteLine("盯着 0x" + addr.ToString("X") + "(" + process.ProcessName + " pid " + process.Id + "),每 0.5 秒一次,共 " + seconds + " 秒"); DateTime dateTime = DateTime.Now.AddSeconds(seconds); try { while (DateTime.Now < dateTime) { byte[] array = new byte[4]; string text = "读失败"; if (ReadProcessMemory(intPtr, new IntPtr(addr), array, new IntPtr(4), out var lpRead) && lpRead.ToInt32() == 4) { int num = BitConverter.ToInt32(array, 0); float num2 = BitConverter.ToSingle(array, 0); text = ((double)num / 1000.0).ToString("F2") + "s (int " + num + " / float " + num2.ToString("F3") + ")"; } Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " " + text); Console.Out.Flush(); Thread.Sleep(500); } } finally { CloseHandle(intPtr); } return 0; } private static void CompareChunk(List<Hit> hits, byte[] a, byte[] b, int len, long baseAddr, long durationMs) { int num = (int)Math.Min(2147483647L, durationMs); double num2 = (double)durationMs / 1000.0; for (int i = 0; i + 4 <= len; i += 4) { int num3 = BitConverter.ToInt32(a, i); int num4 = BitConverter.ToInt32(b, i); if (num3 >= 0 && num3 <= num) { int num5 = num4 - num3; if (num5 >= 70 && num5 <= 190) { hits.Add(new Hit { Addr = baseAddr + i, IsFloat = false, Value = num4 }); continue; } } float num6 = BitConverter.ToSingle(a, i); float num7 = BitConverter.ToSingle(b, i); if (num6 >= 0f && (double)num6 <= num2 && !float.IsNaN(num6) && !float.IsInfinity(num6)) { double num8 = num7 - num6; if (num8 >= 0.07 && num8 <= 0.19) { hits.Add(new Hit { Addr = baseAddr + i, IsFloat = true, Value = (double)num7 * 1000.0 }); } } } } } internal static class Program { private sealed class Snap { public string App = ""; public string Platform = "other"; public string Title = ""; public string Artist = ""; public string Album = ""; public string Status = "closed"; public string CoverB64 = ""; public long Pos; public long Dur; public bool Music; public bool Playing; } private static bool IsMusicApp(string app) { if (string.IsNullOrEmpty(app)) { return false; } string text = app.ToLowerInvariant(); if (!text.Contains("cloudmusic") && !text.Contains("netease") && !text.Contains("qqmusic") && !text.Contains("kugou")) { return text.Contains("kgmusic"); } return true; } private static string PlatformOf(string app) { if (string.IsNullOrEmpty(app)) { return "other"; } string text = app.ToLowerInvariant(); if (text.Contains("cloudmusic") || text.Contains("netease")) { return "netease"; } if (text.Contains("qqmusic")) { return "qq"; } if (text.Contains("kugou") || text.Contains("kgmusic")) { return "kugou"; } return "other"; } private static bool Has(string[] args, string flag) { for (int i = 0; i < args.Length; i++) { if (string.Equals(args[i], flag, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static string ValueOf(string[] args, string flag) { for (int i = 0; i < args.Length; i++) { if (string.Equals(args[i], flag, StringComparison.OrdinalIgnoreCase)) { if (i + 1 >= args.Length) { return null; } return args[i + 1]; } string text = flag + "="; if (args[i].StartsWith(text, StringComparison.OrdinalIgnoreCase)) { return args[i].Substring(text.Length); } } return null; } internal static void WriteAtomic(string path, string text) { try { string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } string text2 = path + ".tmp"; byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(text); using (FileStream fileStream = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.None)) { fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(path)) { try { File.Replace(text2, path, null); return; } catch { } } File.Copy(text2, path, overwrite: true); try { File.Delete(text2); } catch { } } catch { } } internal static string Esc(string s) { if (s == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 8); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\t': stringBuilder.Append("\\t"); continue; case '\r': continue; } if (c < ' ') { stringBuilder.Append(' '); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static async Task<bool> AnyPlaying(GlobalSystemMediaTransportControlsSessionManager mgr) { try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); foreach (GlobalSystemMediaTransportControlsSession session in mgr.GetSessions()) { if (!IsMusicApp(session.SourceAppUserModelId ?? "")) { continue; } GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo(); if (playbackInfo != null && (int)playbackInfo.PlaybackStatus == 4) { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>(session.TryGetMediaPropertiesAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; taskAwaiter = taskAwaiter2; taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); } GlobalSystemMediaTransportControlsSessionMediaProperties result = taskAwaiter.GetResult(); if (result != null && !string.IsNullOrEmpty(result.Title)) { return true; } } } } catch { } return false; } private static async Task<int> Main(string[] args) { bool probe = Has(args, "--probe"); bool once = Has(args, "--once") || probe; bool pretty = Has(args, "--pretty") || probe; bool all = Has(args, "--all"); bool withCover = Has(args, "--cover"); bool quiet = Has(args, "--quiet"); string outFile = ValueOf(args, "--file"); int interval = 400; string text = ValueOf(args, "--interval"); if (!string.IsNullOrEmpty(text)) { int.TryParse(text, out interval); } if (interval < 100) { interval = 100; } if (interval > 5000) { interval = 5000; } if (!string.IsNullOrEmpty(outFile)) { try { outFile = Path.GetFullPath(outFile); } catch { outFile = null; } } try { Console.OutputEncoding = Encoding.UTF8; } catch { } if (Has(args, "--lyric-debug")) { LyricProvider.Debug = true; string text2 = null; string[] array = args; foreach (string text3 in array) { if (!text3.StartsWith("--")) { text2 = text3; break; } } if (string.IsNullOrEmpty(text2)) { Console.WriteLine("用法: --lyric-debug \"歌名|歌手|时长ms\""); return 1; } string[] array2 = text2.Split(new char[1] { '|' }); LyricProvider.Entry e2 = LyricProvider.Get((array2.Length > 3) ? array2[3] : "netease", array2[0], (array2.Length > 1) ? array2[1] : "", (array2.Length > 2) ? long.Parse(array2[2]) : 0); for (int j = 0; j < 50; j++) { if (!string.IsNullOrEmpty(e2.Lyric)) { break; } await Task.Delay(200); } Console.WriteLine(string.IsNullOrEmpty(e2.Lyric) ? "MISS" : ("HIT " + e2.Lyric.Length + " 字 / " + e2.Source)); return 0; } if (Has(args, "--lyric-test")) { string text4 = null; string[] array = args; foreach (string text5 in array) { if (!text5.StartsWith("--")) { text4 = text5; break; } } if (string.IsNullOrEmpty(text4)) { Console.WriteLine("用法: PeakMusic.NowPlaying.exe --lyric-test \"歌名|歌手|时长ms\""); return 1; } string[] array3 = text4.Split(new char[1] { '|' }); string t0 = ((array3.Length != 0) ? array3[0] : ""); string a0 = ((array3.Length > 1) ? array3[1] : ""); long durMs = ((array3.Length > 2) ? long.Parse(array3[2]) : 0); LyricProvider.Entry e2 = LyricProvider.Get("netease", t0, a0, durMs); for (int j = 0; j < 50; j++) { if (!string.IsNullOrEmpty(e2.Lyric)) { break; } await Task.Delay(200); } if (string.IsNullOrEmpty(e2.Lyric)) { Console.WriteLine("MISS " + t0 + " - " + a0); return 3; } Console.WriteLine("HIT " + t0 + " - " + a0); Console.WriteLine("来源 " + e2.Source); Console.WriteLine("原文 " + e2.Lyric.Length + " 字符, 翻译 " + e2.Trans.Length + " 字符"); string[] array4 = e2.Lyric.Replace("\r", "").Split(new char[1] { '\n' }); for (int k = 0; k < array4.Length && k < 6; k++) { Console.WriteLine(" " + array4[k]); } return 0; } if (Has(args, "--diag")) { Compat.Diagnose(); return 0; } if (Has(args, "--peek")) { List<string> list = new List<string>(); for (int l = 0; l < args.Length; l++) { if (!string.Equals(args[l], "--peek", StringComparison.OrdinalIgnoreCase) && !args[l].StartsWith("--")) { list.Add(args[l]); } } string procName = ((list.Count > 0) ? list[0] : "KuGou"); long result = 0L; if (list.Count > 1) { string text6 = list[1]; if (text6.StartsWith("0x") || text6.StartsWith("0X")) { long.TryParse(text6.Substring(2), NumberStyles.HexNumber, null, out result); } else { long.TryParse(text6, out result); } } return MemScan.Peek(seconds: (list.Count > 2) ? int.Parse(list[2]) : 20, procName: procName, addr: result); } if (Has(args, "--scan-mem")) { List<string> list2 = new List<string>(); for (int m = 0; m < args.Length; m++) { if (!string.Equals(args[m], "--scan-mem", StringComparison.OrdinalIgnoreCase) && !args[m].StartsWith("--")) { list2.Add(args[m]); } } string procName2 = ((list2.Count > 0) ? list2[0] : "KuGou"); long result2 = 0L; if (list2.Count > 1) { long.TryParse(list2[1], out result2); } return MemScan.Run(procName2, result2); } if (Has(args, "--watch")) { int result3 = 60; string text7 = ValueOf(args, "--watch"); if (!string.IsNullOrEmpty(text7)) { int.TryParse(text7, out result3); } return await Compat.WatchAsync(result3); } bool flag = probe || Has(args, "--once") || Has(args, "--json") || Has(args, "--lyric-test") || Has(args, "--diag") || Has(args, "--file") || Has(args, "--watch") || Has(args, "--scan-mem") || Has(args, "--peek"); if (Has(args, "--follow") || !flag) { string outFile2 = ValueOf(args, "--out") ?? outFile; int result4 = interval; string text8 = ValueOf(args, "--interval"); if (!string.IsNullOrEmpty(text8)) { int.TryParse(text8, out result4); } if (result4 < 100) { result4 = 100; } if (result4 > 5000) { result4 = 5000; } return await Compat.RunAsync(outFile2, result4, Has(args, "--verbose"), Has(args, "--strict")); } GlobalSystemMediaTransportControlsSessionManager mgr = null; string err = null; TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager>); for (int j = 0; j < 6; j++) { if (mgr != null) { break; } try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionManager>(GlobalSystemMediaTransportControlsSessionManager.RequestAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; taskAwaiter = taskAwaiter2; taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionManager>); } GlobalSystemMediaTransportControlsSessionManager result5 = taskAwaiter.GetResult(); mgr = result5; } catch (Exception ex) { err = ex.Message; } if (mgr == null) { await Task.Delay(400); } } if (mgr == null) { if (pretty) { Console.WriteLine("× 读不到系统媒体控制(需要 Windows 10 1809 或更高)。" + ((err != null) ? (" 原因:" + err) : "")); } else { Console.WriteLine("{\"ok\":false,\"error\":\"no-manager\"}"); Console.Out.Flush(); } return 2; } if (probe) { Console.WriteLine("正在等播放器出声(最多 12 秒)…… 现在去点一首歌,或先让网易云放起来。"); Console.WriteLine(); DateTime deadline = DateTime.UtcNow.AddSeconds(12.0); while (DateTime.UtcNow < deadline && !(await AnyPlaying(mgr))) { await Task.Delay(500); } } do { string text9 = ((!pretty) ? (await Json(mgr, all, withCover)) : (await Pretty(mgr, all))); string text10 = text9; if (!string.IsNullOrEmpty(outFile)) { WriteAtomic(outFile, text10); } if (!quiet) { Console.WriteLine(text10); Console.Out.Flush(); } if (!once) { await Task.Delay(interval); } } while (!once); return 0; } private static string PickAppId(string app) { if (string.IsNullOrEmpty(app)) { return "?"; } int num = app.LastIndexOfAny(new char[3] { '\\', '/', '!' }); if (num < 0 || num >= app.Length - 1) { return app; } return app.Substring(num + 1); } private static string Nvl(string s) { if (!string.IsNullOrEmpty(s)) { return s; } return "(空)"; } private static string StatusOf(GlobalSystemMediaTransportControlsSessionPlaybackInfo info) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected I4, but got Unknown if (info == null) { return "未知"; } GlobalSystemMediaTransportControlsSessionPlaybackStatus playbackStatus = info.PlaybackStatus; return (playbackStatus - 3) switch { 1 => "正在播放", 2 => "暂停", 0 => "已停止", _ => "已关闭", }; } private static string Clock(TimeSpan t) { if (t.TotalHours >= 1.0) { return (int)t.TotalHours + ":" + t.ToString("mm\\:ss"); } return t.ToString("mm\\:ss"); } private static async Task<string> Pretty(GlobalSystemMediaTransportControlsSessionManager mgr, bool all) { StringBuilder sb = new StringBuilder(); IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions = mgr.GetSessions(); sb.AppendLine("系统里有 " + sessions.Count + " 个媒体会话:"); bool anyMusic = false; bool anyPlaying = false; TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); foreach (GlobalSystemMediaTransportControlsSession item in sessions) { string app = item.SourceAppUserModelId ?? ""; bool flag = IsMusicApp(app); if (flag) { anyMusic = true; } GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = item.GetPlaybackInfo(); GlobalSystemMediaTransportControlsSessionTimelineProperties tl = item.GetTimelineProperties(); string text = StatusOf(playbackInfo); if (flag && text == "正在播放") { anyPlaying = true; } if (!all && !flag) { continue; } sb.AppendLine(); sb.AppendLine((flag ? "★ " : " ") + PickAppId(app) + " [" + text + "]"); try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>(item.TryGetMediaPropertiesAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; taskAwaiter = taskAwaiter2; taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); } GlobalSystemMediaTransportControlsSessionMediaProperties result = taskAwaiter.GetResult(); if (result != null) { sb.AppendLine(" 歌曲:" + Nvl(result.Title)); sb.AppendLine(" 歌手:" + Nvl(result.Artist)); if (!string.IsNullOrEmpty(result.AlbumTitle)) { sb.AppendLine(" 专辑:" + result.AlbumTitle); } } } catch (Exception ex) { sb.AppendLine(" (读不到歌曲信息:" + ex.Message + ")"); } if (tl != null && tl.EndTime.TotalMilliseconds > 0.0) { sb.AppendLine(" 进度:" + Clock(tl.Position) + " / " + Clock(tl.EndTime)); } } sb.AppendLine(); if (anyPlaying) { sb.AppendLine("==> 结论:能读到播放器,方案可行。"); } else if (anyMusic) { sb.AppendLine("==> 结论:能看到网易云/QQ音乐,但现在没在放歌(或暂停中)。"); } else { sb.AppendLine("==> 结论:没发现网易云/QQ音乐。可能没装、没开,或版本太旧不上报系统媒体控制。"); } return sb.ToString().TrimEnd(Array.Empty<char>()); } private static async Task<string> Json(GlobalSystemMediaTransportControlsSessionManager mgr, bool all, bool cover) { List<Snap> snaps = new List<Snap>(); Snap best = null; TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); foreach (GlobalSystemMediaTransportControlsSession session in mgr.GetSessions()) { string app = session.SourceAppUserModelId ?? ""; bool music = IsMusicApp(app); if (!all && !music) { continue; } Snap snap = new Snap { App = app, Music = music, Platform = PlatformOf(app) }; GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo(); GlobalSystemMediaTransportControlsSessionTimelineProperties tl = session.GetTimelineProperties(); if (playbackInfo != null) { GlobalSystemMediaTransportControlsSessionPlaybackStatus playbackStatus = playbackInfo.PlaybackStatus; switch (playbackStatus - 3) { case 1: snap.Status = "playing"; snap.Playing = true; break; case 2: snap.Status = "paused"; break; case 0: snap.Status = "stopped"; break; } } try { TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>(session.TryGetMediaPropertiesAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; taskAwaiter = taskAwaiter2; taskAwaiter2 = default(TaskAwaiter<GlobalSystemMediaTransportControlsSessionMediaProperties>); } GlobalSystemMediaTransportControlsSessionMediaProperties result = taskAwaiter.GetResult(); if (result != null) { snap.Title = result.Title ?? ""; snap.Artist = result.Artist ?? ""; snap.Album = result.AlbumTitle ?? ""; if (cover && result.Thumbnail != null) { Snap snap2 = snap; snap2.CoverB64 = await ReadCover(result.Thumbnail); } } } catch { } if (tl != null) { snap.Pos = (long)tl.Position.TotalMilliseconds; snap.Dur = (long)tl.EndTime.TotalMilliseconds; } snaps.Add(snap); if (music && snap.Playing && snap.Title.Length > 0) { best = snap; } } if (best == null && snaps.Count > 0) { best = snaps[0]; } LyricProvider.Entry entry = ((best != null) ? LyricProvider.Get(best.Platform, best.Title, best.Artist, best.Dur) : null); List<string> list = new List<string>(snaps.Count); int num = -1; for (int i = 0; i < snaps.Count; i++) { Snap snap3 = snaps[i]; bool num2 = snap3 == best; if (num2) { num = i; } int num3 = 384; if (num2 && entry != null && entry.Lyric.Length > 0) { num3 += entry.Lyric.Length + entry.Trans.Length; } StringBuilder stringBuilder = new StringBuilder(num3); stringBuilder.Append("{\"app\":\"").Append(Esc(snap3.App)).Append('"'); stringBuilder.Append(",\"platform\":\"").Append(snap3.Platform).Append('"'); stringBuilder.Append(",\"music\":").Append(snap3.Music ? "true" : "false"); stringBuilder.Append(",\"title\":\"").Append(Esc(snap3.Title)).Append('"'); stringBuilder.Append(",\"artist\":\"").Append(Esc(snap3.Artist)).Append('"'); stringBuilder.Append(",\"album\":\"").Append(Esc(snap3.Album)).Append('"'); stringBuilder.Append(",\"status\":\"").Append(snap3.Status).Append('"'); stringBuilder.Append(",\"pos\":").Append(snap3.Pos); stringBuilder.Append(",\"dur\":").Append(snap3.Dur); if (num2 && entry != null && entry.Lyric.Length > 0) { stringBuilder.Append(",\"lyric\":\"").Append(Esc(entry.Lyric)).Append('"'); if (entry.Trans.Length > 0) { stringBuilder.Append(",\"tlyric\":\"").Append(Esc(entry.Trans)).Append('"'); } stringBuilder.Append(",\"lyricSource\":\"").Append(Esc(entry.Source)).Append('"'); } if (snap3.CoverB64.Length > 0) { stringBuilder.Append(",\"cover\":\"").Append(snap3.CoverB64).Append('"'); } stringBuilder.Append('}'); list.Add(stringBuilder.ToString()); } StringBuilder stringBuilder2 = new StringBuilder(800); stringBuilder2.Append("{\"ok\":true,\"pid\":").Append(Process.GetCurrentProcess().Id); stringBuilder2.Append(",\"ts\":").Append(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); stringBuilder2.Append(",\"now\":"); stringBuilder2.Append((num >= 0) ? list[num] : "null"); stringBuilder2.Append(",\"count\":").Append(list.Count); stringBuilder2.Append(",\"all\":["); stringBuilder2.Append(string.Join(",", list.ToArray())); stringBuilder2.Append("]}"); return stringBuilder2.ToString(); } private static async Task<string> ReadCover(IRandomAccessStreamReference thumb) { _ = 1; try { TaskAwaiter<IRandomAccessStreamWithContentType> taskAwaiter = WindowsRuntimeSystemExtensions.GetAwaiter<IRandomAccessStreamWithContentType>(thumb.OpenReadAsync()); if (!taskAwaiter.IsCompleted) { await taskAwaiter; TaskAwaiter<IRandomAccessStreamWithContentType> taskAwaiter2 = default(TaskAwaiter<IRandomAccessStreamWithContentType>); taskAwaiter = taskAwaiter2; } IRandomAccessStreamWithContentType result = taskAwaiter.GetResult(); IRandomAccessStreamWithContentType stream = result; try { if (stream == null || ((IRandomAccessStream)stream).Size == 0L || ((IRandomAccessStream)stream).Size > 4194304) { return ""; } MemoryStream ms = new MemoryStream((int)((IRandomAccessStream)stream).Size); using (Stream read = WindowsRuntimeStreamExtensions.AsStreamForRead((IInputStream)(object)stream)) { await read.CopyToAsync(ms); } return Convert.ToBase64String(ms.ToArray()); } finally { ((IDisposable)stream)?.Dispose(); } } catch { return ""; } } } }
plugins/PeakMusic/PeakMusic.UI.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; 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 System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using PeakMusic.Core; using PeakMusic.Core.Netease; using PeakMusic.Core.Sources; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("PeakMusic.UI")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.4.4.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PeakMusic.UI")] [assembly: AssemblyTitle("PeakMusic.UI")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.4.4.0")] [module: UnverifiableCode] [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 PeakMusic.UI { internal static class Draw { internal static bool Enabled = true; private static bool _faulted; private static bool _prevClick; private static readonly Dictionary<string, bool> _edgeDown = new Dictionary<string, bool>(8); internal static bool Prefix() { try { Overlay.Draw(); } catch { } if (!Enabled || _faulted) { return true; } if (!R.Ready) { return true; } try { Render(); return false; } catch (Exception ex) { _faulted = true; Plugin.Log.LogError((object)("[UI] 新界面绘制异常,本次运行退回经典界面:" + ex)); return true; } } private static void Render() { //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Invalid comparison between Unknown and I4 //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) LibrarySync.Tick(); LibraryPage.Tick(); if (!R.G("_open", fallback: false)) { return; } R.Call("EnsureStyles"); Skin.Refresh(); Event current = Event.current; bool num = current == null || (int)current.type == 7; Rect val = R.RectOf("_rPanel"); bool flag = R.G("_collapsed", fallback: false); float num2 = R.G("_scale", 1f); Vector2 val2 = R.VecOf("_panelPos"); Matrix4x4 matrix = GUI.matrix; GUI.matrix = Matrix4x4.TRS(new Vector3(val2.x, val2.y, 0f), Quaternion.identity, new Vector3(num2, num2, 1f)); if (num) { Skin.Shadow(val, 5f); GUI.Box(val, GUIContent.none, Skin.Panel); Chrome(flag); if (!flag) { Sidebar(); MainArea(); Footer(); } } GUI.matrix = matrix; if (!R.G("_loginOpen", fallback: false)) { return; } Vector2 val3 = R.VecOf("_lm"); R.S("_lm", R.VecOf("_mousePos")); try { R.Call("DrawLogin"); } finally { R.S("_lm", val3); } } private static void Chrome(bool collapsed) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) Rect val = R.RectOf("_rGrip"); GUI.Label(new Rect(((Rect)(ref val)).x + 8f, ((Rect)(ref val)).y, ((Rect)(ref val)).width - 90f, ((Rect)(ref val)).height), "PEAK MUSIC", Skin.T(10, Skin.TextFaint, (TextAnchor)3, bold: true)); if (!collapsed) { SongInfo val2 = R.Current(); if (val2 != null) { GUI.Label(new Rect(((Rect)(ref val)).x + 96f, ((Rect)(ref val)).y, ((Rect)(ref val)).width - 100f, ((Rect)(ref val)).height), val2.Name, Skin.T(10, Skin.TextFaint, (TextAnchor)3)); } } TopBtn(R.RectOf("_rZoomOut"), "−"); TopBtn(R.RectOf("_rZoomIn"), "+"); TopBtn(R.RectOf("_rCollapse"), collapsed ? "▸" : "▾"); TopBtn(R.RectOf("_rClose"), "×"); } private static void TopBtn(Rect r, string text) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (!(((Rect)(ref r)).width <= 0f)) { bool flag = Hover(r); GUI.Box(r, GUIContent.none, flag ? Skin.BtnOn : Skin.Btn); GUI.Label(r, text, Skin.T(12, flag ? Skin.TextMain : Skin.TextDim, (TextAnchor)4)); } } private static void Sidebar() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) GUI.Box(R.RectOf("_rNav"), GUIContent.none, Skin.SideBox); Rect[] array = R.G<Rect[]>("_rNavItem"); Platform val = R.G<Platform>("_platform", (Platform)0); if (array != null) { for (int i = 0; i < array.Length && i < Platforms.All.Length; i++) { Platform val2 = Platforms.All[i]; bool flag = val2 == val; bool flag2 = Hover(array[i]); GUI.Box(array[i], GUIContent.none, flag ? Skin.NavOn : (flag2 ? Skin.NavOff : Skin.NavOff)); if (flag) { Fill(new Rect(((Rect)(ref array[i])).x, ((Rect)(ref array[i])).y + 9f, 3f, ((Rect)(ref array[i])).height - 18f), Skin.Accent); } Fill(new Rect(((Rect)(ref array[i])).x + 16f, ((Rect)(ref array[i])).y + ((Rect)(ref array[i])).height * 0.5f - 4f, 8f, 8f), R.BrandColor(val2)); GUI.Label(new Rect(((Rect)(ref array[i])).x + 32f, ((Rect)(ref array[i])).y, ((Rect)(ref array[i])).width - 38f, ((Rect)(ref array[i])).height), Platforms.DisplayName(val2), flag ? Skin.NavText : Skin.NavTextOff); if (flag) { GUI.Label(new Rect(((Rect)(ref array[i])).xMax - 46f, ((Rect)(ref array[i])).y, 38f, ((Rect)(ref array[i])).height), "●", Skin.T(8, Skin.Accent, (TextAnchor)5)); } } } int num = R.G("_view", 0); List<SongInfo> list = R.ListOf("_favorites"); SideItem(R.RectOf("_rFavNav"), "♥", "我的收藏", list?.Count ?? 0, num == 1); SideItem(R.RectOf("_rQueueNav"), "♪", "一起听", 0, num == 2); float num2 = 266f; GUI.Label(new Rect(20f, num2, 184f, 14f), "歌词", Skin.T(10, Skin.TextDim, (TextAnchor)3, bold: true)); LyricBtn(R.RectOf("_rLyricEdge"), "边缘 " + LyricConfig.Edge); LyricBtn(R.RectOf("_rLyricHeight"), "高度 " + LyricConfig.Y); LyricBtn(R.RectOf("_rLyricSize"), "字号 " + LyricConfig.Size); LyricBtn(R.RectOf("_rLyricDist"), "距离 " + LyricConfig.Distance); LyricBtn(R.RectOf("_rLyricColor"), "颜色 " + LyricConfig.Color); LyricBtn(R.RectOf("_rLyricPos"), "开关 " + LyricConfig.Enabled); LyricBtn(R.RectOf("_rLyricBright"), "亮度 " + LyricConfig.Brightness); LibraryPage.DrawSidebar(); AccountCard(); } private static void SideItem(Rect r, string icon, string title, int count, bool active) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (!(((Rect)(ref r)).width <= 0f)) { bool flag = Hover(r); GUI.Box(r, GUIContent.none, active ? Skin.NavOn : Skin.NavOff); if (active) { Fill(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y + 8f, 3f, ((Rect)(ref r)).height - 16f), Skin.Accent); } GUI.Label(new Rect(((Rect)(ref r)).x + 16f, ((Rect)(ref r)).y, 20f, ((Rect)(ref r)).height), icon, Skin.T(13, active ? Skin.Accent : Skin.TextDim, (TextAnchor)3)); GUI.Label(new Rect(((Rect)(ref r)).x + 38f, ((Rect)(ref r)).y, ((Rect)(ref r)).width - 76f, ((Rect)(ref r)).height), title, (active || flag) ? Skin.NavText : Skin.NavTextOff); if (count > 0) { GUI.Label(new Rect(((Rect)(ref r)).xMax - 38f, ((Rect)(ref r)).y, 30f, ((Rect)(ref r)).height), count.ToString(), Skin.T(10, Skin.TextFaint, (TextAnchor)5)); } } } private static void LyricBtn(Rect r, string text) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (!(((Rect)(ref r)).width <= 0f)) { bool flag = Hover(r); GUI.Box(r, GUIContent.none, flag ? Skin.BtnOn : Skin.Btn); GlowLabel(r, text, 11, flag ? Color.white : Color32.op_Implicit(new Color32((byte)232, (byte)236, (byte)244, byte.MaxValue)), (TextAnchor)4); } } private static void AccountCard() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) string text = R.G("_loginLine", ""); Rect val = default(Rect); ((Rect)(ref val))..ctor(20f, 640f, 184f, 56f); bool flag = Hover(val); GUI.Box(val, GUIContent.none, flag ? Skin.BtnOn : Skin.Btn); Fill(new Rect(((Rect)(ref val)).x + 10f, ((Rect)(ref val)).y + 14f, 28f, 28f), new Color(1f, 1f, 1f, 0.1f)); GUI.Label(new Rect(((Rect)(ref val)).x + 10f, ((Rect)(ref val)).y + 14f, 28f, 28f), "♪", Skin.T(13, Skin.TextDim, (TextAnchor)4)); string text2 = text; string text3 = ""; int num = text.IndexOf("·", StringComparison.Ordinal); if (num > 0) { text2 = text.Substring(0, num).Trim(); text3 = text.Substring(num + 1).Trim(); } if (string.IsNullOrEmpty(text2)) { text2 = "未登录"; text3 = "点这里扫码"; } GUI.Label(new Rect(((Rect)(ref val)).x + 46f, ((Rect)(ref val)).y + 8f, ((Rect)(ref val)).width - 56f, 18f), text2, Skin.T(11, Skin.TextMain, (TextAnchor)3)); GUI.Label(new Rect(((Rect)(ref val)).x + 46f, ((Rect)(ref val)).y + 28f, ((Rect)(ref val)).width - 56f, 16f), string.IsNullOrEmpty(text3) ? "点这里切换账号" : text3, Skin.T(10, Skin.TextDim, (TextAnchor)3)); bool num2 = R.G("_clicked", fallback: false); if (num2 && !_prevClick && flag) { R.Call("StartLogin"); Plugin.Log.LogInfo((object)"[UI] 侧栏账号卡 → 打开扫码登录"); } _prevClick = num2; } private static void MainArea() { Header(); SearchRow(); Presets(); StatusLine(); SongList(); Pager(); PlayerBar(); } private static void Header() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) SongInfo val = R.Current(); R.DrawCover(R.RectOf("_rCover"), val); GUI.Label(R.RectOf("_rTitle"), (val != null) ? val.Name : "未播放", Skin.H1); string text = "搜一首歌,点一行开始放"; if (val != null) { text = val.Artists; if (!string.IsNullOrEmpty(val.Album)) { text = text + "\u3000·\u3000" + val.Album; } } GUI.Label(R.RectOf("_rArtist"), text, Skin.Dim); Rect val2 = R.RectOf("_rTheme"); bool flag = Hover(val2); GUI.Box(val2, GUIContent.none, flag ? Skin.BtnOn : Skin.Btn); GUI.Label(val2, Theme.Name + " ▸", Skin.T(11, flag ? Skin.TextMain : Skin.TextDim, (TextAnchor)4)); if (ClickEdge("theme", val2)) { Theme.Next(); } Rect val3 = R.RectOf("_rBadge"); bool flag2 = R.G("_busy", fallback: false) || LibrarySync.Syncing || LibraryPage.Syncing; bool flag3 = Hover(val3); Color c = (flag2 ? Skin.Accent : Color32.op_Implicit(new Color32((byte)120, (byte)200, (byte)150, byte.MaxValue))); GUI.Box(val3, GUIContent.none, flag3 ? Skin.BtnOn : Skin.Btn); Fill(new Rect(((Rect)(ref val3)).x + 10f, ((Rect)(ref val3)).y + ((Rect)(ref val3)).height * 0.5f - 3f, 6f, 6f), c); GUI.Label(new Rect(((Rect)(ref val3)).x + 20f, ((Rect)(ref val3)).y, ((Rect)(ref val3)).width - 24f, ((Rect)(ref val3)).height), flag2 ? "同步中" : (flag3 ? "点我同步" : "就绪"), Skin.T(11, flag2 ? Skin.Accent : (flag3 ? Skin.TextMain : Skin.TextDim), (TextAnchor)3)); if (ClickEdge("badge", val3)) { LibrarySync.ForceRefresh(); LibraryPage.ForceRefresh(); R.S("_status", "正在同步账号收藏与歌单…"); Plugin.Log.LogInfo((object)"[UI] 手动同步:账号收藏 + 歌单列表"); } Divider(R.RectOf("_rHeaderLine")); } private static void SearchRow() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) Rect val = R.RectOf("_rSearch"); bool flag = R.G("_typing", fallback: false); GUI.Box(val, GUIContent.none, flag ? Skin.FieldOn : Skin.FieldBox); string text = R.G("_keyword", ""); if (string.IsNullOrEmpty(text)) { GUI.Label(new Rect(((Rect)(ref val)).x + 13f, ((Rect)(ref val)).y, ((Rect)(ref val)).width - 26f, ((Rect)(ref val)).height), flag ? "" : "搜歌名 / 歌手", Skin.T(12, Skin.TextFaint, (TextAnchor)3)); } else { GUI.Label(new Rect(((Rect)(ref val)).x + 13f, ((Rect)(ref val)).y, ((Rect)(ref val)).width - 26f, ((Rect)(ref val)).height), text, Skin.Body); } if (flag && Mathf.Repeat(Time.unscaledTime, 1f) < 0.5f) { Fill(new Rect(((Rect)(ref val)).x + 13f + Measure(text) + 1f, ((Rect)(ref val)).y + 10f, 1.5f, 16f), Skin.Accent); } Btn(R.RectOf("_rSearchBtn"), "搜索", enabled: true, primary: true); Btn(R.RectOf("_rClear"), "清空", enabled: false); } private static void Presets() { //IL_0038: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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) Rect[] array = R.G<Rect[]>("_rPreset"); if (array == null) { return; } for (int i = 0; i < array.Length; i++) { if (!(((Rect)(ref array[i])).width <= 0f)) { string text = R.TagLabel(i); if (!string.IsNullOrEmpty(text)) { bool flag = Hover(array[i]); GUI.Box(array[i], GUIContent.none, flag ? Skin.ChipOn : Skin.Chip); GUI.Label(array[i], text, Skin.T(10, flag ? Skin.TextMain : Skin.TextDim, (TextAnchor)4)); } } } } private static void StatusLine() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //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_0055: Unknown result type (might be due to invalid IL or missing references) string text = R.G("_status", ""); if (!string.IsNullOrEmpty(text)) { bool flag = text.StartsWith("失败", StringComparison.Ordinal) || text.StartsWith("拿不到", StringComparison.Ordinal) || text.StartsWith("加载失败", StringComparison.Ordinal); GUI.Label(R.RectOf("_rStatus"), text, Skin.T(10, flag ? Color32.op_Implicit(new Color32((byte)240, (byte)110, (byte)110, byte.MaxValue)) : Skin.TextDim, (TextAnchor)3)); } } private static void SongList() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) Rect val = R.RectOf("_rList"); GUI.Box(val, GUIContent.none, Skin.Card); List<SongInfo> list = R.ActiveSongs(); int num = R.G("_page", 0); int num2 = R.G("_view", 0); if (list == null || list.Count == 0) { string text = num2 switch { 2 => "队列空\u3000·\u3000客机点歌后会排到这里", 1 => "还没有收藏\u3000·\u3000点列表行右侧的 ♡ 收藏喜欢的歌", _ => R.G("_busy", fallback: false) ? "正在搜索…" : "还没有结果\u3000·\u3000上面输入歌名,或点一个预设词", }; GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + ((Rect)(ref val)).height * 0.5f - 10f, ((Rect)(ref val)).width, 20f), text, Skin.Center); return; } SongInfo cur = R.Current(); for (int i = 0; i < 8; i++) { int num3 = num + i; if (num3 < list.Count) { Row(i, num3, list[num3], cur, num2); continue; } break; } } private static void Row(int slot, int index, SongInfo song, SongInfo cur, int view) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) Rect val = RowRect(slot); bool flag = cur != null && !string.IsNullOrEmpty(cur.Key) && cur.Key == song.Key; bool flag2 = Hover(val); if (flag) { GUI.Box(val, GUIContent.none, Skin.RowOn); } else if (flag2) { GUI.Box(val, GUIContent.none, Skin.RowHi); } if (flag || flag2) { Fill(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y + 8f, 3f, ((Rect)(ref val)).height - 16f), Skin.Accent); } GUI.Label(new Rect(((Rect)(ref val)).x + 9f, ((Rect)(ref val)).y, 22f, ((Rect)(ref val)).height), flag ? "▶" : (index + 1).ToString("00"), Skin.T(10, flag ? Skin.Accent : Skin.TextFaint, (TextAnchor)3)); R.DrawCover(new Rect(((Rect)(ref val)).x + 34f, ((Rect)(ref val)).y + 4f, 30f, 30f), song); float num = ((Rect)(ref val)).width - 34f - 72f - 150f; GUI.Label(new Rect(((Rect)(ref val)).x + 72f, ((Rect)(ref val)).y + 3f, num, 17f), song.Name, (flag || flag2) ? Skin.T(12, Skin.TextMain, (TextAnchor)3) : Skin.T(12, Color32.op_Implicit(new Color32((byte)210, (byte)213, (byte)222, byte.MaxValue)), (TextAnchor)3)); GUI.Label(new Rect(((Rect)(ref val)).x + 72f, ((Rect)(ref val)).y + 20f, num, 14f), song.Artists, Skin.T(10, Skin.TextFaint, (TextAnchor)3)); string durationText = song.DurationText; GUI.Label(new Rect(((Rect)(ref val)).xMax - 152f, ((Rect)(ref val)).y, 56f, ((Rect)(ref val)).height), durationText, Skin.Num); string text = R.FeeTag(song); if (!string.IsNullOrEmpty(text)) { GUI.Label(new Rect(((Rect)(ref val)).xMax - 96f, ((Rect)(ref val)).y, 42f, ((Rect)(ref val)).height), text, Skin.T(9, Skin.Accent, (TextAnchor)5)); } Rect[] array = R.G<Rect[]>("_rFavBtn"); if (array != null && slot < array.Length) { bool flag3 = LibraryStore.IsFavorite(song.Key); Rect val2 = array[slot]; bool flag4 = Hover(val2); if (flag4) { GUI.Box(val2, GUIContent.none, Skin.RowHi); } GUI.Label(val2, (view == 2) ? "✕" : (flag3 ? "♥" : "♡"), Skin.T(13, flag3 ? Skin.Accent : (flag4 ? Skin.TextMain : Skin.TextFaint), (TextAnchor)4)); } } private static Rect RowRect(int i) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) Rect val = R.RectOf("_rList"); Rect result = default(Rect); ((Rect)(ref result))..ctor(((Rect)(ref val)).x + 4f, ((Rect)(ref val)).y + 4f + (float)i * 40f, ((Rect)(ref val)).width - 8f, 38f); Rect[] array = R.G<Rect[]>("_rFavBtn"); if (array != null && i < array.Length) { array[i] = new Rect(((Rect)(ref result)).xMax - 46f, ((Rect)(ref result)).y + (((Rect)(ref result)).height - 32f) * 0.5f, 38f, 32f); } return result; } private static void Pager() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) int num = R.ActiveSongs()?.Count ?? 0; int num2 = R.G("_page", 0); int num3 = Mathf.Max(1, Mathf.CeilToInt((float)num / 8f)); Btn(R.RectOf("_rPrev"), "‹ 上一页", num2 > 0); Btn(R.RectOf("_rNext"), "下一页 ›", num2 + 8 < num); GUI.Label(R.RectOf("_rPageInfo"), (num == 0) ? "" : ("第 " + (num2 / 8 + 1) + " / " + num3 + " 页\u3000·\u3000共 " + num + " 首"), Skin.T(10, Skin.TextFaint, (TextAnchor)5)); } private static void PlayerBar() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) GUI.Box(R.RectOf("_rBar"), GUIContent.none, Skin.BarBox); float num = R.Time(); float num2 = R.Duration(); float num3 = ((num2 > 0f) ? Mathf.Clamp01(num / num2) : 0f); Rect val = R.RectOf("_rProgress"); GUI.Box(val, GUIContent.none, Skin.Bar); if (num3 > 0.002f) { GUI.Box(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y, Mathf.Max(4f, ((Rect)(ref val)).width * num3), ((Rect)(ref val)).height), GUIContent.none, Skin.BarFill); Fill(new Rect(((Rect)(ref val)).x + Mathf.Max(4f, ((Rect)(ref val)).width * num3) - 3f, ((Rect)(ref val)).y - 2f, 7f, ((Rect)(ref val)).height + 4f), Color.white); } bool flag = R.HasClip(); GUI.Label(R.RectOf("_rTime"), flag ? (Mmss(num) + "\u3000/\u3000" + Mmss(num2)) : "0:00\u3000/\u30000:00", Skin.T(12, Color32.op_Implicit(new Color32((byte)232, (byte)236, (byte)244, byte.MaxValue)), (TextAnchor)3)); string text = ((!flag) ? "空闲" : (R.IsPlaying() ? "播放中" : "已暂停")); GUI.Label(new Rect(((Rect)(ref val)).x + 160f, ((Rect)(ref val)).y + 10f, 120f, 18f), "● " + text, Skin.T(10, R.IsPlaying() ? Skin.Accent : Skin.TextFaint, (TextAnchor)3)); bool flag2 = R.IsPlaying(); Rect val2 = R.RectOf("_rPause"); Hover(val2); GUI.Box(val2, GUIContent.none, Skin.Circle); GUI.Label(val2, flag2 ? "||" : "▶", Skin.T(flag2 ? 12 : 14, Color.white, (TextAnchor)4)); Btn(R.RectOf("_rStop"), "■ 停止", flag); Btn(R.RectOf("_rVolDown"), "−", enabled: true); Btn(R.RectOf("_rVolUp"), "+", enabled: true); GUI.Label(R.RectOf("_rVolText"), Mathf.RoundToInt(R.Volume() * 100f) + "%", Skin.T(12, Color32.op_Implicit(new Color32((byte)232, (byte)236, (byte)244, byte.MaxValue)), (TextAnchor)5)); } private static void Footer() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) R.G("_loginLine", ""); string text = "拖标题栏移动\u3000·\u3000Ctrl+滚轮缩放\u3000·\u3000T 输入\u3000·\u3000回车搜索"; GUI.Label(R.RectOf("_rHint"), text, Skin.T(11, Color32.op_Implicit(new Color32((byte)196, (byte)201, (byte)214, byte.MaxValue)), (TextAnchor)3)); } private static bool Hover(Rect r) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (R.G("_mouseValid", fallback: false)) { return ((Rect)(ref r)).Contains(R.VecOf("_lm")); } return false; } private static bool ClickEdge(string key, Rect r) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) bool flag = R.G("_clicked", fallback: false); _edgeDown.TryGetValue(key, out var value); _edgeDown[key] = flag; if (flag && !value) { return Hover(r); } return false; } private static void Fill(Rect r, Color c) { //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_000b: Unknown result type (might be due to invalid IL or missing references) Color color = GUI.color; GUI.color = c; GUI.DrawTexture(r, (Texture)(object)Skin.White); GUI.color = color; } private static void Divider(Rect r) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!(((Rect)(ref r)).height <= 0f)) { Fill(r, Skin.Line); } } private static void GlowLabel(Rect r, string text, int size, Color core, TextAnchor anchor = (TextAnchor)4) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) Color c = default(Color); ((Color)(ref c))..ctor(core.r, core.g, core.b, 0.32f); GUIStyle val = Skin.T(size, c, anchor); GUI.Label(new Rect(((Rect)(ref r)).x - 1f, ((Rect)(ref r)).y, ((Rect)(ref r)).width, ((Rect)(ref r)).height), text, val); GUI.Label(new Rect(((Rect)(ref r)).x + 1f, ((Rect)(ref r)).y, ((Rect)(ref r)).width, ((Rect)(ref r)).height), text, val); GUI.Label(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y - 1f, ((Rect)(ref r)).width, ((Rect)(ref r)).height), text, val); GUI.Label(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y + 1f, ((Rect)(ref r)).width, ((Rect)(ref r)).height), text, val); GUI.Label(r, text, Skin.T(size, core, anchor)); } private static void Btn(Rect r, string text, bool enabled) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Btn(r, text, enabled, primary: false); } private static void Btn(Rect r, string text, bool enabled, bool primary) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (!(((Rect)(ref r)).width <= 0f)) { bool flag = enabled && Hover(r); if (primary) { GUI.Box(r, GUIContent.none, flag ? Skin.BtnAccentHot : Skin.BtnAccent); } else { GUI.Box(r, GUIContent.none, flag ? Skin.BtnOn : Skin.Btn); } Color val = ((!enabled) ? Color32.op_Implicit(new Color32((byte)152, (byte)157, (byte)172, byte.MaxValue)) : (primary ? Color.white : (flag ? Color.white : Color32.op_Implicit(new Color32((byte)238, (byte)241, (byte)248, byte.MaxValue))))); if (primary) { GUI.Label(r, text, Skin.T(12, val, (TextAnchor)4)); } else { GlowLabel(r, text, 12, val, (TextAnchor)4); } } } private static string ThemeName() { object obj = R.G<object>("_pal"); if (obj == null) { return "主题"; } try { PropertyInfo property = obj.GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return (property.GetValue(obj, null) as string) ?? "主题"; } FieldInfo field = obj.GetType().GetField("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return (field.GetValue(obj) as string) ?? "主题"; } } catch { } return "主题"; } private static float Measure(string s) { if (string.IsNullOrEmpty(s)) { return 0f; } float num = 0f; for (int i = 0; i < s.Length; i++) { num += ((s[i] < '\u0080') ? 7.5f : 14f); } return num; } private static string Mmss(float sec) { if (sec < 0f) { sec = 0f; } int num = (int)sec; return (num / 60).ToString("00") + ":" + (num % 60).ToString("00"); } } internal static class Follow3D { internal sealed class Host : MonoBehaviour { private void LateUpdate() { try { Pump(); } catch { } } } internal static bool InTick; internal static bool Active; internal static float PositionSec; internal static SongInfo FakeSong; private static object _overlay; private static object _world; private static Type _overlayType; private static Type _worldType; private static MethodInfo _mSetLyrics; private static MethodInfo _mTick; private static MethodInfo _mHide; private static bool _bound; private static bool _owned; private static bool _yielding; private static DateTime _pausedSince = DateTime.MinValue; private static bool _bindFailed; private static string _jsonPath; private static DateTime _nextRead = DateTime.MinValue; private static DateTime _lastWrite = DateTime.MinValue; private static string _lyricKey = ""; private static string _lastStatus = "None"; private static string _title = ""; private static string _artist = ""; private static string _platform = ""; private static string _lyricsRaw = ""; private static long _posMs; private static long _endMs; private static DateTime _posWallAt = DateTime.UtcNow; private static string _posLoggedKey = ""; private static FieldInfo _curField; private static readonly string[] MetaPrefixes = new string[29] { "作词", "作曲", "编曲", "制作人", "出品人", "监制", "混音", "母带", "录音", "和声", "吉他", "贝斯", "键盘", "弦乐", "鼓", "统筹", "策划", "出品", "发行", "词", "曲", "OP", "SP", "Lyrics", "Composed", "Arranged", "Produced", "Mixed", "Mastered" }; private static Type _mpType; private static PropertyInfo _mpActive; private static FieldInfo _srcField; private static bool _srcLooked; internal static void Mount(Harmony harmony) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown EnsureBound(); if (_bindFailed) { return; } try { Type type = AccessTools.TypeByName("PeakMusic.MusicPlayer"); if (type != null) { MethodInfo methodInfo = AccessTools.PropertyGetter(type, "Time"); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Follow3D), "TimePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.PropertyGetter(type, "Current"); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Follow3D), "CurrentPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _curField = type.GetField("<Current>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); } MethodInfo methodInfo3 = AccessTools.Method(_worldType, "FindIndex", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(AccessTools.Method(typeof(Follow3D), "FindIndexPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(_worldType, "Animate", (Type[])null, (Type[])null); if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(AccessTools.Method(typeof(Follow3D), "AnimatePrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Plugin.Log.LogInfo((object)"[跟随3D] 补丁:LateUpdate/TickTime/FindIndex/Animate 已挂"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[跟随3D] 挂补丁失败:" + ex.Message)); } } private static bool LateUpdatePrefix() { if (Plugin.Use3D == null || !Plugin.Use3D.Value) { return true; } if (ModIsPlaying()) { return true; } if (!Active) { if (_lyricKey.Length > 0) { Hide(); } return true; } TickWorld(); return false; } private static void TimePostfix(ref float __result) { if (InTick) { __result = PositionSec; } } private static void CurrentPostfix(ref SongInfo __result) { if (InTick && FakeSong != null) { __result = FakeSong; } } private static void FindIndexPrefix(ref float seconds) { if (InTick) { seconds = PositionSec; } } private static void AnimatePrefix(ref double positionMs) { if (InTick) { positionMs = (double)PositionSec * 1000.0; } } private static object GetCurrent(object player) { try { return (_curField != null) ? _curField.GetValue(player) : null; } catch { return null; } } private static void RestoreCurrent(object player, object value) { try { if (_curField != null) { _curField.SetValue(player, value); } } catch { } } private static bool IsMetaLine(string text) { if (string.IsNullOrEmpty(text)) { return false; } string text2 = text.Trim(); if (text2.Length >= 2 && text2[0] == '【' && text2[text2.Length - 1] == '】') { return true; } if (text2.IndexOf("正版授权", StringComparison.Ordinal) >= 0) { return true; } for (int i = 0; i < MetaPrefixes.Length; i++) { string text3 = MetaPrefixes[i]; if (text2.StartsWith(text3, StringComparison.Ordinal) && text2.Length > text3.Length) { char c = text2[text3.Length]; if (c == ':' || c == ':' || c == ' ' || c == '\u3000') { return true; } } } return false; } private static List<LrcLine> FilterMeta(List<LrcLine> src) { if (src == null || src.Count == 0) { return src; } List<LrcLine> list = new List<LrcLine>(src.Count); for (int i = 0; i < src.Count; i++) { if (!IsMetaLine(src[i].Text)) { list.Add(src[i]); } } return list; } private static void SetCurrent(object player) { if (player == null || FakeSong == null) { return; } try { if (_curField != null) { _curField.SetValue(player, FakeSong); } } catch { } } private static void EnsureBound() { if (_bound || _bindFailed) { return; } _bound = true; try { _overlayType = AccessTools.TypeByName("PeakMusic.LyricOverlay"); _worldType = AccessTools.TypeByName("PeakMusic.WorldLyricRenderer"); if (_overlayType == null || _worldType == null) { _bindFailed = true; Plugin.Log.LogWarning((object)"[跟随3D] 找不到 LyricOverlay / WorldLyricRenderer,保持 2D 卡片"); return; } _mSetLyrics = AccessTools.Method(_worldType, "SetLyrics", (Type[])null, (Type[])null); _mTick = AccessTools.Method(_worldType, "Tick", (Type[])null, (Type[])null); _mHide = AccessTools.Method(_worldType, "Hide", (Type[])null, (Type[])null); Plugin.Log.LogInfo((object)("[跟随3D] 已绑定 WorldLyricRenderer(SetLyrics=" + (_mSetLyrics != null) + " Tick=" + (_mTick != null) + ")")); } catch (Exception ex) { _bindFailed = true; Plugin.Log.LogWarning((object)("[跟随3D] 绑定失败:" + ex.Message)); } } private static object OverlayInstance() { if (_overlay != null) { return _overlay; } try { PropertyInfo property = _overlayType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { _overlay = property.GetValue(null, null); } if (_overlay == null) { _overlay = Object.FindObjectOfType(_overlayType); } } catch { } return _overlay; } private static object World() { if (_world != null) { return _world; } if (_worldType == null) { return null; } try { _world = Activator.CreateInstance(_worldType); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[跟随3D] 渲染器创建失败:" + ex.Message)); } return _world; } private static string JsonPath() { if (!string.IsNullOrEmpty(_jsonPath)) { return _jsonPath; } try { string jsonPathValue = Overlay.JsonPathValue; if (!string.IsNullOrEmpty(jsonPathValue)) { _jsonPath = jsonPathValue; return _jsonPath; } string dir = Plugin.Dir; if (string.IsNullOrEmpty(dir)) { return null; } _jsonPath = Path.Combine(dir, "music_progress.json"); } catch { _jsonPath = null; } return _jsonPath; } internal static void Read() { DateTime now = DateTime.Now; if (now < _nextRead) { return; } _nextRead = now.AddMilliseconds(250.0); string text = JsonPath(); if (string.IsNullOrEmpty(text)) { Active = false; return; } try { FileInfo fileInfo = new FileInfo(text); if (!fileInfo.Exists) { Active = false; return; } if (fileInfo.LastWriteTimeUtc != _lastWrite) { DateTime lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; string json; try { using FileStream stream = new FileStream(text, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using StreamReader streamReader = new StreamReader(stream); json = streamReader.ReadToEnd(); } catch { return; } _lastWrite = lastWriteTimeUtc; _lastStatus = Str(json, "status", "None"); _title = Str(json, "title", ""); _artist = Str(json, "artist", ""); _lyricsRaw = Str(json, "lyrics", ""); _platform = Str(json, "platform", ""); _posMs = Num(json, "positionMs"); _endMs = Num(json, "endMs"); _posWallAt = DateTime.UtcNow; } Active = (DateTime.UtcNow - _lastWrite.ToUniversalTime()).TotalSeconds < 8.0 && _lastStatus != "None" && !string.IsNullOrEmpty(_title); PositionSec = (float)_posMs / 1000f; if (Active) { ApplyLyrics(); } } catch { Active = false; } } private static void ApplyLyrics() { EnsureBound(); if (_bindFailed) { return; } object obj = World(); if (obj == null || _mSetLyrics == null) { return; } string text = _title + "\u0001" + _artist; if (!(text != _lyricKey)) { return; } _lyricKey = text; try { List<LrcLine> list = FilterMeta(LrcDocument.Parse(_lyricsRaw ?? "").Lines); _mSetLyrics.Invoke(obj, new object[2] { text, list }); Plugin.Log.LogInfo((object)("[跟随3D] 歌词已交给 3D 渲染器:" + _title + " - " + _artist + "," + list.Count + " 行")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[跟随3D] SetLyrics 失败:" + ex.Message)); } } private static bool ModIsPlaying() { object obj = PlayerActive(); if (obj != null) { return ModIsPlaying(obj); } return false; } private static bool ModIsPlaying(object player) { try { if (!_srcLooked) { _srcLooked = true; if (_mpType != null) { _srcField = _mpType.GetField("_src", BindingFlags.Instance | BindingFlags.NonPublic); } } object obj = ((_srcField != null) ? _srcField.GetValue(player) : null); if (obj == null) { return false; } Type type = obj.GetType(); PropertyInfo property = type.GetProperty("clip"); if (((property != null) ? property.GetValue(obj, null) : null) == null) { return false; } PropertyInfo property2 = type.GetProperty("isPlaying"); if (property2 != null) { try { if ((bool)property2.GetValue(obj, null)) { return true; } } catch { } } if (_mpType != null) { PropertyInfo property3 = _mpType.GetProperty("Loading", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null) { try { if ((bool)property3.GetValue(player, null)) { return true; } } catch { } } } return false; } catch { return false; } } private static object PlayerActive() { try { if (_mpType == null) { _mpType = AccessTools.TypeByName("PeakMusic.MusicPlayer"); if (_mpType != null) { _mpActive = _mpType.GetProperty("Active", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } } if (_mpActive == null) { return null; } return _mpActive.GetValue(null, null); } catch { return null; } } private static float PosNow() { double num = (double)_posMs / 1000.0; if (_lastStatus == "Playing") { num += (DateTime.UtcNow - _posWallAt).TotalSeconds; } double num2 = ((_endMs > 0) ? ((double)_endMs / 1000.0) : 0.0); if (num2 > 0.0 && num > num2 + 2.0) { num = num2 + 2.0; } if (num < 0.0) { num = 0.0; } num -= (double)Overlay.OffsetFor(_platform); if (num < 0.0) { num = 0.0; } return (float)num; } internal static void TickWorld() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown Read(); if (!Active) { return; } object obj = World(); if (obj == null || _mTick == null) { return; } if (FakeSong == null || FakeSong.Name != _title || FakeSong.Artists != _artist) { FakeSong = new SongInfo { Name = _title, Artists = _artist, DurationMs = _endMs, Platform = (Platform)0 }; } object obj2 = PlayerActive(); if (obj2 == null || ModIsPlaying(obj2)) { return; } Camera val = Camera.main; if ((Object)(object)val == (Object)null) { val = Object.FindObjectOfType<Camera>(); } if ((Object)(object)val == (Object)null) { return; } object current = GetCurrent(obj2); SetCurrent(obj2); if (_posLoggedKey != _lyricKey) { _posLoggedKey = _lyricKey; Plugin.Log.LogInfo((object)("[跟随3D] 开始驱动渲染: " + _title + " - " + _artist + " @ " + PositionSec.ToString("F1") + "s / " + ((float)_endMs / 1000f).ToString("F0") + "s")); } bool inTick = InTick; InTick = true; try { PositionSec = PosNow(); _mTick.Invoke(obj, new object[3] { obj2, val, PositionSec }); } catch (Exception ex) { if (_lyricKey != "__err") { _lyricKey = "__err"; Plugin.Log.LogWarning((object)("[跟随3D] Tick 失败:" + ex.Message)); } } finally { InTick = inTick; RestoreCurrent(obj2, current); } } internal static void Pump() { if (Plugin.Use3D == null || !Plugin.Use3D.Value) { return; } if (Active && !string.Equals(_lastStatus, "Playing", StringComparison.Ordinal)) { if (_pausedSince == DateTime.MinValue) { _pausedSince = DateTime.UtcNow; } else if ((DateTime.UtcNow - _pausedSince).TotalSeconds > 5.0 && _owned) { Hide(); _owned = false; } } else { _pausedSince = DateTime.MinValue; } if (Overlay.Force2D) { if (_owned) { Hide(); _owned = false; } return; } if (ModIsPlaying()) { if (!_yielding) { _yielding = true; Plugin.Log.LogInfo((object)"[跟随3D] 内置播放器正在放,跟随让路(点面板「停止」后自动恢复)"); } if (_owned) { Hide(); _owned = false; } return; } if (_yielding) { _yielding = false; Plugin.Log.LogInfo((object)"[跟随3D] 内置播放器已停,跟随恢复"); } Read(); if (!Active) { if (_owned) { Hide(); _owned = false; } } else { _owned = true; TickWorld(); } } internal static void Hide() { if (_bindFailed) { return; } object obj = World(); if (obj != null && !(_mHide == null)) { try { _mHide.Invoke(obj, null); } catch { } _lyricKey = ""; } } internal static void EnsureHost() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0010: Unknown result type (might be due to invalid IL or missing references) try { GameObject val = new GameObject("PeakMusic.Follow3D"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<Host>(); Plugin.Log.LogInfo((object)"[跟随3D] 已挂常驻宿主(不依赖内置播放器)"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[跟随3D] 宿主创建失败:" + ex.Message)); } } private static string Str(string json, string key, string fallback) { Match match = Regex.Match(json, "\"" + key + "\"\\s*:\\s*\"((?:\\\\.|[^\"\\\\])*)\""); if (!match.Success) { return fallback; } return Regex.Unescape(match.Groups[1].Value); } private static long Num(string json, string key) { Match match = Regex.Match(json, "\"" + key + "\"\\s*:\\s*(-?\\d+)"); if (!match.Success || !long.TryParse(match.Groups[1].Value, out var result)) { return 0L; } return result; } } internal static class Layout { internal const float W = 832f; internal const float H = 722f; internal const float SideX = 12f; internal const float SideY = 34f; internal const float SideW = 200f; internal const float SideH = 676f; internal const float PadX = 20f; internal const float ItemW = 184f; internal const float MainX = 224f; internal const float MainW = 596f; internal const float MainRight = 820f; public static void Postfix() { try { Apply(); } catch (Exception ex) { Plugin.Log.LogError((object)("[UI] 布局异常:" + ex.Message)); } } internal static void Apply() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_04a4: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_0516: Unknown result type (might be due to invalid IL or missing references) //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_0562: Unknown result type (might be due to invalid IL or missing references) //IL_0588: Unknown result type (might be due to invalid IL or missing references) //IL_05ae: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) if (!R.Ready || R.G("_collapsed", fallback: false)) { return; } R.SetRect("_rNav", new Rect(12f, 34f, 200f, 676f)); R.SetRect("_rNavLine", new Rect(216f, 40f, 1f, 664f)); Rect[] array = R.G<Rect[]>("_rNavItem"); if (array != null && array.Length >= 2) { array[0] = new Rect(20f, 44f, 184f, 46f); array[1] = new Rect(20f, 94f, 184f, 46f); } R.SetRect("_rFavNav", new Rect(20f, 152f, 184f, 40f)); R.SetRect("_rQueueNav", new Rect(20f, 196f, 184f, 40f)); float num = 274f; R.SetRect("_rLyricEdge", new Rect(20f, num, 88f, 30f)); R.SetRect("_rLyricHeight", new Rect(116f, num, 88f, 30f)); R.SetRect("_rLyricSize", new Rect(20f, num + 34f, 88f, 30f)); R.SetRect("_rLyricDist", new Rect(116f, num + 34f, 88f, 30f)); R.SetRect("_rLyricColor", new Rect(20f, num + 68f, 88f, 30f)); R.SetRect("_rLyricPos", new Rect(116f, num + 68f, 88f, 30f)); R.SetRect("_rLyricBright", new Rect(20f, num + 102f, 184f, 30f)); R.SetRect("_rCover", new Rect(232f, 44f, 52f, 52f)); R.SetRect("_rTitle", new Rect(296f, 48f, 340f, 26f)); R.SetRect("_rArtist", new Rect(296f, 76f, 340f, 18f)); R.SetRect("_rTheme", new Rect(618f, 46f, 80f, 24f)); R.SetRect("_rBadge", new Rect(704f, 46f, 82f, 24f)); R.SetRect("_rHeaderLine", new Rect(224f, 104f, 596f, 1f)); float num2 = 116f; R.SetRect("_rSearch", new Rect(224f, num2, 444f, 36f)); R.SetRect("_rSearchBtn", new Rect(676f, num2, 76f, 36f)); R.SetRect("_rClear", new Rect(760f, num2, 60f, 36f)); Rect[] array2 = R.G<Rect[]>("_rPreset"); if (array2 != null) { float num3 = 224f; for (int i = 0; i < array2.Length; i++) { string text = R.TagLabel(i); if (text == null) { array2[i] = default(Rect); continue; } float num4 = 24f + (float)text.Length * 15f; array2[i] = new Rect(num3, num2 + 46f, num4, 26f); num3 += num4 + 8f; } } R.SetRect("_rStatus", new Rect(224f, num2 + 78f, 596f, 18f)); R.SetRect("_rList", new Rect(224f, num2 + 102f, 596f, 328f)); float num5 = num2 + 440f; R.SetRect("_rPrev", new Rect(224f, num5, 76f, 28f)); R.SetRect("_rNext", new Rect(308f, num5, 76f, 28f)); R.SetRect("_rPageInfo", new Rect(392f, num5, 428f, 28f)); float num6 = 596f; R.SetRect("_rBar", new Rect(224f, num6, 596f, 84f)); R.SetRect("_rProgress", new Rect(240f, num6 + 10f, 564f, 4f)); R.SetRect("_rTime", new Rect(240f, num6 + 20f, 150f, 18f)); R.SetRect("_rStop", new Rect(240f, num6 + 44f, 72f, 30f)); R.SetRect("_rPause", new Rect(502f, num6 + 38f, 40f, 40f)); R.SetRect("_rVolDown", new Rect(672f, num6 + 44f, 32f, 30f)); R.SetRect("_rVolUp", new Rect(708f, num6 + 44f, 32f, 30f)); R.SetRect("_rVolText", new Rect(744f, num6 + 44f, 60f, 20f)); R.SetRect("_rHint", new Rect(224f, num6 + 88f, 596f, 16f)); } } internal static class LibraryPage { private static List<PlaylistInfo> _lists = new List<PlaylistInfo>(); private static volatile List<PlaylistInfo> _pendingLists; private static volatile List<SongInfo> _pendingSongs; private static string _pendingTitle = ""; private static int _running; private static DateTime _next = DateTime.MinValue; private static Platform _lastPlatform; private static bool _prevClick; private const int MaxRows = 8; private static float _scroll; private static bool _dragging; private static float _dragOffset; internal static bool Syncing => _running != 0; internal static void ForceRefresh() { _next = DateTime.MinValue; } internal static void Tick() { //IL_006b: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) List<PlaylistInfo> pendingLists = _pendingLists; if (pendingLists != null) { _pendingLists = null; _lists = pendingLists; Plugin.Log.LogInfo((object)("[UI] 歌单列表已同步:" + pendingLists.Count + " 个")); } List<SongInfo> pendingSongs = _pendingSongs; if (pendingSongs != null) { _pendingSongs = null; ApplySongs(pendingSongs, _pendingTitle); } Platform platform = R.G<Platform>("_platform", (Platform)0); if (platform != _lastPlatform) { _lastPlatform = platform; _next = DateTime.MinValue; } if (DateTime.UtcNow < _next || Interlocked.CompareExchange(ref _running, 1, 0) != 0) { return; } _next = DateTime.UtcNow.AddSeconds(300.0); MusicSources client = R.G<MusicSources>("_client"); if (client == null) { _running = 0; return; } Task.Run(async delegate { _ = 1; try { List<PlaylistInfo> list = (((int)platform != 1) ? (await client.Netease.GetPlaylistsAsync(0L, default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false)) : (await client.QQ.GetPlaylistsAsync(default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false))); List<PlaylistInfo> list2 = list; if (list2 != null && list2.Count > 0) { _pendingLists = list2; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[UI] 歌单拉取失败:" + ex.Message)); } finally { _running = 0; } }); } internal static void DrawSidebar() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Invalid comparison between Unknown and I4 //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_0425: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_03de: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) float num = 20f; float num2 = 184f; float num3 = 414f; string text = ((_lists.Count > 0) ? ("我的歌单 (" + _lists.Count + ")") : "我的歌单"); GUI.Label(new Rect(num, num3, num2, 14f), text, Skin.T(9, Skin.TextFaint, (TextAnchor)3, bold: true)); num3 += 18f; bool flag = R.G("_clicked", fallback: false); bool flag2 = R.G("_mouseValid", fallback: false); Vector2 val = R.VecOf("_lm"); int count = _lists.Count; float num4 = 27f; int num5 = Math.Min(count, 8); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(num, num3, num2, (float)num5 * num4); float num6 = Mathf.Max(0f, (float)(count - num5) * num4); Event current = Event.current; if (current != null && (int)current.type == 6 && flag2 && ((Rect)(ref val2)).Contains(val)) { _scroll -= current.delta.y * 22f; current.Use(); } _scroll = Mathf.Clamp(_scroll, 0f, num6); int num7 = Mathf.FloorToInt(_scroll / num4); float num8 = _scroll - (float)num7 * num4; bool flag3 = R.G("_held", fallback: false); Rect val4 = default(Rect); for (int i = 0; i < num5; i++) { int num9 = num7 + i; if (num9 >= count) { break; } PlaylistInfo val3 = _lists[num9]; ((Rect)(ref val4))..ctor(num, num3 + (float)i * num4 - num8, num2, 26f); bool flag4 = flag2 && ((Rect)(ref val4)).Contains(val) && ((Rect)(ref val2)).Contains(val); if (flag4) { GUI.Box(val4, GUIContent.none, Skin.NavOff); } string text2 = (val3.IsFavorites ? "我喜欢的音乐" : val3.Name); GUI.Label(new Rect(((Rect)(ref val4)).x + 8f, ((Rect)(ref val4)).y, ((Rect)(ref val4)).width - 44f, ((Rect)(ref val4)).height), text2, Skin.T(10, flag4 ? Skin.TextMain : Skin.TextDim, (TextAnchor)3)); if (val3.TrackCount > 0) { GUI.Label(new Rect(((Rect)(ref val4)).xMax - 38f, ((Rect)(ref val4)).y, 34f, ((Rect)(ref val4)).height), val3.TrackCount.ToString(), Skin.T(9, Skin.TextFaint, (TextAnchor)5)); } if (flag && !_prevClick && flag4) { Load(val3); } } if (num6 > 0.5f) { float num10 = num + num2 + 2f; Rect r = default(Rect); ((Rect)(ref r))..ctor(num10, num3, 3f, ((Rect)(ref val2)).height); Bar(r, new Color(1f, 1f, 1f, 0.07f)); float num11 = Mathf.Max(20f, ((Rect)(ref val2)).height * ((float)num5 / (float)count)); float num12 = num3 + (((Rect)(ref val2)).height - num11) * (_scroll / num6); Rect val5 = default(Rect); ((Rect)(ref val5))..ctor(num10 - 3f, num12, 9f, num11); if (flag && !_prevClick && ((Rect)(ref val5)).Contains(val)) { _dragging = true; _dragOffset = val.y - num12; } if (!flag3) { _dragging = false; } if (_dragging) { float num13 = Mathf.Max(1f, ((Rect)(ref val2)).height - num11); _scroll = Mathf.Clamp((val.y - _dragOffset - num3) / num13 * num6, 0f, num6); } else if (flag && !_prevClick && ((Rect)(ref r)).Contains(val) && !((Rect)(ref val5)).Contains(val)) { _scroll = Mathf.Clamp(_scroll + ((val.y < num12) ? (0f - ((Rect)(ref val2)).height) : ((Rect)(ref val2)).height), 0f, num6); } Bar(new Rect(num10, num12, 3f, num11), new Color(Skin.Accent.r, Skin.Accent.g, Skin.Accent.b, 0.55f)); } else { _dragging = false; } if (count > num5) { GUI.Label(new Rect(num, num3 + ((Rect)(ref val2)).height - 2f, num2, 16f), "滚轮或拖右侧细条 · 共 " + count + " 个", Skin.T(9, Skin.TextFaint, (TextAnchor)3)); } _prevClick = flag; } private static void Bar(Rect r, Color c) { //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_000b: Unknown result type (might be due to invalid IL or missing references) Color color = GUI.color; GUI.color = c; GUI.DrawTexture(r, (Texture)(object)Texture2D.whiteTexture); GUI.color = color; } private static void Load(PlaylistInfo pl) { MusicSources client = R.G<MusicSources>("_client"); if (client == null || pl == null) { return; } _pendingTitle = (pl.IsFavorites ? "我喜欢的音乐" : pl.Name); Plugin.Log.LogInfo((object)("[UI] 打开歌单:" + _pendingTitle + " (" + pl.Id + ")")); Task.Run(async delegate { _ = 1; try { List<SongInfo> list; if ((int)pl.Platform == 1) { list = await client.QQ.GetPlaylistSongsAsync(pl, 300, default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false); } else { if (!long.TryParse(pl.Id, out var result) || result <= 0) { return; } list = await client.Netease.GetPlaylistSongsAsync(result, 300, default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false); } if (list != null) { _pendingSongs = list; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[UI] 歌单曲目失败:" + ex.Message)); } }); } private static void ApplySongs(List<SongInfo> songs, string title) { try { R.S("_songs", songs); R.S("_page", 0); R.S("_view", 0); R.S("_status", "歌单:" + title + " · " + songs.Count + " 首"); Plugin.Log.LogInfo((object)("[UI] 歌单已载入:" + title + " · " + songs.Count + " 首")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[UI] 写入歌单曲目失败:" + ex.Message)); } } } internal static class LibrarySync { private static volatile List<SongInfo> _pending; private static int _running; private static DateTime _next = DateTime.MinValue; private static string _lastTag = ""; internal static bool Syncing => _running != 0; internal static void ForceRefresh() { _next = DateTime.MinValue; _lastTag = ""; } internal static void Tick() { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Invalid comparison between Unknown and I4 List<SongInfo> pending = _pending; if (pending != null) { _pending = null; try { R.S("_favorites", pending); Plugin.Log.LogInfo((object)("[UI] 账号收藏已同步:" + pending.Count + " 首")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[UI] 写收藏列表失败:" + ex.Message)); } } if (DateTime.UtcNow < _next || Interlocked.CompareExchange(ref _running, 1, 0) != 0) { return; } MusicSources client = R.G<MusicSources>("_client"); Platform platform = R.G<Platform>("_platform", (Platform)0); if (client == null) { _running = 0; return; } string text = ((object)Unsafe.As<Platform, Platform>(ref platform)/*cast due to .constrained prefix*/).ToString() + ":" + (((int)platform != 1) ? (client.Netease.HasLogin ? "1" : "0") : (client.QQ.HasLogin ? "1" : "0")); if (text != _lastTag) { _lastTag = text; _next = DateTime.MinValue; } _next = DateTime.UtcNow.AddSeconds(300.0); Task.Run(async delegate { _ = 3; try { PlaylistInfo val = (((int)platform != 1) ? (await client.Netease.GetPlaylistsAsync(0L, default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false)) : (await client.QQ.GetPlaylistsAsync(default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false)))?.FirstOrDefault((Func<PlaylistInfo, bool>)((PlaylistInfo x) => x.IsFavorites)); if (val == null) { Plugin.Log.LogInfo((object)("[UI] " + ((object)Unsafe.As<Platform, Platform>(ref platform)/*cast due to .constrained prefix*/).ToString() + " 没有找到「我喜欢的音乐」歌单")); } else { List<SongInfo> list; if ((int)platform == 1) { list = await client.QQ.GetPlaylistSongsAsync(val, 300, default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false); } else { if (!long.TryParse(val.Id, out var result) || result <= 0) { return; } list = await client.Netease.GetPlaylistSongsAsync(result, 300, default(CancellationToken)).ConfigureAwait(continueOnCapturedContext: false); } if (list != null && list.Count > 0) { _pending = list; } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[UI] 收藏同步失败:" + ex2.Message)); } finally { _running = 0; } }); } } internal static class LyricConfig { private static string _path; private static DateTime _mtime; private static string _enabled = "开"; private static string _size = "84"; private static string _dist = "12"; private static string _edge = "0.40"; private static string _y = "0.62"; private static string _color = "?"; private static string _bright = "1.3"; internal static string Enabled { get { Refresh(); return _enabled; } } internal static string Size { get { Refresh(); return _size; } } internal static string Distance { get { Refresh(); return _dist; } } internal static string Edge { get { Refresh(); return _edge; } } internal static string Y { get { Refresh(); return _y; } } internal static string Brightness { get { Refresh(); return _bright; } } internal static string Color { get { Refresh(); return _color; } } internal static void Refresh() { try { if (_path == null) { _path = Path.Combine(Paths.ConfigPath, "com.peakmusic.probe.cfg"); } if (!File.Exists(_path)) { return; } DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(_path); if (lastWriteTimeUtc == _mtime) { return; } _mtime = lastWriteTimeUtc; string text = ""; string[] array = File.ReadAllLines(_path); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { continue; } if (text2[0] == '[' && text2[text2.Length - 1] == ']') { text = text2.Substring(1, text2.Length - 2); } else { if (text != "歌词") { continue; } int num = text2.IndexOf('='); if (num > 0) { string text3 = text2.Substring(0, num).Trim(); string text4 = text2.Substring(num + 1).Trim(); switch (text3) { case "Enabled": _enabled = ((text4.ToLowerInvariant() == "true") ? "开" : "关"); break; case "Size": _size = text4; break; case "Distance": _dist = text4; break; case "Edge": _edge = text4; break; case "Y": _y = text4; break; case "ColorPreset": _color = PresetName(text4); break; case "Brightness": { float result = 1.3f; float.TryParse(text4, NumberStyles.Float, CultureInfo.InvariantCulture, out result); _bright = result.ToString("0.0", CultureInfo.InvariantCulture); break; } } } } } } catch { } } private static string PresetName(string idxText) { if (!int.TryParse(idxText, out var result) || result < 0) { return "自定义"; } try { Type type = AccessTools.TypeByName("PeakMusic.LyricColors"); if (type == null) { return "自定义"; } FieldInfo field = type.GetField("Presets", BindingFlags.Static | BindingFlags.Public); Array array = ((field != null) ? (field.GetValue(null) as Array) : null); if (array == null || result >= array.Length) { return "自定义"; } object value = array.GetValue(result); FieldInfo field2 = value.GetType().GetField("Name"); return (field2 != null) ? ((field2.GetValue(value) as string) ?? "自定义") : "自定义"; } catch { return "自定义"; } } } internal static class LyricDepth { private static readonly HashSet<string> _logged = new HashSet<string>(); internal static void Mount(Harmony harmony) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown Type type = AccessTools.TypeByName("PeakMusic.WorldLyricRenderer"); if (type == null) { Plugin.Log.LogWarning((object)"[UI] 找不到 WorldLyricRenderer,歌词置顶补丁跳过"); return; } MethodInfo methodInfo = AccessTools.Method(type, "ApplyMaterial", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(LyricDepth).GetMethod("ApplyPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[UI] 歌词置顶补丁已挂上(ApplyMaterial)"); } else { Plugin.Log.LogWarning((object)"[UI] 找不到 ApplyMaterial,只挂 MakeText"); } MethodInfo methodInfo2 = AccessTools.Method(type, "MakeText", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(LyricDepth).GetMethod("MakePostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void Fix(Material m) { if ((Object)(object)m == (Object)null) { return; } try { string text = (((Object)(object)m.shader != (Object)null) ? ((Object)m.shader).name : "(null)"); if (_logged.Add(text)) { Plugin.Log.LogInfo((object)("[UI] 歌词材质 shader=" + text + " HasZTest=" + m.HasProperty("_ZTest"))); } if (m.HasProperty("_ZTest")) { m.SetFloat("_ZTest", 8f); } if (m.HasProperty("_ZWrite")) { m.SetFloat("_ZWrite", 0f); } m.renderQueue = 4000; Shader val = Shader.Find("TextMeshPro/Distance Field Overlay"); if ((Object)(object)val == (Object)null) { val = Shader.Find("TextMeshPro/Mobile/Distance Field Overlay"); } if ((Object)(object)val != (Object)null && (Object)(object)m.shader != (Object)(object)val) { m.shader = val; if (_logged.Add("ov:" + text)) { Plugin.Log.LogInfo((object)("[UI] 歌词材质已切到 Overlay shader(" + text + " → " + ((Object)val).name + ")")); } } else if ((Object)(object)val == (Object)null && _logged.Add("noov:" + text)) { Plugin.Log.LogWarning((object)"[UI] 没找到 TMP Overlay shader,只改了 _ZTest 属性"); } } catch { } } private static void ApplyPostfix(Material __0) { Fix(__0); } private static void MakePostfix(object __result) { Component val = (Component)((__result is Component) ? __result : null); if ((Object)(object)val == (Object)null) { return; } try { Renderer component = val.GetComponent<Renderer>(); if ((Object)(object)component != (Object)null) { Fix(component.material); } } catch { } } } internal static class Overlay { private sealed class Model { public string Status = "None"; public string Title = ""; public string Artist = ""; public string Lyrics = ""; public long PosMs; public long EndMs; public string Platform = ""; } private sealed class Line { public float Time; public string Text = ""; } private static ConfigEntry<bool> _show; private static ConfigEntry<int> _fontSize; private static ConfigEntry<string> _jsonPath; private static ConfigEntry<float> _nx; private static ConfigEntry<float> _ny; private static ConfigEntry<bool> _progress; private static ConfigEntry<string> _hotkey; private static ConfigEntry<int> _offsetMs; private static string _resolved; private static DateTime _nextResolve = DateTime.MinValue; private static DateTime _nextRead = DateTime.MinValue; private static DateTime _lastWrite = DateTime.MinValue; private static Model _model = new Model(); private static string _lrcFor = ""; private static List<Line> _lines = new List<Line>(); private static bool _drag; private static Vector2 _dragOff; private static int _f8Frame = -1; private static float _pausedFor; internal static bool Force2D; private static ConfigFile _cfg; private static DateTime _cfgWrite = DateTime.MinValue; private static DateTime _cfgCheckAt = DateTime.MinValue; private static Type _kbType; private static PropertyInfo _kbCurrent; private static PropertyInfo _kbIndexer; private static Type _kbKeyEnum; private static object _kbKeyObj; private static string _kbKeyName; private static bool _kbLooked; internal static float OffsetSec { get { if (_offsetMs == null) { return 0f; } return (float)_offsetMs.Value / 1000f; } } internal static string JsonPathValue { get { if (_jsonPath == null) { return ""; } return _jsonPath.Value; } } internal static float OffsetFor(string platform) { if (string.Equals(platform, "qq", StringComparison.OrdinalIgnoreCase)) { return 0f; } return OffsetSec; } internal static void Init(ConfigFile cfg) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown _cfg = cfg; try { _cfgWrite = new FileInfo(cfg.ConfigFilePath).LastWriteTimeUtc; } catch { } _show = cfg.Bind<bool>("Follow", "Show", true, "true = 在屏幕顶部显示本机播放器歌词浮层。游戏里按 F8 可随时开关。"); _fontSize = cfg.Bind<int>("Follow", "FontSize", 26, new ConfigDescription("歌词字号(1080p 基准,会自动按分辨率缩放)。", (AcceptableValueBase)(object)new AcceptableValueRange<int>(14, 48), Array.Empty<object>())); _jsonPath = cfg.Bind<string>("Follow", "JsonPath", "", "music_progress.json 的完整路径。留空 = 自动在 BepInEx\\plugins 下查找。"); _nx = cfg.Bind<float>("Follow", "CenterX", 0.5f, new ConfigDescription("浮层水平中心(0~1 屏幕比例),拖动后自动保存。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); _ny = cfg.Bind<float>("Follow", "TopY", 0.006f, new ConfigDescription("浮层顶部位置(0~1 屏幕比例),拖动后自动保存。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); _progress = cfg.Bind<bool>("Follow", "ShowProgress", true, "浮层里是否显示进度条。"); _offsetMs = cfg.Bind<int>("Follow", "LyricOffsetMs", 0, "歌词偏移(毫秒)。0 = 原样;歌词总是「早出来」就填正数(如 5500)=整体延后;「晚出来」填负数。改完立即生效。"); _hotkey = cfg.Bind<string>("Follow", "Hotkey", "F8", "切换热键(3D 世界歌词 ↔ 顶部 2D 卡片)。填 KeyCode 名:F6 / F7 / F8 / F9 / Insert / Home 等,改完立即生效。"); _resolved = null; _nextResolve = DateTime.MinValue; } private static string HotkeyName() { string text = ((_hotkey != null) ? (_hotkey.Value ?? "") : "F8"); text = text.Trim(); if (text.Length != 0) { return text; } return "F8"; } private static bool HotkeyDown() { //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) string text = HotkeyName(); if (!_kbLooked) { _kbLooked = true; try { _kbType = Type.GetType("UnityEngine.InputSystem.Keyboard, Unity.InputSystem"); _kbKeyEnum = Type.GetType("UnityEngine.InputSystem.Key, Unity.InputSystem"); if (_kbType != null && _kbKeyEnum != null) { _kbCurrent = _kbType.GetProperty("current", BindingFlags.Static | BindingFlags.Public); _kbIndexer = _kbType.GetProperty("Item", new Type[1] { _kbKeyEnum }); if (_kbCurrent == null || _kbIndexer == null) { _kbType = null; } } } catch { _kbType = null; } } if (_kbType != null) { try { object value = _kbCurrent.GetValue(null, null); if (value != null) { if (_kbKeyObj == null || _kbKeyName != text) { _kbKeyObj = Enum.Parse(_kbKeyEnum, text, ignoreCase: true); _kbKeyName = text; } object value2 = _kbIndexer.GetValue(value, new object[1] { _kbKeyObj }); if (value2 != null) { PropertyInfo property = value2.GetType().GetProperty("wasPressedThisFrame"); if (property != null) { return (bool)property.GetValue(value2, null); } } } } catch { } } try { if (!Enum.TryParse<KeyCode>(text, ignoreCase: true, out KeyCode result)) { result = (KeyCode)289; } return Input.GetKeyDown(result); } catch { return false; } } internal static void Tick() { if (_show == null) { return; } if (DateTime.Now >= _cfgCheckAt) { _cfgCheckAt = DateTime.Now.AddSeconds(2.0); try { FileInfo fileInfo = new FileInfo(_cfg.ConfigFilePath); if (fileInfo.Exists && fileInfo.LastWriteTimeUtc != _cfgWrite) { _cfgWrite = fileInfo.LastWriteTimeUtc; _cfg.Reload(); Plugin.Log.LogInfo((object)("[跟随] 配置已重载:LyricOffsetMs=" + _offsetMs.Value + ",Hotkey=" + _hotkey.Value)); } } catch { } } if (HotkeyDown() && Time.frameCount != _f8Frame) { _f8Frame = Time.frameCount; if (Plugin.Use3D == null || !Plugin.Use3D.Value) { _show.Value = !_show.Value; } else if (!_show.Value) { _show.Value = true; Force2D = true; } else { Force2D = !Force2D; } if (Plugin.Use3D == null || !Plugin.Use3D.Value) { Plugin.Log.LogInfo((object)("[跟随] " + HotkeyName() + " → 浮层 " + (_show.Value ? "开启" : "关闭"))); } else { Plugin.Log.LogInfo((object)("[跟随] " + HotkeyName() + " → " + (Force2D ? "顶部 2D 卡片(3D 世界歌词暂时让位)" : "3D 世界歌词"))); } } if (!_show.Value) { return; } DateTime now = DateTime.Now; if (string.IsNullOrEmpty(_resolved) || now >= _nextResolve) { _nextResolve = now.AddSeconds(5.0); string text = Resolve(); if (text != _resolved) { _resolved = text; if (text != null) { Plugin.Log.LogInfo((object)("[跟随] 读到进度文件:" + text)); } else { Plugin.Log.LogInfo((object)"[跟随] 暂未找到 music_progress.json,继续等(先启动 NowPlaying --follow)"); } } } if (_resolved == null || now < _nextRead) { return; } _nextRead = now.AddMilliseconds(220.0); try { FileInfo fileInfo2 = new FileInfo(_resolved); if (!fileInfo2.Exists) { _resolved = null; } else { if (fileInfo2.LastWriteTimeUtc == _lastWrite) { return; } _lastWrite = fileInfo2.LastWriteTimeUtc; string json; using (FileStream stream = new FileStream(_resolved, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) { using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); json = streamReader.ReadToEnd(); } _model = new Model { Status = GetStr(json, "status"), Title = GetStr(json, "title"), Artist = GetStr(json, "artist"), Lyrics = GetStr(json, "lyrics"), PosMs = GetLong(json, "positionMs"), EndMs = GetLong(json, "endMs"), Platform = GetStr(json, "platform") }; } } catch { } } private static string Resolve() { string text = ((_jsonPath != null) ? _jsonPath.Value : null); if (!string.IsNullOrEmpty(text) && File.Exists(text)) { return text; } string text2; try { text2 = Path.Combine(Paths.BepInExRootPath, "plugins"); } catch { return null; } List<string> list = new List<string>(); try { string directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location); if (!string.IsNullOrEmpty(directoryName)) { list.Add(Path.Combine(directoryName, "music_progress.json")); } } catch { } list.Add(Path.Combine(text2, "PeakMusic", "music_progress.json")); list.Add(Path.Combine(text2, "PeakFloatingLyrics", "music_progress.json")); list.Add(Path.Combine(text2, "music_progress.json")); list.Add(Path.Combine(Paths.BepInExRootPath, "music_progress.json")); foreach (string item in list) { if (File.Exists(item)) { return item; } } try { string[] files = Directory.GetFiles(text2, "music_progress.json", SearchOption.AllDirectories); if (files.Length != 0) { return files[0]; } } catch { } return null; } internal static void Draw() { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Invalid comparison between Unknown and I4 //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Invalid comparison between Unknown and I4 //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_03a8: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Invalid comparison between Unknown and I4 //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_044f: Unknown result type (might be due to invalid IL or missing references) //IL_0643: Unknown result type (might be due to invalid IL or missing references) //IL_065c: Unknown result type (might be due to invalid IL or missing references) //IL_06e6: Unknown result type (might be due to invalid IL or missing references) //IL_06a2: Unknown result type (might be due to invalid IL or missing references) //IL_06d5: Unknown result type (might be due to invalid IL or missing references) //IL_057e: Unknown result type (might be due to invalid IL or missing references) //IL_05cd: Unknown result type (might be due to invalid IL or missing references) //IL_05f5: Unknown result type (might be due to invalid IL or missing references) //IL_0609: Unknown result type (might be due t