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:
parent
ac66b258ba
commit
ca69e52229
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]; }
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue