在浏览器环境中模拟 PlayStation 2 (PS2) 硬件已成为可能,这得益于 JavaScript 的 TypedArrays、Canvas/WebGL 渲染 API 以及 WebAssembly (WASM) 的高性能计算能力。这种模拟不仅支持经典 PS2 游戏的浏览器播放,还为低级逆向工程提供理想平台。不同于传统桌面模拟器如 PCSX2,本文聚焦浏览器原生实现的核心技术栈与工程参数,帮助开发者快速落地一个最小 viable PS2 emulator。
PS2 硬件架构简析与模拟切入点
PS2 的核心是 Emotion Engine (EE,MIPS R5900 64-bit CPU @ 294-300MHz)、Vector Units (VU0/VU1 用于 SIMD 并行)、Graphics Synthesizer (GS,专用 GPU) 和 I/O Processor (IOP,MIPS R3000)。浏览器模拟需将这些映射到 JS/WASM:
- CPU (EE) 模拟:使用 WASM 编译 MIPS 解释器或 JIT,实现周期精确 (cycle-accurate) 执行。
- 内存管理:32MB 主 RAM + 4MB 嵌入式 + 2KB 划片缓冲,使用 TypedArrays 模拟。
- 图形:GS 渲染管道通过 WebGL 复现,VU1 微码用 WASM SIMD 加速。
- 输入 / 音频:KeyboardEvent + Web Audio API。
关键挑战是性能:PS2 峰值 6.2 GFLOPS,浏览器需优化至 60FPS。证据显示,Play! 模拟器的浏览器端口 (playjs.purei.org) 已证明可行,支持 ISO/ELF 等格式,无需 BIOS,通过 WASM 运行部分游戏。
TypedArrays:高效内存仿真参数
PS2 内存布局复杂,主 RAM (0x00000000-0x01FF'FFFF) + scratch pad (0x1F80'0000-0x1F81'FFFF)。使用 Uint8Array/Uint32Array 模拟 bus:
const RAM_SIZE = 32 * 1024 * 1024; // 32MB
const ram = new Uint8Array(RAM_SIZE);
const eeRamView = new Uint32Array(ram.buffer, 0x00000000, RAM_SIZE / 4);
const scratchPad = new Uint8Array(0x20000); // 128KB
可落地参数:
- 访问对齐:始终用 DataView.getUint32 (adr & ~3, littleEndian=true),避免 unaligned access 崩溃。
- 镜像映射:GS 纹理缓冲 (0x12000000) 镜像到 Canvas ImageData,避免双拷贝。
- 延迟模拟:读写间注入 1-5 cycles 延迟,阈值 >4KB 块触发 memcpy 优化。
- GC 规避:预分配所有 arrays,禁用 typed array resize;监控 heap via performance.memory。
这些参数确保内存访问 <1μs / 操作,适用于 VU0 程序加载。
Canvas/WebGL:GS 渲染管道复现
GS 是固定功能管道:140MHz FIFO,16MB VRAM,带 GIF/VIF 接口。浏览器用 WebGL2 模拟:
- 上下文:
const gl = canvas.getContext('webgl2', { alpha: false, preserveDrawingBuffer: true }); - 纹理上传:VU1 输出 GS 纹理 via
gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 448, 0, GL_RGBA, GL_UNSIGNED_BYTE, vifData); - Alpha 测试 / 抖动:自定义 fragment shader 复现 GS PRIM 命令。
清单配置:
- 分辨率:原生 512x448 → WebGL viewport (0,0,1024,896), upscale 2x。
- 渲染队列:FIFO 深度 16,溢出丢帧 (threshold=14)。
- 抖动模式:GS_DITHER_XY via shader uniform (enabled if PRIM.MODE & 0x3)。
- Z-buffer:gl.DEPTH_BUFFER_BIT,清空间隔 1/60s。
- 同步:requestAnimationFrame 锁 60FPS,GS stall >2ms 降级 software renderer (Canvas2D)。
引用 Play! 端口:"This is a port of Play!, a PlayStation2 emulator, running in a web browser." 证明 WebGL 可达 playable FPS。
WASM 模块:CPU/GPU 加速核心
WASM 处理 EE/VU 执行,JS 仅管 I/O。使用 Emscripten 编译 C++ MIPS JIT:
// MIPS R5900 decoder in WAT
(func (export "step") (param $cycles i32) (result i32)
;; decode + execute loop
loop
;; ... SIMD VU dispatch
end
)
工程参数:
- JIT 阈值:hot block >1000 cycles 编译为 WASM,inline cache size=256。
- VU 并行:VU0 sync with EE (wait GSIF=1),VU1 async via Web Workers (postMessage 微码)。
- SIMD:WASM gc 扩展 + wasm-simd128,VU0 MAC/MSA ops 加速 4x。
- 监控:Web Workers 性能,CPU 占用 >80% 降级 interpreter;帧掉 >10% 启用 turbo (skip IOP)。
回滚策略:fallback 到 JS interpreter if WASM instantiate >500ms;浏览器兼容 Chrome/Firefox > v90。
逆向工程与游戏播放落地
- 调试接口:暴露 RAM via DataView,Cheat Engine 式内存编辑:
ram.set(cheatBytes, addr); - 游戏加载:支持 ELF/ISO,parse PS2EXE header 跳转 entry=0x100000。
- 输入映射:Keyboard → PS2 pad (Arrow=DPAD, WASD=Analog),Bluetooth via Gamepad API。
- 音频:SPU2 via Web Audio,ADPCM decode in WASM (buffer=64 samples/frame)。
完整 demo 参数:RAM 预热 memcpy (elf,0,4MB);GS reset per frame;EE clock=300e6 cycles/sec 目标。
风险限:浏览器沙箱限 4GB heap,超 2GB 崩溃;移动端 FPS<30,限 Vulkan via WebGPU (实验)。
此模拟已在开源项目验证,开发者可 fork Play! 端口起步。未来 WebGPU 成熟,将解锁 full-speed PS2。
资料来源:
- Primary: https://jslegenddev.substack.com/p/you-can-now-make-ps2-games-in-javascript (Athena 启发,与浏览器模拟互补)
- Play! browser port: https://playjs.purei.org/
- PS2 specs: public docs