using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; using Essgee.EventArguments; using Essgee.Utilities; using static Essgee.Emulation.Utilities; namespace Essgee.Emulation.Video { /* Sega 315-5124 (Mark III, SMS) and 315-5246 (SMS 2); differences see 'VDPDIFF' comments */ public class SegaSMSVDP : TMS99xxA { /* VDPDIFF: switch for Mk3/SMS1 vs SMS2/GG behavior; configurable via SetRevision, maybe still split into separate classes instead? */ protected VDPTypes vdpType = VDPTypes.Mk3SMS1; public const int NumActiveScanlinesLow = 192; public const int NumActiveScanlinesMed = 224; public const int NumActiveScanlinesHigh = 240; protected const int NumSpritesMode4 = 64; protected const int NumSpritesPerLineMode4 = 8; public const int PortVCounter = 0x40; // 0x7E canonically, but mirrored across bus public const int PortHCounter = 0x41; // 0x7F canonically, but mirrored across bus [StateRequired] protected byte[] cram; [StateRequired] protected int vCounter, hCounter; protected int nametableHeight, vCounterTableIndex; [StateRequired] protected int lineInterruptCounter; protected int screenHeight; bool isLineInterruptEnabled => IsBitSet(registers[0x00], 4); [StateRequired] bool isLineInterruptPending; bool isColumn0MaskEnabled => IsBitSet(registers[0x00], 5); bool isVScrollPartiallyDisabled => IsBitSet(registers[0x00], 7); /* Columns 24-31, i.e. pixels 192-255 */ bool isHScrollPartiallyDisabled => IsBitSet(registers[0x00], 6); /* Rows 0-1, i.e. pixels 0-15 */ bool isBitM4Set => IsBitSet(registers[0x00], 2); protected override bool isModeGraphics1 => !(isBitM1Set || isBitM2Set || isBitM3Set || isBitM4Set); protected override bool isModeText => (isBitM1Set && !(isBitM2Set || isBitM3Set || isBitM4Set)); protected override bool isModeGraphics2 => (isBitM2Set && !(isBitM1Set || isBitM3Set || isBitM4Set)); protected override bool isModeMulticolor => (isBitM3Set && !(isBitM1Set || isBitM2Set || isBitM4Set)); protected bool isSMS240LineMode => (!isBitM1Set && isBitM2Set && isBitM3Set && isBitM4Set); protected bool isSMS224LineMode => (isBitM1Set && isBitM2Set && !isBitM3Set && isBitM4Set); bool isSpriteShiftLeft8 => IsBitSet(registers[0x00], 3); protected override ushort nametableBaseAddress { get { if (isBitM4Set) { if (isSMS224LineMode || isSMS240LineMode) return (ushort)(((registers[0x02] & 0x0C) << 10) | 0x700); else return (ushort)((registers[0x02] & 0x0E) << 10); } else return (ushort)((registers[0x02] & 0x0F) << 10); } } protected override ushort spriteAttribTableBaseAddress => (ushort)((registers[0x05] & (isBitM4Set ? 0x7E : 0x7F)) << 7); protected override ushort spritePatternGenBaseAddress => (ushort)((registers[0x06] & (isBitM4Set ? 0x04 : 0x07)) << 11); /* http://www.smspower.org/Development/Palette */ // TODO: verify these, SMSPower has some mistakes (RGB approx correct, palette value wrong) // (not that we'll really use this, aside from for F-16 Fighting Falcon, as SG1000 games should always be loaded into the SG1000 core...) readonly byte[] legacyColorMap = new byte[] { 0x00, /* Transparent */ 0x00, /* Black */ 0x08, /* Medium green */ 0x0C, /* Light green */ 0x10, /* Dark blue */ 0x30, /* Light blue */ 0x01, /* Dark red */ 0x3C, /* Cyan */ 0x02, /* Medium red */ 0x03, /* Light red */ 0x05, /* Dark yellow */ 0x0F, /* Light yellow */ 0x04, /* Dark green */ 0x33, /* Magenta */ 0x15, /* Gray */ 0x3F /* White */ }; readonly byte[][] vCounterTables = new byte[][] { /* NTSC, 192 lines */ new byte[] { /* Top blanking */ 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, /* Top border */ 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, /* Active display */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, /* Bottom border */ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, /* Bottom blanking */ 0xD8, 0xD9, 0xDA, /* Vertical blanking */ 0xD5, 0xD6, 0xD7 }, /* NTSC, 224 lines */ new byte[] { /* Top blanking */ 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, /* Top border */ 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, /* Active display */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, /* Bottom border */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, /* Bottom blanking */ 0xE8, 0xE9, 0xEA, /* Vertical blanking */ 0xE5, 0xE6, 0xE7, }, /* NTSC, 240 lines (invalid) */ new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }, /* PAL, 192 lines */ new byte[] { /* Top blanking */ 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, /* Top border */ 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, /* Active display */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, /* Bottom border */ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, /* Bottom blanking */ 0xF0, 0xF1, 0xF2, /* Vertical blanking */ 0xBA, 0xBB, 0xBC }, /* PAL, 224 lines */ new byte[] { /* Top blanking */ 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, /* Top border */ 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, /* Active display */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, /* Bottom border */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, /* Bottom blanking */ 0x00, 0x01, 0x02, /* Vertical blanking */ 0xCA, 0xCB, 0xCC, }, /* PAL, 240 lines */ new byte[] { /* Top blanking */ 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, /* Top border */ 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, /* Active display */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, /* Bottom border */ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* Bottom blanking */ 0x08, 0x09, 0x0A, /* Vertical blanking */ 0xD2, 0xD3, 0xD4, } }; /* For H-counter emulation */ readonly byte[] hCounterTable = new byte[] { 0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0B, 0x0B, 0x0C, 0x0D, 0x0E, 0x0E, 0x0F, 0x10, 0x11, 0x11, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x17, 0x17, 0x18, 0x19, 0x1A, 0x1A, 0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x20, 0x20, 0x21, 0x22, 0x23, 0x23, 0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3B, 0x3B, 0x3C, 0x3D, 0x3E, 0x3E, 0x3F, 0x40, 0x41, 0x41, 0x42, 0x43, 0x44, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4A, 0x4A, 0x4B, 0x4C, 0x4D, 0x4D, 0x4E, 0x4F, 0x50, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54, 0x55, 0x56, 0x56, 0x57, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C, 0x5C, 0x5D, 0x5E, 0x5F, 0x5F, 0x60, 0x61, 0x62, 0x62, 0x63, 0x64, 0x65, 0x65, 0x66, 0x67, 0x68, 0x68, 0x69, 0x6A, 0x6B, 0x6B, 0x6C, 0x6D, 0x6E, 0x6E, 0x6F, 0x70, 0x71, 0x71, 0x72, 0x73, 0x74, 0x74, 0x75, 0x76, 0x77, 0x77, 0x78, 0x79, 0x7A, 0x7A, 0x7B, 0x7C, 0x7D, 0x7D, 0x7E, 0x7F, 0x80, 0x80, 0x81, 0x82, 0x83, 0x83, 0x84, 0x85, 0x86, 0x86, 0x87, 0x88, 0x89, 0x89, 0x8A, 0x8B, 0x8C, 0x8C, 0x8D, 0x8E, 0x8F, 0x8F, 0x90, 0x91, 0x92, 0x92, 0x93, 0xE9, 0xEA, 0xEA, 0xEB, 0xEC, 0xED, 0xED, 0xEE, 0xEF, 0xF0, 0xF0, 0xF1, 0xF2, 0xF3, 0xF3, 0xF4, 0xF5, 0xF6, 0xF6, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFB, 0xFC, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF }; [StateRequired] byte horizontalScrollLatched, verticalScrollLatched; const byte screenUsageBgLowPriority = screenUsageBackground; const byte screenUsageBgHighPriority = (1 << 2); public int ScreenHeight => screenHeight; public int CurrentScanline => currentScanline; public SegaSMSVDP() : base() { registers = new byte[0x0B]; cram = new byte[0x20]; spriteBuffer = new (int Number, int Y, int X, int Pattern, int Attribute)[NumActiveScanlinesHigh][]; for (int i = 0; i < spriteBuffer.Length; i++) spriteBuffer[i] = new (int Number, int Y, int X, int Pattern, int Attribute)[NumSpritesPerLineMode4]; } public override void Reset() { base.Reset(); WriteRegister(0x00, 0x36); WriteRegister(0x01, 0x80); WriteRegister(0x02, 0xFF); WriteRegister(0x03, 0xFF); WriteRegister(0x04, 0xFF); WriteRegister(0x05, 0xFF); WriteRegister(0x06, 0xFB); WriteRegister(0x07, 0x00); WriteRegister(0x08, 0x00); WriteRegister(0x09, 0x00); WriteRegister(0x0A, 0xFF); for (int i = 0; i < cram.Length; i++) cram[i] = 0; vCounter = hCounter = 0; lineInterruptCounter = registers[0x0A]; isLineInterruptPending = false; horizontalScrollLatched = verticalScrollLatched = 0; UpdateResolution(); } public override void SetRevision(int rev) { VDPTypes type = (VDPTypes)rev; Debug.Assert(Enum.IsDefined(typeof(VDPTypes), type), "Invalid revision", "{0} revision is invalid; only rev 0 (MK3/SMS1) or 1 (SMS2/GG) is valid", GetType().FullName); vdpType = type; } protected override void ReconfigureTimings() { /* Calculate cycles/line */ clockCyclesPerLine = (int)Math.Round((clockRate / refreshRate) / numTotalScanlines); /* Create arrays */ screenUsage = new byte[numVisiblePixels * numVisibleScanlines]; outputFramebuffer = new byte[(numVisiblePixels * numVisibleScanlines) * 4]; /* Update resolution/display timing */ UpdateResolution(); } public override void Step(int clockCyclesInStep) { InterruptLine = (((isFrameInterruptEnabled && isFrameInterruptPending) || (isLineInterruptEnabled && isLineInterruptPending)) ? InterruptState.Assert : InterruptState.Clear); cycleCount += clockCyclesInStep; hCounter = hCounterTable[(int)Math.Round((cycleCount + 578) / 3.0) % hCounterTable.Length]; if (cycleCount >= clockCyclesPerLine) { OnEndOfScanline(EventArgs.Empty); horizontalScrollLatched = registers[0x08]; if (currentScanline == scanlineActiveDisplay) verticalScrollLatched = registers[0x09]; CheckSpriteOverflow(currentScanline); RenderLine(currentScanline); if (currentScanline >= scanlineActiveDisplay && currentScanline <= scanlineBottomBorder) { lineInterruptCounter--; if (lineInterruptCounter < 0) { lineInterruptCounter = registers[0x0A]; isLineInterruptPending = true; } } else lineInterruptCounter = registers[0x0A]; if (currentScanline == (scanlineBottomBorder + 1)) isFrameInterruptPending = true; vCounter = vCounterTables[vCounterTableIndex][currentScanline]; currentScanline++; if (currentScanline == numTotalScanlines) { currentScanline = 0; ClearScreenUsage(); PrepareRenderScreen(); } ParseSpriteTable(currentScanline); cycleCount -= clockCyclesPerLine; if (cycleCount <= -clockCyclesPerLine) cycleCount = 0; } } protected override void PrepareRenderScreen() { OnRenderScreen(new RenderScreenEventArgs(numVisiblePixels, numVisibleScanlines, outputFramebuffer.Clone() as byte[])); } protected override byte ReadVram(ushort address) { return vram[address & vramMask16k]; } protected override void WriteVram(ushort address, byte value) { vram[address & vramMask16k] = value; } protected override void RenderLine(int y) { if (y >= scanlineTopBorder && y < scanlineActiveDisplay) { if (layerBordersForceEnable) SetLine(y, 1, backgroundColor); else SetLine(y, 0x00, 0x00, 0x00); } else if (y >= scanlineActiveDisplay && y < scanlineBottomBorder) { if (layerBackgroundForceEnable) { if (isBitM4Set) RenderLineMode4Background(y); else if (isModeGraphics1) RenderLineGraphics1Background(y); else if (isModeGraphics2) RenderLineGraphics2Background(y); else if (isModeMulticolor) RenderLineMulticolorBackground(y); else if (isModeText) RenderLineTextBackground(y); } else SetLine(y, 0x00, 0x00, 0x00); if (layerSpritesForceEnable) { if (isBitM4Set) RenderLineMode4Sprites(y); else if (!isModeText && !isBitM4Set) RenderLineSprites(y); } RenderBorders(y); } else if (y >= scanlineBottomBorder && y < numVisibleScanlines) { if (layerBordersForceEnable) SetLine(y, 1, backgroundColor); else SetLine(y, 0x00, 0x00, 0x00); } } protected override void RenderBorders(int y) { for (int x = pixelLeftBorder; x < pixelActiveDisplay; x++) { if (layerBordersForceEnable) SetPixel(y, x, 1, backgroundColor); else SetPixel(y, x, 0x00, 0x00, 0x00); } for (int x = pixelRightBorder; x < numVisiblePixels; x++) { if (layerBordersForceEnable) SetPixel(y, x, 1, backgroundColor); else SetPixel(y, x, 0x00, 0x00, 0x00); } } protected void SetLine(int y, int palette, int color) { for (int x = 0; x < numVisiblePixels; x++) SetPixel(y, x, palette, color); } protected virtual void SetPixel(int y, int x, int palette, int color) { WriteColorToFramebuffer(palette, color, ((y * numVisiblePixels) + (x % numVisiblePixels)) * 4); } private void RenderLineMode4Background(int y) { /* Determine coordinates in active display */ int activeDisplayY = (y - scanlineActiveDisplay); /* Determine H scrolling parameters */ int currentHorizontalScroll = ((isHScrollPartiallyDisabled && activeDisplayY < 16) ? 0 : horizontalScrollLatched); int horizontalScrollCoarse = (currentHorizontalScroll >> 3); int horizontalScrollFine = (currentHorizontalScroll & 0x07); ushort currentNametableBaseAddress = nametableBaseAddress; bool currentIsVScrollPartiallyDisabled = isVScrollPartiallyDisabled; bool currentIsColumn0MaskEnabled = isColumn0MaskEnabled; for (int x = 0; x < numTotalPixelsPerScanline; x++) { int activeDisplayX = (x - pixelActiveDisplay); if (activeDisplayX < 0 || activeDisplayX >= NumActivePixelsPerScanline) continue; /* Determine V scrolling parameters */ int currentVerticalScroll = ((currentIsVScrollPartiallyDisabled && activeDisplayX >= 192) ? 0 : verticalScrollLatched); int verticalScrollCoarse = (currentVerticalScroll >> 3); int verticalScrollFine = (currentVerticalScroll & 0x07); /* Calculate current scrolled column and row */ int numColumns = 32; int currentColumn = (((activeDisplayX - horizontalScrollFine) / 8) - horizontalScrollCoarse) & (numColumns - 1); int currentRow = (((activeDisplayY + verticalScrollFine) / 8) + verticalScrollCoarse) % (nametableHeight / 8); /* VDPDIFF: Mk3/SMS1 VDP only, adjust current row according to mask bit; http://www.smspower.org/Development/TilemapMirroring * NOTE: Emulating this breaks 224/240-line mode games (ex. Fantastic Dizzy, Cosmic Spacehead, Micro Machines)? */ if (vdpType == VDPTypes.Mk3SMS1) currentRow &= (((registers[0x02] & 0x01) << 4) | 0x0F); /* Fetch data from nametable & extract properties */ ushort nametableAddress = (ushort)(currentNametableBaseAddress + (currentRow * (numColumns * 2)) + (currentColumn * 2)); ushort nametableData = (ushort)(ReadVram((ushort)(nametableAddress + 1)) << 8 | ReadVram(nametableAddress)); int tileIndex = (nametableData & 0x01FF); bool hFlip = ((nametableData & 0x0200) == 0x0200); bool vFlip = ((nametableData & 0x0400) == 0x0400); int palette = (((nametableData & 0x0800) >> 11) & 0x0001); bool priority = ((nametableData & 0x1000) == 0x1000); /* Fetch pixel data for current pixel line */ int hPixel = ((activeDisplayX - horizontalScrollFine) % 8); hPixel = (hFlip ? hPixel : (7 - hPixel)); int vPixel = ((activeDisplayY + verticalScrollFine) % 8); vPixel = (!vFlip ? vPixel : (7 - vPixel)); ushort tileAddress = (ushort)((tileIndex << 5) + (vPixel << 2)); int c = (((ReadVram((ushort)(tileAddress + 0)) >> hPixel) & 0x01) << 0); c |= (((ReadVram((ushort)(tileAddress + 1)) >> hPixel) & 0x01) << 1); c |= (((ReadVram((ushort)(tileAddress + 2)) >> hPixel) & 0x01) << 2); c |= (((ReadVram((ushort)(tileAddress + 3)) >> hPixel) & 0x01) << 3); /* Record screen usage, write to framebuffer */ if (GetScreenUsageFlag(y, x) == screenUsageEmpty) { if ((currentIsColumn0MaskEnabled && (activeDisplayX / 8) == 0) || isDisplayBlanked) SetPixel(y, x, 1, backgroundColor); else SetPixel(y, x, palette, c); SetScreenUsageFlag(y, x, (c != 0 && priority) ? screenUsageBgHighPriority : screenUsageBgLowPriority); } } } protected override void CheckSpriteOverflow(int y) { if (!isBitM4Set) { /* Not in Master System video mode */ base.CheckSpriteOverflow(y); } else { /* Ensure current scanline is within active display */ if (y >= scanlineActiveDisplay && y < scanlineBottomBorder) { int activeDisplayY = (y - scanlineActiveDisplay); /* If last sprite in buffer is valid, sprite overflow occured */ int lastSpriteInBuffer = spriteBuffer[activeDisplayY][NumSpritesPerLineMode4 - 1].Number; if (lastSpriteInBuffer != -1) { isSpriteOverflow = true; /* Store sprite number in status register */ /* NOTE: the last illegal sprite is *technically* only stored here in TMS99xxA modes, but still emulating it in Mode 4 should be fine */ WriteSpriteNumberToStatus(lastSpriteInBuffer); } } } } protected override void ParseSpriteTable(int y) { if (!isBitM4Set) { /* Not in Master System video mode */ base.ParseSpriteTable(y); } else { if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return; /* Determine coordinates in active display */ int activeDisplayY = (y - scanlineActiveDisplay); /* Clear sprite list for current line */ for (int i = 0; i < spriteBuffer[activeDisplayY].Length; i++) spriteBuffer[activeDisplayY][i] = (-1, 0, 0, 0, 0); /* Determine sprite size & get zoomed sprites adjustment */ int zoomShift = (isZoomedSprites ? 1 : 0); int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift); int numValidSprites = 0; for (int sprite = 0; sprite < NumSpritesMode4; sprite++) { int yCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + sprite)); /* Ignore following if Y coord is 208 in 192-line mode */ if (yCoordinate == 208 && screenHeight == NumActiveScanlinesLow) { /* Store first "illegal sprite" number in status register */ WriteSpriteNumberToStatus(sprite); return; } /* Modify Y coord as needed */ yCoordinate++; if (yCoordinate > screenHeight + 32) yCoordinate -= 256; /* Ignore this sprite if on incorrect lines */ if (activeDisplayY < yCoordinate || activeDisplayY >= (yCoordinate + spriteHeight)) continue; /* Check if maximum number of sprites per line is reached */ numValidSprites++; if (numValidSprites > NumSpritesPerLineMode4) return; /* Mark sprite for rendering */ int xCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x80 + (sprite * 2))); int patternNumber = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x80 + (sprite * 2) + 1)); int unusedData = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x40 + (sprite * 2))); spriteBuffer[activeDisplayY][numValidSprites - 1] = (sprite, yCoordinate, xCoordinate, patternNumber, unusedData); } /* Because we didn't bow out before already, store total number of sprites in status register */ WriteSpriteNumberToStatus(NumSprites - 1); } } private void RenderLineMode4Sprites(int y) { if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return; /* Determine coordinates in active display */ int activeDisplayY = (y - scanlineActiveDisplay); /* Determine sprite size & get zoomed sprites adjustment */ int zoomShift = (isZoomedSprites ? 1 : 0); int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift); for (int s = 0; s < spriteBuffer[activeDisplayY].Length; s++) { var sprite = spriteBuffer[activeDisplayY][s]; if (sprite.Number == -1) continue; /* VDPDIFF: Mk3/SMS1 VDP zoomed sprites bug, only first four sprites (same as max sprites/line on TMS99xxA) can be zoomed horizontally * Zoom works normally on SMS2/GG */ int spriteWidth = (8 << zoomShift); if (vdpType == VDPTypes.Mk3SMS1 && s >= NumSpritesPerLine) spriteWidth = 8; if (!isDisplayBlanked) { int yCoordinate = sprite.Y; int xCoordinate = sprite.X; int patternNumber = sprite.Pattern; int unusedData = sprite.Attribute; if (isSpriteShiftLeft8) xCoordinate -= 8; for (int pixel = 0; pixel < spriteWidth; pixel++) { /* Ignore pixel if column 0 masking is enabled and sprite pixel is in column 0 */ if (isColumn0MaskEnabled && (xCoordinate + pixel) < 8) continue; /* Check if sprite is outside active display, else continue to next sprite */ if ((xCoordinate + pixel) < 0 || (xCoordinate + pixel) >= NumActivePixelsPerScanline) continue; /* Determine coordinate inside sprite */ int inSpriteXCoord = (pixel >> zoomShift) % spriteWidth; int inSpriteYCoord = ((activeDisplayY - yCoordinate) >> zoomShift) % spriteHeight; /* Calculate address and fetch pixel data */ int tileIndex = patternNumber; if (isLargeSprites) tileIndex &= ~0x01; ushort tileAddress = (ushort)(spritePatternGenBaseAddress + (tileIndex << 5) + (inSpriteYCoord << 2)); /* Get color & check transparency and position */ int c = (((ReadVram((ushort)(tileAddress + 0)) >> (7 - inSpriteXCoord)) & 0x1) << 0); c |= (((ReadVram((ushort)(tileAddress + 1)) >> (7 - inSpriteXCoord)) & 0x1) << 1); c |= (((ReadVram((ushort)(tileAddress + 2)) >> (7 - inSpriteXCoord)) & 0x1) << 2); c |= (((ReadVram((ushort)(tileAddress + 3)) >> (7 - inSpriteXCoord)) & 0x1) << 3); if (c == 0) continue; int x = pixelActiveDisplay + (xCoordinate + pixel); if (IsScreenUsageFlagSet(y, x, screenUsageSprite)) { /* If sprite was already at this location, set sprite collision flag */ isSpriteCollision = true; } else if (!IsScreenUsageFlagSet(y, x, screenUsageBgHighPriority)) { /* Draw if pixel isn't occupied by high-priority BG */ SetPixel(y, x, 1, c); } /* Note that there is a sprite here regardless */ /* VDPDIFF: Mk3 / SMS1 VDP zoomed sprites bug, horizontally zoomed area of sprite (i.e. pixels 9-16) is ignored by collision; do not mark location as containing a sprite * https://www.smspower.org/forums/post109677#109677 * TODO: verify behavior somehow? */ if ((vdpType == VDPTypes.Mk3SMS1 && isZoomedSprites && pixel < 8) || (vdpType == VDPTypes.Mk3SMS1 && !isZoomedSprites) || vdpType == VDPTypes.SMS2GG) SetScreenUsageFlag(y, x, screenUsageSprite); } } } } protected void UpdateResolution() { /* Check screenmode */ if (isSMS240LineMode) { screenHeight = NumActiveScanlinesHigh; nametableHeight = 256; vCounterTableIndex = (isPalChip ? 5 : 2); } else if (isSMS224LineMode) { screenHeight = NumActiveScanlinesMed; nametableHeight = 256; vCounterTableIndex = (isPalChip ? 4 : 1); } else { screenHeight = NumActiveScanlinesLow; nametableHeight = 224; vCounterTableIndex = (isPalChip ? 3 : 0); } /* Scanline parameters */ if (!isPalChip) { /* NTSC */ if (screenHeight == NumActiveScanlinesHigh) { /* 240 active lines, invalid on NTSC (dummy values); "Line Interrupt Test #1" mode 1 will show blue screen */ topBorderSize = 0; verticalActiveDisplaySize = 0; bottomBorderSize = 0; } else if (screenHeight == NumActiveScanlinesMed) { /* 224 active lines */ topBorderSize = 11; verticalActiveDisplaySize = 224; bottomBorderSize = 8; } else { /* 192 active lines */ topBorderSize = 27; verticalActiveDisplaySize = 192; bottomBorderSize = 24; } } else { /* PAL */ if (screenHeight == NumActiveScanlinesHigh) { /* 240 active lines */ topBorderSize = 30; verticalActiveDisplaySize = 240; bottomBorderSize = 24; } else if (screenHeight == NumActiveScanlinesMed) { /* 224 active lines */ topBorderSize = 38; verticalActiveDisplaySize = 224; bottomBorderSize = 32; } else { /* 192 active lines */ topBorderSize = 54; verticalActiveDisplaySize = 192; bottomBorderSize = 48; } } scanlineTopBorder = 0; scanlineActiveDisplay = (scanlineTopBorder + topBorderSize); scanlineBottomBorder = (scanlineActiveDisplay + verticalActiveDisplaySize); numVisibleScanlines = (topBorderSize + verticalActiveDisplaySize + bottomBorderSize); /* Pixel parameters */ leftBorderSize = 13; horizontalActiveDisplaySize = 256; rightBorderSize = 15; pixelLeftBorder = 0; pixelActiveDisplay = (pixelLeftBorder + leftBorderSize); pixelRightBorder = (pixelActiveDisplay + horizontalActiveDisplaySize); numVisiblePixels = (leftBorderSize + horizontalActiveDisplaySize + rightBorderSize); } protected virtual void WriteColorToFramebuffer(int palette, int color, int address) { WriteColorToFramebuffer(cram[((palette * 16) + color)], address); } protected override void WriteColorToFramebuffer(ushort colorValue, int address) { /* If not in Master System video mode, color value is index into legacy colormap */ if (!isBitM4Set) colorValue = (legacyColorMap[colorValue & 0x000F]); RGB222toBGRA8888(colorValue, ref outputFramebuffer, address); } protected override void WriteDataPort(byte value) { isSecondControlWrite = false; readBuffer = value; switch (codeRegister) { case 0x00: case 0x01: case 0x02: WriteVram(addressRegister, value); break; case 0x03: cram[(addressRegister & 0x001F)] = value; break; } addressRegister++; } protected override byte ReadControlPort() { byte statusCurrent = (byte)statusFlags; statusFlags = StatusFlags.None; isSecondControlWrite = false; isLineInterruptPending = false; InterruptLine = InterruptState.Clear; return statusCurrent; } public override byte ReadPort(byte port) { if ((port & 0x40) == 0x40) if ((port & 0x01) == 0) return (byte)vCounter; /* V counter */ else return (byte)hCounter; /* H counter */ else return base.ReadPort(port); } protected override void WriteRegister(byte register, byte value) { if (register < registers.Length) registers[register] = value; if (register == 0x00 || register == 0x01) UpdateResolution(); } } }