code wiki / (root) / nx_nxa_secdump.nx

nx_nxa_secdump.nx source

↩ module page · 305 lines · 11432 B

1// nx_nxa_secdump.nx -- THE MISSING NXA INSPECTOR: print the section directory of an NXA asset, 2// verify every section against the checksum the format already carries, reconcile the byte 3// partition, and on request print the leading words of any one section. 4// 5// WHY THIS EXISTS (measured 2026-09-01, lane=consumer-gap). The estate holds a WRITER for every 6// NXA section (nx_nxa_texc, nx_nxa_texm, nx_nxa_dyna, nx_nxa_morf, nx_nxa_skin, nx_garment_gen) 7// and a floor gate that prints tag:wordcount -- and NOTHING that can answer "are these two 8// assets the same bytes in the sections they share?" or "how many map records does the SHIPPED 9// TEXM carry, and how long is each?". nx_capsearch over 7,175 organs, corpus_complete=1, returns 10// no inspector. The consequence was concrete: a 40 MB superset claim had to be argued from 11// declared word counts alone, because nx_contentdiff CAPS BOTH SIDES AT 4,194,304 BYTES and 12// therefore cannot adjudicate any asset larger than 4 MiB at all. 13// 14// THE OBSERVATION THIS ORGAN MONETISES: the NXA directory ALREADY CARRIES a per-section 15// order-sensitive rolling checksum over that section's words. Byte-identity of a section between 16// two assets is therefore a FREE, FORMAT-NATIVE comparison. Equal tag + equal wordlen + equal 17// check is a weak-but-native identity claim, and it is stated as exactly that rather than 18// dressed up as a cryptographic hash. 19// 20// COMPOSES, NEVER RE-IMPLEMENTS: nxa_check2 comes from nx_nxa.nx, the ONE definition of the 21// format checksum, so this verifier cannot drift from the writers. 22// 23// nx_nxa_secdump <asset.nxa> -- directory + per-section verify + reconciliation 24// nx_nxa_secdump <asset.nxa> <TAG> [nwords] -- the same, plus the first nwords words of <TAG> 25// 26// EXITS: 0 OK (every section checksum recomputed and matched) 27// 1 I/O -- asset unreadable 28// 2 usage (including a non-numeric nwords, which REFUSES rather than defaulting) 29// 3 bad container (magic, version, section count, or a directory past EOF) 30// 4 named TAG not present in the directory 31// 5 at least one section FAILED verification (corrupt, hand-edited, or out of file) 32// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 33import "nx_syscalls.nx" 34import "nx_nxa.nx" 35 36// ---- exit contract ---- 37const SD_OK: i64 = 0 38const SD_E_IO: i64 = 1 39const SD_E_USAGE: i64 = 2 40const SD_E_BAD: i64 = 3 41const SD_E_NOTAG: i64 = 4 42const SD_E_CKSUM: i64 = 5 43 44// ---- container geometry, named rather than spelled twice ---- 45const SD_HDR_BYTES: i64 = 32 46const SD_DIRENT_BYTES: i64 = 32 47const SD_DIRENT_WORDS: i64 = 4 48const SD_WORD: i64 = 8 49const SD_TAG_CHARS: i64 = 4 50const SD_OFF_VER: i64 = 8 51const SD_OFF_NSEC: i64 = 16 52const SD_OFF_TOCCK: i64 = 24 53const SD_DIR_OFF: i64 = 8 54const SD_DIR_WLEN: i64 = 16 55const SD_DIR_CHECK: i64 = 24 56const SD_NSEC_MAX: i64 = 64 57const SD_MAGIC_CHARS: i64 = 8 58const SD_BYTE: i64 = 256 59const SD_BYTE_MASK: i64 = 255 60const SD_TOP_BYTE: i64 = 7 61const SD_ASCII_ZERO: i64 = 48 62const SD_DECIMAL: i64 = 10 63const SD_DIGIT_MAX: i64 = 9 64const SD_STDOUT: i64 = 1 65const SD_NUMBUF: i64 = 32 66const SD_CHECK_SEED: i64 = 1 67const SD_WORDS_DEFAULT: i64 = 24 68const SD_ARG_ASSET: i64 = 1 69const SD_ARG_TAG: i64 = 2 70const SD_ARG_NWORDS: i64 = 3 71const SD_ARGC_ASSET: i64 = 2 72const SD_ARGC_TAG: i64 = 3 73const SD_ARGC_NWORDS: i64 = 4 74const SD_TAGBUF: i64 = 8 75const SD_SCRATCH: i64 = 16 76const SD_MISS: i64 = 0 - 1 77 78func sd_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 79func sd_out(s: *u8) -> i64 { sys_write(SD_STDOUT, s, sd_slen(s)); return 0 } 80func sd_num(v: i64) -> i64 { 81 if v == 0 { sys_write(SD_STDOUT, "0" as *u8, 1); return 0 } 82 let o: *u8 = sys_mmap(SD_NUMBUF) 83 let t: *u8 = sys_mmap(SD_NUMBUF) 84 var m: i64 = v 85 if m < 0 { sys_write(SD_STDOUT, "-" as *u8, 1); m = 0 - m } 86 var d: i64 = 0 87 while m > 0 { o[d] = ((SD_ASCII_ZERO + (m % SD_DECIMAL)) as u8); m = m / SD_DECIMAL; d = d + 1 } 88 var k: i64 = 0 89 while k < d { t[k] = o[d - 1 - k]; k = k + 1 } 90 sys_write(SD_STDOUT, t, d) 91 return 0 92} 93// little-endian i64 read at a byte offset -- the decode nx_asset_floor_gate and nx_nxa share 94func sd_rd64(b: *u8, off: i64) -> i64 { 95 var v: i64 = 0 96 var i: i64 = SD_TOP_BYTE 97 while i >= 0 { v = v*SD_BYTE + ((b[off + i] & SD_BYTE_MASK) as i64); i = i - 1 } 98 return v 99} 100// SD_MISS on a non-numeric argument rather than 0: "the caller typed nonsense" and "the caller 101// asked for zero words" must not collapse into one answer. 102func sd_atoi(s: *u8) -> i64 { 103 var i: i64 = 0 104 var v: i64 = 0 105 var digits: i64 = 0 106 var ok: i64 = 1 107 while s[i] != (0 as u8) { 108 let c: i64 = (s[i] & SD_BYTE_MASK) as i64 109 if c < SD_ASCII_ZERO { ok = 0 } 110 if c > SD_ASCII_ZERO + SD_DIGIT_MAX { ok = 0 } 111 if ok == 1 { v = v*SD_DECIMAL + (c - SD_ASCII_ZERO); digits = digits + 1 } 112 i = i + 1 113 } 114 if ok == 0 { return SD_MISS } 115 if digits == 0 { return SD_MISS } 116 return v 117} 118func sd_tag_eq(b: *u8, e: i64, t: *u8) -> i64 { 119 var i: i64 = 0 120 while i < SD_TAG_CHARS { if b[e + i] != t[i] { return 0 } i = i + 1 } 121 return 1 122} 123 124func main(argc: i64, argv: *i64) -> i64 { 125 if argc < SD_ARGC_ASSET { 126 sd_out("usage: nx_nxa_secdump <asset.nxa> [TAG] [nwords]\n" as *u8) 127 sys_exit(SD_E_USAGE) 128 return SD_E_USAGE 129 } 130 let apath: *u8 = argv[SD_ARG_ASSET] as *u8 131 var want: *u8 = 0 as *u8 132 if argc >= SD_ARGC_TAG { want = argv[SD_ARG_TAG] as *u8 } 133 var nwords: i64 = SD_WORDS_DEFAULT 134 if argc >= SD_ARGC_NWORDS { 135 let nw: i64 = sd_atoi(argv[SD_ARG_NWORDS] as *u8) 136 if nw == SD_MISS { 137 sd_out("REFUSED non-numeric nwords -- a silent default publishes a window nobody asked for\n" as *u8) 138 sys_exit(SD_E_USAGE) 139 return SD_E_USAGE 140 } 141 nwords = nw 142 } 143 let lp: *i64 = sys_mmap(SD_SCRATCH) as *i64 144 let b: *u8 = sys_read_file(apath, lp) 145 if (b as i64) == 0 { 146 sd_out("NXA-SECDUMP UNREADABLE " as *u8) 147 sd_out(apath) 148 sd_out("\n" as *u8) 149 sys_exit(SD_E_IO) 150 return SD_E_IO 151 } 152 let flen: i64 = lp[0] 153 sd_out("NXA-SECDUMP asset=" as *u8) 154 sd_out(apath) 155 sd_out(" bytes=" as *u8) 156 sd_num(flen) 157 sd_out("\n" as *u8) 158 if flen < SD_HDR_BYTES { 159 sd_out("BAD-CONTAINER shorter than a header\n" as *u8) 160 sys_exit(SD_E_BAD) 161 return SD_E_BAD 162 } 163 var magic: i64 = 1 164 let mg: *u8 = "NXANIM01" as *u8 165 var i: i64 = 0 166 while i < SD_MAGIC_CHARS { if b[i] != mg[i] { magic = 0 } i = i + 1 } 167 if magic == 0 { 168 sd_out("BAD-CONTAINER magic is not NXANIM01\n" as *u8) 169 sys_exit(SD_E_BAD) 170 return SD_E_BAD 171 } 172 let ver: i64 = sd_rd64(b, SD_OFF_VER) 173 let nsec: i64 = sd_rd64(b, SD_OFF_NSEC) 174 sd_out("version=" as *u8) 175 sd_num(ver) 176 sd_out(" sections=" as *u8) 177 sd_num(nsec) 178 if nsec < 1 { 179 sd_out("\nBAD-CONTAINER section count below one\n" as *u8) 180 sys_exit(SD_E_BAD) 181 return SD_E_BAD 182 } 183 if nsec > SD_NSEC_MAX { 184 sd_out("\nBAD-CONTAINER section count above the format ceiling\n" as *u8) 185 sys_exit(SD_E_BAD) 186 return SD_E_BAD 187 } 188 if flen < SD_HDR_BYTES + nsec*SD_DIRENT_BYTES { 189 sd_out("\nBAD-CONTAINER directory extends past end of file\n" as *u8) 190 sys_exit(SD_E_BAD) 191 return SD_E_BAD 192 } 193 let tb: *i64 = ((b as i64) + SD_HDR_BYTES) as *i64 194 let tocck: i64 = sd_rd64(b, SD_OFF_TOCCK) 195 let toccalc: i64 = nxa_check2(SD_CHECK_SEED, tb, nsec*SD_DIRENT_WORDS) 196 var tocbad: i64 = 0 197 if tocck == toccalc { 198 sd_out(" toc_check=MATCH\n" as *u8) 199 } else { 200 sd_out(" toc_check=MISMATCH\n" as *u8) 201 tocbad = 1 202 } 203 204 let tagbuf: *u8 = sys_mmap(SD_TAGBUF) 205 var payload: i64 = 0 206 var bad: i64 = tocbad 207 var found: i64 = SD_MISS 208 var s: i64 = 0 209 while s < nsec { 210 let e: i64 = SD_HDR_BYTES + s*SD_DIRENT_BYTES 211 var ci: i64 = 0 212 while ci < SD_TAG_CHARS { tagbuf[ci] = b[e + ci]; ci = ci + 1 } 213 tagbuf[SD_TAG_CHARS] = (0 as u8) 214 let off: i64 = sd_rd64(b, e + SD_DIR_OFF) 215 let wl: i64 = sd_rd64(b, e + SD_DIR_WLEN) 216 let ck: i64 = sd_rd64(b, e + SD_DIR_CHECK) 217 payload = payload + wl*SD_WORD 218 sd_out("SEC idx=" as *u8) 219 sd_num(s) 220 sd_out(" tag=" as *u8) 221 sd_out(tagbuf) 222 sd_out(" off=" as *u8) 223 sd_num(off) 224 sd_out(" words=" as *u8) 225 sd_num(wl) 226 sd_out(" bytes=" as *u8) 227 sd_num(wl*SD_WORD) 228 sd_out(" check=" as *u8) 229 sd_num(ck) 230 // ANNOUNCE, never assume: a section whose extent leaves the file is REPORTED rather than 231 // read, because reading it is exactly how a corrupt asset becomes a crash in the reader. 232 if off + wl*SD_WORD > flen { 233 sd_out(" verify=OUT-OF-FILE\n" as *u8) 234 bad = 1 235 } else { 236 let pw: *i64 = ((b as i64) + off) as *i64 237 let cc: i64 = nxa_check2(SD_CHECK_SEED, pw, wl) 238 if cc == ck { 239 sd_out(" verify=MATCH\n" as *u8) 240 } else { 241 sd_out(" verify=MISMATCH\n" as *u8) 242 bad = 1 243 } 244 } 245 if (want as i64) != 0 { if sd_tag_eq(b, e, want) == 1 { found = s } } 246 s = s + 1 247 } 248 // THE PARTITION MUST SUM, AND THE SUM IS PRINTED. header + directory + payloads should 249 // account for every byte of the container; a non-zero residual is slack, padding or an 250 // overlap, and the reader is entitled to see it rather than have to infer it. 251 let accounted: i64 = SD_HDR_BYTES + nsec*SD_DIRENT_BYTES + payload 252 sd_out("RECONCILE header=" as *u8) 253 sd_num(SD_HDR_BYTES) 254 sd_out(" directory=" as *u8) 255 sd_num(nsec*SD_DIRENT_BYTES) 256 sd_out(" payload=" as *u8) 257 sd_num(payload) 258 sd_out(" accounted=" as *u8) 259 sd_num(accounted) 260 sd_out(" file=" as *u8) 261 sd_num(flen) 262 sd_out(" residual=" as *u8) 263 sd_num(flen - accounted) 264 sd_out("\n" as *u8) 265 266 if (want as i64) != 0 { 267 if found == SD_MISS { 268 sd_out("TAG-ABSENT " as *u8) 269 sd_out(want) 270 sd_out("\n" as *u8) 271 sys_exit(SD_E_NOTAG) 272 return SD_E_NOTAG 273 } 274 let e2: i64 = SD_HDR_BYTES + found*SD_DIRENT_BYTES 275 let off2: i64 = sd_rd64(b, e2 + SD_DIR_OFF) 276 let wl2: i64 = sd_rd64(b, e2 + SD_DIR_WLEN) 277 var lim: i64 = nwords 278 if lim > wl2 { lim = wl2 } 279 sd_out("WORDS tag=" as *u8) 280 sd_out(want) 281 sd_out(" shown=" as *u8) 282 sd_num(lim) 283 sd_out(" of=" as *u8) 284 sd_num(wl2) 285 sd_out("\n" as *u8) 286 var w: i64 = 0 287 while w < lim { 288 sd_out(" w[" as *u8) 289 sd_num(w) 290 sd_out("]=" as *u8) 291 sd_num(sd_rd64(b, off2 + w*SD_WORD)) 292 sd_out("\n" as *u8) 293 w = w + 1 294 } 295 if lim < wl2 { sd_out(" <== THIS WINDOW IS A PREFIX OF ITS OWN COUNT\n" as *u8) } 296 } 297 if bad == 1 { 298 sd_out("NXA-SECDUMP verdict=CORRUPT\n" as *u8) 299 sys_exit(SD_E_CKSUM) 300 return SD_E_CKSUM 301 } 302 sd_out("NXA-SECDUMP verdict=OK\n" as *u8) 303 sys_exit(SD_OK) 304 return SD_OK 305}