- FNA on .NET NativeAOT with SDL3 and FNA3D's NVN driver; 162 GLSLC outputs in shaders.nx.bin - Rewriter: data blocks in the text segment are left alone (dm/tools/nso_code.py; main starts with 1.7 MB of data that read as x18 sites); the census applies the same rule. The 52 modules of the 19 other games on hand rewrite byte-identically - Upside-down picture: nvnCommandBufferSetViewportSwizzles is recorded and a NEGATIVE_Y swizzle flips the viewport - Logo movie: an H.264 decoder made at Configure (parameter sets in csd-0 / csd-1) now gives RGBA in the native texture mode, not NV12 - SDK calls: vi::CreateLayer with a size; AudioIn (no microphone); the EOS NRO stays unloaded and the game plays offline - Memory for a 5 GB block: getenv answers the new profile key game_env (DOTNET_GCRegionRange: the GC range 3.75 → 1.5 GB); build cuts for malloc's address space (40 → 2.5 GB), the SDK node area and the SDK's 1 GB alignment of large regions - Modules: NVIDIA's OpenGL and Vulkan drivers are never loaded (profile_guess.py); the survey's hook list reads dm/gfx too - dm/host/Makefile: a build for another game always relinks (two builds in the same second kept the first game's host)
124 lines
5.2 KiB
Python
124 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Where an NSO's text segment holds code: function ranges from .eh_frame, and data blocks in the text that no function covers.
|
|
patch_nso.py and insn_census.py import it. Usage on its own: nso_code.py <module.nso | decompressed image .bin> (lists the data blocks)
|
|
|
|
A data block is a stretch of at least DATA_MIN bytes between FDE ranges in which more than a quarter of the words do not decode as instructions. Absolum's main
|
|
(.NET NativeAOT on SDK 18) starts its text with 1.7 MB of data that no code references (no adrp, no relocation): 62% of its words do not decode, and 6,858 of the
|
|
rest read as instructions that use x18, which a linear sweep would rewrite. Code without unwind information (zlib, libunwind's register restore) stays code: it decodes."""
|
|
import struct
|
|
|
|
DATA_MIN = 0x1000
|
|
|
|
|
|
def eh_frame_hdr(img):
|
|
"""(eh_frame_hdr offset, its end) from the MOD0 header"""
|
|
m0 = struct.unpack_from('<I', img, 4)[0]
|
|
assert img[m0:m0 + 4] == b'MOD0', 'no MOD0'
|
|
return m0 + struct.unpack_from('<i', img, m0 + 16)[0], m0 + struct.unpack_from('<i', img, m0 + 20)[0]
|
|
|
|
|
|
def _read(img, o, enc, ehs):
|
|
fmt, app = enc & 0xF, enc & 0x70
|
|
if fmt == 0x3: v, sz = struct.unpack_from('<I', img, o)[0], 4
|
|
elif fmt == 0xB: v, sz = struct.unpack_from('<i', img, o)[0], 4
|
|
elif fmt in (0x0, 0x4): v, sz = struct.unpack_from('<Q', img, o)[0], 8
|
|
elif fmt == 0xC: v, sz = struct.unpack_from('<q', img, o)[0], 8
|
|
else: raise ValueError(f'pointer encoding {enc:#x} at {o:#x}')
|
|
if app == 0x10: v += o
|
|
elif app == 0x30: v += ehs
|
|
return v, sz
|
|
|
|
|
|
def _uleb(img, o):
|
|
r = sh = 0
|
|
while True:
|
|
b = img[o]; o += 1; r |= (b & 0x7F) << sh; sh += 7
|
|
if not b & 0x80: return r, o
|
|
|
|
|
|
def fde_table(img):
|
|
"""[(function start, FDE offset)] from the eh_frame_hdr binary search table; empty when the module has no valid eh_frame_hdr (New Super Mario Bros. U Deluxe's MOD0 points elsewhere)"""
|
|
ehs, ehe = eh_frame_hdr(img)
|
|
if ehe - ehs < 12 or img[ehs] != 1 or img[ehs + 3] in (0xFF, 0): return []
|
|
_, penc, cenc, tenc = img[ehs:ehs + 4]
|
|
o = ehs + 4
|
|
_, s = _read(img, o, penc, ehs); o += s
|
|
n, s = _read(img, o, cenc, ehs); o += s
|
|
out = []
|
|
for _ in range(n):
|
|
a, s = _read(img, o, tenc, ehs); o += s
|
|
f, s = _read(img, o, tenc, ehs); o += s
|
|
out.append((a, f))
|
|
return out
|
|
|
|
|
|
def fde_ranges(img):
|
|
"""Sorted [(start, end)] of every function the FDEs describe (pc begin and pc range, in the encoding the FDE's CIE gives)"""
|
|
ehs, _ = eh_frame_hdr(img)
|
|
cies = {}
|
|
|
|
def cie_encoding(cie):
|
|
if cie not in cies:
|
|
p = cie + 8; ver = img[p]; p += 1
|
|
aug = img[p:img.index(b'\0', p)]; p += len(aug) + 1
|
|
_, p = _uleb(img, p) # code alignment
|
|
while img[p] & 0x80: p += 1 # data alignment (sleb)
|
|
p += 1
|
|
p = p + 1 if ver == 1 else _uleb(img, p)[1] # return address register
|
|
enc = 0
|
|
if aug.startswith(b'z'):
|
|
_, p = _uleb(img, p)
|
|
for ch in aug[1:]:
|
|
if ch == ord('R'): enc = img[p]; p += 1
|
|
elif ch == ord('P'): pe = img[p]; p += 1; p += _read(img, p, pe, ehs)[1]
|
|
elif ch == ord('L'): p += 1
|
|
cies[cie] = enc
|
|
return cies[cie]
|
|
|
|
out = []
|
|
for _, f in fde_table(img):
|
|
cptr = f + 4
|
|
enc = cie_encoding(cptr - struct.unpack_from('<I', img, cptr)[0])
|
|
st, s = _read(img, f + 8, enc, ehs)
|
|
ln, _ = _read(img, f + 8 + s, enc & 0xF, ehs)
|
|
out.append((st, st + ln))
|
|
return sorted(out)
|
|
|
|
|
|
def data_blocks(img, text_end, md):
|
|
"""Sorted [(start, end)] of the data blocks in text [0, text_end) (see the module docstring). md: a capstone AArch64 disassembler"""
|
|
gaps, last = [], 0
|
|
for s, e in fde_ranges(img):
|
|
if s >= text_end: break
|
|
if s > last: gaps.append((last, s))
|
|
last = max(last, e)
|
|
if last < text_end: gaps.append((last, text_end))
|
|
out = []
|
|
for s, e in gaps:
|
|
s, e = (s + 3) & ~3, e & ~3
|
|
if e - s < DATA_MIN: continue
|
|
undecodable = sum(1 for a in range(s, e, 4) if next(md.disasm_lite(bytes(img[a:a + 4]), a), None) is None)
|
|
if undecodable * 4 > (e - s) // 4: out.append((s, e))
|
|
return out
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM
|
|
p = sys.argv[1]; raw = open(p, 'rb').read()
|
|
if raw[:4] == b'NSO0':
|
|
import lz4.block
|
|
flags = struct.unpack_from('<I', raw, 0xc)[0]
|
|
segs = [struct.unpack_from('<III', raw, 0x10 + i * 0x10) for i in range(3)]
|
|
csz = struct.unpack_from('<III', raw, 0x60)
|
|
img = bytearray(segs[2][1] + segs[2][2])
|
|
for i, (fo, mo, sz) in enumerate(segs):
|
|
seg = raw[fo:fo + csz[i]]
|
|
img[mo:mo + sz] = lz4.block.decompress(seg, uncompressed_size=sz) if flags >> i & 1 else seg
|
|
text_end = segs[0][2]
|
|
else:
|
|
img = raw; text_end = int(sys.argv[2], 0)
|
|
blocks = data_blocks(img, text_end, Cs(CS_ARCH_ARM64, CS_MODE_ARM))
|
|
print(f'{p}: {len(blocks)} data blocks in text, {sum(e - s for s, e in blocks)} bytes')
|
|
for s, e in blocks: print(f' {s:#x}..{e:#x} ({e - s} bytes)')
|