#include #include "Common/Log.h" #include "Common/StringUtils.h" #include "Core/LuaContext.h" #include "Core/MemMap.h" // Sol is expensive to include so we only do it here. #include "ext/sol/sol.hpp" LuaContext g_lua; static bool IsProbablyExpression(std::string_view input) { // Heuristic: If it's a single-line statement without assignment or keywords, assume it's an expression. return !(input.find("=") != std::string_view::npos || input.find("function") != std::string_view::npos || input.find("do") != std::string_view::npos || input.find("end") != std::string_view::npos || input.find("return") != std::string_view::npos || input.find("local") != std::string_view::npos); } // Custom print function static void print(const std::string& message) { g_lua.Print(message); } // TODO: Should these also echo to the console? static void debug(const std::string &message) { DEBUG_LOG(Log::System, "%s", message.c_str()); } static void info(const std::string &message) { INFO_LOG(Log::System, "%s", message.c_str()); } static void warn(const std::string &message) { WARN_LOG(Log::System, "%s", message.c_str()); } static void error(const std::string &message) { ERROR_LOG(Log::System, "%s", message.c_str()); } // TODO: We should probably disallow or at least discourage raw read/writes and instead // only support read/writes that refer to the name of a memory region. static int r32(int address) { if (Memory::IsValid4AlignedAddress(address)) { return Memory::Read_U32(address); } else { g_lua.Print(LogLineType::Error, StringFromFormat("r32: bad address %08x", address)); return 0; } } static void w32(int address, int value) { if (Memory::IsValid4AlignedAddress(address)) { Memory::Write_U32(value, address); // NOTE: These are backwards for historical reasons. } else { g_lua.Print(LogLineType::Error, StringFromFormat("w32: bad address %08x trying to write %08x", address, value)); } } void LuaContext::Init() { _dbg_assert_(lua_ == nullptr); lua_.reset(new sol::state()); lua_->open_libraries(sol::lib::base); lua_->open_libraries(sol::lib::table); lua_->open_libraries(sol::lib::bit32); lua_->open_libraries(sol::lib::string); lua_->open_libraries(sol::lib::math); extern const char *PPSSPP_GIT_VERSION; lua_->set("ver", PPSSPP_GIT_VERSION); lua_->set("print", &print); lua_->set("debug", &debug); lua_->set("info", &info); lua_->set("warn", &warn); lua_->set("error", &error); lua_->set("r32", &r32); } void LuaContext::Shutdown() { lua_.reset(); } const char *SolTypeToString(sol::type type) { switch (type) { case sol::type::boolean: return "boolean"; default: return "other"; } } void LuaContext::Print(LogLineType type, std::string_view text) { lines_.push_back(LuaLogLine{ type, std::string(text)}); } void LuaContext::ExecuteConsoleCommand(std::string_view cmd) { // TODO: Also rewrite expressions like: // print "hello" // to // print("hello") ? try { std::string command; if (IsProbablyExpression(cmd)) { command = "return "; command += cmd; } else { command = cmd; } auto result = lua_->script(command); if (result.valid()) { for (const sol::stack_proxy &item : result) { switch (item.get_type()) { case sol::type::number: { int num = item.get(); lines_.push_back(LuaLogLine{ LogLineType::Integer, StringFromFormat("%08x (%d)", num, num), item.get()}); break; } case sol::type::string: { // TODO: Linebreak multi-line strings. lines_.push_back(LuaLogLine{ LogLineType::String, item.get() }); break; } default: break; } } } else { sol::error err = result; lines_.push_back(LuaLogLine{ LogLineType::Error, std::string(err.what()) }); } } catch (sol::error e) { ERROR_LOG(Log::System, "Lua exception: %s", e.what()); lines_.push_back(LuaLogLine{ LogLineType::Error, std::string(e.what()) }); } }