neser 0.1.1

NESER - NES Emulator in Rust - is a NES emulator written in Rust. It aims to be a high-quality, hardware-accurate emulator that is also easy to use and extend. It supports a wide range of NES games and features, including various mappers, audio processing, and input handling. NESER is designed to be modular and extensible, allowing developers to easily add new features or support for additional hardware. It can be run using one of two frontends: a native desktop application using SDL2, or a web application using WebAssembly. The desktop application provides a high-performance, feature-rich experience with support for various input devices and display options, while the web application allows users to play NES games directly in their browsers without needing to install any software in a BYOR manner (Bring Your Own Roms).
Documentation
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NESER Web</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
      crossorigin="anonymous"
    />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div class="container py-4">
      <div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3 mb-4">
        <div>
          <h1 class="h3 mb-1">NESER Web</h1>
          <div class="text-body-secondary">Play classic ROMs with modern filters</div>
        </div>
        <div class="d-flex flex-wrap align-items-center gap-2">
          <label for="rom" class="form-label mb-0">ROM file</label>
          <div class="d-flex flex-nowrap align-items-center gap-2">
            <input type="file" id="rom" accept=".nes" class="form-control form-control-sm" />
            <select id="rom-select" class="form-select form-select-sm" aria-label="Select ROM from server">
              <option value="">Select bundled ROM…</option>
            </select>
            <button id="start" class="btn btn-success btn-sm">Start</button>
            <button id="reset" class="btn btn-outline-warning btn-sm" aria-label="Reset emulation">Reset</button>
            <button id="pause" class="btn btn-outline-secondary btn-sm" aria-label="Pause or resume emulation">Pause/Resume</button>
            <button id="stop" class="btn btn-outline-danger btn-sm" aria-label="Stop emulation">Stop</button>
          </div>
          <div class="d-flex flex-nowrap align-items-center gap-3 mt-1">
            <div class="form-check mb-0">
              <input class="form-check-input" type="checkbox" id="autorun-create" />
              <label class="form-check-label" for="autorun-create">Create autorun</label>
            </div>
            <button id="autorun-load" class="btn btn-outline-info btn-sm">Load autorun</button>
            <span id="autorun-status" class="small"></span>
            <button id="autorun-cancel" class="btn btn-outline-danger btn-sm d-none" aria-label="Cancel autorun">✕ Cancel</button>
          </div>
        </div>
      </div>

      <div class="card shadow-sm mb-3">
        <div class="card-body">
          <div class="row g-3 align-items-start">
            <div class="col-12 col-lg-9">
              <div class="screen-wrap">
                <canvas id="screen" width="256" height="240" tabindex="0"></canvas>
                <div id="shortcut-help-overlay" class="shortcut-help-overlay d-none" aria-hidden="true"></div>
                <div id="debugger-panel" class="debugger-panel d-none" aria-label="Debugger panel"></div>
              </div>
              <div class="mt-3 d-flex flex-wrap align-items-center gap-2">
                <span class="badge text-bg-dark">Status</span>
                <span id="status" class="status">Load a ROM to begin</span>
              </div>
            </div>
            <div class="col-12 col-lg-2">
              <div class="d-grid gap-2" id="screen-controls">
                <button id="screen-minus" class="btn btn-outline-light rounded-pill btn-sm">Zoom -</button>
                <button id="screen-plus" class="btn btn-outline-light rounded-pill btn-sm">Zoom +</button>
                <button id="fullscreen" class="btn btn-outline-light rounded-pill btn-sm">Fullscreen</button>
                <button id="filter-toggle" class="btn btn-outline-light rounded-pill btn-sm">Filter</button>
                <button id="gamepad-toggle" class="btn btn-outline-light rounded-pill btn-sm" aria-pressed="true">Gamepad On</button>
                <button id="mute" class="btn btn-outline-light rounded-pill btn-sm" aria-pressed="false">Mute</button>
                <button id="save-state" class="btn btn-outline-warning rounded-pill btn-sm" disabled>Save State</button>
                <button id="load-state" class="btn btn-outline-warning rounded-pill btn-sm" disabled>Load State</button>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="card bg-body-tertiary border-0">
        <div class="card-body py-3">
          <div id="controls">
            <strong>Controls:</strong> W = Up | A = Left | S = Down | D = Right | F = A | G = B | R = Select | T = Start
          </div>
          <div id="shortcut-reference" class="mt-2"></div>
        </div>
      </div>
    </div>

    <!-- Autorun modal dialog -->
    <div class="modal fade" id="autorun-modal" tabindex="-1" aria-labelledby="autorun-modal-label" aria-hidden="true">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="autorun-modal-label">Configure Autorun</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body">
            <div class="mb-3">
              <label for="autorun-file-input" class="form-label">Autorun file (.autorun)</label>
              <input type="file" id="autorun-file-input" accept=".autorun" class="form-control form-control-sm" />
            </div>
            <div id="autorun-file-info" class="d-none mb-3">
              <div class="alert alert-info py-2 small mb-2" id="autorun-file-summary"></div>
              <div class="mb-3">
                <label for="autorun-checkpoint-select" class="form-label">Start from checkpoint</label>
                <select id="autorun-checkpoint-select" class="form-select form-select-sm">
                  <option value="-1">From the beginning</option>
                </select>
              </div>
              <div class="form-check">
                <input class="form-check-input" type="checkbox" id="autorun-extend-check" />
                <label class="form-check-label" for="autorun-extend-check">Extend autorun (record after playback ends)</label>
              </div>
            </div>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
            <button type="button" class="btn btn-primary" id="autorun-use-btn" disabled>Use Autorun</button>
          </div>
        </div>
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
      crossorigin="anonymous"></script>
    <script type="module" src="./app.js?v=20260224-701"></script>
  </body>
</html>