How to make a simple hello app for the e-Reader

Today, let's make a very simple e-Reader app in z80 assembly. This tutorial won't be very elaborate. There's almost no code at all, instead the focus is on getting the tooling setup and covering some background on how e-Reader apps work.

These tools are not too user friendly. If you've never coded before or are not adept with computers, you might get stuck.

Install pyz80

This tutorial is about writing e-Reader games in z80 assembly, so we need an assembler.

E-Reader games can also be written in C, or you can make an NES game. Each approach is totally different. This tutorial is only about creating z80 apps.

I actually use asz80 to assemble my games, but for this tutorial I chose pyz80 since it's (pretty) easy to install. You will need a python environment, and that varies a lot from OS to OS, so I will leave that up to you.

Once you have a working python environment, installing pyz80 should be as easy as

python -m pip install pyz80

Now run pyz80 and if you see its help text, you should be good to go.

pyz80
No input file specified
pyz80 by Andrew Collier
 Enhancements by Simon Owen, Adrian Brown, Stefan Drissen
 https://github.com/simonowen/pyz80/
Usage:
     pyz80 (options) inputfile(s)
Options:
-o outputfile
   save the resulting disk image at the given path
--nozip
   do not compress the resulting disk image
-B filepath
   Bootable (usually DOS) file added first to disk image, but
   only if the first assembled file has no BOOT signature.
-I filepath
   Add this file to the disk image before assembling
   May be used multiple times to add multiple files
--obj=outputfile
   save the output code as raw binary file at the given path
-D symbol
-D symbol=value
   Define symbol before parsing the source
   (value is integer; if omitted, assume 1)
--exportfile=filename
   Save all symbol information into the given file
--importfile=filename
   Define symbols before assembly, from file previously exported
--mapfile=filename
   Save address-to-symbol map into the given file
--lstfile=filename
   Produce assembly listing into given file
--case
   treat source labels as case sensitive (as COMET itself did)
--nobodmas
   treat arithmetic operators without precedence (as COMET itself did)
--intdiv
   force all division to give an integer result (as COMET itself did)
-s regexp
   print the value of any symbols matching the given regular expression
   This may be used multiple times to output more than one subset
-x
   display values from the -s option and PRINT directives in hex
-e
   use python's own error handling instead of trying to catch parse errors

Make a test binary

Make a file called hello.asm and put this in it.

    ld a, 4

and now assemble it

pyz80 --obj=hello.bin hello.asm

pyz80 has some niceties built into it related to 8 bit computers (which commonly used the z80 as their processor). We don't want any of that. --obj=hello.bin is how we avoid all of those things. This parameter tells pyz80 to just assemble the file and dump the resulting raw bin into hello.bin.

hello.bin should be tiny, just 2 bytes

ls -l
total 8
-rw-rw-r-- 1 matt matt 17 Oct 19 10:38 hello.asm
-rw-rw-r-- 1 matt matt  2 Oct 19 10:40 hello.bin

And if you look at it in a hex editor, you should see

00000000   3E 04

If you get anything else, then pyz80 is not working correctly for you.

Get the e-Reader dev tools

You will need the e-Reader dev tools. You can grab them for Windows at CaitSith2's website here, grab the "Dotcode Dev Package". For other OSes, you can build the tools via this repo. If you are on Linux, you can find compiled binaries in this tutorial's companion repo here.

The non-Windows version of nedcmake often crashes. One day I'll get around to fixing this, but for now I use the Windows version of nedcmake through wine.

Writing an actual e-Reader app

Take hello.asm and expand it to this.

    include "erapi.asm"
    org 0x100
    ERAPI_SetBackgroundMode()
    mode (0-2)
    ld a, 0
    rst 0
    db ERAPI_SetBackgroundMode
    ERAPI_LoadSystemBackground()
    index (1-101)
    bg# (0-3)
    ld  a, 4
    ld  e, 3
    rst 0
    db ERAPI_LoadSystemBackground
    ERAPI_FadeIn()
    number of frames
    ld a, 0
    rst 0
    db ERAPI_FadeIn
    ld h, 0
    ld l, 12
    ld d, 2
    ld e, 2
    ld b, 10
    ld c, 4
    rst 0
    db ERAPI_CreateRegion
    ld (@region_handle), a
    ld a, (@region_handle)
    ld bc, @hello_text
    ld de, 0
    rst 0
    db ERAPI_DrawText
@loop:
    ld a, 1
    halt
    jr @loop
@region_handle:
    db 0
@hello_text:
    defm "hello ereader"
    db 0

hello.asm is now including erapi.asm, so create that file in the same directory. For now, it just needs to be this.

    RST 0
ERAPI_FadeIn:                   equ 0x00
ERAPI_LoadSystemBackground:     equ 0x10
ERAPI_SetBackgroundMode:        equ 0x19
ERAPI_CreateRegion:             equ 0x90
ERAPI_DrawText:                 equ 0x99

hello.asm is now a real e-Reader app. We'll go over the code shortly. But for now, let's just get it running.

Assemble it

First assemble it with pyz80.

pyz80 --obj=hello.bin hello.asm

Compress the binary with nevpk

Invoke nevpk to compress the binary you just made.

nevpk -c -i hello.bin -o hello.vpk

A "vpk" is a compressed file using the vpk0 compression algorithm. It was named that because vpk files (usually) start with the magic string "vpk0". It was developed by Hudson soft and used in their N64 games. Nintendo adopted it for the e-Reader. Most (but not all) e-Reader cards contain vpk0 compressed data. After you scan a card in, the e-Reader will decompress the data automatically.

Turn the vpk into a sav file

After you have scanned a game into the e-Reader, you are asked if you'd like to save it. If you do, the e-Reader will save the game's data into its sav file. The next time you run the e-Reader, you can choose "Access Save Data" to run the game you have saved, so you don't need to scan it in again.

The e-Reader's menu, with "Access Save Data" currently focused.

We can take advantage of this feature of the e-Reader by turning our vpk into an e-Reader sav file, that is what neflmake does.

neflmake -i hello.vpk -o hello.sav -type 1 -name hello

If that succeeded, you will now have hello.sav.

If you load up the e-Reader ROM in a GBA emulator with hello.sav as its save file. You will be able to choose "Access Save Data" and see your app.

The hello app running.

I use mGBA and highly recommend it. It is an excellent emulator. Getting it installed depends on your OS, so I will leave that to you. You will also need the e-Reader ROM. You can get that by dumping your e-Reader using a GBA cart dumper, or, uh, via other methods...

If you have mgba installed and on your path, you should be able to invoke it from the command line.

mgba ereaderUSA.gba

Turn the vpk into a raw

E-Reader cards have the game data in the dot strips, usually two per card.

Solitaire in its card from

For example here is my solitaire card. It has two data strips, at the top and bottom.

A raw file is the binary data from the data strips. This data contains the vpk, but in an encoded format. The encoding allows the e-Reader's camera to ingest the dots, and convert them into binary data in the GBA's RAM. The encoding helps guide the e-Reader's camera, and also contains duplicate copies of the data in the card just in case the camera fails to read part of the card. If you have ever used a parity file to repair a damaged binary, it's pretty much the same idea. The data strips work very similarly to QR Codes, you can think of a data strip as a long, narrow, QR code.

One data strip can hold about 2kb of data. A version 33 QR Code, which is 149x149 dots, can also hold about 2kb.

hello.asm in a version 33 QR Code.

Hoo that's a big boy! It's pretty impressive just how much data these little e-Reader data strips can hold.

Anyway, use nedcmake to create the raw file.

nedcmake -i hello.vpk -o hello.raw -type 1 -region 1 -name hello -raw
If you are using the non-Windows version of nedcmake, it probably crashed doing the above. You may need to use wine and the Windows version of the app.

Scanning the raw in with mGBA

nedcmake will create hello.raw-01.raw. You can run this raw file in mGBA, and it's just like scanning a card into a real e-Reader.

Launch mGBA and run the e-reader ROM. Then go to File > Scan e-Reader dotcodes...

Telling mGBA to scan in a dotcode.

Then choose hello.raw-01.raw in the file dialog that pops up. Now in the e-Reader, choose "Scan Card" and wait. mGBA will scan in the hello card, and after a bit the e-Reader will launch it.

You can also do this on the command line

mgba ereaderUSA.gba --ecard hello.raw-01.raw

Printing the card out and scanning into a real e-Reader

With the raw in hand, you can make a dotstrip image from it using raw2bmp.

raw2bmp -i hello.raw-01.raw -o hello

It will spit out hello.bmp.

The hello app as a dot strip image.

By default this image is 300 dpi, you can increase the dpi by adding -dpi 600|1200|2400 to the raw2bmp call.

Now you can print this out, and scan it into a real e-Reader. This does work, but ... "print it out" is very much "draw the rest of the owl".

How to draw an owl.

Getting a print out that will actually scan can be very difficult. I've written many blog posts on all my adventures doing this. If your print out won't scan, don't fret too much, that is the most common outcome.

A little e-Reader background

Before we jump into the code, let's get some background on the e-Reader.

The e-Reader's z80 emulator

The GBA does not have a z80 processor in it. The Game Boy processor (used to run OG Game Boy and Game Boy Color games) is similar to a z80, but the e-Reader does not use it (indeed, it can't. That processor is completely shut off from GBA games). So to run z80 apps, the e-Reader contains a z80 emulator.

Why bother with an emulator? Why not just make a native GBA app, stick that on a card, and run that? The e-Reader also supports doing this. But z80 apps have a huge advantage in that they tend to be much smaller than an equivalent GBA app. This space savings means games can fit onto fewer data strips. All official standalone e-Reader cards published by Nintendo are z80 apps, most likely due to this space savings.

I say "standalone" because some e-Reader cards are essentially a primitive form of "DLC". For example, Super Mario Advance 4 has e-Reader cards that contain levels for the game. These cards are not z80 based. They just contain raw binary data that only the game they are intended for can understand.

The space savings is the advantage, but, well, everything else is a disadvantage. The z80 emulator has a lot of limitations.

  • It is quite slow
  • It can only allocate 56 sprites
  • It does not support all registers or opcodes of the z80

Check here for more details.

Not to mention, you have to write your game in z80 assembly. This is a mixed bag. Most of the time it's not a big deal. But doing complex stuff can be extremely frustrating.

ERAPI

The e-Reader has a pretty extensive API built into it. This API is a collection of functions for doing things like displaying sprites, collision detection and even multiplying and dividing. This API is called "ERAPI", for "e-Reader API". This is not its official name, we don't know what Nintendo called it. This is the name enthusiasts who reverse engineered the e-Reader came up with.

Looking at hello's code

We are now ready to finally walk through the code.

    org 0x100

The first line is org 0x100. Let's skip this one for now and come back to it later.

    ERAPI_SetBackgroundMode()
    mode (0-2)
    ld a, 0
    rst 0
    db ERAPI_SetBackgroundMode

Here is our first call to ERAPI. SetBackgroundMode tells the GBA which background mode we want to use.

ld a, 0 is "load the a register with zero". ERAPI functions usually take parameters, and the z80's registers are the main way to setup those parameters. Here we are telling SetBackgroundMode to set it to mode zero, which is four standard, 2d backgrounds.

The GBA has other background modes that involve bitmaps and using affine transforms. These more advance modes are often used by commercial GBA games, but as far as I know, only mode zero is compatible with z80 e-Reader apps.

rst 0 is a strange way to call a function on the z80 cpu. Normally functions are called using the call opcode. When the z80 encounters call, it will jump to the address of the function, and start executing it, until it encounters the ret opcode, which causes the cpu to return to where it came from. rst 0 does the same thing, but unlike call it can only jump to one location. The advantage is call takes up 3 bytes, while rst 0 only requires 1. So for functions you will call very often, that space savings can really add up on old computers that didn't have much storage.

The e-Reader uses rst 0 to call ERAPI functions. Since it's just a z80 emulator and not a real cpu, the e-Reader is able to fudge things a bit. Whenever it encounters rst 0, it will look at the next byte to see which ERAPI function is being called. Then when the ERAPI function is done, it will move beyond that extra byte and keep executing the z80 app.

In this case, db ERAPI_SetBackgroundMode is telling ERAPI which function we want to invoke. db just has the assembler drop a byte into the binary, and if you look at erapi.asm, you will see that ERAPI_SetBackgroundMode is just the constant 0x19.

This is how all ERAPI functions are called: set up the register parameters, call rst 0, and finally drop in a single byte that indicates which function we want to invoke. When writing an e-Reader z80 app, you will do this all the time.

Most ERAPI functions are invoked with rst 0. But some are invoked with rst 8. Which one you use just depends on the function. You can see that in the ERAPI API documentation, it is divided into two sections, one for rst 0 functions, and the other for rst8.

Let's keep going...

    ERAPI_LoadSystemBackground()
    index (1-101)
    bg# (0-3)
    ld  a, 4
    ld  e, 3
    rst 0
    db ERAPI_LoadSystemBackground

Another ERAPI call. This time we are loading a background that is built into the e-Reader. We are loading the a register with 4, and the e register with 3. 4 is the ID of the desert scene background we want. 3 is telling the e-Reader to load this into the last GBA background. Remember how we set mode zero for SetBackgroundMode? That gave us four backgrounds to work with, numbered 0 through 3.

In mGBA, go to Tools > Game state views > View map...

The mGBA map tool.

Here you can see the four backgrounds and what are loaded into them. If you choose background 3, you will see the desert scene is loaded there.

When choosing a system background to load, this background dump is helpful.

    ERAPI_FadeIn()
    number of frames
    ld a, 0
    rst 0
    db ERAPI_FadeIn

And next up is another ERAPI call. This tells the e-Reader to display the screen. Without this call, you would just see a black screen. This function takes a duration parameter in register a. We set it to zero so the screen shows up immediately. If you change it to say 100, you will see the screen fade in over time, useful for transitions.

    ld h, 0
    ld l, 12
    ld d, 2
    ld e, 2
    ld b, 10
    ld c, 4
    rst 0
    db ERAPI_CreateRegion

And yet another ERAPI call. This one is a bit more complex, it takes 6 parameters! This is creating a draw region on the screen. Whenever an e-Reader app wants to display text, or draw things like rectangles and lines, this needs to happen inside a draw region. Most of the parameters are telling the e-Reader where on the screen we want the region:

  • d: the x of the region, in tiles
  • e: the y of the region, in tiles
  • b: the width of the region, in tiles
  • c: the height of the region, in tiles

These values are all in tiles. Tiles on the GBA are 8x8 in size, so we are telling the e-Reader to put our region here:

Where our region goes.

Draw regions exist on one of the 4 backgrounds. The h parameter is telling the e-Reader to place this region on background zero. The l parameter is telling the e-Reader this region will use the 13th background palette. This is the desert palette's background. If we didn't use the same palette, the text would be black as we have no other palettes available to us.

This tutorial will not get into palettes. We'll dive into them later.

If you launch the game in mGBA and go to Tools > Game state views > View map..., then head to background 0, you can see the text drawn into it.

Our text drawn into background zero.

Right after this ERAPI call is this line.

    ld (@region_handle), a

ERAPI functions can have return values. CreateRegion returns the region's handle in the a register. A region handle is just a number. Whenever we want to do something with this region, we need to pass this handle back to ERAPI so it knows which region we are working with.

This line is "take the contents of the a register, and save them into memory at the @region_handle location". If you look lower in the file we have

@region_handle:
    db 0

@region_handle: is a label. It's a way to name a part of your assembly program, then use that name in other parts of the program. The db 0 is just telling the assembler to drop a byte with the value zero here. We are just leaving a byte of space to have room to store the region handle into.

The parentheses around @region_handle in the ld call mean we are dereferencing the region_handle pointer. If you don't know what that means, don't worry too much about it, it's not important for now.

    ld a, (@region_handle)
    ld bc, @hello_text
    ld de, 0
    rst 0
    db ERAPI_DrawText

Our final ERAPI call in the program. Here we are telling the e-Reader to draw the text "hello ereader" into our draw region.

The first line is doing the opposite of the last line in the previous section. Here we are saying "take whatever value is stored at @region_handle, and stick it into the a register". The draw region's handle is the a parameter for DrawText.

Since we called CreateRegion just before DrawText, a already has the region handle in it. So we could have just used it and skipped the ld call. But usually you end up calling DrawText at a very different part of your program, so usually you need to load the handle into a.

ld bc, @hello_text is loading the pointer to our string into the bc register.

@loop:
    ld a, 1
    halt
    jr @loop

We have finished putting all of the stuff on the screen, and now we are "running" the game. Since this "game" doesn't do anything, this is just an infinite loop.

Here we are loading a with 1, calling the halt opcode, then jr is "jump relative", it's sending the cpu back to @loop, where it will load a and halt again, over and over and over again, until we finally turn the GBA off.

halt is an opcode on the z80 that is normally used for the cpu to synchronize with external devices, such as a disk drive or something like that. The e-Reader's z80 emulator has co-opted halt to mean "draw a frame to the screen".

Calling things like DrawText won't actually show up on the screen until halt is called. You can also set a to a higher value, say 60, then call halt, and the e-Reader will pause for 60 frames (1 second). This is useful in "real" e-Reader apps.

@region_handle:
    db 0
@hello_text:
    defm "hello ereader"
    db 0

And finally, this is our data for the app. We've already covered @region_handle. @hello_text is an ascii text string. defm tells pyz80 to take this string and place the ascii values for it into the binary. DrawText needs a closing zero terminator to know when the string ends, that is what db 0 is doing.

Oh, org?

We skipped org 0x100. What does that do?

The labels in our program, @region_handle and @hello_text, are really just for us. They make it easier to write assembly programs and refer to different parts of the program. Once the binary is assembled, those labels will be replaced with numbers. The number is the address of the label, and the address is just the position of the label in the file.

Let's pretend pyz80 was assembling our program, and had stuck 149 bytes into the binary when it encounted @region_handle. That means @region_handle's address will be 150, as it will be the 150th byte in the program. So in that case, the line ld (@region_handle), a will basically become ld (100), a.

Ok that all makes sense, but there's a catch. In our example, @region_handle will be at address 100 in the binary, but it won't necessarily be at address 100 when the app is loaded into the GBA's memory. Its actual address in memory will depend on where in memory the e-Reader stuck our app.

When the e-Reader loads a z80 app into the GBA's memory, it starts at memory address 0x100. So in our binary, the first byte is at address 0, but when running, it will actually be at address 0x100.

org 0x100 is telling the assembler that when the app runs, all of the addresses will be off by 0x100. So above when it decided the address for @region_handle was 150, it will add 0x100 onto that address and actually write down the address 356 (which is 100 plus 0x100). Now when our app does ld (356), a, the draw region handle will go into the correct place in memory.

Conclusion

Wow, that was a ton of work just to show "hello ereader" on a GBA screen. e-Reader dev is both very simple in some ways, and very complex in others.

This tutorial will not teach you z80 assembly, as there are many resources out there for that.

Here is the companion git repo for this tutorial.

In future installments we will get into sprites, input, loading graphics, sounds, and more. Stay tuned!