MiSTer-devel / Gameboy_MiSTer

Gameboy for MiSTer
101 stars 48 forks source link

Mapper Request: HuC3 #162

Closed ajgowans closed 1 year ago

ajgowans commented 3 years ago

Can the HuC3 mapper be added to the core?

https://gbhwdb.gekkio.fi/cartridges/huc3.html

It is used for two Pocket Family games and two Robot Poncots games.

Some additional info: Only used outside of Japan with Robopon - Sun Version, supports an RTC, an Infrared Port, Battery Backed Save RAM and has a speaker that can make some simple sounds when the cart is off. The physical cartridge is oversized and has a compartment for a user-replaceable battery and also has an internal save battery. http://nerdlypleasures.blogspot.com/2015/01/the-everdrive-gb-game-boy-and-game-boy.html

birdybro commented 3 years ago

Sameboy has this implemented, it could be used as an reference, but I'm not sure how extensively they have it covered in terms of total support of all features. Search GB_HUC3 in all files from the Sameboy source code.

Big list of snippets to get some perspective:

gb.c

int GB_save_battery_size(GB_gameboy_t *gb)
{
    if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
    if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.

    if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */

    if (gb->cartridge_type->mbc_type == GB_HUC3) {
        return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t);
    }

    if (gb->cartridge_type->mbc_type == GB_TPP1) {
        return gb->mbc_ram_size + sizeof(GB_tpp1_rtc_save_t);
    }

    GB_rtc_save_t rtc_save_size;
    return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0);
}
int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
{
    if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
    if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
    if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */

    if (size < GB_save_battery_size(gb)) return EIO;

    memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size);

    if (gb->cartridge_type->mbc_type == GB_TPP1) {
        buffer += gb->mbc_ram_size;
        GB_tpp1_rtc_save_t rtc_save;
        GB_fill_tpp1_save_data(gb, &rtc_save);
        memcpy(buffer, &rtc_save, sizeof(rtc_save));
    }
    else if (gb->cartridge_type->mbc_type == GB_HUC3) {
        buffer += gb->mbc_ram_size;
int GB_save_battery(GB_gameboy_t *gb, const char *path)
{
    if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
    if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
    if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
    FILE *f = fopen(path, "wb");
    if (!f) {
        GB_log(gb, "Could not open battery save: %s.\n", strerror(errno));
        return errno;
    }

    if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
        fclose(f);
        return EIO;
    }
    if (gb->cartridge_type->mbc_type == GB_TPP1) {
        GB_tpp1_rtc_save_t rtc_save;
        GB_fill_tpp1_save_data(gb, &rtc_save);

        if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
            fclose(f);
            return EIO;
        }
    }
    else if (gb->cartridge_type->mbc_type == GB_HUC3) {
#ifdef GB_BIG_ENDIAN
        GB_huc3_rtc_time_t rtc_save = {
            __builtin_bswap64(gb->last_rtc_second),
            __builtin_bswap16(gb->huc3_minutes),
            __builtin_bswap16(gb->huc3_days),
            __builtin_bswap16(gb->huc3_alarm_minutes),
            __builtin_bswap16(gb->huc3_alarm_days),
            gb->huc3_alarm_enabled,
        };
#else
        GB_huc3_rtc_time_t rtc_save = {
            gb->last_rtc_second,
            gb->huc3_minutes,
            gb->huc3_days,
            gb->huc3_alarm_minutes,
            gb->huc3_alarm_days,
            gb->huc3_alarm_enabled,
        };
#endif
void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
    memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size));
    if (size <= gb->mbc_ram_size) {
        goto reset_rtc;
    }

    if (gb->cartridge_type->mbc_type == GB_TPP1) {
        GB_tpp1_rtc_save_t rtc_save;
        if (size - gb->mbc_ram_size < sizeof(rtc_save)) {
            goto reset_rtc;
        }
        memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save));

        GB_load_tpp1_save_data(gb, &rtc_save);

        if (gb->last_rtc_second > time(NULL)) {
            /* We must reset RTC here, or it will not advance. */
            goto reset_rtc;
        }
        return;
    }

    if (gb->cartridge_type->mbc_type == GB_HUC3) {
        GB_huc3_rtc_time_t rtc_save;
        if (size - gb->mbc_ram_size < sizeof(rtc_save)) {
            goto reset_rtc;
        }
        memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save));
void GB_load_battery(GB_gameboy_t *gb, const char *path)
{
    FILE *f = fopen(path, "rb");
    if (!f) {
        return;
    }

    if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
        goto reset_rtc;
    }

    if (gb->cartridge_type->mbc_type == GB_TPP1) {
        GB_tpp1_rtc_save_t rtc_save;
        if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
            goto reset_rtc;
        }

        GB_load_tpp1_save_data(gb, &rtc_save);

        if (gb->last_rtc_second > time(NULL)) {
            /* We must reset RTC here, or it will not advance. */
            goto reset_rtc;
        }
        return;
    }

    if (gb->cartridge_type->mbc_type == GB_HUC3) {
        GB_huc3_rtc_time_t rtc_save;
        if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
            goto reset_rtc;
        }
unsigned GB_time_to_alarm(GB_gameboy_t *gb)
{
    if (gb->cartridge_type->mbc_type != GB_HUC3) return 0;
    if (!gb->huc3_alarm_enabled) return 0;
    if (!(gb->huc3_alarm_days & 0x2000)) return 0;
    unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60);
    unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60;
    if (current_time > alarm_time) return 0;
    return alarm_time - current_time;
}

gb.h

typedef struct __attribute__((packed)) {
    uint64_t last_rtc_second;
    uint16_t minutes;
    uint16_t days;
    uint16_t alarm_minutes, alarm_days;
    uint8_t alarm_enabled;
} GB_huc3_rtc_time_t;

mbc.c

const GB_cartridge_t GB_cart_defs[256] = {
    // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type
    /* MBC        SUBTYPE          RAM    BAT.   RTC    RUMB.   */
    {  GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h  ROM ONLY
    {  GB_MBC1  , GB_STANDARD_MBC, false, false, false, false}, // 01h  MBC1
    {  GB_MBC1  , GB_STANDARD_MBC, true , false, false, false}, // 02h  MBC1+RAM
    {  GB_MBC1  , GB_STANDARD_MBC, true , true , false, false}, // 03h  MBC1+RAM+BATTERY
    [5] =
    {  GB_MBC2  , GB_STANDARD_MBC, true , false, false, false}, // 05h  MBC2
    {  GB_MBC2  , GB_STANDARD_MBC, true , true , false, false}, // 06h  MBC2+BATTERY
    [8] =
    {  GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h  ROM+RAM
    {  GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h  ROM+RAM+BATTERY
    [0xB] =
    /* Todo: Not supported yet */
    {  GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh  MMM01
    {  GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch  MMM01+RAM
    {  GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh  MMM01+RAM+BATTERY
    [0xF] =
    {  GB_MBC3  , GB_STANDARD_MBC, false, true,  true , false}, // 0Fh  MBC3+TIMER+BATTERY
    {  GB_MBC3  , GB_STANDARD_MBC, true , true,  true , false}, // 10h  MBC3+TIMER+RAM+BATTERY
    {  GB_MBC3  , GB_STANDARD_MBC, false, false, false, false}, // 11h  MBC3
    {  GB_MBC3  , GB_STANDARD_MBC, true , false, false, false}, // 12h  MBC3+RAM
    {  GB_MBC3  , GB_STANDARD_MBC, true , true , false, false}, // 13h  MBC3+RAM+BATTERY
    [0x19] =
    {  GB_MBC5  , GB_STANDARD_MBC, false, false, false, false}, // 19h  MBC5
    {  GB_MBC5  , GB_STANDARD_MBC, true , false, false, false}, // 1Ah  MBC5+RAM
    {  GB_MBC5  , GB_STANDARD_MBC, true , true , false, false}, // 1Bh  MBC5+RAM+BATTERY
    {  GB_MBC5  , GB_STANDARD_MBC, false, false, false, true }, // 1Ch  MBC5+RUMBLE
    {  GB_MBC5  , GB_STANDARD_MBC, true , false, false, true }, // 1Dh  MBC5+RUMBLE+RAM
    {  GB_MBC5  , GB_STANDARD_MBC, true , true , false, true }, // 1Eh  MBC5+RUMBLE+RAM+BATTERY
    [0xFC] =
    {  GB_MBC5  , GB_CAMERA      , true , true , false, false}, // FCh  POCKET CAMERA
    {  GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh  BANDAI TAMA5 (Todo: Not supported)
    {  GB_HUC3  , GB_STANDARD_MBC, true , true , true,  false}, // FEh  HuC3
    {  GB_HUC1  , GB_STANDARD_MBC, true , true , false, false}, // FFh  HuC1+RAM+BATTERY
};
void GB_update_mbc_mappings(GB_gameboy_t *gb)
{
    switch (gb->cartridge_type->mbc_type) {
        case GB_NO_MBC: return;
        case GB_MBC1:
            switch (gb->mbc1_wiring) {
                case GB_STANDARD_MBC1_WIRING:
                    gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5);
                    if (gb->mbc1.mode == 0) {
                        gb->mbc_ram_bank = 0;
                        gb->mbc_rom0_bank = 0;
                    }
                    else {
                        gb->mbc_ram_bank = gb->mbc1.bank_high;
                        gb->mbc_rom0_bank = gb->mbc1.bank_high << 5;
                    }
                    if ((gb->mbc_rom_bank & 0x1F) == 0) {
                        gb->mbc_rom_bank++;
                    }
                    break;
                case GB_MBC1M_WIRING:
                    gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4);
                    if (gb->mbc1.mode == 0) {
                        gb->mbc_ram_bank = 0;
                        gb->mbc_rom0_bank = 0;
                    }
                    else {
                        gb->mbc_rom0_bank = gb->mbc1.bank_high << 4;
                        gb->mbc_ram_bank = 0;
                    }
                    if ((gb->mbc1.bank_low & 0x1F) == 0) {
                        gb->mbc_rom_bank++;
                    }
                    break;
            }
            break;
        case GB_MBC2:
            gb->mbc_rom_bank = gb->mbc2.rom_bank;
            if ((gb->mbc_rom_bank & 0xF) == 0) {
                gb->mbc_rom_bank = 1;
            }
            break;
        case GB_MBC3:
            gb->mbc_rom_bank = gb->mbc3.rom_bank;
            gb->mbc_ram_bank = gb->mbc3.ram_bank;
            if (!gb->is_mbc30) {
                gb->mbc_rom_bank &= 0x7F;
            }
            if (gb->mbc_rom_bank == 0) {
                gb->mbc_rom_bank = 1;
            }
            break;
        case GB_MBC5:
            gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8);
            gb->mbc_ram_bank = gb->mbc5.ram_bank;
            break;
        case GB_HUC1:
            if (gb->huc1.mode == 0) {
                gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6);
                gb->mbc_ram_bank = 0;
            }
            else {
                gb->mbc_rom_bank = gb->huc1.bank_low;
                gb->mbc_ram_bank = gb->huc1.bank_high;
            }
            break;
        case GB_HUC3:
            gb->mbc_rom_bank = gb->huc3.rom_bank;
            gb->mbc_ram_bank = gb->huc3.ram_bank;
            break;
        case GB_TPP1:
            gb->mbc_rom_bank = gb->tpp1_rom_bank;
            gb->mbc_ram_bank = gb->tpp1_ram_bank;
            gb->mbc_ram_enable = (gb->tpp1_mode == 2) || (gb->tpp1_mode == 3);
            break;
    }
}

memory.c

static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
{
    if (gb->cartridge_type->mbc_type == GB_HUC3) {
        switch (gb->huc3_mode) {
            case 0xC: // RTC read
                if (gb->huc3_access_flags == 0x2) {
                    return 1;
                }
                return gb->huc3_read;
            case 0xD: // RTC status
                return 1;
            case 0xE: // IR mode
                return gb->effective_ir_input; // TODO: What are the other bits?
            default:
                GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr);
                return 1; // TODO: What happens in this case?
            case 0: // TODO: R/O RAM? (or is it disabled?)
            case 0xA: // RAM
                break;
        }
    }

...

else if ((!gb->mbc_ram_enable) &&
        gb->cartridge_type->mbc_subtype != GB_CAMERA &&
        gb->cartridge_type->mbc_type != GB_HUC1 &&
        gb->cartridge_type->mbc_type != GB_HUC3) {
        return 0xFF;
    }

...

if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
        gb->mbc3_rtc_mapped) {
        /* RTC read */
        if (gb->mbc_ram_bank <= 4) {
            gb->rtc_latched.seconds &= 0x3F;
            gb->rtc_latched.minutes &= 0x3F;
            gb->rtc_latched.hours &= 0x1F;
            gb->rtc_latched.high &= 0xC1;
            return gb->rtc_latched.data[gb->mbc_ram_bank];
        }
        return 0xFF;
    }
static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
    switch (gb->cartridge_type->mbc_type) {

...

case GB_HUC3:
            switch (addr & 0xF000) {
                case 0x0000: case 0x1000:
                    gb->huc3_mode = value & 0xF;
                    gb->mbc_ram_enable = gb->huc3_mode == 0xA;
                    break;
                case 0x2000: case 0x3000: gb->huc3.rom_bank  = value; break;
                case 0x4000: case 0x5000: gb->huc3.ram_bank  = value; break;
            }
            break;
static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
    if (gb->cartridge_type->mbc_type == GB_HUC3) {
        if (huc3_write(gb, value)) return;
    }

timing.c

static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles)
{
    if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return;
    gb->rtc_cycles += cycles;
    time_t current_time = 0;

    switch (gb->rtc_mode) {
        case GB_RTC_MODE_SYNC_TO_HOST:
            // Sync in a 1/32s resolution
            if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) / 16) return;
            gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) / 16;
            current_time = time(NULL);
            break;
        case GB_RTC_MODE_ACCURATE:
            if (gb->cartridge_type->mbc_type != GB_HUC3 && (gb->rtc_real.high & 0x40)) {
                gb->rtc_cycles -= cycles;
                return;
            }
            if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) * 2) return;
            gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) * 2;
            current_time = gb->last_rtc_second + 1;
            break;
    }

    if (gb->cartridge_type->mbc_type == GB_HUC3) {
        while (gb->last_rtc_second / 60 < current_time / 60) {
            gb->last_rtc_second += 60;
            gb->huc3_minutes++;
            if (gb->huc3_minutes == 60 * 24) {
                gb->huc3_days++;
                gb->huc3_minutes = 0;
            }
        }
        return;
    }
nabilbendafi commented 3 years ago

@paulb-nl @sorgelig Does #170 solve the Issue/Request, partiall or completely?

paulb-nl commented 3 years ago

HuC3 has been implemented with RTC and without infrared port & speaker.

paulb-nl commented 1 year ago

Added with bd97ea80efdd69ed25d94a8f43d4a94927889015