Files
gbemu/lib/cart.c
2025-06-13 15:33:13 -06:00

607 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <cart.h>
#include <common.h>
#include <string.h>
#ifdef __windows__
#include <windows.h>
#endif
static cart_context ctx;
bool cart_need_save () {
return ctx.need_save;
}
bool cart_mbc1() {
return BETWEEN(ctx.header->type, 1, 3);
}
bool cart_mbc2() {
return BETWEEN(ctx.header->type, 5, 6);
}
bool cart_mbc3() {
return BETWEEN(ctx.header->type, 15, 19);
}
bool cart_mbc5() {
return BETWEEN(ctx.header->type, 25, 30);
}
bool cart_mbc() {
return cart_mbc1() || cart_mbc2() || cart_mbc3() || cart_mbc5();
}
bool cart_battery() {
return ctx.header->type == 3 || BETWEEN(ctx.header->type, 15, 16) || ctx.header->type == 19 || ctx.header->type == 6 || ctx.header->type == 26 || ctx.header->type == 29;
}
static const char *ROM_TYPES[] = {
"ROM ONLY",
"MBC1",
"MBC1+RAM",
"MBC1+RAM+BATTERY",
"0X04 ???",
"MBC2",
"MBC2+BATTERY",
"0X07 ???",
"ROM+RAM 1",
"ROM+RAM+BATTERY 1",
"0X0A ???",
"MMM01",
"MMM01+RAM",
"MMM01+RAM+BATTERY",
"0X0E ???",
"MBC3+TIMER+BATTERY",
"MBC3+TIMER+RAM+BATTERY 2",
"MBC3",
"MBC3+RAM 2",
"MBC3+RAM+BATTERY 2",
"0X14 ???",
"0X15 ???",
"0X16 ???",
"0X17 ???",
"0X18 ???",
"MBC5",
"MBC5+RAM",
"MBC5+RAM+BATTERY",
"MBC5+RUMBLE",
"MBC5+RUMBLE+RAM",
"MBC5+RUMBLE+RAM+BATTERY",
"0X1F ???",
"MBC6",
"0X21 ???",
"MBC7+SENSOR+RUMBLE+RAM+BATTERY "
};
static const char *LIC_CODE[0xA5] = {
[0x00] = "None",
[0x01] = "Nintendo R&D1",
[0x08] = "Capcom",
[0x13] = "Electronic Arts",
[0x18] = "Hudson Soft",
[0x19] = "b-ai",
[0x20] = "kss",
[0x22] = "pow",
[0x24] = "PCM Complete",
[0x25] = "san-x",
[0x28] = "Kemco Japan",
[0x29] = "seta",
[0x30] = "Viacom",
[0x31] = "Nintendo",
[0x32] = "Bandai",
[0x33] = "Ocean/Acclaim",
[0x34] = "Konami",
[0x35] = "Hector",
[0x37] = "Taito",
[0x38] = "Hudson",
[0x39] = "Banpresto",
[0x41] = "Ubi Soft",
[0x42] = "Atlus",
[0x44] = "Malibu",
[0x46] = "angel",
[0x47] = "Bullet-Proof",
[0x49] = "irem",
[0x50] = "Absolute",
[0x51] = "Acclaim",
[0x52] = "Activision",
[0x53] = "American sammy",
[0x54] = "Konami",
[0x55] = "Hi tech entertainment",
[0x56] = "LJN",
[0x57] = "Matchbox",
[0x58] = "Mattel",
[0x59] = "Milton Bradley",
[0x60] = "Titus",
[0x61] = "Virgin",
[0x64] = "LucasArts",
[0x67] = "Ocean",
[0x69] = "Electronic Arts",
[0x70] = "Infogrames",
[0x71] = "Interplay",
[0x72] = "Broderbund",
[0x73] = "sculptured",
[0x75] = "sci",
[0x78] = "THQ",
[0x79] = "Accolade",
[0x80] = "misawa",
[0x83] = "lozc",
[0x86] = "Tokuma Shoten Intermedia",
[0x87] = "Tsukuda Original",
[0x91] = "Chunsoft",
[0x92] = "Video system",
[0x93] = "Ocean/Acclaim",
[0x95] = "Varie",
[0x96] = "Yonezawa/spal",
[0x97] = "Kaneko",
[0x99] = "Pack in soft",
[0xA4] = "Konami (Yu-Gi-Oh!)"
};
rom_header *get_rom_header() {
return ctx.header;
}
const char *cart_type_name() {
if(ctx.header->type <= 0x22) {
return ROM_TYPES[ctx.header->type];
}
return "UNKNOWN";
}
const char *cart_lic_name() {
if(ctx.header->new_lic_code <= 0xA4) {
return LIC_CODE[ctx.header->lic_code];
}
return "UNKNOWN";
}
void cart_setup_bamking() {
for (int i=0; i<16; i++) {
ctx.ram_banks[i] = 0;
if((ctx.header->ram_size == 2 && i == 0) ||
(ctx.header->ram_size == 3 && i < 4) ||
(ctx.header->ram_size == 5 && i < 8) ||
(ctx.header->ram_size == 4)) {
ctx.ram_banks[i] = malloc(0x2000);
memset(ctx.ram_banks[i], 0, 0x2000);
}
}
if(cart_mbc2()) {
ctx.ram_banks[0] = malloc(0x2000);
memset(ctx.ram_banks[0], 0, 0x2000);
}
ctx.ram_bank = ctx.ram_banks[0];
ctx.rom_bank_x = ctx.rom_data + 0x4000;
ctx.rom_bank_x2 = ctx.rom_data;
}
bool cart_init() {
char filename[1024];
u32 rom_size;
u8 *rom_data;
rom_header *header;
//mbc1 data
bool ram_enabled;
bool ram_banking;
u8 *rom_bank_x;
u8 *rom_bank_x2;
u8 banking_mode;
u8 rom_bank_value;
u8 rom_bank_value_2;
u8 ram_bank_value;
u8 *ram_bank;
u8 *ram_banks[16];
//battery
bool battery;
bool need_save;
ctx.ram_enabled = 0;
ctx.ram_banking = 0;
ctx.rom_bank_x = ctx.rom_data;
ctx.rom_bank_x2 = ctx.rom_data;
ctx.banking_mode = 0;
ctx.rom_bank_value = 1;
ctx.rom_bank_value_2 = 0;
ctx.ram_bank_value = 0;
cart_setup_bamking();
ctx.need_save = 0;
if(ctx.battery) {
cart_battery_load();
}
}
bool cart_load(char *cart) {
snprintf(ctx.filename, sizeof(ctx.filename), "%s", cart);
FILE *fp = fopen(cart, "r");
if (!fp) {
printf("Failed to open: %s\n", cart);
return false;
}
printf("Opened: %s\n", cart),
fseek(fp, 0, SEEK_END);
ctx.rom_size = ftell(fp);
rewind(fp);
ctx.rom_data = malloc(ctx.rom_size);
fread(ctx.rom_data, ctx.rom_size, 1, fp);
fclose(fp);
ctx.header = (rom_header *)(ctx.rom_data + 0x100);
ctx.header->title[15] = 0;
ctx.battery = cart_battery();
ctx.need_save = false;
printf("Cartridge Loaded:\n");
printf("\t Title : %s\n", ctx.header->title);
printf("\t Type : %2.2X (%s)\n", ctx.header->type, cart_type_name());
printf("\t ROM Size : %d KB\n", 32 << ctx.header->rom_size);
printf("\t RAM Size : %2.2X\n", ctx.header->ram_size);
printf("\t LIC Code : %2.2X (%s)\n", ctx.header->lic_code, cart_lic_name());
printf("\t ROM Vers : %2.2X\n", ctx.header->version);
cart_setup_bamking();
u16 x = 0;
for (u16 i=0X0134; i<=0X014C; i++) {
x = x - ctx.rom_data[i] -1;
}
printf("\t Checksum : %2.2X (%s)\n", ctx.header->checksum, (x & 0xFF) == ctx.header->checksum ? "PASSED" : "Failed");
if((x & 0xFF) != ctx.header->checksum) {
fprintf(stderr, "WARNING!!! The header checksum does not match! ROM may be corrupt or invalid!\n");
}
if(cart_mbc1() && ctx.header->rom_size >= 5) {
int count = 0;
for(int i = 1; i < 4; i++) {
u32 start_addr = (i << 18);
bool same = true;
for(int j = 0x104; j < 0x134; j++) {
if(ctx.rom_data[start_addr | j] != ctx.rom_data[j]) {
same = false;
break;
}
}
if(same)
count++;
}
if(count >= 2) {
ctx.is_multicart = true;
}
}
if(ctx.battery) {
cart_battery_load();
}
return true;
}
u8 cart_read(u16 address){
if(cart_mbc1() && ctx.header->rom_size >= 5 && address < 0x4000) {
return ctx.rom_bank_x2[address];
}
if(!cart_mbc() || address < 0x4000) {
return ctx.rom_data[address];
}
if((address & 0xE000) == 0xA000) {
if(cart_mbc2())
address &= 0xE1FF;
if (!ctx.ram_enabled) {
return 0xFF;
}
if(!ctx.ram_bank) {
return 0xFF;
}
return cart_mbc2() ? ctx.ram_bank[address - 0xA000] | 0xF0 : ctx.ram_bank[address - 0xA000];
}
return ctx.rom_bank_x[address - 0x4000];
}
void cart_write(u16 address, u8 value){
//printf("Cart Write: %04X\n", address);
if(!cart_mbc()) {
return;
}
if(cart_mbc2()) {
if(address < 0x4000){
if(address & 0x100) {
value &= 0b1111;
if(value == 0) {
value = 1;
}
switch(ctx.header->rom_size) {
case 0: value &= 0b1; break;
case 1: value &= 0b11; break;
case 2: value &= 0b111; break;
default: break;
}
ctx.rom_bank_value = value;
ctx.rom_bank_x = ctx.rom_data + (0x4000 * ctx.rom_bank_value);
} else {
ctx.ram_enabled = ((value & 0xF) == 0xA);
}
}
} else {
if(address < 0x2000) {
ctx.ram_enabled = ((value & 0xF) == 0xA);
}
if((address & 0xE000) == 0x2000) {
//rom bank
if(cart_mbc3()) {
value &= 0b1111111;
if(value == 0) {
value = 1;
}
ctx.rom_bank_value = value;
ctx.rom_bank_x = ctx.rom_data + (0x4000 * ctx.rom_bank_value);
} else if(cart_mbc5()) {
if(address < 0x3000){
switch(ctx.header->rom_size) {
case 0: value &= 0b1; break;
case 1: value &= 0b11; break;
case 2: value &= 0b111; break;
case 3: value &= 0b1111; break;
case 4: value &= 0b11111; break;
case 5: value &= 0b111111; break;
case 6: value &= 0b1111111; break;
default: break;
}
ctx.rom_bank_value = value;
ctx.rom_bank_x = ctx.rom_data + (0x4000 * ctx.rom_bank_value + ctx.rom_bank_value_2);
} else {
if(ctx.header->rom_size == 8) {
ctx.rom_bank_value_2 = (value & 0b1) << 8;
ctx.rom_bank_x = ctx.rom_data + (0x4000 * ctx.rom_bank_value + ctx.rom_bank_value_2);
}
}
} else {
//printf("Bank Value: %d\n", value);
value &= 0b11111;
if(value == 0) {
value = 1;
}
if(ctx.is_multicart)
value &= 0b1111;
//printf("Bank Value after filter: %d\n", value);
switch(ctx.header->rom_size) {
case 0: value &= 0b1; break;
case 1: value &= 0b11; break;
case 2: value &= 0b111; break;
case 3: value &= 0b1111; break;
default: break;
}
ctx.rom_bank_value = value;
if(ctx.header->rom_size >= 5) {
ctx.rom_bank_x = ctx.rom_data + (0x4000 * (ctx.rom_bank_value+ctx.rom_bank_value_2));
//printf("(REG1 >5) Banked %d\n", ctx.rom_bank_value+ctx.rom_bank_value_2);
return;
}
ctx.rom_bank_x = ctx.rom_data + (0x4000 * ctx.rom_bank_value);
//printf("(REG1 <5) Banked %d\n", ctx.rom_bank_value);
}
}
if((address & 0xE000) == 0x4000) {
//ram bank number
if(cart_mbc1() && ctx.header->rom_size >= 5) {
if(ctx.header->rom_size == 5)
value &= 0b1;
ctx.rom_bank_value_2 = (value & 0b11) << 5;
if(ctx.is_multicart)
ctx.rom_bank_value_2 = (value & 0b11) << 4;
ctx.rom_bank_x = ctx.rom_data + (0x4000 * (ctx.rom_bank_value+ctx.rom_bank_value_2));
//printf("(REG2 >5) Banked %d\n", ctx.rom_bank_value+ctx.rom_bank_value_2);
if(ctx.banking_mode)
ctx.rom_bank_x2 = ctx.rom_data + (0x4000 * (ctx.rom_bank_value_2));
return;
}
ctx.ram_bank_value = value & 0b11;
if(cart_mbc5())
ctx.ram_bank_value = value & 0xF;
if(ctx.ram_banking || cart_mbc5()) {
if(cart_need_save()) {
cart_battery_save();
}
if((ctx.header->ram_size == 2 && ctx.ram_bank_value == 0) ||
(ctx.header->ram_size == 3 && ctx.ram_bank_value < 4) ||
(ctx.header->ram_size == 5 && ctx.ram_bank_value < 8) ||
(ctx.header->ram_size == 4)) {
ctx.ram_bank = ctx.ram_banks[ctx.ram_bank_value];
}
}
}
if((address & 0xE000) == 0x6000) {
//banking mode select
if(cart_mbc1()){
ctx.banking_mode = value & 1;
ctx.ram_banking = ctx.banking_mode;
if(cart_mbc1() && ctx.header->rom_size >= 5) {
if(ctx.banking_mode) {
ctx.rom_bank_value_2 = (value & 0b11) << 5;
ctx.rom_bank_x = ctx.rom_data + (0x4000 * (ctx.rom_bank_value+ctx.rom_bank_value_2));
ctx.rom_bank_x2 = ctx.rom_data + (0x4000 * (ctx.rom_bank_value_2));
} else {
ctx.rom_bank_x = ctx.rom_data + (0x4000 * (ctx.rom_bank_value));
ctx.rom_bank_x2 = ctx.rom_data;
}
return;
}
if (ctx.ram_banking) {
if(cart_need_save()) {
cart_battery_save();
}
if((ctx.header->ram_size == 2 && ctx.ram_bank_value == 0) ||
(ctx.header->ram_size == 3 && ctx.ram_bank_value < 4) ||
(ctx.header->ram_size == 5 && ctx.ram_bank_value < 8) ||
(ctx.header->ram_size == 4)) {
ctx.ram_bank = ctx.ram_banks[ctx.ram_bank_value];
}
} else {
ctx.ram_bank = ctx.ram_banks[0];
}
}
}
}
if((address & 0xE000) == 0xA000) {
if(cart_mbc2())
address &= 0xE1FF;
if(!ctx.ram_enabled){
return;
}
if(!ctx.ram_bank)
return;
ctx.ram_bank[address - 0xA000] = cart_mbc2() ? value & 0xF : value;
if(ctx.battery) {
ctx.need_save = true;
}
}
}
bool cart_battery_load(){
#ifdef __windows__
char *filename = strrchr(ctx.filename, '\\');
#else
char *filename = strrchr(ctx.filename, '/');
#endif
filename++;
char fn[1048];
#ifdef __windows__
char *profile = getenv("USERPROFILE");
sprintf(fn, "%s/Documents/gbemu/saves/%s.battery", profile, filename);
#else
char *profile = getenv("HOME");
sprintf(fn, "%s/.config/gbemu/saves/%s.battery", profile, filename);
#endif
printf("loading %s\n", fn);
FILE *fp = fopen(fn, "rb");
if(!fp) {
printf("No battery save found.");
return true;
}
fread(ctx.ram_bank, 0x2000, 1, fp);
fclose(fp);
}
bool cart_battery_save(){
#ifdef __windows__
char *filename = strrchr(ctx.filename, '\\');
#else
char *filename = strrchr(ctx.filename, '/');
#endif
filename++;
char fn[1048];
#ifdef __windows__
char *profile = getenv("USERPROFILE");
sprintf(fn, "%s/Documents/gbemu/saves/%s.battery", profile, filename);
#else
char *profile = getenv("HOME");
sprintf(fn, "%s/.config/gbemu/saves/%s.battery", profile, filename);
#endif
FILE *fp = fopen(fn, "wb");
if(!fp) {
fprintf(stderr, "unable to open: %s\n", fn);
}
fwrite(ctx.ram_bank, 0x2000, 1, fp);
fclose(fp);
}
u8 cart_get_rom_bank() {
return ctx.rom_bank_value;
}
void cart_load_state(const cart_state* state){
ctx.ram_enabled = state->ram_enabled;
ctx.ram_banking = state->ram_banking;
ctx.banking_mode = state->banking_mode;
ctx.rom_bank_value = state->rom_bank_value;
ctx.rom_bank_value_2 = state->rom_bank_value_2;
ctx.ram_bank_value = state->ram_bank_value;
ctx.need_save = state->need_save;
for(int i = 0; i < 16; i++) {
if(ctx.ram_banks[i]){
memcpy(ctx.ram_banks[i], &state->ram_banks[i], 0x2000);
}
}
if(ctx.banking_mode) {
ctx.rom_bank_x = ctx.rom_data + (0x4000 * (ctx.rom_bank_value+ctx.rom_bank_value_2));
ctx.rom_bank_x2 = ctx.rom_data + (0x4000 * (ctx.rom_bank_value_2));
} else {
ctx.rom_bank_x = ctx.rom_data + (0x4000 * (ctx.rom_bank_value));
ctx.rom_bank_x2 = ctx.rom_data;
}
if(ctx.ram_banking) {
ctx.ram_bank = ctx.ram_banks[ctx.ram_bank_value];
} else {
ctx.ram_bank = ctx.ram_banks[0];
}
if(ctx.battery) {
cart_battery_load();
}
}
void cart_save_state(cart_state* state){
state->ram_enabled = ctx.ram_enabled;
state->ram_banking = ctx.ram_banking;
state->banking_mode = ctx.banking_mode;
state->rom_bank_value = ctx.rom_bank_value;
state->rom_bank_value_2 = ctx.rom_bank_value_2;
state->ram_bank_value = ctx.ram_bank_value;
state->need_save = ctx.need_save;
for (int i=0; i<16; i++) {
if(ctx.ram_banks[i]) {
memcpy((&state->ram_banks[i]), ctx.ram_banks[i], 0x2000);
}
}
}
cart_context *cart_get_context(){
return &ctx;
}