nx_varfacts_lib.nx source
↩ module page · 708 lines · 29249 B
1// nx_varfacts_lib.nx -- VaM .var PACKAGE FACT EXTRACTION CORE (conversion tier, VaM lane).
2//
3// A .var is a ZIP archive. THE WALL THIS LANE LIVES BEHIND (standing policy 1785794796 +
4// measure-dont-ship): VaM is a CAPABILITY TARGET -- asset BYTES (meshes/textures/morph deltas)
5// never enter our products or our published plane. What this core reads is the CENTRAL DIRECTORY
6// (file NAMES + sizes = the package's own manifest) and meta.json (the package's own declared
7// license) -- the package describing itself, never its content. Extracted MEASUREMENTS flow to
8// refcorpus behind the k>=8 admission wall like every other reference corpus.
9//
10// COMPOSES INCUMBENTS: EOCD/central-directory walk = the nx_zip_read shape (that organ is a
11// hardwired PyTorch-.bin test, not a library -- the walk is factored HERE as callables); ZIP
12// method-8 members inflate via nx_deflate_inflate, the SAME raw-DEFLATE core under nx_gzip_wrap
13// and nx_zlib_wrap (capacity-bounded; NOT the retired duplicate nx_inflate, debt 1785854636).
14//
15// ★TAIL-WINDOW CONTRACT: the EOCD + central directory live at the END of a ZIP, so a 519MB .var
16// measures from its last few MB. The caller hands the TAIL bytes + the true file size; if the
17// central directory starts before the window, the probe REFUSES (ok=0) -- a partial census is a
18// plausible undercount, which is worse than absence.
19//
20// Fact slots (flat i64, VF_N_SLOTS):
21// [0] ok [1] entries [2] morphs(.vmi) [3] scenes(Saves/scene/*.json) [4] textures(jpg/png/tif)
22// [5] clothing(/Clothing/) [6] hair(/Hair/) [7] plugins(.cs/.cslist/.dll) [8] appearance(.vap)
23// [9] meta_found [10] meta_lho [11] meta_method [12] meta_csize [13] meta_usize
24//
25// ★2026-09-11 EXTENSION (VaM census lane, ADDITIVE -- every legacy slot above keeps its meaning):
26// (1) THE WALK IS FACTORED INTO CALLABLES so vf_probe, the census CLI (nx_varcensus) and the gate share ONE walk:
27// vf_cd_locate reads the EOCD and names the outcome (OK / NO-EOCD / ZIP64 / WINDOW / SHORT) instead of one
28// ok=0 for five different reasons, and WINDOW hands the caller the exact window it must read (file_size -
29// cd_abs, derived from the archive itself, so no tail size has to be guessed); vf_cd_entry reads ONE record.
30// (2) vf_member_data: STORED bytes in place, DEFLATE through the incumbent nx_deflate_inflate sized from the member's
31// DECLARED usize and required to decode to exactly usize -- no capacity constant to tune. CRC is NOT verified here
32// (imprecision chosen: a corrupt meta.json fails its JSON parse and is counted as unparsed, it is never trusted).
33// (3) vf_json_*: a DEPTH-AWARE reader for meta.json. A key is matched only as a DIRECT member of the object it is asked
34// of, so a nested dependency's own licenseType can never answer for the package (vf_license below takes the FIRST
35// occurrence in the byte stream; the census prints both readings so their agreement is measured, not assumed).
36// license_tier: ORIGINAL No hw writes (Rule 26).
37import "nx_syscalls.nx"
38import "nx_deflate.nx"
39const VF_MAGIC_1024: i64 = 1024
40const VF_MAGIC_4194304: i64 = 4194304
41
42const VF_N_SLOTS: i64 = 14
43
44// ---- PKZIP layout (APPNOTE 4.3.6 / 4.3.12 / 4.3.16), named so every offset below reads as a field ----
45const VF_SIG_EOCD: i64 = 0x06054b50
46const VF_SIG_CD: i64 = 0x02014b50
47const VF_SIG_LOCAL: i64 = 0x04034b50
48const VF_EOCD_LEN: i64 = 22
49const VF_CD_LEN: i64 = 46
50const VF_LOCAL_LEN: i64 = 30
51const VF_COMMENT_MAX: i64 = 65535
52const VF_U16_ALL: i64 = 0xffff
53const VF_U32_ALL: i64 = 0xffffffff
54const VF_EOCD_ENTRIES: i64 = 10
55const VF_EOCD_CDSIZE: i64 = 12
56const VF_EOCD_CDOFF: i64 = 16
57const VF_CD_FLAGS: i64 = 8
58const VF_CD_METHOD: i64 = 10
59const VF_CD_CSIZE: i64 = 20
60const VF_CD_USIZE: i64 = 24
61const VF_CD_FNL: i64 = 28
62const VF_CD_EXL: i64 = 30
63const VF_CD_CML: i64 = 32
64const VF_CD_LHO: i64 = 42
65const VF_LOCAL_FNL: i64 = 26
66const VF_LOCAL_EXL: i64 = 28
67const VF_METHOD_STORE: i64 = 0
68const VF_METHOD_DEFLATE: i64 = 8
69
70// vf_cd_locate box slots + states
71const VF_L_EOCD: i64 = 0
72const VF_L_ENTRIES: i64 = 1
73const VF_L_CDSIZE: i64 = 2
74const VF_L_CDABS: i64 = 3
75const VF_L_CD: i64 = 4
76const VF_L_STATE: i64 = 5
77const VF_L_N: i64 = 8
78const VF_LOC_OK: i64 = 0
79const VF_LOC_NOEOCD: i64 = 1
80const VF_LOC_ZIP64: i64 = 2
81const VF_LOC_WINDOW: i64 = 3
82const VF_LOC_SHORT: i64 = 4
83
84// vf_cd_entry record slots
85const VF_E_METHOD: i64 = 0
86const VF_E_CSIZE: i64 = 1
87const VF_E_USIZE: i64 = 2
88const VF_E_NAMELEN: i64 = 3
89const VF_E_LHO: i64 = 4
90const VF_E_NAMEOFF: i64 = 5
91const VF_E_NEXT: i64 = 6
92const VF_E_FLAGS: i64 = 7
93const VF_E_Z64: i64 = 8
94const VF_E_N: i64 = 10
95// ZIP64 extended information extra field (APPNOTE 4.5.3). MEASURED 2026-09-11 on real VaM packages: a writer can mark
96// EVERY central-directory size as 0xFFFFFFFF with the true values in extra 0x0001 even when the archive is small and its
97// EOCD is plain 32-bit -- so a reader that trusts the fixed fields reads 4294967295-byte members and every meta.json read fails.
98const VF_CD_DISK: i64 = 34
99const VF_X_ZIP64: i64 = 0x0001
100const VF_X_HDR: i64 = 4
101const VF_U64_LEN: i64 = 8
102const VF_Z64_NONE: i64 = 0
103const VF_Z64_RESOLVED: i64 = 1
104const VF_Z64_UNRESOLVED: i64 = 0 - 1
105
106// vf_member_data states
107const VF_MD_STORED: i64 = 0
108const VF_MD_INFLATED: i64 = 1
109const VF_MD_FAILED: i64 = 2
110const VF_MD_UNSUPPORTED: i64 = 3
111
112// JSON structural bytes
113const VF_J_QUOTE: i64 = 34
114const VF_J_BSL: i64 = 92
115const VF_J_COLON: i64 = 58
116const VF_J_COMMA: i64 = 44
117const VF_J_LBRACE: i64 = 123
118const VF_J_RBRACE: i64 = 125
119const VF_J_LBRACK: i64 = 91
120const VF_J_RBRACK: i64 = 93
121const VF_J_SPACE: i64 = 32
122const VF_J_TAB: i64 = 9
123const VF_J_LF: i64 = 10
124const VF_J_CR: i64 = 13
125// JSON value kinds (vf_json_kind)
126const VF_JK_BAD: i64 = 0
127const VF_JK_STRING: i64 = 1
128const VF_JK_OBJECT: i64 = 2
129const VF_JK_ARRAY: i64 = 3
130const VF_JK_SCALAR: i64 = 4
131// vf_json_member box slots
132const VF_JM_KS: i64 = 0
133const VF_JM_KE: i64 = 1
134const VF_JM_VS: i64 = 2
135const VF_JM_VE: i64 = 3
136const VF_JM_N: i64 = 4
137// UTF-8 encoder structure (RFC 3629)
138const VF_UTF8_CONT: i64 = 128
139const VF_UTF8_LEAD2: i64 = 192
140const VF_UTF8_LEAD3: i64 = 224
141const VF_UTF8_LEAD4: i64 = 240
142const VF_UTF8_SEXTET: i64 = 64
143const VF_UTF8_MAX1: i64 = 128
144const VF_UTF8_MAX2: i64 = 2048
145const VF_UTF8_MAX3: i64 = 65536
146const VF_SURR_HI: i64 = 0xd800
147const VF_SURR_LO: i64 = 0xdc00
148const VF_SURR_END: i64 = 0xe000
149const VF_SURR_BASE: i64 = 0x10000
150const VF_SURR_SPAN: i64 = 1024
151const VF_HEX_DIGITS: i64 = 4
152const VF_HEX_BASE: i64 = 16
153
154func vf_u16(b: *u8, o: i64) -> i64 { return (b[o] as i64 & 0xff) | ((b[o+1] as i64 & 0xff) << 8) }
155func vf_u32(b: *u8, o: i64) -> i64 { return (b[o] as i64 & 0xff) | ((b[o+1] as i64 & 0xff) << 8) | ((b[o+2] as i64 & 0xff) << 16) | ((b[o+3] as i64 & 0xff) << 24) }
156
157// name[0,nl) ends with suffix?
158func vf_ends(name: *u8, nl: i64, suf: *u8) -> i64 {
159 var m: i64 = 0
160 while suf[m] != (0 as u8) { m = m + 1 }
161 if nl < m { return 0 }
162 var i: i64 = 0
163 while i < m { if (name[nl-m+i] as i64) != (suf[i] as i64) { return 0 } i = i + 1 }
164 return 1
165}
166func vf_has(name: *u8, nl: i64, sub: *u8) -> i64 {
167 var m: i64 = 0
168 while sub[m] != (0 as u8) { m = m + 1 }
169 if nl < m { return 0 }
170 var i: i64 = 0
171 while i + m <= nl {
172 var k: i64 = 0
173 while k < m { if (name[i+k] as i64) != (sub[k] as i64) { k = m + 9 } else { k = k + 1 } }
174 if k == m { return 1 }
175 i = i + 1
176 }
177 return 0
178}
179
180// the first window a caller must read to be sure it holds the EOCD: the record plus the longest legal comment
181func vf_eocd_window(file_size: i64) -> i64 {
182 var w: i64 = VF_EOCD_LEN + VF_COMMENT_MAX
183 if w > file_size { w = file_size }
184 if w < 0 { w = 0 }
185 return w
186}
187
188// locate the central directory inside a tail window (tail[0] sits at absolute offset file_size - tailn). Fills box
189// (VF_L_*) and returns the NAMED state. WINDOW means the CD starts before the window: read file_size - box[VF_L_CDABS]
190// bytes from the end and locate again. ZIP64 is refused by name, never parsed as a 32-bit archive.
191func vf_cd_locate(tail: *u8, tailn: i64, file_size: i64, box: *i64) -> i64 {
192 var i: i64 = 0
193 while i < VF_L_N { box[i] = 0 - 1; i = i + 1 }
194 box[VF_L_STATE] = VF_LOC_NOEOCD
195 if tailn < VF_EOCD_LEN { return VF_LOC_NOEOCD }
196 var e: i64 = tailn - VF_EOCD_LEN
197 var eocd: i64 = 0 - 1
198 var run: i64 = 1
199 while run == 1 {
200 if e < 0 { run = 0 } else {
201 if vf_u32(tail, e) == VF_SIG_EOCD { eocd = e; run = 0 } else { e = e - 1 }
202 }
203 }
204 if eocd < 0 { return VF_LOC_NOEOCD }
205 box[VF_L_EOCD] = eocd
206 let entries: i64 = vf_u16(tail, eocd + VF_EOCD_ENTRIES)
207 let cd_size: i64 = vf_u32(tail, eocd + VF_EOCD_CDSIZE)
208 let cd_abs: i64 = vf_u32(tail, eocd + VF_EOCD_CDOFF)
209 box[VF_L_ENTRIES] = entries
210 box[VF_L_CDSIZE] = cd_size
211 box[VF_L_CDABS] = cd_abs
212 var z64: i64 = 0
213 if entries == VF_U16_ALL { z64 = 1 }
214 if cd_size == VF_U32_ALL { z64 = 1 }
215 if cd_abs == VF_U32_ALL { z64 = 1 }
216 if z64 == 1 { box[VF_L_STATE] = VF_LOC_ZIP64; return VF_LOC_ZIP64 }
217 let base: i64 = file_size - tailn
218 let cd: i64 = cd_abs - base
219 if cd < 0 { box[VF_L_STATE] = VF_LOC_WINDOW; return VF_LOC_WINDOW }
220 if cd + cd_size > tailn { box[VF_L_STATE] = VF_LOC_SHORT; return VF_LOC_SHORT }
221 box[VF_L_CD] = cd
222 box[VF_L_STATE] = VF_LOC_OK
223 return VF_LOC_OK
224}
225
226// ONE central-directory record at tail[off]. Fills rec (VF_E_*); returns 1, or 0 when the record or its name would
227// read outside the window or the signature is not a CD record (REFUSE, never a partial row).
228func vf_cd_entry(tail: *u8, tailn: i64, off: i64, rec: *i64) -> i64 {
229 if off < 0 { return 0 }
230 if off + VF_CD_LEN > tailn { return 0 }
231 if vf_u32(tail, off) != VF_SIG_CD { return 0 }
232 let fnl: i64 = vf_u16(tail, off + VF_CD_FNL)
233 let exl: i64 = vf_u16(tail, off + VF_CD_EXL)
234 let cml: i64 = vf_u16(tail, off + VF_CD_CML)
235 if off + VF_CD_LEN + fnl > tailn { return 0 }
236 rec[VF_E_METHOD] = vf_u16(tail, off + VF_CD_METHOD)
237 rec[VF_E_CSIZE] = vf_u32(tail, off + VF_CD_CSIZE)
238 rec[VF_E_USIZE] = vf_u32(tail, off + VF_CD_USIZE)
239 rec[VF_E_NAMELEN] = fnl
240 rec[VF_E_LHO] = vf_u32(tail, off + VF_CD_LHO)
241 rec[VF_E_NAMEOFF] = off + VF_CD_LEN
242 rec[VF_E_NEXT] = off + VF_CD_LEN + fnl + exl + cml
243 rec[VF_E_FLAGS] = vf_u16(tail, off + VF_CD_FLAGS)
244 rec[VF_E_Z64] = vf_zip64_extra(tail, tailn, off + VF_CD_LEN + fnl, exl, rec)
245 return 1
246}
247
248func vf_u64(b: *u8, o: i64) -> i64 { return vf_u32(b, o) | (vf_u32(b, o + 4) << 32) }
249
250// resolve the CD fields that read 0xFFFFFFFF from extra field 0x0001 (values present ONLY for the overflowed fields, in the
251// fixed order usize, csize, lho). The need flags are taken BEFORE any field is rewritten, so resolving one cannot change
252// whether the next is read. Returns VF_Z64_NONE (nothing overflowed), VF_Z64_RESOLVED, or VF_Z64_UNRESOLVED (a field
253// overflowed and no well-formed 0x0001 record carries it -- the caller must refuse that member's sizes by name).
254func vf_zip64_extra(tail: *u8, tailn: i64, xs: i64, xl: i64, rec: *i64) -> i64 {
255 var nu: i64 = 0
256 var nc: i64 = 0
257 var nlh: i64 = 0
258 if rec[VF_E_USIZE] == VF_U32_ALL { nu = 1 }
259 if rec[VF_E_CSIZE] == VF_U32_ALL { nc = 1 }
260 if rec[VF_E_LHO] == VF_U32_ALL { nlh = 1 }
261 if nu + nc + nlh == 0 { return VF_Z64_NONE }
262 let xe: i64 = xs + xl
263 if xe > tailn { return VF_Z64_UNRESOLVED }
264 var p: i64 = xs
265 while p + VF_X_HDR <= xe {
266 let id: i64 = vf_u16(tail, p)
267 let sz: i64 = vf_u16(tail, p + 2)
268 let ds: i64 = p + VF_X_HDR
269 let de: i64 = ds + sz
270 if de > xe { return VF_Z64_UNRESOLVED }
271 if id == VF_X_ZIP64 {
272 if (nu + nc + nlh) * VF_U64_LEN > sz { return VF_Z64_UNRESOLVED }
273 var q: i64 = ds
274 if nu == 1 { rec[VF_E_USIZE] = vf_u64(tail, q); q = q + VF_U64_LEN }
275 if nc == 1 { rec[VF_E_CSIZE] = vf_u64(tail, q); q = q + VF_U64_LEN }
276 if nlh == 1 { rec[VF_E_LHO] = vf_u64(tail, q); q = q + VF_U64_LEN }
277 return VF_Z64_RESOLVED
278 }
279 p = de
280 }
281 return VF_Z64_UNRESOLVED
282}
283
284// probe the TAIL of a .var: tail holds the last tailn bytes, file_size is the true size. (Legacy contract, now a
285// consumer of vf_cd_locate + vf_cd_entry: every slot and every refusal is what it was.)
286func vf_probe(tail: *u8, tailn: i64, file_size: i64, facts: *i64) -> i64 {
287 var i: i64 = 0
288 while i < VF_N_SLOTS { facts[i] = 0 - 1; i = i + 1 }
289 facts[0] = 0
290 let box: *i64 = sys_mmap(VF_L_N * 8) as *i64
291 if vf_cd_locate(tail, tailn, file_size, box) != VF_LOC_OK { return 0 }
292 let entries: i64 = box[VF_L_ENTRIES]
293 let rec: *i64 = sys_mmap(VF_E_N * 8) as *i64
294 var morphs: i64 = 0
295 var scenes: i64 = 0
296 var tex: i64 = 0
297 var cloth: i64 = 0
298 var hair: i64 = 0
299 var plug: i64 = 0
300 var vap: i64 = 0
301 var off: i64 = box[VF_L_CD]
302 var k: i64 = 0
303 while k < entries {
304 if vf_cd_entry(tail, tailn, off, rec) != 1 { facts[0] = 0; return 0 }
305 let nm: *u8 = ((tail as i64) + rec[VF_E_NAMEOFF]) as *u8
306 let fnl: i64 = rec[VF_E_NAMELEN]
307 if vf_ends(nm, fnl, ".vmi" as *u8) == 1 { morphs = morphs + 1 }
308 if vf_has(nm, fnl, "Saves/scene/" as *u8) == 1 { if vf_ends(nm, fnl, ".json" as *u8) == 1 { scenes = scenes + 1 } }
309 if vf_ends(nm, fnl, ".jpg" as *u8) == 1 { tex = tex + 1 }
310 if vf_ends(nm, fnl, ".png" as *u8) == 1 { tex = tex + 1 }
311 if vf_ends(nm, fnl, ".tif" as *u8) == 1 { tex = tex + 1 }
312 if vf_has(nm, fnl, "/Clothing/" as *u8) == 1 { cloth = cloth + 1 }
313 if vf_has(nm, fnl, "/Hair/" as *u8) == 1 { hair = hair + 1 }
314 if vf_ends(nm, fnl, ".cs" as *u8) == 1 { plug = plug + 1 }
315 if vf_ends(nm, fnl, ".cslist" as *u8) == 1 { plug = plug + 1 }
316 if vf_ends(nm, fnl, ".dll" as *u8) == 1 { plug = plug + 1 }
317 if vf_ends(nm, fnl, ".vap" as *u8) == 1 { vap = vap + 1 }
318 if fnl == 9 { if vf_has(nm, fnl, "meta.json" as *u8) == 1 {
319 facts[9] = 1
320 facts[10] = rec[VF_E_LHO]
321 facts[11] = rec[VF_E_METHOD]
322 facts[12] = rec[VF_E_CSIZE]
323 facts[13] = rec[VF_E_USIZE]
324 } }
325 off = rec[VF_E_NEXT]
326 k = k + 1
327 }
328 facts[0] = 1
329 facts[1] = entries
330 facts[2] = morphs
331 facts[3] = scenes
332 facts[4] = tex
333 facts[5] = cloth
334 facts[6] = hair
335 facts[7] = plug
336 facts[8] = vap
337 if facts[9] != 1 { facts[9] = 0 }
338 return 0
339}
340
341// the data of ONE member whose compressed bytes are body[0..csize). out[0] = data length, out[1] = VF_MD_* state.
342// Returns the data pointer (body itself when STORED) or 0. DEFLATE output is sized from the DECLARED usize and must
343// decode to EXACTLY usize bytes, so a lying header refuses instead of truncating.
344func vf_member_data(body: *u8, csize: i64, method: i64, usize: i64, out: *i64) -> *u8 {
345 out[0] = 0
346 out[1] = VF_MD_FAILED
347 if method == VF_METHOD_STORE {
348 out[0] = csize
349 out[1] = VF_MD_STORED
350 return body
351 }
352 if method != VF_METHOD_DEFLATE { out[1] = VF_MD_UNSUPPORTED; return 0 as *u8 }
353 if csize <= 0 { return 0 as *u8 }
354 if usize <= 0 { return 0 as *u8 }
355 let dr: *NxDeflateResult = nx_deflate_inflate(body, csize, usize)
356 if (dr as i64) == 0 { return 0 as *u8 }
357 if (dr.error_code as i64) != 0 { return 0 as *u8 }
358 if (dr.output_size as i64) != usize { return 0 as *u8 }
359 out[0] = usize
360 out[1] = VF_MD_INFLATED
361 return dr.output_data
362}
363
364// ---- depth-aware JSON reading for meta.json ----
365func vf_json_ws(c: i64) -> i64 {
366 if c == VF_J_SPACE { return 1 }
367 if c == VF_J_TAB { return 1 }
368 if c == VF_J_LF { return 1 }
369 if c == VF_J_CR { return 1 }
370 return 0
371}
372func vf_json_skip(src: *u8, n: i64, p: i64) -> i64 {
373 var q: i64 = p
374 var run: i64 = 1
375 while run == 1 {
376 if q >= n { run = 0 } else {
377 if vf_json_ws(src[q] as i64 & 0xff) == 1 { q = q + 1 } else { run = 0 }
378 }
379 }
380 return q
381}
382func vf_json_open(c: i64) -> i64 { if c == VF_J_LBRACE { return 1 } if c == VF_J_LBRACK { return 1 } return 0 }
383func vf_json_close(c: i64) -> i64 { if c == VF_J_RBRACE { return 1 } if c == VF_J_RBRACK { return 1 } return 0 }
384
385// end (exclusive, one past the closing quote) of the string whose opening quote is at p; -1 when unterminated
386func vf_json_str_end(src: *u8, n: i64, p: i64) -> i64 {
387 var j: i64 = p + 1
388 while j < n {
389 let c: i64 = src[j] as i64 & 0xff
390 if c == VF_J_BSL { j = j + 2 } else {
391 if c == VF_J_QUOTE { return j + 1 }
392 j = j + 1
393 }
394 }
395 return 0 - 1
396}
397
398// kind of the value that starts at p
399func vf_json_kind(src: *u8, n: i64, p: i64) -> i64 {
400 if p < 0 { return VF_JK_BAD }
401 if p >= n { return VF_JK_BAD }
402 let c: i64 = src[p] as i64 & 0xff
403 if c == VF_J_QUOTE { return VF_JK_STRING }
404 if c == VF_J_LBRACE { return VF_JK_OBJECT }
405 if c == VF_J_LBRACK { return VF_JK_ARRAY }
406 if vf_json_close(c) == 1 { return VF_JK_BAD }
407 if c == VF_J_COMMA { return VF_JK_BAD }
408 if c == VF_J_COLON { return VF_JK_BAD }
409 return VF_JK_SCALAR
410}
411
412// end (exclusive) of the JSON value starting at p; -1 when malformed or unterminated. Brackets are matched with string
413// awareness, so a brace inside a description never closes an object.
414func vf_json_value_end(src: *u8, n: i64, p: i64) -> i64 {
415 let kind: i64 = vf_json_kind(src, n, p)
416 if kind == VF_JK_BAD { return 0 - 1 }
417 if kind == VF_JK_STRING { return vf_json_str_end(src, n, p) }
418 if kind == VF_JK_SCALAR {
419 var s: i64 = p
420 while s < n {
421 let c: i64 = src[s] as i64 & 0xff
422 if c == VF_J_COMMA { return s }
423 if vf_json_close(c) == 1 { return s }
424 if vf_json_ws(c) == 1 { return s }
425 s = s + 1
426 }
427 return n
428 }
429 var depth: i64 = 0
430 var j: i64 = p
431 while j < n {
432 let c2: i64 = src[j] as i64 & 0xff
433 if c2 == VF_J_QUOTE {
434 let se: i64 = vf_json_str_end(src, n, j)
435 if se < 0 { return 0 - 1 }
436 j = se
437 } else {
438 if vf_json_open(c2) == 1 { depth = depth + 1 }
439 if vf_json_close(c2) == 1 {
440 depth = depth - 1
441 if depth == 0 { return j + 1 }
442 }
443 j = j + 1
444 }
445 }
446 return 0 - 1
447}
448
449// ONE member of an object whose value span ends at `end` (exclusive; src[end-1] is its closing brace), starting at p
450// (whitespace already skipped). box: key content span [KS,KE) (inside the quotes), value span [VS,VE). Returns where the
451// next member starts (end-1 after the last member), or -1 when there is no member here or the object is malformed.
452func vf_json_member(src: *u8, end: i64, p: i64, box: *i64) -> i64 {
453 if p < 0 { return 0 - 1 }
454 if p >= end - 1 { return 0 - 1 }
455 if (src[p] as i64 & 0xff) != VF_J_QUOTE { return 0 - 1 }
456 let ke: i64 = vf_json_str_end(src, end, p)
457 if ke < 0 { return 0 - 1 }
458 let c: i64 = vf_json_skip(src, end, ke)
459 if c >= end { return 0 - 1 }
460 if (src[c] as i64 & 0xff) != VF_J_COLON { return 0 - 1 }
461 let vs: i64 = vf_json_skip(src, end, c + 1)
462 let ve: i64 = vf_json_value_end(src, end, vs)
463 if ve < 0 { return 0 - 1 }
464 box[VF_JM_KS] = p + 1
465 box[VF_JM_KE] = ke - 1
466 box[VF_JM_VS] = vs
467 box[VF_JM_VE] = ve
468 var q: i64 = vf_json_skip(src, end, ve)
469 if q < end { if (src[q] as i64 & 0xff) == VF_J_COMMA { q = vf_json_skip(src, end, q + 1) } }
470 if q <= p { return 0 - 1 }
471 return q
472}
473
474// src[s..e) equals the NUL-terminated lit, byte for byte
475func vf_span_is(src: *u8, s: i64, e: i64, lit: *u8) -> i64 {
476 var m: i64 = 0
477 while lit[m] != (0 as u8) { m = m + 1 }
478 if e - s != m { return 0 }
479 var i: i64 = 0
480 while i < m { if (src[s+i] as i64 & 0xff) != (lit[i] as i64 & 0xff) { return 0 } i = i + 1 }
481 return 1
482}
483
484// the value start of the DIRECT member `key` of the object whose opening brace is at obj; box gets the member spans.
485// -1 when obj is not an object, the key is not a direct member, or the object is malformed before the key.
486func vf_json_find(src: *u8, n: i64, obj: i64, key: *u8, box: *i64) -> i64 {
487 if vf_json_kind(src, n, obj) != VF_JK_OBJECT { return 0 - 1 }
488 let end: i64 = vf_json_value_end(src, n, obj)
489 if end < 0 { return 0 - 1 }
490 var p: i64 = vf_json_skip(src, end, obj + 1)
491 var run: i64 = 1
492 while run == 1 {
493 if p >= end - 1 { run = 0 } else {
494 let nx: i64 = vf_json_member(src, end, p, box)
495 if nx < 0 { run = 0 } else {
496 if vf_span_is(src, box[VF_JM_KS], box[VF_JM_KE], key) == 1 { return box[VF_JM_VS] }
497 p = nx
498 }
499 }
500 }
501 return 0 - 1
502}
503
504// number of DIRECT members of the object at obj, or -1 when it is not a well-formed object
505func vf_json_count_members(src: *u8, n: i64, obj: i64) -> i64 {
506 if vf_json_kind(src, n, obj) != VF_JK_OBJECT { return 0 - 1 }
507 let end: i64 = vf_json_value_end(src, n, obj)
508 if end < 0 { return 0 - 1 }
509 let box: *i64 = sys_mmap(VF_JM_N * 8) as *i64
510 var p: i64 = vf_json_skip(src, end, obj + 1)
511 var cnt: i64 = 0
512 var run: i64 = 1
513 while run == 1 {
514 if p >= end - 1 { run = 0 } else {
515 let nx: i64 = vf_json_member(src, end, p, box)
516 if nx < 0 { return 0 - 1 }
517 cnt = cnt + 1
518 p = nx
519 }
520 }
521 return cnt
522}
523
524// number of elements of the array at arr, or -1 when it is not a well-formed array
525func vf_json_count_elems(src: *u8, n: i64, arr: i64) -> i64 {
526 if vf_json_kind(src, n, arr) != VF_JK_ARRAY { return 0 - 1 }
527 let end: i64 = vf_json_value_end(src, n, arr)
528 if end < 0 { return 0 - 1 }
529 var p: i64 = vf_json_skip(src, end, arr + 1)
530 var cnt: i64 = 0
531 var run: i64 = 1
532 while run == 1 {
533 if p >= end - 1 { run = 0 } else {
534 let ve: i64 = vf_json_value_end(src, end, p)
535 if ve < 0 { return 0 - 1 }
536 cnt = cnt + 1
537 var q: i64 = vf_json_skip(src, end, ve)
538 if q < end { if (src[q] as i64 & 0xff) == VF_J_COMMA { q = vf_json_skip(src, end, q + 1) } }
539 if q <= p { return 0 - 1 }
540 p = q
541 }
542 }
543 return cnt
544}
545
546func vf_hexval(c: i64) -> i64 {
547 if c >= 48 { if c <= 57 { return c - 48 } }
548 if c >= 65 { if c <= 70 { return c - 55 } }
549 if c >= 97 { if c <= 102 { return c - 87 } }
550 return 0 - 1
551}
552func vf_hex4(src: *u8, at: i64) -> i64 {
553 var v: i64 = 0
554 var i: i64 = 0
555 while i < VF_HEX_DIGITS {
556 let h: i64 = vf_hexval(src[at+i] as i64 & 0xff)
557 if h < 0 { return 0 - 1 }
558 v = v * VF_HEX_BASE + h
559 i = i + 1
560 }
561 return v
562}
563func vf_utf8(out: *u8, o: i64, cp: i64) -> i64 {
564 if cp < VF_UTF8_MAX1 { out[o] = cp as u8; return o + 1 }
565 if cp < VF_UTF8_MAX2 {
566 out[o] = (VF_UTF8_LEAD2 + (cp / VF_UTF8_SEXTET)) as u8
567 out[o+1] = (VF_UTF8_CONT + (cp % VF_UTF8_SEXTET)) as u8
568 return o + 2
569 }
570 if cp < VF_UTF8_MAX3 {
571 out[o] = (VF_UTF8_LEAD3 + (cp / (VF_UTF8_SEXTET * VF_UTF8_SEXTET))) as u8
572 out[o+1] = (VF_UTF8_CONT + ((cp / VF_UTF8_SEXTET) % VF_UTF8_SEXTET)) as u8
573 out[o+2] = (VF_UTF8_CONT + (cp % VF_UTF8_SEXTET)) as u8
574 return o + 3
575 }
576 out[o] = (VF_UTF8_LEAD4 + (cp / (VF_UTF8_SEXTET * VF_UTF8_SEXTET * VF_UTF8_SEXTET))) as u8
577 out[o+1] = (VF_UTF8_CONT + ((cp / (VF_UTF8_SEXTET * VF_UTF8_SEXTET)) % VF_UTF8_SEXTET)) as u8
578 out[o+2] = (VF_UTF8_CONT + ((cp / VF_UTF8_SEXTET) % VF_UTF8_SEXTET)) as u8
579 out[o+3] = (VF_UTF8_CONT + (cp % VF_UTF8_SEXTET)) as u8
580 return o + 4
581}
582
583// decode the string CONTENT span src[s..e) (inside the quotes) into out. The caller sizes out at e - s + 1: a decoded
584// string is never longer than its escaped span (\uXXXX is 6 bytes in and at most 3 out; a surrogate pair 12 in, 4 out).
585// A decoded TAB, CR or LF -- and any raw control byte -- becomes a space, so a value can never tear a TSV row.
586// Returns the decoded length, or -1 on a malformed escape.
587func vf_json_decode(src: *u8, s: i64, e: i64, out: *u8) -> i64 {
588 var i: i64 = s
589 var o: i64 = 0
590 while i < e {
591 let c: i64 = src[i] as i64 & 0xff
592 if c != VF_J_BSL {
593 if c < VF_J_SPACE { out[o] = VF_J_SPACE as u8 } else { out[o] = c as u8 }
594 o = o + 1
595 i = i + 1
596 } else {
597 if i + 1 >= e { return 0 - 1 }
598 let x: i64 = src[i+1] as i64 & 0xff
599 var cp: i64 = 0 - 1
600 var adv: i64 = 2
601 if x == VF_J_QUOTE { cp = VF_J_QUOTE }
602 if x == VF_J_BSL { cp = VF_J_BSL }
603 if x == 47 { cp = 47 }
604 if x == 98 { cp = VF_J_SPACE }
605 if x == 102 { cp = VF_J_SPACE }
606 if x == 110 { cp = VF_J_SPACE }
607 if x == 114 { cp = VF_J_SPACE }
608 if x == 116 { cp = VF_J_SPACE }
609 if x == 117 {
610 if i + 2 + VF_HEX_DIGITS > e { return 0 - 1 }
611 cp = vf_hex4(src, i + 2)
612 if cp < 0 { return 0 - 1 }
613 adv = 2 + VF_HEX_DIGITS
614 if cp >= VF_SURR_HI { if cp < VF_SURR_LO {
615 if i + 4 + 2 * VF_HEX_DIGITS > e { return 0 - 1 }
616 if (src[i+6] as i64 & 0xff) != VF_J_BSL { return 0 - 1 }
617 if (src[i+7] as i64 & 0xff) != 117 { return 0 - 1 }
618 let lo: i64 = vf_hex4(src, i + 8)
619 if lo < VF_SURR_LO { return 0 - 1 }
620 if lo >= VF_SURR_END { return 0 - 1 }
621 cp = VF_SURR_BASE + (cp - VF_SURR_HI) * VF_SURR_SPAN + (lo - VF_SURR_LO)
622 adv = 4 + 2 * VF_HEX_DIGITS
623 } }
624 if cp >= VF_SURR_LO { if cp < VF_SURR_END { return 0 - 1 } }
625 if cp < VF_J_SPACE { cp = VF_J_SPACE }
626 }
627 if cp < 0 { return 0 - 1 }
628 o = vf_utf8(out, o, cp)
629 i = i + adv
630 }
631 }
632 out[o] = 0 as u8
633 return o
634}
635
636// decode the DIRECT string member `key` of the object at obj into a fresh buffer sized from its own span.
637// Returns the buffer (NUL-terminated) or 0 when absent, not a string, or malformed; len[0] = decoded length or -1.
638func vf_json_get_str(src: *u8, n: i64, obj: i64, key: *u8, len: *i64) -> *u8 {
639 len[0] = 0 - 1
640 let box: *i64 = sys_mmap(VF_JM_N * 8) as *i64
641 let vs: i64 = vf_json_find(src, n, obj, key, box)
642 if vs < 0 { return 0 as *u8 }
643 if vf_json_kind(src, n, vs) != VF_JK_STRING { return 0 as *u8 }
644 let ve: i64 = box[VF_JM_VE]
645 let out: *u8 = sys_mmap(ve - vs + 1)
646 let d: i64 = vf_json_decode(src, vs + 1, ve - 1, out)
647 if d < 0 { return 0 as *u8 }
648 len[0] = d
649 return out
650}
651
652// extract the declared licenseType from a meta.json BODY (already read from the archive; method 0
653// = as-is, method 8 = raw deflate via the incumbent core). Writes a NUL-terminated label (max 40
654// chars, whitelist alphabet) or "unparsed". The body is the package DESCRIBING itself -- the one
655// member this lane ever opens.
656func vf_license(body: *u8, csize: i64, method: i64, usize: i64, out: *u8) -> i64 {
657 var src: *u8 = body
658 var n: i64 = csize
659 if method == 8 {
660 var cap: i64 = usize + 64
661 if cap < VF_MAGIC_1024 { cap = VF_MAGIC_1024 }
662 if cap > VF_MAGIC_4194304 { out[0] = 0 as u8; return 0 - 1 }
663 let dr: *NxDeflateResult = nx_deflate_inflate(body, csize, cap)
664 if (dr as i64) == 0 { out[0] = 0 as u8; return 0 - 1 }
665 if dr.error_code != 0 { out[0] = 0 as u8; return 0 - 1 }
666 src = dr.output_data
667 n = dr.output_size
668 } else { if method != 0 { out[0] = 0 as u8; return 0 - 1 } }
669 // find "licenseType" then the next quoted string
670 let key: *u8 = "licenseType" as *u8
671 var at: i64 = 0 - 1
672 var i: i64 = 0
673 while i + 11 <= n {
674 var k: i64 = 0
675 while k < 11 { if (src[i+k] as i64) != (key[k] as i64) { k = 20 } else { k = k + 1 } }
676 if k == 11 { at = i + 11; i = n + 1 } else { i = i + 1 }
677 }
678 if at < 0 { out[0] = 0 as u8; return 0 - 1 }
679 // the value: first ':' after the key, then the first '"' after that opens it
680 var colon: i64 = 0 - 1
681 i = at
682 while i < n { if (src[i] as i64) == 58 { colon = i; i = n + 1 } else { i = i + 1 } }
683 if colon < 0 { out[0] = 0 as u8; return 0 - 1 }
684 var vq: i64 = 0 - 1
685 i = colon + 1
686 while i < n { if (src[i] as i64) == 34 { vq = i + 1; i = n + 1 } else { i = i + 1 } }
687 if vq < 0 { out[0] = 0 as u8; return 0 - 1 }
688 var p: i64 = 0
689 i = vq
690 while i < n {
691 let c2: i64 = src[i] as i64
692 if c2 == 34 { out[p] = 0 as u8; return p }
693 var ok: i64 = 0
694 if c2 >= 48 { if c2 <= 57 { ok = 1 } }
695 if c2 >= 65 { if c2 <= 90 { ok = 1 } }
696 if c2 >= 97 { if c2 <= 122 { ok = 1 } }
697 if c2 == 32 { ok = 1 }
698 if c2 == 45 { ok = 1 }
699 if c2 == 46 { ok = 1 }
700 if ok == 0 { out[0] = 0 as u8; return 0 - 1 }
701 if p >= 40 { out[0] = 0 as u8; return 0 - 1 }
702 out[p] = c2 as u8
703 p = p + 1
704 i = i + 1
705 }
706 out[0] = 0 as u8
707 return 0 - 1
708}