code wiki / (root) / nx_nxa_bind.nx

nx_nxa_bind.nx source

↩ module page · 389 lines · 18106 B

1// Rebind an existing NXA geometry stream from its matching binary FBX BindPose. 2// Other supported sections remain byte-identical. Original inputs are never opened for writing. 3// usage: nx_nxa_bind <source.fbx> <original.nxa> <candidate.nxa> 4// license_tier: ORIGINAL 5import "nx_fbx_import.nx" 6import "nx_fbx_bind.nx" 7import "nx_nxa_write_lib.nx" 8import "nx_nxa_posewrite_lib.nx" 9import "nx_readcap_lib.nx" 10 11const NB_MILLI: i64 = 1000 12const NB_BAD_SOURCE: i64 = 0-20 13const NB_UNSUPPORTED: i64 = 0-21 14const NB_OUTPUT_EXISTS: i64 = 0-22 15 16func nb_text(s: *u8) -> i64 { 17 var n: i64=0 18 while s[n] != (0 as u8) { n=n+1 } 19 return sys_write(1,s,n) 20} 21func nb_num(v: i64) -> i64 { 22 let buf: *u8=sys_mmap(32) 23 let rev: *u8=sys_mmap(32) 24 var value: i64=v 25 if value < 0 { nb_text("-" as *u8); value=0-value } 26 var n: i64=0 27 if value == 0 { rev[n]=48 as u8; n=n+1 } 28 while value > 0 { rev[n]=(48+value%10) as u8; value=value/10; n=n+1 } 29 var i: i64=0 30 while i < n { buf[i]=rev[n-1-i]; i=i+1 } 31 sys_write(1,buf,n) 32 sys_munmap(buf,32); sys_munmap(rev,32) 33 return 0 34} 35func nb_supported(tag: i64) -> i64 { 36 if tag == nxa_tag4("VERT" as *u8) || tag == nxa_tag4("TRIS" as *u8) { return 1 } 37 if tag == nxa_tag4("SKEL" as *u8) || tag == nxa_tag4("SKIN" as *u8) { return 1 } 38 if tag == nxa_tag4("TEXC" as *u8) || tag == nxa_tag4("TEXM" as *u8) { return 1 } 39 // Position-dependent or unknown payloads need their own transform before this operation is qualified. 40 return 0 41} 42func nb_scale(v: *i64,count: i64,milli: i64) -> i64 { 43 if milli <= 0 { return FBB_RANGE } 44 var i: i64=0 45 while i < count { 46 if v[i] < (0-FBB_I64_MAX) { return FBB_RANGE } 47 var a: i64=v[i] 48 if a < 0 { a=0-a } 49 if a > FBB_I64_MAX/milli { return FBB_RANGE } 50 i=i+1 51 } 52 i=0 53 while i < count { v[i]=(v[i]*milli)/FBB_VERTEX_Q; i=i+1 } 54 return 0 55} 56func nb_save_new(path: *u8,b: *u8,n: i64) -> i64 { 57 if n<=0{return NB_BAD_SOURCE} 58 let st:*i64=sys_mmap(24) as *i64 59 let check:*u8=sys_mmap(n) 60 let oldfd:i64=sys_openat_rd(path) 61 if oldfd>=0 { 62 let got:i64=rc_fill_file(oldfd,check,n,st);sys_close(oldfd) 63 if rc_complete(st)==0||got!=n{return NB_OUTPUT_EXISTS} 64 var i:i64=0;while i<n{if check[i]!=b[i]{return NB_OUTPUT_EXISTS};i=i+1} 65 sys_munmap(check,n);sys_munmap(st as *u8,24) 66 nb_text("NXA-BIND output=ALREADY-IDENTICAL\n" as *u8);return n 67 } 68 if oldfd!=(0-2){return NXW_ERR_OPEN} 69 let fd:i64=sys_openat_exclusive(path,MODE_0644) 70 if fd<0{return NXW_ERR_OPEN} 71 let written:i64=npw_put(fd,b,n) 72 let synced:i64=sys_fsync(fd);sys_close(fd) 73 if written!=n||synced!=0{return NXW_ERR_SHORT_WRITE} 74 let got:i64=rc_read_file_into(path,check,n,st) 75 if rc_complete(st)==0||got!=n{return NXW_ERR_SHORT_WRITE} 76 var i:i64=0;while i<n{if check[i]!=b[i]{return NXW_ERR_SHORT_WRITE};i=i+1} 77 sys_munmap(check,n);sys_munmap(st as *u8,24) 78 nb_text("NXA-BIND output=CREATED readback=BYTE-IDENTICAL\n" as *u8) 79 return n 80} 81// Request budgets are supplied by the caller; this scanner never inflates arrays. 82const NB_BUDGET: i64 = 0-23 83struct NxaBindReport { 84 vertices: i64, 85 triangles: i64, 86 changed_vertices: i64, 87 preserved_bytes: i64, 88 source_animation_stacks: i64, 89 source_animation_curves: i64, 90 decoded_array_bytes: i64, 91 sections: i64, 92} 93func nb_preflight(b: *u8,n: i64,array_budget: i64,depth_budget: i64,r: *NxaBindReport) -> i64 { 94 let version: i64=fbxc_version(b,n) 95 if version < 0 || array_budget <= 0 || depth_budget <= 0 || depth_budget > n/13 { return NB_BAD_SOURCE } 96 let big: i64=fbxc_big(version) 97 let h: *i64=sys_mmap(FBXC_OUTN*8) as *i64 98 let ends: *i64=sys_mmap(depth_budget*8) as *i64 99 var depth: i64=0 100 ends[0]=n 101 var p: i64=FBXC_TOPOFF 102 var rc: i64=0 103 var done: i64=0 104 while done == 0 && rc == 0 { 105 if p >= ends[depth] { 106 if depth == 0 { done=1 } else { p=ends[depth]; depth=depth-1 } 107 } else { 108 let hit: i64=fbb_node(b,n,p,ends[depth],big,h) 109 if hit < 0 { rc=NB_BAD_SOURCE } 110 else { 111 if hit == 0 { 112 if depth == 0 { done=1 } else { p=ends[depth]; depth=depth-1 } 113 } else { 114 if fbxc_name_is(b,h[FBXC_NAMEOFF],h[FBXC_NAMELEN],"AnimationStack" as *u8) == 1 { r.source_animation_stacks=r.source_animation_stacks+1 } 115 if fbxc_name_is(b,h[FBXC_NAMEOFF],h[FBXC_NAMELEN],"AnimationCurve" as *u8) == 1 { r.source_animation_curves=r.source_animation_curves+1 } 116 var q: i64=h[FBXC_PROPSOFF] 117 let pe: i64=q+h[FBXC_PLEN] 118 var properties: i64=0 119 while q < pe && rc == 0 { 120 let size: i64=fbb_property_size(b,q,pe) 121 if size < 0 { rc=NB_BAD_SOURCE } 122 else { 123 let tag: i64=b[q] as i64 124 var width: i64=0 125 if tag == 100 || tag == 108 { width=8 } 126 if tag == 102 || tag == 105 { width=4 } 127 if tag == 98 { width=1 } 128 if width > 0 { 129 let count: i64=fbxc_u32(b,q+1) 130 if count > (array_budget-r.decoded_array_bytes)/width { rc=NB_BUDGET } 131 else { r.decoded_array_bytes=r.decoded_array_bytes+count*width } 132 } 133 q=q+size; properties=properties+1 134 } 135 } 136 if rc == 0 && properties != h[FBXC_NPROPS] { rc=NB_BAD_SOURCE } 137 if rc == 0 { 138 if pe+h[FBXC_HDRSZ] <= h[FBXC_END] { 139 if depth+1 >= depth_budget { rc=NB_BUDGET } 140 else { depth=depth+1; ends[depth]=h[FBXC_END]; p=pe } 141 } else { p=h[FBXC_END] } 142 } 143 } 144 } 145 } 146 } 147 sys_munmap(h as *u8,FBXC_OUTN*8) 148 sys_munmap(ends as *u8,depth_budget*8) 149 return rc 150} 151// Qualifies the exact fan-triangulation contract before the legacy walker can clamp. 152const NB_POLYGON_OWNER_CAP: i64 = 512 153func nb_geometry_contract(b: *u8,n: i64,nv: i64,nt: i64) -> i64 { 154 let big: i64=fbxc_big(fbxc_version(b,n)) 155 let h: *i64=sys_mmap(FBXC_OUTN*8) as *i64 156 let c: *i64=sys_mmap(FBXC_OUTN*8) as *i64 157 let meta: *i64=sys_mmap(32) as *i64 158 if fbb_find_child(b,n,FBXC_TOPOFF,n,big,"Objects" as *u8,h) != 1 { return NB_BAD_SOURCE } 159 var p: i64=h[FBXC_PROPSOFF]+h[FBXC_PLEN] 160 let end: i64=h[FBXC_END] 161 var vertices: i64=0 162 var triangles: i64=0 163 while p < end { 164 let hit: i64=fbb_node(b,n,p,end,big,h) 165 if hit < 0 { return NB_BAD_SOURCE } 166 if hit == 0 { p=end } 167 else { 168 let next: i64=h[FBXC_END] 169 let child: i64=h[FBXC_PROPSOFF]+h[FBXC_PLEN] 170 if fbxc_name_is(b,h[FBXC_NAMEOFF],h[FBXC_NAMELEN],"Geometry" as *u8) == 1 { 171 if fbb_find_child(b,n,child,next,big,"Vertices" as *u8,c) != 1 { return NB_UNSUPPORTED } 172 let vp: i64=c[FBXC_PROPSOFF] 173 if b[vp] != (100 as u8) { return NB_BAD_SOURCE } 174 let scalars: i64=fbxc_u32(b,vp+1) 175 let count: i64=scalars/3 176 if scalars%3 != 0 || count <= 0 || count > nv-vertices { return NB_BAD_SOURCE } 177 vertices=vertices+count 178 if fbb_find_child(b,n,child,next,big,"PolygonVertexIndex" as *u8,c) != 1 { return NB_BAD_SOURCE } 179 let pp: i64=c[FBXC_PROPSOFF] 180 if b[pp] != (105 as u8) { return NB_BAD_SOURCE } 181 if fbxc_array(b,pp+1,4,meta) != 0 { return NB_BAD_SOURCE } 182 var i: i64=0 183 var polygon: i64=0 184 var rc: i64=0 185 while i < meta[2] && rc == 0 { 186 var index: i64=fbxc_i32(meta[0] as *u8,i*4) 187 var last: i64=0 188 if index < 0 { index=0-index-1; last=1 } 189 if index < 0 || index >= count { rc=NB_BAD_SOURCE } 190 polygon=polygon+1 191 if polygon > NB_POLYGON_OWNER_CAP { rc=NB_UNSUPPORTED } 192 if last == 1 { 193 if polygon < 3 || polygon-2 > nt-triangles { rc=NB_BAD_SOURCE } 194 else { triangles=triangles+polygon-2 } 195 polygon=0 196 } 197 i=i+1 198 } 199 if meta[3] == 1 { sys_munmap(meta[0] as *u8,meta[1]) } 200 if rc != 0 { return rc } 201 if polygon != 0 { return NB_BAD_SOURCE } 202 } 203 p=next 204 } 205 } 206 sys_munmap(h as *u8,FBXC_OUTN*8); sys_munmap(c as *u8,FBXC_OUTN*8); sys_munmap(meta as *u8,32) 207 if vertices != nv || triangles != nt { return NB_BAD_SOURCE } 208 return 0 209} 210// All ranges are admitted before report or output writes; subtraction avoids end-address overflow. 211func nb_range(p: i64,n: i64) -> i64 { 212 if p <= 0 || n <= 0 || n > FBB_I64_MAX-p { return 0 } 213 return 1 214} 215func nb_overlap(a: i64,an: i64,b: i64,bn: i64) -> i64 { 216 if a <= b { return (b-a < an) as i64 } 217 return (a-b < bn) as i64 218} 219func nb_convert_bytes(fbx: *u8,fn: i64,b: *u8,n: i64,candidate: *u8,cap: i64,array_budget: i64,depth_budget: i64,r: *NxaBindReport) -> i64 { 220 if cap < n { return NB_BAD_SOURCE } 221 let report_bytes: i64=__size_of(NxaBindReport) 222 if nb_range(b as i64,n)==0 || nb_range(fbx as i64,fn)==0 || nb_range(candidate as i64,cap)==0 || nb_range(r as i64,report_bytes)==0 { return NB_BAD_SOURCE } 223 if nb_overlap(candidate as i64,cap,b as i64,n)==1 || nb_overlap(candidate as i64,cap,fbx as i64,fn)==1 { return NB_BAD_SOURCE } 224 if nb_overlap(r as i64,report_bytes,b as i64,n)==1 || nb_overlap(r as i64,report_bytes,fbx as i64,fn)==1 || nb_overlap(r as i64,report_bytes,candidate as i64,cap)==1 { return NB_BAD_SOURCE } 225 r.vertices=0; r.triangles=0; r.changed_vertices=0; r.preserved_bytes=0 226 r.source_animation_stacks=0; r.source_animation_curves=0; r.decoded_array_bytes=0; r.sections=0 227 let preflight: i64=nb_preflight(fbx,fn,array_budget,depth_budget,r) 228 if preflight != 0 { return preflight } 229 let va: i64=nxa_counted_section(b,n,nxa_tag4("VERT" as *u8),3) 230 let ta: i64=nxa_counted_section(b,n,nxa_tag4("TRIS" as *u8),3) 231 if va < 0 || ta < 0 { return NB_BAD_SOURCE } 232 let h: *i64=b as *i64 233 let nv: i64=h[va] 234 let nt: i64=h[ta] 235 if nv <= 0 || nt <= 0 { return NB_BAD_SOURCE } 236 var i: i64=0 237 while i < h[2] { 238 let e: i64=4+i*4 239 if nxa_section_entry(b,n,h[e]) != e { return NB_BAD_SOURCE } 240 if nb_supported(h[e]) == 0 { nb_text("NXA-BIND unsupported_section_tag=" as *u8); nb_num(h[e]); nb_text("\n" as *u8); return NB_UNSUPPORTED } 241 i=i+1 242 } 243 let geometry: i64=nb_geometry_contract(fbx,fn,nv,nt) 244 if geometry != 0 { return geometry } 245 let usfm: i64=fbxc_unit_scale_micro(fbx,fn) 246 if usfm <= 0 || usfm%NB_MILLI != 0 { return NB_UNSUPPORTED } 247 let s: *FbxBindScene=sys_mmap(__size_of(FbxBindScene)) as *FbxBindScene 248 let loaded: i64=fbb_load(fbx,fn,s) 249 if loaded != 0 { return loaded } 250 let poses: i64=fbb_pose_models(fbx,fn,s) 251 if poses != 0 { return poses } 252 var expected: i64=0 253 var geoms: i64=0 254 i=0 255 while i < s.count { 256 let g: *FbxBindObject=fbb_object(s,i) 257 if g.kind == FBB_GEOMETRY { 258 if g.count < 0 || g.count > nv-expected { return NB_BAD_SOURCE } 259 expected=expected+g.count; geoms=geoms+1 260 } 261 i=i+1 262 } 263 if expected != nv { return NB_BAD_SOURCE } 264 let raw: *i64=sys_mmap(nv*3*8) as *i64 265 let tris: *i64=sys_mmap(nt*3*8) as *i64 266 let counts: *i64=sys_mmap(64) as *i64 267 let st: *i64=sys_mmap(128) as *i64 268 st[0]=raw as i64; st[1]=tris as i64; st[2]=nv; st[3]=nt; st[12]=fbxc_big(fbxc_version(fbx,fn)) 269 let read: i64=fbx_walk(fbx,fn,FBXC_TOPOFF,fn,st) 270 counts[0]=st[4]; counts[1]=st[5]; counts[3]=st[7]; counts[5]=st[9]; counts[7]=st[11] 271 sys_munmap(st as *u8,128) 272 if read != 0 || counts[0] != nv || counts[1] != nt || counts[3] != geoms || counts[5] != geoms || counts[7] != 0 { return NB_BAD_SOURCE } 273 i=0 274 while i < nt*3 { 275 if tris[i] != h[ta+1+i] || tris[i] < 0 || tris[i] >= nv { return NB_BAD_SOURCE } 276 i=i+1 277 } 278 let corrected: *i64=sys_mmap(nv*3*8) as *i64 279 let applied: i64=fbb_apply_pose(s,raw,nv,corrected) 280 if applied != 0 { return applied } 281 let oldscale: i64=nb_scale(raw,nv*3,usfm/NB_MILLI) 282 let newscale: i64=nb_scale(corrected,nv*3,usfm/NB_MILLI) 283 if oldscale != 0 || newscale != 0 { return FBB_RANGE } 284 i=0 285 while i < nv*3 { 286 if raw[i] != h[va+1+i] { 287 nb_text("NXA-BIND source_vertex_mismatch=" as *u8); nb_num(i); nb_text("\n" as *u8) 288 return NB_BAD_SOURCE 289 } 290 i=i+1 291 } 292 let payload: *i64=sys_mmap((1+nv*3)*8) as *i64 293 payload[0]=nv 294 var changed: i64=0 295 i=0 296 while i < nv { 297 if raw[i*3] != corrected[i*3] || raw[i*3+1] != corrected[i*3+1] || raw[i*3+2] != corrected[i*3+2] { changed=changed+1 } 298 var j: i64=0 299 while j < 3 { payload[1+i*3+j]=corrected[i*3+j]; j=j+1 } 300 i=i+1 301 } 302 let replaced: i64=nxw_replace_same_size(b,n,nxa_tag4("VERT" as *u8),payload,1+nv*3,candidate,n) 303 if replaced != n { return replaced } 304 let ch: *i64=candidate as *i64 305 let ve: i64=nxa_section_entry(candidate,n,nxa_tag4("VERT" as *u8)) 306 if ve < 0 { return NB_BAD_SOURCE } 307 // Verify every unaffected byte, including section contents and unused padding. 308 i=0 309 while i < n { 310 var allowed: i64=0 311 if i >= 24 && i < 32 { allowed=1 } 312 if i >= (ve+3)*8 && i < (ve+4)*8 { allowed=1 } 313 if i >= (va+1)*8 && i < (va+1+nv*3)*8 { allowed=1 } 314 if allowed == 0 { if candidate[i] != b[i] { return NB_BAD_SOURCE } } 315 i=i+1 316 } 317 i=0 318 while i < ch[2] { 319 let e: i64=4+i*4 320 if nxa_section_entry(candidate,n,ch[e]) != e { return NB_BAD_SOURCE } 321 i=i+1 322 } 323 nb_text("NXA-BIND source_vertices=MATCH source_triangles=MATCH unchanged_bytes=MATCH vertices=" as *u8); nb_num(nv) 324 nb_text(" triangles=" as *u8); nb_num(nt); nb_text(" changed_vertices=" as *u8); nb_num(changed) 325 nb_text(" sections=" as *u8); nb_num(ch[2]); nb_text(" bytes=" as *u8); nb_num(n); nb_text("\n" as *u8) 326 r.vertices=nv; r.triangles=nt; r.changed_vertices=changed; r.preserved_bytes=n-nv*3*8-16; r.sections=ch[2] 327 return n 328} 329// Legacy path contract retained; bounded estate jobs call nb_convert_bytes with admitted buffers. 330func nb_convert(fbxpath: *u8,nxapath: *u8,outpath: *u8) -> i64 { 331 let lp: *i64=sys_mmap(16) as *i64 332 let b: *u8=sys_read_file(nxapath,lp) 333 if (b as i64) == 0 { return NB_BAD_SOURCE } 334 let n: i64=lp[0] 335 let fbx: *u8=sys_read_file(fbxpath,lp) 336 if (fbx as i64) == 0 { return NB_BAD_SOURCE } 337 let fn: i64=lp[0] 338 let candidate: *u8=sys_mmap(n) 339 let r: *NxaBindReport=sys_mmap(__size_of(NxaBindReport)) as *NxaBindReport 340 let result: i64=nb_convert_bytes(fbx,fn,b,n,candidate,n,FBB_I64_MAX,fn/13,r) 341 if result != n { return result } 342 return nb_save_new(outpath,candidate,n) 343} 344func nb_budget_arg(s: *u8) -> i64 { 345 var i:i64=0;var n:i64=0 346 if s[0]==(0 as u8){return NB_BAD_SOURCE} 347 while s[i]!=(0 as u8){let c:i64=s[i] as i64;if c<48||c>57{return NB_BAD_SOURCE};if n>(FBB_I64_MAX-(c-48))/10{return NB_BAD_SOURCE};n=n*10+c-48;i=i+1} 348 if n<=0{return NB_BAD_SOURCE} 349 return n 350} 351func nb_report(result: i64,r: *NxaBindReport) -> i64 { 352 nb_text("{\"contract\":1,\"operation\":\"fbx-bind-geometry\",\"result\":" as *u8);nb_num(result) 353 nb_text(",\"vertices\":" as *u8);nb_num(r.vertices);nb_text(",\"triangles\":" as *u8);nb_num(r.triangles) 354 nb_text(",\"changed_vertices\":" as *u8);nb_num(r.changed_vertices);nb_text(",\"preserved_bytes\":" as *u8);nb_num(r.preserved_bytes) 355 nb_text(",\"source_animation_stacks\":" as *u8);nb_num(r.source_animation_stacks);nb_text(",\"source_animation_curves\":" as *u8);nb_num(r.source_animation_curves) 356 nb_text(",\"decoded_array_bytes\":" as *u8);nb_num(r.decoded_array_bytes) 357 nb_text(",\"animation_conversion\":\"not-performed\",\"skin_influence_recovery\":\"not-performed\",\"quantization_error\":\"unmeasured-for-this-input\",\"scene_playback\":\"unqualified\"}\n" as *u8) 358 return 0 359} 360func nb_bounded_cli(argc: i64,argv: *i64) -> i64 { 361 if argc!=9{nb_text("usage: nx_nxa_bind bounded <source.fbx> <original.nxa> <candidate.nxa> <source_bytes> <asset_bytes> <decoded_array_bytes> <depth>\n" as *u8);return 2} 362 let command:*u8=argv[1] as *u8 363 var cl:i64=0;while command[cl]!=(0 as u8){cl=cl+1;if cl>7{return 2}} 364 if cl!=7||fbxc_name_is(command,0,cl,"bounded" as *u8)!=1{return 2} 365 let fb:i64=nb_budget_arg(argv[5] as *u8);let ab:i64=nb_budget_arg(argv[6] as *u8);let db:i64=nb_budget_arg(argv[7] as *u8);let depth:i64=nb_budget_arg(argv[8] as *u8) 366 if fb<=0||ab<=0||db<=0||depth<=0{return 2} 367 let st:*i64=sys_mmap(24) as *i64 368 let r:*NxaBindReport=sys_mmap(__size_of(NxaBindReport)) as *NxaBindReport 369 let fbx:*u8=sys_mmap(fb) 370 let fn:i64=rc_read_file_into(argv[2] as *u8,fbx,fb,st) 371 if rc_complete(st)==0{nb_report(NB_BUDGET,r);return 3} 372 let asset:*u8=sys_mmap(ab) 373 let n:i64=rc_read_file_into(argv[3] as *u8,asset,ab,st) 374 if rc_complete(st)==0{nb_report(NB_BUDGET,r);return 3} 375 let out:*u8=sys_mmap(n) 376 var result:i64=nb_convert_bytes(fbx,fn,asset,n,out,n,db,depth,r) 377 if result>0{result=nb_save_new(argv[4] as *u8,out,result)} 378 nb_report(result,r) 379 if result<=0{return 3} 380 return 0 381} 382func main(argc: i64,argv: *i64) -> i64 { 383 if argc == 9 { return nb_bounded_cli(argc,argv) } 384 if argc != 4 { nb_text("usage: nx_nxa_bind <source.fbx> <original.nxa> <candidate.nxa>\n" as *u8); return 2 } 385 let result: i64=nb_convert(argv[1] as *u8,argv[2] as *u8,argv[3] as *u8) 386 nb_text("NXA-BIND result=" as *u8); nb_num(result); nb_text("\n" as *u8) 387 if result <= 0 { return 3 } 388 return 0 389}