Skip to content

Commit

Permalink
burner/statec.cpp: bullet-proof states, finally. All old states and n…
Browse files Browse the repository at this point in the history
…vram files are deprecated. DO NOT UPGRADE if you're in the midst of playing something w/states or nvram
  • Loading branch information
dinkc64 committed Oct 9, 2024
1 parent 67226e2 commit 2fdf290
Showing 1 changed file with 141 additions and 178 deletions.
319 changes: 141 additions & 178 deletions src/burner/statec.cpp
Original file line number Diff line number Diff line change
@@ -1,248 +1,211 @@
// Driver State Compression module
#include "zlib.h"

// FB Neo Driver State load/save from buffer module - dink 2024
#include "burnint.h"

extern bool bWithEEPROM; // from state.cpp, win32/replay.cpp
// BurnStateCompress: Save a state, "Compress" == organized into a buffer
// BurnStateDecompress: Load a state from buffer

static UINT8* Comp = NULL; // Compressed data buffer
static INT32 nCompLen = 0;
static INT32 nCompFill = 0; // How much of the buffer has been filled so far
#define DEBUG_STATEC 1
#define stateclog(x) do { if (DEBUG_STATEC) bprintf x; } while (0)

static z_stream Zstr; // Deflate stream
extern bool bWithEEPROM; // from state.cpp, win32/replay.cpp

// -----------------------------------------------------------------------------
// Compression
static INT32 nStateLoadFileLength = 0; // file size loaded
static INT32 nCalculatedLength = 0; // calculated size from driver
static INT32 nCalculatedVars = 0; // calculated variable count from driver
static INT32 nBufferPosition = 0; // position of load/save progress
static bool bNeedToRecover = false;
static UINT8 *Buffer = NULL;
static UINT8 *pBuffer = NULL;

static INT32 CompEnlarge(INT32 nAdd)
{
void* NewMem = NULL;
static const UINT32 BLOCK_ID_STATE = 0xa55a0000;
static const UINT32 BLOCK_ID_VARIABLE = 0xa55a0001;
static const UINT32 STATE_SIZE_1_ENTRY = sizeof(UINT32) * 3; // block id, data length, data name hash
static const UINT32 STATE_SIZE_HEADER = sizeof(UINT32) * 2; // block id, reserved (32 bits future data)

// Need to make more room in the compressed buffer
NewMem = realloc(Comp, nCompLen + nAdd);
if (NewMem == NULL) {
return 1;
static UINT32 HashString(char *in_str)
{
UINT32 hash = 0xc0ffee;
const int in_strlen = strlen(in_str);
for (INT32 i = 0; i < in_strlen; i++) {
hash = (hash ^ in_str[i]) + ((hash << 0x1a) + (hash >> 0x06));
}

Comp = (UINT8*)NewMem;
memset(Comp + nCompLen, 0, nAdd);
nCompLen += nAdd;

return 0;
return hash;
}

static INT32 CompGo(INT32 bFinish)
static void AddToBuffer(void *data, UINT32 data_size)
{
INT32 nResult = 0;
INT32 nAvailOut = 0;

bool bRetry, bOverflow;

do {

bRetry = false;

// Point to the remainder of out buffer
Zstr.next_out = Comp + nCompFill;
nAvailOut = nCompLen - nCompFill;
if (nAvailOut < 0) {
nAvailOut = 0;
}
Zstr.avail_out = nAvailOut;

// Try to deflate into the buffer (there may not be enough room though)
if (bFinish) {
nResult = deflate(&Zstr, Z_FINISH); // deflate and finish
if (nResult != Z_OK && nResult != Z_STREAM_END) {
return 1;
}
} else {
nResult = deflate(&Zstr, 0); // deflate
if (nResult != Z_OK) {
return 1;
}
}

nCompFill = Zstr.next_out - Comp; // Update how much has been filled

// Check for overflow
bOverflow = bFinish ? (nResult == Z_OK) : (Zstr.avail_out <= 0);

if (bOverflow) {
if (CompEnlarge(4 * 1024)) {
return 1;
}
memcpy(pBuffer, data, data_size);
pBuffer += data_size;
nBufferPosition += data_size;
}

bRetry = true;
}
} while (bRetry);
static void GetFromBuffer(void *data, UINT32 data_size)
{
if (data != NULL) memcpy(data, pBuffer, data_size);
pBuffer += data_size;
nBufferPosition += data_size;
}

static INT32 __cdecl LenAcb(struct BurnArea* pba)
{
// block id, data length, data name hash, data
nCalculatedLength += STATE_SIZE_1_ENTRY + pba->nLen;
nCalculatedVars ++;
return 0;
}

static INT32 __cdecl StateCompressAcb(struct BurnArea* pba)
static INT32 __cdecl SaveAcb(struct BurnArea* pba)
{
// Set the data as the next available input
Zstr.next_in = (UINT8*)pba->Data;
Zstr.avail_in = pba->nLen;

CompGo(0); // Compress this Area
if (pba->nLen + STATE_SIZE_1_ENTRY + nBufferPosition > nCalculatedLength) {
stateclog((0, _T("SaveAcb(): No space for \"%S\" nCalculatedLength %x pba->nLen %x nBufferPosition %x\n"), pba->szName, nCalculatedLength, pba->nLen, nBufferPosition));
return 1;
}

Zstr.avail_in = 0;
Zstr.next_in = NULL;
UINT32 strhash = HashString(pba->szName);

return 0;
}
stateclog((0, _T("save %S\t\tlength %x %x\n"), pba->szName, pba->nLen, strhash));

// --------- Raw / Uncompressed state handling ----------
static INT32 nTotalLenUncomp = 0;
static UINT8 *BufferUncomp = NULL;
static UINT8 *pBufferUncomp = NULL;
// copy block identifier
AddToBuffer((void*)&BLOCK_ID_VARIABLE, sizeof(UINT32));

static INT32 __cdecl UncompLenAcb(struct BurnArea* pba)
{
nTotalLenUncomp += pba->nLen;
// copy length of variable
AddToBuffer(&pba->nLen, sizeof(UINT32));

return 0;
}
// copy hash of variable string
AddToBuffer(&strhash, sizeof(UINT32));

static INT32 __cdecl UncompSaveAcb(struct BurnArea* pba)
{
memcpy(pBufferUncomp, pba->Data, pba->nLen);
pBufferUncomp += pba->nLen;
// copy variable
AddToBuffer(pba->Data, pba->nLen);

return 0;
}

static INT32 __cdecl UncompLoadAcb(struct BurnArea* pba)
static INT32 __cdecl LoadAcb(struct BurnArea* pba)
{
memcpy(pba->Data, pBufferUncomp, pba->nLen);
pBufferUncomp += pba->nLen;
int tries = 0;
bool gotit = false;

return 0;
}
UINT8 *pBufferSave = pBuffer;
UINT32 nBufferPositionSave = nBufferPosition;

// Compress a state using deflate
INT32 BurnStateCompress(UINT8** pDef, INT32* pnDefLen, INT32 bAll)
{
UINT32 nAddEEPROM = (bWithEEPROM) ? ACB_EEPROM : 0;
do {
if (pba->nLen + STATE_SIZE_1_ENTRY + nBufferPosition > nStateLoadFileLength) {
stateclog((0, _T("LoadAcb(): No space to load \"%S\" nCalculatedLength %x pba->nLen %x nBufferPosition %x\n"), pba->szName, nCalculatedLength, pba->nLen, nBufferPosition));
break;
}

if ((BurnDrvGetHardwareCode() & 0xffff0000) == HARDWARE_CAVE_CV1000) {
// Systems with a huge amount of data can be defined here to
// use this raw state handler.
UINT32 length = 0;
UINT32 loadedblockid = 0;
UINT32 loadedhash = 0;
UINT32 ourhash = HashString(pba->szName);

nTotalLenUncomp = 0;
BurnAcb = UncompLenAcb; // Get length of state buffer
stateclog((0, _T("load %S\t\tlength %x %x (%x, %x)\n"), pba->szName, pba->nLen, ourhash, nBufferPosition, nCalculatedLength));

if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_READ, NULL); // scan all ram, read (from driver <- decompress)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_READ, NULL); // scan nvram, read (from driver <- decompress)
GetFromBuffer(&loadedblockid, sizeof(UINT32));

BufferUncomp = (UINT8*)malloc (nTotalLenUncomp);
if (BLOCK_ID_VARIABLE != loadedblockid) {
stateclog((0, _T("-- block ID fail - we probably can't recover from this!\n")));
break;
}

pBufferUncomp = BufferUncomp;
BurnAcb = UncompSaveAcb;
GetFromBuffer(&length, sizeof(UINT32));
GetFromBuffer(&loadedhash, sizeof(UINT32));

if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_READ, NULL); // scan all ram, read (from driver <- decompress)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_READ, NULL); // scan nvram, read (from driver <- decompress)
if (length == pba->nLen && ourhash == loadedhash) {
GetFromBuffer(pba->Data, pba->nLen);
gotit = true;
} else {
stateclog((0, _T("|-- length doesn't match driver length/hash! skipping.\n")));

// Return the buffer
if (pDef) {
*pDef = BufferUncomp;
}
if (pnDefLen) {
*pnDefLen = nTotalLenUncomp;
GetFromBuffer(NULL, length); // just increment pointer using length from state buffer
}

return 0;
}
} while (!gotit && ++tries < 50);

// FBN-Standard / Compressed state handler
void* NewMem = NULL;
if (gotit == false) {
stateclog((0, _T("Can't seem to match, let's rewind pBuffer!\n")));

memset(&Zstr, 0, sizeof(Zstr));
bNeedToRecover = true;
pBuffer = pBufferSave;
nBufferPosition = nBufferPositionSave;
} else {
if (bNeedToRecover) {
stateclog((0, _T("|-- Success - We made a nice recovery! That was close :)\n")));

Comp = NULL; nCompLen = 0; nCompFill = 0; // Begin with a zero-length buffer
if (CompEnlarge(8 * 1024)) {
return 1;
bNeedToRecover = false;
}
}

deflateInit(&Zstr, Z_DEFAULT_COMPRESSION);

BurnAcb = StateCompressAcb; // callback our function with each area

if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_READ, NULL); // scan all ram, read (from driver <- decompress)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_READ, NULL); // scan nvram, read (from driver <- decompress)

// Finish off
CompGo(1);
return 0;
}

deflateEnd(&Zstr);
// Save a state, "Compress" == organized into a buffer
INT32 BurnStateCompress(UINT8** pDef, INT32* pnDefLen, INT32 bAll)
{
UINT32 nAddEEPROM = (bWithEEPROM) ? ACB_EEPROM : 0;

// Size down
NewMem = realloc(Comp, nCompFill);
if (NewMem) {
Comp = (UINT8*)NewMem;
nCompLen = nCompFill;
}
nCalculatedLength = STATE_SIZE_HEADER;
nCalculatedVars = 0;
BurnAcb = LenAcb; // Get length of state buffer

// Return the buffer
if (pDef) {
*pDef = Comp;
}
if (pnDefLen) {
*pnDefLen = nCompFill;
}
if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_READ, NULL); // scan all ram (read from driver variables)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_READ, NULL); // scan nvram

return 0;
}
stateclog((0, _T("BurnStateCompress(Save): Sizes, Calculated: %x Entries: %x\n"), nCalculatedLength, nCalculatedVars));

// -----------------------------------------------------------------------------
// Decompression
// Set-up the buffer
Buffer = (UINT8*)malloc(nCalculatedLength); // note: this is free()'d in state.cpp
nBufferPosition = 0;
pBuffer = Buffer;
BurnAcb = SaveAcb;

static INT32 __cdecl StateDecompressAcb(struct BurnArea* pba)
{
Zstr.next_out =(UINT8*)pba->Data;
Zstr.avail_out = pba->nLen;
// Add BLOCKID/header to the beginning of state buffer
UINT32 future_use = 0;
AddToBuffer((void *)&BLOCK_ID_STATE, sizeof(UINT32));
AddToBuffer(&future_use, sizeof(UINT32));

inflate(&Zstr, Z_SYNC_FLUSH);
if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_READ, NULL); // scan all ram (read from driver variables)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_READ, NULL); // scan nvram

Zstr.avail_out = 0;
Zstr.next_out = NULL;
if (pDef) *pDef = Buffer;
if (pnDefLen) *pnDefLen = nCalculatedLength;

return 0;
}

// Load a state from buffer
INT32 BurnStateDecompress(UINT8* Def, INT32 nDefLen, INT32 bAll)
{
UINT32 nAddEEPROM = (bWithEEPROM) ? ACB_EEPROM : 0;

if ((BurnDrvGetHardwareCode() & 0xffff0000) == HARDWARE_CAVE_CV1000) {
// Systems with a huge amount of data can be defined here to
// use this raw state handler.
pBufferUncomp = Def;
BurnAcb = UncompLoadAcb;
nStateLoadFileLength = nDefLen;

if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_WRITE, NULL); // scan all ram, write (to driver <- decompress)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_WRITE, NULL); // scan nvram, write (to driver <- decompress)
nCalculatedLength = 0;
nCalculatedVars = 0;
BurnAcb = LenAcb; // Get length of state buffer

return 0;
}
if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_WRITE, NULL); // scan all ram (write to driver variables)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_WRITE, NULL); // scan nvram

// FBN-Standard / Compressed state handler
memset(&Zstr, 0, sizeof(Zstr));
inflateInit(&Zstr);
stateclog((0, _T("BurnStateDecompress(Load): Sizes, Calculated: %x Loaded: %x\n"), nCalculatedLength, nDefLen));

// Set all of the buffer as available input
Zstr.next_in = (UINT8*)Def;
Zstr.avail_in = nDefLen;
bNeedToRecover = false;
nBufferPosition = 0;
pBuffer = Def;
BurnAcb = LoadAcb;

BurnAcb = StateDecompressAcb; // callback our function with each area
UINT32 test_header;
UINT32 future_use;
GetFromBuffer(&test_header, sizeof(UINT32));
GetFromBuffer(&future_use, sizeof(UINT32));

if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_WRITE, NULL); // scan all ram, write (to driver <- decompress)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_WRITE, NULL); // scan nvram, write (to driver <- decompress)
if (test_header != BLOCK_ID_STATE) {
stateclog((0, _T("State file header is missing or corrupt!\n")));
return 1;
}

inflateEnd(&Zstr);
memset(&Zstr, 0, sizeof(Zstr));
if (bAll) BurnAreaScan(ACB_FULLSCAN | ACB_WRITE, NULL); // scan all ram (write to driver variables)
else BurnAreaScan(ACB_NVRAM | nAddEEPROM | ACB_WRITE, NULL); // scan nvram

return 0;
}

1 comment on commit 2fdf290

@dinkc64
Copy link
Collaborator Author

@dinkc64 dinkc64 commented on 2fdf290 Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@barbudreadmon @taoenwen & anyone else
Hi guys,
Please don't commit anything for a day or 2 so people can see this message when they download nightly build - it's important, as the state / nvram format has completely changed.

What's this about?
If you've ever seen fbneo crash/freeze/go nuts upon loading an old state, you'll appreciate this changeover. :)
Secondly, the states are now "mostly" futureproof, as long as the memindex and cpu doesn't change. It'll do it's best to skip over even big changes in the number of variables/devices/etc in a DrvScan()

(minor oops: I left debugging enabled for this code, will turn it off in a few days - I don't want to mess with the commit message right now)

best regards,

  • dink

Please sign in to comment.