Lazy loaded image
🌉让 PotPlayer 把完整的歌名、歌手和封面交给 Windows
字数 2393阅读时长≈ 6 分钟
2026-10-7
2026-10-6
Idea by BarryZed Blog & Code by Deepseek-flash & BarryZed

让 PotPlayer 把歌名、歌手、专辑和封面交给 Windows

事情起因是:我在 Windows 11 上用PotPlayer 听歌,Better Lyrics看歌词的时候不能正确识别,导致几乎只能用本地歌词(完全与文件名匹)。
花了一个天,我写了个小工具把这块补上了。过程比想象中曲折——中间撞了三次墙,最后一次墙还挺硬。这篇就按顺序把它记下来。

先说结论

Windows 上有个东西叫 SMTC(System Media Transport Controls,系统媒体传输控件),媒体浮窗、Discord、歌词工具读的都是它。如果你想知道你的播放器/系统中的SMTC状态,请参阅BarryZed的个人博客BarryZed的个人博客SMTC Reader:读出 Windows 媒体会话的全部字段 | BarryZed的个人博客。
PotPlayer 会创建这个会话,但填得很省:
属性
PotPlayer 的实际表现
Title
有值,但是带扩展名的文件名
歌手 / 专辑 / 专辑歌手 / 曲目号 / 曲目总数 / 流派
全空
封面
有,但是未压缩的 BMP,常见 1.5 MB 上下
PlaybackType
有,但放视频也报"音乐"
其余 30 来个字段(播放状态、倍速、循环随机、时间轴、可用操作)其实填得挺完整,我的工具也不去动它们。但是他全空的属性和 Title 直接写文件名导致BL无法正确识别歌曲信息。

为什么非得"钻进进程里"

SMTC 的读写接口是分开的:
  • 写接口 Windows.Media.SystemMediaTransportControls 是个进程内对象,只有创建它的进程能碰;
  • 读接口 GlobalSystemMediaTransportControlsSessionManager 清一色只读。
也就是说,系统压根没给"从外部改别人会话"这扇门。想让 PotPlayer 自己那个会话带上完整信息,代码就必须跑在 PotPlayer 进程内部——绕不过去。

撞墙第一回合:写个注入器

最直白的办法:写个注入器,把 DLL 塞进 PotPlayer。
能用。注入之后字段确实补上了,我当时挺高兴。
但很快发现两个别扭的地方:
  1. 得有东西一直待着。想做到"双击 PotPlayer 就自动生效",注入器就得开机自启、常驻后台等进程出现。多一个隐藏进程,总觉得脏。
  1. 时机太紧。PotPlayer 进程里 SMTC 对象只创建一次,注入晚了就抓不到创建过程,只能靠兜底手段硬捞。
于是我开始琢磨:能不能装一次、永久生效、后台什么都不留?

撞墙第二回合:改主程序的导入表

思路很漂亮:往 PotPlayerMini64.exe 的导入表里加一条,让 Windows 加载器每次启动 PotPlayer 时自动把我们的 DLL 载进去。没有常驻进程,干净。
写 PE 补丁的过程也是一路小坑(比如 IAT 数组必须按 INT 初值填,留全零会被加载器静默跳过整条导入),最后终于能正常加载了。然后我启动了 PotPlayer:
一开始我以为是补丁写错了,于是做了一组对照实验:
实验
结果
只翻转主程序末尾一个字节(证书表区域)
正常启动
尾部追加 4096 个零字节
拒绝启动,报同一个错
保持文件长度不变、只改导入表
仍然拒绝启动
用转发壳顶替 PotPlayer64.dll
PotPlayer64.dll is modified or hacked...
前两条说明它校验文件长度,第三条说明内容也在校验范围。第四条更有意思:PotPlayerMini64.exe 的导入表里有 WINTRUST.dll,它在加载核心 DLL 之前会验签名,而 PotPlayer64.dll、MediaDB64.dll 都带着 Kakao 的代码签名证书。
再看一眼主程序内部,.themida 和 .boot 两个节把话说明白了——Themida(WinLicense)加壳。
结论:PotPlayer 的文件一个字节都不能改。 这条路走到头是墙。

换思路:不动文件,动"系统怎么启动它"

既然文件不能碰,那就改启动方式。IFEO(Image File Execution Options)正好干这个:
注册表里写一个键,从此每次启动 PotPlayer,Windows 会先启动我的注入器。注入器干四件事:
  1. 用 DEBUG_ONLY_THIS_PROCESS 创建 PotPlayer。为啥要带这个标志?因为 IFEO 会拦所有同名进程——不加的话,注入器拉起的子进程又被拦回注入器,无限递归。实测日志里能看到清清楚楚的 level=0 → 1 → 2 → 3。
  1. 紧接着 DebugActiveProcessStop 脱离调试。用这个标志创建的进程由内核保持挂起,不脱离它一行代码都不跑;脱离之后才真正开始执行,而此时 PEB.BeingDebugged 已经是 0(我写了个小探针实测:BeingDebugged=0 NtGlobalFlag=0),Themida 的反调试看不到我们。
  1. 等目标加载器就绪再注入。刚创建的进程静态导入还没解析完,这时候 CreateRemoteThread(LoadLibraryW) 会让远程线程跳进未映射地址,直接把 PotPlayer 打崩——得轮询到 kernel32.dll 出现再动手。
  1. 注入完立刻退出。
装完的结果就是我要的那种"干净":无常驻进程、无服务、无开机启动项,不修改 PotPlayer 任何文件,它自己更新之后也不用重装,卸载就是删掉那个注册表键。
有个取舍值得说一下:注入用的四个文件我放在 %LOCALAPPDATA%\PotPlayerSmtcBridge\,没有塞进 PotPlayer 安装目录。一是 Program Files 受保护,规则文件每改一次都要过 UAC;二是放进 PotPlayer 目录的话,一旦它被卸载或更新清理,IFEO 就指向一个不存在的文件——PotPlayer 会直接打不开。
⚠️ 所以装完之后别手动删/挪安装目录里的文件。真删了也别慌,把那个 IFEO 键删掉就恢复正常。

补齐字段路上的一些细节

标签优先,文件名兜底。 取值顺序是:文件标签 → 文件名解析 → 留空。文件名解析给了几个占位符:
%title% %artist% %album% %albumArtist% %track% 都能用,其余字符按字面量匹配,%% 表示一个真正的百分号。
Genres 没有 setter。 这个属性只给了 get_Genres,返回一个 IVector<String>,只能拿到它自己 Append。也正因如此,每次都得记得先 Clear()。
切歌时的残留。 这是最容易漏的一处:如果只在"有新值"的时候写入,那么从一首带专辑标签的歌切到没标签的歌,Windows 里会一直挂着上一首的专辑、曲目号和流派。
修的时候还发现一个反直觉的点:写空字符串会被宿主忽略,必须传 NULL 才真的清掉。
封面沿用 PotPlayer 自己的。 它其实会抓封面,只是格式是 BMP、体积偏大,这部分我就没动。

几个踩坑的教训

  • 同一个界面里可能有两份重复代码。 有个按钮重叠的问题我改了七轮都没反应,最后用"标记法"——把按钮文字临时改成 MARK-A,重编看窗口上有没有出现——一分钟定位:我一直在改另一份没有显示的代码。
  • 验证编译产物要换着扫描方式。 .NET 的字符串堆里字符串可以落在奇数偏移,按偶数对齐整体解码会漏签,害我一度误判"改动没进产物"。
  • PowerShell 5.1 发含中文的 JSON 要显式 UTF-8。 Invoke-RestMethod 会按非 UTF-8 把字符串 body 发出去,服务端直接报 invalid UTF-8;改成传 [Text.Encoding]::UTF8.GetBytes($json) 就好了。
  • 把死路也写进文档。 仓库里 docs/06-ifeo-install.md 记的不只是最终方案,还有 Themida、签名校验那几条走不通的路和实测数据。别人(以及未来的我)不用再走一遍。

怎么用

当前版本 v0.8.0,三步:
  1. 点「安装启动注入」,过一次 UAC,之后照常启动 PotPlayer 就行。
界面和弹窗是中英双语的,日志是英文的——顺手把这两块也整齐了。
  • 发布包:v0.8.0 的 Release 附件(注入器、注入模块、配置界面、卸载脚本、文档)
  • 文档:README.md(英文)/ README.zh-CN.md(中文),另有几篇设计笔记放在 docs/
免责声明:这个项目通过进程注入修改 PotPlayer 的运行时行为,属于非官方个人自用工具,与 Kakao Corp. 无关。请仅在自有设备上使用,使用前请确认已理解注入带来的风险。
上一篇
可分离变量的微分方程
下一篇
SMTC Reader:读出 Windows 媒体会话的全部字段

评论
Loading...