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的个人博客SMTC Reader:读出 Windows 媒体会话的全部字段 | BarryZed的个人博客
。

SMTC Reader:读出 Windows 媒体会话的全部字段 | BarryZed的个人博客
因为没人告诉我某个播放器到底通过 SMTC 报了什么,所以写了个工具自己去问。
PotPlayer 会创建这个会话,但填得很省:
属性 | PotPlayer 的实际表现 |
Title | 有值,但是带扩展名的文件名 |
歌手 / 专辑 / 专辑歌手 / 曲目号 / 曲目总数 / 流派 | 全空 |
封面 | 有,但是未压缩的 BMP,常见 1.5 MB 上下 |
PlaybackType | 有,但放视频也报"音乐" |
其余 30 来个字段(播放状态、倍速、循环随机、时间轴、可用操作)其实填得挺完整,我的工具也不去动它们。但是他全空的属性和
Title 直接写文件名导致BL无法正确识别歌曲信息。为什么非得"钻进进程里"
SMTC 的读写接口是分开的:
- 写接口
Windows.Media.SystemMediaTransportControls是个进程内对象,只有创建它的进程能碰;
- 读接口
GlobalSystemMediaTransportControlsSessionManager清一色只读。
也就是说,系统压根没给"从外部改别人会话"这扇门。想让 PotPlayer 自己那个会话带上完整信息,代码就必须跑在 PotPlayer 进程内部——绕不过去。
撞墙第一回合:写个注入器
最直白的办法:写个注入器,把 DLL 塞进 PotPlayer。
能用。注入之后字段确实补上了,我当时挺高兴。
但很快发现两个别扭的地方:
- 得有东西一直待着。想做到"双击 PotPlayer 就自动生效",注入器就得开机自启、常驻后台等进程出现。多一个隐藏进程,总觉得脏。
- 时机太紧。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 会先启动我的注入器。注入器干四件事:
- 用
DEBUG_ONLY_THIS_PROCESS创建 PotPlayer。为啥要带这个标志?因为 IFEO 会拦所有同名进程——不加的话,注入器拉起的子进程又被拦回注入器,无限递归。实测日志里能看到清清楚楚的level=0 → 1 → 2 → 3。
- 紧接着
DebugActiveProcessStop脱离调试。用这个标志创建的进程由内核保持挂起,不脱离它一行代码都不跑;脱离之后才真正开始执行,而此时PEB.BeingDebugged已经是 0(我写了个小探针实测:BeingDebugged=0 NtGlobalFlag=0),Themida 的反调试看不到我们。
- 等目标加载器就绪再注入。刚创建的进程静态导入还没解析完,这时候
CreateRemoteThread(LoadLibraryW)会让远程线程跳进未映射地址,直接把 PotPlayer 打崩——得轮询到kernel32.dll出现再动手。
- 注入完立刻退出。
装完的结果就是我要的那种"干净":无常驻进程、无服务、无开机启动项,不修改 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,三步:
- 点「安装启动注入」,过一次 UAC,之后照常启动 PotPlayer 就行。
界面和弹窗是中英双语的,日志是英文的——顺手把这两块也整齐了。
- 发布包:
v0.8.0的 Release 附件(注入器、注入模块、配置界面、卸载脚本、文档)
- 文档:
README.md(英文)/README.zh-CN.md(中文),另有几篇设计笔记放在docs/
免责声明:这个项目通过进程注入修改 PotPlayer 的运行时行为,属于非官方个人自用工具,与 Kakao Corp. 无关。请仅在自有设备上使用,使用前请确认已理解注入带来的风险。
- 整理:BarryZed
- 链接:
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。








