On startup you will be presented with the language selection menu - Chinese or English. The background seemed to be from another game called “Street Fighter 2010”.
In the Chinese menu, there is a different music being played, while in the English menu, there is no music at all.
If you hold the A + B and then press the Reset button, you will go into the “Main Device Test” where you can test the response of the buttons and the application is also testing different sound channels.
Since these consoles are suspected to be based on these “OneBus” architectures, the reset vector starts at 0x7FFFC.
On some of the handhelds, the bits are swapped. This is a nerve-racking affair because even after you have dumped it, it does not work in the emulator despite having the correct connections.
The ones I found are:
Data bits swapped inside the dump, for example bits 0-1 and bits 8-9. One tip is, view the dump using the YY-CHR app and examine if some of the tiles are distorted or not. If it is distorted (go browse the ROM around in the YY-CHR app and try looking at the alphanumeric characters for a start), you may need to examine the values in the binary files closely and see what bits have swapped.
This is an example of the swapped bits 9-10 and bits 0-1:
This is an original without bit swaps:
Bits at the opcodes are swapped during startup. This is seen in newer handheld that was bought in 2023.
Here is an example when it is viewed in the EmuVT’s trace debugger:
After some inspection, bits 4-5 are swapped. The emulator finally runs the dump if you swap these opcode’s bits at that code fragment:
Both swapping of data bits, opcode bits and unknown calls to functions at unknown locations. This variant comes from the recent purchase of the same handheld in 2024, but with the S29GL128P10TFI01 flash inside. This is the most difficult thing to examine because it needs more work to undo these swapping for the entire file, and then to undo the swapping of opcode bits at the start of the code!
To make it worse… there exists calls to some unknown instructions and calling a function at 0x394F (which leads to nowhere, of course), crashing the whole menu and the test app if it is run on the emulator:
To fix that for running in emulator, replace these unknown function calls to NOP.
The game runs in the emulator - but you must leave the bits in the original state if you need to run it in the handheld! Also, you need to possibly preserve the unknown calls and unknown instructions on the actual handheld too.
This is what makes the handheld console… a handheld console. If these routines are not present, the screen has no output and the only other display is on the AV-out jack. That’s no longer handheld if you need to carry along a TV to play it!
I have examined three of these and each of them seemed to be an improvement over the other one. Let’s call them type 1, 2 and 3:
This one is straightforward. Put a command byte, calls write command, then put a data byte, calls write data:
writeCMD(0xfe);
writeCMD(0xef);
writeCMD(0x36);
writeCMD(0x28);
The functions that write the command and data to the TFT uses $4230 and $4231. At $4233 again if it is a write command, $94 is written. If it is a write data, $D4 is written.
// ORG 0x66ABE
// Write CMD to TFT:
void fun_0x66ABE(uint8_t a0)
{
// Possibly save a0 at 0x23 (RAM area), then copy to addr 0x4231.
// Swap contents of RAM addr. 0x23, 0x24 and assign to 0x4230, 0x4231.
// Then, 0x94 is assigned to addr. 0x4233:
// At RAM area?
*(0x23) = a0;
a0 = *(0x24);
*(0x4230) = a0;
a0 = *(0x23);
*(0x4231) = a0;
*(0x4233) = 0x94;
}
// ORG 0x66AD0
// Write data to TFT:
void fun_0x66AD0(uint8_t a0)
{
// Possibly save a0 at 0x25 (RAM area), then copy to addr 0x4231.
// Swap contents of RAM addr. 0x25, 0x26 and assign to 0x4230, 0x4231.
// Then, 0xd4 is assigned to addr. 0x4233:
// At RAM area?
*(0x25) = a0;
a0 = *(0x26);
*(0x4230) = a0;
a0 = *(0x25);
*(0x4231) = a0;
*(0x4233) = 0xd4;
}
The TFT init values are not stored in an array.
Instead of calling it one-by-one in Type 1, the TFT init values are stored in the array and a small loop is run to do the initialization:
do {
DAT_0130 = LCD_settings[bVar2];
FUN_a8c7();
while( true ) {
bVar3 = bVar2 + 1;
cVar1 = LCD_settings[bVar3];
if (cVar1 == -2) break;
if (cVar1 == -1) {
FUN_a8b5(2);
FUN_a8af();
FUN_a8b5(1);
return;
}
DAT_0132 = cVar1;
FUN_a8d9();
bVar2 = bVar3;
}
bVar2 = bVar2 + 2;
} while( true );
// LCD settings:
uint8_t LCD_settings[] = {
0x11, 0xFE, 0xFE, 0xFE, 0xEF, 0xFE, 0x36, 0x28, 0xFE, 0x3A, 0x05, 0xFE,
0xA4, 0x44, 0x44, 0xFE, 0xA5, 0x42, 0x42, 0xFE, 0xAA, 0x88, 0x88, 0xFE,
0xE8, 0x11, 0x0B, 0xFE, 0xE3, 0x01, 0x10, 0xFE, 0xFF, 0x61, 0xFE, 0xAC,
0x00, 0xFE, 0xAE, 0x2B, 0xFE, 0xAD, 0x33, 0xFE, 0xAF, 0x55, 0xFE, 0xA6,
0x2A, 0x2A, 0xFE, 0xA7, 0x2B, 0x2B, 0xFE, 0xA8, 0x18, 0x18, 0xFE, 0xA9,
0x2A, 0x2A, 0xFE, 0x2A, 0x00, 0x00, 0x01, 0x3F, 0xFE, 0x2B, 0x00, 0x00,
0x00, 0xEF, 0xFE, 0x2C, 0xFE, 0xF0, 0x02, 0x02, 0x00, 0x08, 0x0C, 0x10,
0xFE, 0xF1, 0x01, 0x00, 0x00, 0x14, 0x1D, 0x0E, 0xFE, 0xF2, 0x10, 0x09,
0x37, 0x04, 0x04, 0x48, 0xFE, 0xF3, 0x10, 0x0B, 0x3F, 0x05, 0x05, 0x4E,
0xFE, 0xF4, 0x0D, 0x19, 0x17, 0x1D, 0x1E, 0x0F, 0xFE, 0xF5, 0x06, 0x12,
0x13, 0x1A, 0x1B, 0x0F, 0xFE, 0x29, 0xFE, 0x2C}
Similar to the Type 1, it uses the same registers to write to TFT.
(Ghidra is used to decompile this to C code!)
This one is from the newer handheld in 2023. The code is extremely convoluted even when it is decompiled into C! Unfortunately, it is still not known how it writes to the TFT - there are multiple registers that are involved!
At the startup, the app has to jump to as far as address 0x2A000 where all the TFT routines are.
Basically, instead of putting one kind of TFT, the developer had put many other TFT models inside. I suspect that there is one variable for the TFT model that is saved into the flash and have that function select it and init the TFT accordingly. How convenient!
If you have already read about the “TFT Initialization Routines” earlier, these TFT are equipped with a backlight. The strange part is, these backlight registers are different in each of these builds. For the GC9306 TFTs inside the handhelds, nothing happens when I tried to write anything to its backlight (TFT) registers. The backlight might be connected to the handheld’s system.
I also noticed that as for now I’ve seen that only after the TFT initialization and when it is entering the menu, the backlight switches on.
The backlight is at $412C - putting a 0 there switches it off, and an 0xF switches it on:
; switches off backlight!
lda #$00
sta $412C
; switches on backlight!
lda #$0F
sta $412C
Unfortunately, I did not get how it switches off the backlight. However, for switching it on, at the same menu start, it accesses and writes to this registers, which I suspect one of them are something to do with the backlight:
; Switches on backlight!
lda #$1f
sta $413f
lda #$0b
sta $4138
lda #$0f
sta $4139
As mentioned earlier I was not planning to analyze the menu app. However, during some more experiments, when running Mapper 4 (MMC3) games on these systems, the menu app has to go through the process of assigning the suitable values for the OneBus registers so that it can jump into the game and run it.
The sequence of loading the registers are important - and I did copied the sequence from the menu code. Without the proper sequence and the proper values, the game runs fine but with wrong tiles and sprites.
The correct sequence:
jumpToProgramInRAM:
lda _zR410A
sta $410A
lda _zR410B
sta $410B
lda _zR4100
sta $4100
lda _zR4105
sta $4105
lda _zR4106
sta $4106
lda _zR4107
sta $4107
lda _zR4108
sta $4108
lda _zR4109
sta $4109
Having the correct sequence is one thing - since the CHR-ROM is not involved, the $201x registers are not being used. But again, the lower nibble inside register $4100 has to be between 0x04 and 0x0F for these games to display the correct tiles and sprites! I’m not entirely sure the reason - I have examined the register values of these 400-in-1 menu and they are using that number.
One of the 400-in-1s (2023) has a register at $4118 - if games such as MMC3 and with only CHR-RAM, it is assigned $80. Else it is $00.