code wiki / (root) / nx_pmx.nx

nx_pmx.nx source

↩ module page · 557 lines · 21931 B

1// nx_pmx.nx -- PMX (MikuMikuDance 2.x) MODEL READER: header, section census, and the BONE TABLE. 2// 3// WHY: measured 2026-08-07 from vrstormlab.com/dancexr/preparecontent -- PMX is DanceXR's primary 4// model format. It is also where the MMD standard BONE NAMES live, and bone names are what a 5// retarget binds to. nx_bvhfk gives us third-party motion in world space; PMX gives us the named 6// skeleton that motion is authored against. 7// 8// *YOU CANNOT REACH THE BONES WITHOUT WALKING EVERYTHING, WHICH IS WHY THE WALK PROVES ITSELF. 9// PMX vertex records are VARIABLE LENGTH -- the size depends on a per-vertex weight-deform byte 10// (BDEF1/BDEF2/BDEF4/SDEF/QDEF) and on index widths declared in the header globals. Miscount one 11// vertex by one byte and the bone section is garbage. There is no way to "just skip to bones". 12// 13// FAIL-CLOSED: every read is bounds-checked against the ACTUAL file length before it happens. 14// A count in a header is a CLAIM, never an authority -- the same discipline as nx_vmd, and here it 15// matters more because a lying vertex count would drive a variable-stride walk off the buffer. 16// 17// TEXT: PMX globals[0] declares UTF-16LE (0) or UTF-8 (1). Names are emitted as UTF-8 either way; 18// UTF-16LE is transcoded (BMP). We do NOT guess an encoding -- the file states it. 19// 20// usage: nx_pmx info <file.pmx> version, encoding, index widths, per-section counts 21// nx_pmx bones <file.pmx> bone table: index, parent, position, name 22// nx_pmx --kat selftest: 6 teeth, 3 of them anti-vacuity 23// exit 0 ok | 2 unreadable | 3 usage | 4 REFUSED | 1 KAT RED 24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 25import "nx_gate_verdict.nx" 26const PX_MAGIC_2147483648: i64 = 2147483648 27const PX_MAGIC_4294967296: i64 = 4294967296 28const PX_MAGIC_32768: i64 = 32768 29const PX_MAGIC_65536: i64 = 65536 30const PX_MAGIC_2048: i64 = 2048 31const PX_MAGIC_1073741824: i64 = 1073741824 32const PX_MAGIC_1000000: i64 = 1000000 33 34const PX_HDRSIG: i64 = 4 35const PX_MAXBONE: i64 = 1024 36const PX_NAMEW: i64 = 128 37const PX_ENC_U16: i64 = 0 38const PX_ENC_U8: i64 = 1 39const PX_KATBUF: i64 = 8192 40 41// globals slots 42const PX_G_ENC: i64 = 0 43const PX_G_ADDUV: i64 = 1 44const PX_G_VIDX: i64 = 2 45const PX_G_TIDX: i64 = 3 46const PX_G_MIDX: i64 = 4 47const PX_G_BIDX: i64 = 5 48const PX_G_MOIDX: i64 = 6 49const PX_G_RIDX: i64 = 7 50 51// weight deform types 52const PX_BDEF1: i64 = 0 53const PX_BDEF2: i64 = 1 54const PX_BDEF4: i64 = 2 55const PX_SDEF: i64 = 3 56const PX_QDEF: i64 = 4 57 58// bone flags 59const PX_BF_TAILIDX: i64 = 1 60const PX_BF_IK: i64 = 32 61const PX_BF_INH_ROT: i64 = 256 62const PX_BF_INH_TRN: i64 = 512 63const PX_BF_FIXAXIS: i64 = 1024 64const PX_BF_LOCALAX: i64 = 2048 65const PX_BF_EXTPAR: i64 = 8192 66 67// refusal codes 68const PX_R_MAGIC: i64 = 0 - 1 69const PX_R_TRUNC: i64 = 0 - 2 70const PX_R_COUNT: i64 = 0 - 3 71const PX_R_CAP: i64 = 0 - 4 72const PX_R_DEFORM:i64 = 0 - 5 73 74func px_slen(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { i = i + 1 } return i } 75 76func px_u8(b: *u8, o: i64) -> i64 { return (b[o] & 0xff) as i64 } 77func px_u16(b: *u8, o: i64) -> i64 { return px_u8(b,o) | (px_u8(b,o+1) << 8) } 78func px_u32(b: *u8, o: i64) -> i64 { 79 return px_u8(b,o) | (px_u8(b,o+1) << 8) | (px_u8(b,o+2) << 16) | (px_u8(b,o+3) << 24) 80} 81func px_i32(b: *u8, o: i64) -> i64 { 82 let v: i64 = px_u32(b, o) 83 if v >= PX_MAGIC_2147483648 { return v - PX_MAGIC_4294967296 } 84 return v 85} 86// SIGNED index of declared width; -1 means "none" and MUST survive as -1, not 255/65535. 87func px_idx(b: *u8, o: i64, w: i64) -> i64 { 88 if w == 1 { let v: i64 = px_u8(b,o); if v >= 128 { return v - 256 } return v } 89 if w == 2 { let v: i64 = px_u16(b,o); if v >= PX_MAGIC_32768 { return v - PX_MAGIC_65536 } return v } 90 return px_i32(b, o) 91} 92 93// ---- cursor discipline: C[0] = offset, and every advance is bounds-proven first ---- 94func px_need(C: *i64, len: i64, n: i64) -> i64 { 95 if C[0] < 0 { return 0 } 96 if C[0] + n > len { return 0 } 97 return 1 98} 99 100// UTF-16LE (BMP) -> UTF-8. The file DECLARES its encoding; we never sniff it. 101func px_u16_to_u8(src: *u8, n: i64, dst: *u8, cap: i64) -> i64 { 102 var i: i64 = 0 103 var o: i64 = 0 104 while i + 1 < n { 105 let c: i64 = px_u8(src, i) | (px_u8(src, i+1) << 8) 106 if c < 128 { if o + 1 < cap { dst[o] = c as u8; o = o + 1 } } 107 else { 108 if c < PX_MAGIC_2048 { 109 if o + 2 < cap { 110 dst[o] = (192 | (c >> 6)) as u8 111 dst[o+1] = (128 | (c & 63)) as u8 112 o = o + 2 113 } 114 } else { 115 if o + 3 < cap { 116 dst[o] = (224 | (c >> 12)) as u8 117 dst[o+1] = (128 | ((c >> 6) & 63)) as u8 118 dst[o+2] = (128 | (c & 63)) as u8 119 o = o + 3 120 } 121 } 122 } 123 i = i + 2 124 } 125 dst[o] = 0 as u8 126 return o 127} 128 129// read one PMX text field (int32 byte-length + bytes) into dst as UTF-8 130func px_text(b: *u8, C: *i64, len: i64, enc: i64, dst: *u8, cap: i64) -> i64 { 131 if px_need(C, len, 4) == 0 { C[0] = 0 - 1; return PX_R_TRUNC } 132 let n: i64 = px_i32(b, C[0]) 133 C[0] = C[0] + 4 134 if n < 0 { C[0] = 0 - 1; return PX_R_COUNT } 135 if px_need(C, len, n) == 0 { C[0] = 0 - 1; return PX_R_TRUNC } 136 if (dst as i64) != 0 { 137 if enc == PX_ENC_U16 { px_u16_to_u8(((b as i64) + C[0]) as *u8, n, dst, cap) } 138 else { 139 var i: i64 = 0 140 while i < n { if i < cap - 1 { dst[i] = b[C[0] + i] } i = i + 1 } 141 var e: i64 = n 142 if e > cap - 1 { e = cap - 1 } 143 dst[e] = 0 as u8 144 } 145 } 146 C[0] = C[0] + n 147 return 0 148} 149 150// per-vertex stride is DATA, not a constant: deform type + declared index widths decide it 151func px_vertex(b: *u8, C: *i64, len: i64, G: *i64) -> i64 { 152 let base: i64 = 12 + 12 + 8 + G[PX_G_ADDUV] * 16 153 if px_need(C, len, base + 1) == 0 { return PX_R_TRUNC } 154 C[0] = C[0] + base 155 let dt: i64 = px_u8(b, C[0]) 156 C[0] = C[0] + 1 157 let w: i64 = G[PX_G_BIDX] 158 var need: i64 = 0 159 if dt == PX_BDEF1 { need = w } 160 else { if dt == PX_BDEF2 { need = w * 2 + 4 } 161 else { if dt == PX_BDEF4 { need = w * 4 + 16 } 162 else { if dt == PX_SDEF { need = w * 2 + 4 + 36 } 163 else { if dt == PX_QDEF { need = w * 4 + 16 } 164 else { return PX_R_DEFORM } } } } } 165 if px_need(C, len, need + 4) == 0 { return PX_R_TRUNC } 166 C[0] = C[0] + need + 4 167 return 0 168} 169 170func px_material(b: *u8, C: *i64, len: i64, G: *i64) -> i64 { 171 let enc: i64 = G[PX_G_ENC] 172 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 173 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 174 let fixed: i64 = 16 + 12 + 4 + 12 + 1 + 16 + 4 175 if px_need(C, len, fixed) == 0 { return PX_R_TRUNC } 176 C[0] = C[0] + fixed 177 let tw: i64 = G[PX_G_TIDX] 178 if px_need(C, len, tw * 2 + 2) == 0 { return PX_R_TRUNC } 179 C[0] = C[0] + tw * 2 180 C[0] = C[0] + 1 181 let toonref: i64 = px_u8(b, C[0]) 182 C[0] = C[0] + 1 183 if toonref == 1 { 184 if px_need(C, len, 1) == 0 { return PX_R_TRUNC } 185 C[0] = C[0] + 1 186 } else { 187 if px_need(C, len, tw) == 0 { return PX_R_TRUNC } 188 C[0] = C[0] + tw 189 } 190 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 191 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 192 C[0] = C[0] + 4 193 return 0 194} 195 196// H slots 197const PX_H_VER: i64 = 0 198const PX_H_NVERT: i64 = 1 199const PX_H_NSURF: i64 = 2 200const PX_H_NTEX: i64 = 3 201const PX_H_NMAT: i64 = 4 202const PX_H_NBONE: i64 = 5 203const PX_H_BONEOFF: i64 = 6 204const PX_H_WORDS: i64 = 12 205 206func px_walk(b: *u8, len: i64, G: *i64, H: *i64, names: *u8, bparent: *i64, bpos: *i64) -> i64 { 207 if len < 8 { return PX_R_MAGIC } 208 if b[0] != (80 as u8) { return PX_R_MAGIC } 209 if b[1] != (77 as u8) { return PX_R_MAGIC } 210 if b[2] != (88 as u8) { return PX_R_MAGIC } 211 if b[3] != (32 as u8) { return PX_R_MAGIC } 212 H[PX_H_VER] = px_u32(b, 4) // f32 bits; reported raw, never reinterpreted as a float 213 let C: *i64 = sys_mmap(16) as *i64 214 C[0] = 8 215 if px_need(C, len, 1) == 0 { return PX_R_TRUNC } 216 let ng: i64 = px_u8(b, C[0]) 217 C[0] = C[0] + 1 218 if ng < 8 { return PX_R_COUNT } 219 if px_need(C, len, ng) == 0 { return PX_R_TRUNC } 220 var i: i64 = 0 221 while i < 8 { G[i] = px_u8(b, C[0] + i); i = i + 1 } 222 C[0] = C[0] + ng 223 let enc: i64 = G[PX_G_ENC] 224 225 // four header texts: name local, name universal, comment local, comment universal 226 if px_text(b, C, len, enc, names, PX_NAMEW) < 0 { return PX_R_TRUNC } 227 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 228 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 229 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 230 231 // ---- vertices (variable stride) ---- 232 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 233 let nv: i64 = px_i32(b, C[0]) 234 C[0] = C[0] + 4 235 if nv < 0 { return PX_R_COUNT } 236 H[PX_H_NVERT] = nv 237 var v: i64 = 0 238 while v < nv { 239 let r: i64 = px_vertex(b, C, len, G) 240 if r < 0 { return r } 241 v = v + 1 242 } 243 // ---- surfaces (index count, NOT triangle count) ---- 244 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 245 let ns: i64 = px_i32(b, C[0]) 246 C[0] = C[0] + 4 247 if ns < 0 { return PX_R_COUNT } 248 H[PX_H_NSURF] = ns 249 if px_need(C, len, ns * G[PX_G_VIDX]) == 0 { return PX_R_TRUNC } 250 C[0] = C[0] + ns * G[PX_G_VIDX] 251 // ---- textures ---- 252 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 253 let nt: i64 = px_i32(b, C[0]) 254 C[0] = C[0] + 4 255 if nt < 0 { return PX_R_COUNT } 256 H[PX_H_NTEX] = nt 257 var t: i64 = 0 258 while t < nt { 259 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 260 t = t + 1 261 } 262 // ---- materials ---- 263 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 264 let nm: i64 = px_i32(b, C[0]) 265 C[0] = C[0] + 4 266 if nm < 0 { return PX_R_COUNT } 267 H[PX_H_NMAT] = nm 268 var m: i64 = 0 269 while m < nm { 270 let rm: i64 = px_material(b, C, len, G) 271 if rm < 0 { return rm } 272 m = m + 1 273 } 274 // ---- bones ---- 275 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 276 let nb: i64 = px_i32(b, C[0]) 277 C[0] = C[0] + 4 278 if nb < 0 { return PX_R_COUNT } 279 if nb > PX_MAXBONE { return PX_R_CAP } 280 H[PX_H_NBONE] = nb 281 H[PX_H_BONEOFF] = C[0] 282 let bw: i64 = G[PX_G_BIDX] 283 var k: i64 = 0 284 while k < nb { 285 if px_text(b, C, len, enc, ((names as i64) + (k + 1) * PX_NAMEW) as *u8, PX_NAMEW) < 0 { return PX_R_TRUNC } 286 if px_text(b, C, len, enc, 0 as *u8, 0) < 0 { return PX_R_TRUNC } 287 if px_need(C, len, 12) == 0 { return PX_R_TRUNC } 288 bpos[k*3] = px_u32(b, C[0]) 289 bpos[k*3 + 1] = px_u32(b, C[0] + 4) 290 bpos[k*3 + 2] = px_u32(b, C[0] + 8) 291 C[0] = C[0] + 12 292 if px_need(C, len, bw + 4 + 2) == 0 { return PX_R_TRUNC } 293 bparent[k] = px_idx(b, C[0], bw) 294 C[0] = C[0] + bw + 4 295 let flags: i64 = px_u16(b, C[0]) 296 C[0] = C[0] + 2 297 // every conditional field below is DECLARED BY THE FLAGS -- skipping one desyncs the rest 298 if (flags & PX_BF_TAILIDX) != 0 { 299 if px_need(C, len, bw) == 0 { return PX_R_TRUNC } 300 C[0] = C[0] + bw 301 } else { 302 if px_need(C, len, 12) == 0 { return PX_R_TRUNC } 303 C[0] = C[0] + 12 304 } 305 if (flags & PX_BF_INH_ROT) != 0 { 306 if px_need(C, len, bw + 4) == 0 { return PX_R_TRUNC } 307 C[0] = C[0] + bw + 4 308 } else { 309 if (flags & PX_BF_INH_TRN) != 0 { 310 if px_need(C, len, bw + 4) == 0 { return PX_R_TRUNC } 311 C[0] = C[0] + bw + 4 312 } 313 } 314 if (flags & PX_BF_FIXAXIS) != 0 { 315 if px_need(C, len, 12) == 0 { return PX_R_TRUNC } 316 C[0] = C[0] + 12 317 } 318 if (flags & PX_BF_LOCALAX) != 0 { 319 if px_need(C, len, 24) == 0 { return PX_R_TRUNC } 320 C[0] = C[0] + 24 321 } 322 if (flags & PX_BF_EXTPAR) != 0 { 323 if px_need(C, len, 4) == 0 { return PX_R_TRUNC } 324 C[0] = C[0] + 4 325 } 326 if (flags & PX_BF_IK) != 0 { 327 if px_need(C, len, bw + 4 + 4 + 4) == 0 { return PX_R_TRUNC } 328 C[0] = C[0] + bw + 4 + 4 329 let nl: i64 = px_i32(b, C[0]) 330 C[0] = C[0] + 4 331 if nl < 0 { return PX_R_COUNT } 332 var l: i64 = 0 333 while l < nl { 334 if px_need(C, len, bw + 1) == 0 { return PX_R_TRUNC } 335 C[0] = C[0] + bw 336 let lim: i64 = px_u8(b, C[0]) 337 C[0] = C[0] + 1 338 if lim == 1 { 339 if px_need(C, len, 24) == 0 { return PX_R_TRUNC } 340 C[0] = C[0] + 24 341 } 342 l = l + 1 343 } 344 } 345 k = k + 1 346 } 347 return 0 348} 349 350func px_refuse(c: i64) -> i64 { 351 gv_puts("nx_pmx REFUSED code=" as *u8); gv_num(0 - c) 352 if c == PX_R_MAGIC { gv_puts(" not-a-PMX" as *u8) } 353 if c == PX_R_TRUNC { gv_puts(" truncated-or-count-exceeds-bytes" as *u8) } 354 if c == PX_R_COUNT { gv_puts(" negative-count" as *u8) } 355 if c == PX_R_CAP { gv_puts(" bone-cap" as *u8) } 356 if c == PX_R_DEFORM{ gv_puts(" unknown-weight-deform-type" as *u8) } 357 gv_puts("\n" as *u8) 358 return 4 359} 360 361// ===== KAT ======================================================== 362func px_put(b: *u8, o: i64, s: *u8) -> i64 { 363 let n: i64 = px_slen(s) 364 var i: i64 = 0 365 while i < n { b[o + i] = s[i]; i = i + 1 } 366 return o + n 367} 368func px_p32(b: *u8, o: i64, v: i64) -> i64 { 369 b[o] = (v & 0xff) as u8; b[o+1] = ((v >> 8) & 0xff) as u8 370 b[o+2] = ((v >> 16) & 0xff) as u8; b[o+3] = ((v >> 24) & 0xff) as u8 371 return o + 4 372} 373func px_ptext(b: *u8, o: i64, s: *u8) -> i64 { 374 let n: i64 = px_slen(s) 375 var p: i64 = px_p32(b, o, n) 376 var i: i64 = 0 377 while i < n { b[p + i] = s[i]; i = i + 1 } 378 return p + n 379} 380 381// A REAL minimal PMX 2.0: UTF-8, 1-byte indices, 1 vertex (BDEF1), 3 surfaces, 0 textures, 382// 1 material, 2 bones (flags 0 -> vec3 tail, no IK/inherit/axis). 383func px_fixture(b: *u8) -> i64 { 384 var i: i64 = 0 385 while i < PX_KATBUF { b[i] = 0 as u8; i = i + 1 } 386 var o: i64 = px_put(b, 0, "PMX " as *u8) 387 o = px_p32(b, o, PX_MAGIC_1073741824) // f32 2.0 bits 388 b[o] = 8 as u8; o = o + 1 389 b[o] = 1 as u8; o = o + 1 // encoding UTF-8 390 b[o] = 0 as u8; o = o + 1 // addUV 0 391 b[o] = 1 as u8; o = o + 1 // vertex idx 1 392 b[o] = 1 as u8; o = o + 1 // texture idx 1 393 b[o] = 1 as u8; o = o + 1 // material idx 1 394 b[o] = 1 as u8; o = o + 1 // bone idx 1 395 b[o] = 1 as u8; o = o + 1 // morph idx 1 396 b[o] = 1 as u8; o = o + 1 // rigidbody idx 1 397 o = px_ptext(b, o, "KAT" as *u8) 398 o = px_ptext(b, o, "KAT" as *u8) 399 o = px_ptext(b, o, "" as *u8) 400 o = px_ptext(b, o, "" as *u8) 401 o = px_p32(b, o, 1) // 1 vertex 402 o = o + 12 + 12 + 8 // pos, normal, uv 403 b[o] = 0 as u8; o = o + 1 // BDEF1 404 b[o] = 0 as u8; o = o + 1 // bone index 405 o = o + 4 // edge scale 406 o = px_p32(b, o, 3) // 3 surface indices 407 o = o + 3 408 o = px_p32(b, o, 0) // 0 textures 409 o = px_p32(b, o, 1) // 1 material 410 o = px_ptext(b, o, "MAT" as *u8) 411 o = px_ptext(b, o, "" as *u8) 412 o = o + 16 + 12 + 4 + 12 // diffuse specular specularity ambient 413 b[o] = 0 as u8; o = o + 1 // draw flags 414 o = o + 16 + 4 // edge colour + scale 415 b[o] = 0 as u8; o = o + 1 // texture index 416 b[o] = 0 as u8; o = o + 1 // environment index 417 b[o] = 0 as u8; o = o + 1 // environment blend 418 b[o] = 1 as u8; o = o + 1 // toon reference = internal 419 b[o] = 0 as u8; o = o + 1 // toon value 420 o = px_ptext(b, o, "" as *u8) // meta 421 o = px_p32(b, o, 3) // surface count 422 o = px_p32(b, o, 2) // 2 bones 423 o = px_ptext(b, o, "root" as *u8) 424 o = px_ptext(b, o, "" as *u8) 425 o = o + 12 // position 426 b[o] = 255 as u8; o = o + 1 // parent -1 427 o = px_p32(b, o, 0) // layer 428 b[o] = 0 as u8; b[o+1] = 0 as u8; o = o + 2 // flags 0 429 o = o + 12 // tail vec3 430 o = px_ptext(b, o, "spine" as *u8) 431 o = px_ptext(b, o, "" as *u8) 432 o = o + 12 433 b[o] = 0 as u8; o = o + 1 // parent 0 434 o = px_p32(b, o, 0) 435 b[o] = 0 as u8; b[o+1] = 0 as u8; o = o + 2 436 o = o + 12 437 return o 438} 439 440func px_streq(a: *u8, s: *u8) -> i64 { 441 var i: i64 = 0 442 while s[i] != (0 as u8) { if a[i] != s[i] { return 0 } i = i + 1 } 443 if a[i] != (0 as u8) { return 0 } 444 return 1 445} 446 447func px_kat() -> i64 { 448 let ctr: *i64 = gv_ctr() 449 gv_head("nx_pmx KAT -- PMX walk; the variable-stride vertex record is what makes this provable" as *u8) 450 let b: *u8 = sys_mmap(PX_KATBUF) 451 let G: *i64 = sys_mmap(64) as *i64 452 let H: *i64 = sys_mmap(PX_H_WORDS * 8) as *i64 453 let names: *u8 = sys_mmap((PX_MAXBONE + 2) * PX_NAMEW) 454 let bparent: *i64 = sys_mmap(PX_MAXBONE * 8) as *i64 455 let bpos: *i64 = sys_mmap(PX_MAXBONE * 24) as *i64 456 457 let n: i64 = px_fixture(b) 458 let r: i64 = px_walk(b, n, G, H, names, bparent, bpos) 459 460 var t1: i64 = 0 461 if r == 0 { 462 if H[PX_H_NVERT] == 1 { if H[PX_H_NSURF] == 3 { if H[PX_H_NTEX] == 0 { 463 if H[PX_H_NMAT] == 1 { if H[PX_H_NBONE] == 2 { t1 = 1 } } } } } 464 } 465 gv_check("T1 section counts 1/3/0/1/2" as *u8, t1, ctr) 466 467 var t2: i64 = 0 468 if px_streq(names, "KAT" as *u8) == 1 { t2 = 1 } 469 gv_check("T2 model name decodes" as *u8, t2, ctr) 470 471 // T3 the bone table is the payload: reaching it at all proves the variable-stride vertex walk, 472 // the material walk and the flag-conditional bone walk were all byte-exact. 473 var t3: i64 = 0 474 if px_streq(((names as i64) + PX_NAMEW) as *u8, "root" as *u8) == 1 { 475 if px_streq(((names as i64) + 2 * PX_NAMEW) as *u8, "spine" as *u8) == 1 { 476 if bparent[0] == 0 - 1 { if bparent[1] == 0 { t3 = 1 } } } 477 } 478 gv_check("T3 bone names + parents (-1 root, 0 spine)" as *u8, t3, ctr) 479 480 // T4 ANTI-VACUITY: truncation mid-walk must REFUSE. The header and every count stay perfectly 481 // readable; only a bounds proof catches it. 482 let G2: *i64 = sys_mmap(64) as *i64 483 let H2: *i64 = sys_mmap(PX_H_WORDS * 8) as *i64 484 var t4: i64 = 0 485 if px_walk(b, n - 20, G2, H2, names, bparent, bpos) < 0 { t4 = 1 } 486 gv_check("T4 ANTI-VACUITY truncated file refused" as *u8, t4, ctr) 487 488 // T5 ANTI-VACUITY: bad magic refused before any count is trusted 489 let sav: u8 = b[0] 490 b[0] = 88 as u8 491 var t5: i64 = 0 492 if px_walk(b, n, G2, H2, names, bparent, bpos) == PX_R_MAGIC { t5 = 1 } 493 b[0] = sav 494 gv_check("T5 ANTI-VACUITY bad magic refused" as *u8, t5, ctr) 495 496 // T6 ANTI-VACUITY: THE LYING COUNT. Bytes untouched, magic valid, only the vertex count 497 // overwritten with a number the body cannot hold. A variable-stride walk that trusts it 498 // runs off the buffer. 499 let voff: i64 = 8 + 1 + 8 + 4 + 3 + 4 + 3 + 4 + 4 500 let savc: i64 = px_u32(b, voff) 501 px_p32(b, voff, PX_MAGIC_1000000) 502 var t6: i64 = 0 503 if px_walk(b, n, G2, H2, names, bparent, bpos) < 0 { t6 = 1 } 504 px_p32(b, voff, savc) 505 gv_check("T6 ANTI-VACUITY lying vertex count refused" as *u8, t6, ctr) 506 507 return gv_verdict("PMX-GATE" as *u8, ctr, "variable-stride walk is bounds-proven" as *u8) 508} 509 510func main(argc: i64, argv: *i64) -> i64 { 511 if argc < 2 { gv_puts("usage: nx_pmx info|bones <file.pmx> | --kat\n" as *u8); return 3 } 512 let verb: *u8 = argv[1] as *u8 513 if px_slen(verb) == 5 { if verb[0] == (45 as u8) { let r: i64 = px_kat(); sys_exit(r); return r } } 514 if argc < 3 { gv_puts("usage: nx_pmx info|bones <file.pmx> | --kat\n" as *u8); return 3 } 515 let lenp: *i64 = sys_mmap(16) as *i64 516 let buf: *u8 = sys_read_file(argv[2] as *u8, lenp) 517 if (buf as i64) == 0 { gv_puts("nx_pmx unreadable\n" as *u8); return 2 } 518 let G: *i64 = sys_mmap(64) as *i64 519 let H: *i64 = sys_mmap(PX_H_WORDS * 8) as *i64 520 let names: *u8 = sys_mmap((PX_MAXBONE + 2) * PX_NAMEW) 521 let bparent: *i64 = sys_mmap(PX_MAXBONE * 8) as *i64 522 let bpos: *i64 = sys_mmap(PX_MAXBONE * 24) as *i64 523 let r: i64 = px_walk(buf, lenp[0], G, H, names, bparent, bpos) 524 if r < 0 { return px_refuse(r) } 525 526 if verb[0] == (105 as u8) { 527 gv_puts("PMX bytes=" as *u8); gv_num(lenp[0]) 528 gv_puts(" encoding=" as *u8) 529 if G[PX_G_ENC] == PX_ENC_U8 { gv_puts("UTF-8" as *u8) } else { gv_puts("UTF-16LE" as *u8) } 530 gv_puts("\nidx_widths vertex=" as *u8); gv_num(G[PX_G_VIDX]) 531 gv_puts(" texture=" as *u8); gv_num(G[PX_G_TIDX]) 532 gv_puts(" material=" as *u8); gv_num(G[PX_G_MIDX]) 533 gv_puts(" bone=" as *u8); gv_num(G[PX_G_BIDX]) 534 gv_puts("\nmodel=" as *u8); gv_puts(names) 535 gv_puts("\nvertices=" as *u8); gv_num(H[PX_H_NVERT]) 536 gv_puts(" surface_indices=" as *u8); gv_num(H[PX_H_NSURF]) 537 gv_puts(" triangles=" as *u8); gv_num(H[PX_H_NSURF] / 3) 538 gv_puts("\ntextures=" as *u8); gv_num(H[PX_H_NTEX]) 539 gv_puts(" materials=" as *u8); gv_num(H[PX_H_NMAT]) 540 gv_puts(" bones=" as *u8); gv_num(H[PX_H_NBONE]) 541 gv_puts("\n" as *u8) 542 return 0 543 } 544 if verb[0] == (98 as u8) { 545 gv_puts("idx\tparent\tname\n" as *u8) 546 var k: i64 = 0 547 while k < H[PX_H_NBONE] { 548 gv_num(k); gv_puts("\t" as *u8); gv_num(bparent[k]); gv_puts("\t" as *u8) 549 gv_puts(((names as i64) + (k + 1) * PX_NAMEW) as *u8) 550 gv_puts("\n" as *u8) 551 k = k + 1 552 } 553 return 0 554 } 555 gv_puts("nx_pmx unknown verb\n" as *u8) 556 return 3 557}