mgba/src/gba/gba-savedata.c

322 lines
9.2 KiB
C
Raw Normal View History

2013-04-28 02:59:41 +00:00
#include "gba-savedata.h"
#include "gba.h"
#include <errno.h>
2013-04-28 02:59:41 +00:00
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
2013-09-24 06:04:15 +00:00
static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
static void _flashErase(struct GBASavedata* savedata);
static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart);
2013-04-28 02:59:41 +00:00
void GBASavedataInit(struct GBASavedata* savedata, const char* filename) {
savedata->type = SAVEDATA_NONE;
savedata->data = 0;
2013-09-24 06:04:15 +00:00
savedata->command = EEPROM_COMMAND_NULL;
savedata->flashState = FLASH_STATE_RAW;
2013-04-28 02:59:41 +00:00
savedata->fd = -1;
savedata->filename = filename;
}
void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) {
if (savedata->type != SAVEDATA_NONE) {
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
return;
}
savedata->type = type;
}
2013-04-28 02:59:41 +00:00
void GBASavedataDeinit(struct GBASavedata* savedata) {
switch (savedata->type) {
case SAVEDATA_SRAM:
munmap(savedata->data, SIZE_CART_SRAM);
break;
case SAVEDATA_FLASH512:
munmap(savedata->data, SIZE_CART_FLASH512);
break;
case SAVEDATA_FLASH1M:
munmap(savedata->data, SIZE_CART_FLASH1M);
break;
case SAVEDATA_EEPROM:
munmap(savedata->data, SIZE_CART_EEPROM);
break;
default:
break;
}
close(savedata->fd);
savedata->type = SAVEDATA_NONE;
}
void GBASavedataInitFlash(struct GBASavedata* savedata) {
if (savedata->type == SAVEDATA_NONE) {
savedata->type = SAVEDATA_FLASH512;
}
if (savedata->type != SAVEDATA_FLASH512 && savedata->type != SAVEDATA_FLASH1M) {
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
return;
}
2013-04-28 02:59:41 +00:00
savedata->fd = open(savedata->filename, O_RDWR | O_CREAT, 0666);
off_t end;
int flags = MAP_SHARED;
2013-04-28 02:59:41 +00:00
if (savedata->fd < 0) {
GBALog(0, GBA_LOG_ERROR, "Cannot open savedata file %s (errno: %d)", savedata->filename, errno);
end = 0;
flags |= MAP_ANON;
} else {
end = lseek(savedata->fd, 0, SEEK_END);
if (end < SIZE_CART_FLASH512) {
ftruncate(savedata->fd, SIZE_CART_FLASH1M);
}
2013-04-28 02:59:41 +00:00
}
// mmap enough so that we can expand the file if we need to
savedata->data = mmap(0, SIZE_CART_FLASH1M, PROT_READ | PROT_WRITE, flags, savedata->fd, 0);
2013-09-24 06:04:15 +00:00
savedata->currentBank = savedata->data;
2013-04-28 02:59:41 +00:00
if (end < SIZE_CART_FLASH512) {
memset(&savedata->data[end], 0xFF, SIZE_CART_FLASH512 - end);
2013-04-28 02:59:41 +00:00
}
}
2013-04-28 03:25:31 +00:00
void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
if (savedata->type == SAVEDATA_NONE) {
savedata->type = SAVEDATA_EEPROM;
} else {
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
return;
}
2013-04-28 03:25:31 +00:00
savedata->fd = open(savedata->filename, O_RDWR | O_CREAT, 0666);
off_t end;
int flags = MAP_SHARED;
2013-04-28 03:25:31 +00:00
if (savedata->fd < 0) {
GBALog(0, GBA_LOG_ERROR, "Cannot open savedata file %s (errno: %d)", savedata->filename, errno);
end = 0;
flags |= MAP_ANON;
} else {
end = lseek(savedata->fd, 0, SEEK_END);
if (end < SIZE_CART_EEPROM) {
ftruncate(savedata->fd, SIZE_CART_EEPROM);
}
2013-04-28 03:25:31 +00:00
}
savedata->data = mmap(0, SIZE_CART_EEPROM, PROT_READ | PROT_WRITE, flags, savedata->fd, 0);
2013-04-28 03:25:31 +00:00
if (end < SIZE_CART_EEPROM) {
memset(&savedata->data[end], 0xFF, SIZE_CART_EEPROM - end);
}
}
2013-04-28 02:59:41 +00:00
void GBASavedataInitSRAM(struct GBASavedata* savedata) {
if (savedata->type == SAVEDATA_NONE) {
savedata->type = SAVEDATA_SRAM;
} else {
GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");
return;
}
2013-04-28 02:59:41 +00:00
savedata->fd = open(savedata->filename, O_RDWR | O_CREAT, 0666);
off_t end;
int flags = MAP_SHARED;
if (savedata->fd < 0) {
GBALog(0, GBA_LOG_ERROR, "Cannot open savedata file %s (errno: %d)", savedata->filename, errno);
2013-04-28 02:59:41 +00:00
end = 0;
flags |= MAP_ANON;
} else {
end = lseek(savedata->fd, 0, SEEK_END);
if (end < SIZE_CART_SRAM) {
ftruncate(savedata->fd, SIZE_CART_SRAM);
}
}
savedata->data = mmap(0, SIZE_CART_SRAM, PROT_READ | PROT_WRITE, flags, savedata->fd, 0);
if (end < SIZE_CART_SRAM) {
memset(&savedata->data[end], 0xFF, SIZE_CART_SRAM - end);
}
}
2013-09-24 06:04:15 +00:00
uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
if (savedata->command == FLASH_COMMAND_ID) {
if (savedata->type == SAVEDATA_FLASH512) {
if (address < 2) {
return FLASH_MFG_PANASONIC >> (address * 8);
}
} else if (savedata->type == SAVEDATA_FLASH1M) {
if (address < 2) {
return FLASH_MFG_SANYO >> (address * 8);
}
}
}
return savedata->currentBank[address];
}
2013-04-28 03:25:31 +00:00
2013-09-24 06:04:15 +00:00
void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8_t value) {
switch (savedata->flashState) {
case FLASH_STATE_RAW:
switch (savedata->command) {
case FLASH_COMMAND_PROGRAM:
savedata->currentBank[address] = value;
savedata->command = FLASH_COMMAND_NONE;
break;
case FLASH_COMMAND_SWITCH_BANK:
if (address == 0 && value < 2) {
_flashSwitchBank(savedata, value);
} else {
GBALog(0, GBA_LOG_GAME_ERROR, "Bad flash bank switch");
savedata->command = FLASH_COMMAND_NONE;
}
2013-09-25 11:48:27 +00:00
savedata->command = FLASH_COMMAND_NONE;
2013-09-24 06:04:15 +00:00
break;
default:
if (address == FLASH_BASE_HI && value == FLASH_COMMAND_START) {
savedata->flashState = FLASH_STATE_START;
} else {
GBALog(0, GBA_LOG_GAME_ERROR, "Bad flash write: %#04x = %#02x", address, value);
}
break;
}
break;
case FLASH_STATE_START:
if (address == FLASH_BASE_LO && value == FLASH_COMMAND_CONTINUE) {
savedata->flashState = FLASH_STATE_CONTINUE;
} else {
GBALog(0, GBA_LOG_GAME_ERROR, "Bad flash write: %#04x = %#02x", address, value);
savedata->flashState = FLASH_STATE_RAW;
}
break;
case FLASH_STATE_CONTINUE:
savedata->flashState = FLASH_STATE_RAW;
if (address == FLASH_BASE_HI) {
switch (savedata->command) {
case FLASH_COMMAND_NONE:
switch (value) {
case FLASH_COMMAND_ERASE:
case FLASH_COMMAND_ID:
case FLASH_COMMAND_PROGRAM:
case FLASH_COMMAND_SWITCH_BANK:
savedata->command = value;
break;
default:
GBALog(0, GBA_LOG_GAME_ERROR, "Unsupported flash operation: %#02x", value);
break;
}
break;
case FLASH_COMMAND_ERASE:
switch (value) {
case FLASH_COMMAND_ERASE_CHIP:
_flashErase(savedata);
break;
default:
GBALog(0, GBA_LOG_GAME_ERROR, "Unsupported flash erase operation: %#02x", value);
break;
}
savedata->command = FLASH_COMMAND_NONE;
break;
case FLASH_COMMAND_ID:
if (value == FLASH_COMMAND_TERMINATE) {
savedata->command = FLASH_COMMAND_NONE;
}
break;
default:
GBALog(0, GBA_LOG_ERROR, "Flash entered bad state: %#02x", savedata->command);
savedata->command = FLASH_COMMAND_NONE;
break;
}
} else if (savedata->command == FLASH_COMMAND_ERASE) {
if (value == FLASH_COMMAND_ERASE_SECTOR) {
_flashEraseSector(savedata, address);
savedata->command = FLASH_COMMAND_NONE;
} else {
GBALog(0, GBA_LOG_GAME_ERROR, "Unsupported flash erase operation: %#02x", value);
}
}
break;
}
2013-04-28 02:59:41 +00:00
}
2013-04-28 03:25:31 +00:00
2013-04-28 04:51:58 +00:00
void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize) {
switch (savedata->command) {
// Read header
case EEPROM_COMMAND_NULL:
default:
savedata->command = value & 0x1;
break;
case EEPROM_COMMAND_PENDING:
savedata->command <<= 1;
savedata->command |= value & 0x1;
if (savedata->command == EEPROM_COMMAND_WRITE) {
savedata->addressBits = writeSize - 64 - 2;
savedata->writeAddress = 0;
} else {
savedata->addressBits = writeSize - 2;
savedata->readAddress = 0;
}
break;
// Do commands
case EEPROM_COMMAND_WRITE:
// Write
if (writeSize > 65) {
savedata->writeAddress <<= 1;
savedata->writeAddress |= (value & 0x1) << 6;
} else if (writeSize == 1) {
savedata->command = EEPROM_COMMAND_NULL;
savedata->writePending = 1;
} else {
uint8_t current = savedata->data[savedata->writeAddress >> 3];
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
savedata->data[savedata->writeAddress >> 3] = current;
++savedata->writeAddress;
}
break;
case EEPROM_COMMAND_READ_PENDING:
// Read
if (writeSize > 1) {
savedata->readAddress <<= 1;
if (value & 0x1) {
savedata->readAddress |= 0x40;
}
} else {
savedata->readBitsRemaining = 68;
savedata->command = EEPROM_COMMAND_READ;
}
break;
}
2013-04-28 03:25:31 +00:00
}
uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
2013-04-28 04:51:58 +00:00
if (savedata->command != EEPROM_COMMAND_READ) {
return 1;
}
--savedata->readBitsRemaining;
if (savedata->readBitsRemaining < 64) {
int step = 63 - savedata->readBitsRemaining;
uint8_t data = savedata->data[(savedata->readAddress + step) >> 3] >> (0x7 - (step & 0x7));
if (!savedata->readBitsRemaining) {
savedata->command = EEPROM_COMMAND_NULL;
}
return data & 0x1;
}
2013-04-28 03:25:31 +00:00
return 0;
}
2013-09-24 06:04:15 +00:00
void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
savedata->currentBank = &savedata->data[bank << 16];
if (bank > 0) {
savedata->type = SAVEDATA_FLASH1M;
2013-09-25 11:48:27 +00:00
ftruncate(savedata->fd, SIZE_CART_FLASH1M);
2013-09-24 06:04:15 +00:00
}
}
void _flashErase(struct GBASavedata* savedata) {
size_t size = 0x10000;
if (savedata->type == SAVEDATA_FLASH1M) {
size = 0x20000;
}
memset(savedata->data, 0xFF, size);
}
void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
size_t size = 0x1000;
if (savedata->type == SAVEDATA_FLASH1M) {
GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at %#04x", sectorStart);
}
memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size);
}