NES emulator

The code can be found here: GitHub


I’m writing with my friend Jose Antonio a Nintendo NES emulator.

Every night we have a pair programming session. Jose yells at me and I write random things that at the very end, compile. It’s fun.

We are n00bs in this field and for sure we are making a lot of mistakes. But that is part of the fun!

The main idea is to learn more about one of the most important platforms for us, improve our C knowledge, and have fun. The code is tested and is completely documented.

I’ll try to document the relevant days because we usually work every weekday on it but not all of them are productive.

2018-11-19

No news, dead project? Far from true, but a lot of problems with the GUI and a few businesses travel kept me away from this lovely suffering.

If you check the repository, you’ll find almost daily commits :)

2018-09-04

SDL2, oh boy, what a beast.

So now we have 3 windows and we’re gonna make a inspector window to perform some analysis because we don’t get a lot of PPU stuff.

Maybe is an overkill feature, maybe SDL2 already has this but… We made an event pool just to handle, at least for now, the close and resize events on each window.

So. How we create a new window? Easy peasy.

For each window, at least, we need 3 functions and a struct.

The struct contains the window, the renderer, the texture and the refresh rate.

Build a window with SDL2 is quite easy. You just need to create the window, the texture and then, subscribe it to the event pool:

int build_patterntable_viewer(int refresh_rate) {
	refresh_time = (1.0 / refresh_rate) * MILLISECOND;
	patterntable_window.refresh_rate = refresh_rate;
	patterntable_window.window = SDL_CreateWindow(PATTERNTABLE_WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED,
	                                              SDL_WINDOWPOS_UNDEFINED, PATTERNTABLE_WINDOW_WIDTH,
	                                              PATTERNTABLE_WINDOW_HEIGHT, SDL_WINDOW_SHOWN);

	if (patterntable_window.window == NULL) {
		log_error("Patterntable window could not be created: %s\n", SDL_GetError());
	}

	patterntable_window.renderer = SDL_CreateRenderer(patterntable_window.window, -1, SDL_RENDERER_ACCELERATED);

	patterntable_window.back_buffer_text = SDL_CreateTexture(patterntable_window.renderer, SDL_PIXELFORMAT_ABGR8888,
	                                                         SDL_TEXTUREACCESS_STATIC, PATTERNTABLE_WINDOW_WIDTH,
	                                                         PATTERNTABLE_WINDOW_HEIGHT);

	register_window_cycle(&cycle_patterntable_viewer);

	sevent(SDL_WINDOWEVENT, SDL_WINDOWEVENT_CLOSE, &on_quit_patterntable_viewer_window);
}

When an event occurs, each Windows get a notification. So, we need to check if the event is related to our window:

int on_quit_patterntable_viewer_window(SDL_Event event) {
	if (event.window.windowID == SDL_GetWindowID(patterntable_window.window)) {
		SDL_HideWindow(patterntable_window.window);
		return 1;
	}
	return 0;
}

And, the last part is quite simple too. We cannot refresh the window constantly, we need to refresh it when refresh time has elapsed.

int cycle_patterntable_viewer() {
	static double last_check;
	double time_aux = 0;
	if((time_aux = has_time_elapsed(last_check, refresh_time))) {
		last_check = time_aux;
		SDL_RenderClear(patterntable_window.renderer);
		SDL_RenderPresent(patterntable_window.renderer);
		SDL_UpdateWindowSurface(patterntable_window.window);
	}
	return 0;
}

It took a few hours but now, is easy, right?

2018-08-30

With all the opcodes implemented, we are now trying to use SDL2 to generate the graphics.

Quick humble update: I had no idea about what static on local variables in C does. I’m pretty amazed.

For the record, a quick example:

void hello() {
  int a = 0;
  a++;
}
push    rbp
mov     rbp, rsp
mov     DWORD PTR [rbp-4], 0
add     DWORD PTR [rbp-4], 1
nop
pop     rbp
ret

In summary, you are initializing the a variable to 0 and increment it by 1. If you call again the function, you start again. Nothing new here. But if you use static

void hello() {
  static int a = 0;
  a++;
}
push    rbp
mov     rbp, rsp
mov     eax, DWORD PTR a.1957[rip]
add     eax, 1
mov     DWORD PTR a.1957[rip], eax
nop
pop     rbp
ret

As you can see, the code is a little different. The main idea (this took me a little, to be honest) is that you are storing the variable in a very different location instead inside the function memory.

In the end, static allows us to do this:

void every_second() {
  static long last_second = 0;
  static long ctime = 0;
  ctime = time(NULL);

  //More than one second elapsed
  if(ctime - last_second > 1){
    last_second = time(NULL);
    log_info("Processor speed: %iHz\n", cyclesThisSec);
    cyclesThisSec = 0;
  }
}

Instead of making last_second or ctime global, or pass them as function arguments we can reuse the previous values. Simple and elegant.

2018-08-21

All of the valid opcodes have been implemented but… now we need to implement the invalid ones. We are using a special test rom and we compare the trace of the logs. It is never exactly the same (we have some weird problem with the CPU cycles) but the PC and the registers must be the same.

The problem is SAX. Is a weird opcode that ANDs A and X and subtract to that the PC+1. According to multiple sources, there is more than one SAX. In other sources, there is a thing call AXS which is a SAX that stores the result in memory.

So… now we are going to implement two opcodes. SAX with different modes and ASX.

This is the code for the generic SAX opcode. And we retrieve the value from whenever is necessary, for example using an indirect x param or a zpage y.

void sax(byte value, int cycles, int pcIncrease) {
  log_instruction(1, "\tSAX $%02X\t", value);

  byte xAndA = A & X;

  X = xAndA - value;

  bit_val(&P, flagC, xAndA >= value);

  PC += pcIncrease;
  cyclesThisSec += cycles;
}

2018-08-09

We can see the instructions running!!

SBC ED, #BA - PC=FF2E
AND 35, #0 - PC=FF31
FLAGS 18, #72 - PC=FF32
ORA D, #AD - PC=FF35
LDA AD, #BA - PC=8003

It does nothing, but how cool is that?!

2018-08-07

All the opcodes except the illegal ones are implemented.

We load ROMs onto memory. But we spent like 15 minutes screaming because after 3 bytes our program stopped reading ROMs.

  bytesRead += fread(&rom.nesTitle, 3, 1, file);
  bytesRead += fread(&rom.fileFormat, 1, 1, file);
  bytesRead += fread(&rom.numPRGPages, 1, 1, file);
  bytesRead += fread(&rom.numCHRPages, 1, 1, file);
  bytesRead += fread(&rom.flags6, 1, 1, file);
  bytesRead += fread(&rom.flags7, 1, 1, file);
  bytesRead += fread(&rom.endOfHeader, 8, 1, file);

Upon successful completion, fread() shall return the number of elements successfully read. At the end of that block, bytesRead is 1 because only the first line worked. ferror() returns 0, so no error.

But, feof() returns 16 which means EOF. The problem was how we open the file.

We had:

  file = fopen(filePath, "r");

But you must open the file as binary so…

  file = fopen(filePath, "rb");

Fixed. Easy peasy :P


I'm Alex · I do things ·