Skip to content

源码兼容与适配 — 概念

兼容层架构

pokeemerald-wasm 通过三层兼容机制,在不修改原始游戏逻辑的前提下实现 GBA → WASM 的跨平台适配:

条件编译模式

#if WASM / #else / #endif 模式

这是最常见的模式,用于在 WASM 和 GBA 构建之间切换代码路径:

主要适配场景

适配场景涉及文件WASM 替代方案
主循环拆分main.cAgbMain() 提前返回,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 指针数组
  • 游戏引擎通过这个指针数组执行特效脚本,不需要知道底层实现的差异

相关概念