# NEON ARCADE

网页街机模拟器，使用 EmulatorJS 的 WebAssembly 核心。游戏本体仍在浏览器本地运行；排行榜需要 PHP，手机 2P 房间需要可选的 Node.js WebSocket 服务。

## 添加 ROM

1. 把合法持有的街机 ROM 压缩包放入 `ROM` 文件夹，可以使用子文件夹。
2. 在项目目录运行 `./sync-roms.ps1`。
3. 运行 `./start.ps1`，浏览器访问 `http://localhost:4173`。

不能直接双击 `index.html`：浏览器的安全策略不允许 `file://` 页面读取 ROM 清单和 WebAssembly 文件。

也可以不生成清单，直接点击页面中的“选择本地 ROM”。这种方式不会把 ROM 上传到服务器或第三方。

## ROM 清单

`ROM/games.json` 中每个游戏可以配置：

```json
{
  "games": [
    {
      "title": "游戏显示名称",
      "file": "game.zip",
      "core": "arcade",
      "bios": "neogeo.zip",
      "parent": "parent.zip"
    }
  ]
}
```

- `core: "arcade"`：FBNeo，默认且通常最适合 CPS、Neo Geo 等 2D 街机。
- `core: "mame2003"`：适用于匹配 MAME 2003 ROM set 的文件。
- `bios`：可选 BIOS 文件，相对于 `ROM` 文件夹。
- `parent`：可选父 ROM，适用于 split clone ROM。
- `sha256`：由 `sync-roms.ps1` 自动生成，用于让每个 ROM 拥有独立排行榜。

建议使用与核心版本匹配的 non-merged ROM。ROM set 不匹配是街机游戏启动失败最常见的原因。

## 双人按键

- 1P：方向键移动，`V` 投币，`Enter` 开始，`J / K` 动作。
- 2P：`WASD` 移动，`C` 投币，`R` 开始，`F / G` 动作。
- 浏览器识别到的前两只标准手柄会依次作为 1P、2P。
- 手机 2P：运行 `npm install` 和 `npm run ws`，主页创建房间后让第二位玩家打开邀请链接。

## 排行榜

排行榜按 ROM 的 SHA-256 指纹隔离，只保存每个 ROM 的前三名。玩家只能填写代号，分数输入框只读。

街机游戏的分数内存地址随 ROM 和核心变化，因此只有完成并验证专用内存读分配置的 ROM 才能提交。未配置的游戏会显示“NO SCORE MAP”，不会退回到手工填分。

PHP-FPM 用户必须能写入 `api` 目录，数据文件会在第一次提交时自动创建，不需要提前上传：

```bash
chown -R apache:apache /var/www/home/ham/arcade/api
chmod 755 /var/www/home/ham/arcade/api
```

## 部署到 `/var/www/home/ham/arcade`

主页和同机双人只需上传：

- `index.html`、`style.css`、`features.css`、`script.js`
- `ROM/`（包括 `games.json` 和合法 ROM）

排行榜再上传 `api/arcade-leaderboard.php`。手机 2P 再上传 `controller.html`、`controller.css`、`controller.js`、`websocket-server.js`、`package.json` 和 `package-lock.json`，服务器执行：

```bash
cd /var/www/home/ham/arcade
npm install --omit=dev
ARCADE_WS_PORT=8081 npm run ws
```

生产环境应使用 systemd 或 PM2 常驻 WebSocket 服务，并在 Nginx Proxy Manager 中把站点的 `/ws` 转发到 `127.0.0.1:8081`，启用 WebSocket 支持。HTTPS 页面必须通过 `wss://` 连接，不能直接暴露 `ws://`。

服务器还需允许下载 `.zip`、`.7z` 和 `.json` 文件。模拟器核心默认从 EmulatorJS 官方 CDN 加载，因此首次运行需要联网。

请仅使用你依法有权使用的 ROM、BIOS 和相关文件。
