code wiki / (root) / nx_mod_registry.nx

nx_mod_registry.nx source

↩ module page · 269 lines · 11089 B

1// nx_mod_registry.nx -- mod manifest + hook registries. 2// 3// Per user 2026-05-16: "make sure we roadmap mods and offline playing 4// if the player wants to play offline without giving up a bunch of 5// our nishi language work. i dont want someone ripping and hacking 6// what we built into something nefarious." 7// 8// This primitive ships the foundation of the mod system: 9// - ModManifest record (mod id, hash, capability flags, hook count) 10// - 5 registry types (block / entity / recipe / worldgen / event) 11// - Add / lookup / iterate APIs 12// - CAS-style hash on the mod's code; the runtime can verify mods 13// against the manifest to detect tampering. 14// 15// SECURITY POSTURE: 16// - Mods declare their capability flags (BLOCK_KIND / ENTITY_KIND / 17// etc.). Runtime denies any operation the manifest doesn't grant. 18// - Mod's manifest_hash = caller-provided 64-bit content hash. 19// The official runtime cross-checks this against a known-trusted 20// list AND requires a manifest signature (Ed25519, future). 21// - Mods marked UNTRUSTED can run but the multiplayer layer rejects 22// them. 23// 24// ManifestRecord (8 i64): 25// m[0] = mod_id unique integer 26// m[1] = name_hash fnv-style 64-bit hash of the mod name 27// m[2] = code_hash 64-bit Merkle / CAS hash of the mod .wasm 28// m[3] = capability_flags 29// m[4] = trust_level 0 = UNTRUSTED, 1 = COMMUNITY, 2 = OFFICIAL 30// m[5] = version 31// m[6] = enabled 0/1 32// m[7] = reserved 33// 34// HookRegistry (variable): 35// reg[0] = n_entries 36// reg[1] = max_entries 37// reg[2..] = per-entry (4 i64): mod_id, kind_id, handler_ptr, reserved 38// 39// PUBLIC APIs: 40// nx_mod_manifest_init(m, mod_id, name_hash, code_hash, caps, 41// trust, version) 42// nx_mod_registry_alloc(max) 43// nx_mod_registry_register(reg, mod_id, kind_id, handler_ptr) -> 0/1 44// nx_mod_registry_lookup(reg, kind_id) -> handler_ptr or 0 45// nx_mod_registry_count(reg) -> n 46// 47// genealogy_id: minecraft_modloader_canon + bevy_plugin_canon 48// lineage_id: nx_mod_registry_foundation_v1 49 50// nx_safety_envelope: 51// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 52// sil_target: SIL1 53// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 54// verdict: NOT_YET_EVALUATED 55 56import "nx_syscalls.nx" 57import "nx_tier.nx" 58const NX_MAGIC_12345: i64 = 12345 59const NX_MAGIC_67890: i64 = 67890 60 61const NX_MOD_Q: nx_int = 16384 62 63// ===== Capability flags (bitfield) ================================ 64const NX_MOD_CAP_BLOCK_KIND: nx_int = 1 65const NX_MOD_CAP_ENTITY_KIND: nx_int = 2 66const NX_MOD_CAP_RECIPE: nx_int = 4 67const NX_MOD_CAP_WORLDGEN_HOOK: nx_int = 8 68const NX_MOD_CAP_EVENT_LISTEN: nx_int = 16 69const NX_MOD_CAP_UI_PANEL: nx_int = 32 70const NX_MOD_CAP_MULTIPLAYER: nx_int = 64 // mod is network-safe 71 72// ===== Trust levels ================================================ 73const NX_MOD_TRUST_UNTRUSTED: nx_int = 0 74const NX_MOD_TRUST_COMMUNITY: nx_int = 1 75const NX_MOD_TRUST_OFFICIAL: nx_int = 2 76 77// ===== Manifest layout ============================================ 78const NX_MOD_MANIFEST_STRIDE: nx_int = 8 79const NX_MOD_M_OFF_ID: nx_int = 0 80const NX_MOD_M_OFF_NAME_HASH: nx_int = 1 81const NX_MOD_M_OFF_CODE_HASH: nx_int = 2 82const NX_MOD_M_OFF_CAPS: nx_int = 3 83const NX_MOD_M_OFF_TRUST: nx_int = 4 84const NX_MOD_M_OFF_VERSION: nx_int = 5 85const NX_MOD_M_OFF_ENABLED: nx_int = 6 86 87// ===== Registry layout ============================================ 88const NX_MOD_REG_HEADER_SIZE: nx_int = 2 89const NX_MOD_REG_ENTRY_STRIDE: nx_int = 4 90const NX_MOD_REG_OFF_N: nx_int = 0 91const NX_MOD_REG_OFF_MAX: nx_int = 1 92const NX_MOD_E_MOD_ID: nx_int = 0 93const NX_MOD_E_KIND_ID: nx_int = 1 94const NX_MOD_E_HANDLER: nx_int = 2 95 96// ===== Manifest initializer ======================================== 97func nx_mod_manifest_init( 98 m: *i64, mod_id: nx_int, name_hash: nx_int, code_hash: nx_int, 99 caps: nx_int, trust: nx_int, version: nx_int 100) { 101 m[NX_MOD_M_OFF_ID] = mod_id 102 m[NX_MOD_M_OFF_NAME_HASH] = name_hash 103 m[NX_MOD_M_OFF_CODE_HASH] = code_hash 104 m[NX_MOD_M_OFF_CAPS] = caps 105 m[NX_MOD_M_OFF_TRUST] = trust 106 m[NX_MOD_M_OFF_VERSION] = version 107 m[NX_MOD_M_OFF_ENABLED] = 1 108 m[7] = 0 109} 110 111func nx_mod_manifest_has_cap(m: *i64, cap: nx_int) -> nx_int { 112 let caps: nx_int = m[NX_MOD_M_OFF_CAPS] 113 if (caps / cap) % 2 == 1 { return 1 } 114 return 0 115} 116 117func nx_mod_manifest_is_trusted(m: *i64) -> nx_int { 118 if m[NX_MOD_M_OFF_TRUST] >= NX_MOD_TRUST_COMMUNITY { return 1 } 119 return 0 120} 121 122// ===== Registry alloc ============================================= 123func nx_mod_registry_alloc(max_entries: nx_int) -> *i64 { 124 if max_entries <= 0 { return 0 as *i64 } 125 let n_i64: nx_int = NX_MOD_REG_HEADER_SIZE + max_entries * NX_MOD_REG_ENTRY_STRIDE 126 let reg: *i64 = (sys_mmap(n_i64 * NX_SIZEOF_NX_INT)) as *i64 127 reg[NX_MOD_REG_OFF_N] = 0 128 reg[NX_MOD_REG_OFF_MAX] = max_entries 129 return reg 130} 131 132// ===== Register a hook entry ====================================== 133// Returns 1 on success, 0 if full or duplicate kind_id. 134func nx_mod_registry_register( 135 reg: *i64, mod_id: nx_int, kind_id: nx_int, handler_ptr: nx_int 136) -> nx_int { 137 let n: nx_int = reg[NX_MOD_REG_OFF_N] 138 let max: nx_int = reg[NX_MOD_REG_OFF_MAX] 139 if n >= max { return 0 } 140 // Reject duplicates by kind_id. 141 var i: nx_int = 0 142 while i < n { 143 let e: nx_int = NX_MOD_REG_HEADER_SIZE + i * NX_MOD_REG_ENTRY_STRIDE 144 if reg[e + NX_MOD_E_KIND_ID] == kind_id { return 0 } 145 i = i + 1 146 } 147 let entry: nx_int = NX_MOD_REG_HEADER_SIZE + n * NX_MOD_REG_ENTRY_STRIDE 148 reg[entry + NX_MOD_E_MOD_ID] = mod_id 149 reg[entry + NX_MOD_E_KIND_ID] = kind_id 150 reg[entry + NX_MOD_E_HANDLER] = handler_ptr 151 reg[entry + 3] = 0 152 reg[NX_MOD_REG_OFF_N] = n + 1 153 return 1 154} 155 156// ===== Lookup by kind_id ========================================== 157// Returns handler ptr or 0 if not found. 158func nx_mod_registry_lookup(reg: *i64, kind_id: nx_int) -> nx_int { 159 let n: nx_int = reg[NX_MOD_REG_OFF_N] 160 var i: nx_int = 0 161 while i < n { 162 let e: nx_int = NX_MOD_REG_HEADER_SIZE + i * NX_MOD_REG_ENTRY_STRIDE 163 if reg[e + NX_MOD_E_KIND_ID] == kind_id { 164 return reg[e + NX_MOD_E_HANDLER] 165 } 166 i = i + 1 167 } 168 return 0 169} 170 171func nx_mod_registry_count(reg: *i64) -> nx_int { 172 return reg[NX_MOD_REG_OFF_N] 173} 174 175// ===== Iterate (caller-supplied callback semantics: returns the 176// idx-th entry's mod_id / kind_id / handler via out_buf) =========== 177func nx_mod_registry_at(reg: *i64, idx: nx_int, out_buf: *i64) -> nx_int { 178 let n: nx_int = reg[NX_MOD_REG_OFF_N] 179 if idx < 0 { return 0 } 180 if idx >= n { return 0 } 181 let e: nx_int = NX_MOD_REG_HEADER_SIZE + idx * NX_MOD_REG_ENTRY_STRIDE 182 out_buf[0] = reg[e + NX_MOD_E_MOD_ID] 183 out_buf[1] = reg[e + NX_MOD_E_KIND_ID] 184 out_buf[2] = reg[e + NX_MOD_E_HANDLER] 185 return 1 186} 187 188// ===== Verify mod code hash ======================================= 189// Caller provides the actually-computed hash; returns 1 if it matches 190// the manifest's recorded hash. Used for tamper detection. 191func nx_mod_verify_code_hash(m: *i64, actual_hash: nx_int) -> nx_int { 192 if m[NX_MOD_M_OFF_CODE_HASH] == actual_hash { return 1 } 193 return 0 194} 195 196// ===== Self-test ==================================================== 197func main() -> i64 { 198 let q: nx_int = NX_MOD_Q 199 let m: *i64 = (sys_mmap(NX_MOD_MANIFEST_STRIDE * NX_SIZEOF_NX_INT)) as *i64 200 201 // T1: Manifest init + capability check. 202 nx_mod_manifest_init(m, 42, NX_MAGIC_12345, NX_MAGIC_67890, 203 NX_MOD_CAP_BLOCK_KIND + NX_MOD_CAP_ENTITY_KIND, 204 NX_MOD_TRUST_COMMUNITY, 1) 205 if m[NX_MOD_M_OFF_ID] != 42 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 206 if m[NX_MOD_M_OFF_NAME_HASH] != NX_MAGIC_12345 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 207 if m[NX_MOD_M_OFF_CODE_HASH] != NX_MAGIC_67890 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 208 if m[NX_MOD_M_OFF_ENABLED] != 1 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 209 if nx_mod_manifest_has_cap(m, NX_MOD_CAP_BLOCK_KIND) != 1 { 210 return __syscall(93, 5, 0, 0, 0, 0, 0) 211 } 212 if nx_mod_manifest_has_cap(m, NX_MOD_CAP_RECIPE) != 0 { 213 return __syscall(93, 6, 0, 0, 0, 0, 0) 214 } 215 if nx_mod_manifest_is_trusted(m) != 1 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 216 217 // T2: Untrusted mod. 218 nx_mod_manifest_init(m, 1, 0, 0, 0, NX_MOD_TRUST_UNTRUSTED, 1) 219 if nx_mod_manifest_is_trusted(m) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 220 221 // T3: Registry alloc + register. 222 let reg: *i64 = nx_mod_registry_alloc(16) 223 if reg[NX_MOD_REG_OFF_N] != 0 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 224 if reg[NX_MOD_REG_OFF_MAX] != 16 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 225 let r1: nx_int = nx_mod_registry_register(reg, 42, 100, 0x1234) 226 if r1 != 1 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 227 if nx_mod_registry_count(reg) != 1 { return __syscall(93, 23, 0, 0, 0, 0, 0) } 228 // Duplicate kind_id -> fail. 229 let r2: nx_int = nx_mod_registry_register(reg, 43, 100, 0x5678) 230 if r2 != 0 { return __syscall(93, 24, 0, 0, 0, 0, 0) } 231 // Different kind_id -> success. 232 let r3: nx_int = nx_mod_registry_register(reg, 43, 101, 0x5678) 233 if r3 != 1 { return __syscall(93, 25, 0, 0, 0, 0, 0) } 234 if nx_mod_registry_count(reg) != 2 { return __syscall(93, 26, 0, 0, 0, 0, 0) } 235 236 // T4: Lookup. 237 if nx_mod_registry_lookup(reg, 100) != 0x1234 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 238 if nx_mod_registry_lookup(reg, 101) != 0x5678 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 239 if nx_mod_registry_lookup(reg, 999) != 0 { return __syscall(93, 32, 0, 0, 0, 0, 0) } 240 241 // T5: Iterate. 242 let out_buf: *i64 = (sys_mmap(3 * NX_SIZEOF_NX_INT)) as *i64 243 let ok: nx_int = nx_mod_registry_at(reg, 0, out_buf) 244 if ok != 1 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 245 if out_buf[0] != 42 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 246 if out_buf[1] != 100 { return __syscall(93, 42, 0, 0, 0, 0, 0) } 247 if out_buf[2] != 0x1234 { return __syscall(93, 43, 0, 0, 0, 0, 0) } 248 // Out of range -> 0. 249 if nx_mod_registry_at(reg, 99, out_buf) != 0 { 250 return __syscall(93, 44, 0, 0, 0, 0, 0) 251 } 252 253 // T6: Hash verification. 254 nx_mod_manifest_init(m, 1, 0, 0xDEADBEEF, 0, NX_MOD_TRUST_OFFICIAL, 1) 255 if nx_mod_verify_code_hash(m, 0xDEADBEEF) != 1 { 256 return __syscall(93, 50, 0, 0, 0, 0, 0) 257 } 258 if nx_mod_verify_code_hash(m, 0xCAFEBABE) != 0 { 259 return __syscall(93, 51, 0, 0, 0, 0, 0) 260 } 261 262 // T7: Capacity exhaustion. 263 let small_reg: *i64 = nx_mod_registry_alloc(2) 264 if nx_mod_registry_register(small_reg, 1, 1, 1) != 1 { return __syscall(93, 60, 0, 0, 0, 0, 0) } 265 if nx_mod_registry_register(small_reg, 1, 2, 2) != 1 { return __syscall(93, 61, 0, 0, 0, 0, 0) } 266 if nx_mod_registry_register(small_reg, 1, 3, 3) != 0 { return __syscall(93, 62, 0, 0, 0, 0, 0) } 267 268 return 0 269}