Appearance
源码兼容与适配 — 概念
兼容层架构
pokeemerald-wasm 通过三层兼容机制,在不修改原始游戏逻辑的前提下实现 GBA → WASM 的跨平台适配:
条件编译模式
#if WASM / #else / #endif 模式
这是最常见的模式,用于在 WASM 和 GBA 构建之间切换代码路径:
主要适配场景
| 适配场景 | 涉及文件 | WASM 替代方案 |
|---|---|---|
| 主循环拆分 | main.c | AgbMain() 提前返回,WasmRunFrame() 逐帧执行 |
| 音频系统 | m4a.c, sound.c | 跳过 GBA 音频硬件初始化和播放 |
| Flash 存储 | agb_flash.c 等 | 跳过硬件检测,始终假定 Flash 存在 |
| 链接通信 | link.c, union_room.c | 禁用无线/有线链接功能 |
| RTC 时钟 | main.c | 不初始化 RTC 硬件 |
| 输入系统 | field_player_avatar.c | 跳过部分输入硬件相关代码 |
| 场景特效 | wasm_field_effect_scripts.c | 用 C 结构体重写汇编脚本表 |
| 命名屏幕 | naming_screen.c | 适配输入法相关代码 |
| 宝可梦数据 | pokemon.c | 处理特定数据结构差异 |
main.c 中的关键适配
main.c 是适配最密集的文件,包含 4 处 #if WASM 守卫:
垫片头文件详解
为什么需要垫片?
GBA 开发工具链(agbcc / arm-none-eabi-gcc)和 Clang 对 C 标准库函数的声明方式不同:
stdio.h 垫片
c
// include/wasm/stdio.h
#ifndef GUARD_WASM_STDIO_H
#define GUARD_WASM_STDIO_H
#include <stdarg.h>
int sprintf(char *str, const char *format, ...);
int vsprintf(char *str, const char *format, va_list args);
int snprintf(char *str, unsigned long size, const char *format, ...);
#endif声明的函数:
| 函数 | 用途 |
|---|---|
sprintf | 格式化字符串写入缓冲区 |
vsprintf | 可变参数列表版本 |
snprintf | 带大小限制的安全版本 |
注意:这些函数只有声明没有实现。在 WASM 中,它们由 Clang 的内建实现提供(编译器会自动生成对应的 WASM 代码)。
stdlib.h 垫片
c
// include/wasm/stdlib.h
#ifndef GUARD_WASM_STDLIB_H
#define GUARD_WASM_STDLIB_H
int abs(int n);
#endif仅声明 abs()——这是游戏代码中使用的唯一 stdlib 函数。保持最小化声明减少潜在冲突。
string.h 垫片
c
// include/wasm/string.h
#ifndef GUARD_WASM_STRING_H
#define GUARD_WASM_STRING_H
#include <stddef.h>
void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
void *memset(void *s, int c, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
char *strcat(char *dest, const char *src);
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
size_t strlen(const char *s);
char *strchr(const char *s, int c);
#endif声明了 11 个字符串操作函数。这些是最常用的内存/字符串操作,游戏代码大量使用。
wasm_field_effect_scripts.c
这是项目中唯一的 WASM 专用源文件(整个文件被 #if WASM ... #endif 包裹)。
为什么需要它?
原始 pokeemerald 的场景特效脚本表(gFieldEffectScriptPointers)是用 ARM 汇编编写的,包含字节码指令序列(如"加载调色板"、"调用本地函数"等)。这些汇编脚本无法直接编译为 WASM。
替代方案
关键设计:
- 使用
__attribute__((packed))结构体精确模拟原始字节码的内存布局 - 定义宏(
SCRIPT_CALL_NATIVE等)简化脚本定义 - 最终生成与 GBA 版本兼容的
gFieldEffectScriptPointers指针数组 - 游戏引擎通过这个指针数组执行特效脚本,不需要知道底层实现的差异
相关概念
- WASM 编译管线 —
-DWASM=1和-I include/wasm编译标志 - 项目架构概览 — 兼容层在整体架构中的位置
- GBA 硬件模拟 — JS 运行时替代的 GBA 硬件功能
- WebAssembly — WebAssembly 目标平台的特性