GBHawk: GBC commits round 1

-RAM banks
-Unify VRAM and add second Bank
-Speed Switch and associated reorg.
-Memory Map and Registers
-PPU seperation
This commit is contained in:
alyosha-tas 2018-03-25 10:07:12 -04:00
parent ac66b258ba
commit ca69e52229
11 changed files with 1017 additions and 362 deletions

View File

@ -55,6 +55,8 @@ namespace BizHawk.Emulation.Common.Components.LR35902
private static ushort[] INT_vectors = new ushort[] {0x40, 0x48, 0x50, 0x58, 0x60, 0x00};
public ushort int_src;
public int stop_time;
public bool stop_check;
private void ResetInterrupts()
{

View File

@ -68,6 +68,7 @@ namespace BizHawk.Emulation.Common.Components.LR35902
ResetRegisters();
ResetInterrupts();
TotalExecutedCycles = 0;
stop_check = false;
cur_instr = new ushort[] { OP };
}
@ -78,6 +79,9 @@ namespace BizHawk.Emulation.Common.Components.LR35902
public Func<ushort, byte> PeekMemory;
public Func<ushort, byte> DummyReadMemory;
// Special Function for Speed switching executed on a STOP
public Func<int, int> SpeedFunc;
//this only calls when the first byte of an instruction is fetched.
public Action<ushort> OnExecFetch;
@ -306,7 +310,45 @@ namespace BizHawk.Emulation.Common.Components.LR35902
case STOP:
stopped = true;
if (interrupt_src.Bit(4)) // button pressed, not actually an interrupt though
if (!stop_check)
{
stop_time = SpeedFunc(0);
stop_check = true;
}
if (stop_time > 0)
{
stop_time--;
if (stop_time == 0)
{
if (TraceCallback != null)
{
TraceCallback(new TraceInfo
{
Disassembly = "====un-stop====",
RegisterInfo = ""
});
}
stopped = false;
if (OnExecFetch != null) OnExecFetch(RegPC);
if (TraceCallback != null && !CB_prefix) TraceCallback(State());
FetchInstruction(ReadMemory(RegPC++));
instr_pntr = 0;
stop_check = false;
}
else
{
instr_pntr = 0;
cur_instr = new ushort[]
{IDLE,
IDLE,
IDLE,
STOP };
}
}
else if (interrupt_src.Bit(4)) // button pressed, not actually an interrupt though
{
if (TraceCallback != null)
{
@ -322,6 +364,8 @@ namespace BizHawk.Emulation.Common.Components.LR35902
if (TraceCallback != null && !CB_prefix) TraceCallback(State());
FetchInstruction(ReadMemory(RegPC++));
instr_pntr = 0;
stop_check = false;
}
else
{
@ -434,6 +478,8 @@ namespace BizHawk.Emulation.Common.Components.LR35902
ser.Sync("ExecutedCycles", ref totalExecutedCycles);
ser.Sync("EI_pending", ref EI_pending);
ser.Sync("int_src", ref int_src);
ser.Sync("stop_time", ref stop_time);
ser.Sync("stop_check", ref stop_check);
ser.Sync("instruction_pointer", ref instr_pntr);
ser.Sync("current instruction", ref cur_instr, false);

View File

@ -7,6 +7,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
public class GBC_PPU : PPU
{
// these are the uniquely GBC variables
public byte BG_pal_index;
public byte pal_transfer_byte;
public byte spr_pal_index;
public byte spr_transfer_byte;
public byte HDMA_src_hi;
public byte HDMA_src_lo;
public byte HDMA_dest_hi;
public byte HDMA_dest_lo;
public byte HDMA_ctrl;
// controls for tile attributes
public byte tile_attr_byte;
public int VRAM_sel;
public override byte ReadReg(int addr)
{
byte ret = 0;
@ -25,6 +40,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
case 0xFF49: ret = obj_pal_1; break; // OBP1
case 0xFF4A: ret = window_y; break; // WY
case 0xFF4B: ret = window_x; break; // WX
// These are GBC specific Regs
case 0xFF51: ret = HDMA_src_hi; break; // HDMA1
case 0xFF52: ret = HDMA_src_lo; break; // HDMA2
case 0xFF53: ret = HDMA_dest_hi; break; // HDMA3
case 0xFF54: ret = HDMA_dest_lo; break; // HDMA4
case 0xFF55: ret = HDMA_ctrl; break; // HDMA5
case 0xFF68: ret = BG_pal_index; break; // BGPI
case 0xFF69: ret = pal_transfer_byte; break; // BGPD
case 0xFF6A: ret = spr_pal_index; break; // OBPI
case 0xFF6B: ret = spr_transfer_byte; break; // OBPD
}
return ret;
@ -99,6 +125,35 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
case 0xFF4B: // WX
window_x = value;
break;
// These are GBC specific Regs
case 0xFF51: // HDMA1
HDMA_src_hi = value;
break;
case 0xFF52: // HDMA2
HDMA_src_lo = value;
break;
case 0xFF53: // HDMA3
HDMA_dest_hi = value;
break;
case 0xFF54: // HDMA4
HDMA_dest_lo = value;
break;
case 0xFF55: // HDMA5
HDMA_ctrl = value;
break;
case 0xFF68: // BGPI
BG_pal_index = value;
break;
case 0xFF69: // BGPD
pal_transfer_byte = value;
break;
case 0xFF6A: // OBPI
spr_pal_index = value;
break;
case 0xFF6B: // OBPD
spr_transfer_byte = value;
break;
}
}
@ -582,7 +637,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
y_tile = ((int)Math.Floor((float)(scroll_y + LY) / 8)) % 32;
temp_fetch = y_tile * 32 + (x_tile + tile_inc) % 32;
tile_byte = LCDC.Bit(3) ? Core.BG_map_2[temp_fetch] : Core.BG_map_1[temp_fetch];
tile_byte = Core.VRAM[0x1800 + (LCDC.Bit(3) ? 1 : 0) * 0x400 + temp_fetch];
tile_attr_byte = Core.VRAM[0x3800 + (LCDC.Bit(3) ? 1 : 0) * 0x400 + temp_fetch];
VRAM_sel = tile_attr_byte.Bit(3) ? 1 : 0;
}
else
{
@ -605,7 +662,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if (LCDC.Bit(4))
{
tile_data[0] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[(VRAM_sel * 0x2000) + tile_byte * 16 + y_scroll_offset * 2];
}
else
{
@ -614,7 +671,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
tile_byte -= 256;
}
tile_data[0] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[(VRAM_sel * 0x2000) + 0x1000 + tile_byte * 16 + y_scroll_offset * 2];
}
}
@ -637,7 +694,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte += 256;
}
tile_data[1] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[(VRAM_sel * 0x2000) + tile_byte * 16 + y_scroll_offset * 2 + 1];
}
else
{
@ -647,7 +704,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte -= 256;
}
tile_data[1] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[(VRAM_sel * 0x2000) + 0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
}
}
@ -685,7 +742,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if ((window_counter % 2) == 0)
{
temp_fetch = window_y_tile * 32 + (window_x_tile + window_tile_inc) % 32;
tile_byte = LCDC.Bit(6) ? Core.BG_map_2[temp_fetch] : Core.BG_map_1[temp_fetch];
tile_byte = Core.VRAM[0x1800 + (LCDC.Bit(6) ? 1 : 0) * 0x400 + temp_fetch];
tile_attr_byte = Core.VRAM[0x3800 + (LCDC.Bit(6) ? 1 : 0) * 0x400 + temp_fetch];
VRAM_sel = tile_attr_byte.Bit(3) ? 1 : 0;
}
else
{
@ -705,8 +764,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if (LCDC.Bit(4))
{
tile_data[0] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[(VRAM_sel * 0x2000) + tile_byte * 16 + y_scroll_offset * 2];
}
else
@ -716,8 +775,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
tile_byte -= 256;
}
tile_data[0] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[(VRAM_sel * 0x2000) + 0x1000 + tile_byte * 16 + y_scroll_offset * 2];
}
}
else
@ -739,7 +798,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte += 256;
}
tile_data[1] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[(VRAM_sel * 0x2000) + tile_byte * 16 + y_scroll_offset * 2 + 1];
}
else
{
@ -749,7 +808,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte -= 256;
}
tile_data[1] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[(VRAM_sel * 0x2000) + 0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
}
}
@ -878,5 +937,255 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
}
}
}
public override void process_sprite()
{
int y;
if (SL_sprites[sl_use_index * 4 + 3].Bit(6))
{
if (LCDC.Bit(2))
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
y = 15 - y;
sprite_sel[0] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
sprite_sel[1] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
}
else
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
y = 7 - y;
sprite_sel[0] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
sprite_sel[1] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
}
}
else
{
if (LCDC.Bit(2))
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
sprite_sel[0] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
sprite_sel[1] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
}
else
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
sprite_sel[0] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
sprite_sel[1] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
}
}
if (SL_sprites[sl_use_index * 4 + 3].Bit(5))
{
int b0, b1, b2, b3, b4, b5, b6, b7 = 0;
for (int i = 0; i < 2; i++)
{
b0 = (sprite_sel[i] & 0x01) << 7;
b1 = (sprite_sel[i] & 0x02) << 5;
b2 = (sprite_sel[i] & 0x04) << 3;
b3 = (sprite_sel[i] & 0x08) << 1;
b4 = (sprite_sel[i] & 0x10) >> 1;
b5 = (sprite_sel[i] & 0x20) >> 3;
b6 = (sprite_sel[i] & 0x40) >> 5;
b7 = (sprite_sel[i] & 0x80) >> 7;
sprite_sel[i] = (byte)(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
}
}
}
// normal DMA moves twice as fast in double speed mode on GBC
// So give it it's own function so we can seperate it from PPU tick
public override void DMA_tick()
{
// Note that DMA is halted when the CPU is halted
if (DMA_start && !Core.cpu.halted)
{
if (DMA_clock >= 4)
{
DMA_OAM_access = false;
if ((DMA_clock % 4) == 1)
{
// the cpu can't access memory during this time, but we still need the ppu to be able to.
DMA_start = false;
// Gekkio reports that A14 being high on DMA transfers always represent WRAM accesses
// So transfers nominally from higher memory areas are actually still from there (i.e. FF -> DF)
byte DMA_actual = DMA_addr;
if (DMA_addr > 0xDF) { DMA_actual &= 0xDF; }
DMA_byte = Core.ReadMemory((ushort)((DMA_actual << 8) + DMA_inc));
DMA_start = true;
}
else if ((DMA_clock % 4) == 3)
{
Core.OAM[DMA_inc] = DMA_byte;
if (DMA_inc < (0xA0 - 1)) { DMA_inc++; }
}
}
DMA_clock++;
if (DMA_clock == 648)
{
DMA_start = false;
DMA_OAM_access = true;
}
}
}
// order sprites according to x coordinate
// note that for sprites of equal x coordinate, priority goes to first on the list
public override void reorder_and_assemble_sprites()
{
sprite_ordered_index = 0;
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < SL_sprites_index; j++)
{
if (SL_sprites[j * 4 + 1] == i)
{
sl_use_index = j;
process_sprite();
SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[j * 4 + 1];
SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0];
SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1];
SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[j * 4 + 3];
sprite_ordered_index++;
}
}
}
bool have_pixel = false;
byte s_pixel = 0;
byte sprite_attr = 0;
for (int i = 0; i < 160; i++)
{
have_pixel = false;
for (int j = 0; j < SL_sprites_index; j++)
{
if ((i >= (SL_sprites_ordered[j * 4] - 8)) &&
(i < SL_sprites_ordered[j * 4]) &&
!have_pixel)
{
// we can use the current sprite, so pick out a pixel for it
int t_index = i - (SL_sprites_ordered[j * 4] - 8);
t_index = 7 - t_index;
sprite_data[0] = (byte)((SL_sprites_ordered[j * 4 + 1] >> t_index) & 1);
sprite_data[1] = (byte)(((SL_sprites_ordered[j * 4 + 2] >> t_index) & 1) << 1);
s_pixel = (byte)(sprite_data[0] + sprite_data[1]);
sprite_attr = (byte)SL_sprites_ordered[j * 4 + 3];
// pixel color of 0 is transparent, so if this is the case we dont have a pixel
if (s_pixel != 0)
{
have_pixel = true;
}
}
}
if (have_pixel)
{
sprite_present_list[i] = 1;
sprite_pixel_list[i] = s_pixel;
sprite_attr_list[i] = sprite_attr;
}
else
{
sprite_present_list[i] = 0;
}
}
}
public override void OAM_scan(int OAM_cycle)
{
// we are now in STAT mode 2
// TODO: maybe stat mode 2 flags are set at cycle 0 on visible scanlines?
if (OAM_cycle == 0)
{
OAM_access_read = false;
OAM_scan_index = 0;
SL_sprites_index = 0;
write_sprite = 0;
}
// the gameboy has 80 cycles to scan through 40 sprites, picking out the first 10 it finds to draw
// the following is a guessed at implmenentation based on how NES does it, it's probably pretty close
if (OAM_cycle < 10)
{
// start by clearing the sprite table (probably just clears X on hardware, but let's be safe here.)
SL_sprites[OAM_cycle * 4] = 0;
SL_sprites[OAM_cycle * 4 + 1] = 0;
SL_sprites[OAM_cycle * 4 + 2] = 0;
SL_sprites[OAM_cycle * 4 + 3] = 0;
}
else
{
if (write_sprite == 0)
{
if (OAM_scan_index < 40)
{
ushort temp = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4] : (ushort)0xFF;
// (sprite Y - 16) equals LY, we have a sprite
if ((temp - 16) <= LY &&
((temp - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY)
{
// always pick the first 10 in range sprites
if (SL_sprites_index < 10)
{
SL_sprites[SL_sprites_index * 4] = temp;
write_sprite = 1;
}
else
{
// if we already have 10 sprites, there's nothing to do, increment the index
OAM_scan_index++;
}
}
else
{
OAM_scan_index++;
}
}
}
else
{
ushort temp2 = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4 + write_sprite] : (ushort)0xFF;
SL_sprites[SL_sprites_index * 4 + write_sprite] = temp2;
write_sprite++;
if (write_sprite == 4)
{
write_sprite = 0;
SL_sprites_index++;
OAM_scan_index++;
}
}
}
}
public override void SyncState(Serializer ser)
{
ser.Sync("BG_pal_index", ref BG_pal_index);
ser.Sync("pal_transfer_byte", ref pal_transfer_byte);
ser.Sync("spr_pal_index", ref spr_pal_index);
ser.Sync("spr_transfer_byte", ref spr_transfer_byte);
ser.Sync("HDMA_src_hi", ref HDMA_src_hi);
ser.Sync("HDMA_src_lo", ref HDMA_src_lo);
ser.Sync("HDMA_dest_hi", ref HDMA_dest_hi);
ser.Sync("HDMA_dest_lo", ref HDMA_dest_lo);
ser.Sync("HDMA_ctrl", ref HDMA_ctrl);
ser.Sync("tile_attr_byte", ref tile_attr_byte);
ser.Sync("VRAM_sel", ref VRAM_sel);
base.SyncState(ser);
}
}
}

View File

@ -124,13 +124,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
cpu.ExecuteOne(ref REG_FF0F, REG_FFFF);
timer.tick_2();
if (double_speed)
{
ppu.DMA_tick();
timer.tick_1();
serialport.serial_transfer_tick();
cpu.ExecuteOne(ref REG_FF0F, REG_FFFF);
timer.tick_2();
}
if (in_vblank && !in_vblank_old)
{
vblank_rise = true;
}
ticker++;
if (ticker > 10000000) { throw new Exception("ERROR: Unable to Resolve Frame"); }
if (ticker > 10000000) { vblank_rise = true; }//throw new Exception("ERROR: Unable to Resolve Frame"); }
in_vblank_old = in_vblank;
}
@ -138,6 +147,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
vblank_rise = false;
}
// Switch Speed (GBC only)
public int SpeedFunc(int temp)
{
if (is_GBC)
{
if (speed_switch)
{
speed_switch = false;
int ret = double_speed ? 50000 : 25000; // actual time needs checking
double_speed = !double_speed;
return ret;
}
// if we are not switching speed, return 0
return 0;
}
// if we are in GB mode, return 0 indicating not switching speed
return 0;
}
public void GetControllerState(IController controller)
{
InputCallbacks.Call();

View File

@ -79,11 +79,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
// memory domains
ser.Sync("RAM", ref RAM, false);
ser.Sync("ZP_RAM", ref ZP_RAM, false);
ser.Sync("CHR_RAM", ref CHR_RAM, false);
ser.Sync("BG_map_1", ref BG_map_1, false);
ser.Sync("BG_map_2", ref BG_map_2, false);
ser.Sync("VRAM", ref VRAM, false);
ser.Sync("OAM", ref OAM, false);
ser.Sync("RAM_Bank", ref RAM_Bank);
ser.Sync("VRAM_Bank", ref VRAM_Bank);
ser.Sync("is_GBC", ref is_GBC);
ser.Sync("double_speed", ref double_speed);
ser.Sync("speed_switch", ref speed_switch);
ser.Sync("Use_RTC", ref Use_RTC);
// probably a better way to do this

View File

@ -36,13 +36,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
// memory domains
public byte[] RAM = new byte[0x2000];
public byte[] RAM = new byte[0x8000]; // only 0x2000 available to GB
public byte[] ZP_RAM = new byte[0x80];
public byte[] CHR_RAM = new byte[0x1800];
public byte[] BG_map_1 = new byte[0x400];
public byte[] BG_map_2 = new byte[0x400];
/*
* VRAM is arranged as:
* 0x1800 Tiles
* 0x400 BG Map 1
* 0x400 BG Map 2
* 0x1800 Tiles
* 0x400 CA Map 1
* 0x400 CA Map 2
* Only the top set is available in GB (i.e. VRAM_Bank = 0)
*/
public byte[] VRAM = new byte[0x4000];
public byte[] OAM = new byte[0xA0];
public int RAM_Bank;
public byte VRAM_Bank;
public bool is_GBC;
public bool double_speed;
public bool speed_switch;
public readonly byte[] _rom;
public readonly byte[] _bios;
public readonly byte[] header = new byte[0x50];
@ -75,7 +89,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
WriteMemory = WriteMemory,
PeekMemory = PeekMemory,
DummyReadMemory = ReadMemory,
OnExecFetch = ExecFetch
OnExecFetch = ExecFetch,
SpeedFunc = SpeedFunc,
};
timer = new Timer();
@ -102,6 +117,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load");
ppu = new GBC_PPU();
is_GBC = true;
}
}
@ -114,6 +130,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load");
ppu = new GBC_PPU();
is_GBC = true;
}
if (Bios == null)
@ -152,7 +169,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
SetupMemoryDomains();
HardReset();
iptr0 = Marshal.AllocHGlobal(CHR_RAM.Length + BG_map_1.Length + BG_map_2.Length + 1);
iptr0 = Marshal.AllocHGlobal(VRAM.Length + 1);
iptr1 = Marshal.AllocHGlobal(OAM.Length + 1);
iptr2 = Marshal.AllocHGlobal(color_palette.Length * 2 * 8 + 1);
iptr3 = Marshal.AllocHGlobal(color_palette.Length * 8 + 1);
@ -173,22 +190,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
get
{
byte[] temp = new byte[CHR_RAM.Length + BG_map_1.Length + BG_map_2.Length];
for (int i = 0; i < CHR_RAM.Length; i++)
{
temp[i] = CHR_RAM[i];
}
for (int i = 0; i < BG_map_1.Length; i++)
{
temp[CHR_RAM.Length + i] = BG_map_1[i];
}
for (int i = 0; i < BG_map_2.Length; i++)
{
temp[CHR_RAM.Length + BG_map_1.Length + i] = BG_map_2[i];
}
Marshal.Copy(temp, 0, iptr0, temp.Length);
Marshal.Copy(VRAM, 0, iptr0, VRAM.Length);
Marshal.Copy(OAM, 0, iptr1, OAM.Length);
int[] cp2 = new int[8];
@ -247,6 +249,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
in_vblank = true; // we start off in vblank since the LCD is off
in_vblank_old = true;
RAM_Bank = 1; // RAM bank always starts as 1 (even writing zero still sets 1)
Register_Reset();
timer.Reset();
ppu.Reset();

View File

@ -582,7 +582,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
y_tile = ((int)Math.Floor((float)(scroll_y + LY) / 8)) % 32;
temp_fetch = y_tile * 32 + (x_tile + tile_inc) % 32;
tile_byte = LCDC.Bit(3) ? Core.BG_map_2[temp_fetch] : Core.BG_map_1[temp_fetch];
tile_byte = Core.VRAM[0x1800 + (LCDC.Bit(3) ? 1 : 0) * 0x400 + temp_fetch];
}
else
{
@ -605,7 +605,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if (LCDC.Bit(4))
{
tile_data[0] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2];
}
else
{
@ -614,7 +614,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
tile_byte -= 256;
}
tile_data[0] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
}
}
@ -637,7 +637,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte += 256;
}
tile_data[1] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
}
else
{
@ -647,7 +647,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte -= 256;
}
tile_data[1] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
}
}
@ -685,7 +685,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if ((window_counter % 2) == 0)
{
temp_fetch = window_y_tile * 32 + (window_x_tile + window_tile_inc) % 32;
tile_byte = LCDC.Bit(6) ? Core.BG_map_2[temp_fetch] : Core.BG_map_1[temp_fetch];
tile_byte = Core.VRAM[0x1800 + (LCDC.Bit(6) ? 1 : 0) * 0x400 + temp_fetch]; ;
}
else
{
@ -706,7 +706,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if (LCDC.Bit(4))
{
tile_data[0] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2];
}
else
@ -717,7 +717,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte -= 256;
}
tile_data[0] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
tile_data[0] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
}
}
else
@ -739,7 +739,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte += 256;
}
tile_data[1] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
}
else
{
@ -749,7 +749,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
tile_byte -= 256;
}
tile_data[1] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
tile_data[1] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
}
}
@ -878,5 +878,278 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
}
}
}
public override void process_sprite()
{
int y;
if (SL_sprites[sl_use_index * 4 + 3].Bit(6))
{
if (LCDC.Bit(2))
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
y = 15 - y;
sprite_sel[0] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
sprite_sel[1] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
}
else
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
y = 7 - y;
sprite_sel[0] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
sprite_sel[1] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
}
}
else
{
if (LCDC.Bit(2))
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
sprite_sel[0] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
sprite_sel[1] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
}
else
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
sprite_sel[0] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
sprite_sel[1] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
}
}
if (SL_sprites[sl_use_index * 4 + 3].Bit(5))
{
int b0, b1, b2, b3, b4, b5, b6, b7 = 0;
for (int i = 0; i < 2; i++)
{
b0 = (sprite_sel[i] & 0x01) << 7;
b1 = (sprite_sel[i] & 0x02) << 5;
b2 = (sprite_sel[i] & 0x04) << 3;
b3 = (sprite_sel[i] & 0x08) << 1;
b4 = (sprite_sel[i] & 0x10) >> 1;
b5 = (sprite_sel[i] & 0x20) >> 3;
b6 = (sprite_sel[i] & 0x40) >> 5;
b7 = (sprite_sel[i] & 0x80) >> 7;
sprite_sel[i] = (byte)(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
}
}
}
// normal DMA moves twice as fast in double speed mode on GBC
// So give it it's own function so we can seperate it from PPU tick
public override void DMA_tick()
{
// Note that DMA is halted when the CPU is halted
if (DMA_start && !Core.cpu.halted)
{
if (DMA_clock >= 4)
{
DMA_OAM_access = false;
if ((DMA_clock % 4) == 1)
{
// the cpu can't access memory during this time, but we still need the ppu to be able to.
DMA_start = false;
// Gekkio reports that A14 being high on DMA transfers always represent WRAM accesses
// So transfers nominally from higher memory areas are actually still from there (i.e. FF -> DF)
byte DMA_actual = DMA_addr;
if (DMA_addr > 0xDF) { DMA_actual &= 0xDF; }
DMA_byte = Core.ReadMemory((ushort)((DMA_actual << 8) + DMA_inc));
DMA_start = true;
}
else if ((DMA_clock % 4) == 3)
{
Core.OAM[DMA_inc] = DMA_byte;
if (DMA_inc < (0xA0 - 1)) { DMA_inc++; }
}
}
DMA_clock++;
if (DMA_clock == 648)
{
DMA_start = false;
DMA_OAM_access = true;
}
}
}
// order sprites according to x coordinate
// note that for sprites of equal x coordinate, priority goes to first on the list
public override void reorder_and_assemble_sprites()
{
sprite_ordered_index = 0;
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < SL_sprites_index; j++)
{
if (SL_sprites[j * 4 + 1] == i)
{
sl_use_index = j;
process_sprite();
SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[j * 4 + 1];
SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0];
SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1];
SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[j * 4 + 3];
sprite_ordered_index++;
}
}
}
bool have_pixel = false;
byte s_pixel = 0;
byte sprite_attr = 0;
for (int i = 0; i < 160; i++)
{
have_pixel = false;
for (int j = 0; j < SL_sprites_index; j++)
{
if ((i >= (SL_sprites_ordered[j * 4] - 8)) &&
(i < SL_sprites_ordered[j * 4]) &&
!have_pixel)
{
// we can use the current sprite, so pick out a pixel for it
int t_index = i - (SL_sprites_ordered[j * 4] - 8);
t_index = 7 - t_index;
sprite_data[0] = (byte)((SL_sprites_ordered[j * 4 + 1] >> t_index) & 1);
sprite_data[1] = (byte)(((SL_sprites_ordered[j * 4 + 2] >> t_index) & 1) << 1);
s_pixel = (byte)(sprite_data[0] + sprite_data[1]);
sprite_attr = (byte)SL_sprites_ordered[j * 4 + 3];
// pixel color of 0 is transparent, so if this is the case we dont have a pixel
if (s_pixel != 0)
{
have_pixel = true;
}
}
}
if (have_pixel)
{
sprite_present_list[i] = 1;
sprite_pixel_list[i] = s_pixel;
sprite_attr_list[i] = sprite_attr;
}
else
{
sprite_present_list[i] = 0;
}
}
}
public override void OAM_scan(int OAM_cycle)
{
// we are now in STAT mode 2
// TODO: maybe stat mode 2 flags are set at cycle 0 on visible scanlines?
if (OAM_cycle == 0)
{
OAM_access_read = false;
OAM_scan_index = 0;
SL_sprites_index = 0;
write_sprite = 0;
}
// the gameboy has 80 cycles to scan through 40 sprites, picking out the first 10 it finds to draw
// the following is a guessed at implmenentation based on how NES does it, it's probably pretty close
if (OAM_cycle < 10)
{
// start by clearing the sprite table (probably just clears X on hardware, but let's be safe here.)
SL_sprites[OAM_cycle * 4] = 0;
SL_sprites[OAM_cycle * 4 + 1] = 0;
SL_sprites[OAM_cycle * 4 + 2] = 0;
SL_sprites[OAM_cycle * 4 + 3] = 0;
}
else
{
if (write_sprite == 0)
{
if (OAM_scan_index < 40)
{
ushort temp = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4] : (ushort)0xFF;
// (sprite Y - 16) equals LY, we have a sprite
if ((temp - 16) <= LY &&
((temp - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY)
{
// always pick the first 10 in range sprites
if (SL_sprites_index < 10)
{
SL_sprites[SL_sprites_index * 4] = temp;
write_sprite = 1;
}
else
{
// if we already have 10 sprites, there's nothing to do, increment the index
OAM_scan_index++;
}
}
else
{
OAM_scan_index++;
}
}
}
else
{
ushort temp2 = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4 + write_sprite] : (ushort)0xFF;
SL_sprites[SL_sprites_index * 4 + write_sprite] = temp2;
write_sprite++;
if (write_sprite == 4)
{
write_sprite = 0;
SL_sprites_index++;
OAM_scan_index++;
}
}
}
}
public override void Reset()
{
LCDC = 0;
STAT = 0x80;
scroll_y = 0;
scroll_x = 0;
LY = 0;
LYC = 0;
DMA_addr = 0xFF;
BGP = 0xFF;
obj_pal_0 = 0xFF;
obj_pal_1 = 0xFF;
window_y = 0x0;
window_x = 0x0;
window_x_latch = 0xFF;
LY_inc = 1;
no_scan = false;
OAM_access_read = true;
VRAM_access_read = true;
OAM_access_write = true;
VRAM_access_write = true;
DMA_OAM_access = true;
cycle = 0;
LYC_INT = false;
HBL_INT = false;
VBL_INT = false;
OAM_INT = false;
stat_line = false;
stat_line_old = false;
window_counter = 0;
window_pre_render = false;
window_started = false;
window_tile_inc = 0;
window_y_tile = 0;
window_x_tile = 0;
window_y_tile_inc = 0;
}
}
}

View File

@ -99,11 +99,66 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
ret = ppu.ReadReg(addr);
break;
// Speed Control for GBC
case 0xFF4D:
if (is_GBC)
{
ret = (byte)(((double_speed ? 1 : 0) << 7) + ((speed_switch ? 1 : 0)));
}
else
{
ret = 0xFF;
}
break;
case 0xFF4F: // VBK
if (is_GBC)
{
ret = VRAM_Bank;
}
else
{
ret = 0xFF;
}
break;
// Bios control register. Not sure if it is readable
case 0xFF50:
ret = 0xFF;
break;
// PPU Regs for GBC
case 0xFF51:
case 0xFF52:
case 0xFF53:
case 0xFF54:
case 0xFF55:
case 0xFF68:
case 0xFF69:
case 0xFF6A:
case 0xFF6B:
if (is_GBC)
{
ret = ppu.ReadReg(addr);
}
else
{
ret = 0xFF;
}
break;
// Speed Control for GBC
case 0xFF70:
if (is_GBC)
{
ret = (byte)RAM_Bank;
}
else
{
ret = 0xFF;
}
break;
// interrupt control register
case 0xFFFF:
ret = REG_FFFF;
@ -254,6 +309,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
ppu.WriteReg(addr, value);
break;
// Speed Control for GBC
case 0xFF4D:
if (is_GBC)
{
speed_switch = (value & 1) > 0;
}
break;
// VBK
case 0xFF4F:
if (is_GBC)
{
VRAM_Bank = (byte)(value & 1);
}
break;
// Bios control register. Writing 1 permanently disables BIOS until a power cycle occurs
case 0xFF50:
//Console.WriteLine(value);
@ -263,6 +334,32 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
}
break;
// PPU Regs for GBC
case 0xFF51:
case 0xFF52:
case 0xFF53:
case 0xFF54:
case 0xFF55:
case 0xFF68:
case 0xFF69:
case 0xFF6A:
case 0xFF6B:
if (is_GBC)
{
ppu.WriteReg(addr, value);
}
break;
// RAM Bank in GBC mode
case 0xFF70:
//Console.WriteLine(value);
if (is_GBC)
{
RAM_Bank = value & 7;
if (RAM_Bank == 0) { RAM_Bank = 1; }
}
break;
// interrupt control register
case 0xFFFF:
REG_FFFF = value;

View File

@ -7,26 +7,57 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
// Default mapper with no bank switching
public class MapperMBC5 : MapperBase
{
public int ROM_bank;
public int RAM_bank;
public bool RAM_enable;
public int ROM_mask;
public int RAM_mask;
public override void Initialize()
{
// nothing to initialize
ROM_bank = 1;
RAM_bank = 0;
RAM_enable = false;
ROM_mask = Core._rom.Length / 0x4000 - 1;
// some games have sizes that result in a degenerate ROM, account for it here
if (ROM_mask > 4) { ROM_mask |= 3; }
RAM_mask = 0;
if (Core.cart_RAM != null)
{
RAM_mask = Core.cart_RAM.Length / 0x2000 - 1;
if (Core.cart_RAM.Length == 0x800) { RAM_mask = 0; }
}
}
public override byte ReadMemory(ushort addr)
{
if (addr < 0x8000)
if (addr < 0x4000)
{
return Core._rom[addr];
}
else if (addr < 0x8000)
{
return Core._rom[(addr - 0x4000) + ROM_bank * 0x4000];
}
else
{
if (Core.cart_RAM != null)
{
return Core.cart_RAM[addr - 0xA000];
if (RAM_enable && (((addr - 0xA000) + RAM_bank * 0x2000) < Core.cart_RAM.Length))
{
return Core.cart_RAM[(addr - 0xA000) + RAM_bank * 0x2000];
}
else
{
return 0xFF;
}
}
else
{
return 0;
return 0xFF;
}
}
}
@ -40,13 +71,40 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
if (addr < 0x8000)
{
// no mapping hardware available
if (addr < 0x2000)
{
RAM_enable = (value & 0xF) == 0xA;
}
else if (addr < 0x3000)
{
value &= 0xFF;
ROM_bank &= 0x100;
ROM_bank |= value;
ROM_bank &= ROM_mask;
}
else if (addr < 0x4000)
{
value &= 1;
ROM_bank &= 0xFF;
ROM_bank |= (value << 8);
ROM_bank &= ROM_mask;
}
else if (addr < 0x6000)
{
RAM_bank = value & 0xF;
RAM_bank &= RAM_mask;
}
}
else
{
if (Core.cart_RAM != null)
{
Core.cart_RAM[addr - 0xA000] = value;
if (RAM_enable && (((addr - 0xA000) + RAM_bank * 0x2000) < Core.cart_RAM.Length))
{
Core.cart_RAM[(addr - 0xA000) + RAM_bank * 0x2000] = value;
}
}
}
}
@ -55,5 +113,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
WriteMemory(addr, value);
}
public override void SyncState(Serializer ser)
{
ser.Sync("ROM_Bank", ref ROM_bank);
ser.Sync("ROM_Mask", ref ROM_mask);
ser.Sync("RAM_Bank", ref RAM_bank);
ser.Sync("RAM_Mask", ref RAM_mask);
ser.Sync("RAM_enable", ref RAM_enable);
}
}
}

View File

@ -33,13 +33,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if (ppu.DMA_start)
{
// some of gekkio's tests require these to be accessible during DMA
if (addr < 0x4000)
{
return mapper.ReadMemory(addr); // some of gekkio's tests require this to be accessible during DMA
return mapper.ReadMemory(addr);
}
else if ((addr >= 0xE000) && (addr < 0xFE00))
else if ((addr >= 0xE000) && (addr < 0xF000))
{
return RAM[addr - 0xE000]; // some of gekkio's tests require this to be accessible during DMA
return RAM[addr - 0xE000];
}
else if ((addr >= 0xF000) && (addr < 0xFE00))
{
return RAM[(RAM_Bank * 0x1000) + (addr - 0xF000)];
}
else if ((addr >= 0xFE00) && (addr < 0xFEA0) && ppu.DMA_OAM_access)
{
@ -57,12 +62,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
return 0xFF;
}
if (addr < 0x100)
if (addr < 0x900)
{
// return Either BIOS ROM or Game ROM
if ((GB_bios_register & 0x1) == 0)
if (addr < 0x100)
{
return _bios[addr]; // Return BIOS
// return Either BIOS ROM or Game ROM
if ((GB_bios_register & 0x1) == 0)
{
return _bios[addr]; // Return BIOS
}
else
{
return mapper.ReadMemory(addr);
}
}
else if (addr >= 0x200)
{
// return Either BIOS ROM or Game ROM
if (((GB_bios_register & 0x1) == 0) && is_GBC)
{
return _bios[addr]; // Return BIOS
}
else
{
return mapper.ReadMemory(addr);
}
}
else
{
@ -73,33 +97,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
return mapper.ReadMemory(addr);
}
else if (addr < 0x9800)
{
if (ppu.VRAM_access_read) { return CHR_RAM[addr - 0x8000]; }
else { return 0xFF; }
}
else if (addr < 0x9C00)
{
if (ppu.VRAM_access_read) { return BG_map_1[addr - 0x9800]; }
else { return 0xFF; }
}
else if (addr < 0xA000)
{
if (ppu.VRAM_access_read) { return BG_map_2[addr - 0x9C00]; }
if (ppu.VRAM_access_read) { return VRAM[(VRAM_Bank * 0x2000) + (addr - 0x8000)]; }
else { return 0xFF; }
}
else if (addr < 0xC000)
{
return mapper.ReadMemory(addr);
}
else if (addr < 0xE000)
else if (addr < 0xD000)
{
return RAM[addr - 0xC000];
}
else if (addr < 0xFE00)
else if (addr < 0xE000)
{
return RAM[(RAM_Bank * 0x1000) + (addr - 0xD000)];
}
else if (addr < 0xF000)
{
return RAM[addr - 0xE000];
}
else if (addr < 0xFE00)
{
return RAM[(RAM_Bank * 0x1000) + (addr - 0xF000)];
}
else if (addr < 0xFEA0)
{
if (ppu.OAM_access_read) { return OAM[addr - 0xFE00]; }
@ -131,9 +153,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
if (ppu.DMA_start)
{
if ((addr >= 0xE000) && (addr < 0xFE00))
// some of gekkio's tests require this to be accessible during DMA
if ((addr >= 0xE000) && (addr < 0xF000))
{
RAM[addr - 0xE000] = value; // some of gekkio's tests require this to be accessible during DMA
RAM[addr - 0xE000] = value;
}
else if ((addr >= 0xF000) && (addr < 0xFE00))
{
RAM[(RAM_Bank * 0x1000) + (addr - 0xF000)] = value;
}
else if ((addr >= 0xFE00) && (addr < 0xFEA0) && ppu.DMA_OAM_access)
{
@ -150,12 +177,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
return;
}
if (addr < 0x100)
if (addr < 0x900)
{
// return Either BIOS ROM or Game ROM
if ((GB_bios_register & 0x1) == 0)
if (addr < 0x100)
{
// Can't write to BIOS region
if ((GB_bios_register & 0x1) == 0)
{
// No Writing to BIOS
}
else
{
mapper.WriteMemory(addr, value);
}
}
else if (addr >= 0x200)
{
if (((GB_bios_register & 0x1) == 0) && is_GBC)
{
// No Writing to BIOS
}
else
{
mapper.WriteMemory(addr, value);
}
}
else
{
@ -166,30 +210,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
mapper.WriteMemory(addr, value);
}
else if (addr < 0x9800)
{
if (ppu.VRAM_access_write) { CHR_RAM[addr - 0x8000] = value; }
}
else if (addr < 0x9C00)
{
if (ppu.VRAM_access_write) { BG_map_1[addr - 0x9800] = value; }
}
else if (addr < 0xA000)
{
if (ppu.VRAM_access_write) { BG_map_2[addr - 0x9C00] = value; }
if (ppu.VRAM_access_write) { VRAM[(VRAM_Bank * 0x2000) + (addr - 0x8000)] = value; }
}
else if (addr < 0xC000)
{
mapper.WriteMemory(addr, value);
}
else if (addr < 0xE000)
else if (addr < 0xD000)
{
RAM[addr - 0xC000] = value;
}
else if (addr < 0xFE00)
else if (addr < 0xE000)
{
RAM[(RAM_Bank * 0x1000) + (addr - 0xD000)] = value;
}
else if (addr < 0xF000)
{
RAM[addr - 0xE000] = value;
}
else if (addr < 0xFE00)
{
RAM[(RAM_Bank * 0x1000) + (addr - 0xF000)] = value;
}
else if (addr < 0xFEA0)
{
if (ppu.OAM_access_write) { OAM[addr - 0xFE00] = value; }
@ -216,13 +260,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
if (ppu.DMA_start)
{
// some of gekkio's tests require these to be accessible during DMA
if (addr < 0x4000)
{
return mapper.ReadMemory(addr); // some of gekkio's tests require this to be accessible during DMA
return mapper.ReadMemory(addr);
}
else if ((addr >= 0xE000) && (addr < 0xFE00))
else if ((addr >= 0xE000) && (addr < 0xF000))
{
return RAM[addr - 0xE000]; // some of gekkio's tests require this to be accessible during DMA
return RAM[addr - 0xE000];
}
else if ((addr >= 0xF000) && (addr < 0xFE00))
{
return RAM[(RAM_Bank * 0x1000) + (addr - 0xF000)];
}
else if ((addr >= 0xFE00) && (addr < 0xFEA0) && ppu.DMA_OAM_access)
{
@ -240,49 +289,66 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
return 0xFF;
}
if (addr < 0x100)
if (addr < 0x900)
{
// return Either BIOS ROM or Game ROM
if ((GB_bios_register & 0x1) == 0)
if (addr < 0x100)
{
return _bios[addr]; // Return BIOS
// return Either BIOS ROM or Game ROM
if ((GB_bios_register & 0x1) == 0)
{
return _bios[addr]; // Return BIOS
}
else
{
return mapper.ReadMemory(addr);
}
}
else if (addr >= 0x200)
{
// return Either BIOS ROM or Game ROM
if (((GB_bios_register & 0x1) == 0) && is_GBC)
{
return _bios[addr]; // Return BIOS
}
else
{
return mapper.ReadMemory(addr);
}
}
else
{
return mapper.PeekMemory(addr);
return mapper.ReadMemory(addr);
}
}
else if (addr < 0x8000)
{
return mapper.PeekMemory(addr);
}
else if (addr < 0x9800)
{
if (ppu.VRAM_access_read) { return CHR_RAM[addr - 0x8000]; }
else { return 0xFF; }
}
else if (addr < 0x9C00)
{
if (ppu.VRAM_access_read) { return BG_map_1[addr - 0x9800]; }
else { return 0xFF; }
}
else if (addr < 0xA000)
{
if (ppu.VRAM_access_read) { return BG_map_2[addr - 0x9C00]; }
if (ppu.VRAM_access_read) { return VRAM[(VRAM_Bank * 0x2000) + (addr - 0x8000)]; }
else { return 0xFF; }
}
else if (addr < 0xC000)
{
return mapper.PeekMemory(addr);
}
else if (addr < 0xE000)
else if (addr < 0xD000)
{
return RAM[addr - 0xC000];
}
else if (addr < 0xFE00)
else if (addr < 0xE000)
{
return RAM[(RAM_Bank * 0x1000) + (addr - 0xD000)];
}
else if (addr < 0xF000)
{
return RAM[addr - 0xE000];
}
else if (addr < 0xFE00)
{
return RAM[(RAM_Bank * 0x1000) + (addr - 0xF000)];
}
else if (addr < 0xFEA0)
{
if (ppu.OAM_access_read) { return OAM[addr - 0xFE00]; }

View File

@ -122,278 +122,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
}
public virtual void process_sprite()
{
}
// normal DMA moves twice as fast in double speed mode on GBC
// So give it it's own function so we can seperate it from PPU tick
public virtual void DMA_tick()
{
// Note that DMA is halted when the CPU is halted
if (DMA_start && !Core.cpu.halted)
{
if (DMA_clock >= 4)
{
DMA_OAM_access = false;
if ((DMA_clock % 4) == 1)
{
// the cpu can't access memory during this time, but we still need the ppu to be able to.
DMA_start = false;
// Gekkio reports that A14 being high on DMA transfers always represent WRAM accesses
// So transfers nominally from higher memory areas are actually still from there (i.e. FF -> DF)
byte DMA_actual = DMA_addr;
if (DMA_addr > 0xDF) { DMA_actual &= 0xDF; }
DMA_byte = Core.ReadMemory((ushort)((DMA_actual << 8) + DMA_inc));
DMA_start = true;
}
else if ((DMA_clock % 4) == 3)
{
Core.OAM[DMA_inc] = DMA_byte;
if (DMA_inc < (0xA0 - 1)) { DMA_inc++; }
}
}
DMA_clock++;
if (DMA_clock == 648)
{
DMA_start = false;
DMA_OAM_access = true;
}
}
}
public virtual void OAM_scan(int OAM_cycle)
{
// we are now in STAT mode 2
// TODO: maybe stat mode 2 flags are set at cycle 0 on visible scanlines?
if (OAM_cycle == 0)
{
OAM_access_read = false;
OAM_scan_index = 0;
SL_sprites_index = 0;
write_sprite = 0;
}
// the gameboy has 80 cycles to scan through 40 sprites, picking out the first 10 it finds to draw
// the following is a guessed at implmenentation based on how NES does it, it's probably pretty close
if (OAM_cycle < 10)
{
// start by clearing the sprite table (probably just clears X on hardware, but let's be safe here.)
SL_sprites[OAM_cycle * 4] = 0;
SL_sprites[OAM_cycle * 4 + 1] = 0;
SL_sprites[OAM_cycle * 4 + 2] = 0;
SL_sprites[OAM_cycle * 4 + 3] = 0;
}
else
{
if (write_sprite == 0)
{
if (OAM_scan_index < 40)
{
ushort temp = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4] : (ushort)0xFF;
// (sprite Y - 16) equals LY, we have a sprite
if ((temp - 16) <= LY &&
((temp - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY)
{
// always pick the first 10 in range sprites
if (SL_sprites_index < 10)
{
SL_sprites[SL_sprites_index * 4] = temp;
write_sprite = 1;
}
else
{
// if we already have 10 sprites, there's nothing to do, increment the index
OAM_scan_index++;
}
}
else
{
OAM_scan_index++;
}
}
}
else
{
ushort temp2 = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4 + write_sprite] : (ushort)0xFF;
SL_sprites[SL_sprites_index * 4 + write_sprite] = temp2;
write_sprite++;
if (write_sprite == 4)
{
write_sprite = 0;
SL_sprites_index++;
OAM_scan_index++;
}
}
}
}
public virtual void Reset()
{
LCDC = 0;
STAT = 0x80;
scroll_y = 0;
scroll_x = 0;
LY = 0;
LYC = 0;
DMA_addr = 0xFF;
BGP = 0xFF;
obj_pal_0 = 0xFF;
obj_pal_1 = 0xFF;
window_y = 0x0;
window_x = 0x0;
window_x_latch = 0xFF;
LY_inc = 1;
no_scan = false;
OAM_access_read = true;
VRAM_access_read = true;
OAM_access_write = true;
VRAM_access_write = true;
DMA_OAM_access = true;
cycle = 0;
LYC_INT = false;
HBL_INT = false;
VBL_INT = false;
OAM_INT = false;
stat_line = false;
stat_line_old = false;
window_counter = 0;
window_pre_render = false;
window_started = false;
window_tile_inc = 0;
window_y_tile = 0;
window_x_tile = 0;
window_y_tile_inc = 0;
}
public virtual void process_sprite()
{
int y;
if (SL_sprites[sl_use_index * 4 + 3].Bit(6))
{
if (LCDC.Bit(2))
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
y = 15 - y;
sprite_sel[0] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
sprite_sel[1] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
}
else
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
y = 7 - y;
sprite_sel[0] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
sprite_sel[1] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
}
}
else
{
if (LCDC.Bit(2))
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
sprite_sel[0] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
sprite_sel[1] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
}
else
{
y = LY - (SL_sprites[sl_use_index * 4] - 16);
sprite_sel[0] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
sprite_sel[1] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
}
}
if (SL_sprites[sl_use_index * 4 + 3].Bit(5))
{
int b0, b1, b2, b3, b4, b5, b6, b7 = 0;
for (int i = 0; i < 2; i++)
{
b0 = (sprite_sel[i] & 0x01) << 7;
b1 = (sprite_sel[i] & 0x02) << 5;
b2 = (sprite_sel[i] & 0x04) << 3;
b3 = (sprite_sel[i] & 0x08) << 1;
b4 = (sprite_sel[i] & 0x10) >> 1;
b5 = (sprite_sel[i] & 0x20) >> 3;
b6 = (sprite_sel[i] & 0x40) >> 5;
b7 = (sprite_sel[i] & 0x80) >> 7;
sprite_sel[i] = (byte)(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
}
}
}
// order sprites according to x coordinate
// note that for sprites of equal x coordinate, priority goes to first on the list
public virtual void reorder_and_assemble_sprites()
{
sprite_ordered_index = 0;
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < SL_sprites_index; j++)
{
if (SL_sprites[j * 4 + 1] == i)
{
sl_use_index = j;
process_sprite();
SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[j * 4 + 1];
SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0];
SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1];
SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[j * 4 + 3];
sprite_ordered_index++;
}
}
}
bool have_pixel = false;
byte s_pixel = 0;
byte sprite_attr = 0;
for (int i = 0; i < 160; i++)
{
have_pixel = false;
for (int j = 0; j < SL_sprites_index; j++)
{
if ((i >= (SL_sprites_ordered[j * 4] - 8)) &&
(i < SL_sprites_ordered[j * 4]) &&
!have_pixel)
{
// we can use the current sprite, so pick out a pixel for it
int t_index = i - (SL_sprites_ordered[j * 4] - 8);
t_index = 7 - t_index;
sprite_data[0] = (byte)((SL_sprites_ordered[j * 4 + 1] >> t_index) & 1);
sprite_data[1] = (byte)(((SL_sprites_ordered[j * 4 + 2] >> t_index) & 1) << 1);
s_pixel = (byte)(sprite_data[0] + sprite_data[1]);
sprite_attr = (byte)SL_sprites_ordered[j * 4 + 3];
// pixel color of 0 is transparent, so if this is the case we dont have a pixel
if (s_pixel != 0)
{
have_pixel = true;
}
}
}
if (have_pixel)
{
sprite_present_list[i] = 1;
sprite_pixel_list[i] = s_pixel;
sprite_attr_list[i] = sprite_attr;
}
else
{
sprite_present_list[i] = 0;
}
}
}
public virtual void SyncState(Serializer ser)