python 最新版网易云音乐获取桌面歌词 通过内存读取 在前人的肩膀上 2024 12.26

我在做嵌入式系统大作业的时候,遇到了一些问题,使我暂时放弃了原来制作一个借助电脑算力的同声传译器的计划,为了快些交差,我准备制作一个桌面小副屏,让网易云音乐的歌词可以通过串口显示在STM32F407单片机的显示屏上,为了解决网易云音乐没有实时歌词api接口的问题,我决定使用读取内存的方式获得网易云的歌词。

通过检索资料,我发现了“三十岁开始学编程的大叔”大佬写的一篇古早文章(原文在此:“原文链接”),觉得非常满足我的需求,但是经过试验,由于其代码年代久远,原本的地址,操作系统位数等的变动,程序已经不能正常使用,于是我重复了他的操作,自学了一下CE,重新获得了网易云音乐歌词程序歌词在内存中的地址与复数个偏移量,修改了下代码中的小bug,成功的盘活了这个程序,想到与我有相似需求的人可能不在少数,就准备发上来。

第一步,寻找网易云音乐歌词所在的地址

先找出一首英文歌曲,在CE中搜索正在显示的歌词的首字母,一般是大写。

点击新的扫描,我们会获得复数个(数以万计)数值为首字母的地址,这个时候我们不要慌,我们可以让歌曲(子弹)飞一会儿,在随机的新歌词出现时,暂停歌曲,再次搜索新的歌词的首字母。经过多次重复,最终我们会得到一个(没错,就一个)地址。

恭喜各位,现在你已经学会了如何获得了一个字母的地址(现在去高考吧),但这是远远不够的,将这个地址添加到工作区里。

我们需要获得基地址的地址和多层指针偏移(通常不止一个偏移,但一般也不会太多。网易云老贼有4个偏移)而且网易云的歌词地址还是会动的,一个一个找肯定是要命的。

经过我的实验和多方资料查证,我们最方便的方法是:在CE中对地址右键,点击对这个地址进行指针扫描,设置最大偏移量2078,层数>=4, 64位,开始扫描。

扫描后我们会再次看到复数个基地址和复数^2个偏移,这时候我们要确定谁是我们需要的,持续运行网易云音乐,发现第一个地址对应的数据是会变动的,当第一个地址变动后,第二第三个地址会依次变动成新的值,这里我也不清楚这样的原因,暂时认为是废弃的指针残存在内存中吧!期待其他大佬的解答。

根据这一行的基地址和偏移量,我们修改前人的代码。

我看过新的代码后,认为有以下几个需要着重注意的点:

1. 原作者代码成与32位时代,现在我们的电脑都是64位的,新地址会溢出,所以需要改long位longlong。

2. 原作者代码成时,网易云是双层指针,新版有4个,所以我们需要增加几个偏移。

3. 原作者代码成时,没有使用bytes转utf8的库,为了标准化我们补全这一部分。

4. 原作者代码成时,认为字节的倒置是应用故意为之,其实不然,这是由utf-16-le 小端序排列导致的,使用标准会自动解决这一问题。

修改后的代码如下:

# 2022-10-15 by jd3096 vx:jd3096
# 2024-12-26 rewrite by 五月
import pymem
import time
import win32process
from win32con import PROCESS_ALL_ACCESS 
import win32api
import ctypes
from win32gui import FindWindow

def GetProcssID(address, bufflength):
    kernel32 = ctypes.windll.kernel32
    hwnd = FindWindow("DesktopLyrics", u"桌面歌词")  # 获取窗口句柄
    _, pid = win32process.GetWindowThreadProcessId(hwnd)  # 获取窗口ID
    hProcess = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, pid)  # 获取进程句柄

    # 使用 c_ulonglong 代替 c_ulong 来处理64位地址
    addr = ctypes.c_ulonglong()

    # 使用 ctypes.c_void_p 来处理64位指针
    ReadProcessMemory = kernel32.ReadProcessMemory
    ReadProcessMemory.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]
    ReadProcessMemory.restype = ctypes.c_bool

    # 使用 ctypes.c_void_p 来处理64位地址
    bytes_read = ctypes.c_size_t()
    result = ReadProcessMemory(int(hProcess), ctypes.c_void_p(address), ctypes.byref(addr), bufflength, ctypes.byref(bytes_read))

    if not result:
        raise ctypes.WinError()

    win32api.CloseHandle(hProcess)  # 关闭句柄
    return addr.value


def Get_moduladdr(dll):  #找到dll的内存基址
    modules = list(Game.list_modules())
    for module in modules:
        if module.name == dll:
            Moduladdr = module.lpBaseOfDll
    return Moduladdr

def get_add():   #从基址加偏移量反复三次得到实际内存地址
    Char_Modlue = Get_moduladdr("cloudmusic.dll")
    # 使用 ctypes.c_ulonglong 来处理64位地址
    addr = ctypes.c_ulonglong(GetProcssID(Char_Modlue + 0x019D9410, 8)).value
    ret = addr + 0xE0
    ret2 = ctypes.c_ulonglong(GetProcssID(ret, 8)).value
    ret3 = ret2 + 0x8
    ret4 = ctypes.c_ulonglong(GetProcssID(ret3, 8)).value

    ret5 = ret4 + 0x120
    ret6 = ctypes.c_ulonglong(GetProcssID(ret5, 8)).value

    ret7 = ret6 + 0x8
    ret8 = ctypes.c_ulonglong(GetProcssID(ret7, 8)).value
    print(hex(ret8))
    return ret8


Game = pymem.Pymem("cloudmusic.exe")
lyrics_addr=get_add()#实际内存地址
lyrics_len=200
last_lyrics=b''


def b2u(b):
    """将 bytes 转换为 Unicode 字符串"""
    try:
        # 尝试使用 UTF-16-LE 解码,因为你的数据似乎是以这种格式存储的
        return b.decode('utf-16-le')
    except UnicodeDecodeError:
        # 如果 UTF-16-LE 解码失败,尝试其他编码
        try:
            return b.decode('utf-8')
        except UnicodeDecodeError:
            # 如果所有尝试都失败,返回一个错误消息或空字符串
            return "无法解码的字节序列"

def get_lyrics():
    global last_lyrics
    raw_bytes = Game.read_bytes(lyrics_addr, lyrics_len)
    use_bytes = raw_bytes.split(b'\x00\x00')[0]
    if len(use_bytes) % 2 == 1:
        use_bytes += b'\x00'
    
    if last_lyrics != use_bytes:
        last_lyrics = use_bytes
        return b2u(use_bytes)
    else:
        return None
 
while True:
    lyrics = get_lyrics()
    if lyrics is not None:
        print("原始歌词:", lyrics)
        # 获取 UTF-8 编码
        utf8_encoded = lyrics.encode('utf-8')
        print("UTF-8 编码:", utf8_encoded)
        # 如果你想看十六进制表示
        print("UTF-8 编码 (十六进制):", utf8_encoded.hex())

    time.sleep(0.1)


最终效果如下:

在此,特别鸣谢“30岁开始学编程的大叔”提供的非常好的基础代码。

基于Node.js,提供网易云所有API接口数据,包括:登录,获取用户信息 , 歌单,收藏,mv, dj 数量 获取用户歌单 获取用户电台 获取用户关注列表 获取用户粉丝列表 获取用户动态 获取用户播放记录 获取精品歌单 获取歌单详情 搜索 搜索建议 获取歌词 歌曲评论 收藏单曲到歌单 专辑评论 歌单评论 mv 评论 电台节目评论 banner 获取歌曲详情 获取专辑内容 获取歌手单曲 获取歌手 mv 获取歌手专辑 获取歌手描述 获取相似歌手 获取相似歌单 相似 mv 获取相似音乐 获取最近 5 个听了这首歌的用户 获取每日推荐歌单 获取每日推荐歌曲 私人 FM 签到 喜欢音乐 垃圾桶 歌单 ( 网友精选碟 ) 新碟上架 热门歌手 最新 mv 推荐 mv 推荐歌单 推荐新音乐 推荐电台 推荐节目 独家放送 mv 排行 获取 mv 数据 播放 mv/视频 排行榜 歌手榜 云盘 电台 - 推荐 电台 - 分类 电台 - 分类推荐 电台 - 订阅 电台 - 详情 电台 - 节目 给评论点赞 获取动态 热搜列表(简略) 发送私信 发送私信歌单 新建歌单 收藏/取消收藏歌单 歌单分类 收藏的歌手列表 订阅的电台列表 相关歌单推荐 付费精选接口 音乐是否可用检查接口 登录状态 获取视频播放地址 发送/删除评论 热门评论 视频评论 退出登录 所有榜单 所有榜单内容摘要 收藏视频 收藏 MV 视频详情 相关视频 关注用户 新歌速递 喜欢音乐列表(无序) 收藏的 MV 列表 获取最新专辑 听歌打卡 获取视频标签下的视频 已收藏专辑列表 获取动态评论 歌单收藏者列表 云盘歌曲删除 热门话题 电台 - 推荐类型 电台 - 非热门类型 电台 - 今日优选 心动模式/智能播放 转发动态 删除动态 分享歌曲、歌单、mv、电台、电台节目到动态 通知-私信 通知-评论 通知-@我 通知-通知 设置 云盘数据详情 私信内容 我的数字专辑 batch批量请求接口 获取视频标签列表 全部mv 网易出品mv 收藏/取消收藏专辑 专辑动态信息 热搜列表(详细) 更换绑定手机 检测手机号码是否已注册 初始化昵称 更新歌单描述 更新歌单名 更新歌单标签 默认搜索关键词 删除歌单 电台banner 用户电台 热门电台 电台 - 节目详情 电台 - 节目榜 电台 - 新晋电台榜/热门电台榜 类别热门电台 云村热评 电台24小时节目榜 电台24小时主播榜 电台最热主播榜 电台主播新人榜 电台付费精品榜 歌手热门50首歌曲
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值