code wiki / (root) / nx_nxa_check.nx

nx_nxa_check.nx source

↩ module page · 255 lines · 11565 B

1// nx_nxa_check.nx -- the NXA format's fsck. Validates an .nxa BEYOND the checksums nxa_find 2// already enforces: CLUS coverage invariants (every triangle in exactly one cluster, counts 3// <=128, cluster bounds truly CONTAIN their triangles' vertices). A format is only as strong 4// as the tool that refuses a bad file -- this is that tool ("a session must remember < a tool 5// that cannot do the wrong thing"). 6// usage: nx_nxa_check <file.nxa> rc: 0 green, 1 invariant broken, 5 corrupt, 6 future 7// license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_nxa.nx" 10const K_MAGIC_65535: i64 = 65535 11const K_MAGIC_32767: i64 = 32767 12const K_MAGIC_65536: i64 = 65536 13const K_MAGIC_4096: i64 = 4096 14const K_MAGIC_13421772: i64 = 13421772 15const K_MAGIC_20132659: i64 = 20132659 16 17func ncw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 18func ncn(v: i64) -> i64 { 19 let t: *u8 = sys_mmap(32) as *u8 20 var m: i64 = v; var w: i64 = 0 21 if m<0 { t[w]=45 as u8; w=w+1; m=0-m } 22 if m==0 { t[w]=48 as u8; sys_write(1,t,w+1); return 0 } 23 let d: *u8 = sys_mmap(32) as *u8 24 var k: i64=0 25 while m>0 { d[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 26 var j: i64=0 27 while j<k { t[w]=d[k-1-j]; w=w+1; j=j+1 } 28 sys_write(1,t,w); return 0 29} 30 31// sign-extend a 16-bit lane out of an i64 word (channel-2 packed ANIM keys) 32func nc_i16(w: i64, sh: i64) -> i64 { 33 var v: i64 = (w >> sh) & K_MAGIC_65535 34 if v > K_MAGIC_32767 { v = v - K_MAGIC_65536 } 35 return v 36} 37 38func main(argc: i64, argv: *i64) -> i64 { 39 if argc < 2 { ncw("usage: nx_nxa_check <file.nxa>\n" as *u8); return 2 } 40 let lp: *i64 = sys_mmap(16) as *i64 41 let b: *u8 = sys_map_file(argv[1] as *u8, lp) 42 let flen: i64 = lp[0] 43 if flen < 96 { ncw("unreadable\n" as *u8); return 5 } 44 let h: *i64 = b as *i64 45 let vwo: i64 = nxa_find(b, flen, nxa_tag4("VERT" as *u8)) 46 if vwo == 0 - 2 { ncw("NXA-FUTURE-VERSION\n" as *u8); return 6 } 47 if vwo < 0 { ncw("NXA-CORRUPT vert\n" as *u8); return 5 } 48 let two: i64 = nxa_find(b, flen, nxa_tag4("TRIS" as *u8)) 49 if two < 0 { ncw("NXA-CORRUPT tris\n" as *u8); return 5 } 50 let nv: i64 = h[vwo] 51 let nt: i64 = h[two] 52 let vx: *i64 = ((b as i64) + vwo*8 + 8) as *i64 53 let tr: *i64 = ((b as i64) + two*8 + 8) as *i64 54 // TRIS invariant: every index in range 55 var t0: i64 = 0 56 while t0 < nt*3 { 57 if tr[t0] < 0 { ncw("RED tri index negative\n" as *u8); return 1 } 58 if tr[t0] >= nv { ncw("RED tri index out of range\n" as *u8); return 1 } 59 t0 = t0 + 1 60 } 61 ncw("verts=" as *u8); ncn(nv); ncw(" tris=" as *u8); ncn(nt) 62 let cwo: i64 = nxa_find(b, flen, nxa_tag4("CLUS" as *u8)) 63 if cwo == 0 - 3 { ncw(" NXA-CORRUPT clus\n" as *u8); return 5 } 64 // CLUS is OPTIONAL like every rig section -- absence must NOT skip the rig teeth 65 // (the early-return here left cluster-less rigged files with ZERO validation, seq1286) 66 if cwo < 0 { ncw(" clusters=absent" as *u8) } 67 if cwo >= 0 { 68 let ncl: i64 = h[cwo] 69 let cl: *i64 = ((b as i64) + cwo*8 + 8) as *i64 70 ncw(" clusters=" as *u8); ncn(ncl); ncw("\n" as *u8) 71 var covered: i64 = 0 72 var ci: i64 = 0 73 while ci < ncl { 74 let cb: i64 = ci*10 75 let ts: i64 = cl[cb] 76 let tc: i64 = cl[cb+1] 77 if tc < 1 { ncw("RED cluster empty\n" as *u8); return 1 } 78 if tc > 128 { ncw("RED cluster >128 tris\n" as *u8); return 1 } 79 if ts != covered { ncw("RED cluster ranges not contiguous\n" as *u8); return 1 } 80 if ts + tc > nt { ncw("RED cluster past tris\n" as *u8); return 1 } 81 // bounds truly contain every vertex of every triangle in the cluster 82 var q: i64 = 0 83 while q < tc { 84 var e: i64 = 0 85 while e < 3 { 86 let vi: i64 = tr[(ts+q)*3 + e] 87 var a: i64 = 0 88 while a < 3 { 89 let v: i64 = vx[vi*3 + a] 90 if v < cl[cb+2+a] { ncw("RED vertex below cluster bounds\n" as *u8); return 1 } 91 if v > cl[cb+5+a] { ncw("RED vertex above cluster bounds\n" as *u8); return 1 } 92 a = a + 1 93 } 94 e = e + 1 95 } 96 q = q + 1 97 } 98 covered = covered + tc 99 ci = ci + 1 100 } 101 if covered != nt { ncw("RED clusters cover " as *u8); ncn(covered); ncw(" of " as *u8); ncn(nt); ncw("\n" as *u8); return 1 } 102 ncw("coverage=" as *u8); ncn(covered); ncw("/" as *u8); ncn(nt) 103 } 104 // SKEL/SKIN (optional pair): parents valid + acyclic; weights sum EXACTLY 4096 per vertex 105 let swo: i64 = nxa_find(b, flen, nxa_tag4("SKEL" as *u8)) 106 if swo == 0 - 3 { ncw(" NXA-CORRUPT skel\n" as *u8); return 5 } 107 let kwo: i64 = nxa_find(b, flen, nxa_tag4("SKIN" as *u8)) 108 if kwo == 0 - 3 { ncw(" NXA-CORRUPT skin\n" as *u8); return 5 } 109 if swo >= 0 { 110 let njj: i64 = h[swo] 111 let sk: *i64 = ((b as i64) + swo*8 + 8) as *i64 112 var jj: i64 = 0 113 while jj < njj { 114 let par: i64 = sk[jj*8] 115 if par >= njj { ncw(" RED joint parent out of range\n" as *u8); return 1 } 116 if par == jj { ncw(" RED joint is its own parent\n" as *u8); return 1 } 117 // acyclic: walking up must reach a root within njj hops 118 var cur: i64 = jj 119 var hops: i64 = 0 120 while cur >= 0 { 121 cur = sk[cur*8] 122 hops = hops + 1 123 if hops > njj { ncw(" RED joint parent cycle\n" as *u8); return 1 } 124 } 125 jj = jj + 1 126 } 127 ncw(" joints=" as *u8); ncn(njj) 128 } 129 if kwo >= 0 { 130 if swo < 0 { ncw(" RED skin without skeleton\n" as *u8); return 1 } 131 let nkv: i64 = h[kwo] 132 if nkv != nv { ncw(" RED skin vert count mismatch\n" as *u8); return 1 } 133 let njj2: i64 = h[swo] 134 let kn: *i64 = ((b as i64) + kwo*8 + 8) as *i64 135 var vv: i64 = 0 136 while vv < nkv { 137 var wsum: i64 = 0 138 var s: i64 = 0 139 while s < 4 { 140 if kn[vv*8+s] < 0 { ncw(" RED skin joint negative\n" as *u8); return 1 } 141 if kn[vv*8+s] >= njj2 { ncw(" RED skin joint out of range\n" as *u8); return 1 } 142 wsum = wsum + kn[vv*8+4+s] 143 s = s + 1 144 } 145 if wsum != K_MAGIC_4096 { ncw(" RED skin weights sum != 4096\n" as *u8); return 1 } 146 vv = vv + 1 147 } 148 ncw(" skin=" as *u8); ncn(nkv) 149 } 150 // ANIM (optional): joint range, monotonic key times, quats near unit length 151 let awo: i64 = nxa_find(b, flen, nxa_tag4("ANIM" as *u8)) 152 if awo == 0 - 3 { ncw(" NXA-CORRUPT anim\n" as *u8); return 5 } 153 if awo >= 0 { 154 if swo < 0 { ncw(" RED anim without skeleton\n" as *u8); return 1 } 155 let njj3: i64 = h[swo] 156 let ntr: i64 = h[awo] 157 var rp: i64 = awo + 1 158 var tr2: i64 = 0 159 while tr2 < ntr { 160 let jt: i64 = h[rp] 161 if jt < 0 { ncw(" RED anim joint negative\n" as *u8); return 1 } 162 if jt >= njj3 { ncw(" RED anim joint out of range\n" as *u8); return 1 } 163 let ach: i64 = h[rp+1] 164 let nk: i64 = h[rp+2] 165 if nk < 1 { ncw(" RED anim track empty\n" as *u8); return 1 } 166 var pt2: i64 = 0 - 1 167 var kk2: i64 = 0 168 if ach == 2 { 169 // channel 2 = PACKED world-delta keys, 2 words: 170 // w0 [t_ms u16][dqx dqy dqz i16 q12] / w1 [dqw i16][dt xyz i16 mm] 171 while kk2 < nk { 172 let kb2: i64 = rp + 3 + kk2*2 173 let tt: i64 = h[kb2] & K_MAGIC_65535 174 if tt <= pt2 { if kk2 > 0 { ncw(" RED anim keys not monotonic\n" as *u8); return 1 } } 175 pt2 = tt 176 let qx: i64 = nc_i16(h[kb2], 16) 177 let qy: i64 = nc_i16(h[kb2], 32) 178 let qz: i64 = nc_i16(h[kb2], 48) 179 let qw: i64 = nc_i16(h[kb2+1], 0) 180 let n2: i64 = qx*qx + qy*qy + qz*qz + qw*qw 181 if n2 < K_MAGIC_13421772 { ncw(" RED anim quat far from unit\n" as *u8); return 1 } 182 if n2 > K_MAGIC_20132659 { ncw(" RED anim quat far from unit\n" as *u8); return 1 } 183 kk2 = kk2 + 1 184 } 185 rp = rp + 3 + nk*2 186 } 187 if ach != 2 { 188 while kk2 < nk { 189 let kb: i64 = rp + 3 + kk2*5 190 if h[kb] <= pt2 { if kk2 > 0 { ncw(" RED anim keys not monotonic\n" as *u8); return 1 } } 191 pt2 = h[kb] 192 let n2: i64 = h[kb+1]*h[kb+1] + h[kb+2]*h[kb+2] + h[kb+3]*h[kb+3] + h[kb+4]*h[kb+4] 193 if n2 < K_MAGIC_13421772 { ncw(" RED anim quat far from unit\n" as *u8); return 1 } 194 if n2 > K_MAGIC_20132659 { ncw(" RED anim quat far from unit\n" as *u8); return 1 } 195 kk2 = kk2 + 1 196 } 197 rp = rp + 3 + nk*5 198 } 199 tr2 = tr2 + 1 200 } 201 ncw(" anim_tracks=" as *u8); ncn(ntr) 202 } 203 // POSE (optional): pose_id unique, entry joints in range, quats near unit, walked 204 // length == the TOC's section length (an internal count cannot lie within a valid 205 // checksum -- this tooth closes that gap) 206 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8)) 207 if pwo == 0 - 3 { ncw(" NXA-CORRUPT pose\n" as *u8); return 5 } 208 if pwo >= 0 { 209 if swo < 0 { ncw(" RED pose without skeleton\n" as *u8); return 1 } 210 let njp: i64 = h[swo] 211 let npz: i64 = h[pwo] 212 if npz < 1 { ncw(" RED pose count out of range\n" as *u8); return 1 } 213 if npz > 256 { ncw(" RED pose count out of range\n" as *u8); return 1 } 214 let seen: *i64 = sys_mmap(256*8 + 64) as *i64 215 var rp5: i64 = pwo + 1 216 var pz: i64 = 0 217 while pz < npz { 218 let pid: i64 = h[rp5] 219 var s9: i64 = 0 220 while s9 < pz { 221 if seen[s9] == pid { ncw(" RED pose id duplicate\n" as *u8); return 1 } 222 s9 = s9 + 1 223 } 224 seen[pz] = pid 225 let ne: i64 = h[rp5+1] 226 if ne < 1 { ncw(" RED pose entries out of range\n" as *u8); return 1 } 227 if ne > njp { ncw(" RED pose entries out of range\n" as *u8); return 1 } 228 var e9: i64 = 0 229 while e9 < ne { 230 let eb: i64 = rp5 + 2 + e9*8 231 if h[eb] < 0 { ncw(" RED pose joint out of range\n" as *u8); return 1 } 232 if h[eb] >= njp { ncw(" RED pose joint out of range\n" as *u8); return 1 } 233 let n3: i64 = h[eb+1]*h[eb+1] + h[eb+2]*h[eb+2] + h[eb+3]*h[eb+3] + h[eb+4]*h[eb+4] 234 if n3 < K_MAGIC_13421772 { ncw(" RED pose quat far from unit\n" as *u8); return 1 } 235 if n3 > K_MAGIC_20132659 { ncw(" RED pose quat far from unit\n" as *u8); return 1 } 236 e9 = e9 + 1 237 } 238 rp5 = rp5 + 2 + ne*8 239 pz = pz + 1 240 } 241 // walked words must equal the TOC's declared section length 242 let nsq: i64 = h[2] 243 let tbq: *i64 = ((b as i64) + 32) as *i64 244 var sq: i64 = 0 245 while sq < nsq { 246 if tbq[sq*4] == nxa_tag4("POSE" as *u8) { 247 if rp5 - pwo != tbq[sq*4+2] { ncw(" RED pose length mismatch\n" as *u8); return 1 } 248 } 249 sq = sq + 1 250 } 251 ncw(" poses=" as *u8); ncn(npz) 252 } 253 ncw(" NXA-CHECK GREEN\n" as *u8) 254 return 0 255}