Porting NESticle for Fun and Profit: Part 1

Written by Paco Pascal, on 28 May 2023.
Tags: #nes #emulation

What's going on? Where are the posts?

Originally, I was planning on writing these posts so readers can follow along with the process. I intended to make the problem solving visible instead of just dumping the end result. In my opinion, there's value in showing the actual thought process and methodology. I've helped many friends tackle programming problems in the past. In most cases, they have the potential to solve the problems without help; however, the main skill they were lacking that snagged them was dealing with large amounts of ambiguity, especially in large existing code-bases and systems.

Unfortunately, communicating the hills and vallies of problem solving is time consuming and frankly unenjoyable. RTFM. For the sake of closure, I'm just going to dump end results.

All of this happened last year (in 2022).

Getting Started

Obviously, the first thing to get done was compile each source file into an object file. (Turn the widdle .cpp's into .o's.) This will only imply the code is syntactically correct. Once the code can compile, I can begin testing and playing with specific areas of the code-base.

For projects like this, I prefer using the Acme editor.

Makefile Skeleton

This endeavor is an iterative process. Therefore, I need something simple that I can append each file as I progress. So, I make a simple Makefile to work with.

CPPFLAGS = -ggdb -m32 -O0 -g -Werror -std=c++17

SRC = 
OBJECTS = $(SRC:.cpp=.o)

all: $(OBJECTS)

%.o: %.cpp
        $(CXX) $(CPPFLAGS) -c $<

clean:
        rm -f $(OBJECTS)

Getting the First Files Compiled

I started with dasasm.cpp. There's very little code in it and it's straight forward C++ code with nothing unusual. From there, I decided to work on file.cpp. There's more going on in it but it's still straight forward.

Even though the code in the top directory is intended to be platform independent, it's not entirely. The most obvious thing that has to be changed is the include header files. DOS provides a collection of non-standardized functions and headers such as, conio.h, io.h, and direct.h. Also, header paths that are in a subdirectory such as sys/types.h are written as sys\types.h which isn't valid on Linux.

This is an easy fix. As I'm progressing through each file, issues such as old DOS macros, non-standard C++ flexible arrays, and implicit casts from const char* to char* show up. Some of which were easily forgiven in the 90s. Eventually, each file finds its spot in the makefile.

SRC = portutils.cpp    disasm.cpp    file.cpp      font.cpp \
          mouse.cpp       nes.cpp  nesdlg.cpp  nesvideo.cpp \
           prof.cpp     r2img.cpp     rom.cpp     slist.cpp \
       snapshot.cpp    stddlg.cpp  config.cpp   command.cpp \
            mmc.cpp  nessound.cpp     cpu.cpp   message.cpp \
          input.cpp    inputw.cpp    main.cpp       vol.cpp

Suddenly, I have each file compiling.

nes1_compiled.gif

Figure 1: NESticle C++ files compiling.

Making Things Work

Just because things are compiling, doesn't mean they work. I decided to get file IO working first; specifically, I wanted to be able to read the gui.vol file. This file contains various assets that can be drawn. Being able to read and write a GUI volume file had almost no dependencies. The code was pretty much independent from the rest of the code base. That makes it an easy starting place. Additionally, it'll give me real assets to use later on when I port the drawing code.

So, I pulled GUI.VOL from NESticle versions 040 and 042 into the source directory. Then wrote a simple tool (I called volmain.cpp) that linked to file.o and vol.o which are the only dependencies needed to read a GUI volume file. The tool would just read each asset from the volume and write it out to it's own file.

This is where one of the first bugs rose up which would become a repetitive one throughout the code. The volume files have a header for each asset that is defined by the structure called struct header.

// header for each entry in the volume file
struct header {
        char key[4];         // must be "DSL"
        char type;           // type of data
        unsigned int size;   // size of data
        char name[9];        // name of data
} __attribute__((packed));

However, it didn't look like this originally. The structure wasn't packed using the __attribute__((packed)) attribute. NESticle assumes all it's structures are packed because back when the code was written, structs were packed. These days most compilers pad their structs by default. So, whenever NESticle reads data into a structure, it assumes the data matches it's memory layout. However, the GUI volumes were created using old versions of NESticle that had packed structs by default. It's not a bug in the code. It's just changes in time rusting it a bit.

The alternative to packing the structs is to convert the volume files to have padded struct data. But the idea right now is to maintain and port what was, to today. Changing the memory layout prematurely might cause complications down the line that I can't see at the moment.

./vol.h:17:} __attribute__((packed));
./r2img.h:24:} __attribute__((packed));
./r2img.h:48:} __attribute__((packed));
./r2img.h:56:} __attribute__((packed));
./r2img.h:66:} __attribute__((packed));
./r2img.h:92:} __attribute__((packed));
./r2img.h:108:} __attribute__((packed));

With packed structs, I'm now able to successfully use the file IO and volume segments of the code-base. And I have the original collection of NESticle assets ready to be put on the screen.

nes1_exportvol.gif

Figure 2: Exporting assets from a GUI volume. The error message is the early tool not handling the EOF correctly.

Conclusion

After accomplishing these first critical steps, porting the r2 drawing code was within sight. Half of the code is written in x86 assembly. And I intended on keeping that way.