/* FIXME: File format is not endian-safe. Still possible for corrupt/malicious save game data to cause a crash, from blindly reading past the end of the buffer. */ //--------------------------------------------------------------------------- // NEOPOP : Emulator as in Dreamland // // Copyright (c) 2001-2002 by neopop_uk //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // This program 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. See also the license.txt file for // additional informations. //--------------------------------------------------------------------------- #include "neopop.h" #include "flash.h" #include "mem.h" #include namespace MDFN_IEN_NGP { //----------------------------------------------------------------------------- // Local Definitions //----------------------------------------------------------------------------- //This value is used to verify flash data - it is set to the //version number that the flash description was modified for. #define FLASH_VALID_ID 0x0053 //Number of different flash blocks, this should be enough. #define FLASH_MAX_BLOCKS 256 typedef struct { //Flash Id uint16 valid_flash_id; // = FLASH_VALID_ID uint16 block_count; //Number of flash data blocks uint32 total_file_length; // header + block[0 - block_count] } FlashFileHeader; typedef struct { uint32 start_address; // 24 bit address uint16 data_length; // length of following data //Followed by data_length bytes of the actual data. } FlashFileBlockHeader; //----------------------------------------------------------------------------- // Local Data //----------------------------------------------------------------------------- static FlashFileBlockHeader blocks[FLASH_MAX_BLOCKS]; static uint16 block_count; //============================================================================= //----------------------------------------------------------------------------- // optimise_blocks() //----------------------------------------------------------------------------- static void optimise_blocks(void) { int i, j; // Bubble Sort by address for (i = 0; i < block_count - 1; i++) { for (j = i + 1; j < block_count; j++) { //Swap? if (blocks[i].start_address > blocks[j].start_address) { uint32 temp32; uint16 temp16; temp32 = blocks[i].start_address; blocks[i].start_address = blocks[j].start_address; blocks[j].start_address = temp32; temp16 = blocks[i].data_length; blocks[i].data_length = blocks[j].data_length; blocks[j].data_length = temp16; } } } //Join contiguous blocks //Only advance 'i' if required, this will allow subsequent //blocks to be compared to the newly expanded block. for (i = 0; i < block_count - 1; /**/) { //Next block lies within (or borders) this one? if (blocks[i + 1].start_address <= (blocks[i].start_address + blocks[i].data_length)) { //Extend the first block blocks[i].data_length = (uint16)((blocks[i + 1].start_address + blocks[i + 1].data_length) - blocks[i].start_address); //Remove the next one. for (j = i + 2; j < block_count; j++) { blocks[j - 1].start_address = blocks[j].start_address; blocks[j - 1].data_length = blocks[j].data_length; } block_count--; } else { i++; // Try the next block } } } static bool do_flash_read(const uint8 *flashdata) { FlashFileHeader header; const uint8 *fileptr; uint16 i; uint32 j; bool PREV_memory_unlock_flash_write = memory_unlock_flash_write; // kludge, hack, FIXME memcpy(&header, flashdata, sizeof(header)); if (header.block_count > FLASH_MAX_BLOCKS) { return false; //throw MDFN_Error(0, _("FLASH header block_count(%u) > FLASH_MAX_BLOCKS!"), header.block_count); } //Read header block_count = header.block_count; fileptr = flashdata + sizeof(FlashFileHeader); //Copy blocks memory_unlock_flash_write = TRUE; for (i = 0; i < block_count; i++) { FlashFileBlockHeader *current = (FlashFileBlockHeader *)fileptr; fileptr += sizeof(FlashFileBlockHeader); blocks[i].start_address = current->start_address; blocks[i].data_length = current->data_length; //Copy data for (j = 0; j < blocks[i].data_length; j++) { storeB(blocks[i].start_address + j, *fileptr); fileptr++; } } memory_unlock_flash_write = PREV_memory_unlock_flash_write; optimise_blocks(); //Optimise #if 0 //Output block list... for (i = 0; i < block_count; i++) printf("flash block: %06X, %d bytes\n", blocks[i].start_address, blocks[i].data_length); #endif return true; } bool FLASH_LoadNV(const uint8* data, uint32 size) { FlashFileHeader header; std::vector flashdata; //Initialise the internal flash configuration block_count = 0; //Read flash buffer header if (size < sizeof(FlashFileHeader)) return false; memcpy((uint8 *)&header, data, sizeof(FlashFileHeader)); //Verify correct flash id if (header.valid_flash_id != FLASH_VALID_ID) { return false; //throw MDFN_Error(0, _("FLASH header ID is bad!")); } if (header.total_file_length < sizeof(FlashFileHeader) || header.total_file_length > 16384 * 1024) { return false; //throw MDFN_Error(0, _("FLASH header total_file_length is bad!")); } if (size < header.total_file_length) return false; flashdata.resize(header.total_file_length); memcpy(&flashdata[0], data, flashdata.size()); if (!do_flash_read(&flashdata[0])) return false; return true; } //----------------------------------------------------------------------------- // flash_write() //----------------------------------------------------------------------------- void flash_write(uint32 start_address, uint16 length) { uint16 i; //Now we need a new flash command before the next flash write will work! memory_flash_command = FALSE; // system_debug_message("flash write: %06X, %d bytes", start_address, length); for (i = 0; i < block_count; i++) { //Got this block with enough bytes to cover it if (blocks[i].start_address == start_address && blocks[i].data_length >= length) { return; //Nothing to do, block already registered. } //Got this block with but it's length is too short if (blocks[i].start_address == start_address && blocks[i].data_length < length) { blocks[i].data_length = length; //Enlarge block updating. return; } } if (block_count >= FLASH_MAX_BLOCKS) { MDFN_PrintError(_("[FLASH] Block list overflow!")); return; } else { // New block needs to be added blocks[block_count].start_address = start_address; blocks[block_count].data_length = length; block_count++; } } static void make_flash_commit(std::vector &flashdata) { FlashFileHeader header; uint8 *fileptr; flashdata.clear(); //No flash data? if (block_count == 0) return; //Optimise before writing optimise_blocks(); //Build a header; header.valid_flash_id = FLASH_VALID_ID; header.block_count = block_count; header.total_file_length = sizeof(FlashFileHeader); for (int i = 0; i < block_count; i++) { header.total_file_length += sizeof(FlashFileBlockHeader); header.total_file_length += blocks[i].data_length; } //Write the flash data flashdata.resize(header.total_file_length); //Copy header memcpy(&flashdata[0], &header, sizeof(FlashFileHeader)); fileptr = &flashdata[0] + sizeof(FlashFileHeader); //Copy blocks for (int i = 0; i < block_count; i++) { memcpy(fileptr, &blocks[i], sizeof(FlashFileBlockHeader)); fileptr += sizeof(FlashFileBlockHeader); //Copy data for (uint32 j = 0; j < blocks[i].data_length; j++) { *fileptr = loadB(blocks[i].start_address + j); fileptr++; } } } bool FLASH_IsModified() { return block_count > 0; } void FLASH_SaveNV(void (*callback)(const uint8* data, uint32 size)) { std::vector flashdata; make_flash_commit(flashdata); if (flashdata.size() > 0) { callback(&flashdata[0], flashdata.size()); } } }