nx_xps_lib.nx
buildroot/runtime/nx_xps_lib.nx
about
nx_xps_lib.nx -- XPS / XNALara MODEL READER, both dialects (/compare/modding MD2, 2026-09-06). The reference readers were
mirrored and READ before a line of this was written (knowledge/fetched/cmp_modding_xnalara_read_ascii_xps.py pin ac92fd7f,
read_bin_xps.py pin 0e1dca05, bin_ops.py, xps_const.py). What they say, and this lib follows:
ascii (.mesh.ascii): bone count; per bone a name line, a parent line, an xyz line; mesh count; per mesh a name line, a
uv-layer count, a texture count with a name line and a uv-layer line per texture, a vertex count with position, normal,
RGBA colour (four integers), one uv line per layer and -- when the model has bones -- a four-index line and a
four-weight line (short lines are zero-filled), then a triangle count and three indices per face. Numeric lines may
carry a trailing comment after a hash.
binary (.xps): u32 magic 323232, u16 major, u16 minor, a length-prefixed string (one byte; a second byte when the first
is at least 128, length = b1 % 128 + b2 * 128), u32 settings length in 32-bit words, three more strings (machine,
user, files), then the settings block of exactly that many words (old format raw, new format typed items -- skipped
whole, it carries render flags, never geometry); bones: u32 count, string, i16 parent, three float32; meshes: u32 count,
string, u32 uv layers, u32 textures (string + u32 layer each), u32 vertices (three float32 position, three float32
normal, four colour bytes, per layer two float32 uv plus four float32 tangent when TANGENT versions apply), bone
weights as i16 count on VARIABLE-WEIGHT versions else four, i16 indices then float32 weights; u32 triangles of three u32.
TANGENT versions: minor <= 12 and major <= 2. VARIABLE weights: major >= 3. (bin_ops.py, read verbatim.)
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,
data never a guess), every mesh merged into one vertex table with per-mesh index offsets, SKEL from the bone table with the
identity bind rotation the estate's rig convention declares, SKIN as four q12 weights per vertex summing to exactly 4096
(a float sum that rounds away from 4096 has the residual moved to its largest lane and the vertex COUNTED as fixed).
Normals, colours, uvs and tangents are READ and COUNTED but not carried (TEXC is the texture rung, nx_nxa_texc).
Every refusal is named; a truncated binary is refused at the byte it ran out on, never read as a smaller model.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 1 imports · 2 importers
imports: nx_syscalls.nx
imported by: nx_xps.nxnx_xps_gate.nx
structs
| none |
consts
| 26 | const XP_MAGIC: i64 = 323232 |
| 27 | const XP_STR_LIMIT: i64 = 128 |
| 28 | const XP_TANGENT_MAJOR_MAX: i64 = 2 |
| 29 | const XP_TANGENT_MINOR_MAX: i64 = 12 |
| 30 | const XP_VARW_MAJOR_MIN: i64 = 3 |
| 31 | const XP_INF: i64 = 4 |
| 32 | const XP_Q12: i64 = 4096 |
| 33 | const XP_UNITS_PER_XPS_DEFAULT: i64 = 100000 // 1 XPS unit read as 1 metre = 100000 units of 0.01 mm; DECLARED, overridable |
| 34 | const XP_F32_SIGN: i64 = 2147483648 |
| 35 | const XP_F32_EXP_DIV: i64 = 8388608 |
| 36 | const XP_F32_MAN_MASK: i64 = 8388607 |
| 37 | const XP_F32_HIDDEN: i64 = 8388608 |
| 38 | const XP_F32_BIAS_MAN: i64 = 150 |
| 39 | const XP_F32_SHIFT_CAP: i64 = 20 |
| 40 | const XP_FRAC_DIGITS_MAX: i64 = 9 |
| 41 | const XP_VERT_MIN_BYTES: i64 = 16 // the smallest vertex either dialect can spell; caps are DERIVED from the input size |
| 42 | const XP_TRI_MIN_BYTES: i64 = 6 |
| 43 | const XP_DIALECT_ASCII: i64 = 1 |
| 44 | const XP_DIALECT_BIN: i64 = 2 |
| 46 | const XP_M_NV: i64 = 0 |
| 47 | const XP_M_NT: i64 = 1 |
| 48 | const XP_M_NJ: i64 = 2 |
| 49 | const XP_M_NMESH: i64 = 3 |
| 50 | const XP_M_VX: i64 = 4 |
| 51 | const XP_M_TR: i64 = 5 |
| 52 | const XP_M_JPAR: i64 = 6 |
| 53 | const XP_M_JPOS: i64 = 7 |
| 54 | const XP_M_SJ: i64 = 8 |
| 55 | const XP_M_SW: i64 = 9 |
| 56 | const XP_M_HASBONES: i64 = 10 |
| 57 | const XP_M_DIALECT: i64 = 11 |
| 58 | const XP_M_VMAJ: i64 = 12 |
| 59 | const XP_M_VMIN: i64 = 13 |
| 60 | const XP_M_TANGENT: i64 = 14 |
| 61 | const XP_M_VARW: i64 = 15 |
| 62 | const XP_M_CAPV: i64 = 16 |
| 63 | const XP_M_CAPT: i64 = 17 |
| 64 | const XP_M_NTEX: i64 = 18 |
| 65 | const XP_M_WFIX: i64 = 19 |
| 66 | const XP_M_UVMAX: i64 = 20 |
| 67 | const XP_M_MUL: i64 = 21 |
| 68 | const XP_M_ERRPOS: i64 = 22 |
| 69 | const XP_M_BADJOINT: i64 = 23 |
| 70 | const XP_M_WTRUNC: i64 = 24 |
| 71 | const XP_M_N: i64 = 32 |
| 73 | const XP_OK: i64 = 0 |
| 74 | const XP_ERR_TRUNCATED: i64 = 0 - 1 |
| 75 | const XP_ERR_CAP_VERTS: i64 = 0 - 2 |
| 76 | const XP_ERR_CAP_TRIS: i64 = 0 - 3 |
| 77 | const XP_ERR_MAGIC: i64 = 0 - 4 |
| 78 | const XP_ERR_BAD_JOINT: i64 = 0 - 5 |
| 79 | const XP_ERR_BAD_TRI: i64 = 0 - 6 |
| 80 | const XP_ERR_EMPTY: i64 = 0 - 7 |
| 81 | const XP_ERR_BAD_NUMBER: i64 = 0 - 8 |
functions
| 83 | func xp_err_name(e: i64) -> *u8 |
| 94 | func xp_pow2(k: i64) -> i64 { var v: i64 = 1; var i: i64 = 0; while i < k { v = v * 2; i = i + 1 } return v } |
| 96 | func xp_f32fx(bits: i64, mul: i64) -> i64 |
| 117 | func xp_bu8(b: *u8, n: i64, p: *i64, m: *i64) -> i64 |
| 123 | func xp_bu16(b: *u8, n: i64, p: *i64, m: *i64) -> i64 |
| 129 | func 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 } |
| 130 | func xp_bu32(b: *u8, n: i64, p: *i64, m: *i64) -> i64 |
| 137 | func 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) } |
| 139 | func xp_bstr_skip(b: *u8, n: i64, p: *i64, m: *i64) -> i64 |
| 149 | func xp_is_ws(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } if c == 13 { return 1 } return 0 } |
| 150 | func xp_is_digit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 } |
| 152 | func xp_next_line(b: *u8, n: i64, p: *i64) -> i64 |
| 159 | func xp_skip_ws(b: *u8, n: i64, p: *i64) -> i64 |
| 167 | func xp_afx(b: *u8, n: i64, p: *i64, mul: i64, ok: *i64) -> i64 |
| 202 | func xp_aint(b: *u8, n: i64, p: *i64, m: *i64) -> i64 |
| 211 | func xp_aline(b: *u8, n: i64, p: *i64, m: *i64, k: i64, mul: i64, out: *i64) -> i64 |
| 226 | func 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 } |
| 229 | func xp_fix_weights(sw: *i64, base: i64, m: *i64) -> i64 |
| 245 | func xp_alloc(m: *i64, n: i64) -> i64 |
| 257 | func xp_dialect(b: *u8, n: i64) -> i64 |
| 266 | func xp_read_ascii(b: *u8, n: i64, m: *i64) -> i64 |
| 368 | func xp_read_bin(b: *u8, n: i64, m: *i64) -> i64 |
| 500 | func xp_read(b: *u8, n: i64, units_per_xps: i64, m: *i64) -> i64 |