#include "cart.h" #include #include #include static const char *mbc_name(MbcType m) { switch (m) { case MBC_NONE: return "ROM-only"; case MBC_1: return "MBC1"; case MBC_2: return "MBC2"; case MBC_3: return "MBC3"; case MBC_5: return "MBC5"; } return "?"; } // Decode cartridge type byte (0x0147) into MBC + feature flags. static void decode_type(Cart *c, u8 t) { c->mbc = MBC_NONE; c->has_ram = false; c->has_battery = false; c->has_rtc = false; switch (t) { case 0x00: c->mbc = MBC_NONE; break; case 0x01: c->mbc = MBC_1; break; case 0x02: c->mbc = MBC_1; c->has_ram = true; break; case 0x03: c->mbc = MBC_1; c->has_ram = true; c->has_battery = true; break; case 0x05: c->mbc = MBC_2; break; case 0x06: c->mbc = MBC_2; c->has_battery = true; break; case 0x08: c->mbc = MBC_NONE; c->has_ram = true; break; case 0x09: c->mbc = MBC_NONE; c->has_ram = true; c->has_battery = true; break; case 0x0F: c->mbc = MBC_3; c->has_rtc = true; c->has_battery = true; break; case 0x10: c->mbc = MBC_3; c->has_rtc = true; c->has_ram = true; c->has_battery = true; break; case 0x11: c->mbc = MBC_3; break; case 0x12: c->mbc = MBC_3; c->has_ram = true; break; case 0x13: c->mbc = MBC_3; c->has_ram = true; c->has_battery = true; break; case 0x19: c->mbc = MBC_5; break; case 0x1A: c->mbc = MBC_5; c->has_ram = true; break; case 0x1B: c->mbc = MBC_5; c->has_ram = true; c->has_battery = true; break; case 0x1C: c->mbc = MBC_5; break; case 0x1D: c->mbc = MBC_5; c->has_ram = true; break; case 0x1E: c->mbc = MBC_5; c->has_ram = true; c->has_battery = true; break; default: fprintf(stderr, "warning: unknown cart type 0x%02X, assuming MBC1\n", t); c->mbc = MBC_1; c->has_ram = true; break; } } static size_t ram_size_bytes(u8 code) { switch (code) { case 0x00: return 0; case 0x01: return 2 * 1024; // unofficial case 0x02: return 8 * 1024; case 0x03: return 32 * 1024; case 0x04: return 128 * 1024; case 0x05: return 64 * 1024; default: return 0; } } int cart_load(Cart *c, const char *path) { memset(c, 0, sizeof(*c)); FILE *f = fopen(path, "rb"); if (!f) { perror("fopen rom"); return -1; } fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET); if (sz < 0x150) { fprintf(stderr, "rom too small\n"); fclose(f); return -1; } c->rom = malloc(sz); if (fread(c->rom, 1, sz, f) != (size_t)sz) { fclose(f); return -1; } fclose(f); c->rom_size = sz; memcpy(c->title, c->rom + 0x134, 16); c->title[16] = 0; for (int i = 0; i < 16; i++) if ((u8)c->title[i] < 0x20 || (u8)c->title[i] > 0x7e) c->title[i] = 0; u8 cgb_flag = c->rom[0x143]; c->cgb = (cgb_flag == 0x80 || cgb_flag == 0xC0); decode_type(c, c->rom[0x147]); c->rom_banks = 2 << c->rom[0x148]; if ((size_t)c->rom_banks * 0x4000 > c->rom_size) { // trust the file size c->rom_banks = c->rom_size / 0x4000; if (c->rom_banks < 2) c->rom_banks = 2; } c->ram_size = ram_size_bytes(c->rom[0x149]); if (c->mbc == MBC_2) c->ram_size = 512; // built-in 512x4 bit if (c->has_ram && c->ram_size == 0) c->ram_size = 8 * 1024; if (c->ram_size) { c->ram = calloc(1, c->ram_size); c->ram_banks = c->ram_size / 0x2000; if (c->ram_banks < 1) c->ram_banks = 1; } c->rom_bank = 1; c->rom_bank_hi = 0; c->ram_bank = 0; // battery save path: rom path with .sav snprintf(c->save_path, sizeof(c->save_path), "%s.sav", path); if (c->has_battery && c->ram) { FILE *sf = fopen(c->save_path, "rb"); if (sf) { fread(c->ram, 1, c->ram_size, sf); fclose(sf); } } fprintf(stderr, "Loaded '%s' | %s | %d ROM banks (%zuKB) | %zuKB RAM | %s%s\n", c->title, mbc_name(c->mbc), c->rom_banks, c->rom_size / 1024, c->ram_size / 1024, c->cgb ? "CGB " : "DMG ", c->has_battery ? "battery" : ""); return 0; } void cart_save(Cart *c) { if (!c->has_battery || !c->ram) return; FILE *sf = fopen(c->save_path, "wb"); if (sf) { fwrite(c->ram, 1, c->ram_size, sf); fclose(sf); } } void cart_free(Cart *c) { cart_save(c); free(c->rom); free(c->ram); c->rom = c->ram = NULL; } // ---- Bank math helpers ---- static u8 read_rom(Cart *c, u32 bank, u16 off) { if (c->rom_banks) bank %= (u32)c->rom_banks; size_t idx = (size_t)bank * 0x4000 + off; if (idx >= c->rom_size) return 0xFF; return c->rom[idx]; } u8 cart_read(Cart *c, u16 addr) { if (addr < 0x4000) { // bank 0 region (MBC1 mode1 can remap, handle simply) if (c->mbc == MBC_1 && c->bank_mode == 1) { u32 bank = (c->rom_bank_hi << 5); return read_rom(c, bank, addr); } return read_rom(c, 0, addr); } if (addr < 0x8000) { u32 bank; switch (c->mbc) { case MBC_1: { bank = (c->rom_bank & 0x1F) | (c->rom_bank_hi << 5); if ((bank & 0x1F) == 0) bank |= 1; break; } case MBC_2: bank = c->rom_bank & 0x0F; if (bank == 0) bank = 1; break; case MBC_3: bank = c->rom_bank & 0x7F; if (bank == 0) bank = 1; break; case MBC_5: bank = c->rom_bank | (c->rom_bank_hi << 8); break; default: bank = 1; break; } return read_rom(c, bank, addr - 0x4000); } if (addr >= 0xA000 && addr < 0xC000) { if (!c->ram_enabled) return 0xFF; if (c->mbc == MBC_2) { return 0xF0 | (c->ram[(addr - 0xA000) & 0x1FF] & 0x0F); } if (c->mbc == MBC_3 && c->ram_bank >= 0x08) { // RTC register read if (c->ram_bank <= 0x0C) return c->rtc_reg[c->ram_bank - 0x08]; return 0xFF; } if (!c->ram) return 0xFF; u32 bank = c->ram_bank; if (c->mbc == MBC_1 && c->bank_mode == 0) bank = 0; if (c->ram_banks) bank %= (u32)c->ram_banks; size_t idx = bank * 0x2000 + (addr - 0xA000); if (idx >= c->ram_size) return 0xFF; return c->ram[idx]; } return 0xFF; } void cart_write(Cart *c, u16 addr, u8 val) { switch (c->mbc) { case MBC_NONE: break; case MBC_1: if (addr < 0x2000) c->ram_enabled = (val & 0x0F) == 0x0A; else if (addr < 0x4000) c->rom_bank = val & 0x1F; else if (addr < 0x6000) c->rom_bank_hi = val & 0x03; else if (addr < 0x8000) c->bank_mode = val & 1; break; case MBC_2: if (addr < 0x4000) { if (addr & 0x0100) c->rom_bank = val & 0x0F; else c->ram_enabled = (val & 0x0F) == 0x0A; } break; case MBC_3: if (addr < 0x2000) c->ram_enabled = (val & 0x0F) == 0x0A; else if (addr < 0x4000) c->rom_bank = val & 0x7F; else if (addr < 0x6000) c->ram_bank = val; // 0-3 ram, 8-C rtc else if (addr < 0x8000) { if (c->rtc_latch == 0 && val == 1) { /* latch clock (stub) */ } c->rtc_latch = val; } break; case MBC_5: if (addr < 0x2000) c->ram_enabled = (val & 0x0F) == 0x0A; else if (addr < 0x3000) c->rom_bank = val; else if (addr < 0x4000) c->rom_bank_hi = val & 1; else if (addr < 0x6000) c->ram_bank = val & 0x0F; break; } if (addr >= 0xA000 && addr < 0xC000) { if (!c->ram_enabled) return; if (c->mbc == MBC_2) { c->ram[(addr - 0xA000) & 0x1FF] = val & 0x0F; return; } if (c->mbc == MBC_3 && c->ram_bank >= 0x08) { if (c->ram_bank <= 0x0C) c->rtc_reg[c->ram_bank - 0x08] = val; return; } if (!c->ram) return; u32 bank = c->ram_bank; if (c->mbc == MBC_1 && c->bank_mode == 0) bank = 0; if (c->ram_banks) bank %= (u32)c->ram_banks; size_t idx = bank * 0x2000 + (addr - 0xA000); if (idx < c->ram_size) c->ram[idx] = val; } }