diff --git a/BizHawk.Emulation/Consoles/Coleco/ColecoVision.Core.cs b/BizHawk.Emulation/Consoles/Coleco/ColecoVision.Core.cs index 149b5b2134..6a2250690e 100644 --- a/BizHawk.Emulation/Consoles/Coleco/ColecoVision.Core.cs +++ b/BizHawk.Emulation/Consoles/Coleco/ColecoVision.Core.cs @@ -10,18 +10,40 @@ namespace BizHawk.Emulation.Consoles.Coleco { public partial class ColecoVision : IEmulator { - public byte[] rom; + public byte[] rom = new byte[2048]; + public byte[] expansion = new byte[0x4000]; + public byte[] cartridgeslot = new byte[0xFFFF]; //TODO: how big should this be? public Z80A cpu; public VDP Vdp; //adelikat: Using the SMS one for now public byte ReadMemory(ushort addr) { - return 0xFF; + if (addr < 0x2000) + { + return rom[addr]; + } + else if (addr >= 0x2000 && addr < 0x6000) + { + return expansion[addr]; + } + else if (addr >= 0x6000 && addr < 0x8000) + { + return ram[addr & 1023]; + } + else if (addr >= 0x8000) + { + return cartridgeslot[addr]; + } + + else return 0xFF; } public void WriteMemory(ushort addr, byte value) { - return; + if (addr >= 0x6000 && addr < 0x8000) + { + ram[addr] = value; + } } public void HardReset() diff --git a/BizHawk.Emulation/Consoles/Coleco/ColecoVision.cs b/BizHawk.Emulation/Consoles/Coleco/ColecoVision.cs index 12d4575bdd..cd1f4945f9 100644 --- a/BizHawk.Emulation/Consoles/Coleco/ColecoVision.cs +++ b/BizHawk.Emulation/Consoles/Coleco/ColecoVision.cs @@ -18,7 +18,7 @@ namespace BizHawk.Emulation.Consoles.Coleco public CoreOutputComm CoreOutputComm { get; private set; } public IVideoProvider VideoProvider { get { return this; } } public ISoundProvider SoundProvider { get { return this; } } - public byte[] ram = new byte[1024]; + public byte[] ram = new byte[2048]; public DisplayType DisplayType { get; set; } //TOOD: delete me diff --git a/BizHawk.Emulation/Consoles/Coleco/VDP.cs b/BizHawk.Emulation/Consoles/Coleco/VDP.cs index 862c42b677..da20897566 100644 --- a/BizHawk.Emulation/Consoles/Coleco/VDP.cs +++ b/BizHawk.Emulation/Consoles/Coleco/VDP.cs @@ -5,516 +5,521 @@ using BizHawk.Emulation.CPUs.Z80; namespace BizHawk.Emulation.Consoles.Coleco { - public enum VdpMode { SMS, GameGear } //TODO: delete me + public enum VdpMode { SMS, GameGear } //TODO: delete me - // Emulates the Texas Instruments TMS9918 VDP. - public sealed partial class VDP : IVideoProvider - { - // VDP State - public byte[] VRAM = new byte[0x4000]; //16kb video RAM - public byte[] CRAM; // SMS = 32 bytes, GG = 64 bytes CRAM - public byte[] Registers = new byte[] { 0x06, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xF0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 }; - public byte StatusByte; + // Emulates the Texas Instruments TMS9918 VDP. + public sealed partial class VDP : IVideoProvider + { + // VDP State + public byte[] VRAM = new byte[0x4000]; //16kb video RAM + public byte[] CRAM; // SMS = 32 bytes, GG = 64 bytes CRAM + public byte[] Registers = new byte[] { 0x06, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xF0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public byte StatusByte; - bool VdpWaitingForLatchByte = true; - byte VdpLatch; - byte VdpBuffer; - ushort VdpAddress; - VdpCommand vdpCommand; - int TmsMode = 4; + bool VdpWaitingForLatchByte = true; + byte VdpLatch; + byte VdpBuffer; + ushort VdpAddress; + VdpCommand vdpCommand; + int TmsMode = 4; - bool VIntPending; - bool HIntPending; + bool VIntPending; + bool HIntPending; - ColecoVision Sms; //TODO: rename - VdpMode mode; - DisplayType DisplayType = DisplayType.NTSC; - Z80A Cpu; + ColecoVision Coleco; + VdpMode mode; + DisplayType DisplayType = DisplayType.NTSC; + Z80A Cpu; - public bool SpriteLimit; - public int IPeriod = 228; - public VdpMode VdpMode { get { return mode; } } + public bool SpriteLimit; + public int IPeriod = 228; + public VdpMode VdpMode { get { return mode; } } - int FrameHeight = 192; - public int ScanLine; - public int[] FrameBuffer = new int[256*192]; - public int[] GameGearFrameBuffer = new int[160*144]; + int FrameHeight = 192; + public int ScanLine; + public int[] FrameBuffer = new int[256 * 192]; + public int[] GameGearFrameBuffer = new int[160 * 144]; - public bool Mode1Bit { get { return (Registers[1] & 16) > 0;} } - public bool Mode2Bit { get { return (Registers[0] & 2) > 0; } } - public bool Mode3Bit { get { return (Registers[1] & 8) > 0; } } - public bool Mode4Bit { get { return (Registers[0] & 4) > 0; } } - public bool ShiftSpritesLeft8Pixels { get { return (Registers[0] & 8) > 0; } } - public bool EnableLineInterrupts { get { return (Registers[0] & 16) > 0; } } - public bool LeftBlanking { get { return (Registers[0] & 32) > 0; } } - public bool HorizScrollLock { get { return (Registers[0] & 64) > 0; } } - public bool VerticalScrollLock { get { return (Registers[0] & 128) > 0; } } - public bool EnableDoubledSprites { get { return (Registers[1] & 1) > 0; } } - public bool EnableLargeSprites { get { return (Registers[1] & 2) > 0; } } - public bool EnableFrameInterrupts { get { return (Registers[1] & 32) > 0; } } - public bool DisplayOn { get { return (Registers[1] & 64) > 0; } } - public int SpriteAttributeTableBase { get { return ((Registers[5] >> 1) << 8) & 0x3FFF; } } - public int SpriteTileBase { get { return (Registers[6] & 4) > 0 ? 256: 0; } } - public byte BackdropColor { get { return (byte)(16 + (Registers[7] & 15)); } } + public bool Mode1Bit { get { return (Registers[1] & 16) > 0; } } + public bool Mode2Bit { get { return (Registers[0] & 2) > 0; } } + public bool Mode3Bit { get { return (Registers[1] & 8) > 0; } } + public bool Mode4Bit { get { return (Registers[0] & 4) > 0; } } + public bool ShiftSpritesLeft8Pixels { get { return (Registers[0] & 8) > 0; } } + public bool EnableLineInterrupts { get { return (Registers[0] & 16) > 0; } } + public bool LeftBlanking { get { return (Registers[0] & 32) > 0; } } + public bool HorizScrollLock { get { return (Registers[0] & 64) > 0; } } + public bool VerticalScrollLock { get { return (Registers[0] & 128) > 0; } } + public bool EnableDoubledSprites { get { return (Registers[1] & 1) > 0; } } + public bool EnableLargeSprites { get { return (Registers[1] & 2) > 0; } } + public bool EnableFrameInterrupts { get { return (Registers[1] & 32) > 0; } } + public bool DisplayOn { get { return (Registers[1] & 64) > 0; } } + public int SpriteAttributeTableBase { get { return ((Registers[5] >> 1) << 8) & 0x3FFF; } } + public int SpriteTileBase { get { return (Registers[6] & 4) > 0 ? 256 : 0; } } + public byte BackdropColor { get { return (byte)(16 + (Registers[7] & 15)); } } - int NameTableBase; - int ColorTableBase; - int PatternGeneratorBase; - int SpritePatternGeneratorBase; - int TmsPatternNameTableBase; - int TmsSpriteAttributeBase; - - // preprocessed state assist stuff. - public int[] Palette = new int[32]; - public byte[] PatternBuffer = new byte[0x8000]; + int NameTableBase; + int ColorTableBase; + int PatternGeneratorBase; + int SpritePatternGeneratorBase; + int TmsPatternNameTableBase; + int TmsSpriteAttributeBase; - byte[] ScanlinePriorityBuffer = new byte[256]; - byte[] SpriteCollisionBuffer = new byte[256]; + // preprocessed state assist stuff. + public int[] Palette = new int[32]; + public byte[] PatternBuffer = new byte[0x8000]; - static readonly byte[] SMSPalXlatTable = { 0, 85, 170, 255 }; - static readonly byte[] GGPalXlatTable = { 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255 }; + byte[] ScanlinePriorityBuffer = new byte[256]; + byte[] SpriteCollisionBuffer = new byte[256]; - public VDP(ColecoVision sms, Z80A cpu, VdpMode mode, DisplayType displayType) - { - Sms = sms; - Cpu = cpu; - this.mode = mode; - if (mode == VdpMode.SMS) CRAM = new byte[32]; - if (mode == VdpMode.GameGear) CRAM = new byte[64]; - DisplayType = displayType; - NameTableBase = CalcNameTableBase(); - } + static readonly byte[] SMSPalXlatTable = { 0, 85, 170, 255 }; + static readonly byte[] GGPalXlatTable = { 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255 }; - public byte ReadData() - { - VdpWaitingForLatchByte = true; - byte value = VdpBuffer; - VdpBuffer = VRAM[VdpAddress & 0x3FFF]; - VdpAddress++; - return value; - } + public VDP(ColecoVision sms, Z80A cpu, VdpMode mode, DisplayType displayType) + { + Coleco = sms; + Cpu = cpu; + this.mode = mode; + if (mode == VdpMode.SMS) CRAM = new byte[32]; + if (mode == VdpMode.GameGear) CRAM = new byte[64]; + DisplayType = displayType; + NameTableBase = CalcNameTableBase(); + } - public byte ReadVdpStatus() - { - VdpWaitingForLatchByte = true; - byte returnValue = StatusByte; - StatusByte &= 0x1F; - HIntPending = false; - VIntPending = false; - Cpu.Interrupt = false; - return returnValue; - } + public byte ReadData() + { + VdpWaitingForLatchByte = true; + byte value = VdpBuffer; + VdpBuffer = VRAM[VdpAddress & 0x3FFF]; + VdpAddress++; + return value; + } - public byte ReadVLineCounter() - { - if (DisplayType == DisplayType.NTSC) - { - if (FrameHeight == 192) - return VLineCounterTableNTSC192[ScanLine]; - if (FrameHeight == 224) - return VLineCounterTableNTSC224[ScanLine]; - return VLineCounterTableNTSC240[ScanLine]; - } else { // PAL - if (FrameHeight == 192) - return VLineCounterTablePAL192[ScanLine]; - if (FrameHeight == 224) - return VLineCounterTablePAL224[ScanLine]; - return VLineCounterTablePAL240[ScanLine]; - } - } + public byte ReadVdpStatus() + { + VdpWaitingForLatchByte = true; + byte returnValue = StatusByte; + StatusByte &= 0x1F; + HIntPending = false; + VIntPending = false; + Cpu.Interrupt = false; + return returnValue; + } - public void WriteVdpControl(byte value) - { - if (VdpWaitingForLatchByte) - { - VdpLatch = value; - VdpWaitingForLatchByte = false; - VdpAddress = (ushort)((VdpAddress & 0xFF00) | value); - return; - } + public byte ReadVLineCounter() + { + if (DisplayType == DisplayType.NTSC) + { + if (FrameHeight == 192) + return VLineCounterTableNTSC192[ScanLine]; + if (FrameHeight == 224) + return VLineCounterTableNTSC224[ScanLine]; + return VLineCounterTableNTSC240[ScanLine]; + } + else + { // PAL + if (FrameHeight == 192) + return VLineCounterTablePAL192[ScanLine]; + if (FrameHeight == 224) + return VLineCounterTablePAL224[ScanLine]; + return VLineCounterTablePAL240[ScanLine]; + } + } - VdpWaitingForLatchByte = true; - VdpAddress = (ushort)(((value & 63) << 8) | VdpLatch); - switch (value & 0xC0) - { - case 0x00: // read VRAM - vdpCommand = VdpCommand.VramRead; - VdpBuffer = VRAM[VdpAddress & 0x3FFF]; - VdpAddress++; - break; - case 0x40: // write VRAM - vdpCommand = VdpCommand.VramWrite; - break; - case 0x80: // VDP register write - vdpCommand = VdpCommand.RegisterWrite; - int reg = value & 0x0F; - WriteRegister(reg, VdpLatch); - break; - case 0xC0: // write CRAM / modify palette - vdpCommand = VdpCommand.CramWrite; - break; - } - } + public void WriteVdpControl(byte value) + { + if (VdpWaitingForLatchByte) + { + VdpLatch = value; + VdpWaitingForLatchByte = false; + VdpAddress = (ushort)((VdpAddress & 0xFF00) | value); + return; + } - public void WriteVdpData(byte value) - { - VdpWaitingForLatchByte = true; - VdpBuffer = value; - if (vdpCommand == VdpCommand.CramWrite) - { - // Write Palette / CRAM - int mask = VdpMode == VdpMode.SMS ? 0x1F : 0x3F; - CRAM[VdpAddress & mask] = value; - UpdatePrecomputedPalette(); - } - else - { - // Write VRAM and update pre-computed pattern buffer. - UpdatePatternBuffer((ushort)(VdpAddress & 0x3FFF), value); - VRAM[VdpAddress & 0x3FFF] = value; - } - VdpAddress++; - } + VdpWaitingForLatchByte = true; + VdpAddress = (ushort)(((value & 63) << 8) | VdpLatch); + switch (value & 0xC0) + { + case 0x00: // read VRAM + vdpCommand = VdpCommand.VramRead; + VdpBuffer = VRAM[VdpAddress & 0x3FFF]; + VdpAddress++; + break; + case 0x40: // write VRAM + vdpCommand = VdpCommand.VramWrite; + break; + case 0x80: // VDP register write + vdpCommand = VdpCommand.RegisterWrite; + int reg = value & 0x0F; + WriteRegister(reg, VdpLatch); + break; + case 0xC0: // write CRAM / modify palette + vdpCommand = VdpCommand.CramWrite; + break; + } + } - public void UpdatePrecomputedPalette() - { - if (mode == VdpMode.SMS) - { - for (int i=0; i<32; i++) - { - byte value = CRAM[i]; - byte r = SMSPalXlatTable[(value & 0x03)]; - byte g = SMSPalXlatTable[(value & 0x0C) >> 2]; - byte b = SMSPalXlatTable[(value & 0x30) >> 4]; - Palette[i] = Colors.ARGB(r, g, b); - } - } else { // GameGear - for (int i=0; i<32; i++) - { - ushort value = (ushort) ((CRAM[(i*2) + 1] << 8) | CRAM[(i*2) + 0]); - byte r = GGPalXlatTable[(value & 0x000F)]; - byte g = GGPalXlatTable[(value & 0x00F0) >> 4]; - byte b = GGPalXlatTable[(value & 0x0F00) >> 8]; - Palette[i] = Colors.ARGB(r, g, b); - } - } - } + public void WriteVdpData(byte value) + { + VdpWaitingForLatchByte = true; + VdpBuffer = value; + if (vdpCommand == VdpCommand.CramWrite) + { + // Write Palette / CRAM + int mask = VdpMode == VdpMode.SMS ? 0x1F : 0x3F; + CRAM[VdpAddress & mask] = value; + UpdatePrecomputedPalette(); + } + else + { + // Write VRAM and update pre-computed pattern buffer. + UpdatePatternBuffer((ushort)(VdpAddress & 0x3FFF), value); + VRAM[VdpAddress & 0x3FFF] = value; + } + VdpAddress++; + } - public int CalcNameTableBase() - { - if (FrameHeight == 192) - return 1024 * (Registers[2] & 0x0E); - return (1024 * (Registers[2] & 0x0C)) + 0x0700; - } + public void UpdatePrecomputedPalette() + { + if (mode == VdpMode.SMS) + { + for (int i = 0; i < 32; i++) + { + byte value = CRAM[i]; + byte r = SMSPalXlatTable[(value & 0x03)]; + byte g = SMSPalXlatTable[(value & 0x0C) >> 2]; + byte b = SMSPalXlatTable[(value & 0x30) >> 4]; + Palette[i] = Colors.ARGB(r, g, b); + } + } + else + { // GameGear + for (int i = 0; i < 32; i++) + { + ushort value = (ushort)((CRAM[(i * 2) + 1] << 8) | CRAM[(i * 2) + 0]); + byte r = GGPalXlatTable[(value & 0x000F)]; + byte g = GGPalXlatTable[(value & 0x00F0) >> 4]; + byte b = GGPalXlatTable[(value & 0x0F00) >> 8]; + Palette[i] = Colors.ARGB(r, g, b); + } + } + } - void CheckVideoMode() - { - if (Mode4Bit == false) // check old TMS modes - { - if (Mode1Bit) TmsMode = 1; - else if (Mode2Bit) TmsMode = 2; - else if (Mode3Bit) TmsMode = 3; - else TmsMode = 0; - } + public int CalcNameTableBase() + { + if (FrameHeight == 192) + return 1024 * (Registers[2] & 0x0E); + return (1024 * (Registers[2] & 0x0C)) + 0x0700; + } - else if (Mode4Bit && Mode2Bit) // if Mode4 and Mode2 set, then check extension modes - { - TmsMode = 4; - switch (Registers[1] & 0x18) - { - case 0x00: - case 0x18: // 192-line mode - if (FrameHeight != 192) - { - FrameHeight = 192; - FrameBuffer = new int[256*192]; - NameTableBase = CalcNameTableBase(); - } - break; - case 0x10: // 224-line mode - if (FrameHeight != 224) - { - FrameHeight = 224; - FrameBuffer = new int[256*224]; - NameTableBase = CalcNameTableBase(); - } - break; - case 0x08: // 240-line mode - if (FrameHeight != 240) - { - FrameHeight = 240; - FrameBuffer = new int[256 * 240]; - NameTableBase = CalcNameTableBase(); - } - break; - } - } - - else { // default to standard 192-line mode4 - TmsMode = 4; - if (FrameHeight != 192) - { - FrameHeight = 192; - FrameBuffer = new int[256*192]; - NameTableBase = CalcNameTableBase(); - } - } - } + void CheckVideoMode() + { + if (Mode4Bit == false) // check old TMS modes + { + if (Mode1Bit) TmsMode = 1; + else if (Mode2Bit) TmsMode = 2; + else if (Mode3Bit) TmsMode = 3; + else TmsMode = 0; + } - void WriteRegister(int reg, byte data) - { - Registers[reg] = data; - switch(reg) - { - case 0: // Mode Control Register 1 - CheckVideoMode(); - Cpu.Interrupt = (EnableLineInterrupts && HIntPending); - break; - case 1: // Mode Control Register 2 - CheckVideoMode(); - Cpu.Interrupt = (EnableFrameInterrupts && VIntPending); - break; - case 2: // Name Table Base Address - NameTableBase = CalcNameTableBase(); - TmsPatternNameTableBase = (Registers[2] << 10) & 0x3C00; - break; - case 3: // Color Table Base Address - ColorTableBase = (Registers[3] << 6) & 0x3FC0; - break; - case 4: // Pattern Generator Base Address - PatternGeneratorBase = (Registers[4] << 11) & 0x3800; - break; - case 5: // Sprite Attribute Table Base Address - // ??? should I move from my property to precalculated? - TmsSpriteAttributeBase = (Registers[5] << 7) & 0x3F80; - break; - case 6: // Sprite Pattern Generator Base Adderss - SpritePatternGeneratorBase = (Registers[6] << 11) & 0x3800; - break; - } - } + else if (Mode4Bit && Mode2Bit) // if Mode4 and Mode2 set, then check extension modes + { + TmsMode = 4; + switch (Registers[1] & 0x18) + { + case 0x00: + case 0x18: // 192-line mode + if (FrameHeight != 192) + { + FrameHeight = 192; + FrameBuffer = new int[256 * 192]; + NameTableBase = CalcNameTableBase(); + } + break; + case 0x10: // 224-line mode + if (FrameHeight != 224) + { + FrameHeight = 224; + FrameBuffer = new int[256 * 224]; + NameTableBase = CalcNameTableBase(); + } + break; + case 0x08: // 240-line mode + if (FrameHeight != 240) + { + FrameHeight = 240; + FrameBuffer = new int[256 * 240]; + NameTableBase = CalcNameTableBase(); + } + break; + } + } - static readonly byte[] pow2 = {1, 2, 4, 8, 16, 32, 64, 128}; - - void UpdatePatternBuffer(ushort address, byte value) - { - // writing one byte affects 8 pixels due to stupid planar storage. - for (int i=0; i<8; i++) - { - byte colorBit = pow2[address%4]; - byte sourceBit = pow2[7 - i]; - ushort dest = (ushort) (((address & 0xFFFC)*2) + i); - if ((value & sourceBit) > 0) // setting bit - PatternBuffer[dest] |= colorBit; - else // clearing bit - PatternBuffer[dest] &= (byte)~colorBit; - } - } + else + { // default to standard 192-line mode4 + TmsMode = 4; + if (FrameHeight != 192) + { + FrameHeight = 192; + FrameBuffer = new int[256 * 192]; + NameTableBase = CalcNameTableBase(); + } + } + } - int lineIntLinesRemaining; + void WriteRegister(int reg, byte data) + { + Registers[reg] = data; + switch (reg) + { + case 0: // Mode Control Register 1 + CheckVideoMode(); + Cpu.Interrupt = (EnableLineInterrupts && HIntPending); + break; + case 1: // Mode Control Register 2 + CheckVideoMode(); + Cpu.Interrupt = (EnableFrameInterrupts && VIntPending); + break; + case 2: // Name Table Base Address + NameTableBase = CalcNameTableBase(); + TmsPatternNameTableBase = (Registers[2] << 10) & 0x3C00; + break; + case 3: // Color Table Base Address + ColorTableBase = (Registers[3] << 6) & 0x3FC0; + break; + case 4: // Pattern Generator Base Address + PatternGeneratorBase = (Registers[4] << 11) & 0x3800; + break; + case 5: // Sprite Attribute Table Base Address + // ??? should I move from my property to precalculated? + TmsSpriteAttributeBase = (Registers[5] << 7) & 0x3F80; + break; + case 6: // Sprite Pattern Generator Base Adderss + SpritePatternGeneratorBase = (Registers[6] << 11) & 0x3800; + break; + } + } - void ProcessFrameInterrupt() - { - if (ScanLine == FrameHeight + 1) - { - StatusByte |= 0x80; - VIntPending = true; - } + static readonly byte[] pow2 = { 1, 2, 4, 8, 16, 32, 64, 128 }; - if (VIntPending && EnableFrameInterrupts) - Cpu.Interrupt = true; - } + void UpdatePatternBuffer(ushort address, byte value) + { + // writing one byte affects 8 pixels due to stupid planar storage. + for (int i = 0; i < 8; i++) + { + byte colorBit = pow2[address % 4]; + byte sourceBit = pow2[7 - i]; + ushort dest = (ushort)(((address & 0xFFFC) * 2) + i); + if ((value & sourceBit) > 0) // setting bit + PatternBuffer[dest] |= colorBit; + else // clearing bit + PatternBuffer[dest] &= (byte)~colorBit; + } + } - void ProcessLineInterrupt() - { - if (ScanLine <= FrameHeight) - { - if (lineIntLinesRemaining-- <= 0) - { - HIntPending = true; - if (EnableLineInterrupts) - Cpu.Interrupt = true; - lineIntLinesRemaining = Registers[0x0A]; - } - return; - } - // else we're outside the active display period - lineIntLinesRemaining = Registers[0x0A]; - } + int lineIntLinesRemaining; - public void ExecFrame(bool render) - { - int scanlinesPerFrame = DisplayType == DisplayType.NTSC ? 262 : 313; - for (ScanLine = 0; ScanLine < scanlinesPerFrame; ScanLine++) - { - RenderCurrentScanline(render); + void ProcessFrameInterrupt() + { + if (ScanLine == FrameHeight + 1) + { + StatusByte |= 0x80; + VIntPending = true; + } - ProcessFrameInterrupt(); - ProcessLineInterrupt(); + if (VIntPending && EnableFrameInterrupts) + Cpu.Interrupt = true; + } - Cpu.ExecuteCycles(IPeriod); + void ProcessLineInterrupt() + { + if (ScanLine <= FrameHeight) + { + if (lineIntLinesRemaining-- <= 0) + { + HIntPending = true; + if (EnableLineInterrupts) + Cpu.Interrupt = true; + lineIntLinesRemaining = Registers[0x0A]; + } + return; + } + // else we're outside the active display period + lineIntLinesRemaining = Registers[0x0A]; + } - if (ScanLine == scanlinesPerFrame - 1) - RenderBlankingRegions(); - } - } + public void ExecFrame(bool render) + { + int scanlinesPerFrame = DisplayType == DisplayType.NTSC ? 262 : 313; + for (ScanLine = 0; ScanLine < scanlinesPerFrame; ScanLine++) + { + RenderCurrentScanline(render); - internal void RenderCurrentScanline(bool render) - { - if (ScanLine >= FrameHeight) - return; + ProcessFrameInterrupt(); + ProcessLineInterrupt(); - if (TmsMode == 4) - { - if (render == false) - { - ProcessSpriteCollisionForFrameskip(); - return; - } + Cpu.ExecuteCycles(IPeriod); - RenderBackgroundCurrentLine(Sms.CoreInputComm.SMS_ShowBG); + if (ScanLine == scanlinesPerFrame - 1) + RenderBlankingRegions(); + } + } - if (EnableDoubledSprites) - RenderSpritesCurrentLineDoubleSize(Sms.CoreInputComm.SMS_ShowOBJ); - else - RenderSpritesCurrentLine(Sms.CoreInputComm.SMS_ShowOBJ); - } - else if (TmsMode == 2) - { - if (render == false) - return; + internal void RenderCurrentScanline(bool render) + { + if (ScanLine >= FrameHeight) + return; - RenderBackgroundM2(Sms.CoreInputComm.SMS_ShowBG); - RenderTmsSprites(Sms.CoreInputComm.SMS_ShowOBJ); - } - else if (TmsMode == 0) - { - if (render == false) - return; + if (TmsMode == 4) + { + if (render == false) + { + ProcessSpriteCollisionForFrameskip(); + return; + } - RenderBackgroundM0(Sms.CoreInputComm.SMS_ShowBG); - RenderTmsSprites(Sms.CoreInputComm.SMS_ShowOBJ); - } - } + RenderBackgroundCurrentLine(Coleco.CoreInputComm.SMS_ShowBG); - public void SaveStateText(TextWriter writer) - { - writer.WriteLine("[VDP]"); - writer.WriteLine("StatusByte {0:X2}", StatusByte); - writer.WriteLine("WaitingForLatchByte {0}", VdpWaitingForLatchByte); - writer.WriteLine("Latch {0:X2}", VdpLatch); - writer.WriteLine("ReadBuffer {0:X2}", VdpBuffer); - writer.WriteLine("VdpAddress {0:X4}", VdpAddress); - writer.WriteLine("Command " + Enum.GetName(typeof(VdpCommand), vdpCommand)); + if (EnableDoubledSprites) + RenderSpritesCurrentLineDoubleSize(Coleco.CoreInputComm.SMS_ShowOBJ); + else + RenderSpritesCurrentLine(Coleco.CoreInputComm.SMS_ShowOBJ); + } + else if (TmsMode == 2) + { + if (render == false) + return; - writer.Write("Registers "); - Registers.SaveAsHex(writer); - writer.Write("CRAM "); - CRAM.SaveAsHex(writer); - writer.Write("VRAM "); - VRAM.SaveAsHex(writer); + RenderBackgroundM2(Coleco.CoreInputComm.SMS_ShowBG); + RenderTmsSprites(Coleco.CoreInputComm.SMS_ShowOBJ); + } + else if (TmsMode == 0) + { + if (render == false) + return; - writer.WriteLine("[/VDP]"); - writer.WriteLine(); - } + RenderBackgroundM0(Coleco.CoreInputComm.SMS_ShowBG); + RenderTmsSprites(Coleco.CoreInputComm.SMS_ShowOBJ); + } + } - public void LoadStateText(TextReader reader) - { - while (true) - { - string[] args = reader.ReadLine().Split(' '); - if (args[0].Trim() == "") continue; - if (args[0] == "[/VDP]") break; - if (args[0] == "StatusByte") - StatusByte = byte.Parse(args[1], NumberStyles.HexNumber); - else if (args[0] == "WaitingForLatchByte") - VdpWaitingForLatchByte = bool.Parse(args[1]); - else if (args[0] == "Latch") - VdpLatch = byte.Parse(args[1], NumberStyles.HexNumber); - else if (args[0] == "ReadBuffer") - VdpBuffer = byte.Parse(args[1], NumberStyles.HexNumber); - else if (args[0] == "VdpAddress") - VdpAddress = ushort.Parse(args[1], NumberStyles.HexNumber); - else if (args[0] == "Command") - vdpCommand = (VdpCommand) Enum.Parse(typeof (VdpCommand), args[1]); - else if (args[0] == "Registers") - Registers.ReadFromHex(args[1]); - else if (args[0] == "CRAM") - { - CRAM.ReadFromHex(args[1]); - UpdatePrecomputedPalette(); - } - else if (args[0] == "VRAM") - { - VRAM.ReadFromHex(args[1]); - for (ushort i=0; i