diff --git a/BizHawk.Emulation.Cores/CPUs/LR35902/Interrupts.cs b/BizHawk.Emulation.Cores/CPUs/LR35902/Interrupts.cs index 0dba320f46..bde8ea4309 100644 --- a/BizHawk.Emulation.Cores/CPUs/LR35902/Interrupts.cs +++ b/BizHawk.Emulation.Cores/CPUs/LR35902/Interrupts.cs @@ -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() { diff --git a/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs b/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs index 1b475ef337..bc5c2b3a06 100644 --- a/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs +++ b/BizHawk.Emulation.Cores/CPUs/LR35902/LR35902.cs @@ -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 PeekMemory; public Func DummyReadMemory; + // Special Function for Speed switching executed on a STOP + public Func SpeedFunc; + //this only calls when the first byte of an instruction is fetched. public Action 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); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBC_PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBC_PPU.cs index d308d004f3..e24995e9b1 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBC_PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBC_PPU.cs @@ -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); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs index 39096e025f..9e2a54d165 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IEmulator.cs @@ -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(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IStatable.cs index dbccea7f07..1cafb28dfc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IStatable.cs @@ -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 diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs index 7b09b8a6b5..70035ef974 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs @@ -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(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GB_PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GB_PPU.cs index e0fad7a5bd..e34e75480c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GB_PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GB_PPU.cs @@ -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; + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/HW_Registers.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/HW_Registers.cs index daf9161210..a82cdb820b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/HW_Registers.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/HW_Registers.cs @@ -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; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC5.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC5.cs index 529f48ca4a..7c63b10f55 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC5.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/Mappers/Mapper_MBC5.cs @@ -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); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs index b1b241d39b..616b1c2413 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs @@ -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]; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs index bddb78d179..bda927e0c5 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/PPU.cs @@ -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)