← blog index

Emulating an obscure platform: Gemei A330

24 May 2023

This project was originally carried out in late 2020, but I never got around to finishing the write-up. As my notes are rather spotty, there might be some factual inaccuracies since I had to partially reverse engineer my own work.

The Gemei A330 was one of several attempts to follow up the successful Dingoo Digital A320. Released in 2010, it was praised for good build quality and ergonomy – however, it never came close to the fame of its predecessor. Was it due to the higher price? Or perhaps the strange choice of an LCD with hexagonal pixels? Lack of available software compared to its predecessor? We might never know. In any case, the short life of the console poses a challenge for preservation efforts.

Technical specs – hardware

The information available online often contradicts itself (especially regarding the CPU and clock); the list below has been sourced from a GBATemp review.

While marketed primarily as a media player + emulator machine, the specs suggest it should be reasonably competent for running native games as well. While there is no 3D acceleration, one of the bundled games demonstrates that, thanks to the low-resolution screen and fast CPU, good-quality software rendering is feasible. Unfortunately this potential was never fully realized, leaving the device with a tiny library of native titles, most of them non-exclusive.

Why, then, should we bother trying to emulate it at all? Wrong question! Why should we not?

Software platform

Symbols in the firmware binary reveal that it is based on µC/OS-II, but with a lot of custom functionality on top, for a total of 952 functions exposed in the API.

A SDK for the platform was published (mirror 1, mirror 2), which reveals some more details of the OS architecture. The SDK is based on Cygwin and GCC, and the Makefiles specify ARM926EJ-S as the target CPU. The SDK also contains some source code, but this concerns mostly boring utility libraries. The juiciest bits are available only in compiled form.

For emulation, I was interested in one specific game: 7 Days Salvation.

How the OS works

The firmware/kernel is loaded from a file called ccpmp.bin. The file is exactly 2 MiB in size, but after 1.84 MiB it is filled with 0xFF. It is a flat (unstructured) binary which gets loaded at address 0x1050_0000 by the bootloader.

As mentioned above, the kernel exposes 952 functions for user space code to use.

As far as I could tell, there is no memory protection or virtual memory. Each task can access the entire physical address space.

Executable format

The games ship in a format called CCDL, which is a quite straightforward format used both for executables and libraries on the platform.

The format consists of a number of chunks. The most imporant of those are:

As you might have noticed, there is no support for relocation of dynamic libraries.

Emulation

As the published games appear to use OS-provided APIs exclusively, the platform is a good candidate for high-level emulation. That is, an approach to emulation where the aim is not to replicate hardware behavior faithfully, but instead emulate on the level of OS APIs.

The emulator consists of two parts: the ARM emulator proper, and miniSYS, a reimplementation of some of the functions provided by the operating system.

miniSYS

miniSYS is built as an ARM binary which executes inside the emulator, in the same way as the game being played. It provides implementations of OS functions like fsys_fopen or LCDGetWidth. The former in an example of function that traps into the emulator via an SVC call, while the latter is completely implemented in miniSYS.

It would be possible to eschew miniSYS and implement all the relevant APIs directly in the emulator. However, this leads to quite awkward code with a lot of boilerplate whenever accessing the registers or memory of the emulated device (for an example, see the implementation of Svc_GemeiEmu_fwrite; all those uc_ calls are just marshalling data back and forth). This also implies that any previously written code which assumes normal memory access cannot be reused – even functions like strlen cannot be used for data inside the emulated RAM.

Native part

The native side of the emulator is then very straightforward. It is based on Unicorn, a multi-architecture emulator derived from QEMU code. When booting a game, the emulator starts by allocating some host memory for the emulated device, before proceeding to load the miniSYS binary and the game executable. Control is then handed to Unicorn. Parsing of the executable headers is already implemented in miniSYS, so it runs as ARM32 code.

There is barely any hardware to emulate, as the games don’t access any peripherals directly; for example, instead of having to worry about a GPU, you just get a flat 320x240x16-bit framebuffer at address 0x8000_0000.

Emulator developers frequently end up reimplementing boilerplate functionality like window management and input remapping. To avoid pointlessly re-doing this work, I made use of the libretro project. Essentially, only the emulated-platform-specific code needs to be written, and a libretro front-end (such as RetroArch) takes care of all the boring bits.

In sum, the full stack looks something like this

Compatibility & conclusion

Herein lies the caveat; my original motivation for the emulator work was an interest in playing 7 Days Salvation. However, after getting to the point of game saving working properly I kinda lost interest and ended up never playing through it. (Even more so when it turned out that other ports of the game have better graphical fidelity and eventually an English fan translation came out for the Symbian version.)

game screenshot

Thus, I can’t actually say whether the game can be played through to completion or not.