#include "core/gb/gbMemory.h"

#include <cstdint>

#include "core/base/port.h"
#include "core/base/sizes.h"
#include "core/base/system.h"
#include "core/gb/gb.h"
#include "core/gb/gbGlobals.h"

uint8_t gbDaysinMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
extern int gbGBCColorType;

mapperMBC1 gbDataMBC1 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // memory model
    0, // ROM high address
    0, // RAM address
    0 // Rom Bank 0 remapping
};

// MBC1 ROM write registers
void mapperMBC1ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        gbDataMBC1.mapperRAMEnable = ((value & 0x0a) == 0x0a ? 1 : 0);
        break;
    case 0x2000: // ROM bank select
        //    value = value & 0x1f;
        if ((value == 1) && (address == 0x2100))
            gbDataMBC1.mapperRomBank0Remapping = 1;

        if ((value & 0x1f) == 0)
            value += 1;
        if (value == gbDataMBC1.mapperROMBank)
            break;

        tmpAddress = value << 14;

        // check current model
        if (gbDataMBC1.mapperRomBank0Remapping == 3) {
            tmpAddress = (value & 0xf) << 14;
            tmpAddress |= (gbDataMBC1.mapperROMHighAddress & 3) << 18;
        } else if (gbDataMBC1.mapperMemoryModel == 0) {
            // model is 16/8, so we have a high address in use
            tmpAddress |= (gbDataMBC1.mapperROMHighAddress & 3) << 19;
        }

        tmpAddress &= g_gbCartData.rom_mask();
        gbDataMBC1.mapperROMBank = value;
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        break;
    case 0x4000: // RAM bank select
        if (gbDataMBC1.mapperMemoryModel == 1) {
            if (!g_gbCartData.HasRam()) {
                if (gbDataMBC1.mapperRomBank0Remapping == 3) {
                    gbDataMBC1.mapperROMHighAddress = value & 0x03;
                    tmpAddress = (gbDataMBC1.mapperROMHighAddress) << 18;
                    tmpAddress &= g_gbCartData.rom_mask();
                    gbMemoryMap[0x00] = &gbRom[tmpAddress];
                    gbMemoryMap[0x01] = &gbRom[tmpAddress + 0x1000];
                    gbMemoryMap[0x02] = &gbRom[tmpAddress + 0x2000];
                    gbMemoryMap[0x03] = &gbRom[tmpAddress + 0x3000];
                    gbMemoryMap[0x04] = &gbRom[tmpAddress + 0x4000];
                    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x5000];
                    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x6000];
                    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x7000];
                } else
                    gbDataMBC1.mapperRomBank0Remapping = 0;
            }
            // 4/32 model, RAM bank switching provided
            value = value & 0x03;
            if (value == gbDataMBC1.mapperRAMBank)
                break;
            tmpAddress = value << 13;
            tmpAddress &= g_gbCartData.ram_mask();
            if (g_gbCartData.HasRam()) {
                gbMemoryMap[0x0a] = &gbRam[tmpAddress];
                gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
            }
            gbDataMBC1.mapperRAMBank = value;
            gbDataMBC1.mapperRAMAddress = tmpAddress;

            if (gbDataMBC1.mapperRomBank0Remapping != 3)
                gbDataMBC1.mapperROMHighAddress = 0;
        } else {
            // 16/8, set the high address
            gbDataMBC1.mapperROMHighAddress = value & 0x03;
            tmpAddress = gbDataMBC1.mapperROMBank << 14;
            tmpAddress |= (gbDataMBC1.mapperROMHighAddress) << 19;
            tmpAddress &= g_gbCartData.rom_mask();
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
            if (g_gbCartData.HasRam()) {
                gbMemoryMap[0x0a] = &gbRam[0];
                gbMemoryMap[0x0b] = &gbRam[0x1000];
            }

            gbDataMBC1.mapperRAMBank = 0;
        }
        break;
    case 0x6000: // memory model select
        gbDataMBC1.mapperMemoryModel = value & 1;

        if (gbDataMBC1.mapperMemoryModel == 1) {
            // 4/32 model, RAM bank switching provided

            value = gbDataMBC1.mapperRAMBank & 0x03;
            tmpAddress = value << 13;
            tmpAddress &= g_gbCartData.ram_mask();
            if (g_gbCartData.HasRam()) {
                gbMemoryMap[0x0a] = &gbRam[gbDataMBC1.mapperRAMAddress];
                gbMemoryMap[0x0b] = &gbRam[gbDataMBC1.mapperRAMAddress + 0x1000];
                gbDataMBC1.mapperRomBank0Remapping = 0;
            } else
                gbDataMBC1.mapperRomBank0Remapping |= 2;

            gbDataMBC1.mapperRAMBank = value;
            gbDataMBC1.mapperRAMAddress = tmpAddress;

            tmpAddress = gbDataMBC1.mapperROMBank << 14;

            tmpAddress &= g_gbCartData.rom_mask();
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

        } else {
            // 16/8, set the high address

            tmpAddress = gbDataMBC1.mapperROMBank << 14;
            tmpAddress |= (gbDataMBC1.mapperROMHighAddress) << 19;
            tmpAddress &= g_gbCartData.rom_mask();
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
            if (g_gbCartData.HasRam()) {
                gbMemoryMap[0x0a] = &gbRam[0];
                gbMemoryMap[0x0b] = &gbRam[0x1000];
            }
        }
        break;
    }
}

// MBC1 RAM write
void mapperMBC1RAM(uint16_t address, uint8_t value)
{
    if (gbDataMBC1.mapperRAMEnable) {
        if (g_gbCartData.HasRam()) {
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
            systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
        }
    }
}

// MBC1 read RAM
uint8_t mapperMBC1ReadRAM(uint16_t address)
{

    if (gbDataMBC1.mapperRAMEnable)
        return gbMemoryMap[address >> 12][address & 0x0fff];

    return 0xff;
}

void memoryUpdateMapMBC1()
{
    int tmpAddress = gbDataMBC1.mapperROMBank << 14;

    // check current model
    if (gbDataMBC1.mapperRomBank0Remapping == 3) {
        tmpAddress = (gbDataMBC1.mapperROMHighAddress & 3) << 18;
        tmpAddress &= g_gbCartData.rom_mask();
        gbMemoryMap[0x00] = &gbRom[tmpAddress];
        gbMemoryMap[0x01] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x02] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x03] = &gbRom[tmpAddress + 0x3000];

        tmpAddress |= (gbDataMBC1.mapperROMBank & 0xf) << 14;
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
    } else {
        if (gbDataMBC1.mapperMemoryModel == 0) {
            // model is 16/8, so we have a high address in use
            tmpAddress |= (gbDataMBC1.mapperROMHighAddress & 3) << 19;
        }

        tmpAddress &= g_gbCartData.rom_mask();
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
    }

    if (g_gbCartData.HasRam()) {
        if (gbDataMBC1.mapperMemoryModel == 1) {
            gbMemoryMap[0x0a] = &gbRam[gbDataMBC1.mapperRAMAddress];
            gbMemoryMap[0x0b] = &gbRam[gbDataMBC1.mapperRAMAddress + 0x1000];
        } else {
            gbMemoryMap[0x0a] = &gbRam[0];
            gbMemoryMap[0x0b] = &gbRam[0x1000];
        }
    }
}

mapperMBC2 gbDataMBC2 = {
    0, // RAM enable
    1 // ROM bank
};

// MBC2 ROM write registers
void mapperMBC2ROM(uint16_t address, uint8_t value)
{
    switch (address & 0x6000) {
    case 0x0000: // RAM enable
        if (!(address & 0x0100)) {
            gbDataMBC2.mapperRAMEnable = (value & 0x0f) == 0x0a;
        }
        break;
    case 0x2000: // ROM bank select
        if (address & 0x0100) {
            value &= 0x0f;

            if (value == 0)
                value = 1;
            if (gbDataMBC2.mapperROMBank != value) {
                gbDataMBC2.mapperROMBank = value;

                int tmpAddress = value << 14;

                tmpAddress &= g_gbCartData.rom_mask();

                gbMemoryMap[0x04] = &gbRom[tmpAddress];
                gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
                gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
                gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
            }
        }
        break;
    }
}

// MBC2 RAM write
void mapperMBC2RAM(uint16_t address, uint8_t value)
{
    if (gbDataMBC2.mapperRAMEnable) {
        if (g_gbCartData.HasRam() && address < 0xa200) {
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
            systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
        }
    }
}

void memoryUpdateMapMBC2()
{
    int tmpAddress = gbDataMBC2.mapperROMBank << 14;

    tmpAddress &= g_gbCartData.rom_mask();

    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
}

mapperMBC3 gbDataMBC3 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // RAM address
    0, // timer clock latch
    0, // timer clock register
    0, // timer seconds
    0, // timer minutes
    0, // timer hours
    0, // timer days
    0, // timer control
    0, // timer latched seconds
    0, // timer latched minutes
    0, // timer latched hours
    0, // timer latched days
    0, // timer latched control
    {0}  // last time
};

void memoryUpdateMBC3Clock()
{
    time_t now = time(NULL);
    time_t diff = now - gbDataMBC3.mapperLastTime;
    if (diff > 0) {
        // update the clock according to the last update time
        gbDataMBC3.mapperSeconds += (int)(diff % 60);
        if (gbDataMBC3.mapperSeconds > 59) {
            gbDataMBC3.mapperSeconds -= 60;
            gbDataMBC3.mapperMinutes++;
        }

        diff /= 60;

        gbDataMBC3.mapperMinutes += (int)(diff % 60);
        if (gbDataMBC3.mapperMinutes > 59) {
            gbDataMBC3.mapperMinutes -= 60;
            gbDataMBC3.mapperHours++;
        }

        diff /= 60;

        gbDataMBC3.mapperHours += (int)(diff % 24);
        if (gbDataMBC3.mapperHours > 23) {
            gbDataMBC3.mapperHours -= 24;
            gbDataMBC3.mapperDays++;
        }
        diff /= 24;

        gbDataMBC3.mapperDays += (int)(diff & 0xffffffff);
        if (gbDataMBC3.mapperDays > 255) {
            if (gbDataMBC3.mapperDays > 511) {
                gbDataMBC3.mapperDays %= 512;
                gbDataMBC3.mapperControl |= 0x80;
            }
            gbDataMBC3.mapperControl = (gbDataMBC3.mapperControl & 0xfe) | (gbDataMBC3.mapperDays > 255 ? 1 : 0);
        }
    }
    gbDataMBC3.mapperLastTime = now;
}

// MBC3 ROM write registers
void mapperMBC3ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        gbDataMBC3.mapperRAMEnable = ((value & 0x0a) == 0x0a ? 1 : 0);
        break;
    case 0x2000: { // ROM bank select
        if (g_gbCartData.rom_size() != k4MiB)
            value = value & 0x7f; // Assume 2MiB, unless MBC30.

        if (value == 0)
            value = 1;
        if (value == gbDataMBC3.mapperROMBank)
            break;

        tmpAddress = value << 14;

        tmpAddress &= g_gbCartData.rom_mask();

        gbDataMBC3.mapperROMBank = value;

        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

        break;
    }
    case 0x4000: // RAM bank select
        if (value < 8) {
            if (value == gbDataMBC3.mapperRAMBank)
                break;
            tmpAddress = value << 13;
            tmpAddress &= g_gbCartData.ram_mask();
            gbMemoryMap[0x0a] = &gbRam[tmpAddress];
            gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
            gbDataMBC3.mapperRAMBank = value;
            gbDataMBC3.mapperRAMAddress = tmpAddress;
        } else {
            if (g_gbCartData.has_rtc() && gbDataMBC3.mapperRAMEnable) {
                gbDataMBC3.mapperRAMBank = -1;

                gbDataMBC3.mapperClockRegister = value;
            }
        }
        break;
    case 0x6000: // clock latch
        if (g_gbCartData.has_rtc()) {
            if (gbDataMBC3.mapperClockLatch == 0 && value == 1) {
                memoryUpdateMBC3Clock();
                gbDataMBC3.mapperLSeconds = gbDataMBC3.mapperSeconds;
                gbDataMBC3.mapperLMinutes = gbDataMBC3.mapperMinutes;
                gbDataMBC3.mapperLHours = gbDataMBC3.mapperHours;
                gbDataMBC3.mapperLDays = gbDataMBC3.mapperDays;
                gbDataMBC3.mapperLControl = gbDataMBC3.mapperControl;
            }
            if (value == 0x00 || value == 0x01)
                gbDataMBC3.mapperClockLatch = value;
        }
        break;
    }
}

// MBC3 RAM write
void mapperMBC3RAM(uint16_t address, uint8_t value)
{
    if (gbDataMBC3.mapperRAMEnable) {
        if (gbDataMBC3.mapperRAMBank >= 0) {
            if (g_gbCartData.HasRam()) {
                gbMemoryMap[address >> 12][address & 0x0fff] = value;
                systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
            }
        } else if (g_gbCartData.has_rtc()) {
            time(&gbDataMBC3.mapperLastTime);
            switch (gbDataMBC3.mapperClockRegister) {
            case 0x08:
                gbDataMBC3.mapperSeconds = value;
                break;
            case 0x09:
                gbDataMBC3.mapperMinutes = value;
                break;
            case 0x0a:
                gbDataMBC3.mapperHours = value;
                break;
            case 0x0b:
                gbDataMBC3.mapperDays = value;
                break;
            case 0x0c:
                if (gbDataMBC3.mapperControl & 0x80)
                    gbDataMBC3.mapperControl = 0x80 | value;
                else
                    gbDataMBC3.mapperControl = value;
                break;
            }
        }
    }
}

// MBC3 read RAM
uint8_t mapperMBC3ReadRAM(uint16_t address)
{
    if (gbDataMBC3.mapperRAMEnable) {
        if (gbDataMBC3.mapperRAMBank >= 0) {
            return gbMemoryMap[address >> 12][address & 0x0fff];
        } else if (g_gbCartData.has_rtc()) {
            switch (gbDataMBC3.mapperClockRegister) {
            case 0x08:
                return (uint8_t)(gbDataMBC3.mapperLSeconds);
                break;
            case 0x09:
                return (uint8_t)(gbDataMBC3.mapperLMinutes);
                break;
            case 0x0a:
                return (uint8_t)(gbDataMBC3.mapperLHours);
                break;
            case 0x0b:
                return (uint8_t)(gbDataMBC3.mapperLDays);
                break;
            case 0x0c:
                return (uint8_t)(gbDataMBC3.mapperLControl);
            }
        }
    }

    return 0xff;
}

void memoryUpdateMapMBC3()
{
    int tmpAddress = gbDataMBC3.mapperROMBank << 14;

    tmpAddress &= g_gbCartData.rom_mask();

    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

    if (gbDataMBC3.mapperRAMBank >= 0 && g_gbCartData.HasRam()) {
        tmpAddress = gbDataMBC3.mapperRAMBank << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        gbMemoryMap[0x0a] = &gbRam[tmpAddress];
        gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
    }
}

mapperMBC5 gbDataMBC5 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // ROM high address
    0, // RAM address
    0 // is rumble cartridge?
};

// MBC5 ROM write registers
void mapperMBC5ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        gbDataMBC5.mapperRAMEnable = ((value & 0x0a) == 0x0a ? 1 : 0);
        break;
    case 0x2000: // ROM bank select

        if (address < 0x3000) {
            value = value & 0xff;
            if (value == gbDataMBC5.mapperROMBank)
                break;

            tmpAddress = (value << 14) | (gbDataMBC5.mapperROMHighAddress << 22);

            tmpAddress &= g_gbCartData.rom_mask();
            gbDataMBC5.mapperROMBank = value;
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

        } else {
            value = value & 1;
            if (value == gbDataMBC5.mapperROMHighAddress)
                break;

            tmpAddress = (gbDataMBC5.mapperROMBank << 14) | (value << 22);

            tmpAddress &= g_gbCartData.rom_mask();
            gbDataMBC5.mapperROMHighAddress = value;
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        }
        break;
    case 0x4000: // RAM bank select
        if (gbDataMBC5.isRumbleCartridge) {
            systemCartridgeRumble(value & 8);
            value &= 0x07;
        } else
            value &= 0x0f;
        if (value == gbDataMBC5.mapperRAMBank)
            break;
        tmpAddress = value << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        if (g_gbCartData.HasRam()) {
            gbMemoryMap[0x0a] = &gbRam[tmpAddress];
            gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];

            gbDataMBC5.mapperRAMBank = value;
            gbDataMBC5.mapperRAMAddress = tmpAddress;
        }
        break;
    }
}

// MBC5 RAM write
void mapperMBC5RAM(uint16_t address, uint8_t value)
{
    if (gbDataMBC5.mapperRAMEnable) {
        if (g_gbCartData.HasRam()) {
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
            systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
        }
    }
}

// MBC5 read RAM
uint8_t mapperMBC5ReadRAM(uint16_t address)
{

    if (gbDataMBC5.mapperRAMEnable)
        return gbMemoryMap[address >> 12][address & 0x0fff];

    return 0xff;
}

void memoryUpdateMapMBC5()
{
    int tmpAddress = (gbDataMBC5.mapperROMBank << 14) | (gbDataMBC5.mapperROMHighAddress << 22);

    tmpAddress &= g_gbCartData.rom_mask();
    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

    if (g_gbCartData.HasRam()) {
        tmpAddress = gbDataMBC5.mapperRAMBank << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        gbMemoryMap[0x0a] = &gbRam[tmpAddress];
        gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
    }
}

mapperMBC7 gbDataMBC7 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // RAM address
    0, // chip select
    0, // ??
    0, // mapper state
    0, // buffer for receiving serial data
    0, // idle state
    0, // count of bits received
    0, // command received
    0, // address received
    0, // write enable
    0, // value to return on ram
};

// MBC7 ROM write registers
void mapperMBC7ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000:
        break;
    case 0x2000: // ROM bank select
        value = value & 0x7f;
        if (value == 0)
            value = 1;

        if (value == gbDataMBC7.mapperROMBank)
            break;

        tmpAddress = (value << 14);

        tmpAddress &= g_gbCartData.rom_mask();
        gbDataMBC7.mapperROMBank = value;
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        break;
    case 0x4000: // RAM bank select/enable
        if (value < 8) {
            tmpAddress = (value & 3) << 13;
            tmpAddress &= g_gbCartData.ram_mask();
            gbMemoryMap[0x0a] = &gbRam[0];
            // Only one RAM bank for MBC7 so wrap around.
            gbMemoryMap[0x0b] = &gbRam[0];

            gbDataMBC7.mapperRAMBank = value;
            gbDataMBC7.mapperRAMAddress = tmpAddress;
            gbDataMBC7.mapperRAMEnable = 0;
        } else {
            gbDataMBC7.mapperRAMEnable = 0;
        }
        break;
    }
}

// MBC7 read RAM
uint8_t mapperMBC7ReadRAM(uint16_t address)
{
    switch (address & 0xa0f0) {
    case 0xa000:
    case 0xa010:
    case 0xa060:
    case 0xa070:
        return 0;
    case 0xa020:
        // sensor X low byte
        return systemGetSensorX() & 255;
    case 0xa030:
        // sensor X high byte
        return (uint8_t)(systemGetSensorX() >> 8);
    case 0xa040:
        // sensor Y low byte
        return systemGetSensorY() & 255;
    case 0xa050:
        // sensor Y high byte
        return (uint8_t)(systemGetSensorY() >> 8);
    case 0xa080:
        return (uint8_t)gbDataMBC7.value;
    }

    return 0xff;
}

// MBC7 RAM write
void mapperMBC7RAM(uint16_t address, uint8_t value)
{
    if (address == 0xa080) {
        // special processing needed
        int oldCs = gbDataMBC7.cs, oldSk = gbDataMBC7.sk;

        gbDataMBC7.cs = value >> 7;
        gbDataMBC7.sk = (value >> 6) & 1;

        if (!oldCs && gbDataMBC7.cs) {
            if (gbDataMBC7.state == 5) {
                if (gbDataMBC7.writeEnable) {
                    gbRam[gbDataMBC7.address * 2] = (uint8_t)(gbDataMBC7.buffer >> 8);
                    gbRam[gbDataMBC7.address * 2 + 1] = gbDataMBC7.buffer & 0xff;
                    systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
                }
                gbDataMBC7.state = 0;
                gbDataMBC7.value = 1;
            } else {
                gbDataMBC7.idle = true;
                gbDataMBC7.state = 0;
            }
        }

        if (!oldSk && gbDataMBC7.sk) {
            if (gbDataMBC7.idle) {
                if (value & 0x02) {
                    gbDataMBC7.idle = false;
                    gbDataMBC7.count = 0;
                    gbDataMBC7.state = 1;
                }
            } else {
                switch (gbDataMBC7.state) {
                case 1:
                    // receiving command
                    gbDataMBC7.buffer <<= 1;
                    gbDataMBC7.buffer |= (value & 0x02) ? 1 : 0;
                    gbDataMBC7.count++;
                    if (gbDataMBC7.count == 2) {
                        // finished receiving command
                        gbDataMBC7.state = 2;
                        gbDataMBC7.count = 0;
                        gbDataMBC7.code = gbDataMBC7.buffer & 3;
                    }
                    break;
                case 2:
                    // receive address
                    gbDataMBC7.buffer <<= 1;
                    gbDataMBC7.buffer |= (value & 0x02) ? 1 : 0;
                    gbDataMBC7.count++;
                    if (gbDataMBC7.count == 8) {
                        // finish receiving
                        gbDataMBC7.state = 3;
                        gbDataMBC7.count = 0;
                        gbDataMBC7.address = gbDataMBC7.buffer & 0xff;
                        if (gbDataMBC7.code == 0) {
                            if ((gbDataMBC7.address >> 6) == 0) {
                                gbDataMBC7.writeEnable = 0;
                                gbDataMBC7.state = 0;
                            } else if ((gbDataMBC7.address >> 6) == 3) {
                                gbDataMBC7.writeEnable = 1;
                                gbDataMBC7.state = 0;
                            }
                        }
                    }
                    break;
                case 3:
                    gbDataMBC7.buffer <<= 1;
                    gbDataMBC7.buffer |= (value & 0x02) ? 1 : 0;
                    gbDataMBC7.count++;

                    switch (gbDataMBC7.code) {
                    case 0:
                        if (gbDataMBC7.count == 16) {
                            if ((gbDataMBC7.address >> 6) == 0) {
                                gbDataMBC7.writeEnable = 0;
                                gbDataMBC7.state = 0;
                            } else if ((gbDataMBC7.address >> 6) == 1) {
                                if (gbDataMBC7.writeEnable) {
                                    for (int i = 0; i < 256; i++) {
                                        gbRam[i * 2] = (uint8_t)(gbDataMBC7.buffer >> 8);
                                        gbRam[i * 2 + 1] = gbDataMBC7.buffer & 0xff;
                                        systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
                                    }
                                }
                                gbDataMBC7.state = 5;
                            } else if ((gbDataMBC7.address >> 6) == 2) {
                                if (gbDataMBC7.writeEnable) {
                                    for (int i = 0; i < 256; i++)
                                        WRITE16LE((uint16_t*)&gbRam[i * 2], 0xffff);
                                    systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
                                }
                                gbDataMBC7.state = 5;
                            } else if ((gbDataMBC7.address >> 6) == 3) {
                                gbDataMBC7.writeEnable = 1;
                                gbDataMBC7.state = 0;
                            }
                            gbDataMBC7.count = 0;
                        }
                        break;
                    case 1:
                        if (gbDataMBC7.count == 16) {
                            gbDataMBC7.count = 0;
                            gbDataMBC7.state = 5;
                            gbDataMBC7.value = 0;
                        }
                        break;
                    case 2:
                        if (gbDataMBC7.count == 1) {
                            gbDataMBC7.state = 4;
                            gbDataMBC7.count = 0;
                            gbDataMBC7.buffer = (gbRam[gbDataMBC7.address * 2] << 8) | (gbRam[gbDataMBC7.address * 2 + 1]);
                        }
                        break;
                    case 3:
                        if (gbDataMBC7.count == 16) {
                            gbDataMBC7.count = 0;
                            gbDataMBC7.state = 5;
                            gbDataMBC7.value = 0;
                            gbDataMBC7.buffer = 0xffff;
                        }
                        break;
                    }
                    break;
                }
            }
        }

        if (oldSk && !gbDataMBC7.sk) {
            if (gbDataMBC7.state == 4) {
                gbDataMBC7.value = (gbDataMBC7.buffer & 0x8000) ? 1 : 0;
                gbDataMBC7.buffer <<= 1;
                gbDataMBC7.count++;
                if (gbDataMBC7.count == 16) {
                    gbDataMBC7.count = 0;
                    gbDataMBC7.state = 0;
                }
            }
        }
    }
}

void memoryUpdateMapMBC7()
{
    int tmpAddress = (gbDataMBC7.mapperROMBank << 14);

    tmpAddress &= g_gbCartData.rom_mask();
    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
}

mapperHuC1 gbDataHuC1 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // memory model
    0, // ROM high address
    0 // RAM address
};

// HuC1 ROM write registers
void mapperHuC1ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        gbDataHuC1.mapperRAMEnable = ((value & 0x0a) == 0x0a ? 1 : 0);
        break;
    case 0x2000: // ROM bank select
        value = value & 0x3f;
        if (value == 0)
            value = 1;
        if (value == gbDataHuC1.mapperROMBank)
            break;

        tmpAddress = value << 14;

        tmpAddress &= g_gbCartData.rom_mask();
        gbDataHuC1.mapperROMBank = value;
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        break;
    case 0x4000: // RAM bank select
        if (gbDataHuC1.mapperMemoryModel == 1) {
            // 4/32 model, RAM bank switching provided
            value = value & 0x03;
            if (value == gbDataHuC1.mapperRAMBank)
                break;
            tmpAddress = value << 13;
            tmpAddress &= g_gbCartData.ram_mask();
            gbMemoryMap[0x0a] = &gbRam[tmpAddress];
            gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
            gbDataHuC1.mapperRAMBank = value;
            gbDataHuC1.mapperRAMAddress = tmpAddress;
        } else {
            // 16/8, set the high address
            gbDataHuC1.mapperROMHighAddress = value & 0x03;
            tmpAddress = gbDataHuC1.mapperROMBank << 14;
            tmpAddress |= (gbDataHuC1.mapperROMHighAddress) << 19;
            tmpAddress &= g_gbCartData.rom_mask();
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        }
        break;
    case 0x6000: // memory model select
        gbDataHuC1.mapperMemoryModel = value & 1;
        break;
    }
}

// HuC1 RAM write
void mapperHuC1RAM(uint16_t address, uint8_t value)
{
    if (gbDataHuC1.mapperRAMEnable) {
        if (g_gbCartData.HasRam()) {
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
            systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
        }
    }
}

void memoryUpdateMapHuC1()
{
    int tmpAddress = gbDataHuC1.mapperROMBank << 14;

    tmpAddress &= g_gbCartData.rom_mask();

    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

    if (g_gbCartData.HasRam()) {
        tmpAddress = gbDataHuC1.mapperRAMBank << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        gbMemoryMap[0x0a] = &gbRam[tmpAddress];
        gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
    }
}

mapperHuC3 gbDataHuC3 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // RAM address
    0, // Address
    0, // RAM flag
    0, // RAM read value
    0, // Register 1
    0, // Register 2
    0, // Register 3
    0, // Register 4
    0, // Register 5
    0, // Register 6
    0, // Register 7
    0  // Register 8
};

mapperHuC3RTC gbRTCHuC3 = {
    {0}, // lastTime
    0, // DateTime
    0, // WritingTime
    0, // ModeFlag
    0, // ClockShift
    { 0 }
};

void memoryUpdateHuC3Latch() {
	uint64_t now = time(NULL);
    uint64_t diff = now - gbRTCHuC3.mapperLastTime;
    unsigned minute = (diff / 60) % 1440;
    unsigned day = (diff / 86400) & 0xFFF;

    gbRTCHuC3.mapperDateTime = (day << 12) | minute;
}

void memoryUpdateHuC3Clock() {
    uint64_t now = time(NULL);
    unsigned minute = (gbRTCHuC3.mapperWritingTime & 0xFFF) % 1440;
    unsigned day = (gbRTCHuC3.mapperWritingTime & 0xFFF000) >> 12;

    gbRTCHuC3.mapperLastTime = now - minute * 60 - day * 86400;
}

// HuC3 ROM write registers
void mapperHuC3ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        gbDataHuC3.mapperRAMEnable = (value == 0x0a ? 1 : 0);
        gbDataHuC3.mapperRAMFlag = value;
        if (gbDataHuC3.mapperRAMFlag != 0x0a)
            gbDataHuC3.mapperRAMBank = -1;
        break;
    case 0x2000: // ROM bank select
        value = value & 0x7f;
        if (value == 0)
            value = 1;
        if (value == gbDataHuC3.mapperROMBank)
            break;

        tmpAddress = value << 14;

        tmpAddress &= g_gbCartData.rom_mask();
        gbDataHuC3.mapperROMBank = value;
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        break;
    case 0x4000: // RAM bank select
        value = value & 0x03;
        if (value == gbDataHuC3.mapperRAMBank)
            break;
        tmpAddress = value << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        gbMemoryMap[0x0a] = &gbRam[tmpAddress];
        gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
        gbDataHuC3.mapperRAMBank = value;
        gbDataHuC3.mapperRAMAddress = tmpAddress;
        break;
    case 0x6000: // nothing to do!
        break;
    }
}

// HuC3 read RAM
uint8_t mapperHuC3ReadRAM(uint16_t address)
{
    if (gbDataHuC3.mapperRAMFlag > 0x0b && gbDataHuC3.mapperRAMFlag < 0x0e) {
        if (gbDataHuC3.mapperRAMFlag != 0x0c)
            return 1;
        return (uint8_t)(gbDataHuC3.mapperRAMValue);
    } else
        return gbMemoryMap[address >> 12][address & 0x0fff];
}

// HuC3 RAM write
void mapperHuC3RAM(uint16_t address, uint8_t value)
{
    //int* p;

    if (gbDataHuC3.mapperRAMFlag < 0x0b || gbDataHuC3.mapperRAMFlag > 0x0e) {
        if (gbDataHuC3.mapperRAMEnable) {
            if (g_gbCartData.HasRam()) {
                gbMemoryMap[address >> 12][address & 0x0fff] = value;
                systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
            }
        }
    } else {
        if (gbDataHuC3.mapperRAMFlag == 0x0b) {
            //if (value == 0x62) {
                //gbDataHuC3.mapperRAMValue = 1;
            //} else
            {
                switch (value & 0xf0) {
                case 0x10:
                    /*p = &gbDataHuC3.mapperRegister2;
                    gbDataHuC3.mapperRAMValue = *(p + gbDataHuC3.mapperRegister1++);
                    if (gbDataHuC3.mapperRegister1 > 6)
                        gbDataHuC3.mapperRegister1 = 0;*/

                    // read time
                    memoryUpdateHuC3Latch();
                    if (gbRTCHuC3.memoryTimerRead) {
                        gbDataHuC3.mapperRAMValue = (gbRTCHuC3.mapperDateTime >> gbRTCHuC3.mapperClockShift) & 0x0F;
                        gbRTCHuC3.mapperClockShift += 4;
                        if (gbRTCHuC3.mapperClockShift > 24)
                            gbRTCHuC3.mapperClockShift = 0;
                    }
                    break;
                case 0x30:
                    /*p = &gbDataHuC3.mapperRegister2;
                    *(p + gbDataHuC3.mapperRegister1++) = value & 0x0f;
                    if (gbDataHuC3.mapperRegister1 > 6)
                        gbDataHuC3.mapperRegister1 = 0;
                    gbDataHuC3.mapperAddress = (gbDataHuC3.mapperRegister6 << 24) | (gbDataHuC3.mapperRegister5 << 16) | (gbDataHuC3.mapperRegister4 << 8) | (gbDataHuC3.mapperRegister3 << 4) | (gbDataHuC3.mapperRegister2);*/

                    // write time
                    if (!gbRTCHuC3.memoryTimerRead) {
                        if (gbRTCHuC3.mapperClockShift == 0)
                            gbRTCHuC3.mapperWritingTime = 0;
                        if (gbRTCHuC3.mapperClockShift < 24) {
                            gbRTCHuC3.mapperWritingTime |= (value & 0x0F) << gbRTCHuC3.mapperClockShift;
                            gbRTCHuC3.mapperClockShift += 4;
                            if (gbRTCHuC3.mapperClockShift == 24) {
                                memoryUpdateHuC3Clock();
                                gbRTCHuC3.memoryTimerRead = 1;
                            }
                        }
                    }
                    break;
                case 0x40:
                    /*gbDataHuC3.mapperRegister1 = (gbDataHuC3.mapperRegister1 & 0xf0) | (value & 0x0f);
                    gbDataHuC3.mapperRegister2 = (gbDataHuC3.mapperAddress & 0x0f);
                    gbDataHuC3.mapperRegister3 = ((gbDataHuC3.mapperAddress >> 4) & 0x0f);
                    gbDataHuC3.mapperRegister4 = ((gbDataHuC3.mapperAddress >> 8) & 0x0f);
                    gbDataHuC3.mapperRegister5 = ((gbDataHuC3.mapperAddress >> 16) & 0x0f);
                    gbDataHuC3.mapperRegister6 = ((gbDataHuC3.mapperAddress >> 24) & 0x0f);
                    gbDataHuC3.mapperRegister7 = 0;
                    gbDataHuC3.mapperRegister8 = 0;
                    gbDataHuC3.mapperRAMValue = 0;*/

                    // some kind of mode shift
                    switch(value & 0x0F) {
                    case 0x0:
                        // shift reset?
                        gbRTCHuC3.mapperClockShift = 0;
                        break;
                    case 0x3:
                        // write time?
                        gbRTCHuC3.memoryTimerRead = 0;
                        gbRTCHuC3.mapperClockShift = 0;
                        break;
                    case 0x7:
                        gbRTCHuC3.memoryTimerRead = 1;
                        gbRTCHuC3.mapperClockShift = 0;
                        break;
                        // others are unimplemented so far
                    }
                    break;
                case 0x50:
                    //gbDataHuC3.mapperRegister1 = (gbDataHuC3.mapperRegister1 & 0x0f) | ((value << 4) & 0x0f);
                    break;
                case 0x60:
                    gbRTCHuC3.memoryTimerRead = 1; // ???
                    //gbDataHuC3.mapperRAMValue = 1;
                    break;
                default:
                    //gbDataHuC3.mapperRAMValue = 1;
                    break;
                }
            }
        }
    }
}

void memoryUpdateMapHuC3()
{
    int tmpAddress = gbDataHuC3.mapperROMBank << 14;

    tmpAddress &= g_gbCartData.rom_mask();
    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

    if (g_gbCartData.HasRam()) {
        tmpAddress = gbDataHuC3.mapperRAMBank << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        gbMemoryMap[0x0a] = &gbRam[tmpAddress];
        gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
    }
}

// TAMA5 (for Tamagotchi 3 (gb)).
// Very basic (and ugly :p) support, only rom bank switching is actually working...
mapperTAMA5 gbDataTAMA5 = {
    1, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // RAM address
    0, // RAM Byte select
    0, // mapper command number
    0, // mapper last command;
    {
        0, // commands 0x0
        0, // commands 0x1
        0, // commands 0x2
        0, // commands 0x3
        0, // commands 0x4
        0, // commands 0x5
        0, // commands 0x6
        0, // commands 0x7
        0, // commands 0x8
        0, // commands 0x9
        0, // commands 0xa
        0, // commands 0xb
        0, // commands 0xc
        0, // commands 0xd
        0, // commands 0xe
        0 // commands 0xf
    },
    0, // register
    0, // timer clock latch
    0, // timer clock register
    0, // timer seconds
    0, // timer minutes
    0, // timer hours
    0, // timer days
    0, // timer months
    0, // timer years
    0, // timer control
    0, // timer latched seconds
    0, // timer latched minutes
    0, // timer latched hours
    0, // timer latched days
    0, // timer latched months
    0, // timer latched years
    0, // timer latched control
    {(time_t)-1} // last time
};

void memoryUpdateTAMA5Clock()
{
    if ((gbDataTAMA5.mapperYears & 3) == 0)
        gbDaysinMonth[1] = 29;
    else
        gbDaysinMonth[1] = 28;

    time_t now = time(NULL);
    time_t diff = now - gbDataTAMA5.mapperLastTime;
    if (diff > 0) {
        // update the clock according to the last update time
        gbDataTAMA5.mapperSeconds += (int)(diff % 60);
        if (gbDataTAMA5.mapperSeconds > 59) {
            gbDataTAMA5.mapperSeconds -= 60;
            gbDataTAMA5.mapperMinutes++;
        }

        diff /= 60;

        gbDataTAMA5.mapperMinutes += (int)(diff % 60);
        if (gbDataTAMA5.mapperMinutes > 59) {
            gbDataTAMA5.mapperMinutes -= 60;
            gbDataTAMA5.mapperHours++;
        }

        diff /= 60;

        gbDataTAMA5.mapperHours += (int)(diff % 24);
        diff /= 24;
        if (gbDataTAMA5.mapperHours > 23) {
            gbDataTAMA5.mapperHours -= 24;
            diff++;
        }

        time_t days = diff;
        while (days) {
            gbDataTAMA5.mapperDays++;
            days--;
            if (gbDataTAMA5.mapperDays > gbDaysinMonth[gbDataTAMA5.mapperMonths - 1]) {
                gbDataTAMA5.mapperDays = 1;
                gbDataTAMA5.mapperMonths++;
                if (gbDataTAMA5.mapperMonths > 12) {
                    gbDataTAMA5.mapperMonths = 1;
                    gbDataTAMA5.mapperYears++;
                    if ((gbDataTAMA5.mapperYears & 3) == 0)
                        gbDaysinMonth[1] = 29;
                    else
                        gbDaysinMonth[1] = 28;
                }
            }
        }
    }
    gbDataTAMA5.mapperLastTime = now;
}

// TAMA5 RAM write
void mapperTAMA5RAM(uint16_t address, uint8_t value)
{
    if ((address & 0xffff) <= 0xa001) {
        switch (address & 1) {
        case 0: // 'Values' Register
        {
            value &= 0xf;
            gbDataTAMA5.mapperCommands[gbDataTAMA5.mapperCommandNumber] = value;
            gbMemoryMap[0xa][0] = value;

            /*        int test = gbDataTAMA5.mapperCommands[gbDataTAMA5.mapperCommandNumber & 0x0e] |
                                    (gbDataTAMA5.mapperCommands[(gbDataTAMA5.mapperCommandNumber & 0x0e) +1]<<4);*/

            if ((gbDataTAMA5.mapperCommandNumber & 0xe) == 0) // Read Command !!!
            {
                gbDataTAMA5.mapperROMBank = gbDataTAMA5.mapperCommands[0] | (gbDataTAMA5.mapperCommands[1] << 4);

                int tmpAddress = (gbDataTAMA5.mapperROMBank << 14);

                tmpAddress &= g_gbCartData.rom_mask();
                gbMemoryMap[0x04] = &gbRom[tmpAddress];
                gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
                gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
                gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

                gbDataTAMA5.mapperCommands[0x0f] = 0;
            } else if ((gbDataTAMA5.mapperCommandNumber & 0xe) == 4) {
                gbDataTAMA5.mapperCommands[0x0f] = 1;
                if (gbDataTAMA5.mapperCommandNumber == 4)
                    gbDataTAMA5.mapperCommands[5] = 0; // correct ?
            } else if ((gbDataTAMA5.mapperCommandNumber & 0xe) == 6) {
                gbDataTAMA5.mapperRamByteSelect = (gbDataTAMA5.mapperCommands[7] << 4) | (gbDataTAMA5.mapperCommands[6] & 0x0f);

                // Write Commands !!!
                if (gbDataTAMA5.mapperCommands[0x0f] && (gbDataTAMA5.mapperCommandNumber == 7)) {
                    int data = (gbDataTAMA5.mapperCommands[0x04] & 0x0f) | (gbDataTAMA5.mapperCommands[0x05] << 4);

                    // Not sure when the write command should reset...
                    // but it doesn't seem to matter.
                    // gbDataTAMA5.mapperCommands[0x0f] = 0;

                    if (gbDataTAMA5.mapperRamByteSelect == 0x8) // Timer stuff
                    {
                        switch (data & 0xf) {
                        case 0x7:
                            gbDataTAMA5.mapperDays = ((gbDataTAMA5.mapperDays) / 10) * 10 + (data >> 4);
                            break;
                        case 0x8:
                            gbDataTAMA5.mapperDays = (gbDataTAMA5.mapperDays % 10) + (data >> 4) * 10;
                            break;
                        case 0x9:
                            gbDataTAMA5.mapperMonths = ((gbDataTAMA5.mapperMonths) / 10) * 10 + (data >> 4);
                            break;
                        case 0xa:
                            gbDataTAMA5.mapperMonths = (gbDataTAMA5.mapperMonths % 10) + (data >> 4) * 10;
                            break;
                        case 0xb:
                            gbDataTAMA5.mapperYears = ((gbDataTAMA5.mapperYears) % 1000) + (data >> 4) * 1000;
                            break;
                        case 0xc:
                            gbDataTAMA5.mapperYears = (gbDataTAMA5.mapperYears % 100) + (gbDataTAMA5.mapperYears / 1000) * 1000 + (data >> 4) * 100;
                            break;
                        default:
                            break;
                        }
                    } else if (gbDataTAMA5.mapperRamByteSelect == 0x18) // Timer stuff again
                    {
                        memoryUpdateTAMA5Clock();
                        gbDataTAMA5.mapperLSeconds = gbDataTAMA5.mapperSeconds;
                        gbDataTAMA5.mapperLMinutes = gbDataTAMA5.mapperMinutes;
                        gbDataTAMA5.mapperLHours = gbDataTAMA5.mapperHours;
                        gbDataTAMA5.mapperLDays = gbDataTAMA5.mapperDays;
                        gbDataTAMA5.mapperLMonths = gbDataTAMA5.mapperMonths;
                        gbDataTAMA5.mapperLYears = gbDataTAMA5.mapperYears;
                        gbDataTAMA5.mapperLControl = gbDataTAMA5.mapperControl;

                        uint8_t seconds = (uint8_t)((gbDataTAMA5.mapperLSeconds / 10) * 16 + gbDataTAMA5.mapperLSeconds % 10);
                        uint8_t secondsL = (gbDataTAMA5.mapperLSeconds % 10);
                        uint8_t secondsH = (uint8_t)(gbDataTAMA5.mapperLSeconds / 10);
                        uint8_t minutes = (uint8_t)((gbDataTAMA5.mapperLMinutes / 10) * 16 + gbDataTAMA5.mapperLMinutes % 10);
                        uint8_t hours = (uint8_t)((gbDataTAMA5.mapperLHours / 10) * 16 + gbDataTAMA5.mapperLHours % 10);
                        uint8_t DaysL = gbDataTAMA5.mapperLDays % 10;
                        uint8_t DaysH = (uint8_t)(gbDataTAMA5.mapperLDays / 10);
                        uint8_t MonthsL = gbDataTAMA5.mapperLMonths % 10;
                        uint8_t MonthsH = (uint8_t)(gbDataTAMA5.mapperLMonths / 10);
                        uint8_t Years3 = (gbDataTAMA5.mapperLYears / 100) % 10;
                        uint8_t Years4 = (uint8_t)(gbDataTAMA5.mapperLYears / 1000);

                        switch (data & 0x0f) {
                        // I guess cases 0 and 1 are used for secondsL and secondsH
                        // so the game would update the timer values on screen when
                        // the seconds reset to 0... ?
                        case 0x0:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = secondsL;
                            break;
                        case 0x1:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = secondsH;
                            break;
                        case 0x7:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = DaysL; // days low
                            break;
                        case 0x8:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = DaysH; // days high
                            break;
                        case 0x9:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = MonthsL; // month low
                            break;
                        case 0xa:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = MonthsH; // month high
                            break;
                        case 0xb:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = Years4; // years 4th digit
                            break;
                        case 0xc:
                            gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = Years3; // years 3rd digit
                            break;
                        default:
                            break;
                        }

                        gbTAMA5ram[0x54] = seconds; // incorrect ? (not used by the game) ?
                        gbTAMA5ram[0x64] = minutes;
                        gbTAMA5ram[0x74] = hours;
                        gbTAMA5ram[0x84] = DaysH * 16 + DaysL; // incorrect ? (not used by the game) ?
                        gbTAMA5ram[0x94] = MonthsH * 16 + MonthsL; // incorrect ? (not used by the game) ?

                        time(&gbDataTAMA5.mapperLastTime);

                        gbMemoryMap[0xa][0] = 1;
                    } else if (gbDataTAMA5.mapperRamByteSelect == 0x28) // Timer stuff again
                    {
                        if ((data & 0xf) == 0xb)
                            gbDataTAMA5.mapperYears = ((gbDataTAMA5.mapperYears >> 2) << 2) + (data & 3);
                    } else if (gbDataTAMA5.mapperRamByteSelect == 0x44) {
                        gbDataTAMA5.mapperMinutes = (data / 16) * 10 + data % 16;
                    } else if (gbDataTAMA5.mapperRamByteSelect == 0x54) {
                        gbDataTAMA5.mapperHours = (data / 16) * 10 + data % 16;
                    } else {
                        gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect] = (uint8_t)data;
                    }
                }
            }
        } break;
        case 1: // 'Commands' Register
        {
            gbDataTAMA5.mapperCommandNumber = value;
            gbMemoryMap[0xa][1] = value;

            // This should be only a 'is the flashrom ready ?' command.
            // However as I couldn't find any 'copy' command
            // (that seems to be needed for the saving system to work)
            // I put it there...
            if (value == 0x0a) {
                for (int i = 0; i < 0x10; i++)
                    for (int j = 0; j < 0x10; j++)
                        if (!(j & 2))
                            gbTAMA5ram[((i * 0x10) + j) | 2] = gbTAMA5ram[(i * 0x10) + j];
                // Enable this to see the content of the flashrom in 0xe000
                /*for (int k = 0; k<0x100; k++)
            gbMemoryMap[0xe][k] = gbTAMA5ram[k];*/

                gbDataTAMA5.mapperRAMEnable = 1;
                gbMemoryMap[0xa][0] = 1;
            } else {
                if ((value & 0x0e) == 0x0c) {
                    gbDataTAMA5.mapperRamByteSelect = gbDataTAMA5.mapperCommands[6] | (gbDataTAMA5.mapperCommands[7] << 4);

                    uint8_t byte = gbTAMA5ram[gbDataTAMA5.mapperRamByteSelect];

                    gbMemoryMap[0xa][0] = (value & 1) ? byte >> 4 : byte & 0x0f;

                    gbDataTAMA5.mapperCommands[0x0f] = 0;
                }
            }
            break;
        }
        }
    } else {
        if (gbDataTAMA5.mapperRAMEnable) {
            if (gbDataTAMA5.mapperRAMBank != -1) {
                if (g_gbCartData.HasRam()) {
                    gbMemoryMap[address >> 12][address & 0x0fff] = value;
                    systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
                }
            }
        }
    }
}

// TAMA5 read RAM
uint8_t mapperTAMA5ReadRAM(uint16_t address)
{
    return gbMemoryMap[address >> 12][address & 0xfff];
}

void memoryUpdateMapTAMA5()
{
    int tmpAddress = (gbDataTAMA5.mapperROMBank << 14);

    tmpAddress &= g_gbCartData.rom_mask();
    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

    if (g_gbCartData.HasRam()) {
        tmpAddress = 0 << 13;
        tmpAddress &= g_gbCartData.ram_mask();
        gbMemoryMap[0x0a] = &gbRam[tmpAddress];
        gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
    }
}

// MMM01 Used in Momotarou collection (however the rom is corrupted)
mapperMMM01 gbDataMMM01 = {
    0, // RAM enable
    1, // ROM bank
    0, // RAM bank
    0, // memory model
    0, // ROM high address
    0, // RAM address
    0 // Rom Bank 0 remapping
};

// MMM01 ROM write registers
void mapperMMM01ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        gbDataMMM01.mapperRAMEnable = ((value & 0x0a) == 0x0a ? 1 : 0);
        break;
    case 0x2000: // ROM bank select
        //    value = value & 0x1f;
        if (value == 0)
            value = 1;
        if (value == gbDataMMM01.mapperROMBank)
            break;

        tmpAddress = value << 14;

        // check current model
        if (gbDataMMM01.mapperMemoryModel == 0) {
            // model is 16/8, so we have a high address in use
            tmpAddress |= (gbDataMMM01.mapperROMHighAddress) << 19;
        } else
            tmpAddress |= gbDataMMM01.mapperRomBank0Remapping << 18;

        tmpAddress &= g_gbCartData.rom_mask();
        gbDataMMM01.mapperROMBank = value;
        gbMemoryMap[0x04] = &gbRom[tmpAddress];
        gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
        gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];
        break;
    case 0x4000: // RAM bank select
        if (gbDataMMM01.mapperMemoryModel == 1) {
            // 4/32 model, RAM bank switching provided
            value = value & 0x03;
            if (value == gbDataMBC1.mapperRAMBank)
                break;
            tmpAddress = value << 13;
            tmpAddress &= g_gbCartData.ram_mask();
            gbMemoryMap[0x0a] = &gbRam[tmpAddress];
            gbMemoryMap[0x0b] = &gbRam[tmpAddress + 0x1000];
            gbDataMMM01.mapperRAMBank = value;
            gbDataMMM01.mapperRAMAddress = tmpAddress;
        } else {
            // 16/8, set the high address
            gbDataMMM01.mapperROMHighAddress = value & 0x03;
            tmpAddress = gbDataMMM01.mapperROMBank << 14;
            tmpAddress |= (gbDataMMM01.mapperROMHighAddress) << 19;
            tmpAddress &= g_gbCartData.rom_mask();
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

            gbDataMMM01.mapperRomBank0Remapping = ((value << 1) | (value & 0x40 ? 1 : 0)) & 0xff;
            tmpAddress = gbDataMMM01.mapperRomBank0Remapping << 18;
            tmpAddress &= g_gbCartData.rom_mask();
            gbMemoryMap[0x00] = &gbRom[tmpAddress];
            gbMemoryMap[0x01] = &gbRom[tmpAddress + 0x1000];
            gbMemoryMap[0x02] = &gbRom[tmpAddress + 0x2000];
            gbMemoryMap[0x03] = &gbRom[tmpAddress + 0x3000];
        }
        break;
    case 0x6000: // memory model select
        gbDataMMM01.mapperMemoryModel = value & 1;
        break;
    }
}

// MMM01 RAM write
void mapperMMM01RAM(uint16_t address, uint8_t value)
{
    if (gbDataMMM01.mapperRAMEnable) {
        if (g_gbCartData.HasRam()) {
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
            systemSaveUpdateCounter = SYSTEM_SAVE_UPDATED;
        }
    }
}

void memoryUpdateMapMMM01()
{
    int tmpAddress = gbDataMMM01.mapperROMBank << 14;

    // check current model
    if (gbDataMMM01.mapperMemoryModel == 1) {
        // model is 16/8, so we have a high address in use
        tmpAddress |= (gbDataMMM01.mapperROMHighAddress) << 19;
    }

    tmpAddress &= g_gbCartData.rom_mask();
    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x06] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x07] = &gbRom[tmpAddress + 0x3000];

    tmpAddress = gbDataMMM01.mapperRomBank0Remapping << 18;
    tmpAddress &= g_gbCartData.rom_mask();
    gbMemoryMap[0x00] = &gbRom[tmpAddress];
    gbMemoryMap[0x01] = &gbRom[tmpAddress + 0x1000];
    gbMemoryMap[0x02] = &gbRom[tmpAddress + 0x2000];
    gbMemoryMap[0x03] = &gbRom[tmpAddress + 0x3000];

    if (g_gbCartData.HasRam()) {
        gbMemoryMap[0x0a] = &gbRam[gbDataMMM01.mapperRAMAddress];
        gbMemoryMap[0x0b] = &gbRam[gbDataMMM01.mapperRAMAddress + 0x1000];
    }
}

// GameGenie ROM write registers
void mapperGGROM(uint16_t address, uint8_t value)
{
    switch (address & 0x6000) {
    case 0x0000: // RAM enable register
        break;
    case 0x2000: // GameGenie has only a half bank
        break;
    case 0x4000: // GameGenie has no RAM
        if ((address >= 0x4001) && (address <= 0x4020)) // GG Hardware Registers
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
        break;
    case 0x6000: // GameGenie has only a half bank
        break;
    }
}

// GS3 Used to emulate the GS V3.0 rom bank switching
mapperGS3 gbDataGS3 = { 1 }; // ROM bank

void mapperGS3ROM(uint16_t address, uint8_t value)
{
    int tmpAddress = 0;

    switch (address & 0x6000) {
    case 0x0000: // GS has no ram
        break;
    case 0x2000: // GS has no 'classic' ROM bank select
        break;
    case 0x4000: // GS has no ram
        break;
    case 0x6000: // 0x6000 area is RW, and used for GS hardware registers

        if (address == 0x7FE1) // This is the (half) ROM bank select register
        {
            if (value == gbDataGS3.mapperROMBank)
                break;
            tmpAddress = value << 13;

            tmpAddress &= g_gbCartData.rom_mask();
            gbDataGS3.mapperROMBank = value;
            gbMemoryMap[0x04] = &gbRom[tmpAddress];
            gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
        } else
            gbMemoryMap[address >> 12][address & 0x0fff] = value;
        break;
    }
}

void memoryUpdateMapGS3()
{
    int tmpAddress = gbDataGS3.mapperROMBank << 13;

    tmpAddress &= g_gbCartData.rom_mask();
    // GS can only change a half ROM bank
    gbMemoryMap[0x04] = &gbRom[tmpAddress];
    gbMemoryMap[0x05] = &gbRom[tmpAddress + 0x1000];
}
