miércoles, 28 de diciembre de 2016

Games aside #1: Super Game Boy development (step by step)

Throughout this post we will learn to implement some of the features offered by the Super Game Boy, specifically those used by the recently published Super Game Boy compatible Rocket Man demo. Demo exposed as a local production in the international independent video game contest AzPlay held last November in Bilbao.

Before starting I would like to clarify a number of points:
  1. the program that we will elaborate is programmed in assembler and makes use of RGBDS for the creation of the ROM;
  2. this publication does not cover the bases of the development for Game Boy in assembler, but nor does it require an advanced knowledge on the subject. It can work as an additional lesson to the numerous tutorials that exist on the web in this respect (personal recommendation, https://avivace.ovh/apps/gbdev/salvage/tutorial_de_ensamblador%20%5bLa%20decadence%5d.html);
  3. according to the second point, we will work in the implementation of additional functions related to the Super Game Boy for an existing base program.
This post will cover the use of the following exclusive features of the Super Game Boy:
  • custom frame display;
  • custom game palettes.
Giving as a result the following Game Boy ROM.

Super Game Boy visualization of the ROM
 ROM DOWNLOAD

First we should talk about what the Super Game Boy is. According to the Game Boy Programming Manual (page 124),  it is a device that allows you to enjoy Game Boy software on a television screen. This software (contained in the Game Boy cartridge) can be connected to the Super Game Boy, which operates on a Super Nintendo.

As the text emphasizes, Super Game Boy operates on a Super Nintendo. This cartridge is a replica of the Game Boy hardware capable of communicating with the SNES, it helps us inform the home console of the operations we want to perform and send the necessary data to it, but it is ultimately its hardware and not ours (the Super Game Boy one) the responsible for processing and applying the unique features offered by this technology. That is, the graphics of the frame for example, obey the limitations of the SNES and not the Game Boy ones.

Preparations

That said, we move on to the preparation of the computer where we are going to develop. This is a list of the elements that we should have ready in our PC with Windows:
  1. code editor for program editing (personal recommendation, Visual Studio Code and the Z80 Assembly extension);
  2. RGBDS correctly configured as environment variable;
  3. Game Boy Tile Designer and Game Boy Map Builder for graphics and map editing respectively;
  4. Although the program of this publication is tested in a real Super Game Boy, it is recommended to have the necessary technology to execute the resulting ROM in the final hardware. Failing this, emulators like  BGB or bsnes can serve to test the developments that are made with this device in mind, but do not guarantee a perfect emulation.
Well, it's time to get down to business. As this post is about development for Super Game Boy and not about development for Game Boy we will start from a base project whose ROM offers the following result.

A static title screen

This is the main source file for this template project.


During development it will be important to have some documentation handy. In my opinion, these would be the texts that condense all you need to know when it comes to programming for Super Game Boy:
Each time I give information about the Super Game Boy I will try to complement it with the source of this information, indicating in which part of these two documents it is.

Making the graphics

We will begin by freely designing the Super Game Boy frame that we want to apply. Of course, taking into account the following limitations:
  1. The resolution of the frame must match the resolution of the SNES, ie, 256 pixels wide by 224 pixels high;
  2. we must leave a blank space of 160 by 144 pixels in the central part, which is where the screen of the Game Boy will be displayed;
  3. the SNES has 8 palettes of 16 RGB 15 bit colors (SGB Functions> Available SNES Palettes). The first four (0-3) are used to color the Game Boy's own graphics and the last four (4-7) for the frame. In the section Frame palettes I explain in more detail this limitation;
  4. we can not use more than 256 tiles of 8 by 8 pixels at the time of designing the frame (the tiles of the SNES are 8x8 and we have a byte to map them. The good thing is that these tiles can be flipped horizontally and vertically.

Frame palettes

As we have commented we have at our disposal 4 palettes of 16 colors for the frame. Although not exactly, according to the official documentation we have the 4-6 (Game Boy Programming Manual> page 162), ie 3 palettes. Anyway we can also use the 7. But some emulators will not recognize this last palette correctly.

15 bit RGB color, what does this mean? We have 5 bits to indicate the level of red, 5 for green and 5 for blue. That is, an accuracy of 0-31 to define the intensity of each of the colors. If we have an RGB color of 24 bit (the predominant format) that we want to use on any of our palettes we would have to round up the value from 0 to 255 that we have for color to the equivalent between 0 and 31.

We have said 16 colors, must be qualified: color 0 is shared (SGB Functions> Color 0 Restriction)! This means that if the color 0 of the last palette we assign is red, the color 0 of the other graphics will change to red. And we have 16 colors just in case we want to spend the memory needed to store 4bpp graphics (4 bits per pixel) in the Game Boy. If 2bpp is enough then we have four colors per palette with the color 0 shared, but the graphics will occupy half in memory.

There are many games that accept the limitation of four colors per palette in the frame showing the color 0 to save memory. As for example it happens with the frame of Pokémon Blue.

Comparing the two red squares we see that the frame shows the color 0 shared with the rest of the palettes. Using four colors per tile (2bpp)

Our frame

This is our frame.

256 by 224 pixels frame

These our tiles.

176 tiles (including tile null one) of the 256 available

And these our palettes.

Palettes 4-6 used to represent the colors of each of the tiles

Three palettes of 16 colors (4bpp), the color 0 (first column) is not used so we don't have to share it with the palettes that we are going to use in the graphics of the Game Boy. Because of the characteristics of the development, this is the color distribution that has remained on the three palettes. Only one might as well have been used, since they have many colors in common.

And now, it's time to turn these specifications into data that we can add to our program.

Tiles file

The graphic data of the tiles will be created with Game Boy Tile Designer. In our frame some of the palettes use more than 4 colors so our graphics should be 4bpp.

If the graphics are 2bpp we simply draw the tiles with the editor taking into account that the colors we choose for the values 0, 1, 2, and 3 will correspond to the colors 0, 1, 2 and 3 of the palette that we have specified for that tile.

Left: Game Boy Tile Designer tile; center: palette specified for the tile; Right: tile display in Super Game Boy

The SNES 4bpp graphics can also be created in a simple way using the same program: drawing two consecutive Game Boy tiles for each tile of Super NES. The first for bit planes 1 and 2, and the second one for numbers 3 and 4.

We can combine two Game Boy pixels (2 bits) to create a SNES pixel (4 bits)

Although we use 4bpp graphics we should also have a .gbr file that dedicates a single Game Boy tile per SNES tile. And we will use it when elaborating the map of the frame with Game Boy Map Builder, in this way we will reference the number of the tile as it would do the SNES and not the Game Boy.

The result of the elaboration of these tilesets with Game Boy Tile Designer is the following: the tileset that will store the graphics to use in the frame (FILE DOWNLOAD) and the tileset whose only function will be to contain an index of the tiles to be mapped later (FILE DOWNLOAD). Within the project, we will place these two files in res/tiles/.

And with the following configuration, we export the 4bpp tileset to obtain the necessary data file for the project. We delete the section declaration and automatically generated tags and place the resulting file in dev/data/tiles/ as sgb_tileset.z80 (FILE DOWNLOAD).

We generate a .z80 file compatible with RGBDS

Map file

Now, with Game Boy Map Builder, we will build the frame. We must associate it with the .gbr file of the tileset with the correct indexes, the one that uses a single tile of Game Boy per tile of Super NES. Remembering the fact that the dimensions of the frame are 256 by 224 pixels, which means 32 by 28 tiles. So once we open the program we go to "File> Map properties ..." and we take care of adapting dimensions and tileset.

The tileset we use here only serves as a visual reference when it comes to mapping the tiles

As I mentioned earlier, the SNES tiles can be flipped either horizontally (X Flip) or vertically (Y Flip). Additionally each of the tiles will be associated with one of the palettes we have specified. That's why we need to define additional attributes for each tile we place on our map. We can do this in "File> Location properties ...".

We do not define the vertical flip (Y Flip) because it is not used in our frame

As we will see the flip is defined as a single bit (1 flipped, 0 not flipped) and the palette with 2 (range of 0 to 3 that corresponds respectively with the palettes from 4 to 7), these will be the 2 less significant bits of the 3 that are later assigned to the palette. The third bit, the most significant bit, will always return the value of 1. This gives us a real range of values from 4 to 7 when exporting.

Now we map the tiles of the frame, and define their location properties through the fields available in the bottom row of the editor.

We recall that we must leave a space of null tiles in the position of the Game Boy screen

As a result, we have a .gbm file with the map design that will conform the frame (FILE DOWNLOAD), we place it in res/maps/. As a point, I would like to clarify that the first thing to do when opening the map in Game Boy Map Builder is to reference the tileset of the indexes through "File> Map properties ...".

Now we are going to generate the data file relative to the map through "File> Export to ...". Let's make sure that we comply with the map format specification (SGB Functions> SGB Command 14h - PCT_TRN). In the export options we will include the location properties we defined above. This would be the official specification regarding our export configuration.

Official frame tiles format specification

In the case of the Y Flip property, we ignore it occupying its place with a 0

Once exported, we delete the section declaration and the resulting data tags and place it in dev/data/maps/ (FILE DOWNLOAD). 

We already have all the graphic files ready, only the data from the palettes is missing. To do this we will now create the file sgb.z80, which we will place it dev/data/. Here we are going to write the definitions of the packets that we will send to the SNES and in this case also the macro of the RGB of 15 bits that will be used for the colors of our palettes.


The three parameters of the macro correspond respectively to the value of red, green and blue that we want to give to each color. Well, now we have to specify palettes based on this macro.

We must place the palettes next to the map data (SGB Functions> SGB Command 14h - PCT_TRN), so we will create a file in dev/data/maps called sgborder.z80 (FILE DOWNLOAD), there we will adapt our palettes to the specification.
 
The map must be 32x32 tiles but the last 4 rows are not part of the screen and are unnecessary

  
We remember the specification of our palettes

The first color of each palette (color 0) is specified but not used

Game palettes

Finally we are going to add to sgb.z80 the declaration of the palette that we will use for the graphics of the game itself. Recall that while the 4-7 palettes are used for the frame, the 0-3 palettes are used for the graphics of the game. The Game Boy's monochrome graphics have a 4 color limitation so we can only use 4 colors in this palette.

We will only use one of the four palettes assignable to the Game Boy game

The color 0 that we define should be the one we want to share with the rest of the palettes

As we have already specified all the necessary graphic files, it is time to add them to the memory bank that we have declared in main.asm.

 

Logic programming


Auxiliary definitions

There are two ways of sending information to the Super NES (Game Boy Programming Manual> page 127), through registers P14 and P15 and through the Game Boy's own VRAM (VRAM transfer). Both involve the use of commands. So let's start by defining in the sgb.z80 file the macros of the commands we are going to use (we have a definition of what each command does in Game Boy Programming Manual> page 132 and SGB Functions> SGB Command Summary):
  1. MLT_REQ, makes a multiplayer request, it will be used to detect if the software is running on a Super Game Boy;
  2. CHR_TRN, we will use it to copy the tiles from the frame to the SNES RAM (with a VRAM transfer)
  3. PCT_TRN, we will use it to copy the frame map to the SNES RAM (with a VRAM transfer);
  4. PAL_SET, will assign the palette that we have defined for the graphics of the game;
  5. PAL_TRN, will copy the palette that we have defined for the game graphics to the SNES RAM (with a VRAM transfer);
  6. MASK_EN, will freeze the screen during communication processes with the Super Game Boy, and unfreeze it upon completion;
  7. Finally we will define eight data packets that we will send to initialize the communication as recommended in the official documentation (Game Boy Programming Manual> page 178).


And with this we finally complete the auxiliary file sgb.z80 (FILE DOWNLOAD).

As a prerequisite to logic programming, we must write a 0x03 in the 0x0146 address of our ROM to indicate that we support the functionality of Super Game Boy (Game Boy Programming Manual> page 178). We already have parameterized this address in gbhw.inc, so we modified the second parameter of the header in main.asm.


Communication implementation

Let's explain with a practical example how we have defined the macros of the packets in sgb.z80. Let's take our macro from MLT_REQ and compare it to the memory specification of the command (SGB Functions> SGB Command 11h - MLT_REQ).

As we see the Super Game Boy allows a multiplayer mode of up to 4 players with the SNES Super 5 multi-player adapter


We see how byte 1 defines the number of players requested in the Super Game Boy multiplayer mode. We parameterized this byte in our macro and specified with two labels the two variants that we will use in our program.

All commands are made up of 16 bytes (128 bits), which is the size of the data packet that we can send. So we only send one packet per command. The number of packets per transfer can be specified through the three least significant bits of byte 0 of the first packet (as we see in the specification, byte 0: Command * 8, which happens to occupy the 5 most significant bits + Length, which occupies the remaining 3 bits), so we can send from 1 to 7 packets per transfer. We will represent the possibility of sending more than one packet per command in the code, but in the resulting program we do not use this functionality.

Now we will describe the process of sending each packet as explained on page 128 of the Game Boy Programming Manual. Sending made through bits 4 and 5 of the Game Boy's P1 register (0xFF00), these bits correspond respectively to the output ports P14 and P15:
  1. we start the communication by putting to 0 P14 and P15;
  2. we send the 128 bits of the packet by putting to 0 P14 and to 1 P15 if we want to send a 0 and to 1 P14 and 0 P15 if we want to send a 1;
  3. we send a 0 in bit 129 to close the communication (P14 to 0 and P15 to 1).
The following list of rules must be taken into account:
  1. the values we write in P14 and P15 must be kept for 5μs, and between writtings we must set P14 and P15 to 1 for at least 15μs. Considering that a Game Boy clock cycle already lasts more than 23μs, we ignore the times, but note that we must write a 1 on both ports for each bit sent;
  2. between sending one packet and the next we should wait for a minimum of 60 msec (about 4 frames of Game Boy). The "sgbpackettransfer_wait" routine will take care of this wait.
With all of this in mind, this is our routine of sending packets, which we add to main.asm.


The first functionality that will use this packet sending routine is the one that recognizes whether or not we are in a Super Game Boy. We will do it the following way (which is the recommended way in SGB Functions> Detecting SGB hardware):
  1. we will read the identifier of the register P1, which in addition to serving to send information through the ports is the register that returns the information of the joypad of the Game Boy. By default, its reading returns the identifier of the joypad;
  2. we request the two player mode of Super Game Boy by sending the command packet MLT_REQ with two players as the parameter;
  3. we re-read the identifier of the register P1. If it is the same identifier, it means that the two player mode has not been selected and that therefore we are not in a SGB.
We place the following code in main.asm.


The following routine will be called "init_sgb_default", and does the sending of the initialization packets recommended by the Game Boy Programming Manual (page 178). We also add it to main.asm.


The transfer commands CHR_TRN, PCT_TRN, and PAL_TRN use the VRAM transfer to copy their data to the SNES RAM. Here are the steps we must follow to perform this type of transfer (as explained in SGB Functions> SGB VRAM Transfers):
  1. if we do not want the data to be displayed on the screen of the Game Boy we must have previously configured a mask for the LCD of the Game Boy by  making use of the command MASK_EN;
  2. we disable interrupts and turn off the LCD, to avoid that the graphics we want to send are corrupted;
  3. we assign the default values to the background palette of the Game Boy, which translates to writing 0xE4 in the palette register that is at 0xFF47 (0 white, 1 light gray, 2 dark gray, 3 black);
  4. we copy the 4KB of data that we want to send to the visible background of the Game Boy LCD, the background address begins at 0x9800;
  5. we send the transfer command, either CHR_TRN, PCT_TRN or PAL_TRN. The data displayed in the visible background of the Game Boy will be sent to the SNES.
Before showing the code of the routine, a little more information about on MASK_EN. It is usually used at the beginning of the program to hide the actual display of the Game Boy LCD during data transfer.

(SGB Functions > SGB Command 17h - MASK_EN)

There are 4 possible values for command byte 1, we use the value 1 to freeze the screen image and 0 to unfreeze it when the process is finished. As we see in the tags we have defined in sgb.z80.


And once the digression is finished, this is the data transfer routine. We will place it in main.asm.


As we can see, we have an additional routine called "parsesgbbordertiles". This routine allows us to save memory in case the data we want to copy is 2bpp, since it will fill with zeroes the bit planes 3 and 4 (which we do not use). In our case, being 4bpp tiles, we do not make use of it.

And finally, using the routines we have developed so far, this would be the description of the main initialization routine of the Super Game Boy:
  1. we check if we are in a Super Game Boy, if we are not, we return and the routine ends;
  2. we freeze the LCD display of the Game Boy throughout the process;
  3. we send the initialization packets recommended by the documentation;
  4. we copy the 256 tiles we will use in the frame to the SNES RAM. In two batches of 128;
  5. we copy the frame information to the SNES RAM;
  6. we copy the information of the game palettes to the SNES RAM;
  7. we assign the palette previously copied as a palette to be used in the game;
  8. we clear the Game Boy VRAM and unfreeze the Game Boy LCD display to complete the process.
We add this code to main.asm (FILE DOWNLOAD).


If we now execute the assemble.bat file of the project we will have the ROM with the frame and the customized palettes as a result.



I terminate the explanation of this simple prototype. Naturally, many features of the Super Game Boy remain to be applied. But I hope this article serves as a starting point for people who have been encouraged to develop for the Game Boy but have not found any minimally guided development resources for Super Game Boy.

Any note or critic of any kind will be welcome, and any part of the post is subject to change if necessary.

So I can only give encouragement. Now you have to implement that Super Game Boy frame that you have always wanted to design.

6 comentarios:

  1. The SGB Border doesn't seem to be working on BGB version 1.5.3 (the latest version released around a month ago). Yet the SGB border of games like Pokémon seem to be working alright. BGB creator states that the emulator it is in fact being accurate and that the SGB border created by this method (or by using gbdk) did not work on his real SGB... I unfortunately do not have a real SGB to confirm that. But he sent me pictures as proof of the sgb borders not displaying any custom border to him on real hardware... That puzzles me, version 1.5.2 does display sgb borders but not 1.5.3 and the creator ensures that 1.5.3 is correct and 1.5.2 wrong.

    ResponderEliminar
    Respuestas
    1. Hello Thomas,

      I can confirm that the tutorial program runs correctly in BGB 1.5.2 and incorrectly in 1.5.3. In any case, and as I explain in the entry, I have not found to date a emulator that faithfully reproduces the behavior of the Super Game Boy hardware.

      I can also confirm that the program runs in a real Super Game Boy, with a European Super Nintendo and the program loaded in the Krikzz Everdrive. This SNES has a region mod, so I can also confirm that it works in all regions.

      I have noticed that the reason BGB 1.5.3 does not show the frame may be because the P1 register does not return incrementing joypad IDs after sending the MLT_REQ command. And therefore the program supposes that we are not in a Super Game Boy when trying to detect the hardware.

      I am curious to find out what problem the creator of BGB has had when testing the program. I am open to help in whatever I can to clarify the matter.

      Anyway, thank you for telling me this,

      Imanol

      Eliminar
    2. Someone contacted me about this issue, and told me what the problem with BGB was.

      BGB 1.5.3 needs pad reading to be activated before key reading during the joypad reading simulation of the "check_sgb" routine.

      I updated the entry and the code, now it should work in BGB 1.5.3 too.

      Eliminar
  2. Hi, I'm working on an emulator that implements as much of the Super Gameboy features as I can. This article has been an enourmous help, but I can't figure out how to respond to the MLT_REQ request.

    I'm very new to asm, but picking things up as I go along, but in particular, I don't understand this snippet

    ld hl, MltReqTwoPlayers ; Two player mode selection
    call sgbpackettransfer
    ld a, P1F_4 | P1F_5
    ld [rP1], a ; We disable key and pad reading to read the joypad id

    After the packet transfer, my emulator sets rP1 to some arbitrary value.

    But on line 3 of the snippet above, it looks like you set A to 00110000
    and then on line 4 set rP1 to A, overwriting whatever I had just set.


    How is this really working?

    Thanks

    ResponderEliminar
    Respuestas
    1. Hello J Robs,

      I do not quite understand the doubts regarding the content of the P1 register. But, as you say in the end, I'll explain how is this really working.

      The goal of that code's routine is to check whether the game is running on a Super Game Boy or not. In order to do that, and as the article explains, we must check if multiplayer mode is supported. Because this mode is exclusive to Super Game Boy. That is why we first request the two player mode with a packet transfer.

      rP1 is an I/O port, that means that you write data in it to send commands to the hardware and it also gives you information when you read it. In this case, disabling the key and pad reading implies that the next reading will return the id of the joypad.

      Later, we repeat this operation in order to see if the joypad id changes. If this is the case, the multiplayer mode has been enabled and therefore we are in a Super Game Boy.

      I hope to have addressed your concerns, the documentation linked to the article explains some of these topics more thoroughly.

      Thanks for the interest,

      Imanol

      Eliminar
    2. Thanks I figured out roughly what I needed to do.

      I though I could just set rP1 to a new ID once, immediately after receiving the MLT_REQ.
      Instead I needed to change it each time P1F_4 |P1F_5 are set by the cartridge. I think I only want to change to a different joypad when both bits are set to 1, but could be wrong. The demo ROM here seems to work, as well Donkey Kong Land, but I think Donkey Kong Arcade might have been implemented differently and doesn't work with my solution yet.

      Eliminar