- 桌面应用
- 移动开发
- 音视频
【免费下载链接】Nugget
Unlock the fullest potential of your device
导读
本文以 Nugget 官方文档 documentation.md 为骨架,系统讲解两类驱动 Nugget PosterBoard 壁纸功能的核心文件格式:Tendies 文件(PosterBoard 壁纸/描述符包,.tendies)与Batter 文件(可定制模板包,.batter)。读者将掌握两种文件格式的内部结构、配置清单(config.json)的 Header 与 Option 完整写法、基于nuggetId的 CoreAnimation XML 标识机制,以及 replace / set / remove / picker / bundle_id 五种选项类型的 JSON 规范与底层实现原理,最终能够独立制作、发布属于自己的 Nugget 壁纸与模板文件。
一、Tendies 文件:PosterBoard 壁纸包
Tendies 文件(壁纸包)本质上是存储"将要恢复到 PosterBoard 的文件结构"的压缩归档。Nugget 会读取包内的目录布局,把文件原样还原到设备的 PosterBoard 容器中。依据包内顶层目录的命名,Tendies 文件分为两种格式:
| 格式 | 顶层文件夹名 | 恢复行为 | UUID / 壁纸 ID |
|---|---|---|---|
| 容器格式(Container) | container | 直接恢复到/var/mobile/Containers/Applications/PosterBoard.app应用容器内,并保持原有文件结构 | 不会随机化 |
| 描述符格式(Descriptor) | descriptor或descriptors | 恢复到容器内的描述符(descriptors)目录 | 会随机化,防止冲突 |
1.1 容器格式(Container)
当包内出现名为container的文件夹时,Nugget 将该文件夹下的内容直接映射到 PosterBoard 应用容器(/var/mobile/Containers/Applications/PosterBoard.app)中,目录结构原样保留。由于不做任何改写,包内descriptor的 UUID 与壁纸 ID 也会原封不动地落到设备上。
需要特别警惕的是:如果容器格式的包内包含PBFPosterExtensionDataStoreSQLiteDatabase.sqlite3这一数据库文件,Nugget 会将其标记为unsafe_container,并在导入时弹出警告,提示"可能需要先重置所有壁纸(在设置中开启 Risky Options)并重新应用,该文件才能正常工作"。这一检测逻辑位于 tendie_file.py:扫描归档条目时若命中该文件名即置位危险标志。
1.2 描述符格式(Descriptor)
当包内顶层文件夹名为descriptor或descriptors时,Nugget 将其内容恢复到设备上的描述符目录。恢复的目标位置与系统版本相关:
- iOS 17.0 及以上:恢复到61号目录;
- iOS 16:恢复到59号目录。
这一版本差异在 posterboard_tweak.py 中体现:apply_tweak会按version.startswith("16")决定structure_version = 59,其余情况为61。文档同时指出:如果未来系统目录结构再次变化,Nugget 的后续版本可能会自动处理这一兼容问题。
描述符格式下,每个描述符的UUID 与壁纸 ID 都会被随机化,从而避免与设备上已存在的壁纸发生重叠冲突。随机化在recursive_add中实现:描述符文件夹被重命名为str(uuid.uuid4()).upper()形式的新 UUID,壁纸 ID 则通过randint(9999, 99999)生成,同时会改写以下标识文件以保持一致性(见 posterboard_tweak.py):
com.apple.posterkit.provider.descriptor.identifier:直接写入随机化 ID;com.apple.posterkit.provider.contents.userInfo:设置wallpaperRepresentingIdentifier键;Wallpaper.plist:非递归地设置identifier键。
1.2.1 有序描述符(ordered)
如果希望在集合(Collections)中让壁纸按 ID 有序排列,可以在文件夹名中加入ordered字样(例如ordered-descriptors)。此时 Nugget 会为同一目录下的所有描述符生成连续递减的 ID 序列:recursive_add先取一个随机基数r_id,生成sorted([r_id + i for i in range(len(os.listdir(curr_path)))], reverse=True)的列表,逐个分配给每个描述符(见 posterboard_tweak.py)。源码注释解释了原因:"PosterBoard 以逆序按壁纸 ID 排序壁纸",因此连续递减的 ID 能保证壁纸在集合中的展示顺序。
1.2.2 按扩展(Extension)区分恢复目标(Nugget v7.0+)
Nugget v7.0 起,描述符格式可以根据文件夹名中的关键词,将壁纸恢复到不同的 PosterBoard 扩展(extension)下:
| 文件夹名包含 | 目标扩展(providerId) |
|---|---|
video或photos | Suggested Photos(com.apple.PhotosUIPrivate.PhotosPosterProvider) |
mercury | Mercury Poster(com.apple.MercuryPoster) |
| (不加任何词) | Collections(com.apple.WallpaperKit.CollectionsPoster)——默认 |
对应的扩展识别逻辑位于 posterboard_tweak.py:文件夹名中命中video/photos或mercury时设置对应 provider,否则回落为com.apple.WallpaperKit.CollectionsPoster。恢复路径形如:
/Library/Application Support/PRBPosterExtensionDataStore/{61|59}/Extensions/{extension}/descriptors文档建议:如果目标是恢复到 Collections,优先采用描述符格式,因为随机化 ID 更安全,且对未来版本更具前瞻性。
1.3 Tendie 文件的识别与限制
在 GUI 层,tendie_file.py 通过扫描归档条目统计描述符数量(descriptor_cnt),并据此选择列表图标:容器格式用shippingbox.svg,单个描述符用photo.svg,多个描述符用photo-stack.svg。
同时 Nugget 对壁纸数量设有硬性限制:verify_tendie要求descriptor_cnt + 已有描述符数量 <= 10(见 posterboard_tweak.py),而 Tendies 页面最多同时导入3个.tendies文件(见 posterboard.py),超出即弹出安全提示。
二、Batter 文件:可定制模板包
Batter 文件(模板)与 Tendies 文件类似,同样存储"要恢复到某个 domain 的文件结构",但多了一层用户可定制能力:Nugget 会把包内Container文件夹中的文件结构恢复出去,并依据一个config.json文件在界面上渲染出可交互的选项(按钮、滑块、复选框、颜色选择器、下拉选择器等),让用户无需手工改文件即可定制壁纸。
对于 PosterBoard 模板,Batter 文件沿用了与 Tendies 相同的container/descriptor/descriptors文件夹格式。除 PosterBoard 之外,Batter 模板通过domain字段还可以作用于任意 AppDomain 甚至系统路径,因此其通用性远超壁纸场景(例如通过 sparserestore 写/var/Managed Preferences等系统位置)。
2.1 模板加载流程(源码视角)
TemplateFile 在加载时会:
- 在归档中查找
config.json(排除位于 descriptor / container 目录内的同名文件); - 校验
options与domain字段是否存在,缺失即抛出PBTemplateException; - 兼容 v5.2 beta 时期的旧写法:若
domain == "com.apple.PosterBoard",自动升级为"AppDomain-com.apple.PosterBoard"; - 校验
format_version:若大于当前格式版本CURRENT_FORMAT = 2(见 template_file.py),提示"该配置需要更新版本的 Nugget"; - 按
min_version/max_version与当前设备系统版本比对,版本不符时拒绝加载; - 解析
resources、previews、banner_text、banner_stylesheet等展示信息; - 逐个实例化
options中的选项对象(ReplaceOption/RemoveOption/SetOption/PickerOption/bundle_id)。
三、Batter 文件 Header:config.json 头部信息
config.json的头部携带模板的基础信息,分为必填与可选两类。
3.1 必填字段
| 字段 | 说明 |
|---|---|
title | 操作(模板)的标题,显示在界面上 |
author | 作者姓名 |
domain | 恢复目标 domain。PosterBoard 模板固定填AppDomain-com.apple.PosterBoard;若要使用 sparserestore 能力,填Sparserestore-<FILEPATH>,例如Sparserestore-/var/Managed Preferences |
format_version | 配置格式的最低版本要求;当前 Nugget 最新版本对应的值为"format_version": "2" |
options | 用户可配置选项列表(格式见下文 Option Format 一节);不需要任何可配置项时写"options": [] |
其中domain与format_version的强校验逻辑见 template_file.py;Sparserestore-域在 templates_tweak.py 中被特殊处理:恢复路径直接拼接Sparserestore-前缀后的绝对路径,且不再使用 AppDomain 恢复方式。
3.2 可选字段
| 字段 | 说明 |
|---|---|
description | 显示在标题下方的说明文字 |
min_version | 模板支持的最低 iOS 版本(如"min_version": "18.0") |
max_version | 模板支持的最高 iOS 版本(如"max_version": "18.0") |
resources | 内嵌资源文件列表(如 banner / preview 图片等),支持通配符 |
previews | 预览图片文件名列表;这些文件的路径必须同时出现在resources列表中才会被展示 |
preview_layout | 预览图的排布方式,默认"horizontal";合法值:"horizontal"(横向排列)、"stacked"(叠放,多个预览图叠在一起展示) |
banner_text | banner 横幅的文本内容 |
banner_stylesheet | banner 的 Qt 样式表(style sheet) |
min_version/max_version可只写其一、都不写或都写。加载时 Nugget 会与设备系统版本做Version()比较,不符合则拒绝应用(见 template_file.py)。
resources的落地逻辑在 template_file.py:使用fnmatch.filter支持通配符匹配,将资源解压到临时目录;如果设置了banner_stylesheet,其中的url(<resource>)引用会被改写为临时文件的实际路径;previews中列出的资源则被登记为预览图。preview_layout为"stacked"时,界面使用QStackedLayout叠放预览图(见 template_file.py)。
四、Option Format:选项通用格式
options数组中每个选项都必须包含三个必备属性:
| 属性 | 说明 |
|---|---|
type | 选项类型(各类型的取值见下文"选项类型"一节) |
label | 在 Nugget 界面中显示的文字 |
files | 相对于config.json所在目录(其父目录)的文件路径列表 |
associated_preview(可选) | 与该选项关联的预览图名称。对部分选项会产生特殊效果:set(如颜色)会应用视觉效果,remove/picker会隐藏图片;对picker类型,每个子选项都必须带此标签 |
associated_preview的绑定逻辑见 template_options.py:存在associated_preview且与预览图名称匹配时,该选项获得对应QLabel的引用,remove选项通过显隐预览图实时反馈效果(见 remove_option.py)。
4.1 标识符(Identifiers):用 nuggetId 精确改写 .caml
为了告诉 Nugget 需要修改.caml文件中的哪些属性,你可以在目标 XML 标签上添加一个特殊的nuggetId属性,赋值为任意合法字符串(不一定是数字),例如:
<CALayer nuggetId="1" id="#1" allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 -28 520 563" contentsFormat="RGBA8" cornerCurve="circular" hidden="0" name="Thing" position="195 365" transform="" zPosition="442"> <sublayers/> </CALayer>注意:添加nuggetId需要你用文本编辑器手动修改.caml文件。随后在config.json的选项里,通过identifier标签指定这个字符串值即可定位到该 XML 节点。files中的路径应直接指向.caml文件,而不是.ca文件。
如果你希望使用 CoreAnimation 原生的id属性(即 XML 中的id标签)作为定位依据,可以在 JSON 中设置use_ca_id为true。
底层实现位于 xml_handler.py:set_xml_values会根据use_ca_id选择idKey为"id"或"nuggetId",然后通过 XPath//*[@<idKey>='<id>']找到所有匹配节点并写入属性值;当use_ca_id为true时,还会额外处理targetId指向的节点。布尔值会被自动转为0/1整数写入。删除节点时则使用delete_xml_value(见 xml_handler.py)。
4.1.1 偏移方程(nuggetOffset)
你还可以在.caml属性上添加nuggetOffset标签,使 Nugget 在应用时将用户的输入值按数学方程偏移后写入。该标签的格式类似数学表达式,其中x代表用户输入的单值;对于多值属性(如位置),可用x、y、z、a分别代表各分量。例如:
<CALayer nuggetId="1" nuggetOffset="(2 * x + 10), (3 * y + 15)" id="#1" allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 -28 520 563" contentsFormat="RGBA8" cornerCurve="circular" hidden="0" name="Thing" position="195 365" transform="" zPosition="442"> <sublayers/> </CALayer>规则要点:
- 每个分量之间用逗号分隔;
- 使用
nuggetOffset必须同时有nuggetId(即该标签依赖nuggetId定位); - 底层
parse_equation(见 xml_handler.py)会把用户输入按空格拆分为x/y/z/a数组,对每个逗号分隔的方程做求值,再以空格连接回写。
注意:从源码结构看,偏移方程目前仅在第一个 key上生效(xml_handler.py中留有 "TODO: Allow offsets for more than just the first value" 注释)。
五、选项类型(Option Types)
5.1 Replace Files(替换文件)
"type": "replace"| 属性 | 说明 |
|---|---|
button_label(可选) | 导入按钮上显示的文字 |
allowed_files | 允许导入的文件类型与扩展名,遵循 QT 过滤器格式,如"Image Files (*.png)" |
required | 是否为必选项。若为true而用户未选择文件,应用模板时会直接报错 |
界面表现为"导入按钮 + 已选文件路径"(见 replace_option.py):默认按钮文字为Import {allowed_files},点击后弹出QFileDialog选择文件;选择后按钮变为 "Remove Selected File" 并可撤销。应用时(见 replace_option.py),用户所选文件的内容会被逐字节写入files列表中的每一个目标文件(支持通配符,通过glob.glob(..., recursive=True)展开);若required为真且未选文件,则抛出NuggetException。
5.2 Set Values(设置属性值)
"type": "set"| 属性 | 说明 |
|---|---|
identifier | XML 文件中的标识(对应nuggetId的值) |
use_ca_id(可选) | 是否改用 CoreAnimation 的id而非nuggetId |
key | 要修改的 XML 属性名(key/tag) |
default_value(可选) | 输入的默认值 |
value_type(可选) | 值的类型,合法值:"integer"、"float"、"string" |
setter_type | 用户看到的输入控件类型,合法值:"textbox"、"slider"、"toggle"、"color_picker" |
Slider(滑块)专属选项:
| 属性 | 说明 |
|---|---|
min_value | 允许输入的最小值(slider必填,其他控件可选) |
max_value | 允许输入的最大值(slider必填,其他控件可选) |
step(可选) | 滑块每次变化的间隔 |
Toggle(开关)专属选项:
| 属性 | 说明 |
|---|---|
inverted(可选) | 为true时,写入文件的值取反 |
toggle_off_value(可选) | 开关关闭时写入的值(用于非布尔类型) |
toggle_on_value(可选) | 开关打开时写入的值 |
Color Picker(颜色选择器)专属选项:
| 属性 | 说明 |
|---|---|
sets_opacity(可选) | 是否同时写入透明度;若default_value中提供了 4 个值(RGBA),会自动推断为true |
补充细节:
- 值在
.caml中是什么格式,JSON 里就应保持什么格式;如果是浮点数,在 Nugget 中仍需带引号,即"1.0"; nuggetOffset方程的用法见上文"偏移方程"一节;identifier是必填属性(见 set_option.py)。
实现要点(set_option.py):
- slider的
min_value/max_value为必填;若value_type为"float",内部会按×1000的整数刻度驱动QSlider(convert_float),并用correct_slider_pos把滑块位置校正到step的整数倍(见 set_option.py); - toggle默认值为
false;应用时若inverted为真则取反,再按toggle_on_value/toggle_off_value映射(见 set_option.py); - color_picker从
default_value解析颜色:支持"r g b"或"r g b a"四种分量,自动识别 alpha;界面使用透明棋盘底图叠上当前色(get_stylesheet生成rgba(...)样式),点击弹出QColorDialog; - 应用时根据目标文件类型分支处理(见 set_option.py):
.caml/.xml:调用set_xml_value写入属性;若sets_opacity且值为QColor,则同时写入opacity键;.plist:调用write_plist_value写入键值;- 其他纯文本文件:直接对文件内容做字符串替换。
5.3 Remove Files / Values(删除文件或属性)
"type": "remove"| 属性 | 说明 |
|---|---|
inverted(可选) | 为true时,仅在复选框未勾选时才删除文件 |
default_value(可选) | 复选框的初始状态(默认false) |
identifier(可选) | XML 文件中的标识;只有需要删除属性或图层时才填 |
use_ca_id(可选) | 是否使用 CoreAnimationid而非nuggetId |
label属性会直接作为复选框的文字显示(见 remove_option.py)。
应用逻辑(remove_option.py):当(inverted 且未勾选)或(非 inverted 且勾选)时触发删除——若指定了identifier,调用delete_xml_value删除 XML 中对应属性或节点;否则删除files中的文件(目录用rmtree,文件用os.remove)。
5.4 Picker(选择器)
"type": "picker"| 属性 | 说明 |
|---|---|
options | 选择器中展示的子选项列表,每个子选项包含一个label和一组files |
allow_multiple_selection(可选) | 是否允许多选 |
rename(可选) | 是否将用户选中的文件重命名(allow_multiple_selection为true时不生效) |
names(可选,rename为true时必填) | 重命名后的新文件名列表,顺序必须与options中files的顺序一致 |
default_value(可选) | 默认选中的下标,从 0 开始,如"default_value": 0;多选时改用"default_values": [0, 1, 2] |
行为约定:当用户从选择器中选中某个选项后,应用时其他所有未被选中的选项对应的文件都会被删除(见 picker_option.py)。因此 picker 适合"多选一 / 多选若干"的互斥场景,例如在几套背景 / 前景素材中挑选一套。
界面层:单选使用QComboBox,多选使用 Nugget 自带的MultiComboBox组件(见 picker_option.py)。应用时(picker_option.py):先删除未选中子选项的所有文件,再按names列表将选中文件重命名(目录用shutil.move,文件用os.rename)。若子选项带有associated_preview,预览图会随选择实时显隐(update_preview)。
5.5 Bundle Id(应用包 ID)
"type": "bundle_id"属性:无。
该选项仅对AppDomain-前缀的模板生效:它允许用户在界面中输入自定义的 App Bundle ID,模板随后恢复到该应用容器中。默认值取自domain字段中AppDomain-之后的部分(见 template_file.py 与 templates_tweak.py:应用时若用户修改了 bundle id,domain 会被替换为AppDomain-{新ID})。
注意:修改 Bundle ID 后,恢复时以相同名字开头的文件也会被一并改写(文档原话强调),即对路径中前缀匹配的应用容器文件生效,使用前需确认影响范围。
六、完整示例:一个典型的 config.json
以下为官方文档给出的完整config.json示例,覆盖了 replace、remove(普通 / 反选 / 删属性)、picker(带 rename)与 set(slider / toggle)全部类型:
{ "title": "My Batter", "description": "I love Nugget", "author": "lemin", "format_version": "2", "domain": "AppDomain-com.apple.PosterBoard", "options": [ { "type": "replace", "label": "Background Image", "button_label": "Select Image", "allowed_files": "Image Files (*.png)", "required": true, "files": ["descriptors/UUID/version/1/bg/assets/image.png", "descriptors/UUID/version/1/fg/assets/image2.png"] }, { "type": "remove", "label": "File Thing Visible", "files": ["descriptors/UUID2/version/1/bg/assets/file1.png"] }, { "type": "remove", "label": "Include collection item", "files": ["descriptors/UUID3"], "inverted": true, "default_value": true }, { "type": "picker", "label": "Choose Things", "rename": true, "names": ["descriptors/UUID/background.ca", "descriptors/UUID/foreground.ca"], "options": [ { "label": "Option 1", "files": ["descriptors/UUID/bg1.ca", "descriptors/UUID/fg1.ca"] }, { "label": "Option 2", "files": ["descriptors/UUID/bg2.ca", "descriptors/UUID/fg2.ca"] }, { "label": "Option 3", "files": ["descriptors/UUID/bg3.ca", "descriptors/UUID/fg3.ca"] } ] }, { "type": "remove", "label": "Show Object", "files": ["descriptors/UUID/version/1/fg.ca/main.caml"], "inverted": true, "default_value": true, "identifier": "1" }, { "type": "set", "label": "Animation Duration", "files": ["descriptors/UUID/version/1/fg.ca/main.caml"], "setter_type": "slider", "min_value": 0.5, "max_value": 3.0, "step": 0.1, "default_value": 1.0, "key": "duration", "identifier": "2" }, { "type": "set", "label": "Flip Geometry", "files": ["descriptors/UUID/version/1/fg.ca/main.caml"], "setter_type": "toggle", "inverted": false, "default_value": false, "key": "geometryFlipped", "identifier": "3" } ] }示例解读:
"format_version": "2"与"domain": "AppDomain-com.apple.PosterBoard"是当前版本的标准头部;replace选项要求用户必须选择一张 PNG 图片(required: true),并同时替换背景与前景两张图片;- 第二个
remove为"反选删除":复选框默认勾选(default_value: true),勾选时不删除(inverted: true),取消勾选才删除整个descriptors/UUID3目录; picker三选一,选中的.ca文件会被重命名为background.ca/foreground.ca;set选项分别用 slider 修改duration属性、用 toggle 修改geometryFlipped属性,均通过nuggetId("2"、"3")定位.caml中的节点。
七、从文件到设备:应用链路速览
理解了两类文件的格式之后,可以把"打包 → 导入 → 应用"的完整链路串起来(均可在仓库源码中验证):
- 打包:Tendies 包内按
container/descriptor/descriptors组织目录;Batter 包额外携带config.json(以及可选的resources预览图 / banner 素材)。 - 导入解析:TendieFile 识别格式与描述符数量;TemplateFile 校验头部字段并实例化全部选项。
- 界面交互:TemplateFile.create_ui 依据
resources/previews/banner_text/banner_stylesheet渲染横幅、预览图与各选项控件;bundle_id类型额外生成 Bundle ID 输入框。 - 应用生成:PosterboardTweak.apply_tweak 先按系统版本选定
structure_version(61 / 59),解压 tendies 与模板,再通过recursive_add递归遍历:容器格式原样映射,描述符格式随机化 UUID 与 ID、按文件夹名路由到对应扩展;set/remove选项在此过程中调用 xml_handler.py 完成.caml改写。 - 恢复:最终每个文件被包装为 FileToRestore,由
AppDomain-com.apple.PosterBoard(或 sparserestore 路径)恢复机制写入设备。
Tendies 与 Batter 两类文件共同构成了 Nugget PosterBoard 生态的开放文件规范:前者解决"壁纸包怎么分发",后者解决"壁纸包怎么被用户定制"。理解了这两套格式,你就可以像社区壁纸作者一样,产出既美观又可交互的 Nugget 壁纸与模板。
附:仓库相关文件索引
- 官方格式文档:documentation.md
- Tendie 解析:src/tweaks/posterboard/tendie_file.py
- 模板解析与 UI:src/tweaks/posterboard/template_file.py
- 选项基类与类型枚举:src/tweaks/posterboard/template_options/template_options.py
- 四种选项实现:replace_option.py、set_option.py、remove_option.py、picker_option.py
- PosterBoard 应用逻辑:src/tweaks/posterboard/posterboard_tweak.py
- XML(.caml)改写引擎:src/controllers/xml_handler.py
- PosterBoard 页面与导入限制:src/gui/pages/tools/posterboard.py
- 恢复文件模型:src/restore/restore.py
- 桌面应用
- 移动开发
- 音视频
【免费下载链接】Nugget
Unlock the fullest potential of your device
相关推荐
Nugget PosterBoard深度解析:从视频到壁纸的完整转换流程
想要为你的iOS设备添加独特的动态壁纸吗?Nugget PosterBoard功能让你可以将任何视频文件转换为炫酷的动画壁纸!🎬 这个强大的工具不仅支持视频转
桌面应用移动开发音视频AirCard-iOS .tendies壁纸刷写教程:注入PosterBoard壁纸,不重启iPhone即刻生效
AirCard iOS .tendies壁纸刷写教程:注入PosterBoard壁纸,不重启iPhone即刻生效 AirCard iOS 是一款免越狱的 iOS
Nugget模板系统详解:如何创建自定义操作和batter文件
Nugget模板系统详解:如何创建自定义操作和batter文件 Nugget是一款强大的iOS设备个性化工具,其模板系统让用户能够轻松创建和部署自定义操作。通过
桌面应用移动开发音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考