From 0bc230bd3178b0aea3af6b22b702285ea085a327 Mon Sep 17 00:00:00 2001 From: gocha Date: Sun, 25 Oct 2009 03:02:00 +0000 Subject: [PATCH] add savestate.registerload, savestate.registersave, and savestate.loadscriptdata from snes9x lua (test needed! especially for savestate.loadscriptdata, one of the functions I've never used) --- src/fceulua.h | 44 ++- src/lua-engine.cpp | 759 ++++++++++++++++++++++++++++++++++++++-- src/state.cpp | 68 +++- vc/Help/fceux-2.1.1.hnd | Bin 207559 -> 207597 bytes 4 files changed, 833 insertions(+), 38 deletions(-) diff --git a/src/fceulua.h b/src/fceulua.h index 4d53aad4..e95e9442 100644 --- a/src/fceulua.h +++ b/src/fceulua.h @@ -5,6 +5,8 @@ enum LuaCallID LUACALL_BEFOREEMULATION, LUACALL_AFTEREMULATION, LUACALL_BEFOREEXIT, + LUACALL_BEFORESAVE, + LUACALL_AFTERLOAD, LUACALL_COUNT }; @@ -23,9 +25,43 @@ enum LuaMemHookType }; void CallRegisteredLuaMemHook(unsigned int address, int size, unsigned int value, LuaMemHookType hookType); +struct LuaSaveData +{ + LuaSaveData() { recordList = 0; } + ~LuaSaveData() { ClearRecords(); } + + struct Record + { + unsigned int key; // crc32 + unsigned int size; // size of data + unsigned char* data; + Record* next; + }; + + Record* recordList; + + void SaveRecord(struct lua_State* L, unsigned int key); // saves Lua stack into a record and pops it + void LoadRecord(struct lua_State* L, unsigned int key, unsigned int itemsToLoad) const; // pushes a record's data onto the Lua stack + void SaveRecordPartial(struct lua_State* L, unsigned int key, int idx); // saves part of the Lua stack (at the given index) into a record and does NOT pop anything + + void ExportRecords(void* file) const; // writes all records to an already-open file + void ImportRecords(void* file); // reads records from an already-open file + void ClearRecords(); // deletes all record data + +private: + // disallowed, it's dangerous to call this + // (because the memory the destructor deletes isn't refcounted and shouldn't need to be copied) + // so pass LuaSaveDatas by reference and this should never get called + LuaSaveData(const LuaSaveData& copy) {} +}; + +#define LUA_DATARECORDKEY 42 + +void CallRegisteredLuaSaveFunctions(int savestateNumber, LuaSaveData& saveData); +void CallRegisteredLuaLoadFunctions(int savestateNumber, const LuaSaveData& saveData); + // Just forward function declarations -//void FCEU_LuaWrite(uint32 addr); void FCEU_LuaFrameBoundary(); int FCEU_LoadLuaCode(const char *filename); void FCEU_ReloadLuaCode(); @@ -40,15 +76,11 @@ int FCEU_LuaRerecordCountSkip(); void FCEU_LuaGui(uint8 *XBuf); void FCEU_LuaUpdatePalette(); +struct lua_State* FCEU_GetLuaState(); char* FCEU_GetLuaScriptName(); // And some interesting REVERSE declarations! char *FCEU_GetFreezeFilename(int slot); -// Call this before writing into a buffer passed to FCEU_CheatAddRAM(). -// (That way, Lua-based memwatch will work as expected for somebody -// used to FCEU's memwatch.) -//void FCEU_LuaWriteInform(); // DEADBEEF - #endif diff --git a/src/lua-engine.cpp b/src/lua-engine.cpp index dd2372eb..9ed6cac1 100644 --- a/src/lua-engine.cpp +++ b/src/lua-engine.cpp @@ -21,6 +21,7 @@ extern "C" #include #include #include +#include } #include "types.h" @@ -172,6 +173,8 @@ static const char* luaCallIDStrings [] = "CALL_BEFOREEMULATION", "CALL_AFTEREMULATION", "CALL_BEFOREEXIT", + "CALL_BEFORESAVE", + "CALL_AFTERLOAD", }; //make sure we have the right number of strings @@ -435,6 +438,663 @@ static int emu_registerexit(lua_State *L) { } +// can't remember what the best way of doing this is... +#if defined(i386) || defined(__i386) || defined(__i386__) || defined(M_I86) || defined(_M_IX86) || defined(WIN32) + #define IS_LITTLE_ENDIAN +#endif + +// push a value's bytes onto the output stack +template +void PushBinaryItem(T item, std::vector& output) +{ + unsigned char* buf = (unsigned char*)&item; +#ifdef IS_LITTLE_ENDIAN + for(int i = sizeof(T); i; i--) + output.push_back(*buf++); +#else + int vecsize = output.size(); + for(int i = sizeof(T); i; i--) + output.insert(output.begin() + vecsize, *buf++); +#endif +} +// read a value from the byte stream and advance the stream by its size +template +T AdvanceByteStream(const unsigned char*& data, unsigned int& remaining) +{ +#ifdef IS_LITTLE_ENDIAN + T rv = *(T*)data; + data += sizeof(T); +#else + T rv; unsigned char* rvptr = (unsigned char*)&rv; + for(int i = sizeof(T)-1; i>=0; i--) + rvptr[i] = *data++; +#endif + remaining -= sizeof(T); + return rv; +} +// advance the byte stream by a certain size without reading a value +void AdvanceByteStream(const unsigned char*& data, unsigned int& remaining, int amount) +{ + data += amount; + remaining -= amount; +} + +#define LUAEXT_TLONG 30 // 0x1E // 4-byte signed integer +#define LUAEXT_TUSHORT 31 // 0x1F // 2-byte unsigned integer +#define LUAEXT_TSHORT 32 // 0x20 // 2-byte signed integer +#define LUAEXT_TBYTE 33 // 0x21 // 1-byte unsigned integer +#define LUAEXT_TNILS 34 // 0x22 // multiple nils represented by a 4-byte integer (warning: becomes multiple stack entities) +#define LUAEXT_TTABLE 0x40 // 0x40 through 0x4F // tables of different sizes: +#define LUAEXT_BITS_1A 0x01 // size of array part fits in a 1-byte unsigned integer +#define LUAEXT_BITS_2A 0x02 // size of array part fits in a 2-byte unsigned integer +#define LUAEXT_BITS_4A 0x03 // size of array part fits in a 4-byte unsigned integer +#define LUAEXT_BITS_1H 0x04 // size of hash part fits in a 1-byte unsigned integer +#define LUAEXT_BITS_2H 0x08 // size of hash part fits in a 2-byte unsigned integer +#define LUAEXT_BITS_4H 0x0C // size of hash part fits in a 4-byte unsigned integer +#define BITMATCH(x,y) (((x) & (y)) == (y)) + +static void PushNils(std::vector& output, int& nilcount) +{ + int count = nilcount; + nilcount = 0; + + static const int minNilsWorthEncoding = 6; // because a LUAEXT_TNILS entry is 5 bytes + + if(count < minNilsWorthEncoding) + { + for(int i = 0; i < count; i++) + output.push_back(LUA_TNIL); + } + else + { + output.push_back(LUAEXT_TNILS); + PushBinaryItem(count, output); + } +} + +static std::vector s_tableAddressStack; // prevents infinite recursion of a table within a table (when cycle is found, print something like table:parent) +static std::vector s_metacallStack; // prevents infinite recursion if something's __tostring returns another table that contains that something (when cycle is found, print the inner result without using __tostring) + +static void LuaStackToBinaryConverter(lua_State* L, int i, std::vector& output) +{ + int type = lua_type(L, i); + + // the first byte of every serialized item says what Lua type it is + output.push_back(type & 0xFF); + + switch(type) + { + default: + { + char errmsg [1024]; + sprintf(errmsg, "values of type \"%s\" are not allowed to be returned from registered save functions.\r\n", luaL_typename(L,i)); + if(info_print) + info_print(info_uid, errmsg); + else + puts(errmsg); + } + break; + case LUA_TNIL: + // no information necessary beyond the type + break; + case LUA_TBOOLEAN: + // serialize as 0 or 1 + output.push_back(lua_toboolean(L,i)); + break; + case LUA_TSTRING: + // serialize as a 0-terminated string of characters + { + const char* str = lua_tostring(L,i); + while(*str) + output.push_back(*str++); + output.push_back('\0'); + } + break; + case LUA_TNUMBER: + { + double num = (double)lua_tonumber(L,i); + int32 inum = (int32)lua_tointeger(L,i); + if(num != inum) + { + PushBinaryItem(num, output); + } + else + { + if((inum & ~0xFF) == 0) + type = LUAEXT_TBYTE; + else if((uint16)(inum & 0xFFFF) == inum) + type = LUAEXT_TUSHORT; + else if((int16)(inum & 0xFFFF) == inum) + type = LUAEXT_TSHORT; + else + type = LUAEXT_TLONG; + output.back() = type; + switch(type) + { + case LUAEXT_TLONG: + PushBinaryItem(static_cast(inum), output); + break; + case LUAEXT_TUSHORT: + PushBinaryItem(static_cast(inum), output); + break; + case LUAEXT_TSHORT: + PushBinaryItem(static_cast(inum), output); + break; + case LUAEXT_TBYTE: + output.push_back(static_cast(inum)); + break; + } + } + } + break; + case LUA_TTABLE: + // serialize as a type that describes how many bytes are used for storing the counts, + // followed by the number of array entries if any, then the number of hash entries if any, + // then a Lua value per array entry, then a (key,value) pair of Lua values per hashed entry + // note that the structure of table references are not faithfully serialized (yet) + { + int outputTypeIndex = output.size() - 1; + int arraySize = 0; + int hashSize = 0; + + if(lua_checkstack(L, 4) && std::find(s_tableAddressStack.begin(), s_tableAddressStack.end(), lua_topointer(L,i)) == s_tableAddressStack.end()) + { + s_tableAddressStack.push_back(lua_topointer(L,i)); + struct Scope { ~Scope(){ s_tableAddressStack.pop_back(); } } scope; + + bool wasnil = false; + int nilcount = 0; + arraySize = lua_objlen(L, i); + int arrayValIndex = lua_gettop(L) + 1; + for(int j = 1; j <= arraySize; j++) + { + lua_rawgeti(L, i, j); + bool isnil = lua_isnil(L, arrayValIndex); + if(isnil) + nilcount++; + else + { + if(wasnil) + PushNils(output, nilcount); + LuaStackToBinaryConverter(L, arrayValIndex, output); + } + lua_pop(L, 1); + wasnil = isnil; + } + if(wasnil) + PushNils(output, nilcount); + + if(arraySize) + lua_pushinteger(L, arraySize); // before first key + else + lua_pushnil(L); // before first key + + int keyIndex = lua_gettop(L); + int valueIndex = keyIndex + 1; + while(lua_next(L, i)) + { +// assert(lua_type(L, keyIndex) && "nil key in Lua table, impossible"); +// assert(lua_type(L, valueIndex) && "nil value in Lua table, impossible"); + LuaStackToBinaryConverter(L, keyIndex, output); + LuaStackToBinaryConverter(L, valueIndex, output); + lua_pop(L, 1); + hashSize++; + } + } + + int outputType = LUAEXT_TTABLE; + if(arraySize & 0xFFFF0000) + outputType |= LUAEXT_BITS_4A; + else if(arraySize & 0xFF00) + outputType |= LUAEXT_BITS_2A; + else if(arraySize & 0xFF) + outputType |= LUAEXT_BITS_1A; + if(hashSize & 0xFFFF0000) + outputType |= LUAEXT_BITS_4H; + else if(hashSize & 0xFF00) + outputType |= LUAEXT_BITS_2H; + else if(hashSize & 0xFF) + outputType |= LUAEXT_BITS_1H; + output[outputTypeIndex] = outputType; + + int insertIndex = outputTypeIndex; + if(BITMATCH(outputType,LUAEXT_BITS_4A) || BITMATCH(outputType,LUAEXT_BITS_2A) || BITMATCH(outputType,LUAEXT_BITS_1A)) + output.insert(output.begin() + (++insertIndex), arraySize & 0xFF); + if(BITMATCH(outputType,LUAEXT_BITS_4A) || BITMATCH(outputType,LUAEXT_BITS_2A)) + output.insert(output.begin() + (++insertIndex), (arraySize & 0xFF00) >> 8); + if(BITMATCH(outputType,LUAEXT_BITS_4A)) + output.insert(output.begin() + (++insertIndex), (arraySize & 0x00FF0000) >> 16), + output.insert(output.begin() + (++insertIndex), (arraySize & 0xFF000000) >> 24); + if(BITMATCH(outputType,LUAEXT_BITS_4H) || BITMATCH(outputType,LUAEXT_BITS_2H) || BITMATCH(outputType,LUAEXT_BITS_1H)) + output.insert(output.begin() + (++insertIndex), hashSize & 0xFF); + if(BITMATCH(outputType,LUAEXT_BITS_4H) || BITMATCH(outputType,LUAEXT_BITS_2H)) + output.insert(output.begin() + (++insertIndex), (hashSize & 0xFF00) >> 8); + if(BITMATCH(outputType,LUAEXT_BITS_4H)) + output.insert(output.begin() + (++insertIndex), (hashSize & 0x00FF0000) >> 16), + output.insert(output.begin() + (++insertIndex), (hashSize & 0xFF000000) >> 24); + + } break; + } +} + + +// complements LuaStackToBinaryConverter +void BinaryToLuaStackConverter(lua_State* L, const unsigned char*& data, unsigned int& remaining) +{ +// assert(s_dbg_dataSize - (data - s_dbg_dataStart) == remaining); + + unsigned char type = AdvanceByteStream(data, remaining); + + switch(type) + { + default: + { + char errmsg [1024]; + if(type <= 10 && type != LUA_TTABLE) + sprintf(errmsg, "values of type \"%s\" are not allowed to be loaded into registered load functions. The save state's Lua save data file might be corrupted.\r\n", lua_typename(L,type)); + else + sprintf(errmsg, "The save state's Lua save data file seems to be corrupted.\r\n"); + if(info_print) + info_print(info_uid, errmsg); + else + puts(errmsg); + } + break; + case LUA_TNIL: + lua_pushnil(L); + break; + case LUA_TBOOLEAN: + lua_pushboolean(L, AdvanceByteStream(data, remaining)); + break; + case LUA_TSTRING: + lua_pushstring(L, (const char*)data); + AdvanceByteStream(data, remaining, strlen((const char*)data) + 1); + break; + case LUA_TNUMBER: + lua_pushnumber(L, AdvanceByteStream(data, remaining)); + break; + case LUAEXT_TLONG: + lua_pushinteger(L, AdvanceByteStream(data, remaining)); + break; + case LUAEXT_TUSHORT: + lua_pushinteger(L, AdvanceByteStream(data, remaining)); + break; + case LUAEXT_TSHORT: + lua_pushinteger(L, AdvanceByteStream(data, remaining)); + break; + case LUAEXT_TBYTE: + lua_pushinteger(L, AdvanceByteStream(data, remaining)); + break; + case LUAEXT_TTABLE: + case LUAEXT_TTABLE | LUAEXT_BITS_1A: + case LUAEXT_TTABLE | LUAEXT_BITS_2A: + case LUAEXT_TTABLE | LUAEXT_BITS_4A: + case LUAEXT_TTABLE | LUAEXT_BITS_1H: + case LUAEXT_TTABLE | LUAEXT_BITS_2H: + case LUAEXT_TTABLE | LUAEXT_BITS_4H: + case LUAEXT_TTABLE | LUAEXT_BITS_1A | LUAEXT_BITS_1H: + case LUAEXT_TTABLE | LUAEXT_BITS_2A | LUAEXT_BITS_1H: + case LUAEXT_TTABLE | LUAEXT_BITS_4A | LUAEXT_BITS_1H: + case LUAEXT_TTABLE | LUAEXT_BITS_1A | LUAEXT_BITS_2H: + case LUAEXT_TTABLE | LUAEXT_BITS_2A | LUAEXT_BITS_2H: + case LUAEXT_TTABLE | LUAEXT_BITS_4A | LUAEXT_BITS_2H: + case LUAEXT_TTABLE | LUAEXT_BITS_1A | LUAEXT_BITS_4H: + case LUAEXT_TTABLE | LUAEXT_BITS_2A | LUAEXT_BITS_4H: + case LUAEXT_TTABLE | LUAEXT_BITS_4A | LUAEXT_BITS_4H: + { + unsigned int arraySize = 0; + if(BITMATCH(type,LUAEXT_BITS_4A) || BITMATCH(type,LUAEXT_BITS_2A) || BITMATCH(type,LUAEXT_BITS_1A)) + arraySize |= AdvanceByteStream(data, remaining); + if(BITMATCH(type,LUAEXT_BITS_4A) || BITMATCH(type,LUAEXT_BITS_2A)) + arraySize |= ((uint16)AdvanceByteStream(data, remaining)) << 8; + if(BITMATCH(type,LUAEXT_BITS_4A)) + arraySize |= ((uint32)AdvanceByteStream(data, remaining)) << 16, + arraySize |= ((uint32)AdvanceByteStream(data, remaining)) << 24; + + unsigned int hashSize = 0; + if(BITMATCH(type,LUAEXT_BITS_4H) || BITMATCH(type,LUAEXT_BITS_2H) || BITMATCH(type,LUAEXT_BITS_1H)) + hashSize |= AdvanceByteStream(data, remaining); + if(BITMATCH(type,LUAEXT_BITS_4H) || BITMATCH(type,LUAEXT_BITS_2H)) + hashSize |= ((uint16)AdvanceByteStream(data, remaining)) << 8; + if(BITMATCH(type,LUAEXT_BITS_4H)) + hashSize |= ((uint32)AdvanceByteStream(data, remaining)) << 16, + hashSize |= ((uint32)AdvanceByteStream(data, remaining)) << 24; + + lua_checkstack(L, 8); + + lua_createtable(L, arraySize, hashSize); + + unsigned int n = 1; + while(n <= arraySize) + { + if(*data == LUAEXT_TNILS) + { + AdvanceByteStream(data, remaining, 1); + n += AdvanceByteStream(data, remaining); + } + else + { + BinaryToLuaStackConverter(L, data, remaining); // push value + lua_rawseti(L, -2, n); // table[n] = value + n++; + } + } + + for(unsigned int h = 1; h <= hashSize; h++) + { + BinaryToLuaStackConverter(L, data, remaining); // push key + BinaryToLuaStackConverter(L, data, remaining); // push value + lua_rawset(L, -3); // table[key] = value + } + } + break; + } +} + +static const unsigned char luaBinaryMajorVersion = 9; +static const unsigned char luaBinaryMinorVersion = 1; + +unsigned char* LuaStackToBinary(lua_State* L, unsigned int& size) +{ + int n = lua_gettop(L); + if(n == 0) + return NULL; + + std::vector output; + output.push_back(luaBinaryMajorVersion); + output.push_back(luaBinaryMinorVersion); + + for(int i = 1; i <= n; i++) + LuaStackToBinaryConverter(L, i, output); + + unsigned char* rv = new unsigned char [output.size()]; + memcpy(rv, &output.front(), output.size()); + size = output.size(); + return rv; +} + +void BinaryToLuaStack(lua_State* L, const unsigned char* data, unsigned int size, unsigned int itemsToLoad) +{ + unsigned char major = *data++; + unsigned char minor = *data++; + size -= 2; + if(luaBinaryMajorVersion != major || luaBinaryMinorVersion != minor) + return; + + while(size > 0 && itemsToLoad > 0) + { + BinaryToLuaStackConverter(L, data, size); + itemsToLoad--; + } +} + +// saves Lua stack into a record and pops it +void LuaSaveData::SaveRecord(lua_State* L, unsigned int key) +{ + if(!L) + return; + + Record* cur = new Record(); + cur->key = key; + cur->data = LuaStackToBinary(L, cur->size); + cur->next = NULL; + + lua_settop(L,0); + + if(cur->size <= 0) + { + delete cur; + return; + } + + Record* last = recordList; + while(last && last->next) + last = last->next; + if(last) + last->next = cur; + else + recordList = cur; +} + +// pushes a record's data onto the Lua stack +void LuaSaveData::LoadRecord(struct lua_State* L, unsigned int key, unsigned int itemsToLoad) const +{ + if(!L) + return; + + Record* cur = recordList; + while(cur) + { + if(cur->key == key) + { +// s_dbg_dataStart = cur->data; +// s_dbg_dataSize = cur->size; + BinaryToLuaStack(L, cur->data, cur->size, itemsToLoad); + return; + } + cur = cur->next; + } +} + +// saves part of the Lua stack (at the given index) into a record and does NOT pop anything +void LuaSaveData::SaveRecordPartial(struct lua_State* L, unsigned int key, int idx) +{ + if(!L) + return; + + if(idx < 0) + idx += lua_gettop(L)+1; + + Record* cur = new Record(); + cur->key = key; + cur->next = NULL; + + if(idx <= lua_gettop(L)) + { + std::vector output; + output.push_back(luaBinaryMajorVersion); + output.push_back(luaBinaryMinorVersion); + + LuaStackToBinaryConverter(L, idx, output); + + unsigned char* rv = new unsigned char [output.size()]; + memcpy(rv, &output.front(), output.size()); + cur->size = output.size(); + cur->data = rv; + } + + if(cur->size <= 0) + { + delete cur; + return; + } + + Record* last = recordList; + while(last && last->next) + last = last->next; + if(last) + last->next = cur; + else + recordList = cur; +} + +void fwriteint(unsigned int value, FILE* file) +{ + for(int i=0;i<4;i++) + { + int w = value & 0xFF; + fwrite(&w, 1, 1, file); + value >>= 8; + } +} +void freadint(unsigned int& value, FILE* file) +{ + int rv = 0; + for(int i=0;i<4;i++) + { + int r = 0; + fread(&r, 1, 1, file); + rv |= r << (i*8); + } + value = rv; +} + +// writes all records to an already-open file +void LuaSaveData::ExportRecords(void* fileV) const +{ + FILE* file = (FILE*)fileV; + if(!file) + return; + + Record* cur = recordList; + while(cur) + { + fwriteint(cur->key, file); + fwriteint(cur->size, file); + fwrite(cur->data, cur->size, 1, file); + cur = cur->next; + } +} + +// reads records from an already-open file +void LuaSaveData::ImportRecords(void* fileV) +{ + FILE* file = (FILE*)fileV; + if(!file) + return; + + ClearRecords(); + + Record rec; + Record* cur = &rec; + Record* last = NULL; + while(1) + { + freadint(cur->key, file); + freadint(cur->size, file); + + if(feof(file) || ferror(file)) + break; + + cur->data = new unsigned char [cur->size]; + fread(cur->data, cur->size, 1, file); + + Record* next = new Record(); + memcpy(next, cur, sizeof(Record)); + next->next = NULL; + + if(last) + last->next = next; + else + recordList = next; + last = next; + } +} + +void LuaSaveData::ClearRecords() +{ + Record* cur = recordList; + while(cur) + { + Record* del = cur; + cur = cur->next; + + delete[] del->data; + delete del; + } + + recordList = NULL; +} + + + + + + +void CallRegisteredLuaSaveFunctions(int savestateNumber, LuaSaveData& saveData) +{ + //lua_State* L = FCEU_GetLuaState(); + if(L) + { + lua_settop(L, 0); + lua_getfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_BEFORESAVE]); + + if (lua_isfunction(L, -1)) + { + lua_pushinteger(L, savestateNumber); + int ret = lua_pcall(L, 1, LUA_MULTRET, 0); + if (ret != 0) { + // This is grounds for trashing the function + lua_pushnil(L); + lua_setfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_BEFORESAVE]); +#ifdef WIN32 + MessageBox(hAppWnd, lua_tostring(L, -1), "Lua Error in SAVE function", MB_OK); +#else + fprintf(stderr, "Lua error in registersave function: %s\n", lua_tostring(L, -1)); +#endif + } + saveData.SaveRecord(L, LUA_DATARECORDKEY); + } + else + { + lua_pop(L, 1); + } + } +} + + +void CallRegisteredLuaLoadFunctions(int savestateNumber, const LuaSaveData& saveData) +{ + //lua_State* L = FCEU_GetLuaState(); + if(L) + { + lua_settop(L, 0); + lua_getfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_AFTERLOAD]); + + if (lua_isfunction(L, -1)) + { + // since the scriptdata can be very expensive to load + // (e.g. the registered save function returned some huge tables) + // check the number of parameters the registered load function expects + // and don't bother loading the parameters it wouldn't receive anyway + int numParamsExpected = (L->top - 1)->value.gc->cl.l.p->numparams; // NOTE: if this line crashes, that means your Lua headers are out of sync with your Lua lib + if(numParamsExpected) numParamsExpected--; // minus one for the savestate number we always pass in + + int prevGarbage = lua_gc(L, LUA_GCCOUNT, 0); + + lua_pushinteger(L, savestateNumber); + saveData.LoadRecord(L, LUA_DATARECORDKEY, numParamsExpected); + int n = lua_gettop(L) - 1; + + int ret = lua_pcall(L, n, 0, 0); + if (ret != 0) { + // This is grounds for trashing the function + lua_pushnil(L); + lua_setfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_AFTERLOAD]); +#ifdef WIN32 + MessageBox(hAppWnd, lua_tostring(L, -1), "Lua Error in LOAD function", MB_OK); +#else + fprintf(stderr, "Lua error in registerload function: %s\n", lua_tostring(L, -1)); +#endif + } + else + { + int newGarbage = lua_gc(L, LUA_GCCOUNT, 0); + if(newGarbage - prevGarbage > 50) + { + // now seems to be a very good time to run the garbage collector + // it might take a while now but that's better than taking 10 whiles 9 loads from now + lua_gc(L, LUA_GCCOLLECT, 0); + } + } + } + else + { + lua_pop(L, 1); + } + } +} + static int rom_readbyte(lua_State *L) { lua_pushinteger(L, FCEU_ReadRomByte(luaL_checkinteger(L,1))); return 1; } static int rom_readbytesigned(lua_State *L) { lua_pushinteger(L, (signed char)FCEU_ReadRomByte(luaL_checkinteger(L,1))); return 1; } @@ -462,9 +1122,6 @@ static inline bool isalphaorunderscore(char c) return isalpha(c) || c == '_'; } -static std::vector s_tableAddressStack; // prevents infinite recursion of a table within a table (when cycle is found, print something like table:parent) -static std::vector s_metacallStack; // prevents infinite recursion if something's __tostring returns another table that contains that something (when cycle is found, print the inner result without using __tostring) - #define APPENDPRINT { int _n = snprintf(ptr, remaining, #define END ); if(_n >= 0) { ptr += _n; remaining -= _n; } else { remaining = 0; } } static void toCStringConverter(lua_State* L, int i, char*& ptr, int& remaining) @@ -1504,31 +2161,27 @@ static int joypad_set(lua_State *L) { return 0; } +// Helper function to convert a savestate object to the filename it represents. +static const char *savestateobj2filename(lua_State *L, int offset) { + + // First we get the metatable of the indicated object + int result = lua_getmetatable(L, offset); - -// -//// Helper function to convert a savestate object to the filename it represents. -//static char *savestateobj2filename(lua_State *L, int offset) { -// -// // First we get the metatable of the indicated object -// int result = lua_getmetatable(L, offset); -// -// if (!result) -// luaL_error(L, "object not a savestate object"); -// -// // Also check that the type entry is set -// lua_getfield(L, -1, "__metatable"); -// if (strcmp(lua_tostring(L,-1), "FCEU Savestate") != 0) -// luaL_error(L, "object not a savestate object"); -// lua_pop(L,1); -// -// // Now, get the field we want -// lua_getfield(L, -1, "filename"); -// -// // Return it -// return (char *) lua_tostring(L, -1); -//} -// + if (!result) + luaL_error(L, "object not a savestate object"); + + // Also check that the type entry is set + lua_getfield(L, -1, "__metatable"); + if (strcmp(lua_tostring(L,-1), "FCEU Savestate") != 0) + luaL_error(L, "object not a savestate object"); + lua_pop(L,1); + + // Now, get the field we want + lua_getfield(L, -1, "filename"); + + // Return it + return lua_tostring(L, -1); +} // Helper function for garbage collection. static int savestate_gc(lua_State *L) { @@ -1659,6 +2312,54 @@ static int savestate_load(lua_State *L) { } +static int savestate_registersave(lua_State *L) { + + lua_settop(L,1); + if (!lua_isnil(L,1)) + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_getfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_BEFORESAVE]); + lua_pushvalue(L,1); + lua_setfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_BEFORESAVE]); + //StopScriptIfFinished(luaStateToUIDMap[L]); + return 1; +} +static int savestate_registerload(lua_State *L) { + + lua_settop(L,1); + if (!lua_isnil(L,1)) + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_getfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_AFTERLOAD]); + lua_pushvalue(L,1); + lua_setfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_AFTERLOAD]); + //StopScriptIfFinished(luaStateToUIDMap[L]); + return 1; +} + +static int savestate_loadscriptdata(lua_State *L) { + + const char *filename = savestateobj2filename(L,1); + + { + LuaSaveData saveData; + + char luaSaveFilename [512]; + strncpy(luaSaveFilename, filename, 512); + luaSaveFilename[512-(1+7/*strlen(".luasav")*/)] = '\0'; + strcat(luaSaveFilename, ".luasav"); + FILE* luaSaveFile = fopen(luaSaveFilename, "rb"); + if(luaSaveFile) + { + saveData.ImportRecords(luaSaveFile); + fclose(luaSaveFile); + + lua_settop(L, 0); + saveData.LoadRecord(L, LUA_DATARECORDKEY, (unsigned int)-1); + return lua_gettop(L); + } + } + return 0; +} + // int emu.framecount() // @@ -3546,6 +4247,10 @@ static const struct luaL_reg savestatelib[] = { {"persist", savestate_persist}, {"load", savestate_load}, + {"registersave", savestate_registersave}, + {"registerload", savestate_registerload}, + {"loadscriptdata", savestate_loadscriptdata}, + {NULL,NULL} }; diff --git a/src/state.cpp b/src/state.cpp index 9e82db40..189bd7dd 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -48,6 +48,9 @@ #include "input.h" #include "zlib.h" #include "driver.h" +#ifdef _S9XLUA_H +#include "fceulua.h" +#endif //TODO - we really need some kind of global platform-specific options api #ifdef WIN32 @@ -75,6 +78,8 @@ char lastLoadstateMade[2048]; //Stores the filename of the last state loaded (ne bool undoLS = false; //This will be true if a backupstate was made and it was made since ROM was loaded bool redoLS = false; //This will be true if a backupstate was loaded, meaning redoLoadState can be run +bool internalSaveLoad = false; + #define SFMDATA_SIZE (64) static SFORMAT SFMDATA[SFMDATA_SIZE]; static int SFEXINDEX; @@ -436,7 +441,7 @@ bool FCEUSS_SaveMS(std::ostream* outstream, int compressionLevel) void FCEUSS_Save(const char *fname) { std::fstream* st = 0; - char *fn; + char fn[2048]; if(geniestage==1) { @@ -447,11 +452,12 @@ void FCEUSS_Save(const char *fname) if(fname) //If filename is given use it. { st =FCEUD_UTF8_fstream(fname, "wb"); + strcpy(fn, fname); } else //Else, generate one { //FCEU_PrintError("daCurrentState=%d",CurrentState); - fn = strdup(FCEU_MakeFName(FCEUMKF_STATE,CurrentState,0).c_str()); + strcpy(fn, FCEU_MakeFName(FCEUMKF_STATE,CurrentState,0).c_str()); //backup existing savestate first if (CheckFileExists(fn)) @@ -464,7 +470,6 @@ void FCEUSS_Save(const char *fname) undoSS = false; //so backup made so lastSavestateMade does have a backup file, so no undo st = FCEUD_UTF8_fstream(fn,"wb"); - free(fn); } if(st == NULL) @@ -473,6 +478,32 @@ void FCEUSS_Save(const char *fname) return; } + #ifdef _S9XLUA_H + if (!internalSaveLoad) + { + LuaSaveData saveData; + CallRegisteredLuaSaveFunctions(CurrentState, saveData); + + char luaSaveFilename [512]; + strncpy(luaSaveFilename, fn, 512); + luaSaveFilename[512-(1+7/*strlen(".luasav")*/)] = '\0'; + strcat(luaSaveFilename, ".luasav"); + if(saveData.recordList) + { + FILE* luaSaveFile = fopen(luaSaveFilename, "wb"); + if(luaSaveFile) + { + saveData.ExportRecords(luaSaveFile); + fclose(luaSaveFile); + } + } + else + { + unlink(luaSaveFilename); + } + } + #endif + if(FCEUMOV_Mode(MOVIEMODE_INACTIVE)) FCEUSS_SaveMS(st,-1); else @@ -660,6 +691,7 @@ bool FCEUSS_LoadFP(std::istream* is, ENUM_SSLOADPARAMS params) bool FCEUSS_Load(const char *fname) { std::fstream* st; + char fn[2048]; //mbg movie - this needs to be overhauled ////this fixes read-only toggle problems @@ -676,12 +708,13 @@ bool FCEUSS_Load(const char *fname) if(fname) { st=FCEUD_UTF8_fstream(fname, "rb"); + strcpy(fn, fname); } else { - string fn = FCEU_MakeFName(FCEUMKF_STATE,CurrentState,fname); + strcpy(fn, FCEU_MakeFName(FCEUMKF_STATE,CurrentState,fname).c_str()); st=FCEUD_UTF8_fstream(fn,"rb"); - strcpy(lastLoadstateMade,fn.c_str()); + strcpy(lastLoadstateMade,fn); } if(st == NULL) @@ -711,6 +744,27 @@ bool FCEUSS_Load(const char *fname) SaveStateStatus[CurrentState]=1; } delete st; + + #ifdef _S9XLUA_H + if (!internalSaveLoad) + { + LuaSaveData saveData; + + char luaSaveFilename [512]; + strncpy(luaSaveFilename, fn, 512); + luaSaveFilename[512-(1+7/*strlen(".luasav")*/)] = '\0'; + strcat(luaSaveFilename, ".luasav"); + FILE* luaSaveFile = fopen(luaSaveFilename, "rb"); + if(luaSaveFile) + { + saveData.ImportRecords(luaSaveFile); + fclose(luaSaveFile); + } + + CallRegisteredLuaLoadFunctions(CurrentState, saveData); + } + #endif + #ifdef WIN32 Update_RAM_Search(); // Update_RAM_Watch() is also called. #endif @@ -997,7 +1051,9 @@ bool CheckBackupSaveStateExist() void BackupLoadState() { string filename = GetBackupFileName(); + internalSaveLoad = true; FCEUSS_Save(filename.c_str()); + internalSaveLoad = false; undoLS = true; } @@ -1007,7 +1063,9 @@ void LoadBackup() string filename = GetBackupFileName(); //Get backup filename if (CheckBackupSaveStateExist()) { + internalSaveLoad = true; FCEUSS_Load(filename.c_str()); //Load it + internalSaveLoad = false; redoLS = true; //Flag redoLoadState undoLS = false; //Flag that LoadBackup cannot be run again } diff --git a/vc/Help/fceux-2.1.1.hnd b/vc/Help/fceux-2.1.1.hnd index 2f005e660509645c1676ece8d4c397a228735626..bf9f78ca9ed6aabecd00f6c44f0950392ae9ad12 100644 GIT binary patch delta 4450 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 delta 4418 zcmZ9QX&@AD9LINNbKmE>k0?jjawfScge3}*9MQV(E60Xhks~n@Nyrt7KN~hG_fe7~ z3N1oWM95YBwX^%CeKVi^et*}@GcTTJzKt+;jW9Bqok1SpL?95fhykp{6DBHome zOa2fo3Di&MBS{da-c3ycGd)!X5~N^+=txjyjXFSrF1sLh5@`B}!isZ1IY?4if`i~X z31Pdi5*H!MMzZQ}eukC%=^#BxDaoA&NRXPB4O2Tw5dDOReiX70J5YuLr}2K{^-~Mz6v#m*4NeEgUC@U{E2Rg=13Y9>SosM9V~~ zqqUVmxHGXDI&!F2R(u3oyZPWxoM< zN$N8|h>JAJ5pGZoF6<@ADfN;cEaM@gD&2{2v z>vl$`0b_rYZi;G(@R5(>2&KCj!P2eueSf|?&>(RI%xfI%XKW9TMr}Tvem2&2Cy>fW z=pi9Ao$se&`LD#6$e3W%Ddttbxx^X^Op9(o)ZxB>rO_j*?F30LK2#do3VC+ zvYG4GL)TelRwCxWPASKE7q7PuB&er+)q+gk5FCex{Zl@yjS_82Vv;seM2E#EoX2$T zHf*!uFmtIPl4(Ch!e8Glf@}%xZZ@{q4eS3@ic}2>j#=+ohd2lIhw#2DhrVX=wrbbM zFjd4fU6jh}QRPtMH?S9Zz%tf(&2>_)Df~O1q@XOJmF}24y8goAN1?2_YVT)syeh1F zA9Rz39SB{rt?3=C+VfP(AakP7Vq=C$d0ns{&hn9ri3i_c(Lof$6Sr#hm~y8n!he(H zx`~IQycf^&yKtUQda4z6GkB)X;4w<6N?Sm&U2NJi zl#o;17!BU*O>9r;j&2FXE=EYGyqa;cMi~s_-koBzXnx0zsV)jHY$m=bal6$Aa;&wj z+tCl^b3>m-QV&qO6#KM<`w!(sn||^efzF(=;`QQ;W^#?b%XPxT)&`#tUz2wMNmV_O zNo#ftH!F01N9v*#mOKAuHRJ30`1LSU-oy8GWmH-O)KLrm7%z$AT-*VE$mZLYIq`I& zxJ(1Xy~rSel{6o%+|qn=S~>}D>!qiAJwaX`q3@Uqt3U7cqdWdMpf@eTH|jE4@i$M- zr22~7^F$H!S%W(s++VKlB=9}C?>tZZD4w|IfNomfxU*{JF=O0*DdAmRu+SOt3CnL5 zS?b8ndBtS{C)Z0x#jgx|a-3Bgm1Wr5Ww?gHQk#m#c<8tx^NXNEi3fOQ@#LHK)@>FO-PQNYZxv);F)9 zn#rDhU8AEaaJta=Jbz;GI_uFFOYYgFzp#W^+At<`Ygnj1W38^R%a7hiPbGxM+i7vs zp$g0?bDo`|Wr0So29;~FWT-?;^r0N+xfnt=B}SB9QdTO4oVzMY8s0Sd*K~d-ULv`OsR93c)0-BW(iRhq$EEez~(gCy#g$cYkcp z$g%hQ(JQl!W+$o^w+LHOxH2&Rtj^5~J5@;n)}v7e>*E&o4@g}G*x1| zSR{eZ<+7+wXTy;VJhl3B>y1!ACa^shRXX#dXiq~O?WOynx_MQn=2gWMB-3tM%^{2FGnnNVxSown~{@3t&_f9Q*v%d=Q|tk zIw!!Ot_++`sgv% zM?>BoM17Gw;E+ZfYM=!$tYz|Qm-}RSD{a=Z(ywzk>m=Hk7_;qr;eK4X40{SB<_7AX zKOz0RtcT6L){|Yg8UNsIXnoasP|r@rRpZSKY?GH^_LVkKucMVK2WN6O2kkq5D4B+w zN6^Y^>r~H*zq0O@^D2o^RZwu1J&tAe=gxiOxH@js~8&Y)#C2bzl=CEyA7E*=3mf9&6X4hcgnu3`}WIZxbPBNPOrv5u-24ZaaifD^|rE4x)FOo zG3vO1{IQ$-*;yX>IJ47E*@_R%Y=y((yY+=8kC)796-N-BZ=z@~B{^^W^y)KwQ z<)|m3opDqc8gb5Mnq~9^-Fkxhb-K9Ar_Do~jJbJV2u`JA%yFabiN%qD&$0$KllPv>zrOQ4+1=^a zz1*fo@T{&dL-Eqqq+rS`5R9vIsa&dZ7U!<2D$(x+oD(@)}fi?mn26w)znZ5#D%OW}MyYB%P73{~*;GBh!%D5@M^%-?7L+Z-g0 z=AvSaQuB^$eDQA^3_%yzk7Y^K>eE)z6hz1?VI|fo%r)<%+p7Gz9H>=Ld-^~@4YRjb zX;ysiti`LY43jeEWK61jj}GC$-95NByHME%;dFr0_w(tXsv(`_-2_; zIVH%{2bjw)e&^Q0gr5y@;pSHtiKD5nqXt`X_PUJ9EM;>JwnVpyC-&O^Wr{3ZccRG^ zk3PuV#ugz%H@tgk1@VmPh0bFveQwaox;#J=B`)AEx%wyp=vKsm{#UX zi$m@Z>OM5$1&WIYgbXe74~Yjmrrql0;QZEQ&C8KUv-1%9jaD{$(<;Ym*ne>v67&Y; z0Y%8n85j1bg-=zTyD(sozY2b?CI8SrfWpbxwUj|~MG;T`|2NN^S2N5{v2)*Pga z=`1h<-scAuf?IH|Z!sunN^;ZJKsV9}2*0-nuEF(Ik;rUP26Tf3sr)w#YGXm#kU*G~ z4B-@bM*#&kvXw@GB?_EoCtF1n_(K6(4zh)(00$>o@u5H;1thr0RxAZRQb3KHY(W(G zK>-YkY*kW#hKH;;Q=pXs{Jdlwe+mjXX2U~!0S)lh)pFw*9qGZ^H57-=f<_gvPG$GGQ!2m}`m0wK5m zDFFV7>|b`gA>v`=N%&=xK=LD}{6I+5A4Ef*5=heV8w%f-0W%1aK&lJ>Eq1VHW~}T0 z0&$!1-(r`bAC#cM?`}_7I1q>e^uIwC5JnQIPHLDYx!KOE}cz89g^{eq;v lf28&s^g#U5