The update list deleted ~80 apps the pack never shipped (Goldleaf, linkalho, Moonlight, NX-Shell, ...), Downloader sysmodules (SysDVR, MissionControl, ldn_mitm, Fizeau), game title ID folders (mods/cheats), and user folders like "Patched Apps" or "games". Anything a user installed via OmniNX Downloader was wiped on every update. Update mode only runs on an existing OmniNX install, so the list now keeps only what the pack ships, components OmniNX dropped, and boot-relevant housekeeping. Foreign-pack leftovers stay in the clean list. Also drops 15 entries nested in deleted folders, a case-duplicate (themezernx) and a trailing-space path. tools/audit_update_list.py checks the list against the OmniNX repo.
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Audit source/deletion_lists_update.h against the OmniNX pack repo.
|
|
|
|
Lists every update-mode deletion entry and whether the pack ships it now,
|
|
shipped it in the past, or never did. Entries under switch/ or config/ that
|
|
the pack never shipped are almost always user installs and should not be in
|
|
the update list (see the header comment in deletion_lists_update.h).
|
|
|
|
usage: python3 tools/audit_update_list.py ../OmniNX
|
|
"""
|
|
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
UPDATE_H = ROOT / "source" / "deletion_lists_update.h"
|
|
PACK_PREFIX = re.compile(r"^(?:variants/[^/]+/(?:extras/hekate-ams-pro/)?|staging/)")
|
|
|
|
# Not shipped as files, but reset on purpose during updates.
|
|
INTENTIONAL = {
|
|
"config/nx-hbmenu", # hbmenu settings (hbmenu.nro is shipped)
|
|
"config/sys-patch", # sys-patch settings (sys-patch is shipped)
|
|
"switch/tinfoil/db", # stale title DB cache
|
|
}
|
|
|
|
|
|
def pack_paths(repo: Path, all_history: bool) -> list[str]:
|
|
if all_history:
|
|
cmd = ["git", "-C", str(repo), "log", "--all", "--name-only", "--format=", "--", "variants", "staging"]
|
|
else:
|
|
cmd = ["git", "-C", str(repo), "ls-files", "variants", "staging"]
|
|
out = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout
|
|
paths = set()
|
|
for line in out.splitlines():
|
|
line = line.strip()
|
|
if line and PACK_PREFIX.match(line):
|
|
paths.add(PACK_PREFIX.sub("", line).lower())
|
|
return sorted(paths)
|
|
|
|
|
|
def covered(paths: list[str], entry: str) -> bool:
|
|
return any(p == entry or p.startswith(entry + "/") for p in paths)
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) != 2:
|
|
print(__doc__.strip())
|
|
return 2
|
|
repo = Path(sys.argv[1])
|
|
current = pack_paths(repo, all_history=False)
|
|
history = pack_paths(repo, all_history=True)
|
|
|
|
text = UPDATE_H.read_text(encoding="utf-8")
|
|
suspicious = 0
|
|
for m in re.finditer(r"static const char\*\s+(\w+)\[\]\s*=\s*\{(.*?)\};", text, re.S):
|
|
for entry in re.findall(r'"sd:/([^"]+)"', m.group(2)):
|
|
e = entry.lower()
|
|
if covered(current, e):
|
|
state = "shipped"
|
|
elif covered(history, e):
|
|
state = "dropped"
|
|
else:
|
|
state = "never"
|
|
flag = ""
|
|
if state == "never" and e not in INTENTIONAL and (e.startswith("switch/") or e.startswith("config/")):
|
|
flag = " <-- user install? remove from update list"
|
|
suspicious += 1
|
|
print(f"{state:8} {m.group(1):36} {entry}{flag}")
|
|
|
|
print(f"\n{suspicious} suspicious entr{'y' if suspicious == 1 else 'ies'}")
|
|
return 1 if suspicious else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|