docs: full reverse-engineering writeup + RE toolbench

- docs/REVERSE_ENGINEERING.md: library extraction, scan-out ring, pan,
  chroma plane (V-first words), kernel-side notes, PLD delivery, timeline
- tools/: dumplib payload + sprx/xref hunters + frame renderers (own code only)
This commit is contained in:
fstat13 committed 2026-10-15 14:49:47 +02:00
1 parent 76a6408586
commit 2a58cbcb43
14 files changed
+3076

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# tools/ — the reverse-engineering bench
All scripts here are PSCapture's own tooling (no Sony code). They are the
instruments used to produce the findings in `docs/REVERSE_ENGINEERING.md`.
- `dumplib/` payload: extracts the system libraries from YOUR console
- `sprx.py` sprx (ELF) inspection/extraction
- `callsites.py` find RIP-relative call sites to an address
- `scan_refs.py` scan a disassembly for references to a target
- `whoimports.py`, `stubmap.py` import/stub mapping across libraries
- `p010png.py`, `nv12png.py` deterministic renderers of captured frames
- `flat.py`, `asmwin.py` misc binary helpers
Usage examples are in the headers of each file.
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
"""Stampa le righe di un disassemblato objdump il cui indirizzo cade in [lo,hi)."""
import re, sys
path, lo, hi = sys.argv[1], int(sys.argv[2], 0), int(sys.argv[3], 0)
pat = re.compile(r'^\s*([0-9a-f]+):')
for line in open(path, errors='replace'):
m = pat.match(line)
if not m:
continue
a = int(m.group(1), 16)
if lo <= a < hi:
sys.stdout.write(line)
+186
View File
@@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""Trova i call site di funzioni importate dentro un .sprx PS5.
In un ET_SCE_DYNAMIC le chiamate alle librerie esterne passano dalla PLT, e la
PLT e' anonima (nessun nome, nessuna sezione). Ma ogni stub PLT e' un
`jmp *disp32(%rip)` che punta a uno slot GOT, e quello slot e' descritto da una
rilocazione in .rela.plt il cui indice di simbolo punta al dynsym: li' c'e' il
NID. Quindi:
rilocazione .rela.plt -> (slot GOT -> NID)
scansione `ff 25` -> (stub PLT -> slot GOT)
scansione `e8` -> (call site -> stub PLT)
Da li' si legge come il chiamante riempie le strutture: molto piu' veloce che
ricostruirle a tentativi sulla console.
"""
import struct
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import sprx as sprxmod
def parse_full(path):
d = open(path, 'rb').read()
phoff = struct.unpack_from('<Q', d, 32)[0]
phes = struct.unpack_from('<H', d, 54)[0]
phn = struct.unpack_from('<H', d, 56)[0]
ph = []
for i in range(phn):
o = phoff + i * phes
t, fl, off, va, pa, fsz, msz, al = struct.unpack_from('<IIQQQQQQ', d, o)
ph.append((t, fl, off, va, fsz, msz))
def v2f(v):
for t, fl, off, va, fsz, msz in ph:
if t == 1 and va <= v < va + msz:
delta = v - va
if delta < fsz:
return off + delta
return None
dyn = [p for p in ph if p[0] == 2]
if not dyn:
return None
_, _, doff, _, dsz, _ = dyn[0]
tag = {}
ents = []
for i in range(dsz // 16):
t, v = struct.unpack_from('<QQ', d, doff + i * 16)
ents.append((t, v))
tag.setdefault(t, v)
strtab = v2f(tag.get(5, 0))
symtab = v2f(tag.get(6, 0))
hash_ = v2f(tag.get(4, 0))
def nm(off):
e = d.find(b'\x00', strtab + off)
return d[strtab + off:e].decode('latin1')
nchain = struct.unpack_from('<I', d, hash_ + 4)[0] if hash_ else 0
symnames = []
for i in range(nchain):
o = symtab + i * 24
st_name = struct.unpack_from('<I', d, o)[0]
symnames.append(nm(st_name) if st_name else '')
return {'data': d, 'ph': ph, 'tag': tag, 'ents': ents, 'v2f': v2f,
'symnames': symnames, 'nchain': nchain}
def plt_map(r):
"""slot GOT -> NID, dall'insieme delle rilocazioni PLT."""
d, v2f, tag = r['data'], r['v2f'], r['tag']
jmprel = tag.get(0x17) # DT_JMPREL
pltrelsz = tag.get(2) or tag.get(0x2b) # DT_PLTRELSZ (a volte tag 2)
out = {}
if not jmprel or not pltrelsz:
return out
off = v2f(jmprel)
if off is None:
off = jmprel
for i in range(pltrelsz // 24):
o = off + i * 24
r_offset, r_info, r_addend = struct.unpack_from('<QQq', d, o)
symidx = r_info >> 32
if symidx < len(r['symnames']):
name = r['symnames'][symidx]
nid = name.split('#')[0] if '#' in name else name
out[r_offset] = nid
return out
def scan(r):
"""Ritorna (stub PLT -> NID) e (call site -> stub PLT)."""
d, v2f, ph = r['data'], r['v2f'], r['ph']
got2nid = plt_map(r)
exec_segs = [p for p in ph if p[0] == 1 and (p[1] & 1)]
stub2nid = {}
call2stub = []
for t, fl, off, va, fsz, msz in exec_segs:
code = d[off:off + fsz]
# jmp *disp32(%rip) = ff 25 xx xx xx xx
for i in range(len(code) - 6):
if code[i] == 0xff and code[i + 1] == 0x25:
disp = struct.unpack_from('<i', code, i + 2)[0]
a = va + i
tgt = a + 6 + disp
if tgt in got2nid:
stub2nid[a] = got2nid[tgt]
# call rel32 = e8 xx xx xx xx
for i in range(len(code) - 5):
if code[i] == 0xe8:
rel = struct.unpack_from('<i', code, i + 1)[0]
a = va + i
call2stub.append((a, a + 5 + rel))
return stub2nid, call2stub, got2nid
def report(path, want_nids, ctx=14):
r = parse_full(path)
if not r:
print('%s: nessun PT_DYNAMIC' % path)
return
stub2nid, calls, got2nid = scan(r)
d, v2f = r['data'], r['v2f']
name = os.path.basename(path)
print('=' * 78)
print('%s stub PLT risolti: %d call site totali: %d' %
(name, len(stub2nid), len(calls)))
# stub per nome
print('--- stub PLT per le funzioni cercate ---')
for a, nid in sorted(stub2nid.items()):
if nid in want_nids:
print(' 0x%-7x -> %-12s %s' % (a, nid, want_nids[nid]))
print('--- call site ---')
for a, tgt in calls:
nid = stub2nid.get(tgt)
if not nid or nid not in want_nids:
continue
print()
print(' ### %s chiamata da 0x%x' % (want_nids[nid], a))
# decodifica il contesto attorno alla chiamata
off = v2f(a)
if off is None:
continue
start = max(0, off - ctx * 8)
lines = []
i = start
while i < off + 8:
b = d[i:i + 1]
lines.append('%08x %s' % (i, ' '.join('%02x' % c for c in d[i:i + 8])))
i += 8
for l in lines:
print(' ' + l)
if __name__ == '__main__':
want = {
'svzPXluOz8U': 'sceAvcap2Initialize',
'gojqghDU+1Y': 'sceAvcap2Terminate',
'4jwUgirLFrA': 'sceAvcap2OpenVideo',
'8CGNCwBsItI': 'sceAvcap2Start',
'pN0hIQZsMzc': 'sceAvcap2ReadVideo',
'jkv9MlN7gvs': 'sceAvcap2ReadVideo2',
'tR7gPe1i8hw': 'sceAvcap2Close',
'YaXDckFygvw': 'sceAvcap2RequestKeyFrame',
'Dht3tcbRoEY': 'sceAvcap2QueryVideoEncoderMemorySize',
'X8c-f8LgzHo': 'sceAvcap2MapMemory',
'2GC2UnBJHIA': 'sceAvcap2GetFramePitch',
'RW88AiwmL+Y': 'sceAvcap2GetSupportInformation',
'-NotZDC2pQc': 'sceAvcap2SetVideoEncodeConfig',
'NeKlrUv+lbY': 'sceAvcap2GetNonVclNalUnitStream',
'CYgd06bO5q0': 'sceAvcap2AllocateVideoEncoderMemory',
'j7ClJpPRVQI': 'sceAvcap2GetImagePasteMemory',
}
for p in sys.argv[1:]:
report(p, want)
+28
View File
@@ -0,0 +1,28 @@
# Modellato sul sample hello_world dell'SDK.
# PS5_PORT 9021 = elfldr: e' la porta aperta sulla console.
PS5_HOST ?= 192.168.0.36
PS5_PORT ?= 9021
ifdef PS5_PAYLOAD_SDK
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
else
$(error PS5_PAYLOAD_SDK is undefined — compila dentro il container ps5sdk)
endif
ELF := dumplib.elf
CFLAGS := -Wall -Werror -g -O2
all: $(ELF)
$(ELF): main.c
$(CC) $(CFLAGS) -o $@ $^
clean:
rm -f $(ELF)
# Spedisce il payload a elfldr sulla console; gira subito.
deploy: $(ELF)
$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) $^
.PHONY: all clean deploy
+2262
View File
File diff suppressed because it is too large. Load diff
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Estrae un segmento PT_LOAD di un .sprx come binario piatto, per objdump."""
import struct, sys
src, dst = sys.argv[1], sys.argv[2]
want = sys.argv[3] if len(sys.argv) > 3 else 'exec' # exec | all
d=open(src,'rb').read()
phoff=struct.unpack_from('<Q',d,32)[0]; phes=struct.unpack_from('<H',d,54)[0]; phn=struct.unpack_from('<H',d,56)[0]
segs=[]
for i in range(phn):
o=phoff+i*phes
t,fl,off,va,pa,fsz,msz,al=struct.unpack_from('<IIQQQQQQ',d,o)
if t==1: segs.append((fl,off,va,fsz,msz))
segs.sort(key=lambda s:s[2])
if want=='exec':
sel=[s for s in segs if s[0]&1]
if not sel: print('nessun segmento eseguibile'); sys.exit(1)
fl,off,va,fsz,msz=sel[0]
open(dst,'wb').write(d[off:off+fsz])
print('ADJUST_VMA=0x%x SIZE=0x%x' % (va,fsz))
else:
end=max(s[2]+s[4] for s in segs)
base=min(s[2] for s in segs)
buf=bytearray(end-base)
for fl,off,va,fsz,msz in segs:
buf[va-base:va-base+fsz]=d[off:off+fsz]
open(dst,'wb').write(bytes(buf))
print('ADJUST_VMA=0x%x SIZE=0x%x' % (base,len(buf)))
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
# -*- coding: latin-1 -*-
# Prova le geometrie possibili di un buffer NV12 e salva un PNG per ciascuna
# (cosi' si guarda direttamente cos'e').
#
# python -I nv12png.py <file.bin> <out.png> [W H STRIDEY STRIDEUV]
#
import struct
import sys
import zlib
def png_gray(path, w, h, rows):
raw = b''.join(b'\x00' + r for r in rows)
def chunk(tag, data):
c = tag + data
return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
png = b'\x89PNG\r\n\x1a\n'
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 0, 0, 0, 0))
png += chunk(b'IDAT', zlib.compress(raw, 6))
png += chunk(b'IEND', b'')
open(path, 'wb').write(png)
def png_rgb(path, w, h, rgb):
raw = b''.join(b'\x00' + rgb[y] for y in range(h))
def chunk(tag, data):
c = tag + data
return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
png = b'\x89PNG\r\n\x1a\n'
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
png += chunk(b'IDAT', zlib.compress(raw, 6))
png += chunk(b'IEND', b'')
open(path, 'wb').write(png)
def clamp(v):
return 0 if v < 0 else (255 if v > 255 else v)
def main():
path = sys.argv[1]
out = sys.argv[2]
data = open(path, 'rb').read()
if len(sys.argv) > 6:
w, h, sy, suv = (int(x) for x in sys.argv[3:7])
else:
w, h, sy, suv = 1920, 1080, 1920, 1920
y_plane = data[:sy * h]
uv_off = sy * h
uv = data[uv_off:uv_off + suv * (h // 2)]
rr = []
for yy in range(h):
row = bytearray()
ybase = yy * sy
uvrow = (yy // 2) * suv
for xx in range(w):
Y = y_plane[ybase + xx]
if uv_off and len(uv) >= uvrow + (xx // 2) * 2 + 2:
U = uv[uvrow + (xx // 2) * 2] - 128
V = uv[uvrow + (xx // 2) * 2 + 1] - 128
else:
U = V = 0
c = Y - 16
if c < 0:
c = 0
R = clamp((298 * c + 409 * V + 128) >> 8)
G = clamp((298 * c - 100 * U - 208 * V + 128) >> 8)
B = clamp((298 * c + 516 * U + 128) >> 8)
row += bytes((R, G, B))
rr.append(bytes(row))
png_rgb(out, w, h, rr)
print('scritto %s (%dx%d, strideY %d, strideUV %d)' % (out, w, h, sy, suv))
if __name__ == '__main__':
main()
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: latin-1 -*-
# Rende un frame P010 (10 bit in contenitori da 16) in PNG.
#
# python -I p010png.py <file.bin> <off> <out.png> [W H ]
#
import struct
import sys
import zlib
def chunk(tag, data):
c = tag + data
return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
def png_rgb(path, w, h, rows):
raw = b''.join(b'\x00' + rows[y] for y in range(h))
png = b'\x89PNG\r\n\x1a\n'
png += chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
png += chunk(b'IDAT', zlib.compress(raw, 6))
png += chunk(b'IEND', b'')
open(path, 'wb').write(png)
def clamp(v):
return 0 if v < 0 else (255 if v > 255 else v)
def main():
path = sys.argv[1]
off = int(sys.argv[2], 0)
out = sys.argv[3]
w = int(sys.argv[4]) if len(sys.argv) > 4 else 1920
h = int(sys.argv[5]) if len(sys.argv) > 5 else 1080
d = open(path, 'rb').read()
y_stride = w * 2
uv_off = off + y_stride * h
rgb = []
for y in range(h):
yb = off + y * y_stride
uvrow = uv_off + (y // 2) * (w // 2) * 4
row = bytearray()
for x in range(w):
Y = ((d[yb + x * 2] | (d[yb + x * 2 + 1] << 8)) >> 6) >> 2
p = uvrow + (x // 2) * 4
U = ((d[p] | (d[p + 1] << 8)) >> 6) >> 2
V = ((d[p + 2] | (d[p + 3] << 8)) >> 6) >> 2
c = Y - 16
U -= 128
V -= 128
R = clamp((298 * c + 409 * V + 128) >> 8)
G = clamp((298 * c - 100 * U - 208 * V + 128) >> 8)
B = clamp((298 * c + 516 * U + 128) >> 8)
row += bytes((R, G, B))
rgb.append(bytes(row))
png_rgb(out, w, h, rgb)
print('scritto ' + out)
if __name__ == '__main__':
main()
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# -*- coding: latin-1 -*-
# Scanner grezzo di riferimenti in un ELF64 LE x86-64: trova chi materializza un
# indirizzo (stringa/rodata/vtable) via:
# - lea reg,[rip+disp32] (48/4C 8d, o 8d senza REX)
# - mov reg,[rip+disp32] (48 8b / 48 8d)
# - valore assoluto u64 nei dati (tabelle/vtable)
#
# python -I scan_refs.py <elf> <hexaddr> [<hexaddr> ...]
#
import struct
import sys
def load(path):
data = open(path, 'rb').read()
e_phoff = struct.unpack_from('<Q', data, 0x20)[0]
e_phentsize = struct.unpack_from('<H', data, 0x36)[0]
e_phnum = struct.unpack_from('<H', data, 0x38)[0]
segs = []
for i in range(e_phnum):
off = e_phoff + i * e_phentsize
p_type, p_flags = struct.unpack_from('<II', data, off)
if p_type != 1:
continue
p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_align = \
struct.unpack_from('<QQQQQQ', data, off + 8)
segs.append([p_vaddr, p_offset, p_filesz, p_flags])
return data, segs
def scan(elf, targets):
data, segs = load(elf)
tset = set(targets)
# pass 1: valori assoluti u64 nei dati (vtable/tabelle con reloc pre-applicate)
abs_hits = set()
for vaddr, off, sz, fl in segs:
if fl & 1: # solo segmenti dati
continue
end = min(off + sz, len(data))
for i in range(off, end - 8, 8):
v64 = struct.unpack_from('<Q', data, i)[0]
if v64 in tset:
print('ABS64 @ %x = %x' % (vaddr + (i - off), v64))
abs_hits.add(vaddr + (i - off))
# pass 2: istruzioni x86-64 con displacement RIP-relativo
hits = set()
for vaddr, off, sz, fl in segs:
if not (fl & 1):
continue
end = min(off + sz, len(data))
buf = data[off:end]
base = vaddr
n = len(buf)
for i in range(n - 8):
# candidati: [REX] [8d|8b] modrm(rip)
j = i
rex = 0
if buf[j] in (0x48, 0x4c, 0x49, 0x4d, 0x44, 0x41, 0x40, 0x42, 0x43, 0x45, 0x46, 0x47):
rex = buf[j]
j += 1
if j >= n:
break
op = buf[j]
if op not in (0x8d, 0x8b, 0x89):
continue
if j + 6 > n:
continue
modrm = buf[j + 1]
if (modrm & 0xC7) != 0x05: # mod=00, rm=101 -> RIP+disp32
continue
# con 0x41/0x44 ecc. (REX.R/X/B) e' comunque RIP-rel se mod=00 rm=101
disp = struct.unpack_from('<i', buf, j + 2)[0]
ilen = (j - i) + 1 + 1 + 4
tgt = base + i + ilen + disp
if tgt in tset:
reg = (modrm >> 3) & 7
if rex & 4:
reg += 8
kind = {0x8d: 'lea', 0x8b: 'mov-r', 0x89: 'mov-w'}.get(op, hex(op))
print('CODE %s @ %x (r%d) -> %x' % (kind, base + i, reg, tgt))
hits.add(base + i)
print('abs hits: %d code hits: %d' % (len(abs_hits), len(hits)))
return hits
if __name__ == '__main__':
elf = sys.argv[1]
tgts = [int(x, 16) for x in sys.argv[2:]]
print('targets: %s' % [hex(t) for t in tgts])
scan(elf, tgts)
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Parser per .sprx PS4/PS5 (ET_SCE_DYNAMIC): export/import con nomi risolti via aerolib.csv."""
import struct, sys, os
def load_nid_db(p=None):
p = p or os.path.join(os.path.dirname(os.path.abspath(__file__)), 'aerolib.csv')
m={}
with open(p, encoding='utf-8', errors='replace') as f:
for line in f:
line=line.strip()
if not line: continue
parts=line.split(' ',1)
if len(parts)==2: m[parts[0]]=parts[1]
return m
def parse(path):
d=open(path,'rb').read()
phoff=struct.unpack_from('<Q',d,32)[0]
phes=struct.unpack_from('<H',d,54)[0]
phn=struct.unpack_from('<H',d,56)[0]
ph=[]
for i in range(phn):
o=phoff+i*phes
t,fl,off,va,pa,fsz,msz,al=struct.unpack_from('<IIQQQQQQ',d,o)
ph.append((t,off,va,fsz,msz))
def v2f(v):
for t,off,va,fsz,msz in ph:
if t==1 and va<=v<va+msz: return off+(v-va)
dyn=[p for p in ph if p[0]==2]
if not dyn: return None
_,doff,_,dsz,_=dyn[0]
strtab=symtab=hash_=None; ents=[]
for i in range(dsz//16):
t,v=struct.unpack_from('<QQ',d,doff+i*16)
ents.append((t,v))
if t==5: strtab=v2f(v)
if t==6: symtab=v2f(v)
if t==4: hash_=v2f(v)
if not strtab or not symtab: return None
def nm(off):
e=d.find(b'\x00',strtab+off)
return d[strtab+off:e].decode('latin1')
# nomi delle librerie: DT_NEEDED + tag SCE con indice
needed=[nm(v) for t,v in ents if t==1 and v<0x1000000]
libs={}
for t,v in ents:
if 0x61000000<=t<=0x61ffffff:
idx=v>>48; off=v&0xffffffff
if off and off<0x1000000:
try: s=nm(off)
except Exception: continue
if s and idx not in libs: libs[idx]=s
nchain=struct.unpack_from('<I',d,hash_+4)[0] if hash_ else 0
syms=[]
for i in range(nchain):
o=symtab+i*24
st_name,st_info,st_other,st_shndx,st_value,st_size=struct.unpack_from('<IBBHQQ',d,o)
if not st_name: continue
s=nm(st_name)
if '#' in s:
nid,lid,mid=s.split('#',2)
else:
nid,lid,mid=s,'',''
syms.append({'nid':nid,'lid':lid,'mid':mid,'info':st_info,'type':st_info&0xf,
'shndx':st_shndx,'value':st_value,'size':st_size})
return {'data':d,'phdrs':ph,'needed':needed,'libs':libs,'syms':syms,'strtab':strtab}
def report(path, db):
r=parse(path)
if not r:
print('%s: non e un .sprx valido' % path); return
name=os.path.basename(path)
print('='*78)
print('FILE: %s' % name)
print(' NEEDED: %s' % ', '.join(r['needed']))
exp=[s for s in r['syms'] if s['shndx']!=0 and s['value']!=0 and s['type']==2]
obj=[s for s in r['syms'] if s['shndx']!=0 and s['value']!=0 and s['type']==1]
imp=[s for s in r['syms'] if s['shndx']==0]
print(' EXPORT funzioni: %d EXPORT oggetti: %d IMPORT: %d' % (len(exp),len(obj),len(imp)))
print(' --- EXPORT (funzioni) ---')
for s in sorted(exp,key=lambda x:x['value']):
n=db.get(s['nid'],'??')
print(' %-12s 0x%-7x %s' % (s['nid'], s['value'], n))
if obj:
print(' --- EXPORT (oggetti) ---')
for s in sorted(obj,key=lambda x:x['value']):
print(' %-12s 0x%-7x %s' % (s['nid'], s['value'], db.get(s['nid'],'??')))
print(' --- IMPORT ---')
seen=set()
for s in sorted(imp,key=lambda x:db.get(x['nid'],'zz')):
n=db.get(s['nid'],'??')
if (n,s['lid']) in seen: continue
seen.add((n,s['lid']))
print(' %-12s #%s#%s %s' % (s['nid'], s['lid'], s['mid'], n))
print()
if __name__=='__main__':
db=load_nid_db()
print('DB NID: %d voci' % len(db))
for p in sys.argv[1:]:
report(p, db)
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env python3
"""Stampa la mappa completa stub PLT -> nome import, per un .sprx."""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import callsites, sprx
db = sprx.load_nid_db()
r = callsites.parse_full(sys.argv[1])
stub2nid, calls, got2nid = callsites.scan(r)
for a, nid in sorted(stub2nid.items()):
print('0x%-6x %-14s %s' % (a, nid, db.get(nid, '?')))
+23
View File
@@ -0,0 +1,23 @@
import sys, glob, os
sys.path.insert(0, os.path.expanduser('~/ps5-research/tools'))
import sprx
TARGETS = {
'svzPXluOz8U':'sceAvcap2Initialize','gojqghDU+1Y':'sceAvcap2Terminate',
'4jwUgirLFrA':'sceAvcap2OpenVideo','8CGNCwBsItI':'sceAvcap2Start',
'pN0hIQZsMzc':'sceAvcap2ReadVideo','jkv9MlN7gvs':'sceAvcap2ReadVideo2',
'tR7gPe1i8hw':'sceAvcap2Close','YaXDckFygvw':'sceAvcap2RequestKeyFrame',
'Dht3tcbRoEY':'sceAvcap2QueryVideoEncoderMemorySize','X8c-f8LgzHo':'sceAvcap2MapMemory',
'2GC2UnBJHIA':'sceAvcap2GetFramePitch','RW88AiwmL+Y':'sceAvcap2GetSupportInformation',
'-NotZDC2pQc':'sceAvcap2SetVideoEncodeConfig','NeKlrUv+lbY':'sceAvcap2GetNonVclNalUnitStream',
'0zsTiDhM0nU':'IPMI::Client::create','svzPXluOz8U':'sceAvcap2Initialize',
}
for p in sorted(glob.glob(os.path.join(os.path.expanduser('~/ps5-research/disklibs'),'*.sprx'))):
try: r = sprx.parse(p)
except Exception as e: continue
if not r: continue
hits=[]
for s in r['syms']:
if s['shndx']==0 and s['nid'] in TARGETS:
hits.append(TARGETS[s['nid']])
if hits:
print('%-42s importa: %s' % (os.path.basename(p), ', '.join(sorted(set(hits)))))