Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
399 lines (373 sloc) 12.7 KB
#ifndef PS4
#include <stdio.h>
#endif
#include "cpu.h"
#include "gpu.h"
#include "interrupts.h"
#include "keys.h"
#include "debug.h"
#include "cgb.h"
#include "snd.h"
#include "memory.h"
const unsigned char ioReset[0x100] = {
0x0F, 0x00, 0x7C, 0xFF, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF,
0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x91, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7E, 0xFF, 0xFE,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xC1, 0x00, 0xFE, 0xFF, 0xFF, 0xFF,
0xF8, 0xFF, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E,
0x45, 0xEC, 0x52, 0xFA, 0x08, 0xB7, 0x07, 0x5D, 0x01, 0xFD, 0xC0, 0xFF, 0x08, 0xFC, 0x00, 0xE5,
0x0B, 0xF8, 0xC2, 0xCE, 0xF4, 0xF9, 0x0F, 0x7F, 0x45, 0x6D, 0x3D, 0xFE, 0x46, 0x97, 0x33, 0x5E,
0x08, 0xEF, 0xF1, 0xFF, 0x86, 0x83, 0x24, 0x74, 0x12, 0xFC, 0x00, 0x9F, 0xB4, 0xB7, 0x06, 0xD5,
0xD0, 0x7A, 0x00, 0x9E, 0x04, 0x5F, 0x41, 0x2F, 0x1D, 0x77, 0x36, 0x75, 0x81, 0xAA, 0x70, 0x3A,
0x98, 0xD1, 0x71, 0x02, 0x4D, 0x01, 0xC1, 0xFF, 0x0D, 0x00, 0xD3, 0x05, 0xF9, 0x00, 0x0B, 0x00
};
// Special read bytes (bit 0):
// 0x00 : keyboard
// 0x02 : Trx bit 7 will also read as 0 (since no serial transfer is currently supported)
// 0x04 : DIV, upper 8 bits of internal cpu counter
// 0x05 : TIMA, needs to be updated before being read
// 0x08 - 0x0e : games are known to read from these and expect 0xFF for some reason
// 0x0f : interrupt flags (top 3 bits are always set when read)
// 0x41 : STAT is alway 1 in the high bit
// Special write bytes (bit 1):
// 0x02 : When enable serial trx, we need to issue an interrupt indicating no gameboy is present
// 0x04 : DIV, any writes reset it
// 0x05 : TIMA, writes to it need to adjust our internal timer also
// 0x07 : TAC, writes to it MAY need to adjust our internal timer also
// 0x14,0x19,0x1E,0x23 : Sound channel init enable on bit 7
// 0x26 : NR52, master sound control, avoid writes to sound channel active bits (read only)
// 0x40 : Toggling the window off and on mid-frame effects the actual window draw position
// 0x41 : writes to STAT causes interrupt flags in certain situations
// 0x44 : gpu scanline (read only)
// 0x46 : sprite DMA register (TODO, check clock cycles on this)
// 0x47-49 : color palette writes require DMG palette resolves
// 0x4B : Window enable/disable mid frame effects window render position
// 0x4D : CGB speed switch (only can change bit 0)
// 0x4F : CGB VRAM select
// 0x51-55 : CGB DMA Ops
// 0x69 : CGB BG palette data write
// 0x6B : CGB OBJ palette data write
// 0x70 : CGB WRAM select
// 0x76 : CGB mode unknown register (read only)
// 0x77 : CGB mode unknown register (read only)
unsigned char specialMap[256] ALIGN(256) =
{
0x01, 0x00, 0x03, 0x00, 0x03, 0x03, 0x00, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x03, 0x00, 0x00, 0x03, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// forced alignment allows us to simply bitwise or in the memory map
unsigned char cart[0x4000] ALIGN(256) = { 0 };
unsigned char* vram = NULL;
unsigned char sram[0x2000] ALIGN(256) = { 0 };
unsigned char wram_perm[0x1000] ALIGN(256) = { 0 };
unsigned char wram_gb[0x1000] ALIGN(256) = { 0 };
unsigned char oam[0x100] ALIGN(256) = { 0 };
unsigned char disabledArea[0x100] ALIGN(256);
unsigned char* memoryMap[256] ALIGN(256) = { 0 };
void resetMemoryMaps(bool isCGB) {
// disabled RAM/ROM area should return all '1's
memset(disabledArea, 0xFF, sizeof(disabledArea));
// permanent rom area
for (int i = 0x00; i <= 0x3f; i++) {
memoryMap[i] = &cart[i << 8];
}
// extra rom area starts out disabled
for (int i = 0x40; i <= 0x7f; i++) {
memoryMap[i] = &disabledArea[0];
}
// video RAM needs to be allocated
if (vram != NULL) {
free((void*)vram);
}
if (isCGB) {
vram = (unsigned char*) malloc(0x4000);
memset(vram, 0, 0x4000);
} else {
vram = (unsigned char*) malloc(0x2000);
memset(vram, 0, 0x2000);
}
// first 8k of VRAM gets mapped by default
for (int i = 0x80; i <= 0x9f; i++) {
memoryMap[i] = &vram[(i - 0x80) << 8];
}
// Sram starts out disabled
for (int i = 0xa0; i <= 0xbf; i++) {
memoryMap[i] = &disabledArea[0];
}
// permanent work ram and its echo
for (int i = 0xc0; i <= 0xcf; i++) {
memoryMap[i] = &wram_perm[(i - 0xc0) << 8];
memoryMap[i + 0x20] = &wram_perm[(i - 0xc0) << 8];
}
// DMG work RAM / page 1 of CGB
for (int i = 0xd0; i <= 0xdf; i++) {
memoryMap[i] = &wram_gb[(i - 0xd0) << 8];
}
// echo area of above ram
for (int i = 0xf0; i <= 0xfd; i++) {
memoryMap[i] = &wram_gb[(i - 0xe0) << 8];
}
// on-chip/PPU memory
memoryMap[0xfe] = oam;
memoryMap[0xff] = cpu.memory.all; // on chip memory
}
void oamDMA(unsigned int sourceUpper) {
if (specialMap[sourceUpper] & 0x10)
mbcRead(sourceUpper << 8);
memcpy(oam, memoryMap[sourceUpper], 160);
}
unsigned char readByteSpecial(unsigned int address) {
if (address < 0xFF00) {
return mbcRead(address);
}
unsigned char byte = address & 0x00FF;
switch (byte) {
case 0x00:
{
// keyboard read
if (!(cpu.memory.P1_joypad & 0x20)) {
return (unsigned char)(0xc0 | keys.keys1 | 0x10);
}
else if (!(cpu.memory.P1_joypad & 0x10)) {
return (unsigned char)(0xc0 | keys.keys2 | 0x20);
}
else return 0xff;
}
case 0x02: {
// also return transfer bit as 0
return cpu.memory.SC_serial_ctl & 0x7F;
}
case 0x04: {
updateDiv();
return (cpu.div & 0xFF00) >> 8;
}
case 0x05: {
updateTimer();
return cpu.memory.TIMA_timerctr;
}
// these are unmapped and MUST return 0xFF
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
case 0x0c:
case 0x0d:
case 0x0e:
return 0xff;
case 0x0f: return cpu.memory.IF_intflag | 0xE0; // top 3 bits are always set when reading interrupt flags
case 0x41: return cpu.memory.STAT_lcdstatus | 0x80; // high bit always set in STAT
default:
return cpu.memory.all[byte];
}
}
// this only gets called on 0xFF** addresses
void writeByteSpecial(unsigned int address, unsigned char value) {
switch (address) {
case 0x02:
cpu.memory.SC_serial_ctl = value;
if ((value & 0x81) == 0x81) {
// "receive" 0xFF and trigger interrupt if enabled
cpu.memory.SB_serial_data = 0xFF;
cpu.memory.SC_serial_ctl &= 0x7F;
cpu.memory.IF_intflag |= INTERRUPTS_SERIAL;
}
break;
case 0x04:
// always resets DIV when written to
cpu.div = 0;
cpu.divBase = cpu.clocks;
break;
case 0x05:
writeTIMA(value);
break;
case 0x07:
writeTAC(value);
break;
case 0x14:
if (value & 0x80) {
sndChannelInit(1);
value &= 0x7F;
}
cpu.memory.NR14_snd1ctl = value;
break;
case 0x19:
if (value & 0x80) {
sndChannelInit(2);
value &= 0x7F;
}
cpu.memory.NR24_snd2ctl = value;
break;
case 0x1E:
if (value & 0x80) {
sndChannelInit(3);
value &= 0x7F;
}
cpu.memory.NR34_snd3ctl = value;
break;
case 0x23:
if (value & 0x80) {
sndChannelInit(4);
value &= 0x7F;
}
cpu.memory.NR44_snd4ctl = value;
break;
case 0x26:
// master sound enable : can only write to bit 7:
value = (value & 0x80) | (cpu.memory.NR52_soundmast & 0x7f);
cpu.memory.NR52_soundmast = value;
break;
case 0x40:
// check for window bit change mid frame (ppu 'remembers' the position)
if ((cpu.memory.LCDC_ctl ^ value) & LCDC_WINDOWENABLE) {
if (value & LCDC_WINDOWENABLE) {
// re-enabling?
if (windowLineOffset) {
windowLineOffset -= cpu.memory.LY_lcdline;
}
} else {
// disabling mid frame?
if (cpu.memory.LY_lcdline < 0xA0) {
windowLineOffset = cpu.memory.LY_lcdline;
}
}
}
cpu.memory.LCDC_ctl = value;
break;
case 0x41:
cpu.memory.STAT_lcdstatus = (value & 0x78) | (cpu.memory.STAT_lcdstatus & 0x7);
// This may be a DMG only thing?
if ((GET_LCDC_MODE() == GPU_MODE_HBLANK || GET_LCDC_MODE() == GPU_MODE_VBLANK) && (cpu.memory.LCDC_ctl & 0x80)) {
cpu.memory.IF_intflag |= INTERRUPTS_LCDSTAT;
}
break;
case 0x44: // read only
break;
case 0x46:
oamDMA(value); // OAM DMA
break;
case 0x47:
cpu.memory.BGP_bgpalette = value;
if (!cgb.isCGB) {
resolveDMGBGPalette();
}
break;
case 0x48:
cpu.memory.OBP0_spritepal0 = value;
if (!cgb.isCGB) {
resolveDMGOBJ0Palette();
}
break;
case 0x49:
cpu.memory.OBP1_spritepal1 = value;
if (!cgb.isCGB) {
resolveDMGOBJ1Palette();
}
break;
case 0x4B:
if (cpu.memory.LCDC_ctl & LCDC_WINDOWENABLE) {
if (value < 0xA7 && cpu.memory.WX_windowx >= 0xA7) {
// re-enabling?
if (windowLineOffset) {
windowLineOffset -= cpu.memory.LY_lcdline;
}
} else if (value >= 0xA7 && cpu.memory.WX_windowx < 0xA7) {
// disabling mid frame?
if (cpu.memory.LY_lcdline < 0xA0) {
windowLineOffset = cpu.memory.LY_lcdline;
}
}
}
cpu.memory.WX_windowx = value;
break;
case 0x4D:
if (cgb.isCGB) {
// speed switch prepare bit
cpu.memory.KEY1_cgbspeed = (cpu.memory.KEY1_cgbspeed & 0xFE) | (value & 1);
}
break;
case 0x4F: // CGB VRAM select
if (cgb.isCGB) {
int vram = value & 1;
cpu.memory.VBK_cgbvram = 0xFE | vram;
cgbSelectVRAM(vram);
}
break;
case 0x51: // CGB DMA Src high byte, write only
if (cgb.isCGB) {
cgb.dmaSrc = (cgb.dmaSrc & 0x00FF) | (value << 8);
}
break;
case 0x52: // CGB DMA Src low byte, write only, bottom 4 bits ignored
if (cgb.isCGB) {
cgb.dmaSrc = (cgb.dmaSrc & 0xFF00) | (value & 0xF0);
}
break;
case 0x53: // CGB DMA Dest high byte, write only, top 3 bit ignored
if (cgb.isCGB) {
cgb.dmaDest = (cgb.dmaDest & 0xE0FF) | ((value & 0x1F) << 8);
}
break;
case 0x54: // CGB DMA Dest low byte, write only, bottom 4 bits ignored
if (cgb.isCGB) {
cgb.dmaDest = (cgb.dmaDest & 0xFF00) | (value & 0xF0);
}
break;
case 0x55:
if (cgb.isCGB) {
cgbDMAOp(value);
}
break;
case 0x69: // CGB BG palette write
if (cgb.isCGB) {
int index = cpu.memory.BGPI_bgpalindex & 0x3F;
cgb.paletteMemory[index] = value;
if (cpu.memory.BGPI_bgpalindex & 0x80) {
index = (index + 1) & 0x3F;
cpu.memory.BGPI_bgpalindex = 0x80 | index;
}
cgb.dirtyPalette = true;
}
break;
case 0x6B: // CGB OBJ palette write
if (cgb.isCGB) {
int index = cpu.memory.OBPI_objpalindex & 0x3F;
cgb.paletteMemory[64+index] = value;
if (cpu.memory.OBPI_objpalindex & 0x80) {
index = (index + 1) & 0x3F;
cpu.memory.OBPI_objpalindex = 0x80 | index;
}
cgb.dirtyPalette = true;
}
break;
case 0x70: // CGB WRAM select
if (cgb.isCGB) {
int ram = value & 0x07;
cpu.memory.SVBK_cgbram = 0xF8 | ram;
cgbSelectWRAM(ram);
}
break;
case 0x76: // read only
case 0x77:
break;
}
}