Make efs2.py a CLI with ls/pull/push/backup
Replace the library-style main() with argparse subcommands: ls, stat, pull, push (writes then verifies read-back), and backup (recursive directory download). Drops the hardcoded scratchpad paths. Update the SKILL.md notes to the new commands. Claude-Session: https://claude.ai/code/session_01Ctisr9uXe4H8XsscZ2exGE
This commit is contained in:
parent
b42dc4c3f1
commit
d31efd3352
2 changed files with 116 additions and 51 deletions
|
|
@ -1,10 +1,12 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Minimal Qualcomm DIAG EFS2 client over a serial diag port.
|
"""Minimal Qualcomm DIAG EFS2 client over a serial diag port.
|
||||||
|
|
||||||
Packet layouts and opcodes replicate JohnBel/EfsTools exactly. Used to
|
Command-line tool to list, pull, push and back up files in a rooted phone's
|
||||||
back up and replace policyman RF-band config on a rooted Sony Xperia 1 VI.
|
modem EFS. Packet layouts and opcodes replicate JohnBel/EfsTools exactly.
|
||||||
|
Originally written to back up and replace the policyman RF-band config on a
|
||||||
|
Sony Xperia 1 VI. Run with -h for usage.
|
||||||
"""
|
"""
|
||||||
import sys, os, time, struct, glob, serial
|
import sys, os, time, struct, glob, argparse, serial
|
||||||
|
|
||||||
DIAG_SUBSYS = 0x4B
|
DIAG_SUBSYS = 0x4B
|
||||||
SUBSYS_EFS = 19
|
SUBSYS_EFS = 19
|
||||||
|
|
@ -244,53 +246,113 @@ class Diag:
|
||||||
return off
|
return off
|
||||||
|
|
||||||
|
|
||||||
TARGET_DIR = "/policyman"
|
TYPE_NAMES = {0: "file", 1: "dir", 2: "link", 3: "immovable", 15: "item"}
|
||||||
TARGETS = ["band_set_01.xml", "plmn_mcc_supported_01.xml", "policies.xml"]
|
|
||||||
SRC = "/tmp/claude-1000/-home-grmrgecko-Downloads-XperiFirm-5-8-1--by-Igor-Eisberg-/aa2c5f73-89ed-4884-8a6f-7f146ae4f002/scratchpad/xda_xml"
|
|
||||||
BACKUP = "/tmp/claude-1000/-home-grmrgecko-Downloads-XperiFirm-5-8-1--by-Igor-Eisberg-/aa2c5f73-89ed-4884-8a6f-7f146ae4f002/scratchpad/policyman_backup"
|
def cmd_ls(d, args):
|
||||||
|
entries = d.listdir(args.path)
|
||||||
|
for name, etype, size in entries:
|
||||||
|
print("%-42s %-9s %8d" % (name, TYPE_NAMES.get(etype, str(etype)), size))
|
||||||
|
print("%d entries in %s" % (len(entries), args.path))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_stat(d, args):
|
||||||
|
err, mode, size = d.stat(args.path)
|
||||||
|
if err:
|
||||||
|
raise SystemExit("stat %s: efs error %d" % (args.path, err))
|
||||||
|
print("%s mode=%o size=%d" % (args.path, mode & 0xffff, size))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_pull(d, args):
|
||||||
|
local = args.local or os.path.basename(args.path.rstrip("/"))
|
||||||
|
data = d.read_file(args.path)
|
||||||
|
with open(local, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
print("pulled %s -> %s (%d bytes)" % (args.path, local, len(data)))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_push(d, args):
|
||||||
|
with open(args.local, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
n = d.write_file(args.path, data)
|
||||||
|
verified = d.read_file(args.path) == data
|
||||||
|
print("pushed %s -> %s (%d bytes), read-back %s"
|
||||||
|
% (args.local, args.path, n, "matches" if verified else "MISMATCH"))
|
||||||
|
if not verified:
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_backup(d, args):
|
||||||
|
written = [0]
|
||||||
|
|
||||||
|
def walk(efs_dir, local_dir):
|
||||||
|
os.makedirs(local_dir, exist_ok=True)
|
||||||
|
for name, etype, size in d.listdir(efs_dir):
|
||||||
|
efs_path = efs_dir.rstrip("/") + "/" + name
|
||||||
|
local_path = os.path.join(local_dir, name)
|
||||||
|
if etype == 1: # directory
|
||||||
|
walk(efs_path, local_path)
|
||||||
|
elif etype in (0, 15): # regular file or item file
|
||||||
|
try:
|
||||||
|
data = d.read_file(efs_path)
|
||||||
|
except Exception as e:
|
||||||
|
sys.stderr.write(" skip %s: %s\n" % (efs_path, e))
|
||||||
|
continue
|
||||||
|
with open(local_path, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
written[0] += 1
|
||||||
|
print(" %s (%d bytes)" % (efs_path, len(data)))
|
||||||
|
|
||||||
|
walk(args.path, args.dest)
|
||||||
|
print("backed up %d files from %s to %s" % (written[0], args.path, args.dest))
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
|
p = argparse.ArgumentParser(
|
||||||
|
prog="efs2.py",
|
||||||
|
description="Minimal Qualcomm DIAG EFS2 client: list, pull, push and "
|
||||||
|
"back up files in a rooted phone's modem EFS.",
|
||||||
|
epilog="Put the phone in diag mode first (adb shell su -c 'setprop "
|
||||||
|
"sys.usb.config diag,serial_cdev,rmnet,adb') and bind the option "
|
||||||
|
"driver. The diag port is auto-detected; override with --port or "
|
||||||
|
"the DIAGPORT env var.")
|
||||||
|
p.add_argument("-p", "--port", help="diag serial port (default: auto-detect)")
|
||||||
|
sub = p.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
s = sub.add_parser("ls", help="list an EFS directory")
|
||||||
|
s.add_argument("path")
|
||||||
|
s.set_defaults(func=cmd_ls)
|
||||||
|
|
||||||
|
s = sub.add_parser("stat", help="stat an EFS file")
|
||||||
|
s.add_argument("path")
|
||||||
|
s.set_defaults(func=cmd_stat)
|
||||||
|
|
||||||
|
s = sub.add_parser("pull", help="download one EFS file")
|
||||||
|
s.add_argument("path", help="EFS path, e.g. /policyman/band_set_01.xml")
|
||||||
|
s.add_argument("local", nargs="?", help="local path (default: the basename)")
|
||||||
|
s.set_defaults(func=cmd_pull)
|
||||||
|
|
||||||
|
s = sub.add_parser("push", help="upload/overwrite one EFS file, then verify")
|
||||||
|
s.add_argument("local", help="local file to upload")
|
||||||
|
s.add_argument("path", help="destination EFS path")
|
||||||
|
s.set_defaults(func=cmd_push)
|
||||||
|
|
||||||
|
s = sub.add_parser("backup", help="recursively download an EFS directory")
|
||||||
|
s.add_argument("path", help="EFS directory, e.g. /policyman")
|
||||||
|
s.add_argument("dest", help="local directory to write into")
|
||||||
|
s.set_defaults(func=cmd_backup)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "probe"
|
args = build_parser().parse_args()
|
||||||
d = Diag()
|
d = Diag(args.port)
|
||||||
print("using diag port %s" % d.port)
|
sys.stderr.write("using diag port %s (efs v%d)\n" % (d.port, d.hello()))
|
||||||
ver = d.hello()
|
try:
|
||||||
print("EFS hello ok, version=%d" % ver)
|
args.func(d, args)
|
||||||
|
finally:
|
||||||
if cmd == "probe":
|
|
||||||
entries = d.listdir(TARGET_DIR)
|
|
||||||
print("%s: %d entries" % (TARGET_DIR, len(entries)))
|
|
||||||
for name, etype, size in entries:
|
|
||||||
mark = " <== TARGET" if name in TARGETS else ""
|
|
||||||
print(" %-40s type=%d size=%d%s" % (name, etype, size, mark))
|
|
||||||
for t in TARGETS:
|
|
||||||
err, mode, size = d.stat("%s/%s" % (TARGET_DIR, t))
|
|
||||||
print("stat %s -> err=%d mode=%o size=%d" % (t, err, mode & 0xffff, size))
|
|
||||||
|
|
||||||
elif cmd == "backup":
|
|
||||||
os.makedirs(BACKUP, exist_ok=True)
|
|
||||||
for t in TARGETS:
|
|
||||||
data = d.read_file("%s/%s" % (TARGET_DIR, t))
|
|
||||||
open("%s/%s" % (BACKUP, t), "wb").write(data)
|
|
||||||
print("backed up %s (%d bytes)" % (t, len(data)))
|
|
||||||
|
|
||||||
elif cmd == "write":
|
|
||||||
for t in TARGETS:
|
|
||||||
data = open("%s/%s" % (SRC, t), "rb").read()
|
|
||||||
n = d.write_file("%s/%s" % (TARGET_DIR, t), data)
|
|
||||||
print("wrote %s (%d bytes)" % (t, n))
|
|
||||||
|
|
||||||
elif cmd == "verify":
|
|
||||||
ok = True
|
|
||||||
for t in TARGETS:
|
|
||||||
want = open("%s/%s" % (SRC, t), "rb").read()
|
|
||||||
got = d.read_file("%s/%s" % (TARGET_DIR, t))
|
|
||||||
same = want == got
|
|
||||||
ok = ok and same
|
|
||||||
print("verify %s: %s (device %d bytes, source %d bytes)" %
|
|
||||||
(t, "MATCH" if same else "DIFFER", len(got), len(want)))
|
|
||||||
print("ALL VERIFIED" if ok else "MISMATCH!")
|
|
||||||
|
|
||||||
d.s.close()
|
d.s.close()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -122,10 +122,13 @@ be redone.
|
||||||
3. Talk EFS2 over DIAG. EfsTools (JohnBel) is the usual tool but its
|
3. Talk EFS2 over DIAG. EfsTools (JohnBel) is the usual tool but its
|
||||||
libnserial hits EIO on the option-driver port; the reliable path was a
|
libnserial hits EIO on the option-driver port; the reliable path was a
|
||||||
small pyserial EFS2 client (HDLC + FCS-16, subsys 0x4B/EFS 19). Script is
|
small pyserial EFS2 client (HDLC + FCS-16, subsys 0x4B/EFS 19). Script is
|
||||||
`5g-policyman/efs2.py` in this skill. It does:
|
`5g-policyman/efs2.py` in this skill, a CLI with `ls`, `stat`, `pull`,
|
||||||
hello → list `/policyman` → **back up** the 3 files → write new ones
|
`push` (writes then reads back to verify) and `backup` (recursive):
|
||||||
(unlink then open `O_WRONLY|O_CREAT` perm 0777, 1 KB write chunks) →
|
- `python3 efs2.py backup /policyman ./policyman-backup` (always first)
|
||||||
read back and byte-compare to verify.
|
- `python3 efs2.py push band_set_01.xml /policyman/band_set_01.xml`
|
||||||
|
(and the same for plmn_mcc_supported_01.xml and policies.xml)
|
||||||
|
`push` deletes then re-creates (`O_WRONLY|O_CREAT` perm 0777, 1 KB
|
||||||
|
chunks) and confirms the read-back matches.
|
||||||
4. `adb reboot`. Reboot clears diag mode back to normal adb.
|
4. `adb reboot`. Reboot clears diag mode back to normal adb.
|
||||||
5. Verify: `dumpsys telephony.registry | grep isNrAvailable` → `true`
|
5. Verify: `dumpsys telephony.registry | grep isNrAvailable` → `true`
|
||||||
(stock SEA firmware shows false); `settings get global
|
(stock SEA firmware shows false); `settings get global
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue