/* 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 . */ #include #include #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(src + 0x44)); if (!compressMethod) // unpacked { size_unpacked = *(reinterpret_cast(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(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(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++]; } size = dst_pos; return 0; } 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(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(new uint8_t[fsize]); if (fread(&in_buf[0], 1, fsize, fsrc) == fsize) { auto out_buf = std::unique_ptr(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(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(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(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; }