/*
Copyright (C) 2006 thoduv
Copyright (C) 2006-2007 Theo Berkau
Copyright (C) 2008-2013 DeSmuME team
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <cstring>
#include "types.h"
#include "mc.h"
#include "readwrite.h"
#include "NDSSystem.h"
static const uint8_t FW_CMD_READ = 0x03;
static const uint8_t FW_CMD_WRITEDISABLE = 0x04;
static const uint8_t FW_CMD_READSTATUS = 0x05;
static const uint8_t FW_CMD_WRITEENABLE = 0x06;
static const uint8_t FW_CMD_PAGEWRITE = 0x0A;
static const uint8_t FW_CMD_READ_ID = 0x9F;
// since r2203 this was 0x00.
// but baby pals proves finally that it should be 0xFF:
// the game reads its initial sound volumes from uninitialized data, and if it is 0, the game will be silent
// if it is 0xFF then the game starts with its sound and music at max, as presumably it is supposed to.
// so in r3303 I finally changed it (no$ appears definitely to initialized to 0xFF)
static const uint8_t kUninitializedSaveDataValue = 0xFF;
static const char *kDesmumeSaveCookie = "|-DESMUME SAVE-|";
//the lookup table from user save types to save parameters
const SAVE_TYPE save_types[] =
{
{"Autodetect", MC_TYPE_AUTODETECT, 1},
{"EEPROM 4kbit", MC_TYPE_EEPROM1, MC_SIZE_4KBITS},
{"EEPROM 64kbit", MC_TYPE_EEPROM2, MC_SIZE_64KBITS},
{"EEPROM 512kbit", MC_TYPE_EEPROM2, MC_SIZE_512KBITS},
{"FRAM 256kbit", MC_TYPE_FRAM, MC_SIZE_256KBITS},
{"FLASH 2Mbit", MC_TYPE_FLASH, MC_SIZE_2MBITS},
{"FLASH 4Mbit", MC_TYPE_FLASH, MC_SIZE_4MBITS},
{"FLASH 8Mbit", MC_TYPE_FLASH, MC_SIZE_8MBITS},
{"FLASH 16Mbit", MC_TYPE_FLASH, MC_SIZE_16MBITS},
{"FLASH 32Mbit", MC_TYPE_FLASH, MC_SIZE_32MBITS},
{"FLASH 64Mbit", MC_TYPE_FLASH, MC_SIZE_64MBITS},
{"FLASH 128Mbit", MC_TYPE_FLASH, MC_SIZE_128MBITS},
{"FLASH 256Mbit", MC_TYPE_FLASH, MC_SIZE_256MBITS},
{"FLASH 512Mbit", MC_TYPE_FLASH, MC_SIZE_512MBITS}
};
void mc_init(memory_chip_t *mc, int type)
{
mc->com = 0;
mc->addr = 0;
mc->addr_shift = 0;
mc->data.clear();
mc->size = 0;
mc->write_enable = false;
mc->writeable_buffer = false;
mc->type = type;
mc->autodetectsize = 0;
switch (mc->type)
{
case MC_TYPE_EEPROM1:
mc->addr_size = 1;
break;
case MC_TYPE_EEPROM2:
case MC_TYPE_FRAM:
mc->addr_size = 2;
break;
case MC_TYPE_FLASH:
mc->addr_size = 3;
}
}
uint8_t *mc_alloc(memory_chip_t *mc, uint32_t size)
{
mc->data.clear();
mc->data.resize(size, 0);
mc->size = size;
mc->writeable_buffer = true;
return nullptr;
}
void mc_free(memory_chip_t *mc)
{
mc->data.clear();
mc_init(mc, 0);
}
void fw_reset_com(memory_chip_t *mc)
{
if (mc->com == FW_CMD_PAGEWRITE)
{
if (mc->fp)
{
fseek(mc->fp, 0, SEEK_SET);
fwrite(&mc->data[0], mc->size, 1, mc->fp);
}
if (mc->isFirmware&&CommonSettings.UseExtFirmware)
{
// copy User Settings 1 to User Settings 0 area
memcpy(&mc->data[0x3FE00], &mc->data[0x3FF00], 0x100);
printf("Firmware: save config");
FILE *fp = fopen(mc->userfile, "wb");
if (fp)
{
if (fwrite(&mc->data[0x3FF00], 1, 0x100, fp) == 0x100) // User Settings
{
if (fwrite(&mc->data[0x0002A], 1, 0x1D6, fp) == 0x1D6) // WiFi Settings
{
if (fwrite(&mc->data[0x3FA00], 1, 0x300, fp) == 0x300) // WiFi AP Settings
printf(" - done\n");
else
printf(" - failed\n");
}
}
fclose(fp);
}
else
printf(" - failed\n");
}
mc->write_enable = false;
}
mc->com = 0;
}
uint8_t fw_transfer(memory_chip_t *mc, uint8_t data)
{
if (mc->com == FW_CMD_READ || mc->com == FW_CMD_PAGEWRITE) /* check if we are in a command that needs 3 bytes address */
{
if (mc->addr_shift > 0) /* if we got a complete address */
{
--mc->addr_shift;
mc->addr |= data << (mc->addr_shift * 8); /* argument is a byte of address */
}
else /* if we have received 3 bytes of address, proceed command */
{
switch (mc->com)
{
case FW_CMD_READ:
if (mc->addr < mc->size) /* check if we can read */
{
data = mc->data[mc->addr]; /* return byte */
++mc->addr; /* then increment address */
}
break;
case FW_CMD_PAGEWRITE:
if (mc->addr < mc->size)
{
mc->data[mc->addr] = data; /* write byte */
++mc->addr;
}
}
}
}
else if (mc->com == FW_CMD_READ_ID)
{
switch (mc->addr)
{
// here is an ID string measured from an old ds fat: 62 16 00 (0x62=sanyo)
// but we chose to use an ST from martin's ds fat string so programs might have a clue as to the firmware size:
// 20 40 12
case 0:
data = 0x20;
mc->addr = 1;
break;
case 1:
data = 0x40; // according to gbatek this is the device ID for the flash on someone's ds fat
mc->addr = 2;
break;
case 2:
data = 0x12;
mc->addr = 0;
}
}
else if (mc->com == FW_CMD_READSTATUS)
return mc->write_enable ? 0x02 : 0x00;
else // finally, check if it's a new command
{
switch (data)
{
case 0:
break; // nothing
case FW_CMD_READ_ID:
mc->addr = 0;
mc->com = FW_CMD_READ_ID;
break;
case FW_CMD_READ: //read command
mc->addr = 0;
mc->addr_shift = 3;
mc->com = FW_CMD_READ;
break;
case FW_CMD_WRITEENABLE: //enable writing
if (mc->writeable_buffer)
mc->write_enable = true;
break;
case FW_CMD_WRITEDISABLE: //disable writing
mc->write_enable = false;
break;
case FW_CMD_PAGEWRITE: //write command
if (mc->write_enable)
{
mc->addr = 0;
mc->addr_shift = 3;
mc->com = FW_CMD_PAGEWRITE;
}
else
data = 0;
break;
case FW_CMD_READSTATUS: //status register command
mc->com = FW_CMD_READSTATUS;
break;
default:
printf("Unhandled FW command: %02X\n", data);
}
}
return data;
}
BackupDevice::BackupDevice()
{
this->reset();
}
// due to unfortunate shortcomings in the emulator architecture,
// at reset-time, we won't have a filename to the .dsv file.
// so the only difference between load_rom (init) and reset is that
// one of them saves the filename
void BackupDevice::load_rom(const std::string &fn)
{
this->filename = fn;
this->reset();
}
void BackupDevice::reset_hardware()
{
this->write_enable = false;
this->com = 0;
this->addr = this->addr_counter = 0;
this->motionInitState = MOTION_INIT_STATE_IDLE;
this->motionFlag = MOTION_FLAG_NONE;
this->state = DETECTING;
this->flushPending = false;
this->lazyFlushPending = false;
}
void BackupDevice::reset()
{
memset(&this->info, 0, sizeof(this->info));
this->reset_hardware();
this->resize(0);
this->data_autodetect.resize(0);
this->addr_size = 0;
this->loadfile();
// if the user has requested a manual choice for backup type, and we havent imported a raw save file, then apply it now
if (this->state == DETECTING && CommonSettings.manualBackupType != MC_TYPE_AUTODETECT)
{
this->state = RUNNING;
int savetype = save_types[CommonSettings.manualBackupType].media_type;
int savesize = save_types[CommonSettings.manualBackupType].size;
this->ensure(savesize); // expand properly if necessary
this->resize(savesize); // truncate if necessary
this->addr_size = this->addr_size_for_old_save_type(savetype);
}
}
// guarantees that the data buffer has room enough for the specified number of bytes
void BackupDevice::ensure(uint32_t Addr)
{
uint32_t size = this->data.size();
if (size < Addr)
this->resize(Addr);
}
void BackupDevice::resize(uint32_t size)
{
size_t old_size = this->data.size();
this->data.resize(size);
for (uint32_t i = old_size; i < size; ++i)
this->data[i] = kUninitializedSaveDataValue;
}
uint32_t BackupDevice::addr_size_for_old_save_size(int bupmem_size)
{
switch (bupmem_size)
{
case MC_SIZE_4KBITS:
return 1;
case MC_SIZE_64KBITS:
case MC_SIZE_256KBITS:
case MC_SIZE_512KBITS:
return 2;
case MC_SIZE_1MBITS:
case MC_SIZE_2MBITS:
case MC_SIZE_4MBITS:
case MC_SIZE_8MBITS:
case MC_SIZE_16MBITS:
case MC_SIZE_64MBITS:
return 3;
default:
return 0xFFFFFFFF;
}
}
uint32_t BackupDevice::addr_size_for_old_save_type(int bupmem_type)
{
switch (bupmem_type)
{
case MC_TYPE_EEPROM1:
return 1;
case MC_TYPE_EEPROM2:
case MC_TYPE_FRAM:
return 2;
case MC_TYPE_FLASH:
return 3;
default:
return 0xFFFFFFFF;
}
}
void BackupDevice::load_old_state(uint32_t addrSize, uint8_t *Data, uint32_t datasize)
{
this->state = RUNNING;
this->addr_size = addrSize;
this->resize(datasize);
memcpy(&this->data[0], Data, datasize);
}
// ======================================================================= no$GBA
// =======================================================================
// =======================================================================
static int no_gba_unpackSAV(const uint8_t *in_buf, uint32_t fsize, uint8_t *out_buf, uint32_t &size)
{
const char no_GBA_HEADER_ID[] = "NocashGbaBackupMediaSavDataFile";
const char no_GBA_HEADER_SRAM_ID[] = "SRAM";
const uint8_t *src = in_buf;
uint8_t *dst = out_buf;
uint32_t src_pos = 0;
uint32_t dst_pos = 0;
uint32_t size_unpacked = 0;
uint32_t compressMethod = 0;
if (fsize < 0x50)
return 1;
for (int i = 0; i < 0x1F; ++i)
if (src[i] != no_GBA_HEADER_ID[i])
return 2;
if (src[0x1F] != 0x1A)
return 2;
for (int i = 0; i < 0x4; ++i)
if (src[i + 0x40] != no_GBA_HEADER_SRAM_ID[i])
return 2;
compressMethod = *(reinterpret_cast<const uint32_t *>(src + 0x44));
if (!compressMethod) // unpacked
{
size_unpacked = *(reinterpret_cast<const uint32_t *>(src + 0x48));
src_pos = 0x4C;
for (uint32_t i = 0; i < size_unpacked; ++i)
dst[dst_pos++] = src[src_pos++];
size = dst_pos;
return 0;
}
if (compressMethod == 1) // packed (method 1)
{
size_unpacked = *(reinterpret_cast<const uint32_t *>(src + 0x4C));
src_pos = 0x50;
while (true)
{
uint8_t cc = src[src_pos++];
if (!cc)
{
size = dst_pos;
return 0;
}
if (cc == 0x80)
{
uint16_t tsize = *(reinterpret_cast<const uint16_t *>(src + src_pos + 1));
for (int t = 0; t < tsize; ++t)
dst[dst_pos++] = src[src_pos];
src_pos += 3;
continue;
}
if (cc > 0x80) // repeat
{
cc -= 0x80;
for (int t = 0; t < cc; ++t)
dst[dst_pos++] = src[src_pos];
++src_pos;
continue;
}
// copy
for (int t = 0; t < cc; ++t)
dst[dst_pos++] = src[src_pos++];
}
}
return 200;
}
static uint32_t no_gba_savTrim(uint8_t *buf, uint32_t size)
{
uint32_t rows = size / 16;
uint32_t pos = (size - 16);
uint8_t *src = buf;
for (unsigned i = 0; i < rows; ++i, pos -= 16)
{
if (src[pos] == 0xFF)
{
for (int t = 0; t < 16; ++t)
if (src[pos + t] != 0xFF)
return pos + 16;
}
else
return pos + 16;
}
return size;
}
static uint32_t no_gba_fillLeft(uint32_t size)
{
for (uint32_t i = 1; i < ARRAY_SIZE(save_types); ++i)
if (size <= static_cast<uint32_t>(save_types[i].size))
return size + (save_types[i].size - size);
return size;
}
bool BackupDevice::load_no_gba(const char *fname)
{
FILE *fsrc = fopen(fname, "rb");
if (fsrc)
{
fseek(fsrc, 0, SEEK_END);
uint32_t fsize = ftell(fsrc);
fseek(fsrc, 0, SEEK_SET);
//printf("Open %s file (size %i bytes)\n", fname, fsize);
auto in_buf = std::unique_ptr<uint8_t[]>(new uint8_t[fsize]);
if (fread(&in_buf[0], 1, fsize, fsrc) == fsize)
{
auto out_buf = std::unique_ptr<uint8_t[]>(new uint8_t[8 * 1024 * 1024 / 8]);
memset(&out_buf[0], 0xFF, 8 * 1024 * 1024 / 8);
uint32_t size = 0;
if (!no_gba_unpackSAV(&in_buf[0], fsize, &out_buf[0], size))
{
//printf("New size %i byte(s)\n", size);
size = no_gba_savTrim(&out_buf[0], size);
//printf("--- new size after trim %i byte(s)\n", size);
size = no_gba_fillLeft(size);
//printf("--- new size after fill %i byte(s)\n", size);
raw_applyUserSettings(size);
this->data.resize(size);
for (uint32_t tt = 0; tt < size; ++tt)
this->data[tt] = out_buf[tt];
//dump back out as a dsv, just to keep things sane
printf("---- Loaded no$GBA save\n");
fclose(fsrc);
return true;
}
}
fclose(fsrc);
}
return false;
}
// ======================================================================= end
// =======================================================================
// ======================================================================= no$GBA
void BackupDevice::loadfile()
{
if (this->filename.empty())
return; // No sense crashing if no filename supplied
auto inf = std::unique_ptr<EMUFILE_FILE>(new EMUFILE_FILE(filename.c_str(), "rb"));
if (inf->fail())
{
// no dsv found; we need to try auto-importing a file with .sav extension
printf("DeSmuME .dsv save file not found. Trying to load an old raw .sav file.\n");
// change extension to sav
char tmp[MAX_PATH];
strcpy(tmp, this->filename.c_str());
tmp[strlen(tmp) - 3] = 0;
strcat(tmp, "sav");
inf.reset(new EMUFILE_FILE(tmp, "rb"));
if (inf->fail())
{
printf("Missing save file %s\n", this->filename.c_str());
return;
}
if (!this->load_no_gba(tmp))
this->load_raw(tmp);
}
else
{
// scan for desmume save footer
int32_t cookieLen = strlen(kDesmumeSaveCookie);
auto sigbuf = std::unique_ptr<char[]>(new char[cookieLen]);
inf->fseek(-cookieLen, SEEK_END);
inf->fread(&sigbuf[0], cookieLen);
int cmp = memcmp(&sigbuf[0], kDesmumeSaveCookie,cookieLen);
if (cmp)
{
// maybe it is a misnamed raw save file. try loading it that way
printf("Not a DeSmuME .dsv save file. Trying to load as raw.\n");
if (!this->load_no_gba(this->filename.c_str()))
this->load_raw(this->filename.c_str());
return;
}
// desmume format
inf->fseek(-cookieLen, SEEK_END);
inf->fseek(-4, SEEK_CUR);
uint32_t version = 0xFFFFFFFF;
read32le(&version, inf.get());
if (version)
{
printf("Unknown save file format\n");
return;
}
inf->fseek(-24, SEEK_CUR);
read32le(&this->info.size, inf.get());
read32le(&this->info.padSize, inf.get());
read32le(&this->info.type, inf.get());
read32le(&this->info.addr_size, inf.get());
read32le(&this->info.mem_size, inf.get());
// establish the save data
this->resize(this->info.size);
inf->fseek(0, SEEK_SET);
if (this->info.size > 0)
inf->fread(&this->data[0], this->info.size); // read all the raw data we have
this->state = RUNNING;
this->addr_size = this->info.addr_size;
// none of the other fields are used right now
}
}
void BackupDevice::raw_applyUserSettings(uint32_t &size, bool manual)
{
// respect the user's choice of backup memory type
if (CommonSettings.manualBackupType == MC_TYPE_AUTODETECT && !manual)
{
this->addr_size = this->addr_size_for_old_save_size(size);
this->resize(size);
}
else
{
uint32_t type = CommonSettings.manualBackupType;
int savetype = save_types[type].media_type;
int savesize = save_types[type].size;
this->addr_size = this->addr_size_for_old_save_type(savetype);
if (static_cast<uint32_t>(savesize) < size)
size = savesize;
this->resize(savesize);
}
this->state = RUNNING;
}
bool BackupDevice::load_raw(const char *fn, uint32_t force_size)
{
FILE *inf = fopen(fn,"rb");
if (!inf)
return false;
fseek(inf, 0, SEEK_END);
uint32_t size = ftell(inf);
uint32_t left = 0;
if (force_size > 0)
{
if (size > force_size)
size = force_size;
else if (size < force_size)
{
left = force_size - size;
size = force_size;
}
}
fseek(inf, 0, SEEK_SET);
this->raw_applyUserSettings(size, force_size > 0);
fread(&this->data[0], 1, size - left, inf);
fclose(inf);
return true;
}