Files
Snapintosh/tools/diff-devicetree-nodes.py
Acelogic 1510d8c6ce Snapintosh public release
An EL2 monitor/hypervisor and toolchain that boots macOS 27 on the ASUS A16
(Snapdragon X2 Elite), with an Adreno Metal plug-in and IOGPU kext, and
air2ir3, an AIR to Mesa ir3 shader compiler.

MIT, with GPL-2.0 components listed in LICENSE and THIRD_PARTY_NOTICES.md.
No Apple, Qualcomm, ASUS or Microsoft binaries are included.
2026-10-21 21:26:36 -04:00

88 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Node-level difference between two flattened Apple device trees.
python3 tools/diff-devicetree-nodes.py OLD.bin NEW.bin [--json OUT.json]
Prints removed subtrees (by top-most removed node, with node counts), added
nodes with their properties, and every retained node whose properties or child
order differ (removed/added/changed property names, with sizes). Read only.
"""
import argparse
import hashlib
import json
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'probes'))
from appledt import DeviceTree # noqa: E402
def tops(paths):
"""The top-most members of a set of node paths."""
return sorted(p for p in paths if p.rsplit('/', 1)[0] not in paths)
def show(value):
raw = bytes(value)
text = raw.rstrip(b'\0')
if text and all(32 <= c < 127 for c in text):
return text.decode()
return raw.hex()
def diff(old, new):
removed = set(old.nodes) - set(new.nodes)
added = set(new.nodes) - set(old.nodes)
changed = {}
for path in sorted(set(old.nodes) & set(new.nodes)):
a, b = old.nodes[path], new.nodes[path]
props = dict(
removed={k: len(a[k]) for k in a if k not in b},
added={k: len(b[k]) for k in b if k not in a},
changed={k: [len(a[k]), len(b[k])] for k in a if k in b and bytes(a[k].value) != bytes(b[k].value)})
kids_a = [k for k in old.children[path] if k not in removed]
kids_b = [k for k in new.children[path] if k not in added]
order = kids_a != kids_b
if any(props.values()) or order:
changed[path] = dict(props, child_order_changed=order)
return dict(
old_nodes=len(old.nodes), new_nodes=len(new.nodes),
removed_roots={r: sum(1 for p in removed if p == r or p.startswith(r + '/')) for r in tops(removed)},
added={p: {k: show(v.value) for k, v in new.nodes[p].items()} for p in sorted(added)},
added_parents={p: new.children[p.rsplit('/', 1)[0]].index(p) for p in tops(added)},
changed=changed)
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('old', type=Path)
ap.add_argument('new', type=Path)
ap.add_argument('--json', type=Path)
args = ap.parse_args()
blobs = [p.read_bytes() for p in (args.old, args.new)]
old, new = (DeviceTree(b) for b in blobs)
for tree, blob in zip((old, new), blobs):
assert tree.serialize() == blob, 'tree does not round-trip'
result = dict(old=str(args.old), old_sha256=hashlib.sha256(blobs[0]).hexdigest(), old_bytes=len(blobs[0]),
new=str(args.new), new_sha256=hashlib.sha256(blobs[1]).hexdigest(), new_bytes=len(blobs[1]),
**diff(old, new))
print(f"{result['old_nodes']} -> {result['new_nodes']} nodes, "
f"{result['old_bytes']} -> {result['new_bytes']} bytes")
print(f"removed {sum(result['removed_roots'].values())} nodes in {len(result['removed_roots'])} subtrees:")
for root, count in result['removed_roots'].items():
print(f' - {root} ({count})')
print(f"added {len(result['added'])} node(s):")
for path, props in result['added'].items():
print(f' + {path} {props} (child index {result["added_parents"].get(path)})')
print(f"retained nodes that differ: {len(result['changed'])}")
for path, d in result['changed'].items():
print(f' ~ {path} ' + ', '.join(f'{k}={v}' for k, v in d.items() if v))
if args.json:
args.json.write_text(json.dumps(result, indent=1) + '\n')
if __name__ == '__main__':
main()