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
|
||||
"""Minimal Qualcomm DIAG EFS2 client over a serial diag port.
|
||||
|
||||
Packet layouts and opcodes replicate JohnBel/EfsTools exactly. Used to
|
||||
back up and replace policyman RF-band config on a rooted Sony Xperia 1 VI.
|
||||
Command-line tool to list, pull, push and back up files in a rooted phone's
|
||||
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
|
||||
SUBSYS_EFS = 19
|
||||
|
|
@ -244,53 +246,113 @@ class Diag:
|
|||
return off
|
||||
|
||||
|
||||
TARGET_DIR = "/policyman"
|
||||
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"
|
||||
TYPE_NAMES = {0: "file", 1: "dir", 2: "link", 3: "immovable", 15: "item"}
|
||||
|
||||
|
||||
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():
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "probe"
|
||||
d = Diag()
|
||||
print("using diag port %s" % d.port)
|
||||
ver = d.hello()
|
||||
print("EFS hello ok, version=%d" % ver)
|
||||
|
||||
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!")
|
||||
|
||||
args = build_parser().parse_args()
|
||||
d = Diag(args.port)
|
||||
sys.stderr.write("using diag port %s (efs v%d)\n" % (d.port, d.hello()))
|
||||
try:
|
||||
args.func(d, args)
|
||||
finally:
|
||||
d.s.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -122,10 +122,13 @@ be redone.
|
|||
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
|
||||
small pyserial EFS2 client (HDLC + FCS-16, subsys 0x4B/EFS 19). Script is
|
||||
`5g-policyman/efs2.py` in this skill. It does:
|
||||
hello → list `/policyman` → **back up** the 3 files → write new ones
|
||||
(unlink then open `O_WRONLY|O_CREAT` perm 0777, 1 KB write chunks) →
|
||||
read back and byte-compare to verify.
|
||||
`5g-policyman/efs2.py` in this skill, a CLI with `ls`, `stat`, `pull`,
|
||||
`push` (writes then reads back to verify) and `backup` (recursive):
|
||||
- `python3 efs2.py backup /policyman ./policyman-backup` (always first)
|
||||
- `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.
|
||||
5. Verify: `dumpsys telephony.registry | grep isNrAvailable` → `true`
|
||||
(stock SEA firmware shows false); `settings get global
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue