最喜欢研究跟音乐相关的东西了,就像有的人爱喝酒吗,我离不开音乐,撸代码的时候,来点音乐,状态飙升,就跟晚上有人喜欢自己买点花生米小酌一下。 一直想做一个歌词输出的屏幕,心里暗暗合计了好一阵了,无非大致有如下几种思路: 一、买个副屏,显卡可识别的那种,然后把显示歌词的小窗拖过去即可,这种方法最简单,但是成本较高; 二、自己做个音乐播放器,就可以随便想怎么输出歌词了,这种方法工作量太大,需要自己用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,很占地方,这个看心情再说吧哈哈哈,懂原理了什么时候解决都不急。
|