diff --git a/Vanessa/Players/Tencent.cs b/Vanessa/Players/Tencent.cs index 6a5f3ee..bff3a0d 100644 --- a/Vanessa/Players/Tencent.cs +++ b/Vanessa/Players/Tencent.cs @@ -1,16 +1,155 @@ using System; +using System.Diagnostics; +using System.Text; using Kxnrl.Vanessa.Models; +using Kxnrl.Vanessa.Win32Api; namespace Kxnrl.Vanessa.Players; internal sealed class Tencent : IMusicPlayer { + private const string CurrentSOngInfoPattern + = "B9 ? ? ? ? C7 05 ? ? ? ? ? ? ? ? E8 ? ? ? ? B9"; + + private const string AlbumThumbnailPattern + = "A1 ? ? ? ? 6A ? 51 8B CC 51 89 01 8B C4 8D 4D ? C7 00 ? ? ? ? FF 15 ? ? ? ? A1 ? ? ? ? 6A ? 51 8B CC 51 89 01 8B C4 8D 4D ? C7 00 ? ? ? ? FF 15 ? ? ? ? 6A ? FF 35 ? ? ? ? 51 8B C4 8D 4D ? C7 00 ? ? ? ? FF 15 ? ? ? ? 6A ? FF 35 ? ? ? ? 51 8B C4 8D 4D ? C7 00 ? ? ? ? FF 15 ? ? ? ? 6A ? FF 35 ? ? ? ? 51 8B C4 C7 00"; + + private const string IsPausedPattern + = "0F 29 05 ? ? ? ? C7 05 ? ? ? ? ? ? ? ? C7 05 ? ? ? ? ? ? ? ? C7 05 ? ? ? ? ? ? ? ? E8"; + + private readonly nint _currentSongInfoAddress; + private readonly nint _isPausedAddress; + private readonly nint _albumThumbnailAddress; + + private readonly int _pid; + private readonly ProcessMemory _process; + public Tencent(int pid) - => throw new NotImplementedException(); + { + _pid = pid; + using var p = Process.GetProcessById(pid); + + foreach (ProcessModule module in p.Modules) + { + if (!"QQMusic.dll".Equals(module.ModuleName)) + { + continue; + } + + var process = new ProcessMemory(pid); + var address = module.BaseAddress; + + if (Memory.FindPattern(CurrentSOngInfoPattern, pid, address, out var patternAddress)) + { + var currentSongAddress = process.ReadInt32(patternAddress, 1); + _currentSongInfoAddress = currentSongAddress; + } + + if (Memory.FindPattern(IsPausedPattern, pid, address, out patternAddress)) + { + var isPausedAddress = process.ReadInt32(patternAddress, 3); + _isPausedAddress = isPausedAddress + 4; + } + + if (Memory.FindPattern(AlbumThumbnailPattern, pid, address, out patternAddress)) + { + var albumThumbnailAddress = process.ReadInt32(patternAddress, 1); + _albumThumbnailAddress = albumThumbnailAddress; + } + + _process = process; + + break; + } + + if (_process is null) + { + throw new EntryPointNotFoundException("Failed to find process"); + } + + if (_currentSongInfoAddress == 0) + { + throw new EntryPointNotFoundException("_currentSongAddress is 0"); + } + } public bool Validate(int pid) - => throw new System.NotImplementedException(); + => _pid == pid; public PlayerInfo? GetPlayerInfo() - => throw new System.NotImplementedException(); + { + var id = GetSongIdentity(); + + if (id == 0) + { + return null; + } + + return new PlayerInfo + { + Identity = id.ToString(), + Title = GetSongName(), + Artists = GetArtistName(), + Album = GetAlbumName(), + Cover = GetAlbumThumbnailUrl(), + Schedule = GetSongSchedule() * 0.001, + Duration = GetSongDuration() * 0.001, + Pause = IsPaused(), + + // lock + Url = $"https://y.qq.com/n/ryqq/songDetail/{id}", + }; + } + + private uint GetSongIdentity() + => _process.ReadUInt32(_currentSongInfoAddress); + + private int GetSongDuration() + => _process.ReadInt32(_currentSongInfoAddress, 0x10); + + private int GetSongSchedule() + => _process.ReadInt32(_currentSongInfoAddress, 0xC); + + private string GetSongName() + { + var address = _process.ReadInt32(_currentSongInfoAddress); + var bytes = _process.ReadBytes(address, 512); + + var result = Encoding.Unicode.GetString(bytes); + + return result[..result.IndexOf('\0')]; + } + + private string GetArtistName() + { + var address = _process.ReadInt32(_currentSongInfoAddress, 4); + var bytes = _process.ReadBytes(address, 512); + + var result = Encoding.Unicode.GetString(bytes); + + return result[..result.IndexOf('\0')]; + } + + private string GetAlbumName() + { + var address = _process.ReadInt32(_currentSongInfoAddress, 8); + var bytes = _process.ReadBytes(address, 512); + + var result = Encoding.Unicode.GetString(bytes); + + return result[..result.IndexOf('\0')]; + } + + private string GetAlbumThumbnailUrl() + { + var address = _process.ReadInt32(_albumThumbnailAddress); + var bytes = _process.ReadBytes(address, 512); + + var result = Encoding.Unicode.GetString(bytes); + + return result[..result.IndexOf('\0')]; + } + + private bool IsPaused() + => _process.ReadInt32(_isPausedAddress) == 1; } diff --git a/Vanessa/Program.cs b/Vanessa/Program.cs index 71306cd..7f6a110 100644 --- a/Vanessa/Program.cs +++ b/Vanessa/Program.cs @@ -156,7 +156,7 @@ private static async Task UpdateThread(DiscordRpcClient netEase, DiscordRpcClien rpc.Assets = new Assets { LargeImageKey = info.Cover, - LargeImageText = info.Album, + LargeImageText = $"💿 {info.Album}", SmallImageKey = "timg", SmallImageText = "NetEase CloudMusic", }; diff --git a/Vanessa/Vanessa.csproj b/Vanessa/Vanessa.csproj index 3c17e3e..84cb4dd 100644 --- a/Vanessa/Vanessa.csproj +++ b/Vanessa/Vanessa.csproj @@ -10,7 +10,7 @@ Kxnrl.Vanessa 1.0 1 - $(VersionPrefix).$(VersionSuffix) + 3.0 https://github.com/Kxnrl/NetEase-Cloud-Music-DiscordRPC ©2024 Kyle https://github.com/Kxnrl/NetEase-Cloud-Music-DiscordRPC diff --git a/Vanessa/Win32Api/Memory.cs b/Vanessa/Win32Api/Memory.cs index babde9f..2d7a7dd 100644 --- a/Vanessa/Win32Api/Memory.cs +++ b/Vanessa/Win32Api/Memory.cs @@ -14,12 +14,13 @@ public static bool FindPattern(string pattern, int processId, nint address, out var ntHeader = address + ntOffset; // IMAGE - var fileHeader = ntHeader + 4; - var sections = memory.ReadInt16(ntHeader, 6); + var fileHeader = ntHeader + 4; + var sections = memory.ReadInt16(ntHeader, 6); + var sectionSize = memory.ReadInt16(fileHeader, 16); // OPT HEADER var optHeader = fileHeader + 20; - var sectionHeader = optHeader + 240; + var sectionHeader = optHeader + sectionSize; var cursor = sectionHeader; @@ -167,6 +168,9 @@ public short ReadInt16(IntPtr address, int offset = 0) public int ReadInt32(IntPtr address, int offset = 0) => BitConverter.ToInt32(ReadBytes(IntPtr.Add(address, offset), 4), 0); + public uint ReadUInt32(IntPtr address, int offset = 0) + => BitConverter.ToUInt32(ReadBytes(IntPtr.Add(address, offset), 4), 0); + [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory(IntPtr pHandle, IntPtr address, byte[] buffer, int size, IntPtr bytesRead);