MobiGo MBA/GAM executable format¶
This document describes the bM_gbMQa application container used by VTech
MobiGo .gam files and MobiGo 2 .MBA files. It is based on fourteen reference
files examined during clean-room research, cross-file comparison, entry-point
disassembly, menu-image decoding, and the retained MobiGo 2 research notes in
this repository. Those reference binaries are not distributed as part of the
documentation tree.
The format is only partly understood. Fields and rules below are marked verified, strongly inferred, or unknown so that observations are not presented as invented structure.
Summary¶
- Both
.gamand.MBAfiles use the same container andbM_gbMQamagic. - Multi-byte header integers are little-endian.
- The first
0x1000bytes form a fixed metadata and menu-art header. - Addresses stored in the header are 16-bit word addresses, not byte addresses.
- Offset
0x08stores the complete file size in words. - Offset
0x14stores the executable entry word address. - Offset
0x18stores the runtime word address corresponding to file offset0x1000. - Offset
0x3cstores a CRC-16/CCITT-FALSE of bytes0x00..0x3b. - The header contains a title, a 16-color RGB555 palette, 64-by-104-pixel visible menu art, and a profile-specific launcher footer.
- No relocation, import, section, or resource table has yet been established.
Numeric and address conventions¶
The Generalplus unSP CPU addresses 16-bit words. File offsets and sizes in this document are bytes unless explicitly described as words.
Let:
H = u32le(file + 0x18) body load word address
B = H - 0x800 candidate runtime image base
F = byte offset in the file
W = runtime word address
The fixed header is 0x1000 bytes, or 0x800 words, so the observed mapping
is:
This relationship is verified for the header, body boundary, all fourteen entry points, and examined executable regions. Mapping the whole file this way is useful as an initial static-analysis image, but it is not proof that every later resource remains at that address during execution. Application or loader code may decode, copy, bank, or overwrite later data.
All examined files have even byte lengths. The header's file-size value also makes odd-byte files unrepresentable.
Worked example: G1 role sample¶
body load address H = 0x0c8800
runtime base B = 0x0c8800 - 0x800 = 0x0c8000
entry address W = 0x0e1a55
entry file offset F = (0x0e1a55 - 0x0c8000) * 2
= 0x334aa
The bytes at file offset 0x334aa begin with a plausible unSP function
prologue. The same calculation lands on function-like code in every sample.
Fixed header layout¶
The header occupies file offsets 0x0000..0x0fff.
| Offset | Size | Meaning | Confidence |
|---|---|---|---|
0x00 |
8 | ASCII magic bM_gbMQa |
Verified |
0x08 |
4 | Complete file size in 16-bit words | Verified |
0x0c |
4 | Unknown | Unknown |
0x10 |
4 | Variant-dependent identifier or address-like value | Unknown |
0x14 |
4 | Executable entry word address | Verified |
0x18 |
4 | Word address at which file offset 0x1000 is loaded |
Verified |
0x1c |
4 | Unknown loader/platform field | Unknown |
0x20 |
4 | Unknown loader/platform field | Unknown |
0x24 |
4 | Unknown loader/platform field | Unknown |
0x28 |
4 | Unknown; commonly 0 or 0x6e0 |
Unknown |
0x2c |
16 | Zero in all examined files | Strongly inferred reserved |
0x3c |
2 | Header CRC-16/CCITT-FALSE | Verified |
0x3e |
2 | Zero in all examined files | Strongly inferred reserved |
0x40 |
64 | Variant-specific data; sometimes all zero | Unknown |
0x80 |
32 | NUL-padded ASCII title or application role | Verified |
0xa0 |
32 | 16 little-endian RGB555 palette entries | Verified |
0xc0 |
3328 | 64x104 indexed 4-bpp visible menu art | Verified |
0xdc0 |
576 | Profile-specific launcher resource footer | Verified for bundle/SY loading |
0x1000 |
— | First byte of executable body | Verified |
The visible artwork ends exactly at the launcher footer:
File size at 0x08¶
The stored value is the size of the entire file in 16-bit words:
This equality holds for all fourteen examined files.
Field 0x10¶
This field must not be assigned one universal meaning yet:
- In the four numbered
.gamsamples its values are2,29,30, and31, matching the product numbers in their filenames. - In most MobiGo 2 system/bundle modules it contains one of
0x0f3e5a..0x0f3e62, values which are address-like and are treated as protected callback boundaries by the earlier replacement-window tooling. - The loading application is an outlier with
0x05eca204.
It may be interpreted differently by different launcher generations or
application classes. The Ghidra loader therefore exposes it as field_10
without creating a function or reference from it.
Fields 0x1c, 0x20, and 0x24¶
The older-family files use address-like values:
Most MobiGo 2 bundle modules instead use 0x0000ffff sentinels in the first
two fields and 0x280642 in the third. These patterns imply loader or
platform parameters, but their operational meanings are not established.
Header CRC¶
The stored little-endian 16-bit value at 0x3c is calculated over exactly the
preceding 60 bytes, file offsets 0x00..0x3b.
Parameters:
Equivalent code:
uint16_t mba_header_crc(const uint8_t header[0x1000])
{
uint16_t crc = 0xffff;
for (size_t i = 0; i < 0x3c; ++i) {
crc ^= (uint16_t)header[i] << 8;
for (unsigned bit = 0; bit < 8; ++bit) {
crc = (crc & 0x8000)
? (uint16_t)((crc << 1) ^ 0x1021)
: (uint16_t)(crc << 1);
}
}
return crc;
}
All fourteen samples pass this check. The palette, title, menu art, footer, and executable body are not covered by this CRC.
Title, palette, menu art, and launcher footer¶
The title at 0x80 is a 32-byte NUL-padded ASCII field. It is a display name
in game files and often an internal role such as MGB_SYS, MGB_TM, or
USB APP in system modules.
The palette contains sixteen little-endian 16-bit entries:
The visible bitmap begins at 0xc0. It is row-major, 64 pixels wide,
104 pixels high,
and uses one palette index per nibble. The high nibble is the left/first pixel
and the low nibble is the right/second pixel:
Decoded samples produce recognizable launcher artwork, which independently confirms the visible dimensions, nibble order, and palette placement.
Launcher footer at 0xdc0¶
The final 0x240 bytes of the fixed header were initially mistaken for
another eighteen rows of menu pixels. Normal-boot testing established that
MobiGo 2 bundle/SY launch code consumes this area as profile-specific launcher
data:
- replacing
0xdc0..0xfffwith ordinary pixels or zeroes caused SY to stop after its first header DMA and enter watchdog recovery; - preserving the decoded SY footer while independently generating every
visible pixel allowed the firmware to perform all three DMA stages, call
entry
0x0dfc1d, and execute the homebrew framebuffer code; - the G1, G2, G3, G4, LD, SY, TM, and EBOOK files contain related structured patterns here, while the examined GAM, MM, and UB files use zeroes.
The MobiGo 2 bundle family begins the footer with:
offset little-endian u32
0xdc0 0x000c8000
0xdc4 0x003fd000
0xdc8 0x00075c00
0xdcc 0x00075fe0
0xdd0 0x000006ec
0xdd4 0x00000756
Later footer words form slot-specific masks and flags. Their individual
official meanings remain unknown, but the complete G1 and SY profiles are
synthesized explicitly by tools/build/build_mba.py; no source-container bytes are
read or copied.
Executable body¶
The body starts at file offset 0x1000, corresponding to the header word
address at 0x18. The header entry address at 0x14 maps into this body using
the equation above.
The analyzed entries commonly start with bytes such as:
With the community unSP language module, these decode as plausible function setup code and Ghidra analysis proceeds from the computed entry address.
No general section directory has been identified. Consequently, the supplied Ghidra loader creates two memory blocks:
.mba_header file 0x0000..0x0fff at runtime base B, read-only
.mba_body file 0x1000..EOF at header field 0x18, read/write/execute
The body permissions are intentionally permissive because code and resource boundaries are not known. They do not imply that every body byte is executable or writable on the physical device.
Cross-sample results¶
Every row below has the correct magic, exact word-count size, valid header CRC, and an entry that maps to plausible unSP code.
| File | Header title | Bytes | Runtime base | Body load | Entry | Entry file offset |
|---|---|---|---|---|---|---|
58-115800-000-002_V020.gam |
Wild Cards |
0x47000 |
0x224000 |
0x224800 |
0x22d520 |
0x012a40 |
58-115800-000-029_V020.gam |
Keyboard Jam |
0x48000 |
0x224000 |
0x224800 |
0x22b03a |
0x00e074 |
58-115800-000-030_V020.gam |
DJ Beats |
0x35000 |
0x224000 |
0x224800 |
0x22adc7 |
0x00db8e |
58-115800-000-031_V010.gam |
Monkey Disco |
0x93000 |
0x224000 |
0x224800 |
0x22d88b |
0x013116 |
| G1 role sample | MGB_G1 |
0x214000 |
0x0c8000 |
0x0c8800 |
0x0e1a55 |
0x0334aa |
| G2 role sample | MGB_G2 |
0x13e000 |
0x0c8000 |
0x0c8800 |
0x0cff42 |
0x00fe84 |
| G3 role sample | MGB_G3 |
0x3f0000 |
0x0c8000 |
0x0c8800 |
0x0d8445 |
0x02088a |
| G4 role sample | MGB_G4 |
0x2e2000 |
0x0c8000 |
0x0c8800 |
0x0d83e2 |
0x0207c4 |
| LD role sample | Loading App |
0x1e000 |
0x0c8000 |
0x0c8800 |
0x0c889a |
0x001134 |
| SY role sample | MGB_SYS |
0x174000 |
0x0c8000 |
0x0c8800 |
0x0dfc1d |
0x02f83a |
| TM role sample | MGB_TM |
0x13000 |
0x0c8000 |
0x0c8800 |
0x0ca0aa |
0x004154 |
USENG_EBOOK.MBA |
MGB_EBK |
0x1d000 |
0x0c8000 |
0x0c8800 |
0x0d4908 |
0x019210 |
USENG_MM.MBA |
Main Meun |
0x41000 |
0x224000 |
0x224800 |
0x22d8a5 |
0x01314a |
USENG_UB.MBA |
USB APP |
0x13000 |
0x224000 |
0x224800 |
0x226261 |
0x0044c2 |
The samples reveal two observed base/load families:
MobiGo 2 bundle/EBOOK modules: base 0x0c8000, body 0x0c8800
older GAM plus MM/UB modules: base 0x224000, body 0x224800
These are observations, not hard-coded format constants. A parser should
derive the base from the file's 0x18 field.
Parser validation recommendations¶
A conservative reader or loader should require:
- At least
0x1000bytes and an even file length. - Exact
bM_gbMQamagic. u32le(0x08) * 2equal to the actual file length.- A body load address of at least
0x800words. - An entry at or after the body load and inside the candidate mapped image.
- Addresses fitting the unSP 22-bit word-address space.
CRC failure is valuable evidence of damage or an incomplete edit. A forensic tool may still choose to import such a file with a warning; the supplied loader records both stored and calculated values rather than rejecting the file solely for a CRC mismatch.
AppleDouble sidecar files named ._*.MBA or ._*.gam are macOS metadata, not
application containers, and fail the magic check.
Ghidra loader¶
The loader source is in:
Ghidra does not include an unSP language definition. Install the community
ghidra-unSP processor extension first:
https://github.com/20051231/ghidra-unSP
Then build the loader with GHIDRA_INSTALL_DIR pointing to an unpacked Ghidra
installation:
Install the ZIP written to dist/ using Ghidra's
File > Install Extensions... dialog and restart Ghidra. Import an MBA or
GAM normally; the detected loader name is MobiGo MBA/GAM application.
In addition to mapping and typing the container, the loader:
- creates the
mba_entryexternal entry point; - labels the title, palette, visible menu art, and launcher footer;
- records raw unknown fields and CRC status in Program Information;
- creates documented GPL16250/MobiGo 2 hardware and vector memory blocks; and
- labels PPU, system, NAND, GPIO, interrupt, timer, audio, USB, DMA, SPU, and vector addresses from this repository's hardware documentation.
The MMIO labels are analysis aids derived from the homebrew documentation, not MBA header content.
Remaining questions¶
Further runtime tracing or launcher disassembly is needed to determine:
- the meaning of header field
0x0c; - the exact per-variant semantics of field
0x10; - the meanings of fields
0x1c,0x20,0x24, and0x28; - the structure of variant data at
0x40..0x7f; - the individual meanings of the launcher-footer words and masks;
- whether any resource directory exists in the body;
- which body ranges are copied, decoded, banked, or overwritten; and
- whether other regional or firmware revisions introduce additional header variants.
Until those questions are answered, a new builder should use a tested slot/firmware profile. The included generator constructs both profiles deterministically. SY is canonical for new applications; G1 remains a tested legacy compatibility profile rather than a default.