用 Docker + Ruffle 自建 Flash 老游戏库,让童年游戏在浏览器复活

2020 年底,Adobe 正式宣布 Flash Player 退役,一代人的网页游戏回忆也随之落幕。但总有办法让老游戏活过来——用 Ruffle,一个用 Rust 写成的 Flash 模拟器,编译成 WebAssembly,在浏览器里不装任何插件就能直接跑 .swf。这篇文章记录我如何用 Docker 自建一个 Flash 老游戏在线库,以及过程中最硬核的一段经历:给游戏打字节补丁。

一、为什么是 Ruffle

  • 开源、纯前端,不依赖 Flash 插件,2026 年的浏览器照样能跑 2008 年的游戏;
  • WebAssembly 编译,性能尚可,AS1/AS2 兼容性已经很完整,AS3 在 nightly 版本上不断进步;
  • 本地自托管(ruffle.js + core + wasm),不依赖任何第三方 CDN,断网也能玩。

二、整体架构

一个 Docker Compose 搞定,nginx:alpine 静态托管,一行配置解决 swf/wasm 的 MIME 与缓存,外网走面板反向代理到 127.0.0.1:13400:

# /www/docker_data/games/docker-compose.yml
services:
  games-nginx:
    image: mirror.ccs.tencentyun.com/library/nginx:alpine
    restart: always
    ports:
      - "127.0.0.1:13400:80"
    volumes:
      - ./www:/usr/share/nginx/html

首页是带搜索/分类/收藏的索引页,播放页 game.html?id=<id> 负责按游戏 id 加载对应 swf 与封面。

三、游戏从哪来:4399

写了 add_game.sh 一键抓取:解析游戏页的 _strGamePath → 真实文件其实在 szhong.4399.com/4399swf/ → 下载 swf 和封面 → 注册进 list.json 索引。几个坑:

  • 部分游戏 _strGamePath 指向 .htm 加载器页,得进页面再找真实 .swf;
  • 封面是 // 开头的协议相对地址,curl 要手动补 http:;
  • MochiAds 等广告 SDK 已被 Ruffle 拦截,带广告的游戏可能卡死,这类直接放弃。

四、最硬核的部分:给游戏打字节补丁

不是所有游戏都能直接跑。狂暴飞车(110167) 就卡死在加载页:它的预加载器只有 8 帧,但 PLAY 按钮要第 30 帧才出现,而且 Ruffle 里 framesLoaded >= totalFrames 这个门控永远不成立,于是加载动画永远转下去。

排查过程:用 FFDec 反编译 swf,发现它是 AS3 0.6 版本、内嵌了 Alchemy/C++ 编译的 Flixel 引擎,入口是 A0 预加载器类,流程为 A0 → init() → loadBytes(_swfP) → startMainClass()。真正的浮动画按钮,是一个内嵌的 193 帧加载动画在每帧被 play() 重播造成的。

于是我对 A0 的 ActionScript Bytecode(ABC)手工打了 6 处 AVM2 补丁:

作用 改动
加载门控 iffalse 判断改成恒进入
淡出等待 screenFader.alpha<1 条件跳转改成无条件 jump
帧数门控 currentFrame>=totalFrames 恒真
淡入属性 读取 _fadeP 改成 pop + push true
按钮动画 把播放逻辑改成 playButton.stop()
动画循环 callpropvoid play 改成 stop,加载画面停在第一帧

补丁后 PLAY 按钮终于停住可点。一个关键教训:FFDec 导出的十六进制行号并不等于原始 ABC 偏移,字节定位必须用搜索模式而不是行号;而且 Ruffle 对 FFDec 重编译的代码不执行,只能直接改字节。

五、成果

现在的游戏库:黄金矿工、森林冰火人、死神VS火影、拳皇、是男人就下100层、双人飙车……童年的快乐在浏览器里复活了。技术上还挂了一台 Chromium 无头浏览器做自动化回归,防止哪天 Ruffle 更新把游戏跑挂了都不知道。

./add_game.sh "http://www.4399.com/flash/8940.htm"   # 黄金矿工
./add_game.sh "http://www.4399.com/flash/20082.htm"  # 森林冰火人

Flash 死了,但回忆可以复活。

上一篇
下一篇