codeslinger.co.uk

Sega Master System - Getting Started.

Getting Started:

Throughout the rest of these tutorials you will notice that the SMS deals in bytes and words. Unfortunately there are no standard keywords in C++ for these basic types, however there are other primitive types that are a byte and a word in size. A "char" is a byte and a "short" is a word. However my personal opinion is that having the type "char" when dealing with registers just seems wrong so you will notice I've typedef'd them as BYTE and WORD. On the rare occasion that I needed a signed byte and a signed word I typedef'd them as SIGNED_BYTE and SIGNED_WORD respectively

typedef unsigned char BYTE;
typedef unsigned short int WORD;
typedef signed short int SIGNED_WORD;
typedef signed char SIGNED_BYTE;

ROM Files:

The largest known SMS game is 1 mega byte (equivelant to 0x100000 bytes). This is easily small enough for us to load the entire rom in at the start of our emulation. In the next section of the tutorials I will explain indepth the internal memory structure of the SMS. However for now I need to briefly touch on the read only memory inside the SMS.

The SMS memory has at the beginning space for 0xC000 bytes of ROM memory. This is obviously too small to fit the larger ROMS into, remember the maximum ROM size is 0x100000 which is considerably bigger than 0xC000. Like most gaming systems the SMS pages in and out memory that it needs when it needs it. Each page is 0x4000 bytes and as there is space for 0xC000 bytes of game memory there is enough room for 3 pages (0xc000/0x4000 = 3). When the SMS is loaded the first 3 pages of the ROM file is loaded into the internal SMS memory. We will deal with the memory paging in the next section of the tutorials. However for now it is worth knowing that the smallest ROM file is 32k which is only 2 pages (0x8000 bytes) so this will fit into the first 0xC000 with room to spare. This means that ROMS that are 32k in size do not need to do memory paging. I recommend you start your SMS emulation journey like I did with 32k ROMs.

When using a Sega Genesis/Mega Drive ROM dumping program some of them put a 512 header onto the beginning of the ROM. This needs to be stripped off otherwise the ROM wont work because all the memory will be in the wrong location. The way to detect if the ROM has the header attached to it is to modulus the size of the ROM with 0x4000 and if the answer is 512 then we need to remove the first 512 bytes from the ROM.

Below is the declarations of the variables used with ROM loading:

BYTE m_InternalMemory[0x10000] ;
BYTE m_GameMemory[0x100000] ;
bool m_IsCodeMasters ;
bool m_OneMegCartridge ;

Putting all of this information together we can load in a ROM file like so:

void Emulator::InsertRom(const char* filename)
{
  memset(m_InternalMemory,0,sizeof(m_InternalMemory)) ;
  memset(m_GameMemory,0,sizeof(m_GameMemory)) ;

  FILE *in = NULL;

  // get the file size
  in = fopen( filename, "rb" );
  fseek( in, 0L, SEEK_END );
  long endPos = ftell( in );
  fclose(in);

  in = fopen( filename, "rb" );

  endPos = endPos % 0x4000 ;

  if (endPos == 512)
  {
   // we need to strip off the header
   char header[1000] ;
   if (endPos == 512)
   {
     fread(header,1,512,in) ;
   }
  }

  size_t size = fread(m_CartridgeMemory, 1, 0x100000, in);
  m_OneMegCartridge = (endPos > 0x80000)?true:false ;

  // copy the first 3 pages from cartridge memory to internal memory
  memcpy(&m_InternalMemory[0x0], &m_CartridgeMemory[0x0], 0xC000) ;

  m_IsCodeMasters = IsCodeMasters() ;
}

You will notice that we store in the variable m_OneMegCartridge if the ROM is 1mb in size. We need to know this for memory paging as you will see in the next section. We also need to know if the ROM is a codemasters game, as discussed below.

CodeMasters ROM Files:

CodeMasters games use their own memory mapper so we need to know if a ROM is a CodeMasters game or not. Now this is again something I wish to talk about in the next section of tutorials but in order to determine if a ROM is a CodeMasters ROM we need to check when we are loading in the ROM. There maybe many ways to detect if a ROM is a CodeMasters ROM or not but the way I've done it is by inspecting the header of the ROM.
A CodeMasters ROM header has a checksum part of it so if we compute the checksum and it all works ok then we have a CodeMasters game. The way the checksum works is by combining memory address 0x7fe7 and 0x7fe6 to form a 16 bit number. If this 16 bit number when taken away from 0x10000 is equal to the 16 bit number formed from memory address 0x7fe9 and 0x7fe8 then we have a codemasters game.

bool Emulator::IsCodeMasters( ) const
{
  WORD checksum = m_InternalMemory[0x7fe7] << 8 ;
  checksum |= m_InternalMemory[0x7fe6] ;

  if (checksum == 0x0)
   return false ;

  WORD compute = 0x10000 - checksum ;

  WORD answer = m_InternalMemory[0x7fe9] << 8 ;
  answer |= m_InternalMemory[0x7fe8] ;

  return compute == answer ; }

Main emulation loop:

Now we have our ROM image loaded into memory we can start the emulation process. I have already shown you the code for the emulation loop in the "The Hardware" section of the SMS tutorials. I shall show it again here but this time I shall add where we check for interupts.

// this is responsible for emulating one frame
void Emulator::Update( )
{
  const double MACHINE_CLICKS_PER_FRAME = 10738580 / 60 ;
  unsigned int clicksThisUpdate = 0 ;

  // emulate 1/60th of a seconds amount of machine clicks
  while (clicksThisUpdate < MACHINE_CLICKS_PER_FRAME)
  {
   int z80ClockCycles = m_Z80.ExecuteNextInst();

   HandleInterrupts( ) ;

   // the machine clock is 3 times faster than the z80 clock
   int machineClicks = z80ClockCycles * 3 ;

   // the vdp clock is half the speed of the machine clock
   float vdpCycles = machineClicks / 2 ;

   // the sound clock is the same speed as the z80
   int soundCycles = z80ClockCycles ;

   m_VDP.Update(vdpCycles) ;
   m_Sound.Update(soundCycles) ;

   clicksThisUpdate += machineClicks ;
  }
}

I shall implement the new function called HandleInterrupts in the "Interrupts" section to the SMS tutorials. It is important to handle interrupts after the z80 instruction execution but before the VDP update otherwise some games wont work. After days of debugging I found this was the problem with the game "XENON 2". The fault lies with the game expecting an interrupt at a specific point and if things happen out of sequence then anything could go wrong.

Now the main emulation loop is in place all thats left to do is implement those functions it calls which will be a lot of work. Before we start implementing those functions we will next look at memory management.