-
Notifications
You must be signed in to change notification settings - Fork 364
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
burner/statec.cpp: bullet-proof states, finally. All old states and n…
…vram files are deprecated. DO NOT UPGRADE if you're in the midst of playing something w/states or nvram
- Loading branch information
Showing
1 changed file
with
141 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
2fdf290
There was a problem hiding this comment.
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,