mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
devicetree: Add 8 identity-rewrite properties on EXP variant at fw_patch time
Splits `DeviceTreePatcher`'s property-patch list into two arrays:
- `basePropertyPatches` (4 entries: `serial-number`,
`home-button-type`, `artwork-device-subtype`,
`island-notch-location`) — applied for every variant. Behaviour
identical to pre-split.
- `identityPropertyPatches` (8 entries — Tier 1b + 1c userland-facing
identity surfaces) — applied only when `includeIdentityPatches` is
true, which `FirmwarePipeline` sets exactly when `variant == .exp`.
The 8 EXP-only identity properties flip userland-visible identity toward
D47AP / iPhone17,3:
- Tier 1b (5 properties, slot-length-preserving rewrites):
device-tree.target-sub-type: VPHONE600AP -> D47AP
device-tree.compatible[1]: iPhone99,11 -> iPhone17,3
(reorder, VPHONE600AP kept first
so IOKit's AppleVMApple1IO bind
still resolves)
device-tree/product.fdr-product-type: iPhone99,11 -> iPhone17,3
device-tree/product.sub-product-type: iPhone99,11 -> iPhone17,3
device-tree/product.unique-model: VPHONE600AP -> D47AP
- Tier 1c (3 properties — IOKit secondary matchers + Gestalt subtree
rename, matched against the real D47AP DT):
device-tree/arm-io.device_type: vresearch1-io -> t8140-io
device-tree/arm-io.soc-generation: VResearch1 -> H17
device-tree/product/vphone600-gestalt-variants.name (node rename):
vphone600-gestalt-variants
-> d47-gestalt-variants
Root `model` and root `target-type` are deliberately NOT in this list —
both have been empirically shown to break restore (signed-identity
cross-check in `restored_external`). Those edits run post-restore in a
later commit as EXP-JB-6.
- `sources/FirmwarePatcher/DeviceTree/DeviceTreePatcher.swift` — adds
`includeIdentityPatches: Bool = false` to init (backwards-compatible
default), stores it, splits `propertyPatches` into two static lists,
iterates base first and then optionally identity in `applyPatches`.
- `sources/FirmwarePatcher/Pipeline/FirmwarePipeline.swift` — passes
`includeIdentityPatches: variant == .exp` into the DT factory.
JB and other variants (regular, dev, less) leave the device's identity
properties untouched.
This commit is contained in:
@@ -15,6 +15,12 @@ public final class DeviceTreePatcher: Patcher {
|
||||
public let component = "devicetree"
|
||||
public let verbose: Bool
|
||||
|
||||
/// Whether to apply the 8 identity-rewrite property patches (Tier 1b + 1c)
|
||||
/// that flip device identity towards iPhone17,3 / D47AP. Enabled only for
|
||||
/// the `.exp` variant; all other variants run the base 4 patches only so
|
||||
/// they remain unaffected by the experimental identity rewrites.
|
||||
let includeIdentityPatches: Bool
|
||||
|
||||
let buffer: BinaryBuffer
|
||||
var patches: [PatchRecord] = []
|
||||
var rebuiltData: Data?
|
||||
@@ -36,10 +42,48 @@ public final class DeviceTreePatcher: Patcher {
|
||||
enum PropertyValue {
|
||||
case string(String)
|
||||
case integer(UInt64)
|
||||
/// Raw bytes — used when the property holds a multi-string blob
|
||||
/// (NUL-delimited cstrings packed back-to-back, e.g. `compatible`)
|
||||
/// where Swift String escaping of embedded NULs is awkward.
|
||||
case bytes(Data)
|
||||
}
|
||||
|
||||
/// Fixed set of device tree patches, matching scripts/dtree.py PATCHES.
|
||||
static let propertyPatches: [PropertyPatch] = [
|
||||
/// Multi-string `compatible` blob used by patch #3 (root `compatible`).
|
||||
///
|
||||
/// Original layout (48 bytes):
|
||||
/// "VPHONE600AP\0" "iPhone99,11\0" "AppleVirtualPlatformARM\0"
|
||||
/// 11 + 1 11 + 1 23 + 1 = 48
|
||||
///
|
||||
/// Patched layout (48 bytes, surgical change of the middle string only):
|
||||
/// "VPHONE600AP\0" "iPhone17,3\0" "AppleVirtualPlatformARM\0\0"
|
||||
/// 11 + 1 10 + 1 23 + 2 = 48
|
||||
///
|
||||
/// `VPHONE600AP` stays as the FIRST entry so IOKit's platform-expert
|
||||
/// matching at boot still binds against the kext that claims it. The
|
||||
/// SECOND entry, which userland walks of the compatible list see when
|
||||
/// iterating to enumerate alternate identifiers, is flipped to
|
||||
/// `iPhone17,3`. The trailing `AppleVirtualPlatformARM` shifts one byte
|
||||
/// earlier (now starts at byte 23 instead of 24), but every consumer of
|
||||
/// `compatible` walks by NUL-terminator — none depend on a fixed byte
|
||||
/// offset within the blob — so the shift is harmless.
|
||||
static let compatibleRewrite: Data = {
|
||||
var d = Data()
|
||||
d.append(contentsOf: Array("VPHONE600AP".utf8))
|
||||
d.append(0)
|
||||
d.append(contentsOf: Array("iPhone17,3".utf8))
|
||||
d.append(0)
|
||||
d.append(contentsOf: Array("AppleVirtualPlatformARM".utf8))
|
||||
d.append(0)
|
||||
// 11+1 + 10+1 + 23+1 = 47 bytes so far; pad with one NUL to 48.
|
||||
d.append(0)
|
||||
return d
|
||||
}()
|
||||
|
||||
/// Base device-tree property patches, applied for every variant.
|
||||
/// Matches the pre-experimental set (serial-number, home-button-type,
|
||||
/// artwork-device-subtype, island-notch-location) inherited from
|
||||
/// scripts/dtree.py PATCHES.
|
||||
static let basePropertyPatches: [PropertyPatch] = [
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree"],
|
||||
property: "serial-number",
|
||||
@@ -78,6 +122,157 @@ public final class DeviceTreePatcher: Patcher {
|
||||
),
|
||||
]
|
||||
|
||||
/// Experimental identity-rewrite property patches. Applied only when
|
||||
/// `includeIdentityPatches` is true — currently set only by the `.exp`
|
||||
/// firmware variant. Other variants (regular, dev, jb, less) skip these.
|
||||
///
|
||||
/// Risk categories:
|
||||
/// - LOW (#3 compatible[1], #11 sub-product-type, #12 unique-model):
|
||||
/// read by userland identity APIs; not in the restore-signed path.
|
||||
/// - HIGHER (#2 target-sub-type, #10 fdr-product-type):
|
||||
/// same family as `target-type` (which broke restore in a prior
|
||||
/// attempt) and FDR-related. If restore fails after a build that
|
||||
/// enables these, remove just those two and retry.
|
||||
/// - MEDIUM (#6 arm-io device_type, #7 arm-io soc-generation):
|
||||
/// IOKit secondary matchers; the real iPhone17,3 DT carries these
|
||||
/// exact values so they match the genuine D47AP.
|
||||
/// - LOW-MEDIUM (#13 gestalt-variants rename): some MG-equivalent
|
||||
/// code may look up the subtree by literal node name.
|
||||
/// Patches for root `model` and root `target-type` are deliberately
|
||||
/// NOT included here — both were tried, both broke restore. They are
|
||||
/// applied post-restore by EXP-JB-6 (`cfw_patch_post_restore_dt.py`)
|
||||
/// in the EXP install pipeline.
|
||||
static let identityPropertyPatches: [PropertyPatch] = [
|
||||
// ── Identity rewrite (Tier 1b) ────────────────────────────────
|
||||
// 5 properties from the 13-entry DT inventory chosen as
|
||||
// userland-facing identity surfaces. NONE of root `model` or root
|
||||
// `target-type` are included — both already proven to break restore.
|
||||
|
||||
// #2 — root `target-sub-type` "VPHONE600AP" -> "D47AP".
|
||||
// RISK: HIGHER. Same family as `target-type`; if restore fails
|
||||
// after enabling this, remove this entry first.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree"],
|
||||
property: "target-sub-type",
|
||||
length: 12,
|
||||
flags: 0,
|
||||
value: .string("D47AP"),
|
||||
patchID: "devicetree.target_sub_type",
|
||||
description: "Set target-sub-type to D47AP (was VPHONE600AP)"
|
||||
),
|
||||
|
||||
// #3 — root `compatible` surgical mangle. Keep VPHONE600AP as first
|
||||
// entry (platform-expert binding intact), rewrite iPhone99,11 (the
|
||||
// secondary entry) to iPhone17,3, keep AppleVirtualPlatformARM as
|
||||
// third entry. See `compatibleRewrite` above for byte layout.
|
||||
// RISK: LOW. Iterators of compatible[] read by userland will pick
|
||||
// up the new identity; the kernel's platform-expert bind still
|
||||
// matches the first entry, so boot is unaffected.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree"],
|
||||
property: "compatible",
|
||||
length: 48,
|
||||
flags: 0,
|
||||
value: .bytes(compatibleRewrite),
|
||||
patchID: "devicetree.compatible_secondary",
|
||||
description: "Surgical rewrite of compatible[1]: iPhone99,11 -> iPhone17,3"
|
||||
),
|
||||
|
||||
// #10 — device-tree/product/fdr-product-type "iPhone99,11" -> "iPhone17,3".
|
||||
// RISK: HIGHER. FDR = Factory Data Restore; some restore-time code
|
||||
// reads this field. If restore breaks, remove this entry.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree", "product"],
|
||||
property: "fdr-product-type",
|
||||
length: 12,
|
||||
flags: 0,
|
||||
value: .string("iPhone17,3"),
|
||||
patchID: "devicetree.product.fdr_product_type",
|
||||
description: "Set product/fdr-product-type to iPhone17,3 (was iPhone99,11)"
|
||||
),
|
||||
|
||||
// #11 — device-tree/product/sub-product-type "iPhone99,11" -> "iPhone17,3".
|
||||
// RISK: LOW. Read by userland classification code; not in
|
||||
// restore-signed path.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree", "product"],
|
||||
property: "sub-product-type",
|
||||
length: 12,
|
||||
flags: 0,
|
||||
value: .string("iPhone17,3"),
|
||||
patchID: "devicetree.product.sub_product_type",
|
||||
description: "Set product/sub-product-type to iPhone17,3 (was iPhone99,11)"
|
||||
),
|
||||
|
||||
// #12 — device-tree/product/unique-model "VPHONE600AP" -> "D47AP".
|
||||
// RISK: LOW. Read by libMobileGestalt and "unique device class"
|
||||
// queries; not in restore-signed path.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree", "product"],
|
||||
property: "unique-model",
|
||||
length: 12,
|
||||
flags: 0,
|
||||
value: .string("D47AP"),
|
||||
patchID: "devicetree.product.unique_model",
|
||||
description: "Set product/unique-model to D47AP (was VPHONE600AP)"
|
||||
),
|
||||
|
||||
// ── Identity rewrite (Tier 1c) ────────────────────────────────
|
||||
// Three more candidates from the inventory that haven't been
|
||||
// empirically shown to break restore. Each may still affect kernel
|
||||
// boot if a kext relies on the specific value.
|
||||
|
||||
// #6 — device-tree/arm-io/device_type "vresearch1-io" -> "t8140-io".
|
||||
// RISK: MEDIUM. device_type is a secondary IOKit matcher; many
|
||||
// kexts only use compatible[] for binding, but some require both.
|
||||
// The actual d47ap DT carries "t8140-io" here, so this is the
|
||||
// genuine iPhone17,3 value (not a fabricated one).
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree", "arm-io"],
|
||||
property: "device_type",
|
||||
length: 14,
|
||||
flags: 0,
|
||||
value: .string("t8140-io"),
|
||||
patchID: "devicetree.arm_io.device_type",
|
||||
description: "Set arm-io/device_type to t8140-io (was vresearch1-io)"
|
||||
),
|
||||
|
||||
// #7 — device-tree/arm-io/soc-generation "VResearch1" -> "H17".
|
||||
// RISK: MEDIUM-LOW. soc-generation is typically a capability /
|
||||
// SoC-family descriptor read by kexts to select code paths.
|
||||
// d47ap (iPhone17,3) uses "H17" so we match that exactly.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree", "arm-io"],
|
||||
property: "soc-generation",
|
||||
length: 11,
|
||||
flags: 0,
|
||||
value: .string("H17"),
|
||||
patchID: "devicetree.arm_io.soc_generation",
|
||||
description: "Set arm-io/soc-generation to H17 (was VResearch1)"
|
||||
),
|
||||
|
||||
// #13 — rename node device-tree/product/vphone600-gestalt-variants
|
||||
// to "d47-gestalt-variants" by rewriting its `name` property.
|
||||
// RISK: LOW-MEDIUM. Some libMobileGestalt-equivalent code may look
|
||||
// up the subtree by literal node name. d47 doesn't have a
|
||||
// `*-gestalt-variants` node at all (its product children are
|
||||
// camera/facetime/maps/haptics/audio), so iOS handles missing
|
||||
// gestalt-variants gracefully on real iPhone 17,3 devices anyway.
|
||||
// Renaming should be at-worst-equivalent to that "missing node"
|
||||
// path. The DTNode patcher walks by current name, so the nodePath
|
||||
// here uses the OLD name; the patch rewrites the `name` property
|
||||
// inside that node to the new value.
|
||||
PropertyPatch(
|
||||
nodePath: ["device-tree", "product", "vphone600-gestalt-variants"],
|
||||
property: "name",
|
||||
length: 27,
|
||||
flags: 0,
|
||||
value: .string("d47-gestalt-variants"),
|
||||
patchID: "devicetree.product.gestalt_variants_rename",
|
||||
description: "Rename node vphone600-gestalt-variants -> d47-gestalt-variants"
|
||||
),
|
||||
]
|
||||
|
||||
// MARK: - Device Tree Structures
|
||||
|
||||
/// A single property in a device tree node.
|
||||
@@ -106,9 +301,10 @@ public final class DeviceTreePatcher: Patcher {
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(data: Data, verbose: Bool = true) {
|
||||
public init(data: Data, verbose: Bool = true, includeIdentityPatches: Bool = false) {
|
||||
buffer = BinaryBuffer(data)
|
||||
self.verbose = verbose
|
||||
self.includeIdentityPatches = includeIdentityPatches
|
||||
}
|
||||
|
||||
// MARK: - Patcher
|
||||
@@ -308,6 +504,18 @@ public final class DeviceTreePatcher: Patcher {
|
||||
return raw
|
||||
}
|
||||
|
||||
/// Encode raw bytes for a property whose layout the caller has prepared
|
||||
/// (typically a multi-string NUL-delimited blob like `compatible`).
|
||||
/// Truncates if longer than the slot, pads with NULs if shorter.
|
||||
private static func encodeFixedBytes(_ data: Data, length: Int) -> Data {
|
||||
if data.count > length {
|
||||
return Data(data.prefix(length))
|
||||
}
|
||||
var out = Data(data)
|
||||
out.append(contentsOf: [UInt8](repeating: 0, count: length - out.count))
|
||||
return out
|
||||
}
|
||||
|
||||
/// Encode an integer value as little-endian bytes.
|
||||
private static func encodeInteger(_ value: UInt64, length: Int) throws -> Data {
|
||||
var data = Data(count: length)
|
||||
@@ -331,8 +539,17 @@ public final class DeviceTreePatcher: Patcher {
|
||||
// MARK: - Patch Application
|
||||
|
||||
/// Apply all property patches and record each change.
|
||||
///
|
||||
/// Always runs `basePropertyPatches`. Additionally runs
|
||||
/// `identityPropertyPatches` when `includeIdentityPatches` is true
|
||||
/// (the `.exp` firmware variant) — other variants leave the device's
|
||||
/// identity properties untouched.
|
||||
private func applyPatches(root: DTNode) throws {
|
||||
for patch in Self.propertyPatches {
|
||||
var patchesToApply = Self.basePropertyPatches
|
||||
if includeIdentityPatches {
|
||||
patchesToApply.append(contentsOf: Self.identityPropertyPatches)
|
||||
}
|
||||
for patch in patchesToApply {
|
||||
let node = try resolveNode(root, path: patch.nodePath)
|
||||
let prop = try findProperty(node, name: patch.property)
|
||||
|
||||
@@ -343,6 +560,8 @@ public final class DeviceTreePatcher: Patcher {
|
||||
Self.encodeFixedString(s, length: patch.length)
|
||||
case let .integer(v):
|
||||
try Self.encodeInteger(v, length: patch.length)
|
||||
case let .bytes(d):
|
||||
Self.encodeFixedBytes(d, length: patch.length)
|
||||
}
|
||||
|
||||
prop.length = patch.length
|
||||
|
||||
@@ -306,13 +306,20 @@ public final class FirmwarePipeline {
|
||||
}()
|
||||
))
|
||||
|
||||
// 7. DeviceTree — same base property patches for every variant.
|
||||
// 7. DeviceTree — base property patches for every variant. EXP additionally
|
||||
// applies the 8 identity-rewrite properties (Tier 1b + 1c) that flip the
|
||||
// device's userland-visible identity toward D47AP / iPhone17,3.
|
||||
let dtIncludeIdentity = variant == .exp
|
||||
components.append(ComponentDescriptor(
|
||||
name: "DeviceTree",
|
||||
inRestoreDir: true,
|
||||
searchPatterns: ["Firmware/all_flash/DeviceTree.vphone600ap.im4p"],
|
||||
patcherFactories: [{ data, verbose in
|
||||
DeviceTreePatcher(data: data, verbose: verbose)
|
||||
DeviceTreePatcher(
|
||||
data: data,
|
||||
verbose: verbose,
|
||||
includeIdentityPatches: dtIncludeIdentity
|
||||
)
|
||||
}]
|
||||
))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user