code wiki / (root) / nx_nexus_fetch.nx

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}