added savestate.verify Lua function for automated desync detection (disabled in public release)
This commit is contained in:
parent
5e42454cf4
commit
9c4a2dc20c
|
@ -75,7 +75,7 @@ public:
|
||||||
|
|
||||||
//todo - handle read-only specially?
|
//todo - handle read-only specially?
|
||||||
class EMUFILE_MEMORY : public EMUFILE {
|
class EMUFILE_MEMORY : public EMUFILE {
|
||||||
private:
|
protected:
|
||||||
std::vector<u8> *vec;
|
std::vector<u8> *vec;
|
||||||
s32 pos, len;
|
s32 pos, len;
|
||||||
bool ownvec;
|
bool ownvec;
|
||||||
|
@ -96,7 +96,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
u8* buf() { return &(*vec)[0]; }
|
u8* buf() { return &(*vec)[0]; }
|
||||||
|
|
||||||
|
std::vector<u8>* get_vec() { return vec; };
|
||||||
|
|
||||||
virtual FILE *get_fp() { return NULL; }
|
virtual FILE *get_fp() { return NULL; }
|
||||||
|
|
||||||
virtual int fprintf(const char *format, ...) {
|
virtual int fprintf(const char *format, ...) {
|
||||||
|
@ -133,7 +135,8 @@ public:
|
||||||
u32 todo = std::min(remain,bytes);
|
u32 todo = std::min(remain,bytes);
|
||||||
memcpy((void*)ptr,buf()+pos,todo);
|
memcpy((void*)ptr,buf()+pos,todo);
|
||||||
pos += todo;
|
pos += todo;
|
||||||
if(todo<bytes) failbit = true;
|
if(todo<bytes)
|
||||||
|
failbit = true;
|
||||||
return todo;
|
return todo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +177,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
class EMUFILE_FILE : public EMUFILE {
|
class EMUFILE_FILE : public EMUFILE {
|
||||||
private:
|
protected:
|
||||||
FILE* fp;
|
FILE* fp;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -182,7 +185,8 @@ public:
|
||||||
EMUFILE_FILE(const char* fname, const char* mode)
|
EMUFILE_FILE(const char* fname, const char* mode)
|
||||||
{
|
{
|
||||||
fp = fopen(fname,mode);
|
fp = fopen(fname,mode);
|
||||||
if(!fp) failbit = true;
|
if(!fp)
|
||||||
|
failbit = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~EMUFILE_FILE() {
|
virtual ~EMUFILE_FILE() {
|
||||||
|
@ -211,7 +215,8 @@ public:
|
||||||
|
|
||||||
virtual size_t _fread(const void *ptr, size_t bytes){
|
virtual size_t _fread(const void *ptr, size_t bytes){
|
||||||
size_t ret = ::fread((void*)ptr, 1, bytes, fp);
|
size_t ret = ::fread((void*)ptr, 1, bytes, fp);
|
||||||
if(ret < bytes) failbit = true;
|
if(ret < bytes)
|
||||||
|
failbit = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +225,8 @@ public:
|
||||||
|
|
||||||
virtual void fwrite(const void *ptr, size_t bytes){
|
virtual void fwrite(const void *ptr, size_t bytes){
|
||||||
size_t ret = ::fwrite((void*)ptr, 1, bytes, fp);
|
size_t ret = ::fwrite((void*)ptr, 1, bytes, fp);
|
||||||
if(ret < bytes) failbit = true;
|
if(ret < bytes)
|
||||||
|
failbit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int fseek(int offset, int origin){
|
virtual int fseek(int offset, int origin){
|
||||||
|
|
|
@ -1965,6 +1965,90 @@ DEFINE_LUA_FUNCTION(state_savescriptdata, "location")
|
||||||
return state_save(L);
|
return state_save(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef PUBLIC_RELEASE
|
||||||
|
#include "gfx3d.h"
|
||||||
|
class EMUFILE_MEMORY_VERIFIER : public EMUFILE_MEMORY {
|
||||||
|
public:
|
||||||
|
EMUFILE_MEMORY_VERIFIER(EMUFILE_MEMORY* underlying) : EMUFILE_MEMORY(underlying->get_vec()) { }
|
||||||
|
|
||||||
|
std::vector<std::string> differences;
|
||||||
|
|
||||||
|
virtual void fwrite(const void *ptr, size_t bytes)
|
||||||
|
{
|
||||||
|
if(!failbit)
|
||||||
|
{
|
||||||
|
u8* dst = buf()+pos;
|
||||||
|
const u8* src = (const u8*)ptr;
|
||||||
|
int differencesAddedThisCall = 0;
|
||||||
|
for(int i = pos; i < (int)bytes+pos; i++)
|
||||||
|
{
|
||||||
|
if(*src != *dst)
|
||||||
|
{
|
||||||
|
if(differences.size() == 100)
|
||||||
|
failbit = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char temp [256];
|
||||||
|
sprintf(temp, " " /*"mismatch at "*/ "byte %d(0x%X at 0x%X): %d(0x%X) != %d(0x%X)\n", i, i, dst, *src,*src, *dst,*dst);
|
||||||
|
|
||||||
|
if(ptr == GPU_screen || ptr == gfx3d_convertedScreen) // ignore screen-only differences since frame skipping can cause them and it's probably ok
|
||||||
|
break;
|
||||||
|
|
||||||
|
differences.push_back(temp); // <-- probably the best place for a breakpoint
|
||||||
|
|
||||||
|
if(++differencesAddedThisCall == 4)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src++;
|
||||||
|
dst++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += bytes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// like savestate.save() except instead of actually saving
|
||||||
|
// it compares against what's already in the savestate
|
||||||
|
// and throws an error if any differences are found.
|
||||||
|
// only useful for development (catching desyncs).
|
||||||
|
DEFINE_LUA_FUNCTION(state_verify, "location[,option]")
|
||||||
|
{
|
||||||
|
int type = lua_type(L,1);
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case LUA_TNUMBER: // numbered save file
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
luaL_error(L, "savestate.verify only works for in-memory saves.");
|
||||||
|
} return 0;
|
||||||
|
case LUA_TUSERDATA: // in-memory save slot
|
||||||
|
{
|
||||||
|
EMUFILE_MEMORY** ppEmuFile = (EMUFILE_MEMORY**)luaL_checkudata(L, 1, "EMUFILE_MEMORY*");
|
||||||
|
|
||||||
|
if((*ppEmuFile)->fail())
|
||||||
|
luaL_error(L, "failed to verify, savestate object was dead.");
|
||||||
|
|
||||||
|
EMUFILE_MEMORY_VERIFIER verifier (*ppEmuFile);
|
||||||
|
savestate_save(&verifier, 0);
|
||||||
|
if(verifier.differences.size())
|
||||||
|
{
|
||||||
|
fputs("\n", stdout);
|
||||||
|
for(unsigned int i = 0; i < verifier.differences.size(); i++)
|
||||||
|
fputs(verifier.differences[i].c_str(), stdout);
|
||||||
|
luaL_error(L, "failed to verify savestate! %s", verifier.differences[0].c_str());
|
||||||
|
}
|
||||||
|
} return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//joypad lib
|
//joypad lib
|
||||||
|
|
||||||
|
@ -3857,6 +3941,9 @@ static const struct luaL_reg statelib [] =
|
||||||
{"create", state_create},
|
{"create", state_create},
|
||||||
{"save", state_save},
|
{"save", state_save},
|
||||||
{"load", state_load},
|
{"load", state_load},
|
||||||
|
#ifndef PUBLIC_RELEASE
|
||||||
|
{"verify", state_verify}, // for desync catching
|
||||||
|
#endif
|
||||||
// TODO
|
// TODO
|
||||||
//{"loadscriptdata", state_loadscriptdata},
|
//{"loadscriptdata", state_loadscriptdata},
|
||||||
//{"savescriptdata", state_savescriptdata},
|
//{"savescriptdata", state_savescriptdata},
|
||||||
|
|
|
@ -863,7 +863,7 @@ static void savestate_WriteChunk(EMUFILE* os, int type, void (*saveproc)(EMUFILE
|
||||||
|
|
||||||
//write the type, size(placeholder), and data
|
//write the type, size(placeholder), and data
|
||||||
write32le(type,os);
|
write32le(type,os);
|
||||||
write32le(0,os); // <-- temporary write, re-written later
|
os->fseek(4, SEEK_CUR); // skip the size, we write that later
|
||||||
saveproc(os);
|
saveproc(os);
|
||||||
|
|
||||||
//get the size
|
//get the size
|
||||||
|
|
Loading…
Reference in New Issue