nx_xps_lib.nx source
↩ module page · 510 lines · 22607 B
1// nx_xps_lib.nx -- XPS / XNALara MODEL READER, both dialects (/compare/modding MD2, 2026-09-06). The reference readers were
2// mirrored and READ before a line of this was written (knowledge/fetched/cmp_modding_xnalara_read_ascii_xps.py pin ac92fd7f,
3// read_bin_xps.py pin 0e1dca05, bin_ops.py, xps_const.py). What they say, and this lib follows:
4// ascii (.mesh.ascii): bone count; per bone a name line, a parent line, an xyz line; mesh count; per mesh a name line, a
5// uv-layer count, a texture count with a name line and a uv-layer line per texture, a vertex count with position, normal,
6// RGBA colour (four integers), one uv line per layer and -- when the model has bones -- a four-index line and a
7// four-weight line (short lines are zero-filled), then a triangle count and three indices per face. Numeric lines may
8// carry a trailing comment after a hash.
9// binary (.xps): u32 magic 323232, u16 major, u16 minor, a length-prefixed string (one byte; a second byte when the first
10// is at least 128, length = b1 % 128 + b2 * 128), u32 settings length in 32-bit words, three more strings (machine,
11// user, files), then the settings block of exactly that many words (old format raw, new format typed items -- skipped
12// whole, it carries render flags, never geometry); bones: u32 count, string, i16 parent, three float32; meshes: u32 count,
13// string, u32 uv layers, u32 textures (string + u32 layer each), u32 vertices (three float32 position, three float32
14// normal, four colour bytes, per layer two float32 uv plus four float32 tangent when TANGENT versions apply), bone
15// weights as i16 count on VARIABLE-WEIGHT versions else four, i16 indices then float32 weights; u32 triangles of three u32.
16// TANGENT versions: minor <= 12 and major <= 2. VARIABLE weights: major >= 3. (bin_ops.py, read verbatim.)
17// OUTPUT is the estate's own arrays for nx_nxa_write_lib: VERT in 0.01 mm model units at a DECLARED scale (units_per_xps,
18// data never a guess), every mesh merged into one vertex table with per-mesh index offsets, SKEL from the bone table with the
19// identity bind rotation the estate's rig convention declares, SKIN as four q12 weights per vertex summing to exactly 4096
20// (a float sum that rounds away from 4096 has the residual moved to its largest lane and the vertex COUNTED as fixed).
21// Normals, colours, uvs and tangents are READ and COUNTED but not carried (TEXC is the texture rung, nx_nxa_texc).
22// Every refusal is named; a truncated binary is refused at the byte it ran out on, never read as a smaller model.
23// license_tier: ORIGINAL No hw writes (Rule 26).
24import "nx_syscalls.nx"
25
26const XP_MAGIC: i64 = 323232
27const XP_STR_LIMIT: i64 = 128
28const XP_TANGENT_MAJOR_MAX: i64 = 2
29const XP_TANGENT_MINOR_MAX: i64 = 12
30const XP_VARW_MAJOR_MIN: i64 = 3
31const XP_INF: i64 = 4
32const XP_Q12: i64 = 4096
33const XP_UNITS_PER_XPS_DEFAULT: i64 = 100000 // 1 XPS unit read as 1 metre = 100000 units of 0.01 mm; DECLARED, overridable
34const XP_F32_SIGN: i64 = 2147483648
35const XP_F32_EXP_DIV: i64 = 8388608
36const XP_F32_MAN_MASK: i64 = 8388607
37const XP_F32_HIDDEN: i64 = 8388608
38const XP_F32_BIAS_MAN: i64 = 150
39const XP_F32_SHIFT_CAP: i64 = 20
40const XP_FRAC_DIGITS_MAX: i64 = 9
41const XP_VERT_MIN_BYTES: i64 = 16 // the smallest vertex either dialect can spell; caps are DERIVED from the input size
42const XP_TRI_MIN_BYTES: i64 = 6
43const XP_DIALECT_ASCII: i64 = 1
44const XP_DIALECT_BIN: i64 = 2
45// model slots (plain i64 slots, never struct fields)
46const XP_M_NV: i64 = 0
47const XP_M_NT: i64 = 1
48const XP_M_NJ: i64 = 2
49const XP_M_NMESH: i64 = 3
50const XP_M_VX: i64 = 4
51const XP_M_TR: i64 = 5
52const XP_M_JPAR: i64 = 6
53const XP_M_JPOS: i64 = 7
54const XP_M_SJ: i64 = 8
55const XP_M_SW: i64 = 9
56const XP_M_HASBONES: i64 = 10
57const XP_M_DIALECT: i64 = 11
58const XP_M_VMAJ: i64 = 12
59const XP_M_VMIN: i64 = 13
60const XP_M_TANGENT: i64 = 14
61const XP_M_VARW: i64 = 15
62const XP_M_CAPV: i64 = 16
63const XP_M_CAPT: i64 = 17
64const XP_M_NTEX: i64 = 18
65const XP_M_WFIX: i64 = 19
66const XP_M_UVMAX: i64 = 20
67const XP_M_MUL: i64 = 21
68const XP_M_ERRPOS: i64 = 22
69const XP_M_BADJOINT: i64 = 23
70const XP_M_WTRUNC: i64 = 24
71const XP_M_N: i64 = 32
72// errors
73const XP_OK: i64 = 0
74const XP_ERR_TRUNCATED: i64 = 0 - 1
75const XP_ERR_CAP_VERTS: i64 = 0 - 2
76const XP_ERR_CAP_TRIS: i64 = 0 - 3
77const XP_ERR_MAGIC: i64 = 0 - 4
78const XP_ERR_BAD_JOINT: i64 = 0 - 5
79const XP_ERR_BAD_TRI: i64 = 0 - 6
80const XP_ERR_EMPTY: i64 = 0 - 7
81const XP_ERR_BAD_NUMBER: i64 = 0 - 8
82
83func xp_err_name(e: i64) -> *u8 {
84 if e == XP_ERR_TRUNCATED { return "truncated-input" as *u8 }
85 if e == XP_ERR_CAP_VERTS { return "vertex-count-exceeds-what-the-input-size-can-hold" as *u8 }
86 if e == XP_ERR_CAP_TRIS { return "triangle-count-exceeds-what-the-input-size-can-hold" as *u8 }
87 if e == XP_ERR_MAGIC { return "not-xps-magic" as *u8 }
88 if e == XP_ERR_BAD_JOINT { return "skin-joint-outside-bone-table" as *u8 }
89 if e == XP_ERR_BAD_TRI { return "triangle-index-outside-vertex-table" as *u8 }
90 if e == XP_ERR_EMPTY { return "no-geometry" as *u8 }
91 if e == XP_ERR_BAD_NUMBER { return "unparseable-number" as *u8 }
92 return "unnamed" as *u8
93}
94func xp_pow2(k: i64) -> i64 { var v: i64 = 1; var i: i64 = 0; while i < k { v = v * 2; i = i + 1 } return v }
95// IEEE-754 single from its 32 bits, scaled by mul and rounded to the nearest integer (zero, denormal, inf and nan read as 0)
96func xp_f32fx(bits: i64, mul: i64) -> i64 {
97 let sign: i64 = (bits / XP_F32_SIGN) & 1
98 let exp: i64 = (bits / XP_F32_EXP_DIV) & 255
99 let man: i64 = bits & XP_F32_MAN_MASK
100 if exp == 0 { return 0 }
101 if exp == 255 { return 0 }
102 var v: i64 = (man | XP_F32_HIDDEN) * mul
103 var sh: i64 = exp - XP_F32_BIAS_MAN
104 if sh > 0 {
105 if sh > XP_F32_SHIFT_CAP { sh = XP_F32_SHIFT_CAP }
106 v = v * xp_pow2(sh)
107 } else {
108 if sh < 0 {
109 let k: i64 = 0 - sh
110 if k >= 62 { v = 0 } else { let d: i64 = xp_pow2(k); v = (v + d / 2) / d }
111 }
112 }
113 if sign == 1 { return 0 - v }
114 return v
115}
116// ---- binary cursor reads: every read is bounds-checked and a short read sets XP_M_ERRPOS and returns 0 ----
117func xp_bu8(b: *u8, n: i64, p: *i64, m: *i64) -> i64 {
118 if p[0] + 1 > n { m[XP_M_ERRPOS] = p[0]; return 0 }
119 let v: i64 = (b[p[0]] & 0xff) as i64
120 p[0] = p[0] + 1
121 return v
122}
123func xp_bu16(b: *u8, n: i64, p: *i64, m: *i64) -> i64 {
124 if p[0] + 2 > n { m[XP_M_ERRPOS] = p[0]; return 0 }
125 let v: i64 = ((b[p[0]] & 0xff) as i64) | (((b[p[0] + 1] & 0xff) as i64) * 256)
126 p[0] = p[0] + 2
127 return v
128}
129func xp_bi16(b: *u8, n: i64, p: *i64, m: *i64) -> i64 { let v: i64 = xp_bu16(b, n, p, m); if v >= 32768 { return v - 65536 } return v }
130func xp_bu32(b: *u8, n: i64, p: *i64, m: *i64) -> i64 {
131 if p[0] + 4 > n { m[XP_M_ERRPOS] = p[0]; return 0 }
132 let q: i64 = p[0]
133 let v: i64 = ((b[q] & 0xff) as i64) | (((b[q + 1] & 0xff) as i64) * 256) | (((b[q + 2] & 0xff) as i64) * 65536) | (((b[q + 3] & 0xff) as i64) * 16777216)
134 p[0] = q + 4
135 return v
136}
137func xp_bf32(b: *u8, n: i64, p: *i64, m: *i64, mul: i64) -> i64 { let bits: i64 = xp_bu32(b, n, p, m); return xp_f32fx(bits, mul) }
138// a length-prefixed string: one byte, a second when the first reaches the limit; the bytes are skipped (names are not carried)
139func xp_bstr_skip(b: *u8, n: i64, p: *i64, m: *i64) -> i64 {
140 let b1: i64 = xp_bu8(b, n, p, m)
141 var b2: i64 = 0
142 if b1 >= XP_STR_LIMIT { b2 = xp_bu8(b, n, p, m) }
143 let len: i64 = (b1 % XP_STR_LIMIT) + b2 * XP_STR_LIMIT
144 if p[0] + len > n { m[XP_M_ERRPOS] = p[0]; return 0 }
145 p[0] = p[0] + len
146 return len
147}
148// ---- ascii cursor reads: a line at a time; numeric lines stop at a hash comment ----
149func xp_is_ws(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } if c == 13 { return 1 } return 0 }
150func xp_is_digit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 }
151// advance to the start of the next line
152func xp_next_line(b: *u8, n: i64, p: *i64) -> i64 {
153 var i: i64 = p[0]
154 while i < n { if (b[i] as i64) == 10 { i = i + 1; break } i = i + 1 }
155 p[0] = i
156 return i
157}
158// skip whitespace inside the current line
159func xp_skip_ws(b: *u8, n: i64, p: *i64) -> i64 {
160 var i: i64 = p[0]
161 while i < n { if xp_is_ws(b[i] as i64) == 1 { i = i + 1 } else { break } }
162 p[0] = i
163 return i
164}
165// one decimal number from the current line (optionally signed, fractional, exponent), scaled by mul and rounded; the cursor
166// stays on the line so a second number can follow. ok[0]=1 when digits were consumed.
167func xp_afx(b: *u8, n: i64, p: *i64, mul: i64, ok: *i64) -> i64 {
168 ok[0] = 0
169 xp_skip_ws(b, n, p)
170 var i: i64 = p[0]
171 var neg: i64 = 0
172 if i < n { if (b[i] as i64) == 45 { neg = 1; i = i + 1 } else { if (b[i] as i64) == 43 { i = i + 1 } } }
173 var ip: i64 = 0
174 var got: i64 = 0
175 while i < n { let c: i64 = b[i] as i64; if xp_is_digit(c) == 1 { ip = ip * 10 + (c - 48); got = 1; i = i + 1 } else { break } }
176 var frac: i64 = 0
177 var fd: i64 = 0
178 if i < n { if (b[i] as i64) == 46 {
179 i = i + 1
180 while i < n { let c2: i64 = b[i] as i64; if xp_is_digit(c2) == 1 { if fd < XP_FRAC_DIGITS_MAX { frac = frac * 10 + (c2 - 48); fd = fd + 1 } got = 1; i = i + 1 } else { break } }
181 } }
182 var e: i64 = 0
183 if i < n { if got == 1 { var c3: i64 = b[i] as i64; if c3 == 101 { i = i + 1 } else { if c3 == 69 { i = i + 1 } else { c3 = 0 } }
184 if c3 != 0 {
185 var eneg: i64 = 0
186 if i < n { if (b[i] as i64) == 45 { eneg = 1; i = i + 1 } else { if (b[i] as i64) == 43 { i = i + 1 } } }
187 while i < n { let c4: i64 = b[i] as i64; if xp_is_digit(c4) == 1 { e = e * 10 + (c4 - 48); i = i + 1 } else { break } }
188 if eneg == 1 { e = 0 - e }
189 }
190 } }
191 p[0] = i
192 if got == 0 { return 0 }
193 ok[0] = 1
194 var v: i64 = ip * mul
195 if fd > 0 { var den: i64 = 1; var k: i64 = 0; while k < fd { den = den * 10; k = k + 1 } v = v + (frac * mul + den / 2) / den }
196 while e > 0 { v = v * 10; e = e - 1 }
197 while e < 0 { v = (v + 5) / 10; e = e + 1 }
198 if neg == 1 { return 0 - v }
199 return v
200}
201// an integer line: the first number on the line, then the cursor moves to the next line
202func xp_aint(b: *u8, n: i64, p: *i64, m: *i64) -> i64 {
203 if p[0] >= n { m[XP_M_ERRPOS] = p[0]; return 0 }
204 let ok: *i64 = sys_mmap(16) as *i64
205 let v: i64 = xp_afx(b, n, p, 1, ok)
206 if ok[0] == 0 { m[XP_M_ERRPOS] = p[0]; xp_next_line(b, n, p); return 0 }
207 xp_next_line(b, n, p)
208 return v
209}
210// k numbers from one line scaled by mul into out[]; missing trailing values read as 0 (the reference zero-fills); then next line
211func xp_aline(b: *u8, n: i64, p: *i64, m: *i64, k: i64, mul: i64, out: *i64) -> i64 {
212 if p[0] >= n { m[XP_M_ERRPOS] = p[0]; return 0 }
213 let ok: *i64 = sys_mmap(16) as *i64
214 var got: i64 = 0
215 var i: i64 = 0
216 while i < k {
217 out[i] = xp_afx(b, n, p, mul, ok)
218 if ok[0] == 1 { got = got + 1 } else { out[i] = 0 }
219 i = i + 1
220 }
221 if got == 0 { m[XP_M_ERRPOS] = p[0] }
222 xp_next_line(b, n, p)
223 return got
224}
225// a string line is skipped whole (names are not carried into SKEL)
226func xp_aline_skip(b: *u8, n: i64, p: *i64, m: *i64) -> i64 { if p[0] >= n { m[XP_M_ERRPOS] = p[0]; return 0 } xp_next_line(b, n, p); return 1 }
227
228// ---- shared: weights to q12 summing to exactly 4096 ----
229func xp_fix_weights(sw: *i64, base: i64, m: *i64) -> i64 {
230 var sum: i64 = 0
231 var big: i64 = 0
232 var k: i64 = 0
233 while k < XP_INF {
234 if sw[base + k] < 0 { sw[base + k] = 0 }
235 sum = sum + sw[base + k]
236 if sw[base + k] > sw[base + big] { big = k }
237 k = k + 1
238 }
239 if sum == XP_Q12 { return 0 }
240 if sum == 0 { sw[base] = XP_Q12; m[XP_M_WFIX] = m[XP_M_WFIX] + 1; return 1 }
241 sw[base + big] = sw[base + big] + (XP_Q12 - sum)
242 m[XP_M_WFIX] = m[XP_M_WFIX] + 1
243 return 1
244}
245func xp_alloc(m: *i64, n: i64) -> i64 {
246 let capv: i64 = n / XP_VERT_MIN_BYTES + 16
247 let capt: i64 = n / XP_TRI_MIN_BYTES + 16
248 m[XP_M_CAPV] = capv
249 m[XP_M_CAPT] = capt
250 m[XP_M_VX] = sys_mmap(8 * 3 * capv) as i64
251 m[XP_M_TR] = sys_mmap(8 * 3 * capt) as i64
252 m[XP_M_SJ] = sys_mmap(8 * XP_INF * capv) as i64
253 m[XP_M_SW] = sys_mmap(8 * XP_INF * capv) as i64
254 return capv
255}
256// dialect sniff: the binary magic is an exact u32 at offset 0
257func xp_dialect(b: *u8, n: i64) -> i64 {
258 if n >= 4 {
259 let v: i64 = ((b[0] & 0xff) as i64) | (((b[1] & 0xff) as i64) * 256) | (((b[2] & 0xff) as i64) * 65536) | (((b[3] & 0xff) as i64) * 16777216)
260 if v == XP_MAGIC { return XP_DIALECT_BIN }
261 }
262 return XP_DIALECT_ASCII
263}
264
265// ---- the ascii dialect ----
266func xp_read_ascii(b: *u8, n: i64, m: *i64) -> i64 {
267 let p: *i64 = sys_mmap(16) as *i64
268 p[0] = 0
269 if n >= 3 { if (b[0] & 0xff) as i64 == 0xef { if (b[1] & 0xff) as i64 == 0xbb { if (b[2] & 0xff) as i64 == 0xbf { p[0] = 3 } } } }
270 let mul: i64 = m[XP_M_MUL]
271 let nj: i64 = xp_aint(b, n, p, m)
272 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
273 m[XP_M_NJ] = nj
274 let jpar: *i64 = sys_mmap(8 * (nj + 1)) as *i64
275 let jpos: *i64 = sys_mmap(8 * 3 * (nj + 1)) as *i64
276 m[XP_M_JPAR] = jpar as i64
277 m[XP_M_JPOS] = jpos as i64
278 let tmp: *i64 = sys_mmap(8 * 8) as *i64
279 var j: i64 = 0
280 while j < nj {
281 xp_aline_skip(b, n, p, m)
282 jpar[j] = xp_aint(b, n, p, m)
283 xp_aline(b, n, p, m, 3, mul, tmp)
284 jpos[j * 3] = tmp[0]; jpos[j * 3 + 1] = tmp[1]; jpos[j * 3 + 2] = tmp[2]
285 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
286 j = j + 1
287 }
288 m[XP_M_HASBONES] = (nj > 0) as i64
289 let nmesh: i64 = xp_aint(b, n, p, m)
290 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
291 m[XP_M_NMESH] = nmesh
292 xp_alloc(m, n)
293 let vx: *i64 = m[XP_M_VX] as *i64
294 let tr: *i64 = m[XP_M_TR] as *i64
295 let sj: *i64 = m[XP_M_SJ] as *i64
296 let sw: *i64 = m[XP_M_SW] as *i64
297 var nv: i64 = 0
298 var nt: i64 = 0
299 var mi: i64 = 0
300 while mi < nmesh {
301 xp_aline_skip(b, n, p, m)
302 let uvl: i64 = xp_aint(b, n, p, m)
303 if uvl > m[XP_M_UVMAX] { m[XP_M_UVMAX] = uvl }
304 let ntex: i64 = xp_aint(b, n, p, m)
305 var t: i64 = 0
306 while t < ntex { xp_aline_skip(b, n, p, m); xp_aint(b, n, p, m); t = t + 1 }
307 m[XP_M_NTEX] = m[XP_M_NTEX] + ntex
308 let mv: i64 = xp_aint(b, n, p, m)
309 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
310 if nv + mv > m[XP_M_CAPV] { return XP_ERR_CAP_VERTS }
311 let base: i64 = nv
312 var v: i64 = 0
313 while v < mv {
314 xp_aline(b, n, p, m, 3, mul, tmp)
315 vx[nv * 3] = tmp[0]; vx[nv * 3 + 1] = tmp[1]; vx[nv * 3 + 2] = tmp[2]
316 xp_aline(b, n, p, m, 3, 1, tmp) // normal: read, not carried
317 xp_aline(b, n, p, m, 4, 1, tmp) // colour
318 var u: i64 = 0
319 while u < uvl { xp_aline(b, n, p, m, 2, 1, tmp); u = u + 1 }
320 if m[XP_M_HASBONES] == 1 {
321 xp_aline(b, n, p, m, XP_INF, 1, tmp)
322 var k: i64 = 0
323 while k < XP_INF {
324 var ji: i64 = tmp[k]
325 if ji < 0 { m[XP_M_BADJOINT] = m[XP_M_BADJOINT] + 1; ji = 0 }
326 if ji >= nj { m[XP_M_BADJOINT] = m[XP_M_BADJOINT] + 1; ji = 0 }
327 sj[nv * XP_INF + k] = ji
328 k = k + 1
329 }
330 xp_aline(b, n, p, m, XP_INF, XP_Q12, tmp)
331 k = 0
332 while k < XP_INF { sw[nv * XP_INF + k] = tmp[k]; k = k + 1 }
333 xp_fix_weights(sw, nv * XP_INF, m)
334 }
335 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
336 nv = nv + 1
337 v = v + 1
338 }
339 let mt: i64 = xp_aint(b, n, p, m)
340 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
341 if nt + mt > m[XP_M_CAPT] { return XP_ERR_CAP_TRIS }
342 var f: i64 = 0
343 while f < mt {
344 xp_aline(b, n, p, m, 3, 1, tmp)
345 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
346 var c: i64 = 0
347 while c < 3 {
348 let ix: i64 = tmp[c]
349 if ix < 0 { return XP_ERR_BAD_TRI }
350 if ix >= mv { return XP_ERR_BAD_TRI }
351 tr[nt * 3 + c] = base + ix
352 c = c + 1
353 }
354 nt = nt + 1
355 f = f + 1
356 }
357 mi = mi + 1
358 }
359 m[XP_M_NV] = nv
360 m[XP_M_NT] = nt
361 if nv == 0 { return XP_ERR_EMPTY }
362 if nt == 0 { return XP_ERR_EMPTY }
363 if m[XP_M_BADJOINT] > 0 { return XP_ERR_BAD_JOINT }
364 return XP_OK
365}
366
367// ---- the binary dialect ----
368func xp_read_bin(b: *u8, n: i64, m: *i64) -> i64 {
369 let p: *i64 = sys_mmap(16) as *i64
370 p[0] = 0
371 let mul: i64 = m[XP_M_MUL]
372 let magic: i64 = xp_bu32(b, n, p, m)
373 if magic != XP_MAGIC { return XP_ERR_MAGIC }
374 let vmaj: i64 = xp_bu16(b, n, p, m)
375 let vmin: i64 = xp_bu16(b, n, p, m)
376 m[XP_M_VMAJ] = vmaj
377 m[XP_M_VMIN] = vmin
378 var tangent: i64 = 0
379 if vmaj <= XP_TANGENT_MAJOR_MAX { if vmin <= XP_TANGENT_MINOR_MAX { tangent = 1 } }
380 var varw: i64 = 0
381 if vmaj >= XP_VARW_MAJOR_MIN { varw = 1 }
382 m[XP_M_TANGENT] = tangent
383 m[XP_M_VARW] = varw
384 xp_bstr_skip(b, n, p, m) // XNAaraL
385 let settings_words: i64 = xp_bu32(b, n, p, m)
386 xp_bstr_skip(b, n, p, m) // machine
387 xp_bstr_skip(b, n, p, m) // user
388 xp_bstr_skip(b, n, p, m) // files
389 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
390 if p[0] + settings_words * 4 > n { m[XP_M_ERRPOS] = p[0]; return XP_ERR_TRUNCATED }
391 p[0] = p[0] + settings_words * 4 // the settings block carries render flags, never geometry
392 let nj: i64 = xp_bu32(b, n, p, m)
393 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
394 m[XP_M_NJ] = nj
395 let jpar: *i64 = sys_mmap(8 * (nj + 1)) as *i64
396 let jpos: *i64 = sys_mmap(8 * 3 * (nj + 1)) as *i64
397 m[XP_M_JPAR] = jpar as i64
398 m[XP_M_JPOS] = jpos as i64
399 var j: i64 = 0
400 while j < nj {
401 xp_bstr_skip(b, n, p, m)
402 jpar[j] = xp_bi16(b, n, p, m)
403 jpos[j * 3] = xp_bf32(b, n, p, m, mul)
404 jpos[j * 3 + 1] = xp_bf32(b, n, p, m, mul)
405 jpos[j * 3 + 2] = xp_bf32(b, n, p, m, mul)
406 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
407 j = j + 1
408 }
409 m[XP_M_HASBONES] = (nj > 0) as i64
410 let nmesh: i64 = xp_bu32(b, n, p, m)
411 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
412 m[XP_M_NMESH] = nmesh
413 xp_alloc(m, n)
414 let vx: *i64 = m[XP_M_VX] as *i64
415 let tr: *i64 = m[XP_M_TR] as *i64
416 let sj: *i64 = m[XP_M_SJ] as *i64
417 let sw: *i64 = m[XP_M_SW] as *i64
418 let wtmp: *i64 = sys_mmap(8 * 64) as *i64
419 var nv: i64 = 0
420 var nt: i64 = 0
421 var mi: i64 = 0
422 while mi < nmesh {
423 xp_bstr_skip(b, n, p, m)
424 let uvl: i64 = xp_bu32(b, n, p, m)
425 if uvl > m[XP_M_UVMAX] { m[XP_M_UVMAX] = uvl }
426 let ntex: i64 = xp_bu32(b, n, p, m)
427 var t: i64 = 0
428 while t < ntex { xp_bstr_skip(b, n, p, m); xp_bu32(b, n, p, m); t = t + 1 }
429 m[XP_M_NTEX] = m[XP_M_NTEX] + ntex
430 let mv: i64 = xp_bu32(b, n, p, m)
431 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
432 if nv + mv > m[XP_M_CAPV] { return XP_ERR_CAP_VERTS }
433 let base: i64 = nv
434 var v: i64 = 0
435 while v < mv {
436 vx[nv * 3] = xp_bf32(b, n, p, m, mul)
437 vx[nv * 3 + 1] = xp_bf32(b, n, p, m, mul)
438 vx[nv * 3 + 2] = xp_bf32(b, n, p, m, mul)
439 xp_bu32(b, n, p, m); xp_bu32(b, n, p, m); xp_bu32(b, n, p, m) // normal
440 xp_bu32(b, n, p, m) // RGBA colour bytes
441 var u: i64 = 0
442 while u < uvl {
443 xp_bu32(b, n, p, m); xp_bu32(b, n, p, m)
444 if tangent == 1 { xp_bu32(b, n, p, m); xp_bu32(b, n, p, m); xp_bu32(b, n, p, m); xp_bu32(b, n, p, m) }
445 u = u + 1
446 }
447 if m[XP_M_HASBONES] == 1 {
448 var wc: i64 = XP_INF
449 if varw == 1 { wc = xp_bi16(b, n, p, m) }
450 if wc < 0 { wc = 0 }
451 if wc > 64 { m[XP_M_ERRPOS] = p[0]; return XP_ERR_TRUNCATED }
452 var k: i64 = 0
453 while k < wc { wtmp[k] = xp_bi16(b, n, p, m); k = k + 1 }
454 k = 0
455 while k < wc { wtmp[32 + k] = xp_bf32(b, n, p, m, XP_Q12); k = k + 1 }
456 if wc > XP_INF { m[XP_M_WTRUNC] = m[XP_M_WTRUNC] + 1 }
457 k = 0
458 while k < XP_INF {
459 var ji: i64 = 0
460 var wq: i64 = 0
461 if k < wc { ji = wtmp[k]; wq = wtmp[32 + k] }
462 if ji < 0 { m[XP_M_BADJOINT] = m[XP_M_BADJOINT] + 1; ji = 0 }
463 if ji >= nj { m[XP_M_BADJOINT] = m[XP_M_BADJOINT] + 1; ji = 0 }
464 sj[nv * XP_INF + k] = ji
465 sw[nv * XP_INF + k] = wq
466 k = k + 1
467 }
468 xp_fix_weights(sw, nv * XP_INF, m)
469 }
470 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
471 nv = nv + 1
472 v = v + 1
473 }
474 let mt: i64 = xp_bu32(b, n, p, m)
475 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
476 if nt + mt > m[XP_M_CAPT] { return XP_ERR_CAP_TRIS }
477 var f: i64 = 0
478 while f < mt {
479 var c: i64 = 0
480 while c < 3 {
481 let ix: i64 = xp_bu32(b, n, p, m)
482 if m[XP_M_ERRPOS] >= 0 { return XP_ERR_TRUNCATED }
483 if ix >= mv { return XP_ERR_BAD_TRI }
484 tr[nt * 3 + c] = base + ix
485 c = c + 1
486 }
487 nt = nt + 1
488 f = f + 1
489 }
490 mi = mi + 1
491 }
492 m[XP_M_NV] = nv
493 m[XP_M_NT] = nt
494 if nv == 0 { return XP_ERR_EMPTY }
495 if nt == 0 { return XP_ERR_EMPTY }
496 if m[XP_M_BADJOINT] > 0 { return XP_ERR_BAD_JOINT }
497 return XP_OK
498}
499// the entry: sniff the dialect, read, leave every count and array in m; units_per_xps is DATA (0 selects the declared default)
500func xp_read(b: *u8, n: i64, units_per_xps: i64, m: *i64) -> i64 {
501 var i: i64 = 0
502 while i < XP_M_N { m[i] = 0; i = i + 1 }
503 m[XP_M_ERRPOS] = 0 - 1
504 m[XP_M_MUL] = units_per_xps
505 if units_per_xps <= 0 { m[XP_M_MUL] = XP_UNITS_PER_XPS_DEFAULT }
506 let d: i64 = xp_dialect(b, n)
507 m[XP_M_DIALECT] = d
508 if d == XP_DIALECT_BIN { return xp_read_bin(b, n, m) }
509 return xp_read_ascii(b, n, m)
510}