原生鸿蒙 GBC 模拟器开发实录:用 Anime4K 把 160×144 画面实时放大到 1080P
原生鸿蒙 GBC 模拟器开发实录:用 Anime4K 把 160×144 画面实时放大到 1080P
导语:160×144 像素,4.19 MHz 主频,1998 年的一台彩色掌机。二十多年后,我用 ArkTS + C++ 把它做成了鸿蒙原生应用,还给它加上了 Anime4K AI 超分滤镜和正版 ROM 校验。
摘要:本文完整复盘一款 HarmonyOS 原生 GB / GBC 模拟器 开发主要过程。第一章讲清在鸿蒙上开发模拟器的整体思路与工程搭建;第二章是全文重点,逐一拆解五个重难点——SameBoy 核心移植、NAPI + ArkTS 渲染架构、OpenGL ES 纹理绘制、Anime4K 超分移植、游戏速度调整。
关键词:
HarmonyOS鸿蒙GBC模拟器SameBoyNAPIXComponentOpenGL ESAnime4KGLSL超分辨率原生开发应用上架
| 项目 | 内容 |
|---|---|
| 应用名称 | GBC模拟器 |
| 应用市场 | 点击前往华为应用市场 |

文章目录
一、如何在原生鸿蒙开发GBC模拟器
1.1 先想清楚:模拟一台 GBC 到底要复刻什么
GBC 的硬件参数如下:
| 项目 | 规格 |
|---|---|
| 处理器 | Sharp LR35902(基于 Zilog Z80 定制) |
| 主频 | 4.194304 MHz,双速模式下 8.388608 MHz |
| 屏幕分辨率 | 160 × 144 像素 |
| 发售时间 | 1998 年 10 月 21 日(日本),定价 8,900 日元 |
但"能跑起来"和"跑得对"是两回事。要用软件扮演这台机器,必须让四个子系统在正确的时刻做正确的事:
| 子系统 | 职责 | 做不对会怎样 |
|---|---|---|
| CPU(SM83) | 逐条解释执行指令,节奏对齐 4.19 MHz | 游戏逻辑错乱、卡死 |
| PPU(图形) | 逐扫描线生成 160×144 画面 | 花屏、精灵错位、滚动撕裂 |
| APU(音频) | 4 声道合成 | 爆音、变调 |
| 卡带控制器(MBC) | 存储体切换、存档读写 | 大容量卡带读不出来、存档丢失 |
所以第一件事不是写代码,而是决定"自己写核心"还是"移植成熟核心"。 我选了后者,一直成熟的sameboy核心,理由在第二章 2.1 节展开。

1.2 技术选型:为什么是「ArkTS + NAPI + C++ 核心」
鸿蒙应用层是 ArkTS(声明式 UI),但模拟器核心是性能敏感的 C 代码。两者之间需要一个桥,这就是 NAPI(Native API)。
| 层次 | 选型 | 职责 |
|---|---|---|
| 界面层 | ArkTS / ArkUI | ROM 库、设置页、虚拟按键、菜单 |
| 桥接层 | NAPI(C++) | ArkTS 与 C++ 互相调用、帧数据传递 |
| 核心层 | C / C++(SameBoy) | CPU / PPU / APU / MBC 全部模拟逻辑 |
| 输出层 | XComponent + OpenGL ES / EGL | 纹理上传、滤镜后处理、上屏 |
| 音频输出 | 系统音频渲染管线 | PCM 实时播放 |
为什么不让 ArkTS 直接画像素?因为 160×144 的画面每帧要处理 23040 个像素,逐像素走 JS 引擎的桥,经过实测,性能较弱,模拟起来非常卡顿。
1.3 整体架构
1.4 工程搭建:DevEco Studio + Native C++ 模块
在 DevEco Studio 里新建工程时勾选 Native C++ 模板,会得到一个 ArkTS 主模块加一个 src/main/cpp 的 Native 目录。桥接层对 ArkTS 只需暴露少量接口,在 index.d.ts 里声明即可:
// index.d.ts —— 桥接层暴露给 ArkTS 的接口(示例)
export const loadRom: (buffer: ArrayBuffer) => boolean;
export const runFrame: () => void;
export const setInput: (mask: number) => void;
把 SameBoy 的 Core/ 目录整体拷进 cpp/sameboy/,在 CMakeLists.txt 里编译成静态库即可——SameBoy 核心是纯 C、零外部依赖,这一步几乎没有编译摩擦,这也是选它的重要原因。
entry/src/main/cpp/
├─ CMakeLists.txt
├─ napi_init.cpp # NAPI 模块注册与接口导出
├─ gbc_wrapper.cpp # 模拟器生命周期、线程、缓冲区管理
├─ render/ # EGL 初始化、纹理上传、滤镜链
│ ├─ egl_core.cpp
│ ├─ shader_pipeline.cpp
│ └─ shaders/ # 18 种滤镜的 GLSL ES 源码
└─ sameboy/Core/ # SameBoy 核心(未修改,保持原样便于升级)
1.5 最小闭环:从 GB_load_rom 到第一帧画面
先把"能出一帧画面"跑通,剩下的都是优化。 这是整个项目的第一条主线:
// 载入 ROM → 推进一帧 → 取回画面,这三步就是模拟器的骨架
GB_load_rom_from_buffer(&gb, rom_data, rom_size);
GB_run_frame(&gb);
uint32_t *pixels = GB_get_pixels_output(&gb);
就这么几行,画面就出来了。注意 GB_run_frame 的返回值——它返回"距上一帧实际经过的纳秒数",这个数字是后面做变速和音画同步的基础,第二章 2.5 节会用到。
二、GBC模拟器开发的重难点
这一章是全文重点。五个难点不是并列关系,而是层层依赖:
2.1 难点一:SameBoy 核心移植
为什么选 SameBoy
| 候选 | 语言 | 许可 | 取舍 |
|---|---|---|---|
| SameBoy | 纯 C | Expat(MIT 系) Copyright © 2015-2026 Lior Halphon | 精度极高、零外部依赖、核心与前端彻底解耦 |
| Gambatte | C++ | GPL | 精度好,但 GPL 传染性对商业分发不友好 |
| Peanut-GB | C | MIT | 体积极小,但精度做了取舍 |
决定性因素是架构:SameBoy 本身就是"核心库 + 多前端"的设计(Core/ 纯 C 无依赖,上层分别有 Cocoa、SDL、libretro 三套前端)。这意味着它的核心天生就是为被别的前端调用而写的——我要做的只是再写一个鸿蒙前端。
Core/ 目录共 44 个文件,模块划分非常干净:
| 文件 | 职责 |
|---|---|
sm83_cpu.c | LR35902 指令解释执行 |
display.c | PPU:扫描线渲染、精灵、窗口层 |
apu.c | 4 声道音频合成 |
mbc.c / memory.c | 卡带控制器与存储映射 |
save_state.c / rewind.c | 即时存档与倒退 |
timing.c | 时序调度 |
gb.c / gb.h | 对外 API 全部在这里 |
核心 API 的分层
移植的本质就是"找到该调用的那几个函数"。SameBoy 对外暴露上百个接口,实际项目里高频使用的只有这些:
| 用途 | 接口 |
|---|---|
| 生命周期 | GB_init()、GB_free()、GB_reset() |
| 载入 ROM | GB_load_rom()、GB_load_rom_from_buffer() |
| 推进运行 | GB_run()、GB_run_frame() |
| 取画面 | GB_get_pixels_output() |
| 输入 | GB_set_input_callback() |
| 存档 | GB_save_battery()、GB_load_battery()、GB_get_battery_dirty()、GB_clear_battery_dirty() |
| 变速 | GB_set_clock_multiplier()、GB_set_turbo_mode()、GB_set_turbo_cap() |
| 性能 | GB_set_rendering_disabled() |
| ROM 信息 | GB_get_rom_title()、GB_get_rom_crc32() |
注意:
GB_update_clock_rate()虽然也在头文件里,但它被#ifdef GB_INTERNAL包裹,属于核心内部接口,前端不应该调用。变速要用的公开接口是GB_set_clock_multiplier()。
移植时真正要改的三件事
核心代码一行没动,我要做的是把它的"外设"接到鸿蒙上:
- 输入:实现
GB_set_input_callback,把 ArkTS 传来的按键掩码返回给核心。核心会在每一帧的合适时机主动来问,不要自己去"推"。 - 画面:不去订阅逐行回调,而是每帧结束后调用
GB_get_pixels_output()取整块缓冲。逐行回调(GB_set_lcd_line_callback)留给需要做研究级调试的场景。 - 存档:核心不会自己写文件。正确姿势是轮询
GB_get_battery_dirty(),为真时调用GB_save_battery_to_buffer()落盘,然后GB_clear_battery_dirty()复位标志。
// 存档落盘:用"脏标志"驱动,而不是用定时器硬写
if (GB_get_battery_dirty(&gb)) {
GB_save_battery_to_buffer(&gb, save_buffer, GB_save_battery_size(&gb));
GB_clear_battery_dirty(&gb);
}
2.2 难点二:NAPI + ArkTS 的高效图形渲染架构
NAPI 的职责边界要划清楚
NAPI 只做两件事,其余一律不碰:
| 方向 | 内容 | 频率 |
|---|---|---|
| ArkTS → C++ | 启动、载入 ROM、改设置、按键状态 | 低频(按键除外) |
| C++ → ArkTS | 帧缓冲、音频数据 | 每帧一次 |
按键是唯一的例外:它虽然是 ArkTS → C++,但频率可能到每秒几十次。所以按键不要用"每次事件调一次 NAPI"的方式传,而是让 ArkTS 只更新一个原子变量,C++ 侧在输入回调里直接读。
最大的性能陷阱:每帧分配内存
这是整个项目里最容易被忽略、但影响最大的一处。
反面写法(我最初就是这么写的):
// 错误示范:每帧新建 ArrayBuffer,再整块拷贝一次
napi_create_arraybuffer(env, 160 * 144 * 4, &data, &buffer);
memcpy(data, GB_get_pixels_output(&gb), 160 * 144 * 4);
一秒钟 60 帧,就是每秒 60 次 90 KB 的内存分配 + 60 次拷贝。结果是GC 压力陡增、内存碎片累积、帧率周期性抖动。
正确做法是复用同一块缓冲区:
// 正确做法:缓冲区在初始化阶段创建一次并长期持有
static uint8_t *g_frame_buffer_data; // 只绑定一次
// 每帧只做"回填",零分配
memcpy(g_frame_buffer_data, GB_get_pixels_output(&gb), 160 * 144 * 4);
再把 napi_create_external 与 napi_create_reference 配合使用,让 ArkTS 侧"看到"的始终是同一个 ArrayBuffer,这样既能避免分配,也能避免 ArkTS 侧产生新的对象引用。
线程模型
模拟线程与 UI 线程必须分开,但要避免锁竞争:
| 线程 | 做什么 | 与另一线程的交互方式 |
|---|---|---|
| 模拟线程(Native) | GB_run_frame() 循环推进 | 读原子变量(按键)、写帧缓冲 |
| UI 线程(ArkTS) | 渲染、响应触控 | 写原子变量、读帧缓冲 |
共享数据只有两类:按键掩码(单字节)和帧缓冲(一块固定内存)。 都是"一写一读"的简单结构,用原子变量 + 双缓冲就能保证无锁安全,不需要引入互斥锁——在每帧都要跑 60 次的路径上加锁,代价比问题本身还大。
2.3 难点三:OpenGL ES 纹理绘制
为什么用纹理,而不是逐像素画
先看两条路线的开销对比:
| 方案 | 每帧工作量 | 结果 |
|---|---|---|
| ArkTS Canvas 逐像素 | 23040 次像素写入,全部经过 JS 引擎 | 无法维持满帧 |
| GL 纹理上传 + 着色器放大 | 一次 90 KB 纹理上传 + 一次 Draw Call | 占用极低 |
关键在于:160×144 只有 90 KB,上传这张小纹理几乎不花时间,放大交给 GPU 的采样器。
XComponent → EGL → GL 的链路
鸿蒙侧通过 XComponent 暴露原生渲染表面:ArkTS 用 XComponentController 拿到 surfaceId,Native 侧再用 OH_NativeXComponent_GetXComponentId 与之对应,然后建立 EGL 上下文:
// ArkTS 侧声明渲染组件:libraryname 指向我们的 NAPI 模块
XComponent({ id: 'gbcSurface', type: XComponentType.SURFACE,
libraryname: 'libgbc.so', controller: this.controller })
.onLoad(() => { /* Surface 就绪,开始渲染 */ })
.width('100%').height('100%')
Native 侧的关键三步:
eglGetDisplay()→eglInitialize()拿到显示连接;- 用 XComponent 提供的
EGLNativeWindowType创建EGLSurface; - 每帧
glTexImage2D()上传 160×144 像素 →glDrawArrays()绘制铺满视口的两个三角形 →eglSwapBuffers()上屏。
点对点缩放:整数倍才是清晰的
这是"画面糊不糊"的关键。非整数倍缩放会让像素落在半个像素的位置上,GPU 双线性插值一采样,边缘立刻发虚。
// 最近邻采样:把坐标换算成整数像素位置再取值,避开半像素导致的模糊
ivec2 texel = ivec2(vTexCoord * uTextureSize);
fragColor = texelFetch(uTexture, texel, 0);
texelFetch 按整数坐标取值,不做插值——这正是像素画需要的"硬边"。选好整数倍后,剩余的那几个像素用黑边补上,而不是拉伸铺满。

图 1 ~ 16 倍点对点缩放,满足不同设备的分辨率需求
2.4 难点四:Anime4K 超分移植
Anime4K 是什么
Anime4K 是 Bloc97 开源的一套高质量实时动漫放大与降噪算法,采用 MIT 许可。它的设计目标很明确:针对动画/线稿类画面做实时超分。
这恰好和 GBC 的画面特征对上了:
| GBC 画面特征 | Anime4K 的设计假设 | 匹配度 |
|---|---|---|
| 大面积纯色块 | 色块内部应保持平坦 | 高 |
| 明显的黑/深色描边线条 | 线条需要保留锐利、不能糊 | 高 |
| 像素边缘规整、少噪声 | 降噪与边缘保持算法收益大 | 高 |
这是我把 Anime4K 引入 GBC 模拟器的依据——不是随便找个滤镜套上去,而是画面特征确实匹配。
移植路径:GLSL → GLSL ES
Anime4K 官方提供 GLSL 实现,但它面向桌面 OpenGL(#version 330+)。移植到鸿蒙的 OpenGL ES 3.0,主要差异集中在四处:
| 差异点 | 桌面 GLSL | GLSL ES 3.0 |
|---|---|---|
| 精度限定 | 不需要 | 必须声明 precision highp float;,否则编译失败 |
| 片段输出 | gl_FragColor | 必须声明 out vec4 fragColor; |
| 纹理采样 | texture() 多版本 | 统一为 texture(),但需注意不支持隐式 LOD 的场景 |
| 数组与循环 | 长度可动态 | 循环边界必须是编译期常量,动态数组长度受限 |
其中最容易翻车的是最后一条:Anime4K 的某些 pass 用到了运行期决定的循环次数,直接搬过来在 ES 上编译不过。解决办法是把循环展开成固定次数,或把参数做成 #define 宏在编译期确定。
// AI 超分 pass 的核心思路(示意,非 Anime4K 原始 shader)
vec3 c = texture(uTexture, vTexCoord).rgb; // 中心像素
vec3 l = texture(uTexture, vTexCoord - vec2(uTexelSize.x, 0.0)).rgb; // 左邻域
vec3 r = texture(uTexture, vTexCoord + vec2(uTexelSize.x, 0.0)).rgb; // 右邻域
float gx = abs(luma(r) - luma(l)); // 水平梯度,luma 为 RGB 加权亮度
// 梯度小的方向就是"线条方向",沿它插值才不会跨越线条
vec3 blurred = (l + r + c * 2.0) * 0.25;
fragColor = vec4(mix(blurred, c, clamp(gx, 0.0, 1.0)), 1.0);
滤镜链的调度
18 种滤镜不是 18 段独立的代码,而是一条可配置的后处理链:
| 档位 | 滤镜 | 处理成本 | 适用场景 |
|---|---|---|---|
| 零成本 | NearestNeighbor | 只切采样方式 | 追求原汁原味像素感 |
| 低成本 | Linear、Bilinear、LCD、MonoLCD | 单次采样 | 日常游玩 |
| 中成本 | HQ2x、AAScale2x、AAScale4x、AAOmniScaleLegacy | 多邻域采样 | 想要平滑边缘 |
| 高成本 | Anime4K | 多 pass AI 超分 | 大屏 + 性能充裕 |
| 风格化 | CRT、FlatCRT | 多 pass 扫描线 | 怀旧显像管质感 |
关键设计决策:Anime4K 做成可选开关,而不是默认开启。
理由很实在——AI 超分要吃算力,而用户设备的性能差异极大。把它强开,等于把"低端机卡顿"变成所有用户的默认体验。做成可选项后,用户自己就能做"画质换性能"的取舍,这比开发者替所有人拍板更合理。

图 画面效果:支持 18 种滤镜,含 Anime4K AI 超分
2.5 难点五:游戏速度调整
变速不是"把帧率调高"
最直观的想法是"加速 = 每秒钟多跑几帧"。但这样做会立刻撞上两个问题:
| 做法 | 后果 |
|---|---|
| 简单提高每秒执行的帧数 | 音频来不及消费 → 爆音;画面丢帧 → 撕裂 |
| 只加速画面不加速音频 | 音画不同步,声音越拖越远 |
根因:GBC 的 CPU、PPU、APU 是按同一个时钟同步推进的。你只动其中一环,三者就脱节了。
用 GB_run_frame 的返回值做音画同步
2.1 节提到 GB_run_frame() 返回"距上一帧实际经过的纳秒数"。这个值就是音画同步的关键:
uint64_t elapsed_ns = GB_run_frame(&gb); // 距上一帧实际经过的纳秒数
// 按真实时间消费 PCM,而不是"每帧固定送 N 个采样点"
apus_consume_by_time(elapsed_ns, speed_multiplier);
核心思路是以音频时钟为主时钟驱动画面,而不是反过来。 因为人对音频断续的敏感度远高于画面轻微丢帧:画面偶尔掉一帧几乎察觉不到,声音一旦断一下就是刺耳的爆音。
动态同步策略
固定倍率还不够。实际运行中,设备的渲染耗时会有波动,所以我做了一套动态同步算法:
| 情况 | 表现 | 处理策略 |
|---|---|---|
| 画面跟不上 | 帧缓冲还没渲染完,新帧又来了 | 丢弃旧帧,音频继续走(音频优先) |
| 音频缓冲将耗尽 | 即将断音 | 微调帧推进节奏,让音频补上 |
| 加速倍率高 | 撕裂与爆音风险最大 | 动态调整倍率上限,按设备性能自适应 |
这套"画面、音频动态同步加速算法"是这款应用里性能调优花时间最多的一块,也是"加速时减少画面撕裂与爆音"这个特性的由来。
2.6 五个难点的经验总结
| 难点 | 最容易踩的坑 | 经验 |
|---|---|---|
| SameBoy 移植 | 试图"修改核心"来适配前端 | 核心一行不改,只实现回调与轮询接口 |
| NAPI 架构 | 每帧新建 ArrayBuffer | 缓冲区复用,零分配是底线 |
| OpenGL ES | 用非整数倍缩放铺满屏幕 | 先算最大整数倍,再居中加黑边 |
| Anime4K 移植 | 直接搬桌面 GLSL | ES 3.0 的精度声明与静态循环边界必须处理 |
| 速度调整 | 只提高帧率 | 改时钟倍率让 CPU/PPU/APU 同步加速,以音频为主时钟 |
三、GBC模拟器需要遵循哪些相关标准、规则以及法律
声明:本节是基于公开资料的整理与个人实践总结,不构成法律意见。具体合规判断请咨询专业律师,或以上架时平台的审核意见为准。
3.1 开源许可证合规:这是最容易被忽略的一条
用了别人的开源核心,就必须遵守它的许可证。这是硬性技术要求,不是道德倡议。
| 组件 | 许可证 | 我必须履行的义务 |
|---|---|---|
| SameBoy 核心 | Expat License(MIT 系) Copyright © 2015-2026 Lior Halphon | 保留版权声明与许可证全文;在应用内或关于页声明使用了该组件 |
| Anime4K | MIT License Copyright © Bloc97 | 同上 |
SameBoy 的 LICENSE 原文写的是 Expat License(常被统称为 MIT),而不是字面上的 “MIT”——这一点差别必须写对,不能想当然。
我的做法:
- 应用内"关于"页面列出第三方开源组件清单,包含组件名、许可证类型、版权归属;
- 仓库中保留各组件原始的
LICENSE文件,未做删改; - 核心代码保持原样、不做魔改,这样升级上游版本时不会产生许可证纠纷。
为什么没选 GPL 系核心:GPL 具有传染性,会要求整个应用以同许可证开源。Expat/MIT 只要求保留声明,对闭源分发的应用友好得多。技术选型阶段就要把许可证看清楚,别等到要上架才发现问题。
3.2 模拟器的法律地位与 ROM 版权
这两件事经常被混为一谈,但界限其实很清楚。
模拟器软件本身
在美国的司法实践中,模拟器软件本身曾多次被认定为合法:
| 判例 | 结论 |
|---|---|
| Sega v. Accolade(第九巡回法院,1992) | 为互操作性进行的逆向工程属于合理使用 |
| Sony v. Connectix(第九巡回法院,2000) | 逆向工程中的中间复制属于合理使用,Virtual Game Station 模拟器本身不侵权 |
| Sony v. Bleem | 商业模拟器本身得以继续销售 |
共性结论是:模拟器是一种技术中立的产品,能够运行他人软件不等于侵权。
但 2024 年的 Yuzu 案划出了新的红线
任天堂诉 Yuzu 案(2024 年,以和解告终)给整个行业提了醒:风险不在"模拟"这件事本身,而在模拟器是否绕过了技术保护措施、是否在为盗版提供便利。
具体来说,以下行为会显著抬高法律风险:
| 高危行为 | 为什么危险 |
|---|---|
| 内置解密密钥(如主机固件的 prod.keys) | 涉嫌规避技术保护措施 |
| 内置或引导下载 ROM | 直接参与侵权传播 |
| 用"内置游戏"作为卖点引流 | 以他人作品牟利,主观恶意明显 |
| 提供绕过正版验证的教程 | 教唆侵权 |
ROM 的版权归属
游戏 ROM 是游戏程序的复制件,受《中华人民共和国著作权法》中关于计算机软件的保护。核心结论很简明:
- 模拟器是工具,ROM 是作品,两者的法律地位完全不同;
- 复制、传播 ROM 需要权利人的许可,个人备份自己合法拥有的卡带与从网络下载 ROM是两件性质不同的事;
- 本站不给"ROM 是否合法"做背书,判断依据是用户是否持有正版卡带或官方授权版本。
3.3 中国大陆上架必须满足的合规要求
依据华为官方文档《应用资质审核要求》,AppGallery 的合规要求在近两年明显收紧,其中 APP 备案是硬门槛:
| 要求 | 具体内容 | 是否强制 |
|---|---|---|
| APP 核准(备案) | 依据《工业和信息化部关于开展移动互联网应用程序备案工作的通知》,APP 主办者应履行核准(备案)手续;未履行备案的,不得从事 APP 互联网信息服务 | 强制 |
| 增值电信业务经营许可证 | 从事增值电信业务的需提供 | 按业务类型 |
| 软件著作权证书 | 《计算机软件著作权登记证书》《APP 电子版权证书》或《软件著作权认证证书》 | 官方原文为"非必选资质",但建议申请以保护知识产权 |
| 游戏版号 | 《网络游戏出版物号(ISBN)核发单》 | 仅游戏类应用需要 |
| 隐私政策 | 需提供可公开访问的链接,应用内可查 | 强制 |
关于版号:本应用在应用市场的分类是「工具」,不是「游戏」,因此不涉及 ISBN 版号。这一点对模拟器类应用很关键——但分类最终由平台审核判定,不要把"我认为它是工具"当作当然结论。
3.4 我的落地做法:把合规做进产品里
法律要求是底线,但更稳妥的做法是把它变成产品的一部分。这是我在这个项目上最坚持的一点:
| 做法 | 具体落地 |
|---|---|
| 不内置任何游戏 | 安装包里没有任何 ROM 文件,装完打开是空的 ROM 库 |
| 内置正版 ROM 校验 | 导入时对 ROM 做指纹校验,来源异常或被篡改的文件不予放行 |
| 支持 DAT 数据库导入 | 校验库不写死,可导入社区维护的指纹数据库持续扩充 |
| 不提供下载入口 | 应用内没有 ROM 下载、推荐、外链跳转 |
| 不做"私藏分享" | 不搞"内置 N 个经典游戏、开箱即用"那一套 |
| 开源组件合规 | 关于页列出 SameBoy / Anime4K 的许可证与版权声明 |
| 权限最小化 | 未申请敏感权限,访问文件走系统安全访问机制 |
需要坦白的一点
没有任何一款模拟器能真正证明"你拥有这款游戏"。
指纹校验能判断的是:这份文件是不是某个游戏的原始副本、有没有被改动过、是不是已知的正式游戏。但它查不到你抽屉里有没有那张卡带。
所以校验只是第一道门槛,真正的边界在用户自己手里。我能做的是:
- 不提供盗版内容;
- 不提供获取盗版内容的便利;
- 把这句话同时写进应用和写在这里:请使用你合法拥有的游戏备份。
四、这款GBC模拟器的特色功能
4.1 Anime4K AI 超分
把 Anime4K 引入 GBC 模拟器,是这款应用最核心的差异化点。
| 项目 | 说明 |
|---|---|
| 技术来源 | Anime4K(Bloc97,MIT 许可),面向动画线稿的实时超分算法 |
| 移植方式 | 桌面 GLSL → OpenGL ES 3.0 GLSL ES 重写 |
| 实际效果 | 线条边缘更锐利,纯色区域更干净,大屏下观感提升明显 |
| 性能定位 | 可选开关,不强制开启 |
具体移植细节见 2.4 节。
4.2 正版 ROM 校验与 DAT 数据库
这是我在这个项目上最"较真"的功能。
它是怎么工作的
导入 ROM 时,应用会读取 ROM 的 CRC32 指纹(对应 SameBoy 的 GB_get_rom_crc32()),与校验库比对,再决定是否放行:
| 校验结果 | 应用的处理 |
|---|---|
| 校验通过 | 正常导入,加入 ROM 库 |
| 校验不通过(来源异常 / 文件被篡改) | 明确提示原因,不予放行 |
| 文件损坏或格式不支持 | 提示具体原因,建议重新导出备份 |
为什么要做成"可导入 DAT"
如果把校验库写死在安装包里,会立刻遇到两个问题:
- 库会过时:新的游戏、新的版本无法覆盖;
- 无法扩容:用户手上有更权威的指纹数据也用不上。
所以我把校验库做成可导入的 DAT 文件——DAT 是模拟器社区长期使用的指纹数据库格式,里面记录了大量游戏的标准 CRC32 等校验值。用户导入 DAT 后,校验范围立刻扩展到整库,而不是被安装包锁死。
这个设计让"正版校验"从一个宣传点变成了真正可用的机制。

图 ROM 库:支持批量导入导出 ROM 和存档
4.3 18 种滤镜与 1 ~ 16 倍点对点缩放
| 能力 | 说明 |
|---|---|
| 滤镜数量 | 18 种,覆盖像素风、智能放大、AI 超分、真机模拟四类 |
| 缩放倍率 | 1、2、4、6、8、10、12、14、16 倍,全部为整数倍 |
| 缩放意义 | 整数倍保证像素边缘锐利,不产生半像素模糊与摩尔纹 |
| 大屏适配 | 16 倍缩放覆盖高分辨率设备,剩余像素用黑边补齐 |
4.4 画面、音频动态同步加速
| 项目 | 说明 |
|---|---|
| 加速原理 | 通过时钟倍率让 CPU / PPU / APU 同步加速,而非简单提高帧率 |
| 同步策略 | 以音频时钟为主时钟驱动画面 |
| 解决的问题 | 加速时的画面撕裂与爆音 |
| 上限控制 | 按设备性能自适应,避免低端机强行高倍率 |
4.5 即时存档与批量导出
- 游戏内存档:和当年一样,在游戏里找存档点存档(写入卡带 SRAM);
- 即时存档:把当前整个运行状态冻结保存,每个 ROM 提供多个存档位;
- 存档管理:查看时间戳、单独加载或删除,支持批量导出,换设备时直接迁移;
- 自动落盘:基于核心的"脏标志"机制自动写盘,不需要用户手动保存。

图 存档管理:存档批量导出,快速加载历史存档
4.6 自研测试 ROM《宇宙探索》
模拟器最怕的不是性能,而是"这个游戏能跑、那个游戏花屏"。
只靠现成商业游戏去测,会遇到两个问题:一是覆盖面靠运气,二是出问题时说不清是模拟器的错还是测试用例本身的限制。
所以我写了一个 GBC 测试程序《宇宙探索》——用真实的 GBC 硬件行为去跑,能直接暴露出 PPU 渲染、精灵优先级、滚动、中断时序等环节的问题。有了它,兼容性验证从"碰运气"变成了"有靶子"。

图 自研 GBC 测试 ROM《宇宙探索》

图 9 游戏运行画面
4.7 隐私与无广告
| 项目 | 做法 |
|---|---|
| 权限 | 未申请敏感权限,权限列表为空 |
| 文件访问 | 使用 HarmonyOS 安全访问机制:只能访问用户主动选定的文件,不读取存储目录中的其它内容 |
| 广告 | 纯净无广告 |
| 隐私政策 | 查看隐私政策 |
五、如何上架鸿蒙应用商店
5.1 上架前的三件事
| 顺序 | 事项 | 说明 |
|---|---|---|
| 1 | 开发者账号与实名认证 | 个人开发者需完成实名认证,企业需企业认证;未实名无法发布 |
| 2 | APP 备案(核准) | 依据工信部要求,上架前须完成 APP 备案并提供备案号;这是硬门槛 |
| 3 | 软著(建议) | 华为官方原文将其列为"非必选资质",但建议申请以保护知识产权 |
特别提醒:先把备案办了。备案有周期,等开发完才想起来办,会把上架时间整体往后拖。
5.2 签名与打包
HarmonyOS 应用通过数字证书(.cer) 和 Profile 文件(.p7b) 保证完整性,最终打包为 App Pack(.app) 用于上架。
| 步骤 | 操作 |
|---|---|
| 生成密钥 | DevEco Studio → Build → Generate Key and CSR,生成 .p12 密钥库与 CSR 文件 |
| 申请证书 | 在 AppGallery Connect 用 CSR 申请发布证书 .cer |
| 申请 Profile | 创建发布 Profile .p7b(需绑定包名与证书) |
| 打包 | Build → Build App(s)/Hap(s),产出 .app / .hap |
| 校验 | 用 hap-sign-tool 或 DevEco 自带校验确认签名有效 |
# 通过命令行构建(CI 场景常用)
hvigorw assembleHap --mode module -p product=default -p buildMode=release
踩坑提示:调试证书与发布证书不能混用。用调试证书打出来的包上传会被直接拒绝,错误信息往往不直观,我第一次上架就在这卡了一会儿。
5.3 在 AppGallery Connect 提交
关键填写项:
| 项目 | 注意点 |
|---|---|
| 应用分类 | 本应用选择「工具」;分类会影响所需资质(游戏类需版号) |
| 隐私政策 | 必须是可公开访问的链接,且应用内可查 |
| 权限说明 | 每个申请的权限都要写明使用场景;本应用未申请敏感权限,这一项反而简单 |
| 素材 | 图标、截图有尺寸与数量要求,建议按平台提供的两套尺寸(手机 / 平板)分别上传 |
| 更新说明 | 每版都要写清楚改了什么,审核会看 |
5.4 审核容易被驳回的几类问题
| 驳回类型 | 常见原因 | 规避方法 |
|---|---|---|
| 资质缺失 | 未提供 APP 备案号 | 上架前完成备案 |
| 隐私政策问题 | 链接打不开、内容空泛、未列出权限用途 | 逐条写明收集的信息类型与用途 |
| 权限越界 | 申请了与功能无关的权限 | 权限最小化,用系统安全访问机制替代 |
| 使用调试证书 | 用调试证书打了发布包 | 严格区分调试/发布证书 |
| 内容违规 | 含侵权内容、引导下载盗版 | 不内置侵权资源,不提供下载入口 |
| 名称/包名不一致 | 应用名称与资质文件不符 | 保持各处名称一致 |
对模拟器类应用特别提醒:审核会重点关注是否内置游戏资源、是否提供下载入口。这两条是明确的红线,从产品设计阶段就要避开——这也是我把"正版 ROM 校验"做进产品的原因之一。
5.5 我的上架结果
| 项目 | 结果 |
|---|---|
| 应用名称 | GBC模拟器 |
| 分类 | 工具 |
| 上架状态 | 已上架,可正常下载 |
| 当前版本 | 1.1.3(versionCode 1000013) |
| 安装量 | < 1 万次安装 |
| 用户评分 | 4.5 分(19 人评分,其中 5 星 15 人) |
对一个个人开发者的工具类应用来说,这个开局我认为是达标的——真正有价值的反馈往往来自低分评价,它比好评更能指出兼容性问题。
5.6 上架检查清单
- 开发者账号已完成实名认证
- APP 备案已通过并拿到备案号
- 软著 / 电子版权证书已申请(建议项)
- 隐私政策已上线且链接可公开访问
- 应用内可查看隐私政策与第三方开源组件声明
- 用发布证书打包,已校验签名有效
- 应用分类选择正确(工具 / 游戏,影响资质要求)
- 权限清单与功能一一对应,无多余权限
- 图标与截图素材齐备,覆盖手机 / 平板两套尺寸
- 版本更新说明已填写
- 未内置任何游戏资源、未提供 ROM 下载入口
- 已在真机完成核心功能回归测试
下载与参考资料
下载
开发参考
- HarmonyOS 开发者官网 —— 开发者账号注册与实名认证
- DevEco Studio 下载 —— 官方集成开发环境
- HarmonyOS 开发者文档中心 —— ArkTS / ArkUI / NAPI / XComponent 文档
- AppGallery Connect —— 应用创建、软件包管理与提交审核
- 应用资质审核要求(官方) —— 备案、软著、版号的官方口径
- SameBoy 开源项目 —— 本应用使用的 GB / GBC 模拟器核心(Expat License)
- Anime4K 开源项目 —— 实时动漫超分算法(MIT License)
- OpenHarmony 开源项目(Gitee) —— 阅读系统源码
相关阅读
- 《使用 Python 开发任天堂 Game Boy 模拟器|PyBoy 开发实践》
- 《跟着官方教程学习鸿蒙 ArkTS 语言:6 大核心知识点深度解读与实践指南》
- 《教你如何获取 HarmonyOS 应用开发者基础认证》
更多推荐

所有评论(0)