• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
1110 lines
38 KiB
Python
1110 lines
38 KiB
Python
#!/usr/bin/env python3
|
|
"""Apply the App Launcher patch (v2.1) to the stock hiby_player (v1.4).
|
|
|
|
Replaces the Qobuz streaming entry with a "MeowAPPs" app list page:
|
|
1. checks the TF card (/usr/data/mnt/sd_0 via /proc/mounts); if missing
|
|
shows "未检测到存储卡" (no hang)
|
|
2. scans /usr/data/mnt/sd_0/MeowAPPs/*/app.json; if absent/empty shows
|
|
"无可用应用" + directory example (no hang); malformed app.json is
|
|
skipped (no crash)
|
|
3. shows app names (UTF-8 -> UTF-16 runtime conversion) with per-row
|
|
click callbacks
|
|
4. on row click: access(X_OK) check -> "无法启动该应用" hint on failure
|
|
(no hang); else fork(4002)+execve(4011), parent blocks in
|
|
waitpid(4007) = "让位"; on app exit the UI resumes
|
|
|
|
Keeps the v2.0 fixes: "viewgroup" type @ 0x7ee7a0 (not "imageview"),
|
|
0x46c800(parent, view), text setter vtable[0x198], child enumeration.
|
|
|
|
Usage: python3 apply_patch_v2.py [stock] [output]
|
|
"""
|
|
import sys, struct
|
|
|
|
STOCK = sys.argv[1] if len(sys.argv) > 1 else "hiby_player_stock"
|
|
OUT = sys.argv[2] if len(sys.argv) > 2 else "hiby_player_patched_v2"
|
|
|
|
# ---------------------------------------------------------------- encoders
|
|
def enc_lw(rt, off, rs): return (0x23 << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_sw(rt, off, rs): return (0x2B << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_sb(rt, off, rs): return (0x28 << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_sh(rt, off, rs): return (0x29 << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_lbu(rt, off, rs): return (0x24 << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_slt(rd, rs, rt): return 0x0000002A | (rs << 21) | (rt << 16) | (rd << 11)
|
|
def enc_slti(rt, rs, imm): return (0x0A << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF)
|
|
def enc_sltiu(rt, rs, imm): return (0x0B << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF)
|
|
def enc_addiu(rt, rs, imm): return (0x09 << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF)
|
|
def enc_andi(rt, rs, imm): return (0x0C << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF)
|
|
def enc_lui(rt, imm): return (0x0F << 26) | (rt << 16) | (imm & 0xFFFF)
|
|
def enc_beq(rs, rt, off): return (0x04 << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_bne(rs, rt, off): return (0x05 << 26) | (rs << 21) | (rt << 16) | (off & 0xFFFF)
|
|
def enc_blez(rs, off): return (0x06 << 26) | (rs << 21) | (off & 0xFFFF)
|
|
def enc_j(target): return (0x02 << 26) | ((target >> 2) & 0x03FFFFFF)
|
|
def enc_jal(target): return (0x03 << 26) | ((target >> 2) & 0x03FFFFFF)
|
|
def enc_jr(rs): return 0x00000008 | (rs << 21)
|
|
def enc_jalr(rd, rs): return 0x00000009 | (rs << 21) | (rd << 11)
|
|
def enc_move(rd, rs): return 0x00000021 | (rs << 21) | (rd << 11)
|
|
def enc_addu(rd, rs, rt): return 0x00000021 | (rs << 21) | (rt << 16) | (rd << 11)
|
|
def enc_or(rd, rs, rt): return 0x00000025 | (rs << 21) | (rt << 16) | (rd << 11)
|
|
def enc_sll(rd, rt, sa): return 0x00000000 | (rt << 16) | (rd << 11) | (sa << 6)
|
|
NOP = 0x00000000
|
|
SYSCALL = 0x0000000C
|
|
|
|
# ---------------------------------------------------------------- globals
|
|
# .data zero region 0x9636bc (verified zeros in stock; see ANALYSIS.md §17)
|
|
# 全局区迁移(2026-08-28): 0x9636bc 被 hiby_player 运行时复用(实测 g_page 被写 1)
|
|
# -> 0x9060e4 (.data 大零区, 真机 gdb 实测启动后 60s 全零未被写)
|
|
G_BASE_HI = 0x90 # lui 高 16 位
|
|
G_BASE_LO = 0x60E4 # addiu 低 16 位(正偏移)
|
|
G_STATE = 0x0000 # 0=ok 1=no card 2=no apps
|
|
G_PAGE = 0x0004
|
|
G_QOBUZ = 0x0008
|
|
G_COUNT = 0x000C
|
|
G_ROWS = 0x0010 # 8x u32
|
|
G_NAMES = 0x0030 # 8x16B
|
|
G_EXECS = 0x00B0 # 8x48B
|
|
G_SCAN = 0x0230 # 256B
|
|
G_UTF16 = 0x0330 # 8x48B
|
|
G_HINT = 0x04B0 # 48B utf16
|
|
G_NOTE = 0x04E0 # 48B utf16
|
|
G_PENDING = 0x0510 # autotest pending flag
|
|
G_TRACE = 0x0514 # loop_hook 执行计数器(调试)
|
|
G_SIGREG = 0x0518 # SIGUSR1 handler 已注册标志
|
|
G_PARENT = 0x051C # entry 回调原始 parent(0x46c800/0x46c600 需要)
|
|
|
|
PLT = dict(fread=0x8E6180, access=0x8E6240, strstr=0x8E6280, fopen=0x8E62E0,
|
|
open=0x8E6120, read=0x8E6F00,
|
|
signal=0x8E6970,
|
|
fgets=0x8E6440, strlen=0x8E6510, readdir=0x8E65B0, strcmp=0x8E6650,
|
|
fclose=0x8E6760, opendir=0x8E6C00, closedir=0x8E6C40, strcpy=0x8E70B0)
|
|
|
|
CREATE_VIEW = 0x49BE00
|
|
ACTIVATE_VIEW = 0x46C800
|
|
SET_CALLBACK = 0x46C2C0
|
|
|
|
CAVE = 0x41B8B0
|
|
STR_BASE = CAVE + 0x1000
|
|
STR_VIEWGROUP = 0x7EE7A0
|
|
|
|
# ---------------------------------------------------------------- assembler
|
|
class Asm:
|
|
def __init__(self, base):
|
|
self.base = base
|
|
self.words = []
|
|
self.patches = []
|
|
self.syms = {}
|
|
|
|
def here(self):
|
|
return self.base + 4 * len(self.words)
|
|
|
|
def label(self, name):
|
|
self.syms[name] = self.here()
|
|
|
|
def emit(self, word):
|
|
self.words.append((self.here(), word))
|
|
return self.here() - 4
|
|
|
|
def raw(self, words):
|
|
for w in words:
|
|
self.emit(w)
|
|
|
|
def b(self, rs, rt, label, eq=True):
|
|
a = self.emit(None)
|
|
self.patches.append((a, "beq" if eq else "bne", rs, rt, label))
|
|
|
|
def blez(self, rs, label):
|
|
a = self.emit(None)
|
|
self.patches.append((a, "blez", rs, 0, label))
|
|
|
|
def j(self, label):
|
|
a = self.emit(None)
|
|
self.patches.append((a, "j", 0, 0, label))
|
|
|
|
def jal(self, label_or_abs):
|
|
if isinstance(label_or_abs, int):
|
|
self.emit(enc_jal(label_or_abs))
|
|
else:
|
|
a = self.emit(None)
|
|
self.patches.append((a, "jal", 0, 0, label_or_abs))
|
|
|
|
def load_cstr(self, rt, label):
|
|
a1 = self.emit(None)
|
|
a2 = self.emit(None)
|
|
self.patches.append((a1, "lui", rt, rt, label))
|
|
self.patches.append((a2, "addiu", rt, rt, label))
|
|
|
|
def load_abs(self, rt, addr):
|
|
hi, lo = (addr >> 16) & 0xFFFF, addr & 0xFFFF
|
|
if lo & 0x8000:
|
|
self.raw([enc_lui(rt, (hi + 1) & 0xFFFF), enc_addiu(rt, rt, lo - 0x10000)])
|
|
else:
|
|
self.raw([enc_lui(rt, hi), enc_addiu(rt, rt, lo)])
|
|
|
|
def gp(self):
|
|
self.raw([enc_lui(28, 0x97), enc_addiu(28, 28, -0x3BC0)])
|
|
|
|
def load_g(self, rt, off):
|
|
self.raw([enc_lui(rt, G_BASE_HI), enc_lw(rt, G_BASE_LO + off, rt)])
|
|
|
|
def addr_g(self, rt, off):
|
|
self.raw([enc_lui(rt, G_BASE_HI), enc_addiu(rt, rt, G_BASE_LO + off)])
|
|
|
|
def store_g(self, rs, off):
|
|
self.raw([enc_lui(8, G_BASE_HI), enc_sw(rs, G_BASE_LO + off, 8)])
|
|
|
|
def li(self, rt, imm):
|
|
if 0 <= imm <= 0x7FFF:
|
|
self.raw([enc_addiu(rt, 0, imm)])
|
|
else:
|
|
self.raw([enc_lui(rt, (imm >> 16) & 0xFFFF), enc_addiu(rt, rt, imm & 0xFFFF)])
|
|
|
|
def resolve(self):
|
|
for a, kind, rs, rt, arg in self.patches:
|
|
t = self.syms[arg] if isinstance(arg, str) else arg
|
|
if kind in ("beq", "bne"):
|
|
off = (t - (a + 4)) // 4
|
|
assert -0x8000 <= off < 0x8000, f"{kind}@{a:#x}->{t:#x} off {off}"
|
|
w = enc_beq(rs, rt, off) if kind == "beq" else enc_bne(rs, rt, off)
|
|
elif kind == "blez":
|
|
off = (t - (a + 4)) // 4
|
|
assert -0x8000 <= off < 0x8000
|
|
w = enc_blez(rs, off)
|
|
elif kind == "j":
|
|
w = enc_j(t)
|
|
elif kind == "jal":
|
|
w = enc_jal(t)
|
|
elif kind == "lui":
|
|
w = enc_lui(rt, (t >> 16) & 0xFFFF)
|
|
elif kind == "addiu":
|
|
lo = t & 0xFFFF
|
|
if lo & 0x8000:
|
|
lo -= 0x10000
|
|
for j in range(len(self.words)):
|
|
va, ww = self.words[j]
|
|
if va == a - 4 and ww is not None and (ww >> 26) == 0x0F \
|
|
and ((ww >> 16) & 0x1F) == rt:
|
|
self.words[j] = (va, enc_lui(rt, ((t >> 16) + 1) & 0xFFFF))
|
|
break
|
|
else:
|
|
for j in range(len(self.words)):
|
|
va, ww = self.words[j]
|
|
if va == a - 4 and ww is not None and (ww >> 26) == 0x0F \
|
|
and ((ww >> 16) & 0x1F) == rt:
|
|
self.words[j] = (va, enc_lui(rt, (t >> 16) & 0xFFFF))
|
|
break
|
|
w = enc_addiu(rt, rt, lo & 0xFFFF)
|
|
self.words[self.words.index((a, None))] = (a, w)
|
|
for a, w in self.words:
|
|
assert w is not None, f"unresolved @{a:#x}"
|
|
|
|
def blob(self):
|
|
self.resolve()
|
|
out = bytearray()
|
|
for a, w in self.words:
|
|
out.extend(struct.pack("<I", w & 0xFFFFFFFF))
|
|
return bytes(out)
|
|
|
|
STRINGS = [
|
|
("s_apps_main", b"hiby_apps_main\x00"),
|
|
("s_tv_prefix", b"apps_tv_\x00"),
|
|
("s_tv_back", b"apps_tv_back\x00"),
|
|
("s_tv_hint", b"apps_tv_hint\x00"),
|
|
("s_tv_note", b"apps_tv_note\x00"),
|
|
("s_appsdir", b"/usr/data/mnt/sd_0/MeowAPPs/\x00"), # 尾斜杠: 拼 app.json 路径
|
|
("s_appjson", b"/app.json\x00"),
|
|
("s_mounts", b"/proc/mounts\x00"),
|
|
("s_sd0", b"sd_0\x00"),
|
|
("s_r", b"r\x00"),
|
|
("s_name_k", b'"name":\x00'),
|
|
("s_exec_k", b'"exec":\x00'),
|
|
("s_t_no_card", "未检测到存储卡".encode()),
|
|
("s_t_no_app", "无可用应用".encode()),
|
|
("s_t_example", b"MeowAPPs/MyApp/app.json\x00"),
|
|
("s_t_fail", "无法启动该应用".encode()),
|
|
("s_t_hint_ok", "点击应用启动,任意键退出".encode()),
|
|
("s_wd", b"/usr/data/meow_watchdog\x00"),
|
|
("s_autotest", b"/tmp/meow_autotest\x00"),
|
|
("s_qobuz_name", b"stream_media_vg_qobuz\x00"),
|
|
]
|
|
|
|
A = Asm(CAVE)
|
|
off = 0
|
|
for name, bs in STRINGS:
|
|
A.syms[name] = STR_BASE + off
|
|
off += len(bs)
|
|
|
|
# ---------------- apps_entry_cb ----------------
|
|
A.label("entry")
|
|
A.gp()
|
|
A.raw([enc_lw(2, 0, 6), enc_slti(3, 2, 7)])
|
|
A.b(3, 0, "entry_ret", eq=True) # type>=7(扩展事件)才返回; type<7 继续
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(3, 0, 4)])
|
|
A.b(2, 3, "entry_ret", eq=False)
|
|
A.raw([NOP])
|
|
# parent fallback: if $a0==0 use layout ctx (*(0x96d124))
|
|
A.b(4, 0, "entry_have_parent", eq=False)
|
|
A.raw([NOP])
|
|
A.raw([enc_lui(8, 0x97), enc_lw(4, -0x2EDC, 8)])
|
|
A.label("entry_have_parent")
|
|
A.store_g(4, G_PARENT) # g_parent = effective parent
|
|
# view guard: if $a1==0 skip mark/g_qobuz
|
|
A.b(5, 0, "entry_noview", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(2, 0, 1), enc_sb(2, 0xC1, 5)]) # mark selected
|
|
A.addr_g(8, G_QOBUZ)
|
|
A.raw([enc_sw(5, 0, 8)]) # g_qobuz = view
|
|
A.label("entry_noview")
|
|
A.load_g(8, G_PAGE)
|
|
A.b(8, 0, "entry_activate", eq=False)
|
|
A.raw([NOP])
|
|
# ---- build page ----
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(4, 0x18, 29), enc_sw(16, 0x14, 29), enc_sw(17, 0x10, 29)])
|
|
# card check
|
|
A.jal("check_card")
|
|
A.raw([NOP])
|
|
A.store_g(2, G_STATE) # state = 0/1
|
|
# scan MeowAPPs
|
|
A.jal("scan")
|
|
A.raw([NOP])
|
|
# if count==0 and card present -> state=2
|
|
A.load_g(8, G_COUNT)
|
|
A.b(8, 0, "entry_have", eq=False)
|
|
A.raw([NOP])
|
|
A.load_g(8, G_STATE)
|
|
A.b(8, 0, "entry_have", eq=False) # card missing -> keep state=1
|
|
A.raw([NOP])
|
|
A.li(8, 2)
|
|
A.store_g(8, G_STATE)
|
|
A.label("entry_have")
|
|
# create page: 0x49be00(parent, "hiby_apps_main", "viewgroup")
|
|
A.load_abs(6, STR_VIEWGROUP)
|
|
A.load_cstr(5, "s_apps_main")
|
|
A.raw([enc_lw(4, 0x18, 29)])
|
|
A.jal(CREATE_VIEW)
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "entry_skip_setup", eq=True)
|
|
A.raw([NOP])
|
|
A.addr_g(8, G_PAGE)
|
|
A.raw([enc_sw(2, 0, 8)]) # g_page = view
|
|
A.raw([enc_move(4, 2)])
|
|
A.jal("setup")
|
|
A.raw([NOP])
|
|
# 两条路径(建页成功/失败)在此汇聚恢复栈帧, 避免 v0==0 时 $ra 指向
|
|
# CREATE_VIEW 返回地址跳回 entry 中间(潜在崩溃)
|
|
A.label("entry_skip_setup")
|
|
A.raw([enc_lw(17, 0x10, 29), enc_lw(16, 0x14, 29), enc_lw(31, 0x1C, 29)])
|
|
A.raw([enc_addiu(29, 29, 0x20)])
|
|
A.label("entry_activate")
|
|
A.load_g(4, G_PAGE)
|
|
A.b(4, 0, "entry_ret", eq=True)
|
|
A.raw([NOP])
|
|
# 0x46c800(a0=parent, a1=view): 沿 a1 的 0xf8 链找 layout_main 并显示 a1
|
|
# (0x46c800 反汇编实证: s0=s2=a1 起链; 原版回调 0x4f0120 参数原样传递)
|
|
A.load_g(5, G_PAGE) # a1 = page (激活应用页)
|
|
A.load_g(4, G_PARENT) # a0 = parent
|
|
A.jal(ACTIVATE_VIEW)
|
|
A.raw([NOP])
|
|
A.label("entry_ret")
|
|
A.gp()
|
|
A.raw([enc_jr(31), NOP])
|
|
|
|
# ---------------- check_card() -> $v0=1 if sd_0 mounted ----------------
|
|
A.label("check_card")
|
|
A.gp()
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29)])
|
|
A.load_cstr(4, "s_mounts")
|
|
A.load_cstr(5, "s_r")
|
|
A.jal(PLT["fopen"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "cc_nofile", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(16, 2)]) # s0 = FILE*
|
|
A.raw([enc_addiu(17, 0, 0)]) # s1 = found = 0
|
|
A.label("cc_loop")
|
|
A.addr_g(4, G_SCAN) # a0 = buf (fgets(buf, size, FILE))
|
|
A.raw([enc_addiu(5, 0, 128)]) # a1 = size
|
|
A.raw([enc_move(6, 16)]) # a2 = FILE* (原顺序错误: a0=FILE,a1=buf,a2=size)
|
|
A.jal(PLT["fgets"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "cc_done", eq=True)
|
|
A.raw([NOP])
|
|
A.addr_g(4, G_SCAN)
|
|
A.load_cstr(5, "s_sd0")
|
|
A.jal(PLT["strstr"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "cc_found", eq=False)
|
|
A.raw([NOP])
|
|
A.j("cc_loop")
|
|
A.raw([NOP])
|
|
A.label("cc_found")
|
|
A.raw([enc_addiu(17, 0, 1)])
|
|
A.label("cc_done")
|
|
A.raw([enc_move(4, 16)])
|
|
A.jal(PLT["fclose"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.raw([enc_move(2, 17)])
|
|
A.raw([enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29), enc_lw(31, 0x1C, 29)])
|
|
A.raw([enc_addiu(29, 29, 0x20), enc_jr(31), NOP])
|
|
A.label("cc_nofile")
|
|
A.raw([enc_addiu(2, 0, 0), enc_jr(31), NOP])
|
|
|
|
# ---------------- setup($a0=page): rows + hints + back ----------------
|
|
A.label("setup")
|
|
A.gp()
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29), enc_sw(18, 0x10, 29)])
|
|
A.raw([enc_move(16, 4)])
|
|
A.raw([enc_addiu(17, 0, 0)]) # i
|
|
A.label("setup_loop")
|
|
A.load_g(2, G_COUNT)
|
|
A.raw([enc_slt(3, 17, 2)])
|
|
A.b(3, 0, "setup_hints", eq=True)
|
|
A.raw([NOP])
|
|
A.load_cstr(4, "s_tv_prefix")
|
|
A.addr_g(5, G_SCAN)
|
|
A.jal(PLT["strcpy"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.addr_g(4, G_SCAN)
|
|
A.jal(PLT["strlen"])
|
|
A.raw([NOP])
|
|
A.addr_g(8, G_SCAN)
|
|
A.raw([enc_addu(8, 8, 2)])
|
|
A.raw([enc_addiu(2, 17, 0x30), enc_sb(2, 0, 8), enc_sb(0, 1, 8)])
|
|
A.raw([enc_move(5, 8)])
|
|
A.raw([enc_move(4, 16)])
|
|
A.jal("find")
|
|
A.raw([NOP])
|
|
A.b(2, 0, "setup_next", eq=True)
|
|
A.raw([NOP])
|
|
A.addr_g(8, G_ROWS)
|
|
A.raw([enc_sll(9, 17, 2), enc_addu(8, 8, 9), enc_sw(2, 0, 8)])
|
|
A.raw([enc_move(18, 2)])
|
|
A.addr_g(5, G_NAMES)
|
|
A.raw([enc_sll(9, 17, 4), enc_addu(5, 5, 9)])
|
|
A.addr_g(6, G_UTF16)
|
|
A.raw([enc_sll(9, 17, 5), enc_sll(10, 17, 4), enc_addu(6, 6, 9)])
|
|
A.raw([enc_move(4, 18)])
|
|
A.jal("settext")
|
|
A.raw([NOP])
|
|
A.raw([enc_move(4, 18)])
|
|
A.load_cstr(5, "rowcb")
|
|
A.jal(SET_CALLBACK)
|
|
A.raw([NOP])
|
|
A.label("setup_next")
|
|
A.raw([enc_addiu(17, 17, 1)])
|
|
A.j("setup_loop")
|
|
A.raw([NOP])
|
|
# hints by state
|
|
A.label("setup_hints")
|
|
A.load_g(8, G_STATE)
|
|
A.raw([enc_addiu(9, 0, 1)])
|
|
A.b(8, 9, "setup_nocard", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(9, 0, 2)])
|
|
A.b(8, 9, "setup_noapp", eq=True)
|
|
A.raw([NOP])
|
|
# state 0
|
|
A.load_cstr(4, "s_t_hint_ok")
|
|
A.jal("set_hint")
|
|
A.raw([NOP])
|
|
A.j("setup_backrow")
|
|
A.raw([NOP])
|
|
A.label("setup_nocard")
|
|
A.load_cstr(4, "s_t_no_card")
|
|
A.jal("set_hint")
|
|
A.raw([NOP])
|
|
A.j("setup_backrow")
|
|
A.raw([NOP])
|
|
A.label("setup_noapp")
|
|
A.load_cstr(4, "s_t_no_app")
|
|
A.jal("set_hint")
|
|
A.raw([NOP])
|
|
A.load_cstr(4, "s_t_example")
|
|
A.jal("set_note")
|
|
A.raw([NOP])
|
|
# back row
|
|
A.label("setup_backrow")
|
|
A.raw([enc_move(4, 16)])
|
|
A.load_cstr(5, "s_tv_back")
|
|
A.jal("find")
|
|
A.raw([NOP])
|
|
A.b(2, 0, "setup_skipback", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(4, 2)])
|
|
A.load_cstr(5, "backcb")
|
|
A.jal(SET_CALLBACK)
|
|
A.raw([NOP])
|
|
A.label("setup_skipback")
|
|
A.raw([enc_lw(18, 0x10, 29), enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29)])
|
|
A.raw([enc_lw(31, 0x1C, 29), enc_addiu(29, 29, 0x20)])
|
|
A.raw([enc_jr(31), NOP])
|
|
|
|
# ---------------- set_hint($a0=utf8) ----------------
|
|
A.label("set_hint")
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29)])
|
|
A.raw([enc_move(17, 4)])
|
|
A.load_g(4, G_PAGE)
|
|
A.b(4, 0, "hint_out", eq=True)
|
|
A.raw([NOP])
|
|
A.load_cstr(5, "s_tv_hint")
|
|
A.jal("find")
|
|
A.raw([NOP])
|
|
A.b(2, 0, "hint_out", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(4, 2), enc_move(5, 17)])
|
|
A.addr_g(6, G_HINT)
|
|
A.jal("settext")
|
|
A.raw([NOP])
|
|
A.label("hint_out")
|
|
A.raw([enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29), enc_lw(31, 0x1C, 29)])
|
|
A.raw([enc_addiu(29, 29, 0x20), enc_jr(31), NOP])
|
|
|
|
# ---------------- set_note($a0=utf8) ----------------
|
|
A.label("set_note")
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29)])
|
|
A.raw([enc_move(17, 4)])
|
|
A.load_g(4, G_PAGE)
|
|
A.b(4, 0, "note_out", eq=True)
|
|
A.raw([NOP])
|
|
A.load_cstr(5, "s_tv_note")
|
|
A.jal("find")
|
|
A.raw([NOP])
|
|
A.b(2, 0, "note_out", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(4, 2), enc_move(5, 17)])
|
|
A.addr_g(6, G_NOTE)
|
|
A.jal("settext")
|
|
A.raw([NOP])
|
|
A.label("note_out")
|
|
A.raw([enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29), enc_lw(31, 0x1C, 29)])
|
|
A.raw([enc_addiu(29, 29, 0x20), enc_jr(31), NOP])
|
|
|
|
# ---------------- settext($a0=row, $a1=utf8, $a2=buf) ----------------
|
|
A.label("settext")
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29), enc_sw(18, 0x10, 29)])
|
|
A.raw([enc_move(16, 4), enc_move(17, 5), enc_move(18, 6)])
|
|
A.raw([enc_move(4, 5), enc_move(5, 6)])
|
|
A.jal("u2u")
|
|
A.raw([NOP])
|
|
A.raw([enc_lw(4, 0x48, 16)])
|
|
A.b(4, 0, "settext_out", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_lw(9, 0x198, 4)])
|
|
A.raw([enc_move(5, 18)])
|
|
A.raw([enc_jalr(31, 9), NOP])
|
|
A.label("settext_out")
|
|
A.raw([enc_lw(18, 0x10, 29), enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29)])
|
|
A.raw([enc_lw(31, 0x1C, 29), enc_addiu(29, 29, 0x20), enc_jr(31), NOP])
|
|
|
|
# ---------------- u2u($a0=utf8, $a1=utf16buf): utf8->utf16le ----------------
|
|
A.label("u2u")
|
|
A.label("u2u_loop")
|
|
A.raw([enc_lbu(9, 0, 4)])
|
|
A.b(9, 0, "u2u_end", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_andi(10, 9, 0x80)])
|
|
A.b(10, 0, "u2u_ascii", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_andi(10, 9, 0xE0)])
|
|
A.raw([enc_addiu(11, 0, 0xC0)])
|
|
A.b(10, 11, "u2u_two", eq=True)
|
|
A.raw([NOP])
|
|
# 3-byte
|
|
A.raw([enc_lbu(11, 1, 4), enc_lbu(12, 2, 4)])
|
|
A.raw([enc_andi(10, 9, 0x0F), enc_sll(10, 10, 12)])
|
|
A.raw([enc_andi(13, 11, 0x3F), enc_sll(13, 13, 6)])
|
|
A.raw([enc_or(10, 10, 13)])
|
|
A.raw([enc_andi(13, 12, 0x3F), enc_or(10, 10, 13)])
|
|
A.raw([enc_sh(10, 0, 5)])
|
|
A.raw([enc_addiu(4, 4, 3), enc_addiu(5, 5, 2)])
|
|
A.j("u2u_loop")
|
|
A.raw([NOP])
|
|
A.label("u2u_two")
|
|
A.raw([enc_lbu(11, 1, 4)])
|
|
A.raw([enc_andi(10, 9, 0x1F), enc_sll(10, 10, 6)])
|
|
A.raw([enc_andi(13, 11, 0x3F), enc_or(10, 10, 13)])
|
|
A.raw([enc_sh(10, 0, 5)])
|
|
A.raw([enc_addiu(4, 4, 2), enc_addiu(5, 5, 2)])
|
|
A.j("u2u_loop")
|
|
A.raw([NOP])
|
|
A.label("u2u_ascii")
|
|
A.raw([enc_sh(9, 0, 5)])
|
|
A.raw([enc_addiu(4, 4, 1), enc_addiu(5, 5, 2)])
|
|
A.j("u2u_loop")
|
|
A.raw([NOP])
|
|
A.label("u2u_end")
|
|
A.raw([enc_sh(0, 0, 5), enc_jr(31), NOP])
|
|
|
|
# ---------------- find($a0=parent, $a1=name) -> $v0 ----------------
|
|
A.label("find")
|
|
A.raw([enc_lw(8, 0xFC, 4)])
|
|
A.b(8, 0, "find_nf", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(13, 5)])
|
|
A.raw([enc_lw(9, 0x30, 8)])
|
|
A.raw([enc_jalr(31, 9), enc_move(4, 8)])
|
|
A.blez(2, "find_nf")
|
|
A.raw([enc_move(12, 2)])
|
|
A.raw([enc_addiu(10, 0, 0)])
|
|
A.label("find_loop")
|
|
A.raw([enc_lw(9, 0x2C, 8)])
|
|
A.raw([enc_move(5, 10)])
|
|
A.raw([enc_jalr(31, 9), enc_move(4, 8)])
|
|
A.b(2, 0, "find_next", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(11, 2)])
|
|
A.raw([enc_addiu(4, 11, 0x20)])
|
|
A.raw([enc_move(5, 13)])
|
|
A.jal(PLT["strcmp"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "find_found", eq=True)
|
|
A.raw([NOP])
|
|
A.label("find_next")
|
|
A.raw([enc_addiu(10, 10, 1)])
|
|
A.raw([enc_slt(3, 10, 12)])
|
|
A.b(3, 0, "find_loop", eq=False)
|
|
A.raw([NOP])
|
|
A.label("find_nf")
|
|
A.raw([enc_addiu(2, 0, 0), enc_jr(31), NOP])
|
|
A.label("find_found")
|
|
A.raw([enc_move(2, 11), enc_jr(31), NOP])
|
|
|
|
# ---------------- rowcb(parent, view, &type) ----------------
|
|
A.label("rowcb")
|
|
A.gp()
|
|
A.raw([enc_lw(2, 0, 6), enc_slti(3, 2, 7)])
|
|
A.b(3, 0, "rowcb_ret", eq=True) # type>=7 返回
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(3, 0, 4)])
|
|
A.b(2, 3, "rowcb_ret", eq=False)
|
|
A.raw([NOP])
|
|
A.addr_g(8, G_ROWS)
|
|
A.raw([enc_addiu(9, 0, 0)])
|
|
A.label("rowcb_loop")
|
|
A.raw([enc_sltiu(3, 9, 8)])
|
|
A.b(3, 0, "rowcb_ret", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_lw(2, 0, 8)])
|
|
A.b(2, 5, "rowcb_launch", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(8, 8, 4), enc_addiu(9, 9, 1)])
|
|
A.j("rowcb_loop")
|
|
A.raw([NOP])
|
|
A.label("rowcb_launch")
|
|
A.raw([enc_move(4, 9)])
|
|
A.jal("launch")
|
|
A.raw([NOP])
|
|
A.label("rowcb_ret")
|
|
A.gp()
|
|
A.raw([enc_jr(31), NOP])
|
|
|
|
# ---------------- backcb(parent, view, &type) ----------------
|
|
# 返回流媒体页: 0x46c800(parent, qobuz 入口 view) — 沿 qobuz 链显示流媒体页
|
|
A.label("backcb")
|
|
A.gp()
|
|
A.raw([enc_lw(2, 0, 6), enc_slti(3, 2, 7)])
|
|
A.b(3, 0, "backcb_ret", eq=True) # type>=7 返回
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(3, 0, 4)])
|
|
A.b(2, 3, "backcb_ret", eq=False)
|
|
A.raw([NOP])
|
|
A.load_g(5, G_QOBUZ)
|
|
A.b(5, 0, "backcb_ret", eq=True)
|
|
A.raw([NOP])
|
|
A.load_g(4, G_PARENT)
|
|
A.jal(ACTIVATE_VIEW)
|
|
A.raw([NOP])
|
|
A.label("backcb_ret")
|
|
A.gp()
|
|
A.raw([enc_jr(31), NOP])
|
|
|
|
# ---------------- launch($a0=index) ----------------
|
|
# frame: ra@0x24 s0@app_pid@0x20 s1@wd_pid@0x1c appargv@0x08,0x0c
|
|
# wdargv@0x10,0x14,0x18 status@0x04
|
|
A.label("launch")
|
|
A.gp()
|
|
A.raw([enc_addiu(29, 29, -0x28)])
|
|
A.raw([enc_sw(31, 0x24, 29), enc_sw(16, 0x20, 29), enc_sw(17, 0x1C, 29)])
|
|
A.addr_g(8, G_EXECS)
|
|
A.raw([enc_sll(9, 4, 5), enc_sll(10, 4, 4), enc_addu(9, 9, 10)])
|
|
A.raw([enc_addu(8, 8, 9)])
|
|
# access(path, X_OK=1)
|
|
A.raw([enc_move(4, 8)])
|
|
A.raw([enc_addiu(5, 0, 1)])
|
|
A.jal(PLT["access"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "launch_fail", eq=False)
|
|
A.raw([NOP])
|
|
# fork app
|
|
A.raw([enc_addiu(2, 0, 4002), SYSCALL])
|
|
A.b(2, 0, "launch_app_parent", eq=False)
|
|
A.raw([NOP])
|
|
# app child: execve(path, {path,0}, 0)
|
|
A.raw([enc_sw(8, 0x08, 29), enc_sw(0, 0x0C, 29)])
|
|
A.raw([enc_move(4, 8)])
|
|
A.raw([enc_addiu(5, 29, 0x08)])
|
|
A.raw([enc_addiu(6, 0, 0)])
|
|
A.raw([enc_addiu(2, 0, 4011), SYSCALL])
|
|
A.raw([enc_addiu(2, 0, 4001), enc_addiu(4, 0, 1), SYSCALL])
|
|
A.label("launch_app_parent")
|
|
A.raw([enc_move(16, 2)]) # s0 = app_pid
|
|
# itoa(app_pid) -> g_scan
|
|
A.addr_g(5, G_SCAN)
|
|
A.raw([enc_move(4, 16)])
|
|
A.jal("itoa")
|
|
A.raw([NOP])
|
|
# fork watchdog
|
|
A.raw([enc_addiu(2, 0, 4002), SYSCALL])
|
|
A.b(2, 0, "launch_wd_parent", eq=False)
|
|
A.raw([NOP])
|
|
# wd child: execve("/usr/data/meow_watchdog", {wd, g_scan, 0}, 0)
|
|
A.load_cstr(4, "s_wd")
|
|
A.raw([enc_sw(4, 0x10, 29)])
|
|
A.addr_g(8, G_SCAN)
|
|
A.raw([enc_sw(8, 0x14, 29), enc_sw(0, 0x18, 29)])
|
|
A.raw([enc_addiu(5, 29, 0x10)])
|
|
A.raw([enc_addiu(6, 0, 0)])
|
|
A.raw([enc_addiu(2, 0, 4011), SYSCALL])
|
|
A.raw([enc_addiu(2, 0, 4001), enc_addiu(4, 0, 1), SYSCALL])
|
|
A.label("launch_wd_parent")
|
|
A.raw([enc_move(17, 2)]) # s1 = wd_pid
|
|
# waitpid(app_pid, &status, 0) — blocks = 让位
|
|
A.raw([enc_move(4, 16)])
|
|
A.raw([enc_addiu(5, 29, 0x04)])
|
|
A.raw([enc_addiu(6, 0, 0)])
|
|
A.raw([enc_addiu(2, 0, 4007), SYSCALL])
|
|
# kill(wd_pid, SIGTERM=15)
|
|
A.raw([enc_move(4, 17)])
|
|
A.raw([enc_addiu(5, 0, 15)])
|
|
A.raw([enc_addiu(2, 0, 4037), SYSCALL])
|
|
A.raw([enc_addiu(2, 0, 0)]) # v0 = 0 (ok)
|
|
A.raw([enc_lw(17, 0x1C, 29), enc_lw(16, 0x20, 29), enc_lw(31, 0x24, 29)])
|
|
A.raw([enc_addiu(29, 29, 0x28), enc_jr(31), NOP])
|
|
A.label("launch_fail")
|
|
# hint "无法启动该应用"
|
|
A.raw([enc_sw(31, 0x24, 29)])
|
|
A.load_cstr(4, "s_t_fail")
|
|
A.jal("set_hint")
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(2, 0, 1)]) # v0 = 1 (failed)
|
|
A.raw([enc_lw(31, 0x24, 29), enc_addiu(29, 29, 0x28), enc_jr(31), NOP])
|
|
|
|
# ---------------- itoa($a0=value, $a1=buf) ----------------
|
|
# t0=value t1=end t2=tmp t3=quot t4=rem; writes digits reversed then copies
|
|
A.label("itoa")
|
|
A.raw([enc_move(8, 4)]) # t0 = value
|
|
A.raw([enc_addiu(9, 5, 12)]) # t1 = buf+12 (temp tail)
|
|
A.raw([enc_sb(0, 1, 9)]) # temp[13] = 0 terminator
|
|
A.label("itoa_loop")
|
|
A.raw([enc_addiu(10, 0, 10)])
|
|
A.raw([0x0000001B | (8 << 21) | (10 << 16)]) # divu $t0, $t2
|
|
A.raw([NOP])
|
|
A.raw([0x00000012 | (11 << 11)]) # mflo $t3 (商)
|
|
A.raw([0x00000010 | (12 << 11)]) # mfhi $t4 (余)
|
|
A.raw([enc_addiu(12, 12, 0x30)]) # t4 = rem + '0'
|
|
A.raw([enc_sb(12, 0, 9)])
|
|
A.raw([enc_addiu(9, 9, -1)])
|
|
A.raw([enc_move(8, 11)]) # t0 = quotient
|
|
A.b(11, 0, "itoa_loop", eq=False) # quotient!=0 -> loop
|
|
A.raw([NOP])
|
|
A.label("itoa_copy")
|
|
A.raw([enc_addiu(10, 9, 1)]) # t2 = end+1
|
|
A.label("itoa_cp")
|
|
A.raw([enc_lbu(12, 0, 10)])
|
|
A.raw([enc_sb(12, 0, 5)])
|
|
A.raw([enc_addiu(10, 10, 1)])
|
|
A.raw([enc_addiu(5, 5, 1)])
|
|
A.b(12, 0, "itoa_done", eq=False)
|
|
A.raw([NOP])
|
|
A.j("itoa_cp")
|
|
A.raw([NOP])
|
|
A.label("itoa_done")
|
|
A.raw([enc_jr(31), NOP])
|
|
|
|
# ---------------- scan() ----------------
|
|
A.label("scan")
|
|
A.store_g(0, G_COUNT)
|
|
A.load_cstr(4, "s_appsdir")
|
|
A.jal(PLT["opendir"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "scan_done", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(29, 29, -0x20)])
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29), enc_sw(18, 0x10, 29)])
|
|
A.raw([enc_move(16, 2)])
|
|
A.raw([enc_addiu(17, 0, 0)])
|
|
A.label("scan_loop")
|
|
A.raw([enc_move(4, 16)])
|
|
A.jal(PLT["readdir"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "scan_close", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(18, 2)])
|
|
A.raw([enc_lbu(2, 0x0B, 18)])
|
|
A.raw([enc_addiu(3, 0, 0x2E)])
|
|
A.b(2, 3, "scan_proc", eq=False)
|
|
A.raw([NOP])
|
|
A.raw([enc_lbu(2, 0x0C, 18)])
|
|
A.b(2, 0, "scan_loop", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(3, 0, 0x2E)])
|
|
A.b(2, 3, "scan_proc", eq=False)
|
|
A.raw([NOP])
|
|
A.raw([enc_lbu(2, 0x0D, 18)])
|
|
A.b(2, 0, "scan_proc", eq=False)
|
|
A.raw([NOP])
|
|
A.j("scan_loop")
|
|
A.raw([NOP])
|
|
A.label("scan_proc")
|
|
A.raw([enc_sltiu(2, 17, 8)])
|
|
A.b(2, 0, "scan_close", eq=True)
|
|
A.raw([NOP])
|
|
A.addr_g(4, G_SCAN)
|
|
A.load_cstr(5, "s_appsdir")
|
|
A.jal(PLT["strcpy"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.addr_g(4, G_SCAN)
|
|
A.jal(PLT["strlen"])
|
|
A.raw([NOP])
|
|
A.addr_g(8, G_SCAN)
|
|
A.raw([enc_addu(8, 8, 2)])
|
|
A.raw([enc_move(4, 8)])
|
|
A.raw([enc_addiu(5, 18, 0x0B)])
|
|
A.jal(PLT["strcpy"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.addr_g(4, G_SCAN)
|
|
A.jal(PLT["strlen"])
|
|
A.raw([NOP])
|
|
A.addr_g(8, G_SCAN)
|
|
A.raw([enc_addu(4, 8, 2)])
|
|
A.load_cstr(5, "s_appjson")
|
|
A.jal(PLT["strcpy"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.addr_g(4, G_SCAN)
|
|
A.load_cstr(5, "s_r")
|
|
A.jal(PLT["fopen"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "scan_next", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(18, 2)])
|
|
A.addr_g(4, G_SCAN) # a0 = buf (fgets(buf, size, FILE))
|
|
A.raw([enc_addiu(4, 4, 0x60)])
|
|
A.raw([enc_addiu(5, 0, 96)]) # a1 = size
|
|
A.raw([enc_move(6, 18)]) # a2 = FILE*
|
|
A.jal(PLT["fgets"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.raw([enc_move(4, 18)])
|
|
A.jal(PLT["fclose"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
# parse name
|
|
A.addr_g(4, G_SCAN)
|
|
A.raw([enc_addiu(4, 4, 0x60)])
|
|
A.load_cstr(5, "s_name_k")
|
|
A.jal(PLT["strstr"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "scan_next", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(18, 2)])
|
|
A.label("scan_skip1")
|
|
A.raw([enc_lbu(2, 0, 18)])
|
|
A.raw([enc_addiu(3, 0, 0x22)])
|
|
A.b(2, 3, "scan_got1", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(18, 18, 1)])
|
|
A.j("scan_skip1")
|
|
A.raw([NOP])
|
|
A.label("scan_got1")
|
|
A.raw([enc_addiu(18, 18, 1)])
|
|
A.addr_g(8, G_NAMES)
|
|
A.raw([enc_sll(9, 17, 4), enc_addu(8, 8, 9)])
|
|
A.raw([enc_move(9, 18)])
|
|
A.label("scan_copy1")
|
|
A.raw([enc_lbu(2, 0, 9)])
|
|
A.raw([enc_addiu(3, 0, 0x22)])
|
|
A.b(2, 3, "scan_end1", eq=True)
|
|
A.raw([NOP])
|
|
A.b(2, 0, "scan_end1", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_sb(2, 0, 8), enc_addiu(8, 8, 1), enc_addiu(9, 9, 1)])
|
|
A.j("scan_copy1")
|
|
A.raw([NOP])
|
|
A.label("scan_end1")
|
|
A.raw([enc_sb(0, 0, 8)])
|
|
# parse exec (missing -> skip app: no count++)
|
|
A.addr_g(4, G_SCAN)
|
|
A.raw([enc_addiu(4, 4, 0x60)])
|
|
A.load_cstr(5, "s_exec_k")
|
|
A.jal(PLT["strstr"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "scan_next", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_move(18, 2)])
|
|
A.label("scan_skip2")
|
|
A.raw([enc_lbu(2, 0, 18)])
|
|
A.raw([enc_addiu(3, 0, 0x22)])
|
|
A.b(2, 3, "scan_got2", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(18, 18, 1)])
|
|
A.j("scan_skip2")
|
|
A.raw([NOP])
|
|
A.label("scan_got2")
|
|
A.raw([enc_addiu(18, 18, 1)])
|
|
A.addr_g(8, G_EXECS)
|
|
A.raw([enc_sll(9, 17, 5), enc_sll(10, 17, 4), enc_addu(9, 9, 10)])
|
|
A.raw([enc_addu(8, 8, 9)])
|
|
A.raw([enc_move(9, 18)])
|
|
A.label("scan_copy2")
|
|
A.raw([enc_lbu(2, 0, 9)])
|
|
A.raw([enc_addiu(3, 0, 0x22)])
|
|
A.b(2, 3, "scan_end2", eq=True)
|
|
A.raw([NOP])
|
|
A.b(2, 0, "scan_end2", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_sb(2, 0, 8), enc_addiu(8, 8, 1), enc_addiu(9, 9, 1)])
|
|
A.j("scan_copy2")
|
|
A.raw([NOP])
|
|
A.label("scan_end2")
|
|
A.raw([enc_sb(0, 0, 8)])
|
|
A.raw([enc_addiu(17, 17, 1)])
|
|
A.label("scan_next")
|
|
A.j("scan_loop")
|
|
A.raw([NOP])
|
|
A.label("scan_close")
|
|
A.raw([enc_move(4, 16)])
|
|
A.jal(PLT["closedir"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.store_g(17, G_COUNT)
|
|
A.raw([enc_lw(18, 0x10, 29), enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29)])
|
|
A.raw([enc_lw(31, 0x1C, 29), enc_addiu(29, 29, 0x20)])
|
|
A.label("scan_done")
|
|
A.raw([enc_jr(31), NOP])
|
|
|
|
# ---------------- touch_hook / autotest_check ----------------
|
|
# 0x47b820 (hgl_ts 触摸读取, lg_ial 线程, 息屏时也在 poll 触摸) 入口被
|
|
# patch 为 "j touch_hook; nop" —— nop 消除 delay slot, 避免原 prologue
|
|
# 第 2 条 (sw ra) 以未调整的 sp 先执行(乱写调用者栈, 上一版 SIGTRAP 主嫌)。
|
|
# hook 内补执行原 prologue 两条后跳回 0x47b828。
|
|
A.label("touch_hook")
|
|
A.raw([enc_addiu(29, 29, -0x80)]) # 原 0x47b820 prologue 指令1
|
|
A.raw([enc_sw(31, 0x7C, 29)]) # 原 prologue 指令2
|
|
A.raw([enc_sw(28, 0x08, 29)]) # 保存 gp (0x08(sp) 原函数未用)
|
|
# 无信号方案: 早期 lg_ial 线程与主线程并发 signal() 疑触发 glibc 竞态 SIGSEGV。
|
|
# touch_hook 只置 pending, 由主循环 loop_hook 轮询消费(主循环带超时, 无需唤醒信号)。
|
|
A.jal("autotest_check")
|
|
A.raw([NOP])
|
|
A.raw([enc_lw(28, 0x08, 29)]) # 恢复 gp
|
|
A.raw([enc_j(0x47B828)]) # 回原函数继续
|
|
A.raw([NOP])
|
|
|
|
# loop_hook: 0x436fc0 (system_if 主循环, 主线程) -> "j loop_hook; nop"
|
|
# pending 置位时: 解析 qobuz 入口视图(0x49be00), 调它的回调 (view+0x140)
|
|
# cb(parent, view, &type=4) —— 与真实点击完全同路径, 主线程执行无 UI 竞态。
|
|
A.label("loop_hook")
|
|
A.raw([enc_addiu(29, 29, -0x20)]) # 原 0x436fc0 prologue 指令1
|
|
A.raw([enc_sw(31, 0x1C, 29)]) # 原 prologue 指令2
|
|
A.raw([enc_sw(28, 0x08, 29)]) # 保存 gp (0x08(sp) 原函数未用)
|
|
# trace 计数器(调试用, gdb 读 0x906614)
|
|
A.raw([enc_lui(8, G_BASE_HI), enc_lw(9, G_BASE_LO + G_TRACE, 8)])
|
|
A.raw([enc_addiu(9, 9, 1), enc_sw(9, G_TRACE, 8)])
|
|
A.load_g(8, G_PENDING)
|
|
A.b(8, 0, "lh_done", eq=True)
|
|
A.raw([NOP])
|
|
A.store_g(0, G_PENDING) # 清 pending
|
|
# type=4 @ sp+0x0c
|
|
A.raw([enc_addiu(2, 0, 4), enc_sw(2, 0x0C, 29)])
|
|
# a0 = ctx = *(0x96d124)
|
|
A.raw([enc_lui(8, 0x97), enc_lw(4, -0x2EDC, 8)])
|
|
A.b(4, 0, "lh_done", eq=True) # ctx 未就绪 -> 放弃
|
|
A.raw([NOP])
|
|
# 0x49be00(ctx, "stream_media_vg_qobuz", "viewgroup") -> qobuz 入口视图
|
|
A.load_abs(6, STR_VIEWGROUP)
|
|
A.load_cstr(5, "s_qobuz_name")
|
|
A.jal(CREATE_VIEW)
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "lh_entry_fallback", eq=True) # 视图未找到(未进过流媒体页) -> 直接 entry
|
|
A.raw([NOP])
|
|
# 调视图回调: lw t9, 0x140(view); t9(ctx, view, &type)
|
|
A.raw([enc_move(10, 2)]) # t2 = view (t 寄存器, 不污染 callee-saved)
|
|
A.raw([enc_lw(9, 0x140, 10)])
|
|
A.b(9, 0, "lh_entry_fallback", eq=True)
|
|
A.raw([NOP])
|
|
A.raw([enc_lui(8, 0x97), enc_lw(4, -0x2EDC, 8)]) # a0 = ctx
|
|
A.raw([enc_move(5, 10)]) # a1 = view
|
|
A.raw([enc_addiu(6, 29, 0x0C)]) # a2 = &type
|
|
A.raw([enc_jalr(31, 9), NOP])
|
|
A.j("lh_done")
|
|
A.raw([NOP])
|
|
# fallback: entry(ctx, 0, &type) —— 从任意页面直接创建+激活应用列表页。
|
|
# view=0 跳过 g_qobuz/选中标记; g_parent 由 entry 内取 ctx 兜底。
|
|
A.label("lh_entry_fallback")
|
|
A.raw([enc_lui(8, 0x97), enc_lw(4, -0x2EDC, 8)]) # a0 = ctx
|
|
A.raw([enc_addiu(5, 0, 0)]) # a1 = 0
|
|
A.raw([enc_addiu(6, 29, 0x0C)]) # a2 = &type
|
|
A.jal("entry")
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.label("lh_done")
|
|
A.raw([enc_lw(28, 0x08, 29)]) # 恢复 gp
|
|
A.raw([enc_j(0x436FC8)]) # 回原函数继续
|
|
A.raw([NOP])
|
|
|
|
# ---------------- pending_hook (v3, 2026-08-28) ----------------
|
|
# 触发机制修正: 0x436fc0 不是主循环(init 一次性调用, 已实证 G_TRACE 恒 0)。
|
|
# 真正的主线程周期循环 = LiteGUI 主循环 0x44a820/0x44a900(同构双副本), 每轮
|
|
# 调用 0x488020(触摸轮询, 已实证 X/Y 全局更新)。把两处 "jal 0x488020"
|
|
# (0x44a898/0x44a950) 改为 "j pending_hook_{a,b}; nop", hook 内:
|
|
# 执行原 delay slot → 检查 G_PENDING → 有则清 + entry(ctx, 0, &type=4)
|
|
# → jal 0x488020(原调用) → jr t0 回各自循环继续。主线程执行, 无 UI 竞态。
|
|
A.label("pending_hook_a")
|
|
A.load_abs(8, 0x44A8A0) # t0 = 0x44a8a0 (循环 A 续点)
|
|
A.b(0, 0, "pending_common", eq=True) # 无条件跳公共段
|
|
A.raw([NOP])
|
|
A.label("pending_hook_b")
|
|
A.load_abs(8, 0x44A958) # t0 = 0x44a958 (循环 B 续点)
|
|
A.label("pending_common")
|
|
A.raw([enc_sw(8, 0x14, 29)]) # 续点存 sp+0x14 (entry 会践踏 t0)
|
|
A.raw([enc_addiu(4, 29, 0x18)]) # a0 = sp+0x18 (原 delay slot)
|
|
A.raw([enc_lui(9, 0x90), enc_lw(9, 0x65F4, 9)]) # t9 = G_PENDING (0x9065f4)
|
|
A.b(9, 0, "ph_call", eq=True) # pending == 0 → 直接原调用
|
|
A.raw([NOP])
|
|
A.raw([enc_lui(9, 0x90), enc_sw(0, 0x65F4, 9)]) # 清 pending
|
|
A.raw([enc_addiu(2, 0, 4), enc_sw(2, 0x0C, 29)]) # type=4 @ sp+0x0c
|
|
A.raw([enc_lui(9, 0x97), enc_lw(4, -0x2EDC, 9)]) # a0 = ctx = *(0x96d124)
|
|
A.b(4, 0, "ph_call", eq=True) # ctx 未就绪 → 跳过
|
|
A.raw([NOP])
|
|
A.raw([enc_addiu(5, 0, 0)]) # a1 = 0 (view: 跳过 g_qobuz/选中)
|
|
A.raw([enc_addiu(6, 29, 0x0C)]) # a2 = &type
|
|
A.raw([enc_lui(8, 0x90), enc_lw(9, 0x65F8, 8)]) # 诊断: G_TRACE++
|
|
A.raw([enc_addiu(9, 9, 1), enc_sw(9, 0x65F8, 8)])
|
|
A.jal("entry")
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.label("ph_call")
|
|
A.jal(0x488020) # 原调用 (触摸轮询, 保持 UI 状态)
|
|
A.raw([enc_addiu(4, 29, 0x18)]) # 原 delay slot
|
|
A.raw([enc_lw(8, 0x14, 29)]) # jal 返回后恢复续点 (0x488020 践踏 t0)
|
|
A.raw([enc_jr(8)]) # 回各自循环续点
|
|
A.raw([NOP])
|
|
|
|
A.label("autotest_check")
|
|
# 注意: 此函数被插在 0x47b820 prologue 中间执行, 原函数依赖 $a0 (touch obj)
|
|
# 必须保存/恢复所有 caller-saved: a0-a3
|
|
A.raw([enc_addiu(29, 29, -0x20)]) # 独立临时帧
|
|
A.raw([enc_sw(31, 0x1C, 29), enc_sw(16, 0x18, 29), enc_sw(17, 0x14, 29)])
|
|
A.raw([enc_sw(4, 0x10, 29), enc_sw(5, 0x0C, 29), enc_sw(6, 0x08, 29), enc_sw(7, 0x04, 29)])
|
|
# 防抖: 布局上下文(0x96d124)必须已就绪(UI 初始化完成)才触发
|
|
A.raw([enc_lui(8, 0x97), enc_lw(8, -0x2EDC, 8)])
|
|
A.b(8, 0, "at_done", eq=True)
|
|
A.raw([NOP])
|
|
A.load_cstr(4, "s_autotest")
|
|
A.raw([enc_addiu(5, 0, 0)]) # F_OK
|
|
A.jal(PLT["access"])
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.b(2, 0, "at_done", eq=False) # 文件不存在 -> done
|
|
A.raw([NOP])
|
|
# 存在: 删除(一次性) + pending=1 (主循环 loop_hook 轮询消费, 无信号)
|
|
A.load_cstr(4, "s_autotest")
|
|
A.jal(0x8E6E60) # remove
|
|
A.raw([NOP])
|
|
A.gp()
|
|
A.raw([enc_addiu(2, 0, 1)])
|
|
A.store_g(2, G_PENDING)
|
|
A.label("at_done")
|
|
A.raw([enc_lw(7, 0x04, 29), enc_lw(6, 0x08, 29), enc_lw(5, 0x0C, 29), enc_lw(4, 0x10, 29)])
|
|
A.raw([enc_lw(17, 0x14, 29), enc_lw(16, 0x18, 29), enc_lw(31, 0x1C, 29)])
|
|
A.raw([enc_addiu(29, 29, 0x20), enc_jr(31), NOP])
|
|
|
|
# ---------------- assemble ----------------
|
|
cave_code = A.blob()
|
|
assert len(cave_code) <= 0x1000, f"cave code overflow: {len(cave_code):#x}"
|
|
cave_code += b"\x00" * (STR_BASE - (CAVE + len(cave_code)))
|
|
assert len(cave_code) == STR_BASE - CAVE
|
|
for name, bs in STRINGS:
|
|
cave_code += bs
|
|
while len(cave_code) % 4:
|
|
cave_code += b"\x00"
|
|
|
|
print(f"cave total: {len(cave_code)} bytes")
|
|
for k in ["entry", "check_card", "setup", "set_hint", "set_note", "settext",
|
|
"u2u", "find", "rowcb", "backcb", "launch", "scan"]:
|
|
print(f" {k}: {A.syms[k]:#x}")
|
|
|
|
PATCH_LUI_OFF = 0x4EFE28 - 0x400000
|
|
PATCH_ADD_OFF = 0x4EFE2C - 0x400000
|
|
PATCH_TOUCH_OFF = 0x47B820 - 0x400000
|
|
PATCH_LOOP_OFF = 0x436FC0 - 0x400000
|
|
CAVE_OFF = CAVE - 0x400000
|
|
|
|
def main():
|
|
data = bytearray(open(STOCK, "rb").read())
|
|
orig_lui = struct.unpack_from("<I", data, PATCH_LUI_OFF)[0]
|
|
orig_add = struct.unpack_from("<I", data, PATCH_ADD_OFF)[0]
|
|
assert orig_lui == 0x3C05004F, f"unexpected lui at 0x4efe28: {orig_lui:#x}"
|
|
assert orig_add == 0x24A50120, f"unexpected addiu at 0x4efe2c: {orig_add:#x}"
|
|
|
|
struct.pack_into("<I", data, PATCH_LUI_OFF, 0x3C050041)
|
|
struct.pack_into("<I", data, PATCH_ADD_OFF, 0x24A5B8B0)
|
|
|
|
# hooks(重启用, v2.4): 0x47b820 / 0x436fc0 入口改为 j hook + nop。
|
|
# nop 消除 MIPS delay slot —— 上一版(仅 j 无 nop)会令原 prologue 第 2 条
|
|
# (sw ra, off(sp)) 以未调整的 sp 先执行, 乱写调用者栈 → 启动 SIGTRAP 主嫌。
|
|
TOUCH_OFF = 0x47B820 - 0x400000
|
|
LOOP_OFF = 0x436FC0 - 0x400000
|
|
orig_t1 = struct.unpack_from("<I", data, TOUCH_OFF)[0]
|
|
orig_t2 = struct.unpack_from("<I", data, TOUCH_OFF + 4)[0]
|
|
orig_l1 = struct.unpack_from("<I", data, LOOP_OFF)[0]
|
|
orig_l2 = struct.unpack_from("<I", data, LOOP_OFF + 4)[0]
|
|
assert orig_t1 == 0x27BDFF80, f"unexpected 0x47b820: {orig_t1:#x}" # addiu sp,sp,-0x80
|
|
assert orig_t2 == 0xAFBF007C, f"unexpected 0x47b824: {orig_t2:#x}" # sw ra,0x7c(sp)
|
|
assert orig_l1 == 0x27BDFFE0, f"unexpected 0x436fc0: {orig_l1:#x}" # addiu sp,sp,-0x20
|
|
assert orig_l2 == 0xAFBF001C, f"unexpected 0x436fc4: {orig_l2:#x}" # sw ra,0x1c(sp)
|
|
struct.pack_into("<I", data, TOUCH_OFF, enc_j(A.syms["touch_hook"]))
|
|
struct.pack_into("<I", data, TOUCH_OFF + 4, NOP)
|
|
struct.pack_into("<I", data, LOOP_OFF, enc_j(A.syms["loop_hook"]))
|
|
struct.pack_into("<I", data, LOOP_OFF + 4, NOP)
|
|
|
|
# v3: 主循环 hook (LiteGUI 主循环, 主线程每轮执行)。0x436fc0 非周期主循环
|
|
# (实证 G_TRACE 恒 0), 触发链改挂 0x44a898/0x44a950 两处 (同一循环的两个编译副本)。
|
|
for ui_vaddr, ui_orig in ((0x44A898, 0x0C122008), (0x44A950, 0x0C122008)):
|
|
UI_OFF = ui_vaddr - 0x400000
|
|
orig_u1 = struct.unpack_from("<I", data, UI_OFF)[0]
|
|
assert orig_u1 == ui_orig, f"unexpected {ui_vaddr:#x}: {orig_u1:#x} # jal 0x488020"
|
|
struct.pack_into("<I", data, UI_OFF, enc_j(A.syms["pending_hook_a" if ui_vaddr == 0x44A898 else "pending_hook_b"]))
|
|
struct.pack_into("<I", data, UI_OFF + 4, NOP)
|
|
|
|
for i in range(len(cave_code)):
|
|
assert data[CAVE_OFF + i] == 0, f"cave @ {CAVE_OFF+i:#x} not empty"
|
|
data[CAVE_OFF + i] = cave_code[i]
|
|
|
|
open(OUT, "wb").write(data)
|
|
print(f"patched: {STOCK} -> {OUT} ({len(data)} bytes)")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|