Exemple #1
0
        public override void StepVideo(int scanLine, int cycle, bool showBackground, bool showSprites)
        {
            ppuRendering = scanLine >= 0 && scanLine < 240 && (showBackground || showSprites);

            if (cycle != 0)
            {
                return;
            }

            if (ppuRendering)
            {
                if (!inFrame)
                {
                    inFrame    = true;
                    irqCounter = 0;
                    CancelInterruptRequest?.Invoke();
                }
                else
                {
                    ++irqCounter;
                    if (irqCounter == irqLatch)
                    {
                        irqPending = true;
                        if (irqEnabled)
                        {
                            TriggerInterruptRequest?.Invoke();
                        }
                    }
                }
            }
            else
            {
                inFrame = false;
            }
        }
 private void AcknowledgeInterrupt()
 {
     if (irqTriggered)
     {
         CancelInterruptRequest?.Invoke();
         irqTriggered = false;
     }
 }
 protected void WriteIrqAcknowledge()
 {
     if (irqTriggered)
     {
         CancelInterruptRequest?.Invoke();
     }
     irqEnable    = irqEnableOnAcknowledge;
     irqTriggered = false;
 }
        public override byte this[ushort address]
        {
            set
            {
                if (address == 0x8000)
                {
                    // ignore mirror mode set by register $8000 for TC0190
                    MirrorMode oldMirrorMode = MirrorMode;
                    base[address] = value;
                    MirrorMode = oldMirrorMode;
                }
                else if (address == 0xC000)
                {
                    // irq latch - reload value
                    irqReload = value;
                    irqReload ^= 0xFF;
                }
                else if (address == 0xC001)
                {
                    // irq reload - set irq counter to reload value
                    irqReloadPrimed = true;
                }
                else if (address == 0xC002)
                {
                    // enable IRQ
                    irqEnabled = true;
                }
                else if (address == 0xC003)
                {
                    // disable IRQ
                    irqEnabled = false;
                    CancelInterruptRequest?.Invoke();
                }
                else if (address == 0xE000)
                {
                    // $E000: [.M.. ....]   Mirroring: 0 = Vert, 1 = Horz
                    MirrorMode = (value & Bin.Bit6) != 0 ? MirrorMode.Horizontal : MirrorMode.Vertical;
                }
                else
                {
                    // default Taito TC0190 behaviour (mapper 33)
                    base[address] = value;
                }

            }
        }
Exemple #5
0
        public override byte this[ushort address]
        {
            get
            {
                if (address < 0x2000)
                {
                    int bank   = address / 0x400;
                    int offset = address % 0x400;
                    return(Cartridge.CharacterRom[characterRomBank[bank] * 0x400 + offset]);
                }
                else if (address >= 0x8000)
                {
                    int bank   = (address - 0x8000) / 0x2000;
                    int offset = address % 0x2000;
                    return(Cartridge.ProgramRom[programRomBank[bank] * 0x2000 + offset]);
                }
                else
                {
                    return((byte)(address >> 8)); // assuming open bus
                }
            }

            set
            {
                if (address >= 0x8000 && address < 0xA000)
                {
                    int offset1000 = address % 0x1000;
                    if (offset1000 >= 4)
                    {
                        return;
                    }

                    int bankIndex1000 = (address - 0x8000) / 0x1000;

                    int programBankIndex = bankIndex1000 * 2 + offset1000 / 2;

                    // last bank must remain fixed
                    if (programBankIndex > 2)
                    {
                        return;
                    }

                    int oldProgramBank = programRomBank[programBankIndex];

                    if (offset1000 % 2 == 0)
                    {
                        programRomBank[programBankIndex] = SetLowerNybble(programRomBank[programBankIndex], value);
                    }
                    else
                    {
                        programRomBank[programBankIndex] = SetHigherNybble(programRomBank[programBankIndex], value);
                    }


                    if (programRomBank[programBankIndex] != oldProgramBank)
                    {
                        Debug.WriteLine("Program Bank " + programBankIndex + " (" + Hex.Format(address) + ") = " + programRomBank[programBankIndex]);
                        ProgramBankSwitch?.Invoke((ushort)(0x8000 + programBankIndex * 0x2000), 0x2000);
                    }
                }
                else if (address >= 0xA000 && address < 0xE000)
                {
                    int offset1000 = address % 0x1000;
                    if (offset1000 >= 4)
                    {
                        return;
                    }

                    int bankIndex1000 = (address - 0xA000) / 0x1000;

                    int characterBankIndex = bankIndex1000 * 2 + offset1000 / 2;

                    int oldCharacterBank = characterRomBank[characterBankIndex];

                    if (offset1000 % 2 == 0)
                    {
                        characterRomBank[characterBankIndex] = SetLowerNybble(characterRomBank[characterBankIndex], value);
                    }
                    else
                    {
                        characterRomBank[characterBankIndex] = SetHigherNybble(characterRomBank[characterBankIndex], value);
                    }

                    if (characterRomBank[characterBankIndex] != oldCharacterBank)
                    {
                        Debug.WriteLine("Character Bank " + characterBankIndex + " (" + Hex.Format(address) + ") = " + characterRomBank[characterBankIndex]);
                        CharacterBankSwitch?.Invoke((ushort)(characterBankIndex * 0x400), 0x400);
                    }
                }
                else if (address == 0xE000)
                {
                    // bits 0..3 of IRQ counter reload
                    irqReload &= 0xFFF0;
                    irqReload |= (ushort)(value & 0x0F);
                }
                else if (address == 0xE001)
                {
                    // bits 4..7 of IRQ counter reload
                    irqReload &= 0xFF0F;
                    irqReload |= (ushort)((value & 0x0F) << 4);
                }
                else if (address == 0xE002)
                {
                    // bits 8..11 of IRQ counter reload
                    irqReload &= 0xF0FF;
                    irqReload |= (ushort)((value & 0x0F) << 8);
                }
                else if (address == 0xE003)
                {
                    // bits 12..15 of IRQ counter reload
                    irqReload &= 0x0FFF;
                    irqReload |= (ushort)((value & 0x0F) << 12);
                }
                else if (address == 0xF000)
                {
                    irqCounter = irqReload;
                    Debug.WriteLine("IRQ Reloaded to " + irqReload);
                    CancelInterruptRequest?.Invoke();
                    irqPrimed = false;
                }
                else if (address == 0xF001)
                {
                    irqCounterEnabled = (value & 0x01) != 0;

                    int counterBits = (value >> 1) & 0x07;

                    switch (counterBits)
                    {
                    case 0: irqFixedMask = 0x0000; break;

                    case 1: irqFixedMask = 0xF000; break;

                    case 2:
                    case 3: irqFixedMask = 0xFF00; break;

                    default: irqFixedMask = 0xFFF0; break;
                    }
                    irqCounterMask = 0xFFFF - irqFixedMask; // complement

                    CancelInterruptRequest?.Invoke();
                    irqPrimed = false;
                }
                else if (address == 0xF002)
                {
                    // mirror mode
                    switch (value & 0x03)
                    {
                    case 0x00: MirrorMode = MirrorMode.Horizontal; break;

                    case 0x01: MirrorMode = MirrorMode.Vertical; break;

                    case 0x02: MirrorMode = MirrorMode.Single0; break;

                    case 0x03: MirrorMode = MirrorMode.Single1; break;
                    }
                }
            }
        }
Exemple #6
0
        public override byte this[ushort address]
        {
            get
            {
                int bankOffset = address % characterBankSize;

                // character ROM
                if (address < 0x2000)
                {
                    // handle extended mode - bank registers from extended ram page
                    if (extendedRamMode == 1 && !AccessingSpriteCharacters)
                    {
                        bankOffset = address % 0x1000;

                        int extendedCharacterBank = extendedRam[lastTileIndex];
                        extendedCharacterBank &= 0x3F;
                        extendedCharacterBank |= (characterBankUpper >> 2);
                        return(Cartridge.CharacterRom[extendedCharacterBank * 0x1000 + bankOffset]);
                    }


                    // handles all CHR modes

                    // bank range CHR modes 8K: [0], 4K: [0, 1] 2K: [0..3], 1k: [0..7]
                    int bankRange = address / characterBankSize;

                    // determine stride between bank registers, given CHR mode: 8K: 8, 4K: 4, 2K: 2, 1K: 1
                    int indexStride = characterBankSize / 0x0400;

                    // determine bank index: 8K: [7], 4k: [3, 7], 2K: [1, 3, 5, 7], 8K: [0..7]
                    int bankIndex = (bankRange + 1) * indexStride - 1;

                    // if sprite mode is 8x16 and chr access if for background, use upper
                    // bank switching register indexes  8K: [11], 4k: [11], 2K: [9, 11], 8K: [8..11]
                    // or
                    // sprite mode is 8x8 and last register banks written are upper
                    if ((SpriteSize == Video.SpriteSize.Size8x16 && !AccessingSpriteCharacters) ||
                        (SpriteSize == Video.SpriteSize.Size8x8 && characterBankLastWrittenUpper))
                    {
                        bankIndex %= 4;
                        bankIndex += 8;
                    }

                    int characterBank = characterBanks[bankIndex];
                    int addressBase   = characterBank * characterBankSize;
                    return(Cartridge.CharacterRom[addressBase + bankOffset]);
                }

                if (address >= 0x5000 && address <= 0x5007)
                {
                    // TODO: pulse generators 1 and 2
                    return(0x00);
                }
                if (address == 0x5010 || address == 0x5011 || address == 0x5015)
                {
                    // TODO: irq, pcm, status
                    return(0x00);
                }

                // read-only IRQ counter - return open bus?
                if (address == 0x5203)
                {
                    return(0x52);
                }

                if (address == 0x5204)
                {
                    byte result = 0;
                    if (irqPending)
                    {
                        result |= 0x80;
                    }
                    if (inFrame)
                    {
                        result |= 0x40;
                    }
                    irqPending = false;
                    CancelInterruptRequest?.Invoke();
                    return(result);
                }

                if (address == 0x5205)
                {
                    return(productLow);
                }

                if (address == 0x5206)
                {
                    return(productHigh);
                }

                if (address >= 0x5C00 && address < 0x6000)
                {
                    // expansion ram - all modes
                    switch (extendedRamMode)
                    {
                    case 0:
                    case 1:
                        // expansion ram mode 0/1 - returns open bus
                        return((byte)(address >> 8));

                    case 2:
                    // expansion ram mode 2 - 1K r/w memory
                    case 3:
                        // expansion ram mode 3 - 1K ROM
                        return(extendedRam[address % 0x400]);

                    default:
                        throw new Exception("MMC5 Invalid expansion ram mode");
                    }
                }

                if (address >= 0x6000 && address < 0x8000)
                {
                    // all bank modes - 8K switchable RAM bank
                    int offset = address % 0x2000;
                    return(programRam[programRamBank * 0x2000 + offset]);
                }

                if (address >= 0x8000)
                {
                    // program banks for all modes
                    switch (programBankMode)
                    {
                    case 0:
                    {
                        // PRG mode 0 - single 32k switchable ROM bank
                        int offset = address % 0x8000;
                        return(Cartridge.ProgramRom[(programRomBank >> 2) * 0x8000 + offset]);
                    }

                    case 1:
                        if (address < 0xC000)
                        {
                            // PRG mode 1 - first 16k switchable ROM/RAM bank
                            int offset      = address % 0x4000;
                            int flatAddress = (programBank1 >> 1) * 0x4000 + offset;
                            if (romMode1)
                            {
                                return(Cartridge.ProgramRom[flatAddress]);
                            }
                            else
                            {
                                return(programRam[flatAddress]);
                            }
                        }
                        else     // if (address >= 0xC000)
                        {
                            // PRG mode 1 - second 16k switchable ROM bank
                            int offset = address % 0x4000;
                            return(Cartridge.ProgramRom[programRomBank * 0x4000 + offset]);
                        }

                    case 2:
                        if (address < 0xC000)
                        {
                            // PRG mode 2 - 16k switchable ROM/RAM bank
                            int offset      = address % 0x4000;
                            int flatAddress = (programBank1 >> 1) * 0x4000 + offset;
                            if (romMode1)
                            {
                                return(Cartridge.ProgramRom[flatAddress]);
                            }
                            else
                            {
                                return(programRam[flatAddress]);
                            }
                        }
                        else if (address < 0xE000)
                        {
                            // PRG mode 2 - first 8k switchable ROM/RAM bank
                            int offset      = address % 0x2000;
                            int flatAddress = programBank2 * 0x2000 + offset;
                            if (romMode2)
                            {
                                return(Cartridge.ProgramRom[flatAddress]);
                            }
                            else
                            {
                                return(programRam[flatAddress]);
                            }
                        }
                        else     // if (address >= 0xE000 )
                        {
                            // PRG mode 2 - second 8k switchable ROM bank
                            int offset = address % 0x2000;
                            return(Cartridge.ProgramRom[programRomBank * 0x2000 + offset]);
                        }

                    case 3:
                        if (address < 0xA000)
                        {
                            // PRG mode 3 - first 8k switchable ROM/RAM bank
                            int offset      = address % 0x2000;
                            int flatAddress = programBank0 * 0x2000 + offset;
                            if (romMode0)
                            {
                                return(Cartridge.ProgramRom[flatAddress]);
                            }
                            else
                            {
                                return(programRam[flatAddress]);
                            }
                        }
                        else if (address < 0xC000)
                        {
                            // PRG mode 3 - second 8k switchable ROM/RAM bank
                            int offset      = address % 0x2000;
                            int flatAddress = programBank1 * 0x2000 + offset;
                            if (romMode1)
                            {
                                return(Cartridge.ProgramRom[flatAddress]);
                            }
                            else
                            {
                                return(programRam[flatAddress]);
                            }
                        }
                        else if (address < 0xE000)
                        {
                            // PRG mode 3 - third 8k switchable ROM/RAM bank
                            int offset      = address % 0x2000;
                            int flatAddress = programBank2 * 0x2000 + offset;
                            if (romMode2)
                            {
                                return(Cartridge.ProgramRom[flatAddress]);
                            }
                            else
                            {
                                return(programRam[flatAddress]);
                            }
                        }
                        else     // if (address >= 0xE000)
                        {
                            // PRG mode 3 - fourth 8k switchable ROM bank
                            int offset = address % 0x2000;
                            return(Cartridge.ProgramRom[programRomBank * 0x2000 + offset]);
                        }

                    default:
                        throw new Exception("MMC5 Invalid program bank mode");
                    }
                }

                // invalid / unhandled addresses
                //throw new Exception("Unhandled " + Name + " mapper read at address: " + Hex.Format(address));
                // return open bus for unhandled read
                return((byte)(address >> 8));
            }

            set
            {
                // character ROM
                if (address < 0x2000)
                {
                    // handles all CHR modes

                    // bank range CHR modes 8K: [0], 4K: [0, 1] 2K: [0..3], 1k: [0..7]
                    int bankRange = address / characterBankSize;

                    // determine stride between bank registers, given CHR mode: 8K: 8, 4K: 4, 2K: 2, 1K: 1
                    int indexStride = characterBankSize / 0x0400;

                    // determine bank index: 8K: [7], 4k: [3, 7], 2K: [1, 3, 5, 7], 8K: [0..7]
                    int bankIndex = (bankRange + 1) * indexStride - 1;

                    // if sprite mode is 8x16 and chr access if for background, use upper
                    // bank switching register indexes  8K: [11], 4k: [11], 2K: [9, 11], 8K: [8..11]
                    // or
                    // sprite mode is 8x8 and last register banks written are upper
                    if ((SpriteSize == Video.SpriteSize.Size8x16 && !AccessingSpriteCharacters) ||
                        (SpriteSize == Video.SpriteSize.Size8x8 && characterBankLastWrittenUpper))
                    {
                        bankIndex %= 4;
                        bankIndex += 8;
                    }

                    int characterBank = characterBanks[bankIndex];
                    int addressBase   = characterBank * characterBankSize;
                    int bankOffset    = address % characterBankSize;
                    Cartridge.CharacterRom[addressBase + bankOffset] = value;
                    return;
                }

                // registers
                if (address >= 0x5000 && address <= 0x5007)
                {
                    // TODO: pulse generators 1 and 2
                    return;
                }
                if (address == 0x5010 || address == 0x5011 || address == 0x5015)
                {
                    // TODO: irq, pcm, status
                    return;
                }

                if (address == 0x5100)
                {
                    programBankMode = (byte)(value & 0x03);
                    Debug.WriteLine("MMC5 PRG Bank Mode ($5100) = " + Hex.Format(programBankMode));
                    return;
                }
                if (address == 0x5101)
                {
                    SetCharacterBankMode((byte)(value & 0x03));

                    Debug.WriteLine("MMC5 CHR Bank Mode ($5101) = " + Hex.Format(characterBankMode) + " (" + characterBankCount + " " + characterBankSize + "b Banks)");
                    return;
                }
                if (address == 0x5102)
                {
                    programRamProtect1 = value == 2;
                    programRamProtect  = programRamProtect1 && programRamProtect2;
                    Debug.WriteLine("MMC5 Ram Protect 1 ($5103) = " + (programRamProtect1 ? "Enabled" : "Disabled") + " (P1 && P2 = " + programRamProtect + ")");
                    return;
                }
                if (address == 0x5103)
                {
                    programRamProtect2 = value == 1;
                    programRamProtect  = programRamProtect1 && programRamProtect2;
                    Debug.WriteLine("MMC5 Ram Protect 2 ($5103) = " + (programRamProtect2 ? "Enabled" : "Disabled") + " (P1 && P2 = " + programRamProtect + ")");
                    return;
                }
                if (address == 0x5104)
                {
                    extendedRamMode = (byte)(value & 0x03);
                    Debug.WriteLine("MMC5 ExRamMode ($5104) = " + Hex.Format((byte)extendedRamMode));
                    return;
                }
                if (address == 0x5105)
                {
                    // Mirror mode - DD CC BB AA
                    MirrorMode = (MirrorMode)value;
                    Debug.WriteLine("MMC5 Mirror Mode ($5105) = " + MirrorMode + " (" + Hex.Format((byte)MirrorMode) + ") (" + Bin.Format((byte)MirrorMode) + ")");

                    return;
                }
                if (address == 0x5106)
                {
                    fillModeTile = value;
                    Debug.WriteLine("MMC5 Fill Mode Tile ($5106) = " + Hex.Format(fillModeTile));
                    return;
                }
                if (address == 0x5107)
                {
                    int attrbibutes = value & 0x03;
                    // replicate bits 0,1 to rest of byte ------AB -> ABABABAB
                    fillModeAttributes = (byte)(attrbibutes | (attrbibutes << 2) | (attrbibutes << 4) | (attrbibutes << 6));
                    Debug.WriteLine("MMC5 Fill Mode Attributes ($5107) = " + Hex.Format((byte)attrbibutes));
                    return;
                }
                if (address == 0x5113)
                {
                    //---- -CBB : C: chip, BB: bank within chip (CBB can be treated as 8 banks)
                    programRamBank = (byte)(value & 0x07);
                    Debug.WriteLine("MMC5 PRG RAM Bank ($5113) = " + Hex.Format(programRamBank));
                    return;
                }
                if (address == 0x5114)
                {
                    //RBBB BBBB : R - ROM mode, BBBBBBB - bank number
                    romMode0     = (value & 0x80) != 0;
                    programBank0 = (byte)(value & 0x7F);
                    Debug.WriteLine("MMC5 PRG ROM/RAM Bank 0 ($5114) = " + Hex.Format(programBank0) + " in " + (romMode0 ? "ROM" : "RAM") + " mode");
                    return;
                }
                if (address == 0x5115)
                {
                    //RBBB BBBB : R - ROM mode, BBBBBBB - bank number
                    romMode1     = (value & 0x80) != 0;
                    programBank1 = (byte)(value & 0x7F);
                    Debug.WriteLine("MMC5 PRG ROM/RAM Bank 1 ($5115) = " + Hex.Format(programBank1) + " in " + (romMode1 ? "ROM" : "RAM") + " mode");
                    return;
                }
                if (address == 0x5116)
                {
                    //RBBB BBBB : R - ROM mode, BBBBBBB - bank number
                    romMode2     = (value & 0x80) != 0;
                    programBank2 = (byte)(value & 0x7F);
                    Debug.WriteLine("MMC5 PRG ROM/RAM Bank 2 ($5116) = " + Hex.Format(programBank2) + " in " + (romMode2 ? "ROM" : "RAM") + " mode");
                    return;
                }
                if (address == 0x5117)
                {
                    //-BBB BBBB : BBBBBBB - bank number
                    programRomBank = (byte)(value & 0x7F);
                    Debug.WriteLine("MMC5 PRG ROM Bank Register ($5117) = " + Hex.Format(programRomBank));
                    return;
                }

                if (address >= 0x5120 && address <= 0x512B)
                {
                    if (characterBankCount == 0)
                    {
                        return;
                    }

                    characterBankLastWrittenUpper = address > 0x5127;

                    ushort characterBank = 0;
                    // merge low bits
                    characterBank |= value;
                    // merge high bits
                    characterBank |= characterBankUpper;
                    // ensure within available banks
                    characterBank %= characterBankCount;
                    // assign to corresponding bank switch
                    characterBanks[address - 0x5120] = characterBank;

                    Debug.WriteLine("MMC5 CHR Bank Register (" + Hex.Format(address) + ") = " + Hex.Format(value) + " (" + Hex.Format(characterBank) + ")");

                    return;
                }

                if (address == 0x5130)
                {
                    // upper 2 bits (bit 8, 9) for character bank selection (all banks)
                    characterBankUpper  = value;
                    characterBankUpper &= 0x03;
                    Debug.WriteLine("MMC5 Upper CHR Bits ($5130) = " + Bin.Format((byte)characterBankUpper));
                    characterBankUpper <<= 8;
                    return;
                }

                if (address == 0x5200)
                {
                    //ES-W WWWW
                    verticalSplitModeEnabled   = (value & 0x80) != 0;
                    verticalSplitSide          = (VerticalSplitSide)((value >> 6) & 0x01);
                    verticalSplitStartStopTile = (byte)(value & 0x1F);
                    return;
                }

                if (address == 0x5203)
                {
                    irqLatch = value;
                    Debug.WriteLine("MMC5 IRQ Counter($5203) = " + irqLatch);
                    return;
                }

                if (address == 0x5204)
                {
                    irqEnabled = (value & 0x80) != 0;
                    Debug.WriteLine("MMC5 IRQ ($5204) = " + (irqEnabled ? "Enabled" : "Disabled"));
                    return;
                }

                if (address == 0x5205)
                {
                    factor1 = value;
                    EvaluateProduct();
                    Debug.WriteLine("MMC5 Mul Factor 1 ($5205) = " + Hex.Format(factor1) + " (Result High: " + Hex.Format(productHigh) + ", Low: " + Hex.Format(productLow) + ")");
                    return;
                }
                if (address == 0x5206)
                {
                    factor2 = value;
                    EvaluateProduct();
                    Debug.WriteLine("MMC5 Mul Factor 2 ($5205) = " + Hex.Format(factor1) + " (Result High: " + Hex.Format(productHigh) + ", Low: " + Hex.Format(productLow) + ")");
                    return;
                }

                if (address >= 0x5C00 && address < 0x6000)
                {
                    // expansion ram - all modes
                    Debug.WriteLine("MMC5 Write to Extended RAM (" + Hex.Format(address) + ") = " + Hex.Format(value));

                    switch (extendedRamMode)
                    {
                    case 0:
                    case 1:
                        // expansion ram mode 0/1 - writes allowed when ppu rendering, otherwise zero written
                        extendedRam[address % 0x400] = ppuRendering ? value : (byte)0;
                        return;

                    case 2:
                        // expansion ram mode 2 - 1K r/w memory
                        extendedRam[address % 0x400] = value;
                        return;

                    case 3:
                        // expansion ram mode 3 - 1K ROM (read only - do nothing?)
                        return;

                    default:
                        throw new Exception("MMC5 Invalid expansion ram mode");
                    }
                }

                if (address >= 0x6000 && address < 0x8000)
                {
                    // all bank modes - 8K switchable RAM bank
                    int offset = address % 0x2000;
                    programRam[programRamBank * 0x2000 + offset] = value;
                    return;
                }

                if (address >= 0x8000)
                {
                    // program banks for all modes
                    switch (programBankMode)
                    {
                    case 0:
                    {
                        // PRG mode 0 - single 32k switchable ROM bank
                        return;
                    }

                    case 1:
                        if (address < 0xC000)
                        {
                            // PRG mode 1 - first 16k switchable ROM/RAM bank
                            if (!romMode1 && !programRamProtect)
                            {
                                int offset = address % 0x4000;
                                programRam[(programBank1 >> 1) * 0x4000 + offset] = value;
                            }
                            return;
                        }
                        else     // if (address >= 0xC000)
                        {
                            // PRG mode 1 - second 16k switchable ROM bank
                            return;
                        }

                    case 2:
                        if (address < 0xC000)
                        {
                            // PRG mode 2 - 16k switchable ROM/RAM bank
                            if (!romMode1 && !programRamProtect)
                            {
                                int offset = address % 0x4000;
                                programRam[(programBank1 >> 1) * 0x4000 + offset] = value;
                            }
                            return;
                        }
                        else if (address < 0xE000)
                        {
                            // PRG mode 2 - first 8k switchable ROM/RAM bank
                            if (!romMode2 && !programRamProtect)
                            {
                                int offset = address % 0x2000;
                                programRam[programBank2 * 0x2000 + offset] = value;
                            }
                            return;
                        }
                        else     // if (address >= 0xE000 )
                        {
                            // PRG mode 2 - second 8k switchable ROM bank
                            return;
                        }

                    case 3:
                        if (address < 0xA000)
                        {
                            // PRG mode 3 - first 8k switchable ROM/RAM bank
                            if (!romMode0 && !programRamProtect)
                            {
                                int offset = address % 0x2000;
                                programRam[programBank0 * 0x2000 + offset] = value;
                            }
                            return;
                        }
                        else if (address < 0xC000)
                        {
                            // PRG mode 3 - second 8k switchable ROM/RAM bank
                            if (!romMode1 && !programRamProtect)
                            {
                                int offset = address % 0x2000;
                                programRam[programBank1 * 0x2000 + offset] = value;
                            }
                            return;
                        }
                        else if (address < 0xE000)
                        {
                            // PRG mode 3 - third 8k switchable ROM/RAM bank
                            if (!romMode2 && !programRamProtect)
                            {
                                int offset = address % 0x2000;
                                programRam[programBank2 * 0x2000 + offset] = value;
                            }
                            return;
                        }
                        else     // if (address >= 0xE000)
                        {
                            // PRG mode 3 - fourth 8k switchable ROM bank
                            return;
                        }

                    default:
                        throw new Exception("MMC5 Invalid program bank mode");
                    }
                }

                // invalid / unhandled addresses
                throw new Exception("Unhandled " + Name + " mapper write at address: " + Hex.Format(address));
            }
        }
        public override byte this[ushort address]
        {
            get
            {
                if (address < 0x2000)
                {
                    if (characterBanksSupported)
                    {
                        // get data from corresponding 1K bank
                        int bankindex  = address / 0x400;
                        int bankOffset = address % 0x400;
                        return(Cartridge.CharacterRom[characterBank[bankindex] * 0x400 + bankOffset]);
                    }
                    else
                    {
                        // for Datach Joint ROM System - treat as flat CHR RAM
                        return(Cartridge.CharacterRom[address]);
                    }
                }

                if (address >= 0x6000 && address < 0x7FFF)
                {
                    // return SRAM or open bus depending on variant
                    return(saveRamSupported ? Cartridge.SaveRam[(ushort)(address - 0x6000)] : (byte)(address >> 8));
                }

                if (address >= 0x8000 && address < 0xC000)
                {
                    int bankOffset = address % 0x4000;
                    return(Cartridge.ProgramRom[outerProgramBank + programBank * 0x4000 + bankOffset]);
                }

                if (address >= 0xC000)
                {
                    // fixed at last bank
                    int lastBankOffset = address % 0x4000;
                    return(Cartridge.ProgramRom[outerProgramBank + lastProgramBankBase + lastBankOffset]);
                }

                //if (address >= 0x6000)
                //    return Cartridge.SaveRam[(ushort)(address - 0x6000)];

                //throw new Exception("Unhandled " + Name + " mapper read at address: " + Hex.Format(address));
                // return open bus?
                return((byte)(address >> 8));
            }

            set
            {
                if (address < 0x2000)
                {
                    if (characterBanksSupported)
                    {
                        // CHR bank switches for 8 0x400 ranges
                        // set CHR bank for corresponding 0x400 range
                        int bankindex  = address / 0x400;
                        int bankOffset = address % 0x400;
                        Cartridge.CharacterRom[characterBank[bankindex] * 0x400 + bankOffset] = value;
                    }
                    else
                    {
                        // for Datach Joint ROM System - treat as flat CHR RAM
                        Cartridge.CharacterRom[address] = value;
                    }
                }
                else if (saveRamSupported && address >= 0x6000 && address < 0x8000)
                {
                    Cartridge.SaveRam[(ushort)(address - 0x6000)] = value;
                }
                else if (address >= registerBase)
                {
                    // NOTE: FCG variants vary register base between $6000 and $8000
                    // variants lumped under mapper 16 require mirroring these bases to get most games to work
                    int registerAddress = address % 0x10;
                    if (outerProgramBankSupported && address >= 0x8000 && address < 0x8004)
                    {
                        int oldOuterProgramBank = outerProgramBank;
                        // outer program bank (mapper 153 only)
                        if ((value & 0x01) != 0)
                        {
                            outerProgramBank = 0x40000;
                        }
                        else
                        {
                            outerProgramBank = 0;
                        }

                        if (outerProgramBank != oldOuterProgramBank)
                        {
                            ProgramBankSwitch?.Invoke(0x8000, 0x8000);
                        }
                    }
                    else if (characterBanksSupported && registerAddress < 0x08)
                    {
                        // CHR bank switch
                        int oldCharacterBank = characterBank[registerAddress];
                        characterBank[registerAddress] = value;
                        if (value != oldCharacterBank)
                        {
                            CharacterBankSwitch?.Invoke((ushort)(registerAddress * 0x400), 0x400);
                        }
                    }
                    else if (registerAddress == 0x08)
                    {
                        // program bank switch
                        int oldProgramBank = programBank;
                        programBank  = value & 0x0F;
                        programBank %= programBankCount; // probably not needed, but anyhow

                        // invalidate address region
                        if (programBank != oldProgramBank)
                        {
                            ProgramBankSwitch?.Invoke(0x8000, 0x4000);
                        }
                    }
                    else if (registerAddress == 0x09)
                    {
                        // mirroring mode
                        switch (value & 0x03)
                        {
                        case 0: MirrorMode = MirrorMode.Vertical; break;

                        case 1: MirrorMode = MirrorMode.Horizontal; break;

                        case 2: MirrorMode = MirrorMode.Single0; break;

                        case 3: MirrorMode = MirrorMode.Single1; break;
                        }
                    }
                    else if (registerAddress == 0x0A)
                    {
                        irqEnabled = (value & 0x01) != 0;
                        CancelInterruptRequest?.Invoke();
                    }
                    else if (registerAddress == 0x0B)
                    {
                        irqCounter &= 0xFF00;
                        irqCounter |= value;
                    }
                    else if (registerAddress == 0x0C)
                    {
                        irqCounter &= 0x00FF;
                        irqCounter |= (ushort)(value << 8);
                    }
                    // TODO: D: eeprom/PRG ram enable
                    // TODO: variants
                }
                else
                {
                    throw new Exception("Unhandled " + Name + " mapper write at address: " + Hex.Format(address));
                    //ignore writes?
                }
            }
        }