mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
An end-to-end FIPS 180-4 SHA-256 hasher running entirely on the NES.
The player types up to 16 ASCII characters on a 5x8 on-screen
keyboard, presses Enter, and the program computes and displays the
64-character hex digest.
Layout (`examples/sha256/*.ne`):
constants.ne layout + K[64] / H_INIT[8] tables
(declared as `var` with init_array because the
v0.1 compiler treats `const u8[N] = [...]` as
a no-op — noted in the file)
assets.ne 44-tile Tileset (A..Z, 0..9, punctuation,
special keys, cursor) shared between BG and
sprite layers
background.ne static nametable (title, labels, keyboard
grid) painted at reset
state.ne globals
sha_core.ne 32-bit byte primitives (copy, xor, and, add,
not, rotr, shr) in inline asm + sigma/Sigma
mixers + schedule/round steps + fold
render.ne OAM helpers for cursor, input buffer, and
64-nibble digest
keyboard.ne key dispatch table
entering_state.ne cursor navigation + typing + auto-demo
computing_state.ne phased driver (48 schedule steps + 64 rounds
+ fold across ~30 frames at 4 iterations each)
showing_state.ne renders the 256-bit digest as 8 rows of 8
sprite glyphs
Implementation notes:
- All 32-bit words live as 4 little-endian bytes in `wk[64]`,
`w[256]`, `h_state[32]` so every primitive walks four bytes with
`LDA {arr},X`/`STA {arr},X` chains and, for adds, a carry chain.
- Every primitive reads its parameters straight out of the
transport slots `$04`/`$05` rather than `{dst}`/`{src}`
substitutions: the inline-asm resolver looks parameters up in
the analyzer's allocation table but the codegen spills them to a
different per-function RAM slot, so `{dst}` would resolve to a
ZP slot nothing ever writes to. Bypassing the substitution
entirely sidesteps the issue without a compiler change.
- Rotate-right by any amount is a byte-rotate loop plus a bit-
rotate loop so the 10 SHA amounts (2, 6, 7, 11, 13, 17, 18, 19,
22, 25) all compile to a handful of chained `ROR`s.
- The headless jsnes golden auto-types "NES" after 1 s of idle and
captures its SHA-256 digest
AE9145DB5CABC41FE34B54E34AF8881F462362EA20FD8F861B26532FFBB84E0D
— byte-identical to `shasum` / `hashlib.sha256(b"NES")`.
Build: `cargo run --release -- build examples/sha256.ne`
https://claude.ai/code/session_01FRmSBruVWCufm3LsUVMs8v
126 lines
4.1 KiB
Text
126 lines
4.1 KiB
Text
// sha256/entering_state.ne — the interactive typing state.
|
|
//
|
|
// Reads controller 1 each frame, moves the keyboard cursor on
|
|
// D-pad presses, types a character on A, deletes a character on
|
|
// B, and starts compression on START (or when the cursor is
|
|
// over the ↵ key and A is pressed). SELECT clears the buffer at
|
|
// any time so the user can start over.
|
|
//
|
|
// If the user doesn't press anything in the first AUTO_DELAY
|
|
// frames, the handler auto-types DEMO_TEXT ("NES") and
|
|
// transitions to Computing so the jsnes golden always captures
|
|
// a fully-rendered hash.
|
|
|
|
state Entering {
|
|
on enter {
|
|
kb_row = 0
|
|
kb_col = 0
|
|
idle_timer = 0
|
|
debounce = 0
|
|
blink_timer = 0
|
|
}
|
|
|
|
on frame {
|
|
// ── Debounce + blink tick ───────────────────────
|
|
if debounce > 0 {
|
|
debounce -= 1
|
|
}
|
|
blink_timer += 1
|
|
if blink_timer >= 40 {
|
|
blink_timer = 0
|
|
}
|
|
|
|
// ── D-pad navigation ────────────────────────────
|
|
var pressed: u8 = 0
|
|
if debounce == 0 {
|
|
if button.left {
|
|
if kb_col > 0 {
|
|
kb_col -= 1
|
|
} else {
|
|
kb_col = KB_COLS - 1
|
|
}
|
|
debounce = 8
|
|
pressed = 1
|
|
} else if button.right {
|
|
kb_col += 1
|
|
if kb_col >= KB_COLS {
|
|
kb_col = 0
|
|
}
|
|
debounce = 8
|
|
pressed = 1
|
|
} else if button.up {
|
|
if kb_row > 0 {
|
|
kb_row -= 1
|
|
} else {
|
|
kb_row = KB_ROWS - 1
|
|
}
|
|
debounce = 8
|
|
pressed = 1
|
|
} else if button.down {
|
|
kb_row += 1
|
|
if kb_row >= KB_ROWS {
|
|
kb_row = 0
|
|
}
|
|
debounce = 8
|
|
pressed = 1
|
|
} else if button.a {
|
|
// Press the key under the cursor. Backspace
|
|
// and enter dispatch to their actions; any
|
|
// other character is appended to `msg`.
|
|
var ch: u8 = current_key()
|
|
if ch == KEY_BKSP {
|
|
input_backspace()
|
|
} else if ch == KEY_ENTER {
|
|
if msg_len > 0 {
|
|
transition Computing
|
|
}
|
|
} else {
|
|
input_append(ch)
|
|
}
|
|
debounce = 10
|
|
pressed = 1
|
|
} else if button.b {
|
|
input_backspace()
|
|
debounce = 10
|
|
pressed = 1
|
|
} else if button.start {
|
|
if msg_len > 0 {
|
|
transition Computing
|
|
}
|
|
debounce = 10
|
|
pressed = 1
|
|
} else if button.select {
|
|
input_clear()
|
|
debounce = 10
|
|
pressed = 1
|
|
}
|
|
}
|
|
|
|
// ── Idle / auto-demo ────────────────────────────
|
|
if pressed == 1 {
|
|
idle_timer = 0
|
|
} else {
|
|
if idle_timer < 255 {
|
|
idle_timer += 1
|
|
}
|
|
if idle_timer == AUTO_DELAY {
|
|
if msg_len == 0 {
|
|
// Auto-type "NES" → SHA-256 digest visible
|
|
// in the headless jsnes golden.
|
|
input_append(0x4E) // N
|
|
input_append(0x45) // E
|
|
input_append(0x53) // S
|
|
transition Computing
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Render ──────────────────────────────────────
|
|
// Input buffer takes 16 sprites, cursor 1 (when on).
|
|
// Well under the 64-sprite OAM budget.
|
|
draw_input()
|
|
if blink_timer < 25 {
|
|
draw_cursor(1)
|
|
}
|
|
}
|
|
}
|