From 2fdf290ec5b285a339b42d12b5f110cc5e86df17 Mon Sep 17 00:00:00 2001 From: dinkc64 Date: Wed, 9 Oct 2024 10:02:54 -0400 Subject: [PATCH] burner/statec.cpp: bullet-proof states, finally. All old states and nvram files are deprecated. DO NOT UPGRADE if you're in the midst of playing something w/states or nvram --- src/burner/statec.cpp | 319 +++++++++++++++++++----------------------- 1 file changed, 141 insertions(+), 178 deletions(-) diff --git a/src/burner/statec.cpp b/src/burner/statec.cpp index 77ae32136d..cffea86d91 100644 --- a/src/burner/statec.cpp +++ b/src/burner/statec.cpp @@ -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; }