Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pret/pokeemerald/llms.txt

Use this file to discover all available pages before exploring further.

All non-modern code contributions must produce a ROM that is byte-identical to the original. This page describes what that means in practice, and the conventions you must follow to get there.

The matching requirement

make compare is the gating test for every PR. Your code must compile to identical machine code as the original ROM. If make compare fails, the contribution is not ready to merge.
make compare
The target ROM is:
pokeemerald.gba  sha1: f3ae088181bf583e55daf962a92bb46f4f1d07b7

agbcc vs. modern builds

The original game was compiled with agbcc, an old ARM C compiler derived from GCC 2.7. Non-modern code must compile cleanly under agbcc.
The repository supports a MODERN build flag that uses a current GCC toolchain. Modern-only code is guarded with #if MODERN. Do not introduce modern-only constructs into code paths that must match the original ROM.
The most important agbcc constraint is C89/C90 compatibility. agbcc does not support C99 features. The most common mistake is declaring variables mid-block:
// Not valid under agbcc — declaration after a statement
void BadExample(void)
{
    DoSomething();
    int x = 5; // error: agbcc rejects this
}

// Correct — all declarations at the top of the block
void GoodExample(void)
{
    int x;
    DoSomething();
    x = 5;
}

Integer types

Use the GBA integer types from include/gba/types.h. Do not use int, unsigned int, short, etc. directly in game structs or function signatures where a specific width is required.
TypeWidthSignedness
u88-bitunsigned
u1616-bitunsigned
u3232-bitunsigned
s88-bitsigned
s1616-bitsigned
s3232-bitsigned
bool88-bitboolean
bool1616-bitboolean
bool3232-bitboolean

Naming conventions

Follow the patterns already established in the codebase:
  • Functions and variables: snake_case
  • Constants and macros: UPPER_CASE
  • Files: snake_case matching the existing src/ and include/ layout
Do not introduce camelCase or PascalCase for new identifiers outside of existing patterns.

Key macros from global.h

Several macros defined in include/global.h have matching implications — they reflect patterns GameFreak used that the compiler must reproduce exactly.

BLOCK_CROSS_JUMP

// Prevent cross-jump optimization.
#define BLOCK_CROSS_JUMP asm("");
Used to prevent agbcc from merging identical code blocks in a way that would change the output binary. Insert it where a function would otherwise mis-match due to the optimizer collapsing branches.

Array size macros

#define ARRAY_COUNT(array) (size_t)(sizeof(array) / sizeof((array)[0]))

// GameFreak used a macro called "NELEMS", as evidenced by AgbAssert calls.
#define NELEMS(arr) (sizeof(arr)/sizeof(*(arr)))
Use ARRAY_COUNT for new code. NELEMS is retained because it matches AgbAssert call sites in the original binary.

SWAP

#define SWAP(a, b, temp)    \
{                           \
    temp = a;               \
    a = b;                  \
    b = temp;               \
}
Use this macro wherever the original code performs an in-place swap via a temporary variable. Do not substitute stdlib or C99 compound-literal tricks.

Fixed-point math

The GBA lacks an FPU. GameFreak used Q-format fixed-point arithmetic throughout. The relevant macros are:
// Converts a number to Q8.8 fixed-point format
#define Q_8_8(n)   ((s16)((n) * 256))

// Converts a number to Q4.12 fixed-point format
#define Q_4_12(n)  ((s16)((n) * 4096))

// Converts a number to Q24.8 fixed-point format
#define Q_24_8(n)  ((s32)((n) << 8))
Corresponding inverse macros (Q_8_8_TO_INT, Q_4_12_TO_INT, Q_24_8_TO_INT) are also defined. Use the appropriate format to match the original code’s precision.

gametypes.h and save-compatible types

include/gametypes.h defines families of typedefs for fields that appear in save data. The naming convention encodes the underlying width so it is visible at a glance:
typedef u8  mapsec_u8_t;
typedef u16 mapsec_u16_t;
typedef s16 mapsec_s16_t;
typedef s32 mapsec_s32_t;
The smallest typedef in a family (e.g. mapsec_u8_t) is the canonical type — the width at which the value is actually stored in flash memory. Larger variants exist only to describe places where the original code handled the value inconsistently.
When adding a new persistent field, declare its typedef in gametypes.h following this pattern, and use the smallest type that fits the full range of valid values.

Save data constraints

The vanilla game uses 96% of its available EEPROM. The remaining free space is not contiguous:
  • SaveBlock1 can grow by at most 84 bytes
  • SaveBlock2 can grow by at most 120 bytes
  • Additional free space exists after PC-boxed Pokémon data, but is less accessible
Be conservative. Every byte added to a save block reduces the headroom available to all future contributors and ROM hackers.
Struct members that land in save blocks must be ordered to minimize compiler-inserted padding. Because field widths are visible in the gametypes.h typedef names, you can reason about padding without needing a sizeof check each time.

asm stubs

Functions not yet decompiled exist as assembly stubs under asm/. The goal of the project is to replace each stub with matching C code.
asm/
└── battle_anim_utility_funcs.s   ← example stub awaiting decompilation
When you decompile a function:
  1. Write the C implementation in the appropriate src/ file.
  2. Remove the corresponding .s stub (or the relevant label from it).
  3. Run make compare to confirm the output is still byte-identical.
Use asm_unified and the NAKED attribute when you need to write or preserve hand-written assembly within a C file:
#define asm_unified(x) asm(".syntax unified\n" x "\n.syntax divided")
#define NAKED __attribute__((naked))

UBFIX and BUGFIX

The codebase uses two annotation macros to mark intentional fixes:
#ifdef UBFIX
#define SAFE_DIV(a, b) (((b) != 0) ? (a) / (b) : 0)
#else
#define SAFE_DIV(a, b) ((a) / (b))  // matches original behavior
#endif
  • UBFIX — guards fixes for undefined behavior present in the original ROM. Enabled separately from a standard build.
  • BUGFIX — guards fixes for observable gameplay bugs.
Do not silently fix UB or bugs in matching code. If you encounter undefined behavior while decompiling, reproduce it faithfully and guard any fix behind the appropriate macro so that make compare still passes with the fix disabled.

IDE support macros

#if defined(__APPLE__) || defined(__CYGWIN__) || defined(__INTELLISENSE__)
#define _(x)        {x}
#define __(x)       {x}
#define INCBIN(...) {0}
#endif
These macros are defined only when an IDE preprocessor is detected. They exist solely to suppress false IDE errors. Do not rely on them in code that will actually be compiled.

Build docs developers (and LLMs) love