nx_nxa_bind_t29.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}