/* Copyright 2021 flyinghead This file is part of Flycast. Flycast 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. Flycast 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 Flycast. If not, see . */ #include "lua.h" #ifdef USE_LUA #include #include #include "ui/gui.h" #include "ui/gui_util.h" #include "hw/mem/addrspace.h" #include "cfg/option.h" #include "emulator.h" #include "input/gamepad_device.h" #include "input/mouse.h" #include "hw/maple/maple_devs.h" #include "hw/maple/maple_if.h" #include "stdclass.h" #include "imgui.h" namespace lua { const char *CallbackTable = "flycast_callbacks"; static lua_State *L; using namespace luabridge; static std::recursive_mutex mutex; using lock_guard = std::lock_guard; static void emuEventCallback(Event event, void *) { if (L == nullptr || settings.raHardcoreMode) return; lock_guard lock(mutex); try { LuaRef v = LuaRef::getGlobal(L, CallbackTable); if (!v.isTable()) return; const char *key = nullptr; switch (event) { case Event::Start: key = "start"; break; case Event::Resume: key = "resume"; break; case Event::Pause: key = "pause"; break; case Event::Terminate: key = "terminate"; break; case Event::LoadState: key = "loadState"; break; case Event::VBlank: key = "vblank"; break; case Event::Network: key = "network"; break; case Event::DiskChange: key = "diskChange"; break; } if (v[key].isFunction()) v[key](); } catch (const LuaException& e) { WARN_LOG(COMMON, "Lua exception: %s", e.what()); } } static void eventCallback(const char *tag) { if (L == nullptr) return; lock_guard lock(mutex); try { LuaRef v = LuaRef::getGlobal(L, CallbackTable); if (v.isTable() && v[tag].isFunction()) v[tag](); } catch (const LuaException& e) { WARN_LOG(COMMON, "Lua exception[%s]: %s", tag, e.what()); } } void overlay() { eventCallback("overlay"); } template static LuaRef readMemoryTable(u32 address, int count, lua_State* L) { LuaRef t(L); t = newTable(L); while (count > 0) { t[address] = addrspace::readt(address); address += sizeof(T); count--; } return t; } #define CONFIG_ACCESSORS(Config) \ template \ static T get ## Config() { \ return config::Config.get(); \ } \ template \ static void set ## Config(T v) \ { \ config::Config.set(v); \ } // General CONFIG_ACCESSORS(Cable); CONFIG_ACCESSORS(Region); CONFIG_ACCESSORS(Broadcast); CONFIG_ACCESSORS(Language); CONFIG_ACCESSORS(AutoLoadState); CONFIG_ACCESSORS(AutoSaveState); CONFIG_ACCESSORS(SavestateSlot); // TODO Option, false> ContentPath; CONFIG_ACCESSORS(HideLegacyNaomiRoms) // Video CONFIG_ACCESSORS(RendererType) CONFIG_ACCESSORS(Widescreen) CONFIG_ACCESSORS(UseMipmaps) CONFIG_ACCESSORS(SuperWidescreen) CONFIG_ACCESSORS(ShowFPS) CONFIG_ACCESSORS(RenderToTextureBuffer) CONFIG_ACCESSORS(TranslucentPolygonDepthMask) CONFIG_ACCESSORS(ModifierVolumes) CONFIG_ACCESSORS(TextureUpscale) CONFIG_ACCESSORS(MaxFilteredTextureSize) CONFIG_ACCESSORS(ExtraDepthScale) CONFIG_ACCESSORS(CustomTextures) CONFIG_ACCESSORS(DumpTextures) CONFIG_ACCESSORS(ScreenStretching) CONFIG_ACCESSORS(Fog) CONFIG_ACCESSORS(FloatVMUs) CONFIG_ACCESSORS(Rotate90) CONFIG_ACCESSORS(PerStripSorting) CONFIG_ACCESSORS(DelayFrameSwapping) CONFIG_ACCESSORS(WidescreenGameHacks) //TODO CrosshairColor; CONFIG_ACCESSORS(SkipFrame) CONFIG_ACCESSORS(MaxThreads) CONFIG_ACCESSORS(AutoSkipFrame) CONFIG_ACCESSORS(RenderResolution) CONFIG_ACCESSORS(VSync) CONFIG_ACCESSORS(PixelBufferSize) CONFIG_ACCESSORS(AnisotropicFiltering) CONFIG_ACCESSORS(TextureFiltering) CONFIG_ACCESSORS(ThreadedRendering) // Audio CONFIG_ACCESSORS(DSPEnabled) CONFIG_ACCESSORS(AudioBufferSize) CONFIG_ACCESSORS(AutoLatency) CONFIG_ACCESSORS(AudioBackend) CONFIG_ACCESSORS(AudioVolume) // Advanced CONFIG_ACCESSORS(DynarecEnabled) CONFIG_ACCESSORS(SerialConsole) CONFIG_ACCESSORS(SerialPTY) CONFIG_ACCESSORS(UseReios) CONFIG_ACCESSORS(FastGDRomLoad) CONFIG_ACCESSORS(OpenGlChecks) // Network CONFIG_ACCESSORS(NetworkEnable) CONFIG_ACCESSORS(ActAsServer) CONFIG_ACCESSORS(DNS) CONFIG_ACCESSORS(NetworkServer) CONFIG_ACCESSORS(EmulateBBA) CONFIG_ACCESSORS(GGPOEnable) CONFIG_ACCESSORS(GGPODelay) CONFIG_ACCESSORS(NetworkStats) CONFIG_ACCESSORS(GGPOAnalogAxes) // Maple devices static int getMapleType(int bus, lua_State *L) { luaL_argcheck(L, bus >= 1 && bus <= 4, 1, "bus must be between 1 and 4"); if (MapleDevices[bus - 1][5] == nullptr) return MDT_None; return MapleDevices[bus - 1][5]->get_device_type(); } static int getMapleSubType(int bus, int port, lua_State *L) { luaL_argcheck(L, bus >= 1 && bus <= 4, 1, "bus must be between 1 and 4"); luaL_argcheck(L, port >= 1 && port <= 2, 2, "port must be between 1 and 2"); if (MapleDevices[bus - 1][port - 1] == nullptr) return MDT_None; return MapleDevices[bus - 1][port - 1]->get_device_type(); } static void setMapleType(int bus, int type, lua_State *L) { luaL_argcheck(L, bus >= 1 && bus <= 4, 1, "bus must be between 1 and 4"); switch ((MapleDeviceType)type) { case MDT_SegaController: case MDT_AsciiStick: case MDT_Keyboard: case MDT_Mouse: case MDT_LightGun: case MDT_TwinStick: case MDT_None: config::MapleMainDevices[bus - 1] = (MapleDeviceType)type; maple_ReconnectDevices(); break; default: luaL_argerror(L, 2, "Invalid device type"); break; } } static void setMapleSubType(int bus, int port, int type, lua_State *L) { luaL_argcheck(L, bus >= 1 && bus <= 4, 1, "bus must be between 1 and 4"); luaL_argcheck(L, port >= 1 && port <= 2, 2, "port must be between 1 and 2"); switch ((MapleDeviceType)type) { case MDT_SegaVMU: case MDT_PurupuruPack: case MDT_Microphone: case MDT_None: config::MapleExpansionDevices[bus - 1][port - 1] = (MapleDeviceType)type; maple_ReconnectDevices(); break; default: luaL_argerror(L, 3, "Invalid device type"); break; } } // Inputs static void checkPlayerNum(lua_State *L, int player) { luaL_argcheck(L, player >= 1 && player <= 4, 1, "player must be between 1 and 4"); } static u32 getButtons(int player, lua_State *L) { checkPlayerNum(L, player); return kcode[player - 1]; } static void pressButtons(int player, u32 buttons, lua_State *L) { checkPlayerNum(L, player); kcode[player - 1] &= ~buttons; } static void releaseButtons(int player, u32 buttons, lua_State *L) { checkPlayerNum(L, player); kcode[player - 1] |= buttons; } static int getAxis(int player, int axis, lua_State *L) { checkPlayerNum(L, player); luaL_argcheck(L, axis >= 1 && axis <= 6, 2, "axis must be between 1 and 6"); switch (axis - 1) { case 0: return joyx[player - 1]; case 1: return joyy[player - 1]; case 2: return joyrx[player - 1]; case 3: return joyry[player - 1]; case 4: return lt[player - 1]; case 5: return rt[player - 1]; default: return 0; } } static void setAxis(int player, int axis, int value, lua_State *L) { checkPlayerNum(L, player); luaL_argcheck(L, axis >= 1 && axis <= 6, 2, "axis must be between 1 and 6"); switch (axis - 1) { case 0: joyx[player - 1] = value; break; case 1: joyy[player - 1] = value; break; case 2: joyrx[player - 1] = value; break; case 3: joyry[player - 1] = value; break; case 4: lt[player - 1] = value; break; case 5: rt[player - 1] = value; break; default: break; } } static int getAbsCoordinates(lua_State *L) { int player = luaL_checkinteger(L, 1); checkPlayerNum(L, player); lua_pushnumber(L, mo_x_abs[player - 1]); lua_pushnumber(L, mo_y_abs[player - 1]); return 2; } static void setAbsCoordinates(int player, int x, int y, lua_State *L) { checkPlayerNum(L, player); SetMousePosition(x, y, settings.display.width, settings.display.height, player - 1); } static int getRelCoordinates(lua_State *L) { int player = luaL_checkinteger(L, 1); checkPlayerNum(L, player); lua_pushnumber(L, mo_x_delta[player - 1]); lua_pushnumber(L, mo_y_delta[player - 1]); return 2; } static void setRelCoordinates(int player, float x, float y, lua_State *L) { checkPlayerNum(L, player); SetRelativeMousePosition(x, y, player - 1); } // UI static void beginWindow(const char *title, int x, int y, int w, int h) { ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::SetNextWindowPos(ImVec2(x, y)); ImGui::SetNextWindowSize(ScaledVec2(w, h)); ImGui::SetNextWindowBgAlpha(0.7f); ImGui::Begin(title, NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); } static void endWindow() { ImGui::PopStyleColor(); ImGui::End(); ImGui::PopStyleVar(2); } static void uiText(const std::string& text) { ImGui::Text("%s", text.c_str()); } static void uiTextRightAligned(const std::string& text) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(text.c_str()).x); uiText(text); } static void uiBargraph(float v) { ImGui::ProgressBar(v, ImVec2(-1, uiScaled(10.f)), ""); } static int uiButton(lua_State *L) { const char *label = luaL_checkstring(L, 1); if (ImGui::Button(label)) { LuaRef callback = LuaRef::fromStack(L, 2); if (callback.isFunction()) callback(); } return 0; } static void luaRegister(lua_State *L) { getGlobalNamespace(L) .beginNamespace ("flycast") .beginNamespace("emulator") .addFunction("startGame", gui_start_game) // FIXME threading! .addFunction("stopGame", std::function([]() { gui_stop_game(""); })) .addFunction("pause", std::function([]() { if (gui_state == GuiState::Closed) gui_open_settings(); })) .addFunction("resume", std::function([]() { if (gui_state == GuiState::Commands) gui_open_settings(); })) .addFunction("saveState", std::function([](int index) { bool restart = false; if (gui_state == GuiState::Closed) { gui_open_settings(); restart = true; } dc_savestate(index); if (restart) gui_open_settings(); })) .addFunction("loadState", std::function([](int index) { bool restart = false; if (gui_state == GuiState::Closed) { gui_open_settings(); restart = true; } dc_loadstate(index); if (restart) gui_open_settings(); })) .addFunction("exit", dc_exit) .addFunction("displayNotification", os_notify) .endNamespace() .beginNamespace("config") #define CONFIG_PROPERTY(Config, type) .addProperty(#Config, get ## Config, set ## Config) .beginNamespace("general") CONFIG_PROPERTY(Cable, int) CONFIG_PROPERTY(Region, int) CONFIG_PROPERTY(Broadcast, int) CONFIG_PROPERTY(Language, int) CONFIG_PROPERTY(AutoLoadState, bool) CONFIG_PROPERTY(AutoSaveState, bool) CONFIG_PROPERTY(SavestateSlot, int) CONFIG_PROPERTY(HideLegacyNaomiRoms, bool) .endNamespace() .beginNamespace("video") // FIXME .addProperty("RendererType", getRendererType, setRendererType) CONFIG_PROPERTY(Widescreen, bool) CONFIG_PROPERTY(SuperWidescreen, bool) CONFIG_PROPERTY(UseMipmaps, bool) CONFIG_PROPERTY(ShowFPS, bool) CONFIG_PROPERTY(RenderToTextureBuffer, bool) CONFIG_PROPERTY(TranslucentPolygonDepthMask, bool) CONFIG_PROPERTY(ModifierVolumes, bool) CONFIG_PROPERTY(TextureUpscale, int) CONFIG_PROPERTY(MaxFilteredTextureSize, int) CONFIG_PROPERTY(ExtraDepthScale, float) CONFIG_PROPERTY(CustomTextures, bool) CONFIG_PROPERTY(DumpTextures, bool) CONFIG_PROPERTY(ScreenStretching, int) CONFIG_PROPERTY(Fog, bool) CONFIG_PROPERTY(FloatVMUs, bool) CONFIG_PROPERTY(Rotate90, bool) CONFIG_PROPERTY(PerStripSorting, bool) CONFIG_PROPERTY(DelayFrameSwapping, bool) CONFIG_PROPERTY(WidescreenGameHacks, bool) // TODO CrosshairColor; CONFIG_PROPERTY(SkipFrame, int) CONFIG_PROPERTY(MaxThreads, int) CONFIG_PROPERTY(AutoSkipFrame, int) CONFIG_PROPERTY(RenderResolution, int) CONFIG_PROPERTY(VSync, bool) CONFIG_PROPERTY(PixelBufferSize, u64) CONFIG_PROPERTY(AnisotropicFiltering, int) CONFIG_PROPERTY(TextureFiltering, int) CONFIG_PROPERTY(ThreadedRendering, bool) .endNamespace() .beginNamespace("audio") CONFIG_PROPERTY(DSPEnabled, bool) CONFIG_PROPERTY(AudioBufferSize, int) CONFIG_PROPERTY(AutoLatency, bool) CONFIG_PROPERTY(AudioBackend, std::string) CONFIG_PROPERTY(AudioVolume, int) .endNamespace() .beginNamespace("advanced") CONFIG_PROPERTY(DynarecEnabled, bool) CONFIG_PROPERTY(SerialConsole, bool) CONFIG_PROPERTY(SerialPTY, bool) CONFIG_PROPERTY(UseReios, bool) CONFIG_PROPERTY(FastGDRomLoad, bool) CONFIG_PROPERTY(OpenGlChecks, bool) .endNamespace() .beginNamespace("network") CONFIG_PROPERTY(NetworkEnable, bool) CONFIG_PROPERTY(ActAsServer, bool) CONFIG_PROPERTY(DNS, std::string) CONFIG_PROPERTY(NetworkServer, std::string) CONFIG_PROPERTY(EmulateBBA, bool) CONFIG_PROPERTY(GGPOEnable, bool) CONFIG_PROPERTY(GGPODelay, int) CONFIG_PROPERTY(NetworkStats, bool) CONFIG_PROPERTY(GGPOAnalogAxes, int) .endNamespace() .beginNamespace("maple") .addFunction("getDeviceType", getMapleType) .addFunction("getSubDeviceType", getMapleSubType) .addFunction("setDeviceType", setMapleType) .addFunction("setSubDeviceType", setMapleSubType) .endNamespace() .endNamespace() .beginNamespace("memory") .addFunction("read8", addrspace::readt) .addFunction("read16", addrspace::readt) .addFunction("read32", addrspace::readt) .addFunction("read64", addrspace::readt) .addFunction("readTable8", readMemoryTable) .addFunction("readTable16", readMemoryTable) .addFunction("readTable32", readMemoryTable) .addFunction("readTable64", readMemoryTable) .addFunction("write8", addrspace::writet) .addFunction("write16", addrspace::writet) .addFunction("write32", addrspace::writet) .addFunction("write64", addrspace::writet) .endNamespace() .beginNamespace("input") .addFunction("getButtons", getButtons) .addFunction("pressButtons", pressButtons) .addFunction("releaseButtons", releaseButtons) .addFunction("getAxis", getAxis) .addFunction("setAxis", setAxis) .addFunction("getAbsCoordinates", getAbsCoordinates) .addFunction("setAbsCoordinates", setAbsCoordinates) .addFunction("getRelCoordinates", getRelCoordinates) .addFunction("setRelCoordinates", setRelCoordinates) .endNamespace() .beginNamespace("state") .addProperty("system", &settings.platform.system, false) .addProperty("media", &settings.content.path, false) .addProperty("gameId", &settings.content.gameId, false) .beginNamespace("display") .addProperty("width", &settings.display.width, false) .addProperty("height", &settings.display.height, false) .endNamespace() .endNamespace() .beginNamespace("ui") .addFunction("beginWindow", beginWindow) .addFunction("endWindow", endWindow) .addFunction("text", uiText) .addFunction("rightText", uiTextRightAligned) .addFunction("bargraph", uiBargraph) .addFunction("button", uiButton) .endNamespace() .endNamespace(); } static std::string getLuaFile() { std::string initFile; if( !config::LuaFileName.get().empty()){ initFile = get_readonly_config_path(config::LuaFileName.get()); } else { initFile = get_readonly_config_path("flycast.lua"); } return initFile; } static void doExec(const std::string& path) { if (L == nullptr) return; DEBUG_LOG(COMMON, "Executing script: %s", path.c_str()); int err = luaL_dofile(L, path.c_str()); if (err != 0) WARN_LOG(COMMON, "Lua error: %s", lua_tostring(L, -1)); } void exec(const std::string& path) { std::string file = get_readonly_config_path(path); if (!file_exists(file)) return; doExec(file); } void init() { std::string initFile = getLuaFile(); if (!file_exists(initFile)) return; L = luaL_newstate(); luaL_openlibs(L); luaRegister(L); EventManager::listen(Event::Start, emuEventCallback); EventManager::listen(Event::Resume, emuEventCallback); EventManager::listen(Event::Pause, emuEventCallback); EventManager::listen(Event::Terminate, emuEventCallback); EventManager::listen(Event::LoadState, emuEventCallback); EventManager::listen(Event::VBlank, emuEventCallback); EventManager::listen(Event::Network, emuEventCallback); doExec(initFile); } void term() { if (L == nullptr) return; EventManager::unlisten(Event::Start, emuEventCallback); EventManager::unlisten(Event::Resume, emuEventCallback); EventManager::unlisten(Event::Pause, emuEventCallback); EventManager::unlisten(Event::Terminate, emuEventCallback); EventManager::unlisten(Event::LoadState, emuEventCallback); EventManager::unlisten(Event::VBlank, emuEventCallback); EventManager::unlisten(Event::Network, emuEventCallback); lua_close(L); L = nullptr; } } #endif