# Intro For Kernelcon 2022 I created a Super Nintendo CTF Challenge. I had never made a Super Nintendo game before and I thought it would be fun to show the process. # Part 1: Building a game that prints text After a little googling I found some good resources: - https://wiki.superfamicom.org/tags/tutorial - https://nesdoug.com/2020/03/19/snes-projects/ Back in the day programming these games was very low level and required intricate knowledge of the hardware itself to optimize everything. The super nintendo used a special version of assembly written for the 65816 processor. That sounds hard, it's 2022 there's gotta be an easier way. After some googling I found a guy who created and sold a physical SNES video game in 2020. https://drludos.itch.io/keeping-snes-alive https://drludos.itch.io/the-last-super Quite an interesting journey, in the blog he references how he used [pvsneslib](https://github.com/alekmaul/pvsneslib) a library on github that enables you to write games using C code. Hell yeah that sounds way easier than learning specialized assembly. Following the fantastic instructions on the wiki page and within minutes I had a .SFC rom that printed hello world https://github.com/alekmaul/pvsneslib/wiki/Installation-with-Windows https://github.com/alekmaul/pvsneslib/blob/master/snes-examples/hello_world/src/hello_world.c Building hello world is as simple as installing the library, opening the example folder in a command prompt, and running the make command. Pretty neat stuff. I recommend using bsnes to play your game ![](images/snes/snes1.JPG) There are some fantastic examples in the repo: https://github.com/alekmaul/pvsneslib/tree/master/snes-examples Some open source games written using the library: https://github.com/alekmaul/pvsneslib/wiki/Community-code and the people on the discord server are very helpful The command to add text is just: ``` consoleDrawText(1,2,"some text on the screen"); ``` Where it draws the text at column 1, row 2. To delete text you simply overwrite it with something else like spaces. # Part 2: Showing a picture Printing text is really neat, but any real game would need to be able to display a picture. How do I do that? When you call the library function to print text what it's actually doing is printing part of a picture. Inside the hello world folder is a pvsneslibfont.bmp that looks like this: ![](https://raw.githubusercontent.com/alekmaul/pvsneslib/master/snes-examples/hello_world/pvsneslibfont.png) On make the image is converted to a .pic file using a special tool in the library called gfx2snes. The data is then imported inside data.asm as snesfont ``` snesfont: .incbin "pvsneslibfont.pic" ``` and referenced in c from ``` extern char snesfont; ``` The picture is then initialized to address 0x0000 by the library with ``` consoleInitText(0, 0, &snesfont); ``` Which you then display with `consoleDrawText()` Notably, the example code prints the text using graphics mode 1, the most common mode. So there are 8 different graphics modes the super nintendo can use and they support various resolutions, numbers of colors, and layers of pictures. You can read more here: https://nesdoug.com/2020/04/02/snes-overview/ You can watch more about them here: https://www.youtube.com/watch?v=5SBEAZIfDAg and another great video here: https://www.youtube.com/watch?v=kwAjG2dj5ZM To skip the rabbit hole all you really need to know is to display a picture I'm going to use mode 1 which has three different layers you can show images on. In mode 1, layer 0 uses 16 colors, layer 1 uses 16 colors, layer 2 uses 4 colors. Ok enough background how do I make a game with a picture in it that isn't just text? There's an example you can build: https://github.com/alekmaul/pvsneslib/tree/master/snes-examples/graphics/Backgrounds/Mode1Png ![](images/snes/snes2.JPG) # Part 3: Using your own image as a background If you attempt to just edit this picture with something like paint.net and overwrite the file you'll find the game doesn't compile anymore or worse yet prints distorted images with the wrong colors. What gives? Well the gfx2snes tool is currently very poorly documented, but it requires images be exactly 256 colors with a resolution of 256x224. Additionally it should only use the first 16 of the 256 colors. How I found this was the result of numerous hours of trial and error with extensive googling. There very well could be a better way, the gfx2snes tool takes various flags, this is just what I got working. ## Lets go on a tangent on images and how I figured this out Since Kernelcon 2022 was ninja turtles themed. I took a screenshot of the super nintendo game from [youtube](https://www.youtube.com/watch?v=wpz0xxjZuCw&t=31) In order to alter the text to say HACKERS instead of turtles I used http://glench.com/tmnt/ And with paint.net I cropped and resized the image to 256x224, cut out the old logo, added the new one, filled the empty space, and saved it as a 8bpp png. Attempting to use this in the project showed a garbled image. Paint.Net is easy to use, but isn't the most advanced tool in the world. So I opened the image in gimp to try again. I converted the image to rgb: image -> mode -> RGB And then converted it back to indexed image -> mode -> Indexed... -> Generate optimum palette -> 256 -> Convert (You might want to use dithering to get a good conversion depending on your image) file -> export as -> export -> uncheck save creation time, save exif data, save xmp data, save thumbnail, save color profile -> export Now we have a file with the correct dimensions, should be using 256 colors, and is made by gimp. Attempting to use this image in the game gave me similiar graphical glitches. ![](images/snes/snes3.JPG) What is going on? Well what images can display? The example and the font. So using yet another tool GraphicsGale I opened the font image and saw it used 2 colors pink, white, and 254 blacks. Opening up the original example png and it also has a first color of pink. Interesting. So editing my image in Graphics Gale and double clicking the first pixel and making it pink replaces the black with pink, not ideal ![](images/snes/snes5.JPG) which makes a game of ![](images/snes/snes6.JPG) It's like it replaced black with pink and showed black for the rest, super weird. Taking a closer look at the original I noticed it only has colors for the first 16 and then they are all black. While my image had pink and 15 shades of black as the first 16 colors. So with gimp I took the original down to 16 colors and saved it. Using GraphicsGale I then resized it to 256 colors All Frames -> Color Depth -> Ok -> Save And recompiled yielding: ![](images/snes/snes7.JPG) ## /end tangent So to fix this issue and to edit a picture. Create your file in Paint.net with a resolution of 256x224. When saving as a png select 4 bit which only saves 16 colors. Then open the image in GraphicsGale to pad to 256 colors: All Frames -> Color Depth -> Ok -> Save # Part 4: Button input Button input is pretty straightforward. There's an example here: https://github.com/alekmaul/pvsneslib/tree/master/snes-examples/pads/input ![](images/snes/snes8.JPG) There's a function to read the keypad and a simple switch statement to call various functions based on the keypress Keep in mind no button press is also a key state. # Part 5: Bringing it all together When showing both an image and text at the same time I kept getting garbled data displayed. It took me a while but I eventually found `consoleInitText()` initializes the font to 0x0000 And the example for printing an image also uses 0x0000. I modified `bgInitMapSet()` to use an address of 0x1000 and they played nice. Here is the source for the easy challenge I made for kernelcon ``` /*--------------------------------------------------------------------------------- Built using pvsneslib ---------------------------------------------------------------------------------*/ #include <snes.h> #include <string.h> extern char patterns, patterns_end; extern char palette, palette_end; extern char map, map_end; extern char snesfont; char p1[10]; char p2[10]; char p3[10]; char p4[10]; char p5[10]; char theguess[50]; char prior[10]; int clear = 0; //--------------------------------------------------------------------------------- void parseInput(char *theinput) { clear = 0; if (strcmp(prior,theinput) == 0) { return; } strcpy(prior,theinput); if (strcmp(theinput,"N") != 0) { consoleDrawText(4,16," "); // Debug, print last key press //consoleDrawText(4,15,"%s ",theinput); if (strlen(p1) == 0) { strcpy(p1,theinput); } else if (strlen(p2) == 0) { strcpy(p2,theinput); } else if (strlen(p3) == 0) { strcpy(p3,theinput); } else if (strlen(p4) == 0) { strcpy(p4,theinput); } else if (strlen(p5) == 0) { strcpy(p5,theinput); // check if string is correct sprintf(theguess,"[%s,%s,%s,%s,%s]",p1,p2,p3,p4,p5); /*char combos[6] = "UDAYX"; char combo[20] = "[A,A,A,A,A]"; combo[1]=combos[0]; combo[3]=combos[1]; combo[5]=combos[2]; combo[7]=combos[3]; combo[9]=combos[4];*/ int i; if (strcmp(theguess,"[U,D,A,Y,X]") == 0){ char o1[25] = "hfqmfoxz3vqfmlwkvnbmp~"; for (i=0; i<strlen(o1); i++){ o1[i] = o1[i]^3; } //consoleDrawText(4,16,"kernel{y0urenothumans}"); consoleDrawText(4,16,o1); } else{ consoleDrawText(4,16,"Fail "); } clear = 1; sprintf(theguess, "[%s,%s,%s,%s,%s] ",p1,p2,p3,p4,p5); consoleDrawText(11,14,theguess); strcpy(p1,""); strcpy(p2,""); strcpy(p3,""); strcpy(p4,""); strcpy(p5,""); } if (clear == 0){ sprintf(theguess, "[%s,%s,%s,%s,%s] ",p1,p2,p3,p4,p5); consoleDrawText(11,14,theguess); } } } int main(void) { // init gamepad var unsigned short pad0; // Initialize SNES consoleInit(); consoleInitText(0, 5, &snesfont); // Copy tiles to VRAM bgInitTileSet(1, &patterns, &palette, 0, (&patterns_end - &patterns), (&palette_end - &palette), BG_16COLORS, 0x4000); // Copy Map to VRAM bgInitMapSet(1, &map, (&map_end - &map),SC_32x32, 0x1000); setMode(BG_MODE1,0); bgSetDisable(2); // Print Text consoleDrawText(4,12,"Can you crack the combo?"); consoleDrawText(4,14,"Guess: "); //consoleDrawText(4,16,"Fail"); setScreenOn(); // Null globals strcpy(theguess,""); strcpy(p1,""); strcpy(p2,""); strcpy(p3,""); strcpy(p4,""); strcpy(p5,""); strcpy(prior,"none"); while(1) { pad0 = padsCurrent(0); switch (pad0) { case KEY_A : parseInput("A"); break; case KEY_B : parseInput("B"); break; case KEY_X : parseInput("X"); break; case KEY_Y : parseInput("Y"); break; case KEY_RIGHT : parseInput("R"); break; case KEY_LEFT : parseInput("L"); break; case KEY_DOWN : parseInput("D"); break; case KEY_UP : parseInput("U"); break; //case KEY_SELECT : parseInput("SELECT"); break; //case KEY_START : parseInput("START"); break; case KEY_L : parseInput("LT"); break; case KEY_R : parseInput("RT"); break; default : parseInput("N"); break; } WaitForVBlank(); } return 0; } ``` data.asm ``` .include "hdr.asm" .section ".rodata1" superfree snesfont: .incbin "pvsneslibfont.pic" patterns: .incbin "pvsneslib.pic" patterns_end: .ends .section ".rodata2" superfree map: .incbin "pvsneslib.map" map_end: palette: .incbin "pvsneslib.pal" palette_end: .ends ``` makefile ``` ifeq ($(strip $(PVSNESLIB_HOME)),) $(error "Please create an environment variable PVSNESLIB_HOME with path to its folder and restart application. (you can do it on windows with <setx PVSNESLIB_HOME "/c/snesdev">)") endif include ${PVSNESLIB_HOME}/devkitsnes/snes_rules .PHONY: bitmaps all #--------------------------------------------------------------------------------- # ROMNAME is used in snes_rules file export ROMNAME := Mode1Png all: bitmaps $(ROMNAME).sfc clean: cleanBuildRes cleanRom cleanGfx #--------------------------------------------------------------------------------- pvsneslib.pic: pvsneslib.png @echo convert bitmap ... $(notdir $@) $(GFXCONV) -pc16 -po16 -n -gs8 -pe0 -fpng -m $< pvsneslibfont.pic: pvsneslibfont.bmp @echo convert font with no tile reduction ... $(notdir $@) $(GFXCONV) -n -gs8 -po2 -pc16 -pe1 -mR! -m! -p! $< bitmaps : pvsneslib.pic pvsneslibfont.pic ``` ![](images/snes/snes9.JPG) The password can be found by just running strings on the binary or opening the strings view in ida. The harder challenge used an XOR'd combo as well.