Porting an emulator to Nintendo Swith (Tutorial/Walkthrough and Discussion)
Hey guys, A few weeks ago I ported a CHIP 8 emulator to the Nintendo switch and I was pretty surprised with how easy it turned out to be. I wanted to do a quick write up/guide so other people can hopefully get involved. I am hoping to find some people interested in porting other things to help me with future projects.
It would be great to get a few people going to work together on stuff and bounce ideas off of each other. Without further introduction, here begins my guide:
Step 1: Finding a suitable candidate for porting
The first thing we need to do is find something to port, right now the tools we have in libnx are somewhat limited, but we do have SDL2! I wanted to start off simple, so I decided to port a CHIP 8 emulator, possibly the easiest system to emulate. Because I knew I had access to SDL2, I googled "SDL2 chip 8 emulator".
I was able to find this project on github (scanlong/c8-master) which is licensed under the WTHPL license, which means "do whatever the hell you want". This is a perfect candidate for our purposes because it's only dependency is SDL2 and is written in standard c.
Step 2: Successful compilation
Now we have a source that we are ready to start working on. However, it will not compile under libnx currently, as it was written for pc. We need to completely rewrite the makefile.
A good starting point is a makefile from one of the switchbrew examples on github. However we will need to make some changes to get it to compile.
To link SDL2 to the source, we need to add lLSDL2 to the libs line in the makefile.
From:
LIBS := -lnx
To:
LIBS := -lSDL2 -lm -lnx
Notice we also added -lm library, which is for mathematical functions, which we also need.
Now we need to change some of the include statements in the header files to find SDL2 in the correct place.
From:
#include "SDL.h"
To:
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
We now have a compilable project! However it still does not work.
Step 3: Getting it to run
If we try to run our current build, we will notice that it exits immediately.
If we look at the entrypoint to the emulator, we will notice a few things:
int main(int argc, char* args[]) {
if (argc != 2) {
printf("usage: c8 game\n");
printf(" game: Chip8 bianry file to play\n");
return -1;
}
char* filename = args[1];
struct cpu cpu;
if (cpu_init(&cpu, filename)) {
fprintf(stderr, "Unable to initialize the emulator!\n");
return -1;
};
struct display display;
if (display_init(&display)) {
fprintf(stderr, "Unable to initialize the display!\n");
return -1;
}
The first thing we see is that the emu is looking for command line arguments, there's no way were passing any command line args on the switch, so we have to remove those checks. The arguments are for a path to a rom, and to make this quick and not jump into switch FS stuff, we will use a hardcoded rom for this proof of concept.
We can remove this entire section of code.
if (argc != 2) {
printf("usage: c8 game\n");
printf(" game: Chip8 bianry file to play\n");
return -1;
}
char* filename = args[1];
We wont use filename, but it is an argument for the creation of the cpu, so when we call the cpu create method, we can put whatever in there:
cpu_init(&cpu, "_");
Now, in the cpu.c file, we see this section of code related to pulling a rom from the filesystem:
FILE* game = fopen(filename, "rb");
if (!game) {
fprintf(stderr, "Unable to open file '%s'!\n", filename);
return -1;
}
fread(&cpu->memory[PC_START], 1, MEMORY_SIZE - PC_START, game);
fclose(game);
You can comment this all out or delete it, we will use a hardcoded rom for now, just to get something running.
To get the data from a chip8 rom, you can run xxd -i on it, which will output a C array with the relevent binary data.
unsigned char rom_bin[] = {
0x12, 0x25, 0x53, 0x50, 0x41, 0x43, 0x45...
We will store this on the heap, and then copy it into the cpu struct for the emulator.
memcpy(&cpu->memory[PC_START], &rom_bin, MEMORY_SIZE-PC_START);
We are getting close, but the program still immediately crashes.
After some debugging, we find the culprit in the display.c file:
display->renderer =
SDL_CreateRenderer(display->window, -1, SDL_RENDERER_ACCELERATED);
if (display_check_error("renderer", display->renderer)) return -1;
We have no accelerated rendering in libnx yet, so we must use software rendering.
display->renderer =
SDL_CreateRenderer(display->window, -1, SDL_RENDERER_SOFTWARE);
if (display_check_error("renderer", display->renderer)) return -1;
Now make again, and test. We now have a working emulator!
Step 4: Next steps
This is far from finished, and uses some bad coding practices to get things going, but I think its a great beginners proof of concept.
You can use what you've learned and more research to add controller support, loading roms from filesystem, and any other features!
Thanks for reading guys, this is my first tutorial so please let me know if I could have done anything better, or anything needs clarification. I would post some pictures, links, and files, but I don't think I have enough posts yet to do that.
It would be great to get a few people going to work together on stuff and bounce ideas off of each other. Without further introduction, here begins my guide:
Step 1: Finding a suitable candidate for porting
The first thing we need to do is find something to port, right now the tools we have in libnx are somewhat limited, but we do have SDL2! I wanted to start off simple, so I decided to port a CHIP 8 emulator, possibly the easiest system to emulate. Because I knew I had access to SDL2, I googled "SDL2 chip 8 emulator".
I was able to find this project on github (scanlong/c8-master) which is licensed under the WTHPL license, which means "do whatever the hell you want". This is a perfect candidate for our purposes because it's only dependency is SDL2 and is written in standard c.
Step 2: Successful compilation
Now we have a source that we are ready to start working on. However, it will not compile under libnx currently, as it was written for pc. We need to completely rewrite the makefile.
A good starting point is a makefile from one of the switchbrew examples on github. However we will need to make some changes to get it to compile.
To link SDL2 to the source, we need to add lLSDL2 to the libs line in the makefile.
From:
LIBS := -lnx
To:
LIBS := -lSDL2 -lm -lnx
Notice we also added -lm library, which is for mathematical functions, which we also need.
Now we need to change some of the include statements in the header files to find SDL2 in the correct place.
From:
#include "SDL.h"
To:
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
We now have a compilable project! However it still does not work.
Step 3: Getting it to run
If we try to run our current build, we will notice that it exits immediately.
If we look at the entrypoint to the emulator, we will notice a few things:
int main(int argc, char* args[]) {
if (argc != 2) {
printf("usage: c8 game\n");
printf(" game: Chip8 bianry file to play\n");
return -1;
}
char* filename = args[1];
struct cpu cpu;
if (cpu_init(&cpu, filename)) {
fprintf(stderr, "Unable to initialize the emulator!\n");
return -1;
};
struct display display;
if (display_init(&display)) {
fprintf(stderr, "Unable to initialize the display!\n");
return -1;
}
The first thing we see is that the emu is looking for command line arguments, there's no way were passing any command line args on the switch, so we have to remove those checks. The arguments are for a path to a rom, and to make this quick and not jump into switch FS stuff, we will use a hardcoded rom for this proof of concept.
We can remove this entire section of code.
if (argc != 2) {
printf("usage: c8 game\n");
printf(" game: Chip8 bianry file to play\n");
return -1;
}
char* filename = args[1];
We wont use filename, but it is an argument for the creation of the cpu, so when we call the cpu create method, we can put whatever in there:
cpu_init(&cpu, "_");
Now, in the cpu.c file, we see this section of code related to pulling a rom from the filesystem:
FILE* game = fopen(filename, "rb");
if (!game) {
fprintf(stderr, "Unable to open file '%s'!\n", filename);
return -1;
}
fread(&cpu->memory[PC_START], 1, MEMORY_SIZE - PC_START, game);
fclose(game);
You can comment this all out or delete it, we will use a hardcoded rom for now, just to get something running.
To get the data from a chip8 rom, you can run xxd -i on it, which will output a C array with the relevent binary data.
unsigned char rom_bin[] = {
0x12, 0x25, 0x53, 0x50, 0x41, 0x43, 0x45...
We will store this on the heap, and then copy it into the cpu struct for the emulator.
memcpy(&cpu->memory[PC_START], &rom_bin, MEMORY_SIZE-PC_START);
We are getting close, but the program still immediately crashes.
After some debugging, we find the culprit in the display.c file:
display->renderer =
SDL_CreateRenderer(display->window, -1, SDL_RENDERER_ACCELERATED);
if (display_check_error("renderer", display->renderer)) return -1;
We have no accelerated rendering in libnx yet, so we must use software rendering.
display->renderer =
SDL_CreateRenderer(display->window, -1, SDL_RENDERER_SOFTWARE);
if (display_check_error("renderer", display->renderer)) return -1;
Now make again, and test. We now have a working emulator!
Step 4: Next steps
This is far from finished, and uses some bad coding practices to get things going, but I think its a great beginners proof of concept.
You can use what you've learned and more research to add controller support, loading roms from filesystem, and any other features!
Thanks for reading guys, this is my first tutorial so please let me know if I could have done anything better, or anything needs clarification. I would post some pictures, links, and files, but I don't think I have enough posts yet to do that.