diff options
| author | gbc dev <gbc@localhost> | 2026-07-04 22:05:31 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-04 22:05:31 +0200 |
| commit | 0d0f4f1d23851c0c34e518834f7d316e61dec1f3 (patch) | |
| tree | e46296c247617ee0ead1fb93e9d0dc475a07f9b9 /src/cart.c | |
| download | sl0pboy-0d0f4f1d23851c0c34e518834f7d316e61dec1f3.tar.gz sl0pboy-0d0f4f1d23851c0c34e518834f7d316e61dec1f3.tar.xz sl0pboy-0d0f4f1d23851c0c34e518834f7d316e61dec1f3.zip | |
scaffolding: build system, cartridge loader with MBC1/2/3/5, core types
Diffstat (limited to 'src/cart.c')
| -rw-r--r-- | src/cart.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/src/cart.c b/src/cart.c new file mode 100644 index 0000000..95cd279 --- /dev/null +++ b/src/cart.c @@ -0,0 +1,253 @@ +#include "cart.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +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; + } +} |
