Dump complete! Disassembling the Menu in progress...

A project log for Dissecting a hand-held NOAC console

This is an attempt to understand how these little things work, and what we can do on it.

nyh-workshopNYH-workshop 02/23/2023 at 15:250 Comments

Finally, after the one whole day it dumped the contents, I immediately used the BIN file to test it in the EmuVT 1.36. (The latest version doesn't work properly, the screen blacked out when scrolling the menu)

It just worked! Some of the games doesn't work - it could be the dumping issue, or the emulator issue. However, most of the game worked flawlessly:

(What is Utmost Warfare??? And that music... Urgh!)

Due to many, many reasons, I felt uncomfortable putting this ROM image here - since the method of dumping is possibly correct, you can replicate this process using the Raspberry Pi, a bunch of wires, and 3 MCP23017s. Here's a rough schematic:

Here is the Python code - I accidentally did a dump from 0x800000 to 0xFFFFFF, but the contents are mirrored from 0x000000 to 0x7FFFFF so the dump still worked as usual! Use a Raspberry Pi according to the schematic too.

Caution: The Python code is quite messy - it has not been finalized. This dumping process takes almost a whole day!  

import time
from datetime import datetime

import board
import busio
import digitalio

from adafruit_mcp230xx.mcp23017 import MCP23017

# ROM dump utility
print("ROM dump utility")

# Initialize the I2C bus:
i2c = busio.I2C(board.SCL, board.SDA)

mcp00 = MCP23017(i2c, address=0x20)
mcp01 = MCP23017(i2c, address=0x21)
mcp02 = MCP23017(i2c, address=0x22)
# mcp00 = addr (A0-A15) [A+B]
# mcp01 = addr (A16-A23) [A]; /CE, /WE, /OE [B]
# mcp02 = data (D0-D15) [A+B]

# setting data pins:
mcp02.iodira = 0xff
mcp02.iodirb = 0xff
mcp02.gppua = 0x00
mcp02.gppub = 0x00

# setting address pins (A0-A15):
mcp00.iodira = 0x00
mcp00.iodirb = 0x00
mcp00.gpioa = 0x00
mcp00.gpiob = 0x00
mcp00.gppua = 0x00
mcp00.gppub = 0x00

# setting address pins (A16-A23):
mcp01.iodira = 0x00
mcp01.gpioa = 0x00
mcp01.gppua = 0x00

# setting control pins (/CE, /WE, /OE):
# bit 0 = /CE
# bit 1 = /WE
# bit 2 = /OE
mcp01.iodirb = 0x00
mcp01.gpiob = 0x07
pinCE = mcp01.get_pin(8)
pinWE = mcp01.get_pin(9)
pinOE = mcp01.get_pin(10)


pinCE.pull = digitalio.Pull.UP
pinWE.pull = digitalio.Pull.UP
pinOE.pull = digitalio.Pull.UP

pinCE.value = True
pinWE.value = True
pinOE.value = True

def _CE(en):
    if (en == True):
        pinCE.value = False
    elif (en == False):
        pinCE.value = True
        raise Exception("True or False only!")

def _OE(en):
    if (en == True):
        pinOE.value = False
    elif (en == False):
        pinOE.value = True
        raise Exception("True or False only!")

def putAddr(addr):
    mcp00.gpioa = (addr & 0x000000ff)
    mcp00.gpiob = (addr & 0x0000ff00) >> 8
    mcp01.gpioa = (addr & 0x00ff0000) >> 16
    #print("mcp00 gpioa = " + hex(mcp00.gpioa))
    #print("mcp00 gpiob = " + hex(mcp00.gpiob))
    #print("mcp01 gpioa = " + hex(mcp01.gpioa))
def getData():
    dataL = mcp02.gpioa
    dataH = mcp02.gpiob
    return (dataH << 8) | dataL

def readAddr(addr):
    value = None
    value = getData()
    return value

def dumpROM():
    for addr in range(0x800000, 0xffffff,1):
        print("Reading block: ", hex(addr))
        f.write(readAddr(addr).to_bytes(2, byteorder="little"))   

f = open("romdump.hex","wb")

# possibly read only 8 megabits!
#f.write(readAddr(0).to_bytes(2, byteorder="big"))

startTime =
addr = 0
#f.write(readAddr(addr).to_bytes(2, byteorder="big"))
# let's read each 2048 blocks first!
stopTime =

print("time taken: ", stopTime-startTime)

What's next? I'm trying to understand how the menu and the Aaronix Test screen works. I suspect this had to do a initial startup on the LCD and possibly some other peripherals.