rom:把复古游戏渲染进终端,用 Kitty 图形协议跑 libretro 核心
rom:在终端里直接运行 ROM 的 libretro 前端
如果你玩过复古游戏模拟器,大概熟悉这套流程:打开模拟器窗口,加载 ROM,再开一个窗口看攻略或者聊天。rom 这个项目想换个思路,它把游戏画面直接渲染到终端里,用 Kitty 图形协议显示,不需要额外开窗口。
项目作者是 jhickner,用 C 写的,目前 GitHub 上 5 个 star,仓库地址在文末。按 README 的说法,它是一个面向 macOS 和 Linux 的 libretro 前端,支持存档、音频、快进,还能根据终端主题实时重着色。注意它本身不包含游戏、BIOS 文件或模拟器核心,只跑你有合法权利使用的软件。
它解决什么问题
终端模拟器通常只处理文本,但 Kitty 图形协议允许在终端里显示图片。rom 利用这一点,把模拟器的画面输出到终端单元格里,而不是弹出一个 GUI 窗口。好处是你可以把游戏嵌在终端工作流里,比如在 tmux 里开一个窗格玩游戏,旁边窗格继续写代码。
另一个实际痛点是核心管理。libretro 核心(比如 snes9x、mgba)需要自己下载编译,rom 会在你首次打开某个平台的 ROM 时询问是否自动拉取并构建对应核心。如果你拒绝,它会打印核心的仓库地址和安装路径,让你自己处理。
支持哪些平台和核心
按 README 里的表格,rom 通过 libretro 核心支持这些格式:
| |
|---|
| |
| |
| |
| |
| |
| mednafen_pce_fast_libretro |
| mupen64plus_next_libretro |
| |
| |
核心搜索路径按顺序是:~/.config/rom/cores/、./cores/、<可执行文件>/../cores/。也可以用 --core <路径> 直接指定。
有几个值得注意的细节。N64 默认用 Mupen64Plus-Next 的 Angrylion 软件渲染器,而不是 GLideN64,因为作者说 GLideN64 在 macOS/arm64 上会损坏纹理缓存并在游戏开始几秒后崩溃,而在终端分辨率下软件渲染器看起来一样。如果你想试 GLideN64,可以在配置里设 mupen64plus-rdp-plugin = gliden64。
Doom 相关的 prboom 分支除了 Doom 还能玩 Heretic 和 Hexen,通过 IWAD 的 lump 表自动检测。README 列了验证可用的 WAD 列表,包括 doom.wad、doom2.wad、heretic.wad、hexen.wad 等。不工作的包括 Strife(会挂起核心)和 hexdd.wad、voices.wad(会崩溃),Chex Quest 因为是以 PWAD 补丁形式分发,需要基础 IWAD 加载,rom 只传一个文件给核心,所以会被拒绝。
Wolfenstein 3D 接受游戏的数据文件(比如 VSWAP.WL1),ECWolf 会在同目录找其他文件,核心自带的 ecwolf.pk3 内部处理。
怎么跑起来
前提是终端得是 Ghostty 或 kitty,以及一个 C 编译器。macOS 上先装 Xcode 命令行工具:
xcode-select --install
Ubuntu 或 Debian 上:
sudo apt install build-essential libasound2-dev
然后:
git clone https://github.com/jhickner/romcd rommake./rom "path/to/game.sfc"
可选安装到用户目录:
make install PREFIX="$HOME/.local"
首次打开某个平台的 ROM 且没有对应核心时,rom 会问你是否自动获取并构建。自动构建会浅克隆仓库到 ~/.config/rom/src/,编译后安装核心并启动游戏。如果标准输入不是终端(比如在脚本里跑),它会跳过询问直接打印构建说明。
实际使用体验
默认是内联模式,游戏画面直接嵌在终端里,退出时清除 Kitty 图像,滚动缓冲区不受影响。--resume 会列出最近玩过的 40 个游戏,按时间排序,显示存档槽位情况,支持方向键或 j/k 选择,Enter 开始,数字键直接跳转。游戏按绝对路径匹配,所以换目录不影响记录。
按键绑定比较常规:方向键是十字键,z/x 是 B/A,a/s 是 Y/X,q/w 是 L/R,Enter 和右 Shift 是 Start/Select,F2/F3 存读档,F6/F7 切换存档槽,F5 重置,Tab 按住快进,F8 循环重着色模式,方括号调节缩放,F9 切换终端缩放模式,减号等号调音量,m 静音。
存档和配置都在 ~/.config/rom/ 下。config 文件可以改按键、音量、缩放、重着色和焦点行为。支持按系统后缀覆盖设置,比如 [options.gb] 里设 scale = 4 只影响 Game Boy 游戏。[core] 段落直接透传 libretro 核心选项,名字和值属于核心本身,rom 不解释。音量、缩放、重着色强度这些设置会按 ROM 分别记忆,下次打开同一游戏恢复上次的状态。
几个值得说的技术点
tmux 支持不是简单放行。tmux 本身不能跟踪光标位置的图像,所以 rom 用了一个技巧:帧不指定位置传输,给一个虚拟位置,占用的单元格填上占位字符,这些字符的前景色携带图像 ID,行和列用组合附加符号编码。tmux 把它们当普通文本移动、滚动、重绘,底层终端再替换成像素。输入也类似,键盘协议通过转义序列透传到终端,tmux 把不认识的按键报告(包括释放和修饰键)直接交给窗格。
如果终端不响应能力查询,rom 会退化为从自动重复推断按键释放:按键重复停止就认为松开了,所以按住方向键开头会有一个小卡顿。
缩放方面,默认终端把图像拉伸到单元格矩形,所以无论缩放多少都只传输一帧原生分辨率。如果设置 term_scale = false,改为本地缩放,保持像素画的锯齿感,但代价是每帧传输的数据量按缩放比的平方增加。README 给了具体数字:3 倍缩放下本地缩放每帧 2020 KB,终端缩放只要 225 KB;4 倍时本地缩放单独就能把渲染线程限制在接近 60fps。
GPU 读回也有讲究。N64 这类 GPU 渲染的核心,每帧要从 GPU 读回画面。默认异步读回,传输排队到下一帧收集,和模拟重叠而不是阻塞,在 640x480 下大约快 3 倍,代价是一帧输入延迟。可以设 async_readback = false 改回同步。
终端颜色重着色是另一个特色。rom 启动时读取终端的 ANSI 调色板、前景色和背景色,用 Oklab 色彩空间做感知匹配,通过预构建查找表保证速度和稳定性。几种模式:hue 保持明暗同时采用主题色相,nearest 直接映射到终端调色板,duotone 用背景到前景的渐变,tint 把一切折叠成背景色的色调,dither 混合附近调色板颜色恢复中间调。tint 模式只借用背景的色相而不是饱和度,因为终端背景接近中性色,直接照搬会看不见;纯黑或纯灰背景只能得到灰度。
受众圈层
适合已经在终端里生活、用 kitty 或 Ghostty、想偶尔玩一把复古游戏的人。特别是如果你习惯 tmux 多窗格工作,rom 能让你在终端里开一个游戏窗格,不打断其他工作。自动获取核心的机制也省了手动编译的麻烦。
不太适合的场景:如果你想要完整的模拟器体验,比如滤镜、着色器、多窗口、手柄配置,rom 不是干这个的,它定位就是终端里的轻量前端。另外它依赖 Kitty 图形协议,普通终端模拟器跑不了。项目目前 star 数很少,属于个人项目,README 里也明确写了部分核心的兼容性问题(比如 Strife 挂起、Chex Quest 不支持),遇到问题可能需要自己看源码解决。
最后提醒一句:rom 不包含游戏、BIOS 或核心,你需要自己准备合法拥有的 ROM 文件。核心的自动构建需要 git、make 和 C/C++ 工具链,首次构建可能要几分钟。
仓库地址:https://github.com/jhickner/rom