324 lines
8.0 KiB
C++
324 lines
8.0 KiB
C++
/*
|
|
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 <vector>
|
|
|
|
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<uint8> 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<uint8> &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<uint8> flashdata;
|
|
|
|
make_flash_commit(flashdata);
|
|
|
|
if (flashdata.size() > 0)
|
|
{
|
|
callback(&flashdata[0], flashdata.size());
|
|
}
|
|
}
|
|
}
|