kongling-music-player
管理"空灵本地音乐播放器"(KL-MUSIC-YK, MusicPlayer2 V2.78 分支)的配置、图标替换、皮肤 XML、打包/备份。
项目位置: E:\空灵本地音乐播放器\
关键路径
- EXE:
E:\空灵本地音乐播放器\空灵本地音乐播放器.exe - Config:
E:\空灵本地音乐播放器\config.ini - 皮肤 XML:
E:\空灵本地音乐播放器\skins\(根目录 + miniMode\ + panels) - 用户图标:
E:\空灵本地音乐播放器\播放器图标\(含 图标功能对照表.txt + IDI编号表.json) - 播放器背景:
E:\空灵本地音乐播放器\播放器背景\ - 备份目录:
E:\空灵本地音乐播放器\_备份_修改前\ - 项目记忆:
E:\空灵本地音乐播放器\.workbuddy\memory\2026-10-05.md
已完成的定制 (2026-10-05)
- 标题补丁: exe 内 "KL-MUSIC-YK" → "空灵-Music-Yk" (UTF-16LE 等长替换 9 处)
- 图标 2× 放大: .rdata 偏移 0x220040 的 standard_size 表 ×2
- 浮动播放列表: config.ini
playlist_btn_for_float_playlist = true - 界面优化: 5 个 XML 文件已补齐按钮 (stop/fullScreen/switchDisplay/favorite) 并加反射频谱
- BUG 修复: 删除损坏的
user_ui.dat(13B 截断,每次启动报 CArchiveException m_cause=3)
config.ini 关键开关
UI_selected = 1 ; 当前界面 (1=内置, 可切换到 5=空灵极光)
playlist_btn_for_float_playlist = true ; true=点击播放列表按钮向外展开浮动面板
float_playlist_width = 400 ; 浮动面板宽度
float_playlist_height = 602 ; 浮动面板高度
float_playlist_follow_main_wnd = true ; 跟随主窗口
default_background = E:\...\播放器背景\浅棕色磨砂玻璃.png
enable_background = true
background_gauss_blur = true
⚠️ 播放器退出时会回写 config.ini (UI_selected/default_background 可能被重置) → 改完必须通过软件内"切换界面"选择并保存,否则重启丢失。
自定义界面 (XML)
- 新建 XML 放入
skins/根目录 (UTF-8 无 BOM),启动时自动加载 - 合法 button key 全枚举:
dark light menu miniMode miniModeClose fullScreen repeatMode settings equalizer skin info find abRepeat desktopLyric lyricTranslate stop previous next playPause favorite mediaLib showPlaylist addToPlaylist switchDisplay darkLightMode locateTrack openFolder newPlaylist playMyFavourite medialibFolderSort medialibPlaylistSort karaoke showPlayQueue closePanel showPanel showHideElement - 常用元素:
button / albumCover / spectrum (type=64col|32col|16col|8col) / lyrics / progressBar / classicalControlBar / stackElement (hover_to_switch) / text (type=userDefine|artist_title|title|play_time_and_volume) / verticalLayout / horizontalLayout / rectangle (theme_color=true / color_mode=light|dark) - XSD 白名单校验脚本见下方代码块
图标替换工作流
- 把 PNG 放进
播放器图标/按图标功能对照表.txt命名 (或用 ALIAS 别名) - 运行
修复并换图标.py(需 Python 3.13) - 运行
验证图标.py生成图标渲染验证.png核对 - 重启播放器
⚠️ PE 修改铁律:
- 绝不物理删除节头字节 (d[:hdr]+d[hdr+40:] 会让后续所有节 PointerToRawData 错位)
- 解析资源必须在截断数据节之前完成 (截断后旧 DATA_ENTRY 指向已删区域)
- 新节追加到文件尾 + 对齐 SizeOfImage + 补齐文件到 FileAlignment
语言文件规范
- 3 个 ini (
Simplified_Chinese.ini/Traditional_Chinese.ini/English.ini): UTF-8 BOM DEFAULT_FONT在简体中文里是故意注释掉的 (;DEFAULT_FONT) → 字体走 config.ini 的font=幼圆TXT_OPT_DATA_UPDATE_SOURCE_MAIN等键 3 文件都有 → 不是缺失 (error.log 里的记录是过期的)
XSD 白名单校验脚本 (Python)
import xml.etree.ElementTree as ET
import re, glob
xsd = open('skins/skin.xsd', encoding='utf-8').read()
NAME = r'[\w-]+'
def attrs_of(ty):
m = re.search(r'<xs:complexType name="%s">(.*?)</xs:complexType>' % ty, xsd, re.S)
return re.findall(r'<xs:attribute name="(%s)"' % NAME, m.group(1)) if m else []
def group_of(gn):
m = re.search(r'<xs:attributeGroup name="%s">(.*?)</xs:attributeGroup>' % gn, xsd, re.S)
out=[]
for sub in re.findall(r'attributeGroup ref="(\w+)"', m.group(1)) if m else []:
out += group_of(sub)
out += re.findall(r'<xs:attribute name="(%s)"' % NAME, m.group(1)) if m else []
return out
shared = group_of('sharedattr'); listattr = group_of('listattr')
elems = set(re.findall(r'<xs:element name="(\w+)"', re.search(r'<xs:group name="elements">(.*?)</xs:group>', xsd, re.S).group(1))) | {'root','ui','panel'}
keys = set(re.findall(r'enumeration value="([a-zA-Z]+)"', re.search(r'<xs:complexType name="buttonType">.*?</xs:complexType>', xsd, re.S).group(0)))
LISTE = ('searchBox','medialibFolderExplore','playlist','recentPlayedList','mediaLibItemList','mediaLibFolder','mediaLibPlaylist','myFavouriteList','allTracksList','trackList')
def full(local):
return shared + (listattr if local in LISTE else [])
extra = {k: attrs_of(v) for k,v in {
'stackElement':'stackElementType','rectangle':'rectangleType','button':'buttonType','text':'textType',
'albumCover':'albumCoverType','spectrum':'spectrumType','progressBar':'progressBarType','lyrics':'lyricsType',
'volume':'volumeType','searchBox':'searchBoxType','medialibFolderExplore':'medialibFolderExploreType',
'trackInfo':'trackInfoType','elementSwitcher':'elementSwitcherType','icon':'iconType',
'placeHolder':'placeHolderType','classicalControlBar':'classicalControlBarType','navigationBar':'navigationBarType',
'navigationItem':'navigationItemType','beatIndicator':'beatIndicatorType','miniSpectrum':'miniSpectrumType'}.items()}
NSURI='{https://github.com/zhongyang219/KL-MUSIC-YK//skin}'; XSI='{http://www.w3.org/2001/XMLSchema-instance}'
for f in sorted(glob.glob('skins/**/*.xml', recursive=True)):
root=ET.parse(f).getroot(); problems=[]
def walk(el):
tag=el.tag; local=tag.replace(NSURI,'')
if not tag.startswith(XSI) and local not in elems: problems.append(f"未知元素 <{local}>")
allow=set(full(local)+extra.get(local,[]))
for an,av in el.attrib.items():
if an.startswith(XSI): continue
anl=an.replace(NSURI,'')
if anl not in allow: problems.append(f"<{local}> 非法属性 '{anl}'")
if anl=='key' and local=='button' and av not in keys: problems.append(f"<button key=\"{av}\"> 非合法key")
for c in el: walk(c)
walk(root)
print(f"{f}: {'✓' if not problems else '✗ '+str(problems)}")
常见操作
# 备份 config.ini
cp config.ini _备份_修改前/config.ini_日期.bak
# 验证 XML 合法性
python -c "import xml.etree.ElementTree as ET; [ET.parse(f) for f in ['skins/**/*.xml']]; print('OK')"
# 查看 error.log 最新条目
tail -20 error.log