This project details my efforts in experimenting with running games on the ESP32, and porting new games to it.

I started off with an ESP32 WROVER DevKit, added an SPI LCD screen (type ILI9341), SPI SD Card slot, amp + speaker, and a few buttons.  Using an ESP32 WROVER is important, because the WROVER contains 4MB ram - which is essential for running games. Now I had everything I need to start running games.

DOOM

I started off trying to run DOOM.  But it used the onboard SPI Flash to hold the game data.  An ESP32 only has a max of 16Mb spiflash - which means that this would be a limitation to what games could be run, and how easy is is for others to run these games.

So my first task was getting DOOM to run from the SD Card - this was a fairly easy swap. 

Also I added back in the code to have sound working, and modified it to use the i2s sound output.  The ESP32 can internally connect the i2s output to it's DACs, so it can directly product sound output, it just needs some amplification...

Code: https://github.com/jkirsons/Doom


Development Environment

At this stage I was using PlatformIO + VSCode as my development environment.  It was easy to initially setup, but I quickly hit limits using PlatformIO.

PlatformIO didn't give me the ability to run "make menuconfig" where you can directly change lots of memory/spi/rtos related settings.

My next step was to install the full ESP-IDF toolchain:

https://docs.espressif.com/projects/esp-idf/en/latest/get-started/

This let me unlock lots more potential from the ESP32, and gives valuable debug tools (including backtrace code address resolution).  The good this is, I can still use VSCode as my IDE.

If you're using Mac or Linux, you can even compile/monitor/menuconfig directly from a terminal in the IDE:

OpenTyrian

My next step was to port a new game over to the ESP32 - one that no one has ported yet.  So what game has been ported to nearly every platform?  Tyrian!

OpenTyrian has the advantage that nearly all the platform dependent code (ie video, sound, input, disk) has been removed, and handled by one library.  That library is SDL (https://www.libsdl.org/).

I ended up making my own "mini" SDL library for the ESP32.  I call it mini, as it only implemented the functions that OpenTyrian needed.  The screen writing code was used from Doom (ie flipping a memory array (SDL Surface) to the screen), Input events re-written to use GPIO inputs, sound re-written to use i2s sound output, and initialisation of the SD Card before any disk I/O calls.

Here is the result:

Code: https://github.com/jkirsons/OpenTyrian

Alright, I've ported my first game!  What's next?  I have a partial SDL 1.2 library that can make porting the next game a bit easier, so I just need an SDL port of an old classic DOS game that would run in an early 486.

I found this: Chocolate Duke Nukem 3D
http://fabiensanglard.net/duke3d/chocolate_duke_nukem_3D.php

Duke Nukem 3D

So I stated at getting Duke Nukem 3D to compile on the ESP32.

My first problem was memory.  Duke Nukem 3D had lots of (large) arrays.  An array takes up DRAM, of which the ESP32 only has around 290kb.  The 4Mb SPI RAM can only be used by malloc() calls.  OpenTyrian was ok because there were not too many arrays, but Duke Nukem 3D has lots of arrays.

At this point, I found in one of the later ESP-IDF releases, an attribute was introduced that lets the array be allocated in SPI RAM instead of DRAM.  It is EXT_RAM_ATTR - and I used this a lot:

So now I had Duke Nukem 3D compiling and running on an ESP32!!!

Code: https://github.com/jkirsons/Duke3D

The next level - ODROID-GO

OK, now I have proven that I old DOS games can be ported to the ESP32, it's time to take it to the next level.  The breadboard works, but if you wobble things too much it might crash, and most importantly, the SD Card reads aren't very stable with breadboard connections.

So I decided to get an ODROID-GO (https://www.hardkernel.com/shop/odroid-go/).

It's basically the same as what I've been using on the breadboard, but much neater - and very cheap.

It comes in a kit form that you assemble yourself.  Assembly is basically plugging in parts and screwing it together.

Now it's time to move the games to the Odroid-Go - some of the changes for this are:

So first I moved over Duke Nukem 3D:

And then OpenTyrian:


For this release of OpenTyrian, I changed the malloc() calls and replaced them with EXT_RAM_ATTR.

I was still having some problems getting sound working on Duke Nukem 3D, but then I worked out that I was trying to play back sound at 22khz when it was actually 11khz.  Problem fixed:

What's next?  I heard a few people asking for Wolfenstein 3D, so I decided to give this a go.

Wolfenstein 3D

I found this port of Wolfenstein 3D to SDL, it sounds like exactly what I needed:

https://github.com/mozzwald/wolf4sdl

The only problem was that this port used more SDL functions than OpenTyrian and Duke3D, and it also used SDL_mixer - another SDL add-on that handles audio mixing.  So I decided to get this working. 

This time with SDL_mixer ported, I did not have to remove the music code from the game.  So this is my first game with FM Synthesis (Ad-lib emulation) working.

Code: https://github.com/jkirsons/wolf4sdl

It was a bit of a juggling act getting every thread to run smoothly:

If any one of these is starved for time, then the sound/music is glitchy, or the framerate drops.

Spear Of Destiny

Porting Spear Of Destiny was pretty easy - it's basically Wolf3D with a few things changed.

The code was not letting go of one of the audio buffers, so sounds were stored twice - once I found this, the game was done:

Code: https://github.com/jkirsons/wolf4sdl

A few viewers notice that you couldn't play the mission packs.

SOD mission packs are played through the command line.

So I added a feature during game initialisation that if you hold down start or select, it loads up Mission 2 or Mission 3.