code wiki / (root) / nx_game_manifest.nx

nx_game_manifest.nx source

↩ module page · 481 lines · 17371 B

1// nx_game_manifest.nx -- sovereign game-update manifest generator 2// (Steam depot-manifest / itch wharf equivalent, sovereign). 3// 4// Reads content/games.versions, hashes each game's served wasm with 5// SHA-256 (FIPS 180-4, inlined from the canonical nx_sha256.nx -- the 6// crypto organs import the RV64 nx_syscalls.nx which collides with the 7// x86_64 file-I/O layer, so we inline the pure core; see 8// [[nishi-serving-syscall-dup-gotcha]]), and emits: 9// dist/api/games.json -- the registry (all games + latest + hash) 10// dist/api/games/<id>.json -- per-game manifest (versions + channels + 11// per-version sha256 + bytes + changelog) 12// The in-browser auto-update client (/assets/nx-update.js) channels off 13// these. SHA-256 already EXCEEDS Steam's SHA-1 depot hashes; Ed25519 14// signing is the next exceed rung. A known-answer test (sha256("abc")) 15// runs first -- a wrong hash aborts rather than publishing bad integrity. 16// 17// Build: ./nxc2.exe --target x86_64 runtime/nx_game_manifest.nx > /tmp/gm.s 18// wsl bash -c "gcc -no-pie /tmp/gm.s -o /tmp/nx_game_manifest" 19// Run: cd nishi-pages && mkdir -p dist/api/games && /tmp/nx_game_manifest 20// 21// nx_capability_claims: 22// needs: [x86_64_syscalls, sha256_fips_180_4] 23// provides: [game_update_manifest, content_hash_integrity, 24// version_channels, publish_registry] 25// safety: [sha256_KAT_gated, no_floating_point, json_escape_changelog] 26// verdict: [exit_0_ok, exit_9x_KAT_fail] 27// license: ORIGINAL (sha256 core: INDEPENDENT_REDERIVE nx_sha256.nx) 28// kind: nishi_pages_specialist 29// layer: L4 (crypto-integrity over L1 syscalls) 30// raci: [R=nishi_pages_builder, A=elder_west, C=nx_sha256_owner, I=conductor] 31 32import "nx_syscalls_x86_64.nx" 33const K_MAGIC_65536: i64 = 65536 34const K_MAGIC_1024: i64 = 1024 35const K_MAGIC_8192: i64 = 8192 36 37// ===================== SHA-256 (inlined, pure) ===================== 38struct Sha256 { 39 h0: i64, h1: i64, h2: i64, h3: i64, 40 h4: i64, h5: i64, h6: i64, h7: i64, 41 b0: i64, b1: i64, b2: i64, b3: i64, 42 b4: i64, b5: i64, b6: i64, b7: i64, 43 idx: i64, bit_len: i64, 44 bufptr: i64, kptr: i64, wptr: i64, 45} 46const M32: i64 = 0xFFFFFFFF 47 48func sha256_k(i: i64) -> i64 { 49 if i == 0 { return 0x428a2f98 } 50 if i == 1 { return 0x71374491 } 51 if i == 2 { return 0xb5c0fbcf } 52 if i == 3 { return 0xe9b5dba5 } 53 if i == 4 { return 0x3956c25b } 54 if i == 5 { return 0x59f111f1 } 55 if i == 6 { return 0x923f82a4 } 56 if i == 7 { return 0xab1c5ed5 } 57 if i == 8 { return 0xd807aa98 } 58 if i == 9 { return 0x12835b01 } 59 if i == 10 { return 0x243185be } 60 if i == 11 { return 0x550c7dc3 } 61 if i == 12 { return 0x72be5d74 } 62 if i == 13 { return 0x80deb1fe } 63 if i == 14 { return 0x9bdc06a7 } 64 if i == 15 { return 0xc19bf174 } 65 if i == 16 { return 0xe49b69c1 } 66 if i == 17 { return 0xefbe4786 } 67 if i == 18 { return 0x0fc19dc6 } 68 if i == 19 { return 0x240ca1cc } 69 if i == 20 { return 0x2de92c6f } 70 if i == 21 { return 0x4a7484aa } 71 if i == 22 { return 0x5cb0a9dc } 72 if i == 23 { return 0x76f988da } 73 if i == 24 { return 0x983e5152 } 74 if i == 25 { return 0xa831c66d } 75 if i == 26 { return 0xb00327c8 } 76 if i == 27 { return 0xbf597fc7 } 77 if i == 28 { return 0xc6e00bf3 } 78 if i == 29 { return 0xd5a79147 } 79 if i == 30 { return 0x06ca6351 } 80 if i == 31 { return 0x14292967 } 81 if i == 32 { return 0x27b70a85 } 82 if i == 33 { return 0x2e1b2138 } 83 if i == 34 { return 0x4d2c6dfc } 84 if i == 35 { return 0x53380d13 } 85 if i == 36 { return 0x650a7354 } 86 if i == 37 { return 0x766a0abb } 87 if i == 38 { return 0x81c2c92e } 88 if i == 39 { return 0x92722c85 } 89 if i == 40 { return 0xa2bfe8a1 } 90 if i == 41 { return 0xa81a664b } 91 if i == 42 { return 0xc24b8b70 } 92 if i == 43 { return 0xc76c51a3 } 93 if i == 44 { return 0xd192e819 } 94 if i == 45 { return 0xd6990624 } 95 if i == 46 { return 0xf40e3585 } 96 if i == 47 { return 0x106aa070 } 97 if i == 48 { return 0x19a4c116 } 98 if i == 49 { return 0x1e376c08 } 99 if i == 50 { return 0x2748774c } 100 if i == 51 { return 0x34b0bcb5 } 101 if i == 52 { return 0x391c0cb3 } 102 if i == 53 { return 0x4ed8aa4a } 103 if i == 54 { return 0x5b9cca4f } 104 if i == 55 { return 0x682e6ff3 } 105 if i == 56 { return 0x748f82ee } 106 if i == 57 { return 0x78a5636f } 107 if i == 58 { return 0x84c87814 } 108 if i == 59 { return 0x8cc70208 } 109 if i == 60 { return 0x90befffa } 110 if i == 61 { return 0xa4506ceb } 111 if i == 62 { return 0xbef9a3f7 } 112 if i == 63 { return 0xc67178f2 } 113 return 0 114} 115 116func blk_byte(c: *Sha256, n: i64) -> i64 { 117 let p: *u8 = c.bufptr as *u8 118 return p[n] as i64 119} 120func blk_set_byte(c: *Sha256, n: i64, v: i64) -> i64 { 121 let p: *u8 = c.bufptr as *u8 122 p[n] = v & 0xFF 123 return 0 124} 125func blk_word(c: *Sha256, i: i64) -> i64 { 126 let off: i64 = i * 4 127 let b0: i64 = blk_byte(c, off + 0) 128 let b1: i64 = blk_byte(c, off + 1) 129 let b2: i64 = blk_byte(c, off + 2) 130 let b3: i64 = blk_byte(c, off + 3) 131 return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) & M32 132} 133 134func sha256_compress(c: *Sha256) -> i64 { 135 let w: *i64 = c.wptr as *i64 136 let k: *i64 = c.kptr as *i64 137 var i: i64 = 0 138 while i < 16 { 139 w[i] = blk_word(c, i) 140 i = i + 1 141 } 142 i = 16 143 while i < 64 { 144 let x15: i64 = w[i - 15] 145 let x2: i64 = w[i - 2] 146 let s0: i64 = (((x15 >> 7) | (x15 << 25)) ^ ((x15 >> 18) | (x15 << 14)) ^ (x15 >> 3)) & M32 147 let s1: i64 = (((x2 >> 17) | (x2 << 15)) ^ ((x2 >> 19) | (x2 << 13)) ^ (x2 >> 10)) & M32 148 w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & M32 149 i = i + 1 150 } 151 var a: i64 = c.h0 152 var b: i64 = c.h1 153 var cc: i64 = c.h2 154 var d: i64 = c.h3 155 var e: i64 = c.h4 156 var ff: i64 = c.h5 157 var g: i64 = c.h6 158 var h: i64 = c.h7 159 i = 0 160 while i < 64 { 161 let S1: i64 = (((e >> 6) | (e << 26)) ^ ((e >> 11) | (e << 21)) ^ ((e >> 25) | (e << 7))) & M32 162 let ch: i64 = ((e & ff) ^ ((e ^ M32) & g)) & M32 163 let t1: i64 = (h + S1 + ch + k[i] + w[i]) & M32 164 let S0: i64 = (((a >> 2) | (a << 30)) ^ ((a >> 13) | (a << 19)) ^ ((a >> 22) | (a << 10))) & M32 165 let mj: i64 = ((a & b) ^ (a & cc) ^ (b & cc)) & M32 166 let t2: i64 = (S0 + mj) & M32 167 h = g 168 g = ff 169 ff = e 170 e = (d + t1) & M32 171 d = cc 172 cc = b 173 b = a 174 a = (t1 + t2) & M32 175 i = i + 1 176 } 177 c.h0 = (c.h0 + a) & M32 178 c.h1 = (c.h1 + b) & M32 179 c.h2 = (c.h2 + cc) & M32 180 c.h3 = (c.h3 + d) & M32 181 c.h4 = (c.h4 + e) & M32 182 c.h5 = (c.h5 + ff) & M32 183 c.h6 = (c.h6 + g) & M32 184 c.h7 = (c.h7 + h) & M32 185 return 0 186} 187 188func sha256_init(c: *Sha256) -> i64 { 189 c.h0 = 0x6a09e667; c.h1 = 0xbb67ae85; c.h2 = 0x3c6ef372; c.h3 = 0xa54ff53a 190 c.h4 = 0x510e527f; c.h5 = 0x9b05688c; c.h6 = 0x1f83d9ab; c.h7 = 0x5be0cd19 191 c.bufptr = sys_mmap(64) as i64 192 c.kptr = sys_mmap(64 * 8) as i64 193 c.wptr = sys_mmap(64 * 8) as i64 194 let kp: *i64 = c.kptr as *i64 195 var i: i64 = 0 196 while i < 64 { kp[i] = sha256_k(i); i = i + 1 } 197 let bp: *u8 = c.bufptr as *u8 198 i = 0 199 while i < 64 { bp[i] = 0; i = i + 1 } 200 c.idx = 0 201 c.bit_len = 0 202 return 0 203} 204 205func sha256_update(c: *Sha256, bytes: *u8, n: i64) -> i64 { 206 var i: i64 = 0 207 while i < n { 208 blk_set_byte(c, c.idx, bytes[i]) 209 c.idx = c.idx + 1 210 c.bit_len = c.bit_len + 8 211 if c.idx == 64 { 212 sha256_compress(c) 213 c.idx = 0 214 } 215 i = i + 1 216 } 217 return 0 218} 219 220func sha256_final(c: *Sha256, out: *u8) -> i64 { 221 let total_bits: i64 = c.bit_len 222 blk_set_byte(c, c.idx, 0x80) 223 c.idx = c.idx + 1 224 if c.idx > 56 { 225 while c.idx < 64 { 226 blk_set_byte(c, c.idx, 0) 227 c.idx = c.idx + 1 228 } 229 sha256_compress(c) 230 c.idx = 0 231 } 232 while c.idx < 56 { 233 blk_set_byte(c, c.idx, 0) 234 c.idx = c.idx + 1 235 } 236 blk_set_byte(c, 56, (total_bits >> 56) & 0xFF) 237 blk_set_byte(c, 57, (total_bits >> 48) & 0xFF) 238 blk_set_byte(c, 58, (total_bits >> 40) & 0xFF) 239 blk_set_byte(c, 59, (total_bits >> 32) & 0xFF) 240 blk_set_byte(c, 60, (total_bits >> 24) & 0xFF) 241 blk_set_byte(c, 61, (total_bits >> 16) & 0xFF) 242 blk_set_byte(c, 62, (total_bits >> 8) & 0xFF) 243 blk_set_byte(c, 63, total_bits & 0xFF) 244 sha256_compress(c) 245 out[0] = (c.h0 >> 24) & 0xFF 246 out[1] = (c.h0 >> 16) & 0xFF 247 out[2] = (c.h0 >> 8) & 0xFF 248 out[3] = c.h0 & 0xFF 249 out[4] = (c.h1 >> 24) & 0xFF 250 out[5] = (c.h1 >> 16) & 0xFF 251 out[6] = (c.h1 >> 8) & 0xFF 252 out[7] = c.h1 & 0xFF 253 out[8] = (c.h2 >> 24) & 0xFF 254 out[9] = (c.h2 >> 16) & 0xFF 255 out[10] = (c.h2 >> 8) & 0xFF 256 out[11] = c.h2 & 0xFF 257 out[12] = (c.h3 >> 24) & 0xFF 258 out[13] = (c.h3 >> 16) & 0xFF 259 out[14] = (c.h3 >> 8) & 0xFF 260 out[15] = c.h3 & 0xFF 261 out[16] = (c.h4 >> 24) & 0xFF 262 out[17] = (c.h4 >> 16) & 0xFF 263 out[18] = (c.h4 >> 8) & 0xFF 264 out[19] = c.h4 & 0xFF 265 out[20] = (c.h5 >> 24) & 0xFF 266 out[21] = (c.h5 >> 16) & 0xFF 267 out[22] = (c.h5 >> 8) & 0xFF 268 out[23] = c.h5 & 0xFF 269 out[24] = (c.h6 >> 24) & 0xFF 270 out[25] = (c.h6 >> 16) & 0xFF 271 out[26] = (c.h6 >> 8) & 0xFF 272 out[27] = c.h6 & 0xFF 273 out[28] = (c.h7 >> 24) & 0xFF 274 out[29] = (c.h7 >> 16) & 0xFF 275 out[30] = (c.h7 >> 8) & 0xFF 276 out[31] = c.h7 & 0xFF 277 return 0 278} 279 280func sha256_digest(bytes: *u8, n: i64, out: *u8) -> i64 { 281 let ctx_raw: *u8 = sys_mmap(256) 282 let ctx: *Sha256 = ctx_raw as *Sha256 283 sha256_init(ctx) 284 sha256_update(ctx, bytes, n) 285 sha256_final(ctx, out) 286 return 0 287} 288 289// ===================== JSON / manifest helpers ===================== 290 291func mf_puts(buf: *u8, off: i64, s: *u8) -> i64 { 292 var o: i64 = off 293 var i: i64 = 0 294 while s[i] != 0 { buf[o] = s[i]; o = o + 1; i = i + 1 } 295 return o 296} 297func mf_putb(buf: *u8, off: i64, src: *u8, n: i64) -> i64 { 298 var o: i64 = off 299 var i: i64 = 0 300 while i < n { buf[o] = src[i]; o = o + 1; i = i + 1 } 301 return o 302} 303func mf_putdec(buf: *u8, off: i64, v: i64) -> i64 { 304 if v == 0 { buf[off] = 0x30; return off + 1 } 305 let tmp: *u8 = sys_mmap(32) 306 var k: i64 = 0 307 var x: i64 = v 308 while x > 0 { tmp[k] = 0x30 + (x - (x / 10) * 10); x = x / 10; k = k + 1 } 309 var o: i64 = off 310 var ri: i64 = k - 1 311 while ri >= 0 { buf[o] = tmp[ri]; o = o + 1; ri = ri - 1 } 312 return o 313} 314// 32-byte digest -> 64 lowercase hex chars. 315func mf_hex(buf: *u8, off: i64, dig: *u8) -> i64 { 316 let hx: *u8 = "0123456789abcdef" as *u8 317 var o: i64 = off 318 var i: i64 = 0 319 while i < 32 { 320 let b: i64 = dig[i] as i64 321 buf[o] = hx[(b >> 4) & 0xF]; o = o + 1 322 buf[o] = hx[b & 0xF]; o = o + 1 323 i = i + 1 324 } 325 return o 326} 327// JSON-escape " and \ in src[0..n). 328func mf_jsesc(buf: *u8, off: i64, src: *u8, n: i64) -> i64 { 329 var o: i64 = off 330 var i: i64 = 0 331 while i < n { 332 let c: i64 = src[i] as i64 333 if c == 0x22 { buf[o] = 0x5c; o = o + 1; buf[o] = 0x22; o = o + 1 } 334 else { 335 if c == 0x5c { buf[o] = 0x5c; o = o + 1; buf[o] = 0x5c; o = o + 1 } 336 else { buf[o] = src[i]; o = o + 1 } 337 } 338 i = i + 1 339 } 340 return o 341} 342func mf_split(buf: *u8, ls: i64, le: i64, foff: *i64, flen: *i64) -> i64 { 343 var fi: i64 = 0 344 var s: i64 = ls 345 var i: i64 = ls 346 while i < le { 347 if (buf[i] as i64) == 0x7c { 348 if fi < 6 { foff[fi] = s; flen[fi] = i - s; fi = fi + 1 } 349 s = i + 1 350 } 351 i = i + 1 352 } 353 if fi < 6 { foff[fi] = s; flen[fi] = le - s; fi = fi + 1 } 354 return fi 355} 356 357func main() -> i64 { 358 // --- SHA-256 known-answer test: sha256("abc") = ba7816bf... --- 359 let kat: *u8 = sys_mmap(32) 360 sha256_digest("abc" as *u8, 3, kat) 361 if (kat[0] as i64) != 0xba { return 90 } 362 if (kat[1] as i64) != 0x78 { return 91 } 363 if (kat[2] as i64) != 0x16 { return 92 } 364 if (kat[3] as i64) != 0xbf { return 93 } 365 366 let len_p: *i64 = sys_mmap(8) as *i64 367 let data: *u8 = sys_read_file_x86_64("content/games.versions" as *u8, len_p) 368 if data == (0 as *u8) { return 5 } 369 let dn: i64 = len_p[0] 370 371 let reg: *u8 = sys_mmap(K_MAGIC_65536) 372 var ro: i64 = 0 373 ro = mf_puts(reg, ro, "{\"generator\":\"nx_game_manifest\",\"hash\":\"sha256\",\"games\":[" as *u8) 374 var rcount: i64 = 0 375 376 let foff: *i64 = sys_mmap(64) as *i64 377 let flen: *i64 = sys_mmap(64) as *i64 378 379 var ls: i64 = 0 380 while ls < dn { 381 var le: i64 = 0 - 1 382 var k: i64 = ls 383 while k < dn { 384 if le < 0 { 385 if (data[k] as i64) == 0x0a { le = k } 386 } 387 k = k + 1 388 } 389 if le < 0 { le = dn } 390 391 var skip: i64 = 0 392 if le <= ls { skip = 1 } 393 if skip == 0 { 394 if (data[ls] as i64) == 0x23 { skip = 1 } 395 } 396 397 if skip == 0 { 398 let nf: i64 = mf_split(data, ls, le, foff, flen) 399 if nf >= 5 { 400 // Build the file path "dist" + wasm_path (wasm_path starts with /). 401 let fp: *u8 = sys_mmap(K_MAGIC_1024) 402 var fpo: i64 = 0 403 fpo = mf_puts(fp, fpo, "dist" as *u8) 404 fpo = mf_putb(fp, fpo, ((data as i64) + foff[3]) as *u8, flen[3]) 405 fp[fpo] = 0 406 407 let fln: *i64 = sys_mmap(8) as *i64 408 let fbuf: *u8 = sys_read_file_x86_64(fp, fln) 409 var fbytes: i64 = 0 410 let dig: *u8 = sys_mmap(32) 411 var have: i64 = 0 412 if fbuf != (0 as *u8) { 413 fbytes = fln[0] 414 sha256_digest(fbuf, fbytes, dig) 415 have = 1 416 } 417 418 if have == 1 { 419 // Per-game manifest. 420 let pm: *u8 = sys_mmap(K_MAGIC_8192) 421 var po: i64 = 0 422 po = mf_puts(pm, po, "{\"id\":\"" as *u8) 423 po = mf_putb(pm, po, ((data as i64) + foff[0]) as *u8, flen[0]) 424 po = mf_puts(pm, po, "\",\"title\":\"" as *u8) 425 po = mf_jsesc(pm, po, ((data as i64) + foff[1]) as *u8, flen[1]) 426 po = mf_puts(pm, po, "\",\"latest\":\"" as *u8) 427 po = mf_putb(pm, po, ((data as i64) + foff[2]) as *u8, flen[2]) 428 po = mf_puts(pm, po, "\",\"channels\":{\"stable\":\"" as *u8) 429 po = mf_putb(pm, po, ((data as i64) + foff[2]) as *u8, flen[2]) 430 po = mf_puts(pm, po, "\"},\"versions\":[{\"v\":\"" as *u8) 431 po = mf_putb(pm, po, ((data as i64) + foff[2]) as *u8, flen[2]) 432 po = mf_puts(pm, po, "\",\"wasm\":\"" as *u8) 433 po = mf_putb(pm, po, ((data as i64) + foff[3]) as *u8, flen[3]) 434 po = mf_puts(pm, po, "\",\"bytes\":" as *u8) 435 po = mf_putdec(pm, po, fbytes) 436 po = mf_puts(pm, po, ",\"sha256\":\"" as *u8) 437 po = mf_hex(pm, po, dig) 438 po = mf_puts(pm, po, "\",\"changelog\":\"" as *u8) 439 po = mf_jsesc(pm, po, ((data as i64) + foff[4]) as *u8, flen[4]) 440 po = mf_puts(pm, po, "\"}]}" as *u8) 441 442 let mp: *u8 = sys_mmap(K_MAGIC_1024) 443 var mpo: i64 = 0 444 mpo = mf_puts(mp, mpo, "dist/api/games/" as *u8) 445 mpo = mf_putb(mp, mpo, ((data as i64) + foff[0]) as *u8, flen[0]) 446 mpo = mf_puts(mp, mpo, ".json" as *u8) 447 mp[mpo] = 0 448 let fd: i64 = sys_openat_wr(mp, 420) 449 if fd >= 0 { sys_write(fd, pm, po); sys_close(fd) } 450 451 // Registry entry. 452 if rcount > 0 { ro = mf_puts(reg, ro, "," as *u8) } 453 ro = mf_puts(reg, ro, "{\"id\":\"" as *u8) 454 ro = mf_putb(reg, ro, ((data as i64) + foff[0]) as *u8, flen[0]) 455 ro = mf_puts(reg, ro, "\",\"title\":\"" as *u8) 456 ro = mf_jsesc(reg, ro, ((data as i64) + foff[1]) as *u8, flen[1]) 457 ro = mf_puts(reg, ro, "\",\"latest\":\"" as *u8) 458 ro = mf_putb(reg, ro, ((data as i64) + foff[2]) as *u8, flen[2]) 459 ro = mf_puts(reg, ro, "\",\"bytes\":" as *u8) 460 ro = mf_putdec(reg, ro, fbytes) 461 ro = mf_puts(reg, ro, ",\"sha256\":\"" as *u8) 462 ro = mf_hex(reg, ro, dig) 463 ro = mf_puts(reg, ro, "\",\"manifest\":\"/api/games/" as *u8) 464 ro = mf_putb(reg, ro, ((data as i64) + foff[0]) as *u8, flen[0]) 465 ro = mf_puts(reg, ro, ".json\"}" as *u8) 466 rcount = rcount + 1 467 } 468 } 469 } 470 ls = le + 1 471 } 472 ro = mf_puts(reg, ro, "]}" as *u8) 473 let fd2: i64 = sys_openat_wr("dist/api/games.json" as *u8, 420) 474 if fd2 >= 0 { sys_write(fd2, reg, ro); sys_close(fd2) } 475 476 let banner: *u8 = "nx_game_manifest: wrote dist/api/games.json + per-game manifests (sha256 KAT ok)\n" as *u8 477 var bn: i64 = 0 478 while banner[bn] != 0 { bn = bn + 1 } 479 sys_write(1, banner, bn) 480 return 0 481}