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}