Last active
February 9, 2025 07:40
-
-
Save beanieaxolotl/6a66f0b9cb1444c82263f6025c4d22a0 to your computer and use it in GitHub Desktop.
WIP documentation of the A2M GBA game engine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A2Mx Game Engine | |
> The A2Mx game engine is a 2D engine that has been used for platformer, isometric and top-down games, with support for SRAM saving (or passwords), and cutscenes. It was developed by Stéphane Hockenhull[1] (https://rv6502.ca/), with the tools being developed by Benoit Hubert[2]. | |
> A2Mx is not the confirmed name of the engine, but it could be judging by the names "A2M4" and so on appearing in the ROM. | |
> Known games | |
A2M0: | |
- Enchanted - Once Upon Andalasia (last game) | |
A2M1: | |
- Kim Possible 2: Drakken's Demise | |
- Happy Feet | |
- everGirl | |
- Scooby-Doo: Mystery Mayhem (debut) | |
A2M2: | |
- Lizzie McGuire 2 - Lizzie Diaries | |
A2M4: | |
- Chicken Little | |
A2M5: | |
- Ed Edd & Eddy: The Mis-Ed-Ventures | |
A2M7: | |
- The Sims 2: Pets | |
A2MR: | |
- Monster House | |
A2MT: | |
- Drake & Josh | |
A2M4: | |
Collision tiles: | |
These are stored as 8bpp 16x16 tiles (although treated as a 1bpp array) | |
For each pixel: | |
00 - Air | |
01 - Solid ground | |
Entirely empty or solid tiles (with tile indexes of 00 and 01) are generated at runtime. | |
Debug menu: | |
- The debug strings are stored in plain text, as opposed to the internal encoding seen in the dialog text. These are the known strings: | |
> Level | |
> MapX | |
> MapY | |
> FX Player | |
> Music Player | |
> Obj Viewer | |
> Asset Viewer | |
- The debug menu code is present in the game's ROM, but no remaining code seems to call the relevant functions. | |
Sound engine: | |
- All of the above games use Shin'en's GAX Sound Engine for their audio mixing. The only games that don't use this engine also don't use Stéphane's game engine (i.e High School Musical). Refer to the GAX documents for a more thorough overview of how it works (to do). | |
For example, in Chicken Little, the data is stored like this, directly after the debug menu strings: | |
u32[*] - Pointers to each subsong | |
(These have been observed to be in any order. One example of this is Monster House) | |
GAX_Blob - Music data (music.o) | |
GAX_Blob - SFX data (fx.o) | |
To-do: | |
- Where is the FX pointer? | |
- Chicken Little's FX mapping differs from the internal FX mapping in the blob. | |
Save RAM: | |
- The save data format is highly customizable, and allows up to 3 save slots at a time. The smallest save size I have seen is in Chicken Little (0xA0 bytes), while the largest is Monster House (0xF7 bytes). | |
- Before every save file is a 16-byte header, which is also used for checking the integrity of all of the saves on the SRAM chip (for detecting corruption or intentional tampering): | |
- u16 - Checksum | |
- u16 - ?? | |
- u32 - ?? | |
- char[4] - Game engine identifier | |
- u8 - ?? (unused?) | |
- u8 - ?? | |
- u8 - ?? (unused?) | |
- u8 - ?? (unused?) | |
Passwords: | |
Only spotted in Drake & Josh so far. | |
In that game, the password is composed of 4 entries, | |
with 8 icons each. | |
To do: everything else | |
Dialogue system: | |
Text data: | |
u16 - Global font index | |
This is the font to use for the entire text stream. | |
For example, Chicken Little uses these values for its fonts: | |
> 0x0000 - Font (Menus) | |
> 0x0100 - Font (Cutscenes) | |
> 0x0300 - Alien characters | |
u16 - Global palette index | |
u16 - Unused (always set to 0) | |
u16 - Portrait index | |
> This is unused in A2M games that use the talking-heads cutscene system. | |
Encoded text stream data begins here: | |
The text data is remapped in a certain way: | |
If a character is lowercase, the character is XORed by 32. | |
If a character is uppercase or is a symbol (i.e punctuation), | |
the character is incremented by 32. | |
Any special characters are remapped using a table? | |
Any special characters with a resulting value more than 0x7A | |
has a 0x80 byte appended at the start. | |
Any Japanese characters start from 0x80C0. The small characters are seperated into 80F5-80FF. | |
> For example: | |
0x80C1 = あ | |
0x80C2 = い | |
0x80C3 = う | |
0x80C4 = え | |
0x80C5 = お | |
0x80C6 = か | |
... | |
0x80D3 = て | |
The alternate characters are 8101+. | |
> For example: | |
0x8101 = が | |
Every space is the byte string 0xFFFC. | |
u16 - 0xFFFE -> this ends the text stream. | |
This is padded with 0x00 to the nearest dword | |
Dialog data: | |
u16 - Frames to wait before executing | |
u8 - Skips command if not 0 | |
u8 - Always set to 5. This is treated as a magic number, | |
and is checked for every dialog command. If this isn't | |
set to 5, the command is ignored | |
u8 - Command type | |
00 -> Create text | |
01 -> Create mugshot actor | |
02 -> Hide mugshot actor | |
03 -> Create text (w/ dialog box) | |
04 -> ~~ | |
05 -> End cutscene | |
06 -> ~~ | |
07 -> ~~ | |
0A -> Load level ID | |
0C -> Play sound ID | |
0D -> Fade out to black | |
0E -> Fade in from black | |
0F -> Stop sound ID | |
11 -> Related to screen variables | |
12 -> Related to screen variables | |
13 -> Set music update boolean | |
14 -> Play music ID | |
<Command types> | |
Create text (0x00): | |
u8 - Character ID | |
u8 - Dialog line index | |
u8 - Font offset (broken?) | |
u8 - Unused | |
Create mugshot actor (0x01): | |
u8 - Character mugshot index | |
> Any dialog that is displayed after | |
the mugshot is loaded must have its ID | |
match this index. If not the cutscene system | |
will skip past it until the bytestream is valid. | |
u8 - Mugshot direction | |
0 -> left | |
1 -> right | |
u8 - Reserved | |
u8 - Unknown (always set to 0) | |
Hide mugshot actor (0x02): | |
u8 - Character mugshot index | |
bool - Whenever or not to slide the sprite off-screen | |
> Typically set to 0 in most cases | |
Create textbox (0x03): | |
u8 - Character ID | |
u8 - Dialog line index | |
u8 - Font offset (broken?) | |
u8 - Dialog box style | |
?? (0x04): | |
> unknown | |
?? (0x05): | |
u8 - Reserved | |
u16 - Set to 0x2 if ending a cutscene | |
?? (0x06): | |
> unknown | |
?? (0x07): | |
> unknown | |
Load level ID (0x0A): | |
u8 - Level index | |
Play sound ID (0x0C): | |
u8 - Sound effect index to play | |
?? (0x0D): | |
> No parameters | |
?? (0x0E): | |
> No parameters | |
Stop sound ID (0x0F): | |
u8 - Sound effect index to stop | |
Related to screen variables (0x11, 0x12): | |
u8 - Unknown | |
Set music update boolean (0x13): | |
bool -> Truth value to update the boolean to | |
Play music ID (0x14) | |
u8 -> Music track index | |
Cutscene data: | |
To do: everything! | |
This is seen in games such as Monster House and The Ant Bully. | |
Use cases for this are ingame cutscenes w/ dialog boxes. | |
Levels: | |
Tilemap data: | |
u8 - Tile packing type bitfield | |
bit 0 -> Compress tilemap (used for screens) | |
bit 1 -> Compress tilemap (used in levels) | |
bit 2 -> ~~ | |
bit 7 -> ~~ (Always set?) | |
u8 - Tile-related | |
00 -> Shifts tilemap to the right slightly | |
01 -> ~~ | |
02:FF -> Tile 0 with palette 0 across the screen | |
u8 - Tile width | |
u8 - Tile height | |
u16 - Map width | |
u16 - Map height | |
> If the map width/height is less than what it's | |
designed for, The display width/height is modulo'd by this number | |
u8 - Tilemap properties | |
bit 0:1 -> Priority index (0 to 3) | |
bit 2:3 -> Tileset index | |
bit 4:6 -> Unused | |
bit 7 -> 8bpp mode | |
u8 - Unknown | |
u8 - Tile data read offset | |
u8 - Reserved | |
If packing bit 1 is set: | |
u32 - Tilemap address (not ROM address) | |
> Calculated and displayed correctly if the address is word-aligned | |
<Unused here> | |
> u16 - Height in pixels | |
> u8 - Unused | |
> u8 - Width in pixels | |
u32 - Mostly (if not always) set to 0x00000000 | |
u8 - ~~ (Decoding bitfield?) | |
u8 - ~~ | |
u8 - ~~ | |
<Tilemap data (Compressed)> | |
u8 - Number of commands to decode | |
u8 - Uncompressed size in bytes? | |
- The non compressed entries are just u8 indexes into the metatile data | |
- Compression is unknown (pretty unintuitive as to how it works) | |
> Padded to nearest dword | |
If not: | |
u32 - Tilemap address (not ROM address) | |
> Calculated and displayed correctly if the address is word-aligned | |
<Unused here> | |
> u16 - Height in pixels | |
> u8 - Unused | |
> u8 - Width in pixels | |
u32 - Mostly (if not always) set to 0x00000000 | |
u8[*] - Tile index | |
> Number of indexes is calculated using: | |
map width * map height | |
Metatile definition data: | |
u16 - Subtile properties | |
-> YYYYXXXXXXXXXXXX | |
bits 0-11 -> tile ID | |
bits 12-15 -> palette ID | |
For example: if a 16x16 metatile rule is described, a single metatile takes up a single dword, and is formatted like this: | |
[A] [B] | |
[C] [D] ... | |
The tiles are stored in ascending, rightwards order here. | |
If the header declares absolute tile indexing: | |
u8 - Palette index (0x00-0x3F) | |
The resulting index into the palette RAM is calculated as is: | |
> (a & 0x40) / 4 | |
u8 - Reserved | |
u16 - Absolute index into tile data | |
Collision map: | |
The collision data is stored as an 8bpp bitmap array. Each entry is an entry in the level's collision map. Each entry is also represented in-game as a 16x16 collision block. The size of the data is the W tile count * the H tile count. | |
For example: These are the values that Chicken Little uses: | |
0x00 -> nothing | |
0x01 -> ground tile | |
0x2c -> jump thru solid (left) | |
0x30 -> jump thru solid | |
0x2e -> jump thru solid (right) | |
0x37 -> bouncy tile | |
0x58, 0x50 -> climbable tile (not solid) | |
0x2c, 0x30 -> climbable tile (solid) | |
Object list (WIP): | |
To do: Where are the positions stored, and how? | |
Object property chunk (WIP): | |
... | |
u16 -> Object type | |
u16 -> Object params / state value | |
u16[2] -> set to 0xFFFF if not in use, unknown purpose | |
u16 -> Object ID (read by the game, but not used) | |
u16 -> ? | |
u16 -> ? | |
u16 -> Count of objects | |
... | |
Footer (WIP): | |
Unknown data[*]: | |
s16 - X | |
s16 - Y | |
u16 - Unused | |
u16 - Packet ID | |
Unknown pointers: | |
u32*[*] - ~~ | |
Layer setting[*]: | |
u8 - Tilemap ID | |
u8 - Bitfield(?) | |
-> bit 0 - Disable horizontal scrolling | |
-> bit 1 - Disable all scrolling | |
-> bit 3 - Enable constant scrolling | |
u16 - Reserved | |
s16 - Param #1 | |
s16 - Param #2 | |
char[16] -> Level name (i.e School01a, Logo_A2M, etc.) | |
-> padded with 0x00 | |
<8 bytes, this depends on size> | |
u16 - Global scanline effect params | |
u16 - ~~ | |
bool - Use absolute tile indexing | |
bool - ~~ | |
u16 - ~~ | |
u16 - Layer count | |
u16 - Reserved | |
u32 - ~~ | |
u32 - ~~ | |
u32 - ~~ | |
u16 - ~~ | |
u16 - Tileset index | |
u16 - Tileset-related (unknown) | |
u16 - Palette index | |
u32 - Reserved | |
u32 - ~~ | |
u32 - ~~ | |
u32* - ~~ | |
u32 - ~~ | |
u16 - Pointer list width | |
u16 - Pointer list height | |
u32 - Pointer list address (relative) | |
u32* - ~~ | |
u32 - Layer setting data start offset | |
-> In Chicken Little, the offset is calculated by adding this offset to the ROM address 0x81D8474 | |
u32[2] - ~~ | |
u32 - Unused | |
u32* - ~~ | |
Character controller (WIP): | |
> Notes: | |
- In Chicken Little, there is a u32 constant for velocity changes, which is treated as if it were a float. | |
- These are the max velocity speed for specific movement actions, like moving in-air, moving left or right, moving then jumping, etc. | |
- Collision tiles are interacted with per-pixel. | |
- Some games use fixed point 16.16 floats for positions[3]. | |
Palettes: | |
Stored in the general GBA color format, but the first color of each row is black instead of pink/magenta. This doesn't matter as that color is simply ignored. | |
Objects / animations: | |
Palettes seem to be not a part of this data, and are called seperately. | |
This allows for calling of the same object with different display properties (i.e transparent tutorial player NPCs like those seen in Chicken Little or Kim Possible 2) | |
<Unknown data> | |
Sprite graphic data: | |
- Stored in 4bpp or 8bpp little-endian tiles (8x8 px per tile). | |
- This stores every tile of an animation frame | |
- This is padded with 0x00 to the nearest dword | |
Animation data: | |
Keyframe: | |
u16 - Keyframe chunk size | |
u32 - Sprite graphic data offset | |
> Seems to be different for every frame. This suggests that | |
this is remapping the graphics data onto every subsprite. | |
> This is also not a ROM address, which implies there is | |
some offset calculation going on to convert it into a ROM pointer | |
u16 - ? | |
Sprite placement: | |
s16 - Sprite offset (X) | |
s16 - Sprite offset (Y) | |
u16[2] - ? | |
u8 - Subsprite count | |
> Number of sprites in a given animation frame. | |
> Maximum is ~97 sprites | |
s8 - Frame speed | |
u8 - Frame ID | |
bool (u8) - Is a sound playing back on this keyframe? | |
u8 - ? | |
u8 - Known values are 8 and 0 | |
Subsprite placement: | |
For each subsprite: | |
s8 - Subsprite Y position | |
u8 - Subsprite properties #1 | |
bit 0 - Transform | |
bit 1 - Visibility | |
bit 2 - Blend effect | |
bit 3 - Window | |
bit 4 - Mosaic | |
bit 5 - Bit depth | |
0 -> 4bpp | |
1 -> 8bpp | |
bit 6 - Double/halve tile height | |
(i.e 8x8 -> 8x16, 32x32 -> 32x16) | |
bit 7 - Double/halve tile width | |
(i.e 8x8 -> 16x8, 32x32 -> 16x32) | |
> Note: When both bits 6 and 7 | |
are set, they don't do anything | |
u8 - Subsprite X position | |
u8 - Subsprite properties #2 | |
bit 0 - Off-screen(?) | |
bit 1 - *unused* | |
bit 2 - *unused* | |
bit 3 - *unused* | |
bit 4 - Horizontal mirror | |
bit 5 - Vertical mirror | |
bit 6-7 - Tile multiplier | |
0 - 8x8 | |
1 - 16x16 | |
2 - 32x32 | |
3 - 64x64 | |
~ | |
If a sound is defined: | |
u8 - Set to 0x02 | |
u8 - If this is more than 0x0 there is no sound | |
u16 - SFX index | |
> In Chicken Little, this uses a different | |
SFX mapping than observed in the original GAX FX blob. | |
Check me: This and the sound effect player menu rely on a | |
remapping of the GAX sound effect bank. Is this | |
done in just Chicken Little or does this occur in | |
other games as well? | |
If not, then: | |
u16 - ?? (seems to depend on the subsprite count over every frame of the animation?) | |
~ | |
u16 - Object script (for every frame) | |
u16 - *unused* | |
u16 - Palette offset | |
u16 - Object script (on init) | |
u8 - Interactibility with objects? | |
> in Chicken Little, 0x3 means that the object can interact with acorns | |
u8 - Animation count | |
u8 - Number of animations to fetch on init | |
u8 - Starting animation index offset | |
<For each animation> | |
u32 - Relative pointer to animation data | |
u8 - Animation index | |
u8 - ?? | |
u32 - Pointer to script (on animation init?) | |
u16 - ?? | |
Notes: | |
> The A2Mx Game Engine did not debut on Ice Age for the GBA. The engine used in that game was instead done by Vincent Van Eeckhout, with the tools developed by Simon Chouinard. Stéphane's game engine actually debuted in 2003 on Scooby-Doo!: Mystery Mayhem for the same system. | |
[1] - https://www.mobygames.com/game/159188/ed-edd-n-eddy-the-mis-edventures/credits/gameboy-advance/ | |
[2] - https://www.mobygames.com/game/205630/disneys-chicken-little/credits/gameboy-advance/ | |
[3] - https://tasvideos.org/Forum/Topics/17310 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment