nx_nexus_fetch.nx source
↩ module page · 517 lines · 26383 B
1// nx_nexus_fetch.nx -- THE NEXUS MODS API DOOR (/compare/modding MD28, 2026-09-05): the modder's OWN API key, from the
2// sovereign vault, over our own TLS, against api.nexusmods.com -- never a scraper through the mod page's Cloudflare
3// challenge (measured 2026-09-05: nexusmods.com/skyrimspecialedition/mods/54994 answers the sovereign fetcher 403
4// Cf-Mitigated challenge, 8,279 B), never a bundled third-party client.
5// THE FETCH LADDER IS THE SPINE'S OWN: nx_https_fetch_follow_hdr_best -- minimal TLS-1.3 hello, then the Chrome-JA3
6// hello, then TLS-1.2 -- with the api header injected into every 1.3 request. MEASURED 2026-09-06: the single-hello
7// path (nx_https_fetch_hdr) completed the handshake with api.nexusmods.com and received ZERO application bytes
8// (nrecs=1), which is what a fingerprint-gated CDN does to a bare client; the ladder exists for exactly that host class.
9// THE KEY NEVER TOUCHES ARGV, STDOUT, A JOURNAL OR A LOG: it is opened by the estate's secret CLI
10// (nx_secret get nexus_api -> /tmp/nxsecret.out, machine-bound AES-128-GCM vault), read once, used to build one request
11// header, then the buffers are zeroed and the plaintext file is overwritten and unlinked. validate.json ECHOES the key in
12// its body, so that body is never printed -- only the fields named below.
13// usage: nx_nexus_fetch validate
14// nx_nexus_fetch info <game_domain> <mod_id>
15// nx_nexus_fetch files <game_domain> <mod_id>
16// nx_nexus_fetch link <game_domain> <mod_id> <file_id> [nxm_key nxm_expires]
17// nx_nexus_fetch fetch <game_domain> <mod_id> <file_id> <out-path> [nxm_key nxm_expires]
18// exits: 0 ok | 2 usage | 3 NO-KEY (the vault could not open nexus_api) | 4 NET (the ladder returned nothing: bad url,
19// trust, connect or every hello refused) | 5 HTTP (a status other than 200 -- the body's first bytes are printed,
20// they carry the API's own reason) | 6 UNPARSED (200 but the named field is absent -- printed as absent, never
21// invented)
22// Every receipt is one line per record with key=value fields; the download receipt carries the sha256 of the bytes it
23// wrote, sized from the file's DECLARED size_kb (an output buffer derived from the input, never a guessed cap).
24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
25import "nx_syscalls.nx"
26import "nx_x509_trust_store.nx"
27import "nx_trust_store_load_from_certdata.nx"
28import "nx_https_fetch_follow.nx"
29import "nx_tool_run.nx"
30import "nx_media_state.nx"
31import "nx_sha256.nx"
32
33const NF_API: *u8 = "https://api.nexusmods.com/v1/"
34const NF_SECRET_CLI: *u8 = "_offc/nx_secret_cli.elf"
35const NF_SECRET_NAME: *u8 = "nexus_api"
36const NF_SECRET_OUT: *u8 = "/tmp/nxsecret.out"
37const NF_CERTDATA: *u8 = "data/mozilla_certdata.txt"
38const NF_TRUST_MAX_CERTS: i64 = 512 // the fetch spine's own trust-store load parameters (nx_https_fetch.nx)
39const NF_TRUST_RESERVE: i64 = 4194304
40const NF_MAX_HOPS: i64 = 5
41const NF_OUT_CAP: i64 = 4194304 // one API body; the spine REFUSES a body that does not fit, it never truncates silently
42const NF_URL_CAP: i64 = 2048
43const NF_HDR_CAP: i64 = 2048
44const NF_KEY_CAP: i64 = 512
45const NF_CLI_CAP: i64 = 8192
46const NF_CLI_TMO_MS: i64 = 180000 // the machine-key derive is argon2id at 32 MiB, measured ~40 s on the NAS under load; a hang guard
47const NF_VAL_CAP: i64 = 65536
48const NF_SNIP: i64 = 300
49const NF_DL_HEADROOM: i64 = 1048576 // headers plus rounding above the declared size
50const NF_MODE_0644: i64 = 420
51const NF_EXIT_USAGE: i64 = 2
52const NF_EXIT_NOKEY: i64 = 3
53const NF_EXIT_NET: i64 = 4
54const NF_EXIT_HTTP: i64 = 5
55const NF_EXIT_UNPARSED: i64 = 6
56const NF_HTTP_OK: i64 = 200
57
58func nf_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
59func nf_puts(s: *u8) -> i64 { sys_write(1, s, nf_slen(s)); return 0 }
60func nf_putn(v: i64) -> i64 {
61 let t: *u8 = sys_mmap(32)
62 var m: i64 = v
63 var w: i64 = 0
64 if m < 0 { t[0] = 45 as u8; sys_write(1, t, 1); m = 0 - m }
65 if m == 0 { t[0] = 48 as u8; sys_write(1, t, 1); return 0 }
66 let d: *u8 = sys_mmap(32)
67 var k: i64 = 0
68 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
69 var j: i64 = 0
70 while j < k { t[w] = d[k - 1 - j]; w = w + 1; j = j + 1 }
71 sys_write(1, t, w)
72 return 0
73}
74func nf_streq(a: *u8, b: *u8) -> i64 {
75 var i: i64 = 0
76 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
77 if b[i] != (0 as u8) { return 0 }
78 return 1
79}
80func nf_cat(dst: *u8, off: i64, s: *u8, cap: i64) -> i64 {
81 var o: i64 = off
82 var i: i64 = 0
83 while s[i] != (0 as u8) { if o < cap - 1 { dst[o] = s[i]; o = o + 1 } i = i + 1 }
84 dst[o] = 0 as u8
85 return o
86}
87func nf_catn(dst: *u8, off: i64, s: *u8, n: i64, cap: i64) -> i64 {
88 var o: i64 = off
89 var i: i64 = 0
90 while i < n { if o < cap - 1 { dst[o] = s[i]; o = o + 1 } i = i + 1 }
91 dst[o] = 0 as u8
92 return o
93}
94func nf_crlf(dst: *u8, off: i64, cap: i64) -> i64 {
95 var o: i64 = off
96 if o < cap - 2 { dst[o] = 13 as u8; dst[o+1] = 10 as u8; o = o + 2 }
97 dst[o] = 0 as u8
98 return o
99}
100func nf_zero(b: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { b[i] = 0 as u8; i = i + 1 } return 0 }
101func nf_unlink(path: *u8) -> i64 { __syscall(263, AT_FDCWD, path, 0, 0, 0, 0) return 0 }
102func nf_is_digit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 }
103
104// ---- the key: opened by the vault CLI, read once, shredded ----------------------------------------------
105// returns the key length, or -1 with the refusal already printed
106func nf_key(out: *u8, cap: i64) -> i64 {
107 let av: *i64 = sys_mmap(8 * 4) as *i64
108 av[0] = NF_SECRET_CLI as i64; av[1] = "get" as *u8 as i64; av[2] = NF_SECRET_NAME as i64; av[3] = 0
109 let cout: *u8 = sys_mmap(NF_CLI_CAP)
110 let clen: *i64 = sys_mmap(16) as *i64
111 let rc: i64 = tr_run_capture_to(NF_SECRET_CLI, av, cout, NF_CLI_CAP, clen, NF_CLI_TMO_MS)
112 let lp: *i64 = sys_mmap(16) as *i64
113 let raw: *u8 = sys_read_file(NF_SECRET_OUT, lp)
114 if (raw as i64) == 0 {
115 nf_puts("NEXUS-REFUSE no-key: the vault did not open nexus_api (nx_secret get rc=" as *u8); nf_putn(rc)
116 nf_puts(") -- seal it with nx_secret put nexus_api on the NAS; the CLI said: " as *u8)
117 sys_write(1, cout, clen[0]); nf_puts("\n" as *u8)
118 return 0 - 1
119 }
120 var n: i64 = lp[0]
121 var s: i64 = 0
122 // a UTF-8 BOM and trailing CR/LF/space are transport artefacts, never key bytes (the porkbun readers strip the same)
123 if n >= 3 { if (raw[0] & 0xff) as i64 == 0xef { if (raw[1] & 0xff) as i64 == 0xbb { if (raw[2] & 0xff) as i64 == 0xbf { s = 3 } } } }
124 var e: i64 = n
125 var trim: i64 = 1
126 while trim == 1 {
127 if e <= s { trim = 0 }
128 else {
129 let c: i64 = (raw[e-1] & 0xff) as i64
130 if c == 10 { e = e - 1 } else { if c == 13 { e = e - 1 } else { if c == 32 { e = e - 1 } else { trim = 0 } } }
131 }
132 }
133 let kl: i64 = e - s
134 if kl <= 0 { nf_puts("NEXUS-REFUSE no-key: the vault opened an EMPTY nexus_api\n" as *u8); nf_zero(raw, n); nf_unlink(NF_SECRET_OUT); return 0 - 1 }
135 if kl >= cap { nf_puts("NEXUS-REFUSE no-key: nexus_api longer than the key buffer\n" as *u8); nf_zero(raw, n); nf_unlink(NF_SECRET_OUT); return 0 - 1 }
136 var i: i64 = 0
137 while i < kl { out[i] = raw[s + i]; i = i + 1 }
138 out[kl] = 0 as u8
139 // shred: overwrite the plaintext file in place, then unlink it, then zero the mapping
140 let fd: i64 = sys_openat_wr(NF_SECRET_OUT, 384)
141 if fd >= 0 { let z: *u8 = sys_mmap(n + 16); sys_write(fd, z, n); sys_close(fd) }
142 nf_unlink(NF_SECRET_OUT)
143 nf_zero(raw, n)
144 return kl
145}
146
147// ---- one authenticated GET against the API through the spine's full hello ladder ----------------------
148// returns the BODY length (the ladder hands back body bytes only) or the spine's negative code; st[0] = HTTP status
149func nf_get(store: *TrustStore, tail: *u8, key: *u8, klen: i64, out: *u8, cap: i64, st: *i64) -> i64 {
150 let url: *u8 = sys_mmap(NF_URL_CAP)
151 var u: i64 = nf_cat(url, 0, NF_API, NF_URL_CAP)
152 u = nf_cat(url, u, tail, NF_URL_CAP)
153 let h: *u8 = sys_mmap(NF_HDR_CAP)
154 var o: i64 = nf_cat(h, 0, "apikey: " as *u8, NF_HDR_CAP)
155 o = nf_catn(h, o, key, klen, NF_HDR_CAP)
156 o = nf_crlf(h, o, NF_HDR_CAP)
157 o = nf_cat(h, o, "Accept: application/json" as *u8, NF_HDR_CAP)
158 o = nf_crlf(h, o, NF_HDR_CAP)
159 o = nf_cat(h, o, "Application-Name: nishi-estate" as *u8, NF_HDR_CAP)
160 o = nf_crlf(h, o, NF_HDR_CAP)
161 o = nf_cat(h, o, "Application-Version: 1" as *u8, NF_HDR_CAP)
162 o = nf_crlf(h, o, NF_HDR_CAP)
163 st[0] = 0
164 let n: i64 = nx_https_fetch_follow_hdr_best(url, store, out, cap, NF_MAX_HOPS, st, h, o)
165 nf_zero(h, NF_HDR_CAP)
166 return n
167}
168
169// ---- response reading ------------------------------------------------------------------------------------
170// the value of "<key>": inside obj[0..n) -- a string (unescaped, quotes removed) or a bare number/true/false/null.
171// returns the value length, or -1 when the key is absent (the caller prints ABSENT, never a default)
172func nf_json_field(obj: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 {
173 let needle: *u8 = sys_mmap(256)
174 var q: i64 = 0
175 needle[q] = 34 as u8; q = q + 1
176 q = nf_cat(needle, q, key, 256)
177 if q < 254 { needle[q] = 34 as u8; needle[q+1] = 58 as u8; needle[q+2] = 0 as u8; q = q + 2 }
178 let p: i64 = ms_find(obj, n, needle, q, 0)
179 if p < 0 { out[0] = 0 as u8; return 0 - 1 }
180 var i: i64 = p + q
181 while i < n { if obj[i] == (32 as u8) { i = i + 1 } else { break } }
182 var w: i64 = 0
183 if i < n { if obj[i] == (34 as u8) {
184 i = i + 1
185 var go: i64 = 1
186 while go == 1 {
187 if i >= n { go = 0 }
188 else {
189 let c: i64 = (obj[i] & 0xff) as i64
190 if c == 92 { if i + 1 < n { if w < cap - 1 { out[w] = obj[i+1]; w = w + 1 } } i = i + 2 }
191 else { if c == 34 { go = 0 } else { if w < cap - 1 { out[w] = obj[i]; w = w + 1 } i = i + 1 } }
192 }
193 }
194 out[w] = 0 as u8
195 return w
196 } }
197 while i < n {
198 let c: i64 = (obj[i] & 0xff) as i64
199 if c == 44 { break }
200 if c == 125 { break }
201 if c == 93 { break }
202 if c == 32 { break }
203 if c == 13 { break }
204 if c == 10 { break }
205 if w < cap - 1 { out[w] = obj[i]; w = w + 1 }
206 i = i + 1
207 }
208 out[w] = 0 as u8
209 return w
210}
211func nf_field_out(obj: *u8, n: i64, key: *u8, label: *u8, val: *u8, cap: i64) -> i64 {
212 nf_puts(" " as *u8); nf_puts(label); nf_puts("=" as *u8)
213 let l: i64 = nf_json_field(obj, n, key, val, cap)
214 if l < 0 { nf_puts("ABSENT" as *u8); return 0 }
215 // one line per record: a value that carries a newline would break the receipt, so it is folded to a space
216 var i: i64 = 0
217 while i < l { if val[i] == (10 as u8) { val[i] = 32 as u8 } if val[i] == (13 as u8) { val[i] = 32 as u8 } i = i + 1 }
218 sys_write(1, val, l)
219 return 1
220}
221// the byte offset of the [ that opens the array named <key>, or -1
222func nf_array_start(body: *u8, n: i64, key: *u8) -> i64 {
223 let needle: *u8 = sys_mmap(256)
224 var q: i64 = 0
225 needle[q] = 34 as u8; q = q + 1
226 q = nf_cat(needle, q, key, 256)
227 if q < 254 { needle[q] = 34 as u8; needle[q+1] = 58 as u8; needle[q+2] = 0 as u8; q = q + 2 }
228 let p: i64 = ms_find(body, n, needle, q, 0)
229 if p < 0 { return 0 - 1 }
230 var i: i64 = p + q
231 while i < n { if body[i] == (91 as u8) { return i } if body[i] == (32 as u8) { i = i + 1 } else { return 0 - 1 } }
232 return 0 - 1
233}
234
235func nf_http_refuse(body: *u8, n: i64, st: i64) -> i64 {
236 nf_puts("NEXUS-HTTP status=" as *u8); nf_putn(st); nf_puts(" bytes=" as *u8); nf_putn(n)
237 var l: i64 = n
238 if l > NF_SNIP { l = NF_SNIP }
239 if l > 0 { nf_puts(" body=" as *u8); sys_write(1, body, l) }
240 nf_puts("\n" as *u8)
241 return NF_EXIT_HTTP
242}
243
244// one call: 0 with the body in resp[0..lenbox[0]) and status 200, or the exit code with the refusal printed
245func nf_call(store: *TrustStore, tail: *u8, key: *u8, klen: i64, resp: *u8, cap: i64, lenbox: *i64) -> i64 {
246 let st: *i64 = sys_mmap(16) as *i64
247 let n: i64 = nf_get(store, tail, key, klen, resp, cap, st)
248 if n <= 0 {
249 nf_puts("NEXUS-REFUSE net rc=" as *u8); nf_putn(n); nf_puts(" status=" as *u8); nf_putn(st[0])
250 nf_puts(" (the spine's hello ladder returned no body: bad url, trust, connect, or every hello refused) url=" as *u8)
251 nf_puts(NF_API); nf_puts(tail); nf_puts("\n" as *u8)
252 return NF_EXIT_NET
253 }
254 lenbox[0] = n
255 if st[0] != NF_HTTP_OK { return nf_http_refuse(resp, n, st[0]) }
256 return 0
257}
258
259func nf_tail3(dst: *u8, game: *u8, modid: *u8, suffix: *u8) -> i64 {
260 var o: i64 = nf_cat(dst, 0, "games/" as *u8, NF_URL_CAP)
261 o = nf_cat(dst, o, game, NF_URL_CAP)
262 o = nf_cat(dst, o, "/mods/" as *u8, NF_URL_CAP)
263 o = nf_cat(dst, o, modid, NF_URL_CAP)
264 o = nf_cat(dst, o, suffix, NF_URL_CAP)
265 return o
266}
267
268func nf_validate(store: *TrustStore, key: *u8, klen: i64) -> i64 {
269 let resp: *u8 = sys_mmap(NF_OUT_CAP)
270 let lb: *i64 = sys_mmap(16) as *i64
271 let rc: i64 = nf_call(store, "users/validate.json" as *u8, key, klen, resp, NF_OUT_CAP, lb)
272 if rc != 0 { return rc }
273 let n: i64 = lb[0]
274 let val: *u8 = sys_mmap(NF_VAL_CAP)
275 // the body ECHOES the key: only these fields leave this process
276 nf_puts("NEXUS-VALIDATE status=200" as *u8)
277 let have: i64 = nf_field_out(resp, n, "user_id" as *u8, "user_id" as *u8, val, NF_VAL_CAP)
278 nf_field_out(resp, n, "name" as *u8, "name" as *u8, val, NF_VAL_CAP)
279 nf_field_out(resp, n, "is_premium" as *u8, "is_premium" as *u8, val, NF_VAL_CAP)
280 nf_field_out(resp, n, "is_supporter" as *u8, "is_supporter" as *u8, val, NF_VAL_CAP)
281 nf_puts("\n" as *u8)
282 nf_zero(resp, n)
283 if have == 0 { nf_puts("NEXUS-UNPARSED validate: the 200 body carries no user_id field\n" as *u8); return NF_EXIT_UNPARSED }
284 return 0
285}
286
287func nf_info(store: *TrustStore, game: *u8, modid: *u8, key: *u8, klen: i64) -> i64 {
288 let tail: *u8 = sys_mmap(NF_URL_CAP)
289 nf_tail3(tail, game, modid, ".json" as *u8)
290 let resp: *u8 = sys_mmap(NF_OUT_CAP)
291 let lb: *i64 = sys_mmap(16) as *i64
292 let rc: i64 = nf_call(store, tail, key, klen, resp, NF_OUT_CAP, lb)
293 if rc != 0 { return rc }
294 let n: i64 = lb[0]
295 let val: *u8 = sys_mmap(NF_VAL_CAP)
296 nf_puts("NEXUS-INFO status=200 game=" as *u8); nf_puts(game); nf_puts(" mod_id=" as *u8); nf_puts(modid)
297 let have: i64 = nf_field_out(resp, n, "name" as *u8, "name" as *u8, val, NF_VAL_CAP)
298 nf_field_out(resp, n, "version" as *u8, "version" as *u8, val, NF_VAL_CAP)
299 nf_field_out(resp, n, "author" as *u8, "author" as *u8, val, NF_VAL_CAP)
300 nf_field_out(resp, n, "uploaded_by" as *u8, "uploaded_by" as *u8, val, NF_VAL_CAP)
301 nf_field_out(resp, n, "mod_downloads" as *u8, "mod_downloads" as *u8, val, NF_VAL_CAP)
302 nf_field_out(resp, n, "mod_unique_downloads" as *u8, "unique_downloads" as *u8, val, NF_VAL_CAP)
303 nf_field_out(resp, n, "endorsement_count" as *u8, "endorsements" as *u8, val, NF_VAL_CAP)
304 nf_field_out(resp, n, "contains_adult_content" as *u8, "adult" as *u8, val, NF_VAL_CAP)
305 nf_field_out(resp, n, "status" as *u8, "mod_status" as *u8, val, NF_VAL_CAP)
306 nf_field_out(resp, n, "available" as *u8, "available" as *u8, val, NF_VAL_CAP)
307 nf_field_out(resp, n, "updated_time" as *u8, "updated" as *u8, val, NF_VAL_CAP)
308 nf_field_out(resp, n, "category_id" as *u8, "category_id" as *u8, val, NF_VAL_CAP)
309 nf_field_out(resp, n, "picture_url" as *u8, "picture" as *u8, val, NF_VAL_CAP)
310 nf_field_out(resp, n, "summary" as *u8, "summary" as *u8, val, NF_VAL_CAP)
311 nf_puts("\n" as *u8)
312 if have == 0 { nf_puts("NEXUS-UNPARSED info: the 200 body carries no name field\n" as *u8); return NF_EXIT_UNPARSED }
313 return 0
314}
315
316func nf_files(store: *TrustStore, game: *u8, modid: *u8, key: *u8, klen: i64, want_fid: *u8, size_kb_out: *i64) -> i64 {
317 let tail: *u8 = sys_mmap(NF_URL_CAP)
318 nf_tail3(tail, game, modid, "/files.json" as *u8)
319 let resp: *u8 = sys_mmap(NF_OUT_CAP)
320 let lb: *i64 = sys_mmap(16) as *i64
321 let rc: i64 = nf_call(store, tail, key, klen, resp, NF_OUT_CAP, lb)
322 if rc != 0 { return rc }
323 let bn: i64 = lb[0]
324 let body: *u8 = resp
325 let a: i64 = nf_array_start(body, bn, "files" as *u8)
326 if a < 0 { nf_puts("NEXUS-UNPARSED files: no files array\n" as *u8); return NF_EXIT_UNPARSED }
327 let obj: *u8 = sys_mmap(NF_OUT_CAP)
328 let val: *u8 = sys_mmap(NF_VAL_CAP)
329 var i: i64 = a + 1
330 var count: i64 = 0
331 var found: i64 = 0
332 var go: i64 = 1
333 while go == 1 {
334 while i < bn { if body[i] == (32 as u8) { i = i + 1 } else { if body[i] == (44 as u8) { i = i + 1 } else { if body[i] == (10 as u8) { i = i + 1 } else { if body[i] == (13 as u8) { i = i + 1 } else { break } } } } }
335 if i >= bn { go = 0 }
336 else { if body[i] == (93 as u8) { go = 0 }
337 else { if body[i] != (123 as u8) { go = 0 }
338 else {
339 let ol: i64 = ms_extract_json_value(body, i, bn, obj, NF_OUT_CAP)
340 if ol <= 0 { go = 0 }
341 else {
342 count = count + 1
343 let fl: i64 = nf_json_field(obj, ol, "file_id" as *u8, val, NF_VAL_CAP)
344 var thisone: i64 = 1
345 if (want_fid as i64) != 0 { thisone = 0; if fl > 0 { if nf_streq(val, want_fid) == 1 { thisone = 1; found = 1 } } }
346 if thisone == 1 {
347 nf_puts("NEXUS-FILE" as *u8)
348 nf_field_out(obj, ol, "file_id" as *u8, "file_id" as *u8, val, NF_VAL_CAP)
349 nf_field_out(obj, ol, "name" as *u8, "name" as *u8, val, NF_VAL_CAP)
350 nf_field_out(obj, ol, "version" as *u8, "version" as *u8, val, NF_VAL_CAP)
351 nf_field_out(obj, ol, "category_name" as *u8, "category" as *u8, val, NF_VAL_CAP)
352 nf_field_out(obj, ol, "is_primary" as *u8, "primary" as *u8, val, NF_VAL_CAP)
353 nf_field_out(obj, ol, "size_kb" as *u8, "size_kb" as *u8, val, NF_VAL_CAP)
354 if (size_kb_out as i64) != 0 {
355 var kb: i64 = 0
356 var d: i64 = 0
357 while val[d] != (0 as u8) { let c: i64 = (val[d] & 0xff) as i64; if nf_is_digit(c) == 1 { kb = kb * 10 + (c - 48) } d = d + 1 }
358 size_kb_out[0] = kb
359 }
360 nf_field_out(obj, ol, "file_name" as *u8, "file_name" as *u8, val, NF_VAL_CAP)
361 nf_field_out(obj, ol, "uploaded_time" as *u8, "uploaded" as *u8, val, NF_VAL_CAP)
362 nf_puts("\n" as *u8)
363 }
364 i = i + ol
365 }
366 } } }
367 }
368 nf_puts("NEXUS-FILES status=200 game=" as *u8); nf_puts(game); nf_puts(" mod_id=" as *u8); nf_puts(modid)
369 nf_puts(" count=" as *u8); nf_putn(count); nf_puts("\n" as *u8)
370 if (want_fid as i64) != 0 { if found == 0 { nf_puts("NEXUS-REFUSE file_id not in this mod's files\n" as *u8); return NF_EXIT_UNPARSED } }
371 if count == 0 { return NF_EXIT_UNPARSED }
372 return 0
373}
374
375// the CDN URI for one file; returns 0 with the first URI in uri, or a nonzero exit
376func nf_link(store: *TrustStore, game: *u8, modid: *u8, fid: *u8, nxm_key: *u8, nxm_exp: *u8, key: *u8, klen: i64, uri: *u8, ucap: i64, quiet: i64) -> i64 {
377 let tail: *u8 = sys_mmap(NF_URL_CAP)
378 var o: i64 = nf_tail3(tail, game, modid, "/files/" as *u8)
379 o = nf_cat(tail, o, fid, NF_URL_CAP)
380 o = nf_cat(tail, o, "/download_link.json" as *u8, NF_URL_CAP)
381 if (nxm_key as i64) != 0 {
382 o = nf_cat(tail, o, "?key=" as *u8, NF_URL_CAP)
383 o = nf_cat(tail, o, nxm_key, NF_URL_CAP)
384 o = nf_cat(tail, o, "&expires=" as *u8, NF_URL_CAP)
385 o = nf_cat(tail, o, nxm_exp, NF_URL_CAP)
386 }
387 let resp: *u8 = sys_mmap(NF_OUT_CAP)
388 let lb: *i64 = sys_mmap(16) as *i64
389 let rc: i64 = nf_call(store, tail, key, klen, resp, NF_OUT_CAP, lb)
390 if rc != 0 { return rc }
391 let bn: i64 = lb[0]
392 let body: *u8 = resp
393 let obj: *u8 = sys_mmap(NF_OUT_CAP)
394 let val: *u8 = sys_mmap(NF_VAL_CAP)
395 var i: i64 = 0
396 while i < bn { if body[i] == (91 as u8) { break } i = i + 1 }
397 if i >= bn { nf_puts("NEXUS-UNPARSED link: no array\n" as *u8); return NF_EXIT_UNPARSED }
398 i = i + 1
399 var count: i64 = 0
400 var ul: i64 = 0
401 var go: i64 = 1
402 while go == 1 {
403 while i < bn { if body[i] == (32 as u8) { i = i + 1 } else { if body[i] == (44 as u8) { i = i + 1 } else { if body[i] == (10 as u8) { i = i + 1 } else { break } } } }
404 if i >= bn { go = 0 }
405 else { if body[i] != (123 as u8) { go = 0 }
406 else {
407 let ol: i64 = ms_extract_json_value(body, i, bn, obj, NF_OUT_CAP)
408 if ol <= 0 { go = 0 }
409 else {
410 count = count + 1
411 if quiet == 0 {
412 nf_puts("NEXUS-LINK-URI" as *u8)
413 nf_field_out(obj, ol, "name" as *u8, "name" as *u8, val, NF_VAL_CAP)
414 nf_field_out(obj, ol, "short_name" as *u8, "short_name" as *u8, val, NF_VAL_CAP)
415 nf_field_out(obj, ol, "URI" as *u8, "uri" as *u8, val, NF_VAL_CAP)
416 nf_puts("\n" as *u8)
417 }
418 if count == 1 { ul = nf_json_field(obj, ol, "URI" as *u8, uri, ucap) }
419 i = i + ol
420 }
421 } }
422 }
423 if quiet == 0 { nf_puts("NEXUS-LINK status=200 file_id=" as *u8); nf_puts(fid); nf_puts(" count=" as *u8); nf_putn(count); nf_puts("\n" as *u8) }
424 if ul <= 0 { nf_puts("NEXUS-UNPARSED link: no URI in the first entry\n" as *u8); return NF_EXIT_UNPARSED }
425 return 0
426}
427
428func nf_hex(dig: *u8, out: *u8) -> i64 {
429 let hx: *u8 = "0123456789abcdef" as *u8
430 var i: i64 = 0
431 while i < 32 { let v: i64 = (dig[i] & 0xff) as i64; out[i*2] = hx[v >> 4]; out[i*2+1] = hx[v & 15]; i = i + 1 }
432 out[64] = 0 as u8
433 return 64
434}
435
436func nf_fetch(store: *TrustStore, game: *u8, modid: *u8, fid: *u8, outp: *u8, nxm_key: *u8, nxm_exp: *u8, key: *u8, klen: i64) -> i64 {
437 let kbbox: *i64 = sys_mmap(16) as *i64
438 kbbox[0] = 0
439 let frc: i64 = nf_files(store, game, modid, key, klen, fid, kbbox)
440 if frc != 0 { return frc }
441 let uri: *u8 = sys_mmap(NF_URL_CAP * 4)
442 let lrc: i64 = nf_link(store, game, modid, fid, nxm_key, nxm_exp, key, klen, uri, NF_URL_CAP * 4, 1)
443 if lrc != 0 { return lrc }
444 // the output buffer is DERIVED from the declared size: twice the declared bytes plus headroom, never a picked cap
445 var cap: i64 = kbbox[0] * 1024 * 2 + NF_DL_HEADROOM
446 if kbbox[0] == 0 { cap = NF_OUT_CAP + NF_DL_HEADROOM }
447 let resp: *u8 = sys_mmap(cap)
448 let st: *i64 = sys_mmap(16) as *i64
449 st[0] = 0
450 // the CDN link carries its own token; no api header travels to it
451 let n: i64 = nx_https_fetch_follow_hdr_best(uri, store, resp, cap, NF_MAX_HOPS, st, 0 as *u8, 0)
452 if n <= 0 { nf_puts("NEXUS-REFUSE cdn net rc=" as *u8); nf_putn(n); nf_puts(" status=" as *u8); nf_putn(st[0]); nf_puts(" declared_kb=" as *u8); nf_putn(kbbox[0]); nf_puts("\n" as *u8); return NF_EXIT_NET }
453 if st[0] != NF_HTTP_OK { return nf_http_refuse(resp, n, st[0]) }
454 let bl: i64 = n
455 let fd: i64 = sys_openat_wr(outp, NF_MODE_0644)
456 if fd < 0 { nf_puts("NEXUS-REFUSE cannot open the output path for write\n" as *u8); return NF_EXIT_NET }
457 var done: i64 = 0
458 while done < bl { let k: i64 = sys_write(fd, resp + done, bl - done); if k <= 0 { break } done = done + k }
459 sys_close(fd)
460 var c: Sha256
461 sha256_init(&c)
462 sha256_update(&c, resp, bl)
463 let dig: *u8 = sys_mmap(32)
464 sha256_final(&c, dig)
465 let hex: *u8 = sys_mmap(72)
466 nf_hex(dig, hex)
467 nf_puts("NEXUS-FETCH status=200 file_id=" as *u8); nf_puts(fid)
468 nf_puts(" declared_kb=" as *u8); nf_putn(kbbox[0])
469 nf_puts(" bytes=" as *u8); nf_putn(bl)
470 nf_puts(" wrote=" as *u8); nf_putn(done)
471 nf_puts(" sha256=" as *u8); nf_puts(hex)
472 nf_puts(" out=" as *u8); nf_puts(outp); nf_puts("\n" as *u8)
473 if done != bl { nf_puts("NEXUS-REFUSE short write on the output path\n" as *u8); return NF_EXIT_NET }
474 return 0
475}
476
477func nf_usage() -> i64 {
478 nf_puts("usage: nx_nexus_fetch validate | info <game> <mod_id> | files <game> <mod_id> | link <game> <mod_id> <file_id> [nxm_key nxm_expires] | fetch <game> <mod_id> <file_id> <out> [nxm_key nxm_expires]\n" as *u8)
479 return NF_EXIT_USAGE
480}
481
482func main(argc: i64, argv: *i64) -> i64 {
483 if argc < 2 { return nf_usage() }
484 let verb: *u8 = argv[1] as *u8
485 let tr: i64 = nx_trust_store_load_from_certdata(NF_CERTDATA, NF_TRUST_MAX_CERTS, NF_TRUST_RESERVE)
486 if tr <= 0 { nf_puts("NEXUS-REFUSE trust-store: cannot load " as *u8); nf_puts(NF_CERTDATA); nf_puts(" from this CWD\n" as *u8); return NF_EXIT_NET }
487 let store: *TrustStore = tr as *TrustStore
488 let key: *u8 = sys_mmap(NF_KEY_CAP)
489 let klen: i64 = nf_key(key, NF_KEY_CAP)
490 if klen < 0 { return NF_EXIT_NOKEY }
491 var rc: i64 = NF_EXIT_USAGE
492 if nf_streq(verb, "validate" as *u8) == 1 { rc = nf_validate(store, key, klen) }
493 else { if nf_streq(verb, "info" as *u8) == 1 { if argc < 4 { rc = nf_usage() } else { rc = nf_info(store, argv[2] as *u8, argv[3] as *u8, key, klen) } }
494 else { if nf_streq(verb, "files" as *u8) == 1 { if argc < 4 { rc = nf_usage() } else { rc = nf_files(store, argv[2] as *u8, argv[3] as *u8, key, klen, 0 as *u8, 0 as *i64) } }
495 else { if nf_streq(verb, "link" as *u8) == 1 {
496 if argc < 5 { rc = nf_usage() }
497 else {
498 var nk: *u8 = 0 as *u8
499 var ne: *u8 = 0 as *u8
500 if argc >= 7 { nk = argv[5] as *u8; ne = argv[6] as *u8 }
501 let uri: *u8 = sys_mmap(NF_URL_CAP * 4)
502 rc = nf_link(store, argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, nk, ne, key, klen, uri, NF_URL_CAP * 4, 0)
503 }
504 }
505 else { if nf_streq(verb, "fetch" as *u8) == 1 {
506 if argc < 6 { rc = nf_usage() }
507 else {
508 var nk: *u8 = 0 as *u8
509 var ne: *u8 = 0 as *u8
510 if argc >= 8 { nk = argv[6] as *u8; ne = argv[7] as *u8 }
511 rc = nf_fetch(store, argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, nk, ne, key, klen)
512 }
513 }
514 else { rc = nf_usage() } } } } }
515 nf_zero(key, NF_KEY_CAP)
516 return rc
517}