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)
This commit is contained in:
parent
ea92db188b
commit
0bc230bd31
|
@ -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
|
||||
|
|
|
@ -21,6 +21,7 @@ extern "C"
|
|||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include <lstate.h>
|
||||
}
|
||||
|
||||
#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<typename T>
|
||||
void PushBinaryItem(T item, std::vector<unsigned char>& 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<typename T>
|
||||
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<unsigned char>& 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<uint32>(count, output);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<const void*> s_tableAddressStack; // prevents infinite recursion of a table within a table (when cycle is found, print something like table:parent)
|
||||
static std::vector<const void*> 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<unsigned char>& 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<int32>(static_cast<int32>(inum), output);
|
||||
break;
|
||||
case LUAEXT_TUSHORT:
|
||||
PushBinaryItem<uint16>(static_cast<uint16>(inum), output);
|
||||
break;
|
||||
case LUAEXT_TSHORT:
|
||||
PushBinaryItem<int16>(static_cast<int16>(inum), output);
|
||||
break;
|
||||
case LUAEXT_TBYTE:
|
||||
output.push_back(static_cast<uint8>(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<unsigned char>(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<uint8>(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<double>(data, remaining));
|
||||
break;
|
||||
case LUAEXT_TLONG:
|
||||
lua_pushinteger(L, AdvanceByteStream<int32>(data, remaining));
|
||||
break;
|
||||
case LUAEXT_TUSHORT:
|
||||
lua_pushinteger(L, AdvanceByteStream<uint16>(data, remaining));
|
||||
break;
|
||||
case LUAEXT_TSHORT:
|
||||
lua_pushinteger(L, AdvanceByteStream<int16>(data, remaining));
|
||||
break;
|
||||
case LUAEXT_TBYTE:
|
||||
lua_pushinteger(L, AdvanceByteStream<uint8>(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<uint8>(data, remaining);
|
||||
if(BITMATCH(type,LUAEXT_BITS_4A) || BITMATCH(type,LUAEXT_BITS_2A))
|
||||
arraySize |= ((uint16)AdvanceByteStream<uint8>(data, remaining)) << 8;
|
||||
if(BITMATCH(type,LUAEXT_BITS_4A))
|
||||
arraySize |= ((uint32)AdvanceByteStream<uint8>(data, remaining)) << 16,
|
||||
arraySize |= ((uint32)AdvanceByteStream<uint8>(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<uint8>(data, remaining);
|
||||
if(BITMATCH(type,LUAEXT_BITS_4H) || BITMATCH(type,LUAEXT_BITS_2H))
|
||||
hashSize |= ((uint16)AdvanceByteStream<uint8>(data, remaining)) << 8;
|
||||
if(BITMATCH(type,LUAEXT_BITS_4H))
|
||||
hashSize |= ((uint32)AdvanceByteStream<uint8>(data, remaining)) << 16,
|
||||
hashSize |= ((uint32)AdvanceByteStream<uint8>(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<uint32>(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<unsigned char> 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<unsigned char> 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<const void*> s_tableAddressStack; // prevents infinite recursion of a table within a table (when cycle is found, print something like table:parent)
|
||||
static std::vector<const void*> 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}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue