admin管理员组

文章数量:1602099

最喜欢研究跟音乐相关的东西了,就像有的人爱喝酒吗,我离不开音乐,撸代码的时候,来点音乐,状态飙升,就跟晚上有人喜欢自己买点花生米小酌一下。
一直想做一个歌词输出的屏幕,心里暗暗合计了好一阵了,无非大致有如下几种思路:
一、买个副屏,显卡可识别的那种,然后把显示歌词的小窗拖过去即可,这种方法最简单,但是成本较高;
二、自己做个音乐播放器,就可以随便想怎么输出歌词了,这种方法工作量太大,需要自己用pyqt写个音乐播放器,我又有强迫症,界面太丑不干,得花费好一阵;
三、想办法获取电脑桌面歌词,输出到单片机,这种方法比较好,但是技术要求高一些,还是想采用这种方法。
如何获取电脑屏幕上的歌词呢?
最先想到的,截图,然后获取直接OCR。。。太笨而且费资源,然后看到python其实可以操作内存,有人用python写了植物大战僵尸的修改器,受到启发,直接通过查找内存找到歌词,然后随便怎么输出都行了。
奥里给,开始干,没想到网上有用的资料太少,然后自己汇编、内存了解的比较少,这一做就是一整天,好在终于搞定了。

首先,任何数据都在内存里,最直观的就是游戏数据,血量,金钱之类的,小时候应该很多人都用过金山游侠修改数据,就是那套原理,那么歌词作为文本,也是数据,为啥我不找找呢,于是搞了个CE打法,先显示英文的歌词,一直查找第一位字母的ASCII码,果然找到了,歌词不是什么敏感数据,一般也不会加密之类的,所以很典型很顺畅的找到了。

然后,网上教程说用OD去找偏移量,其实CE也可以搞定,一顿顺腾摸瓜,最最最重要的偏移量他来了,上图:

可以很清楚看到实时显示歌词的地址是怎么来的,从cloudmusic.dll的基址开始,经过三次偏移得道,当然最后一次是0,可以不用算。
原理知道了,愣着干嘛,一顿操作如虎,搞定了,这里,网易云音乐歌词的规则也被我看出来了,每个字,不管是英文还是中文,都占用两个bytes,中文用的是unicode编码,两个字符高低位反过来,如原来是\x34\x12就变成u1234,就行了,这里网上居然没找到现成的转换方式,网上找点有点的东西是真的费劲。。。于是自己手动写了坨屎山,转换了。英文就是\x00接ascii吗,如果遇到连续两个\x00\x00视为词句歌词结束,现在规则全看不透了,搞定。

这样就做好了,感觉干了件大事,网上没有相关资料代码,全靠自己摸索哦
上代码:

#2022-10-15 by jd3096 vx:jd3096
import pymem
import time
import socket
import win32process
from win32con import PROCESS_ALL_ACCESS 
import win32api
import ctypes
from win32gui import FindWindow

def GetProcssID(address,bufflength):
    pid = ctypes.c_ulong() 
    kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
    hwnd = FindWindow("DesktopLyrics", u"桌面歌词")#获取窗口句柄
    hpid, pid = win32process.GetWindowThreadProcessId(hwnd)#获取窗口ID
    hProcess = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, pid)#获取进程句柄
    ReadProcessMemory = kernel32.ReadProcessMemory
    addr = ctypes.c_ulong()
    ReadProcessMemory(int(hProcess), address, ctypes.byref(addr), bufflength, None)#读内存
    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")
    addr = GetProcssID((Char_Modlue + 0xAF7C44),4)
    ret = addr + 0xc8
    ret2 = GetProcssID(ret, 4)
    ret3 = ret2 + 0x14
    ret4 = GetProcssID(ret3, 4)
    print (hex(ret4))
    return ret4


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


def B2Q(uchar):
    """单个字符 半角转全角"""
    inside_code = ord(uchar)
    if inside_code < 0x0020 or inside_code > 0x7e: # 不是半角字符就返回原来的字符
        return uchar 
    if inside_code == 0x0020: # 除了空格其他的全角半角的公式为: 半角 = 全角 - 0xfee0
        inside_code = 0x3000
    else:
        inside_code += 0xfee0
    return chr(inside_code)

def stringB2Q(ustring):
    """把字符串强行全角"""
    return "".join([B2Q(uchar) for uchar in ustring])

def b2u(b):    #bytes转unicode方法 我感觉应该有现成的函数,就是找不到好气,只能写了坨屎山凑合用
    length=len(b)
    sr=''
    for i in range(0,length,2):
        b0=hex(b[i])
        b1=hex(b[i+1])
        if b0=='0x0':      #第一位如果是0说明不是中文,中文占2bytes
            s1=chr(b[i+1])
            sr+=s1
        else:
            if len(b0)==4:
                s0=str(b0[2:4])
            else:
                s0='0'+str(b0[3])
            if len(b1)==4:
                s1=str(b1[2:4])
            else:
                s1='0'+str(b1[2])
            s=s0+s1
            result=b'\x5c\x75'
            for ss in s:
                bb=ss.encode()
                result+=bb
            sr+=result.decode("unicode_escape")
    return sr


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'
    lyrics_bytes=b''
    for i in range(0,lyrics_len,2):  #构建bytes 这里高低位需要调换一下顺序
        b1=use_bytes[i:i+1]
        b2=use_bytes[i+1:i+2]
        lyrics_bytes+=b2+b1
    if last_lyrics!=lyrics_bytes:   #检查看词是否变化
        last_lyrics=lyrics_bytes
        return b2u(lyrics_bytes)
    else:
        return None

def sendto(lyric):
    quan=stringB2Q(lyric)
    data=quan.encode('gbk')
    return data
    
# tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# tcp_server.bind(("", 30960))
# tcp_server.listen(5)
# tcp_client, tcp_client_address= tcp_server.accept()
    
while True:
    lyrics=get_lyrics()
    if lyrics!=None:
        print(lyrics)
        data_send=sendto(lyrics)
        #tcp_client.send(data_send)
    time.sleep(0.1)

中间注释的部分就是可以通过tcp发送歌词数据到任意单片机上,老本行回归,也可以用串口,随便发挥,上俩做好的图:

K210 FFT加歌词输出

之前提到的中景园oled带字库那个屏幕,用TCP实现。

这次真的是爽爆了,完全实时同步,随便切歌,拉进度,歌词永远同步。
不过没有彻底完善,比如遇到日文韩文等显示不了,英文强行被我转GBK,很占地方,这个看心情再说吧哈哈哈,懂原理了什么时候解决都不急。

本文标签: 重磅网易桌面内存歌词