code wiki / (root) / nx_nif_lib.nx

nx_nif_lib.nx source

↩ module page · 401 lines · 19011 B

1// nx_nif_lib.nx -- NETIMMERSE/GAMEBRYO NIF STRUCTURAL FACTS CORE (the Bethesda mesh container; 2// /compare/koikatsu KK12 contract, legs 1+2 2026-08-30). SCHEMA GROUND TRUTH is the mirrored 3// niftools nif.xml (GPL-3.0, read as an ORACLE) plus REAL fixtures from the nifly test corpus, 4// hex-measured the same day: magic line then u32 version 0x14020007, u8 endian, u32 user version, 5// u32 num blocks, BSStreamHeader, u16 num block types, SizedString type table, u16 type index per 6// block, u32 size per block, string table, groups. 7// BSStreamHeader export-string rule, VERCONDS FROM nif.xml AND CONFIRMED ON REAL BYTES (SE bsver 8// 100 and FO76 bsver 155): Author always; one u32 when BS > 130; Process Script when BS < 131; 9// Export Script always; Max Filepath when BS >= 103. 10// THREE-STATE HONESTY: is_nif = 0 not a NIF; 1 parsed (modern layout); 2 RECOGNIZED-UNSUPPORTED -- 11// the magic line is real but the version or a bound refuses, so every count ABSTAINS at -1 rather 12// than misparse into confident garbage. A bounds failure is a named abstention, never a guess. 13// MEASURED COVERAGE: Skyrim SE (100), Skyrim LE (83) and FO76 (155) headers all parse. 14// GEOMETRY LEG (nif_geom): per-TriShape vertex/triangle counts extracted at the SSE BSTriShape 15// tuple (desc u64, tri u16, vert u16, datasize u32 behind the 100+4*extra NiAVObject prefix) and 16// VERIFIED BY THE SCHEMA'S OWN ARITHMETIC -- nif.xml declares DataSize = (desc AND 0xF) * verts * 17// 4 + tris * 6, so a tuple that satisfies it is self-proven and one that does not is counted 18// UNVERIFIED by name (LE NiTriShape and FO76 layouts land there deliberately). Full vertex DATA 19// extraction to .nxmesh remains the contract's declared final half. 20// SCOPE DECLARED: the census counts blocks whose TYPE NAME contains TriShape; NiTriStrips is not 21// counted -- stated imprecision, widen deliberately. 22// Slots: [0] is_nif [1] version [2] bs_version [3] num_blocks [4] num_block_types 23// [5] trishape_blocks [6] total_block_bytes [7] num_strings [8] header_bytes [9] endian 24// license_tier: ORIGINAL No hw writes (Rule 26). 25import "nx_syscalls.nx" 26import "nx_kkfacts_lib.nx" 27 28const NF_N_SLOTS: i64 = 10 29const NF_MAGIC_SCAN: i64 = 64 // the magic line ends within the first 64 bytes on every known dialect 30const NF_VER_SSE: i64 = 335675399 // 0x14020007 = 20.2.0.7, the modern Bethesda layout this lib parses 31const NF_MAX_TYPES: i64 = 2048 // a type table beyond this refuses as UNSUPPORTED (bound, not a guess) 32const NF_MAX_TYPENAME: i64 = 64 33const NF_MAX_STRINGS: i64 = 65536 34const NF_MAX_STRLEN: i64 = 4096 35const NF_MAX_GROUPS: i64 = 65536 36const NF_BS_EXTRAINT_GT: i64 = 130 // BS Version above which the header carries one extra u32 (nif.xml) 37const NF_BS_PROCSCRIPT_LT: i64 = 131 // BS Version below which Process Script exists (nif.xml) 38const NF_BS_MAXPATH_GTE: i64 = 103 // BS Version at/above which Max Filepath exists (nif.xml) 39const NF_BS_SSE: i64 = 100 // the one BS version whose BSTriShape tuple the geometry leg verifies 40const NF_UV_BS: i64 = 12 // user version at/above which the BSStreamHeader exists 41const NF_CH_NL: i64 = 10 42const NF_GEOM_PREFIX_BASE: i64 = 100 // SSE NiAVObject fixed part 72 + NiBound 16 + skin/shader/alpha refs 12 43const NF_GEOM_TUPLE: i64 = 16 // desc u64 + tri u16 + vert u16 + datasize u32 44const NF_MAX_EXTRA: i64 = 64 // an extra-data list beyond this reads UNVERIFIED, never trusted 45const NF_DESC_LOW_MASK: i64 = 0xF // vertex-record size in dwords lives in the desc's low nibble 46 47func nf_u8(buf: *u8, o: i64) -> i64 { return buf[o] as i64 & 0xff } 48func nf_u16(buf: *u8, o: i64) -> i64 { return nf_u8(buf, o) | (nf_u8(buf, o + 1) << 8) } 49func nf_u32(buf: *u8, o: i64) -> i64 { return nf_u8(buf, o) | (nf_u8(buf, o + 1) << 8) | (nf_u8(buf, o + 2) << 16) | (nf_u8(buf, o + 3) << 24) } 50 51// does buf[at..] start with the nul-str prefix (bounded)? 52func nf_starts(buf: *u8, at: i64, end: i64, pre: *u8) -> i64 { 53 var i: i64 = 0 54 while pre[i] != (0 as u8) { 55 if at + i >= end { return 0 } 56 if buf[at + i] != pre[i] { return 0 } 57 i = i + 1 58 } 59 return 1 60} 61// does the span [off,off+len) contain the nul-str needle? 62func nf_span_has(buf: *u8, off: i64, len: i64, needle: *u8) -> i64 { 63 let hit: i64 = kk_find(buf, off, off + len, needle) 64 if hit < 0 { return 0 } 65 return 1 66} 67// one ExportString: length byte (counting the terminating nul) + bytes. Returns new offset or -1. 68func nf_export(buf: *u8, p: i64, n: i64) -> i64 { 69 if p >= n { return 0 - 1 } 70 let elen: i64 = nf_u8(buf, p) 71 let q: i64 = p + 1 + elen 72 if q > n { return 0 - 1 } 73 return q 74} 75 76// walk the modern header. Returns the offset PAST the parsed header, or -1 on any bounds refusal. 77// Writes the census into facts and the type table into toffs/tlens/tcounts (up to maxtypes). 78// bidx (when non-zero cap bcap) receives each block's type index; bsz its declared size. 79func nf_walk(buf: *u8, n: i64, facts: *i64, toffs: *i64, tlens: *i64, tcounts: *i64, maxtypes: i64, bidx: *i64, bsz: *i64, bcap: i64) -> i64 { 80 let nl: i64 = kk_find(buf, 0, NF_MAGIC_SCAN, "\n" as *u8) 81 if nl < 0 { return 0 - 1 } 82 var p: i64 = nl + 1 83 if p + 13 > n { return 0 - 1 } 84 let ver: i64 = nf_u32(buf, p) 85 facts[1] = ver 86 p = p + 4 87 let endian: i64 = nf_u8(buf, p) 88 facts[9] = endian 89 p = p + 1 90 if ver != NF_VER_SSE { return 0 - 1 } 91 if endian != 1 { return 0 - 1 } 92 let userver: i64 = nf_u32(buf, p) 93 p = p + 4 94 let nblocks: i64 = nf_u32(buf, p) 95 p = p + 4 96 facts[3] = nblocks 97 if userver >= NF_UV_BS { 98 if p + 4 > n { return 0 - 1 } 99 let bsver: i64 = nf_u32(buf, p) 100 facts[2] = bsver 101 p = p + 4 102 // BSStreamHeader per nif.xml verconds, confirmed on SE (100) and FO76 (155) real bytes 103 p = nf_export(buf, p, n) // Author, always 104 if p < 0 { return 0 - 1 } 105 if bsver > NF_BS_EXTRAINT_GT { if p + 4 > n { return 0 - 1 } p = p + 4 } 106 if bsver < NF_BS_PROCSCRIPT_LT { p = nf_export(buf, p, n); if p < 0 { return 0 - 1 } } 107 p = nf_export(buf, p, n) // Export Script, always 108 if p < 0 { return 0 - 1 } 109 if bsver >= NF_BS_MAXPATH_GTE { p = nf_export(buf, p, n); if p < 0 { return 0 - 1 } } 110 } 111 if p + 2 > n { return 0 - 1 } 112 let ntypes: i64 = nf_u16(buf, p) 113 p = p + 2 114 facts[4] = ntypes 115 if ntypes < 1 { return 0 - 1 } 116 if ntypes > NF_MAX_TYPES { return 0 - 1 } 117 var t: i64 = 0 118 while t < ntypes { 119 if p + 4 > n { return 0 - 1 } 120 let slen: i64 = nf_u32(buf, p) 121 if slen < 1 { return 0 - 1 } 122 if slen > NF_MAX_TYPENAME { return 0 - 1 } 123 p = p + 4 124 if p + slen > n { return 0 - 1 } 125 if t < maxtypes { toffs[t] = p; tlens[t] = slen; tcounts[t] = 0 } 126 p = p + slen 127 t = t + 1 128 } 129 // per-block type index; high bit masked (historic phantom flag) 130 var b: i64 = 0 131 var trishapes: i64 = 0 132 while b < nblocks { 133 if p + 2 > n { return 0 - 1 } 134 let raw: i64 = nf_u16(buf, p) 135 let ti: i64 = raw & 0x7fff 136 p = p + 2 137 if ti >= ntypes { return 0 - 1 } 138 if ti < maxtypes { 139 tcounts[ti] = tcounts[ti] + 1 140 if nf_span_has(buf, toffs[ti], tlens[ti], "TriShape" as *u8) == 1 { trishapes = trishapes + 1 } 141 } 142 if bcap > 0 { if b < bcap { bidx[b] = ti } } 143 b = b + 1 144 } 145 facts[5] = trishapes 146 // per-block sizes 147 var total: i64 = 0 148 b = 0 149 while b < nblocks { 150 if p + 4 > n { return 0 - 1 } 151 let sz: i64 = nf_u32(buf, p) 152 total = total + sz 153 if bcap > 0 { if b < bcap { bsz[b] = sz } } 154 p = p + 4 155 b = b + 1 156 } 157 facts[6] = total 158 // string table 159 if p + 8 > n { return 0 - 1 } 160 let nstr: i64 = nf_u32(buf, p) 161 p = p + 8 162 facts[7] = nstr 163 if nstr > NF_MAX_STRINGS { return 0 - 1 } 164 var s: i64 = 0 165 while s < nstr { 166 if p + 4 > n { return 0 - 1 } 167 let sl: i64 = nf_u32(buf, p) 168 if sl > NF_MAX_STRLEN { return 0 - 1 } 169 p = p + 4 + sl 170 if p > n { return 0 - 1 } 171 s = s + 1 172 } 173 // groups 174 if p + 4 > n { return 0 - 1 } 175 let ngroups: i64 = nf_u32(buf, p) 176 p = p + 4 177 if ngroups > NF_MAX_GROUPS { return 0 - 1 } 178 p = p + ngroups * 4 179 if p > n { return 0 - 1 } 180 facts[8] = p 181 return p 182} 183 184// probe core: buf holds the first n bytes; file_size = true size. A partial read of a recognized 185// NIF is state 2 with every count abstained -- a truncated header cannot be censused honestly. 186func nif_probe(buf: *u8, n: i64, file_size: i64, facts: *i64) -> i64 { 187 var i: i64 = 0 188 while i < NF_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 189 facts[0] = 0 190 if n < 40 { return 0 } 191 var magic: i64 = 0 192 if nf_starts(buf, 0, n, "Gamebryo File Format, Version " as *u8) == 1 { magic = 1 } 193 if nf_starts(buf, 0, n, "NetImmerse File Format, Version " as *u8) == 1 { magic = 1 } 194 if magic == 0 { return 0 } 195 facts[0] = 2 196 if file_size > n { return 0 } 197 let toffs: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 198 let tlens: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 199 let tcounts: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 200 let none: *i64 = sys_mmap(8) as *i64 201 let endp: i64 = nf_walk(buf, n, facts, toffs, tlens, tcounts, NF_MAX_TYPES, none, none, 0) 202 if endp < 0 { 203 // recognized but refused: keep version/endian if read, abstain the rest 204 facts[3] = 0 - 1 205 facts[4] = 0 - 1 206 facts[5] = 0 - 1 207 facts[6] = 0 - 1 208 facts[7] = 0 - 1 209 facts[8] = 0 - 1 210 return 0 211 } 212 facts[0] = 1 213 return 0 214} 215 216// type-table census for the blocks verb: fills name spans + per-type block counts. 217// meta[0] = types written (bounded by maxtypes); returns is_nif state as nif_probe would. 218func nif_types(buf: *u8, n: i64, file_size: i64, toffs: *i64, tlens: *i64, tcounts: *i64, maxtypes: i64, meta: *i64) -> i64 { 219 meta[0] = 0 220 let facts: *i64 = sys_mmap(NF_N_SLOTS * 8) as *i64 221 var i: i64 = 0 222 while i < NF_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 223 facts[0] = 0 224 if n < 40 { return 0 } 225 var magic: i64 = 0 226 if nf_starts(buf, 0, n, "Gamebryo File Format, Version " as *u8) == 1 { magic = 1 } 227 if nf_starts(buf, 0, n, "NetImmerse File Format, Version " as *u8) == 1 { magic = 1 } 228 if magic == 0 { return 0 } 229 if file_size > n { return 2 } 230 let none: *i64 = sys_mmap(8) as *i64 231 let endp: i64 = nf_walk(buf, n, facts, toffs, tlens, tcounts, maxtypes, none, none, 0) 232 if endp < 0 { return 2 } 233 var written: i64 = facts[4] 234 if written > maxtypes { written = maxtypes } 235 meta[0] = written 236 return 1 237} 238 239const NF_GEOM_MAX_BLOCKS: i64 = 65536 240 241// GEOMETRY FACTS LEG: per-TriShape vertex/triangle counts, each tuple VERIFIED by the schema's own 242// arithmetic (DataSize = (desc AND 0xF) * verts * 4 + tris * 6, from nif.xml). vouts/touts/vflags 243// receive counts + a per-shape verified flag; a tuple that fails the formula (LE NiTriShape, FO76 244// layouts, or a lying file) is UNVERIFIED with zeros -- counted, never trusted. 245// meta[0] = verified shapes; meta[1] = total verts (verified only); meta[2] = total tris (verified). 246// Returns TriShape blocks examined, or -1 when the header itself does not parse (state != 1). 247func nif_geom(buf: *u8, n: i64, file_size: i64, vouts: *i64, touts: *i64, vflags: *i64, cap: i64, meta: *i64) -> i64 { 248 meta[0] = 0 249 meta[1] = 0 250 meta[2] = 0 251 let facts: *i64 = sys_mmap(NF_N_SLOTS * 8) as *i64 252 var i: i64 = 0 253 while i < NF_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 254 if n < 40 { return 0 - 1 } 255 var magic: i64 = 0 256 if nf_starts(buf, 0, n, "Gamebryo File Format, Version " as *u8) == 1 { magic = 1 } 257 if nf_starts(buf, 0, n, "NetImmerse File Format, Version " as *u8) == 1 { magic = 1 } 258 if magic == 0 { return 0 - 1 } 259 if file_size > n { return 0 - 1 } 260 let toffs: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 261 let tlens: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 262 let tcounts: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 263 let bidx: *i64 = sys_mmap(NF_GEOM_MAX_BLOCKS * 8) as *i64 264 let bsz: *i64 = sys_mmap(NF_GEOM_MAX_BLOCKS * 8) as *i64 265 let endp: i64 = nf_walk(buf, n, facts, toffs, tlens, tcounts, NF_MAX_TYPES, bidx, bsz, NF_GEOM_MAX_BLOCKS) 266 if endp < 0 { return 0 - 1 } 267 let nblocks: i64 = facts[3] 268 if nblocks > NF_GEOM_MAX_BLOCKS { return 0 - 1 } 269 var off: i64 = facts[8] 270 var shapes: i64 = 0 271 var b: i64 = 0 272 while b < nblocks { 273 let ti: i64 = bidx[b] 274 let sz: i64 = bsz[b] 275 if nf_span_has(buf, toffs[ti], tlens[ti], "TriShape" as *u8) == 1 { 276 var vok: i64 = 0 277 var nverts: i64 = 0 278 var ntris: i64 = 0 279 let nextra: i64 = nf_u32(buf, off + 4) 280 if nextra >= 0 { if nextra <= NF_MAX_EXTRA { 281 let doff: i64 = off + NF_GEOM_PREFIX_BASE + nextra * 4 282 if doff + NF_GEOM_TUPLE <= off + sz { if doff + NF_GEOM_TUPLE <= n { 283 let desc_lo: i64 = nf_u32(buf, doff) & NF_DESC_LOW_MASK 284 let tri: i64 = nf_u16(buf, doff + 8) 285 let vrt: i64 = nf_u16(buf, doff + 10) 286 let dsz: i64 = nf_u32(buf, doff + 12) 287 let calc: i64 = desc_lo * vrt * 4 + tri * 6 288 if dsz > 0 { if calc == dsz { 289 vok = 1 290 nverts = vrt 291 ntris = tri 292 meta[0] = meta[0] + 1 293 meta[1] = meta[1] + vrt 294 meta[2] = meta[2] + tri 295 } } 296 } } 297 } } 298 if shapes < cap { 299 vouts[shapes] = nverts 300 touts[shapes] = ntris 301 vflags[shapes] = vok 302 } 303 shapes = shapes + 1 304 } 305 off = off + sz 306 b = b + 1 307 } 308 return shapes 309} 310 311const NF_BOUND_OFF: i64 = 72 // NiBound (center xyz f32 + radius f32) after the 72-byte NiAVObject fixed part 312const NF_INSIDE_SLACK_NUM: i64 = 1051 // 5.1 percent radius slack for f32-to-permil rounding -- named imprecision 313const NF_INSIDE_SLACK_DEN: i64 = 1000 314 315// f32 at a byte offset to PERMIL, composing the sibling lib's mantissa decoder -- one float ruler 316func nf_f32p(buf: *u8, o: i64) -> i64 { return kk_f32_permil(nf_u32(buf, o)) } 317 318// VERTEX POSITIONS LEG: decode the shape_sel-th VERIFIED TriShape's vertex positions to PERMIL and 319// test every one against the block's OWN declared NiBound sphere -- the container carries its own 320// oracle, so the decode is self-proving: a wrong stride or offset scatters points outside the 321// bound. SSE stores positions as full f32 x,y,z at each record start (nif.xml BSVertexDataSSE). 322// meta[0] = verts decoded; meta[1] = verts inside the declared bound (with the named slack); 323// meta[2] = bound radius in permil. Returns verts, or 0 when the selected verified shape is absent. 324func nif_verts(buf: *u8, n: i64, file_size: i64, shape_sel: i64, pxs: *i64, pys: *i64, pzs: *i64, cap: i64, meta: *i64) -> i64 { 325 meta[0] = 0 326 meta[1] = 0 327 meta[2] = 0 328 let facts: *i64 = sys_mmap(NF_N_SLOTS * 8) as *i64 329 var i: i64 = 0 330 while i < NF_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 331 if n < 40 { return 0 } 332 var magic: i64 = 0 333 if nf_starts(buf, 0, n, "Gamebryo File Format, Version " as *u8) == 1 { magic = 1 } 334 if nf_starts(buf, 0, n, "NetImmerse File Format, Version " as *u8) == 1 { magic = 1 } 335 if magic == 0 { return 0 } 336 if file_size > n { return 0 } 337 let toffs: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 338 let tlens: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 339 let tcounts: *i64 = sys_mmap(NF_MAX_TYPES * 8) as *i64 340 let bidx: *i64 = sys_mmap(NF_GEOM_MAX_BLOCKS * 8) as *i64 341 let bsz: *i64 = sys_mmap(NF_GEOM_MAX_BLOCKS * 8) as *i64 342 let endp: i64 = nf_walk(buf, n, facts, toffs, tlens, tcounts, NF_MAX_TYPES, bidx, bsz, NF_GEOM_MAX_BLOCKS) 343 if endp < 0 { return 0 } 344 let nblocks: i64 = facts[3] 345 if nblocks > NF_GEOM_MAX_BLOCKS { return 0 } 346 var off: i64 = facts[8] 347 var seen: i64 = 0 348 var b: i64 = 0 349 while b < nblocks { 350 let ti: i64 = bidx[b] 351 let sz: i64 = bsz[b] 352 if nf_span_has(buf, toffs[ti], tlens[ti], "TriShape" as *u8) == 1 { 353 let nextra: i64 = nf_u32(buf, off + 4) 354 if nextra >= 0 { if nextra <= NF_MAX_EXTRA { 355 let doff: i64 = off + NF_GEOM_PREFIX_BASE + nextra * 4 356 if doff + NF_GEOM_TUPLE <= off + sz { if doff + NF_GEOM_TUPLE <= n { 357 let desc_lo: i64 = nf_u32(buf, doff) & NF_DESC_LOW_MASK 358 let tri: i64 = nf_u16(buf, doff + 8) 359 let vrt: i64 = nf_u16(buf, doff + 10) 360 let dsz: i64 = nf_u32(buf, doff + 12) 361 let calc: i64 = desc_lo * vrt * 4 + tri * 6 362 if dsz > 0 { if calc == dsz { 363 if seen == shape_sel { 364 // the block's own oracle: NiBound at the fixed NiAVObject offset 365 let boff: i64 = off + nextra * 4 + NF_BOUND_OFF 366 let cx: i64 = nf_f32p(buf, boff) 367 let cy: i64 = nf_f32p(buf, boff + 4) 368 let cz: i64 = nf_f32p(buf, boff + 8) 369 let rp: i64 = nf_f32p(buf, boff + 12) 370 meta[2] = rp 371 let rr: i64 = (rp * NF_INSIDE_SLACK_NUM) / NF_INSIDE_SLACK_DEN 372 let stride: i64 = desc_lo * 4 373 let vdata: i64 = doff + NF_GEOM_TUPLE 374 var v: i64 = 0 375 while v < vrt { 376 let ro: i64 = vdata + v * stride 377 if ro + 12 > n { v = vrt } else { 378 let px: i64 = nf_f32p(buf, ro) 379 let py: i64 = nf_f32p(buf, ro + 4) 380 let pz: i64 = nf_f32p(buf, ro + 8) 381 if v < cap { pxs[v] = px; pys[v] = py; pzs[v] = pz } 382 meta[0] = meta[0] + 1 383 let dx: i64 = px - cx 384 let dy: i64 = py - cy 385 let dz: i64 = pz - cz 386 if dx * dx + dy * dy + dz * dz <= rr * rr { meta[1] = meta[1] + 1 } 387 v = v + 1 388 } 389 } 390 return meta[0] 391 } 392 seen = seen + 1 393 } } 394 } } 395 } } 396 } 397 off = off + sz 398 b = b + 1 399 } 400 return 0 401}