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 2f005e66..bf9f78ca 100644 Binary files a/vc/Help/fceux-2.1.1.hnd and b/vc/Help/fceux-2.1.1.hnd differ