diff options
Diffstat (limited to 'engines/sword25/script/luascript.cpp')
-rw-r--r-- | engines/sword25/script/luascript.cpp | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/engines/sword25/script/luascript.cpp b/engines/sword25/script/luascript.cpp new file mode 100644 index 0000000000..aa2bdb9e06 --- /dev/null +++ b/engines/sword25/script/luascript.cpp @@ -0,0 +1,557 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "LUA" + +#include "common/array.h" +#include "common/debug-channels.h" + +#include "sword25/sword25.h" +#include "sword25/package/packagemanager.h" +#include "sword25/script/luascript.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lualib.h" +#include "sword25/util/lua/lauxlib.h" +#include "sword25/util/pluto/pluto.h" + +namespace Sword25 { + +LuaScriptEngine::LuaScriptEngine(Kernel *KernelPtr) : + ScriptEngine(KernelPtr), + _state(0), + _pcallErrorhandlerRegistryIndex(0) { +} + +LuaScriptEngine::~LuaScriptEngine() { + // Lua de-initialisation + if (_state) + lua_close(_state); +} + +namespace { +int panicCB(lua_State *L) { + BS_LOG_ERRORLN("Lua panic. Error message: %s", lua_isnil(L, -1) ? "" : lua_tostring(L, -1)); + return 0; +} + +void debugHook(lua_State *L, lua_Debug *ar) { + if (!lua_getinfo(L, "Sn", ar)) + return; + + debug("LUA: %s %s: %s %d", ar->namewhat, ar->name, ar->short_src, ar->currentline); +} +} + +bool LuaScriptEngine::init() { + // Lua-State initialisation, as well as standard libaries initialisation + _state = luaL_newstate(); + if (!_state || ! registerStandardLibs() || !registerStandardLibExtensions()) { + BS_LOG_ERRORLN("Lua could not be initialized."); + return false; + } + + // Register panic callback function + lua_atpanic(_state, panicCB); + + // Error handler for lua_pcall calls + // The code below contains a local error handler function + const char errorHandlerCode[] = + "local function ErrorHandler(message) " + " return message .. '\\n' .. debug.traceback('', 2) " + "end " + "return ErrorHandler"; + + // Compile the code + if (luaL_loadbuffer(_state, errorHandlerCode, strlen(errorHandlerCode), "PCALL ERRORHANDLER") != 0) { + // An error occurred, so dislay the reason and exit + BS_LOG_ERRORLN("Couldn't compile luaL_pcall errorhandler:\n%s", lua_tostring(_state, -1)); + lua_pop(_state, 1); + + return false; + } + // Running the code, the error handler function sets the top of the stack + if (lua_pcall(_state, 0, 1, 0) != 0) { + // An error occurred, so dislay the reason and exit + BS_LOG_ERRORLN("Couldn't prepare luaL_pcall errorhandler:\n%s", lua_tostring(_state, -1)); + lua_pop(_state, 1); + + return false; + } + + // Place the error handler function in the Lua registry, and remember the index + _pcallErrorhandlerRegistryIndex = luaL_ref(_state, LUA_REGISTRYINDEX); + + // Initialise the Pluto-Persistence library + luaopen_pluto(_state); + lua_pop(_state, 1); + + // Initialize debugging callback + if (DebugMan.isDebugChannelEnabled(kDebugScript)) { + int mask = 0; + if ((gDebugLevel & 1) != 0) + mask |= LUA_MASKCALL; + if ((gDebugLevel & 2) != 0) + mask |= LUA_MASKRET; + if ((gDebugLevel & 4) != 0) + mask |= LUA_MASKLINE; + + if (mask != 0) + lua_sethook(_state, debugHook, mask, 0); + } + + BS_LOGLN("Lua initialized."); + + return true; +} + +bool LuaScriptEngine::executeFile(const Common::String &fileName) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(_state); +#endif + debug(2, "LuaScriptEngine::executeFile(%s)", fileName.c_str()); + + // Get a pointer to the package manager + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + // File read + uint fileSize; + byte *fileData = pPackage->getFile(fileName, &fileSize); + if (!fileData) { + BS_LOG_ERRORLN("Couldn't read \"%s\".", fileName.c_str()); +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(_state)); +#endif + return false; + } + + // Run the file content + if (!executeBuffer(fileData, fileSize, "@" + pPackage->getAbsolutePath(fileName))) { + // Release file buffer + delete[] fileData; +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(_state)); +#endif + return false; + } + + // Release file buffer + delete[] fileData; + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(_state)); +#endif + + return true; +} + +bool LuaScriptEngine::executeString(const Common::String &code) { + return executeBuffer((byte *)code.c_str(), code.size(), "???"); +} + +namespace { + +void removeForbiddenFunctions(lua_State *L) { + static const char *FORBIDDEN_FUNCTIONS[] = { + "dofile", + 0 + }; + + const char **iterator = FORBIDDEN_FUNCTIONS; + while (*iterator) { + lua_pushnil(L); + lua_setfield(L, LUA_GLOBALSINDEX, *iterator); + ++iterator; + } +} +} + +bool LuaScriptEngine::registerStandardLibs() { + luaL_openlibs(_state); + removeForbiddenFunctions(_state); + return true; +} + +bool LuaScriptEngine::executeBuffer(const byte *data, uint size, const Common::String &name) const { + // Compile buffer + if (luaL_loadbuffer(_state, (const char *)data, size, name.c_str()) != 0) { + BS_LOG_ERRORLN("Couldn't compile \"%s\":\n%s", name.c_str(), lua_tostring(_state, -1)); + lua_pop(_state, 1); + + return false; + } + + // Error handling function to be executed after the function is put on the stack + lua_rawgeti(_state, LUA_REGISTRYINDEX, _pcallErrorhandlerRegistryIndex); + lua_insert(_state, -2); + + // Run buffer contents + if (lua_pcall(_state, 0, 0, -2) != 0) { + BS_LOG_ERRORLN("An error occured while executing \"%s\":\n%s.", + name.c_str(), + lua_tostring(_state, -1)); + lua_pop(_state, 2); + + return false; + } + + // Remove the error handler function from the stack + lua_pop(_state, 1); + + return true; +} + +void LuaScriptEngine::setCommandLine(const Common::StringArray &commandLineParameters) { + lua_newtable(_state); + + for (size_t i = 0; i < commandLineParameters.size(); ++i) { + lua_pushnumber(_state, i + 1); + lua_pushstring(_state, commandLineParameters[i].c_str()); + lua_settable(_state, -3); + } + + lua_setglobal(_state, "CommandLine"); +} + +namespace { +const char *PERMANENTS_TABLE_NAME = "Permanents"; + +// This array contains the name of global Lua objects that should not be persisted +const char *STANDARD_PERMANENTS[] = { + "string", + "xpcall", + "package", + "tostring", + "print", + "os", + "unpack", + "require", + "getfenv", + "setmetatable", + "next", + "assert", + "tonumber", + "io", + "rawequal", + "collectgarbage", + "getmetatable", + "module", + "rawset", + "warning", + "math", + "debug", + "pcall", + "table", + "newproxy", + "type", + "coroutine", + "select", + "gcinfo", + "pairs", + "rawget", + "loadstring", + "ipairs", + "_VERSION", + "setfenv", + "load", + "error", + "loadfile", + + "pairs_next", + "ipairs_next", + "pluto", + "Cfg", + "Translator", + "Persistence", + "CommandLine", + 0 +}; + +enum PERMANENT_TABLE_TYPE { + PTT_PERSIST, + PTT_UNPERSIST +}; + +bool pushPermanentsTable(lua_State *L, PERMANENT_TABLE_TYPE tableType) { + // Permanents-Table + lua_newtable(L); + + // All standard permanents are inserted into this table + uint Index = 0; + while (STANDARD_PERMANENTS[Index]) { + // Permanents are placed onto the stack; if it does not exist, it is simply ignored + lua_getglobal(L, STANDARD_PERMANENTS[Index]); + if (!lua_isnil(L, -1)) { + // Name of the element as a unique value on the stack + lua_pushstring(L, STANDARD_PERMANENTS[Index]); + + // If it is loaded, then it can be used + // In this case, the position of name and object are reversed on the stack + if (tableType == PTT_UNPERSIST) + lua_insert(L, -2); + + // Make an entry in the table + lua_settable(L, -3); + } else { + // Pop nil value from stack + lua_pop(L, 1); + } + + ++Index; + } + + // All registered C functions to be inserted into the table + // BS_LuaBindhelper places in the register a table in which all registered C functions + // are stored + + // Table is put on the stack + lua_getfield(L, LUA_REGISTRYINDEX, PERMANENTS_TABLE_NAME); + + if (!lua_isnil(L, -1)) { + // Iterate over all elements of the table + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // Value and index duplicated on the stack and changed in the sequence + lua_pushvalue(L, -1); + lua_pushvalue(L, -3); + + // If it is loaded, then it can be used + // In this case, the position of name and object are reversed on the stack + if (tableType == PTT_UNPERSIST) + lua_insert(L, -2); + + // Make an entry in the results table + lua_settable(L, -6); + + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(L, 1); + } + } + + // Pop the C-Permanents table from the stack + lua_pop(L, 1); + + // coroutine.yield must be registered in the extra-Permanents table because they + // are inactive coroutine C functions on the stack + + // Function coroutine.yield placed on the stack + lua_getglobal(L, "coroutine"); + lua_pushstring(L, "yield"); + lua_gettable(L, -2); + + // Store coroutine.yield with it's own unique value in the Permanents table + lua_pushstring(L, "coroutine.yield"); + + if (tableType == PTT_UNPERSIST) + lua_insert(L, -2); + + lua_settable(L, -4); + + // Coroutine table is popped from the stack + lua_pop(L, 1); + + return true; +} +} + +namespace { +int chunkwriter(lua_State *L, const void *p, size_t sz, void *ud) { + Common::Array<byte> & chunkData = *reinterpret_cast<Common::Array<byte> * >(ud); + const byte *buffer = reinterpret_cast<const byte *>(p); + + while (sz--) + chunkData.push_back(*buffer++); + + return 1; +} +} + +bool LuaScriptEngine::persist(OutputPersistenceBlock &writer) { + // Empty the Lua stack. pluto_persist() xepects that the stack is empty except for its parameters + lua_settop(_state, 0); + + // Garbage Collection erzwingen. + lua_gc(_state, LUA_GCCOLLECT, 0); + + // Permanents-Table is set on the stack + // pluto_persist expects these two items on the Lua stack + pushPermanentsTable(_state, PTT_PERSIST); + lua_getglobal(_state, "_G"); + + // Lua persists and stores the data in a Common::Array + Common::Array<byte> chunkData; + pluto_persist(_state, chunkwriter, &chunkData); + + // Persistenzdaten in den Writer schreiben. + writer.writeByteArray(chunkData); + + // Die beiden Tabellen vom Stack nehmen. + lua_pop(_state, 2); + + return true; +} + +namespace { + +struct ChunkreaderData { + void *BufferPtr; + size_t Size; + bool BufferReturned; +}; + +const char *chunkreader(lua_State *L, void *ud, size_t *sz) { + ChunkreaderData &cd = *reinterpret_cast<ChunkreaderData *>(ud); + + if (!cd.BufferReturned) { + cd.BufferReturned = true; + *sz = cd.Size; + return reinterpret_cast<const char *>(cd.BufferPtr); + } else { + return 0; + } +} + +void clearGlobalTable(lua_State *L, const char **exceptions) { + // Iterate over all elements of the global table + lua_pushvalue(L, LUA_GLOBALSINDEX); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // Now the value and the index of the current element is on the stack + // This value does not interest us, so it is popped from the stack + lua_pop(L, 1); + + // Determine whether the item is set to nil, so you want to remove from the global table. + // For this will determine whether the element name is a string and is present in + // the list of exceptions + bool setElementToNil = true; + if (lua_isstring(L, -1)) { + const char *indexString = lua_tostring(L, -1); + const char **exceptionsWalker = exceptions; + while (*exceptionsWalker) { + if (strcmp(indexString, *exceptionsWalker) == 0) + setElementToNil = false; + ++exceptionsWalker; + } + } + + // If the above test showed that the item should be removed, it is removed by setting the value to nil. + if (setElementToNil) { + lua_pushvalue(L, -1); + lua_pushnil(L); + lua_settable(L, LUA_GLOBALSINDEX); + } + } + + // Pop the Global table from the stack + lua_pop(L, 1); + + // Perform garbage collection, so that all removed elements are deleted + lua_gc(L, LUA_GCCOLLECT, 0); +} +} + +bool LuaScriptEngine::unpersist(InputPersistenceBlock &reader) { + // Empty the Lua stack. pluto_persist() xepects that the stack is empty except for its parameters + lua_settop(_state, 0); + + // Permanents table is placed on the stack. This has already happened at this point, because + // to create the table all permanents must be accessible. This is the case only for the + // beginning of the function, because the global table is emptied below + pushPermanentsTable(_state, PTT_UNPERSIST); + + // All items from global table of _G and __METATABLES are removed. + // After a garbage collection is performed, and thus all managed objects deleted + + // __METATABLES is not immediately removed becausen the Metatables are needed + // for the finalisers of objects. + static const char *clearExceptionsFirstPass[] = { + "_G", + "__METATABLES", + 0 + }; + clearGlobalTable(_state, clearExceptionsFirstPass); + + // In the second pass, the Metatables are removed + static const char *clearExceptionsSecondPass[] = { + "_G", + 0 + }; + clearGlobalTable(_state, clearExceptionsSecondPass); + + // Persisted Lua data + Common::Array<byte> chunkData; + reader.readByteArray(chunkData); + + // Chunk-Reader initialisation. It is used with pluto_unpersist to restore read data + ChunkreaderData cd; + cd.BufferPtr = &chunkData[0]; + cd.Size = chunkData.size(); + cd.BufferReturned = false; + + pluto_unpersist(_state, chunkreader, &cd); + + // Permanents-Table is removed from stack + lua_remove(_state, -2); + + // The read elements in the global table about + lua_pushnil(_state); + while (lua_next(_state, -2) != 0) { + // The referenec to the global table (_G) must not be overwritten, or ticks from Lua total + bool isGlobalReference = lua_isstring(_state, -2) && strcmp(lua_tostring(_state, -2), "_G") == 0; + if (!isGlobalReference) { + lua_pushvalue(_state, -2); + lua_pushvalue(_state, -2); + + lua_settable(_state, LUA_GLOBALSINDEX); + } + + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(_state, 1); + } + + // The table with the loaded data is popped from the stack + lua_pop(_state, 1); + + // Force garbage collection + lua_gc(_state, LUA_GCCOLLECT, 0); + + return true; +} + +} // End of namespace Sword25 |