From fd7fe8091a502f00729b5aa5f1005d3bdf2aeba7 Mon Sep 17 00:00:00 2001 From: offtkp Date: Wed, 17 Jan 2024 04:01:49 +0200 Subject: [PATCH] I... forgot what I did... --- CMakeLists.txt | 23 ++- discord/bot.cxx | 392 ++++++++++++++++++++++------------------ discord/bot.hxx | 26 +++ include/corewrapper.hxx | 2 + include/gamewindow.hxx | 12 +- include/mainwindow.hxx | 3 + include/settings.hxx | 26 ++- src/app.cxx | 2 +- src/filepicker.cxx | 24 ++- src/gamewindow.cxx | 24 ++- src/mainwindow.cxx | 89 +++++++-- 11 files changed, 391 insertions(+), 232 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c494b84..54f53661 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,9 +101,14 @@ else() endif() endif() +if(HYDRA_LINUX OR HYDRA_FREEBSD OR HYDRA_MACOS OR HYDRA_WINDOWS) + set(HYDRA_DESKTOP 1) + set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_DESKTOP) +endif() + option(BUILD_QT "Build the Qt frontend" OFF) option(BUILD_LUA "Build with lua for script support" OFF) -option(BUILD_DISCORD_BOT "Build with discord bot support" OFF) +option(BUILD_DISCORD_BOT "Build with discord bot support" ON) option(USE_NATIVE_FILE_DIALOGS "Use native file dialogs" OFF) add_subdirectory(vendored/fmt) @@ -122,11 +127,6 @@ set_property(TARGET ssl PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") set_property(TARGET crypto PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") set(HYDRA_LIBRARIES ${HYDRA_LIBRARIES} ssl crypto) -if(HYDRA_LINUX OR HYDRA_FREEBSD OR HYDRA_MACOS OR HYDRA_WINDOWS) - set(HYDRA_DESKTOP 1) - set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_DESKTOP) -endif() - if(NOT HYDRA_WEB) set(CURL_USE_LIBSSH2 OFF) set(CURL_USE_LIBPSL OFF) @@ -246,15 +246,19 @@ if(BUILD_QT) set(CMAKE_AUTORCC ON) find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets OpenGLWidgets) - add_definitions(-DHYDRA_BUILD_QT) + set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_QT) set(HYDRA_SOURCES ${HYDRA_SOURCES} ${HYDRA_QT_FILES}) endif() -if(BUILD_DISCORD_BOT) +if(BUILD_DISCORD_BOT AND HYDRA_DESKTOP) + set(DPP_INSTALL OFF CACHE BOOL "" FORCE) + set(BUILD_VOICE_SUPPORT OFF CACHE BOOL "" FORCE) + set(OPENSSL_CRYPTO_LIBRARY crypto CACHE STRING "" FORCE) + set(OPENSSL_SSL_LIBRARY ssl CACHE STRING "" FORCE) add_subdirectory(vendored/DPP) - add_definitions(-DHYDRA_DISCORD_BOT) set(HYDRA_SOURCES ${HYDRA_SOURCES} ${HYDRA_BOT_FILES}) set(HYDRA_LIBRARIES ${HYDRA_LIBRARIES} dpp) + set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_DISCORD_BOT) endif() if(BUILD_LUA) @@ -263,6 +267,7 @@ if(BUILD_LUA) ${HYDRA_INCLUDES} ${LUA_INCLUDE_DIR} ) + set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_LUA) endif() set(HYDRA_SOURCES ${HYDRA_SOURCES} ${HYDRA_IMGUI_FILES} ${HYDRA_COMMON_FILES}) diff --git a/discord/bot.cxx b/discord/bot.cxx index 7a2f3ed5..b85bb635 100644 --- a/discord/bot.cxx +++ b/discord/bot.cxx @@ -1,153 +1,29 @@ -#include "commands.hxx" -#include "corewrapper.hxx" -#include "hydra/core.hxx" -#include "intents.h" -#include "message.h" -#include "unicode_emoji.h" +#include "glad/glad.h" +#include +#include #include -#include #include -#include -#include -#include -#include -#include constexpr uint32_t operator""_hash(const char* str, size_t) { return hydra::str_hash(str); } -namespace fs = std::filesystem; - -GLuint fbo; -void* get_proc_address; - -void init_gl() -{ - if (!glfwInit()) - printf("glfwInit() failed\n"); - // glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - GLFWwindow* window = glfwCreateWindow(640, 480, "", nullptr, nullptr); - if (!window) - printf("glfwCreateWindow() failed\n"); - glfwMakeContextCurrent(window); - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) - printf("gladLoadGLLoader() failed\n"); - get_proc_address = (void*)glfwGetProcAddress; - - GLuint texture; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 400, 480); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); -} - -int32_t read_input_callback(uint32_t player, hydra::ButtonType button) +Bot::Bot(std::shared_ptr emulator, GLuint fbo, const std::string& token, + int frames_pressed, int frames_released) + : emulator(emulator), fbo(fbo), + token(token.c_str()) // dpp doesn't like when the string has a bunch of extra \0s + , + frames_pressed(frames_pressed), frames_released(frames_released) { - return 0; + start(); } -struct BotState +void Bot::start() { - BotState() - { - init_gl(); - std::string core_path = Settings::Get("core_path"); - emulator = hydra::EmulatorFactory::Create(fs::path(core_path) / "libAlber.so"); - width = emulator->shell->getNativeSize().width; - height = emulator->shell->getNativeSize().height; - buffer.resize(width * height * 4); - - if (emulator->shell->hasInterface(hydra::InterfaceType::IOpenGlRendered)) - { - auto gli = emulator->shell->asIOpenGlRendered(); - gli->setGetProcAddress(get_proc_address); - gli->resetContext(); - gli->setFbo(fbo); - emulator->shell->setOutputSize({width, height}); - } - - if (emulator->shell->hasInterface(hydra::InterfaceType::IInput)) - { - hydra::IInput* shell_input = emulator->shell->asIInput(); - shell_input->setPollInputCallback([]() {}); - shell_input->setCheckButtonCallback(read_input_callback); - } - - emulator->LoadGame("/home/offtkp/Roms/3DS/zelda.3ds"); - } - - const std::vector& get_frame() - { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data()); - - for (uint32_t y = 0; y < height / 2; y++) - { - for (uint32_t x = 0; x < width; x++) - { - std::swap(buffer[(y * width + x) * 4 + 0], - buffer[((height - 1 - y) * width + x) * 4 + 0]); - std::swap(buffer[(y * width + x) * 4 + 1], - buffer[((height - 1 - y) * width + x) * 4 + 1]); - std::swap(buffer[(y * width + x) * 4 + 2], - buffer[((height - 1 - y) * width + x) * 4 + 2]); - std::swap(buffer[(y * width + x) * 4 + 3], - buffer[((height - 1 - y) * width + x) * 4 + 3]); - } - } - return buffer; - } + native_size = emulator->shell->getNativeSize(); - uint32_t width, height; - std::shared_ptr emulator; - std::vector buffer; -}; - -std::unique_ptr state; - -int bot_main() -{ - std::string token = Settings::Get("bot_token"); - - if (token.empty()) - { - token = std::string(std::getenv("BOT_TOKEN")); - Settings::Set("bot_token", token); - } - - if (token.empty()) - { - std::cerr << "No bot token found. Please set the BOT_TOKEN environment variable." - << std::endl; - return 1; - } - - std::string core_path = Settings::Get("core_path"); - - if (core_path.empty()) - { - core_path = std::string(std::getenv("CORE_PATH")); - Settings::Set("core_path", core_path); - } - - if (core_path.empty()) - { - std::cerr << "No core path found. Please set the CORE_PATH environment variable." - << std::endl; - return 1; - } - - state = std::make_unique(); - - dpp::cluster bot(token, dpp::i_default_intents | dpp::i_message_content); + dpp::cluster bot(this->token, dpp::i_default_intents | dpp::i_message_content); bot.on_log(dpp::utility::cout_logger()); bot.on_ready([&bot](const dpp::ready_t& event) { if (dpp::run_once()) @@ -178,23 +54,24 @@ int bot_main() } case "status"_hash: { - dpp::embed embed = - dpp::embed() - .set_title(state->emulator->GetInfo(hydra::InfoType::CoreName)) - .set_url(state->emulator->GetInfo(hydra::InfoType::Website)) - .set_description(state->emulator->GetInfo(hydra::InfoType::Description)) - .set_author(state->emulator->GetInfo(hydra::InfoType::Author), - "https://github.com/hydra-emu/hydra", "attachment://icon.png") - .set_thumbnail("attachment://icon.png"); - dpp::message msg = dpp::message(event.command.channel_id, embed); - if (state->emulator->GetIcon().size() > 0) - { - msg.add_file("icon.png", - std::string(state->emulator->GetIcon().begin(), - state->emulator->GetIcon().end()), - "image/png"); - } - event.reply(msg); + // dpp::embed embed = + // dpp::embed() + // .set_title(state->emulator->GetInfo(hydra::InfoType::CoreName)) + // .set_url(state->emulator->GetInfo(hydra::InfoType::Website)) + // .set_description(state->emulator->GetInfo(hydra::InfoType::Description)) + // .set_author(state->emulator->GetInfo(hydra::InfoType::Author), + // "https://github.com/hydra-emu/hydra", + // "attachment://icon.png") + // .set_thumbnail("attachment://icon.png"); + // dpp::message msg = dpp::message(event.command.channel_id, embed); + // if (state->emulator->GetIcon().size() > 0) + // { + // msg.add_file("icon.png", + // std::string(state->emulator->GetIcon().begin(), + // state->emulator->GetIcon().end()), + // "image/png"); + // } + // event.reply(msg); break; } case "screen"_hash: @@ -286,23 +163,194 @@ int bot_main() } }); - bot.on_message_create([&bot](const dpp::message_create_t& event) { - /* See if the message contains the phrase we want to check for. - * If there's at least a single match, we reply and say it's not allowed. - */ - printf("Got message: %s\n", event.msg.content.c_str()); - if (event.msg.content.find("I hate pandas") != std::string::npos) - { - std::string fake_ip = std::to_string(rand() % 255) + "." + - std::to_string(rand() % 255) + "." + - std::to_string(rand() % 255) + "." + std::to_string(rand() % 255); - event.reply("That is not allowed here. Please, mind your language! I will now dox your " - "ip for saying this. Your ip is:" + - fake_ip, - true); - } - }); + bot.start(dpp::st_return); +} + +void Bot::runFrame() +{ + bool flip_y = false; + if (emulator->shell->hasInterface(hydra::InterfaceType::IOpenGlRendered)) + { + hydra::IOpenGlRendered* shell_gl = emulator->shell->asIOpenGlRendered(); + shell_gl->setFbo(fbo); + flip_y = true; + } + if (emulator->shell->hasInterface(hydra::InterfaceType::IFrontendDriven)) + { + hydra::IFrontendDriven* shell = emulator->shell->asIFrontendDriven(); + // If there's an active bot, it runs the frame instead of the frontend + shell->runFrame(); + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + std::vector buffer; + buffer.resize(native_size.width * native_size.height * 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glReadPixels(0, 0, native_size.width, native_size.height, GL_RGBA, GL_UNSIGNED_BYTE, + buffer.data()); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + pushFrame(buffer.data(), native_size); +} + +void Bot::pushFrame(void* data, hydra::Size size) +{ + frames.push_back( + std::vector((uint8_t*)data, (uint8_t*)data + size.width * size.height * 4)); +} + +Bot::~Bot() {} + +// namespace fs = std::filesystem; + +// GLuint fbo; +// void* get_proc_address; + +// void init_gl() +// { +// if (!glfwInit()) +// printf("glfwInit() failed\n"); +// // glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); +// glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); +// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); +// GLFWwindow* window = glfwCreateWindow(640, 480, "", nullptr, nullptr); +// if (!window) +// printf("glfwCreateWindow() failed\n"); +// glfwMakeContextCurrent(window); +// if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) +// printf("gladLoadGLLoader() failed\n"); +// get_proc_address = (void*)glfwGetProcAddress; + +// GLuint texture; +// glGenTextures(1, &texture); +// glBindTexture(GL_TEXTURE_2D, texture); +// glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 400, 480); +// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +// glGenFramebuffers(1, &fbo); +// glBindFramebuffer(GL_FRAMEBUFFER, fbo); +// glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); +// } - bot.start(dpp::st_wait); +// int32_t read_input_callback(uint32_t player, hydra::ButtonType button) +// { +// return 0; +// } + +// struct BotState +// { +// BotState() +// { +// init_gl(); +// std::string core_path = Settings::Get("core_path"); +// emulator = hydra::EmulatorFactory::Create(fs::path(core_path) / "libAlber.so"); +// width = emulator->shell->getNativeSize().width; +// height = emulator->shell->getNativeSize().height; +// buffer.resize(width * height * 4); + +// if (emulator->shell->hasInterface(hydra::InterfaceType::IOpenGlRendered)) +// { +// auto gli = emulator->shell->asIOpenGlRendered(); +// gli->setGetProcAddress(get_proc_address); +// gli->resetContext(); +// gli->setFbo(fbo); +// emulator->shell->setOutputSize({width, height}); +// } + +// if (emulator->shell->hasInterface(hydra::InterfaceType::IInput)) +// { +// hydra::IInput* shell_input = emulator->shell->asIInput(); +// shell_input->setPollInputCallback([]() {}); +// shell_input->setCheckButtonCallback(read_input_callback); +// } + +// emulator->LoadGame("/home/offtkp/Roms/3DS/zelda.3ds"); +// } + +// const std::vector& get_frame() +// { +// glBindFramebuffer(GL_FRAMEBUFFER, fbo); +// glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data()); + +// for (uint32_t y = 0; y < height / 2; y++) +// { +// for (uint32_t x = 0; x < width; x++) +// { +// std::swap(buffer[(y * width + x) * 4 + 0], +// buffer[((height - 1 - y) * width + x) * 4 + 0]); +// std::swap(buffer[(y * width + x) * 4 + 1], +// buffer[((height - 1 - y) * width + x) * 4 + 1]); +// std::swap(buffer[(y * width + x) * 4 + 2], +// buffer[((height - 1 - y) * width + x) * 4 + 2]); +// std::swap(buffer[(y * width + x) * 4 + 3], +// buffer[((height - 1 - y) * width + x) * 4 + 3]); +// } +// } +// return buffer; +// } + +// uint32_t width, height; +// std::shared_ptr emulator; +// std::vector buffer; +// }; + +// std::unique_ptr state; + +int bot_main() +{ return 0; -} \ No newline at end of file +} + +// std::string token = Settings::Get("bot_token"); + +// if (token.empty()) +// { +// token = std::string(std::getenv("BOT_TOKEN")); +// Settings::Set("bot_token", token); +// } + +// if (token.empty()) +// { +// std::cerr << "No bot token found. Please set the BOT_TOKEN environment variable." +// << std::endl; +// return 1; +// } + +// std::string core_path = Settings::Get("core_path"); + +// if (core_path.empty()) +// { +// core_path = std::string(std::getenv("CORE_PATH")); +// Settings::Set("core_path", core_path); +// } + +// if (core_path.empty()) +// { +// std::cerr << "No core path found. Please set the CORE_PATH environment variable." +// << std::endl; +// return 1; +// } + +// state = std::make_unique(); + +// bot.on_message_create([&bot](const dpp::message_create_t& event) { +// /* See if the message contains the phrase we want to check for. +// * If there's at least a single match, we reply and say it's not allowed. +// */ +// printf("Got message: %s\n", event.msg.content.c_str()); +// if (event.msg.content.find("I hate pandas") != std::string::npos) +// { +// std::string fake_ip = std::to_string(rand() % 255) + "." + +// std::to_string(rand() % 255) + "." + +// std::to_string(rand() % 255) + "." + std::to_string(rand() % +// 255); +// event.reply("That is not allowed here. Please, mind your language! I will now dox +// your " +// "ip for saying this. Your ip is:" + +// fake_ip, +// true); +// } +// }); + +// bot.start(dpp::st_wait); +// return 0; +// } \ No newline at end of file diff --git a/discord/bot.hxx b/discord/bot.hxx index 22a50f33..26234ba7 100644 --- a/discord/bot.hxx +++ b/discord/bot.hxx @@ -1,3 +1,29 @@ #pragma once +#include "corewrapper.hxx" +#include +#include +#include +#include + +struct Bot +{ + Bot(std::shared_ptr emulator, GLuint fbo, const std::string& token, + int frames_pressed, int frames_released); + ~Bot(); + void runFrame(); + void pushFrame(void* data, hydra::Size size); + static void videoCallback(Bot* bot, void* data, hydra::Size size); + +private: + void start(); + std::shared_ptr emulator; + GLuint fbo; + std::string token; + int frames_pressed; + int frames_released; + hydra::Size native_size; + std::vector> frames; +}; + int bot_main(); \ No newline at end of file diff --git a/include/corewrapper.hxx b/include/corewrapper.hxx index c8841c03..6e8a4583 100644 --- a/include/corewrapper.hxx +++ b/include/corewrapper.hxx @@ -117,6 +117,8 @@ namespace hydra void EnableCheat(uint32_t handle); void DisableCheat(uint32_t handle); + void RunFrame(); + private: dynlib_handle_t handle; void (*destroy_function)(IBase*); diff --git a/include/gamewindow.hxx b/include/gamewindow.hxx index a92ede9d..c1191d56 100644 --- a/include/gamewindow.hxx +++ b/include/gamewindow.hxx @@ -1,9 +1,10 @@ #pragma once -#include "hydra/core.hxx" -#include "imgui.h" +#include #include #include +#include +#include enum class UpdateResult { @@ -27,6 +28,10 @@ public: return loaded; } + GLuint fbo = 0; + std::shared_ptr emulator; + std::unique_ptr bot; + private: float velocity_x = 0.0f; float velocity_y = 0.0f; @@ -43,14 +48,11 @@ private: bool animating = false; bool loaded = false; GLuint texture = 0; - GLuint fbo = 0; ImVec2 start_drag; hydra::Size texture_size; hydra::PixelFormat pixel_format; GLenum gl_format; - std::shared_ptr emulator; - static GameWindow* instance; static void video_callback(void* data, hydra::Size size); static GLenum get_gl_format(hydra::PixelFormat format); diff --git a/include/mainwindow.hxx b/include/mainwindow.hxx index 123a2e79..df378c62 100644 --- a/include/mainwindow.hxx +++ b/include/mainwindow.hxx @@ -4,6 +4,7 @@ #include "gamewindow.hxx" #include "settings.hxx" #include +#include #include #include #include @@ -34,6 +35,7 @@ private: void draw_games(); void draw_cores(); void draw_about(); + void draw_bot(); void draw_settings(); void draw_stars(ImVec2 center, float radius); void draw_pending_rom_load(); @@ -55,6 +57,7 @@ private: std::unordered_map>> settings_functions; std::unique_ptr game_window; std::deque recent_roms; + std::string bot_token; bool pending_rom_load = false; std::filesystem::path pending_rom_path; diff --git a/include/settings.hxx b/include/settings.hxx index 8cc66be1..e40b0fc0 100644 --- a/include/settings.hxx +++ b/include/settings.hxx @@ -221,16 +221,26 @@ public: ++it; continue; } + + auto get_info_w = [get_info_p](hydra::InfoType type) { + const char* info = get_info_p(type); + if (!info) + { + return std::string(); + } + return std::string(info); + }; + struct CoreInfo info; info.path = it->path().string(); - info.core_name = get_info_p(hydra::InfoType::CoreName); - info.system_name = get_info_p(hydra::InfoType::SystemName); - info.version = get_info_p(hydra::InfoType::Version); - info.author = get_info_p(hydra::InfoType::Author); - info.description = get_info_p(hydra::InfoType::Description); - info.extensions = hydra::split(get_info_p(hydra::InfoType::Extensions), ','); - info.url = get_info_p(hydra::InfoType::Website); - info.license = get_info_p(hydra::InfoType::License); + info.core_name = get_info_w(hydra::InfoType::CoreName); + info.system_name = get_info_w(hydra::InfoType::SystemName); + info.version = get_info_w(hydra::InfoType::Version); + info.author = get_info_w(hydra::InfoType::Author); + info.description = get_info_w(hydra::InfoType::Description); + info.extensions = hydra::split(get_info_w(hydra::InfoType::Extensions), ','); + info.url = get_info_w(hydra::InfoType::Website); + info.license = get_info_w(hydra::InfoType::License); info.max_players = 1; bool one_active = false; diff --git a/src/app.cxx b/src/app.cxx index 063b9b0f..8d0185cf 100644 --- a/src/app.cxx +++ b/src/app.cxx @@ -549,7 +549,7 @@ void Settings::ReinitCoresFrontend() } picker->update(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), - {{"All files", "*"}}); + {{"All files", ".*"}}); ImGui::PopID(); }); break; diff --git a/src/filepicker.cxx b/src/filepicker.cxx index 0b88e877..a1a21ae3 100644 --- a/src/filepicker.cxx +++ b/src/filepicker.cxx @@ -154,16 +154,24 @@ void FilePicker::update(ImVec2 start, ImVec2 end, const FilePickerFilters& filte { imgui_replacement: std::string filter_str; - for (size_t i = 0; i < filters.size(); ++i) + if (filters[0].second != "*.*") + { + for (size_t i = 0; i < filters.size(); ++i) + { + filter_str += filters[i].first + '(' + filters[i].second + ") {" + + filters[i].second + "},"; + } + filter_str.pop_back(); + } + else { - filter_str += - filters[i].first + '(' + filters[i].second + ") {" + filters[i].second + "},"; } - filter_str.pop_back(); - IGFD::FileDialogConfig config; - config.path = default_path; - config.countSelectionMax = 1; - config.flags = ImGuiFileDialogFlags_Modal; + + IGFD::FileDialogConfig config = { + .path = default_path, + .countSelectionMax = 1, + .flags = ImGuiFileDialogFlags_Modal, + }; std::string sname = ICON_MD_FOLDER_OPEN + name; ImGuiFileDialog::Instance()->OpenDialog("dialog", sname, filter_str.c_str(), config); } diff --git a/src/gamewindow.cxx b/src/gamewindow.cxx index 0d6132c5..bda285bd 100644 --- a/src/gamewindow.cxx +++ b/src/gamewindow.cxx @@ -122,18 +122,22 @@ void GameWindow::video_callback(void* data, hydra::Size size) UpdateResult GameWindow::update() { bool flip_y = false; - if (emulator->shell->hasInterface(hydra::InterfaceType::IOpenGlRendered)) - { - hydra::IOpenGlRendered* shell_gl = emulator->shell->asIOpenGlRendered(); - shell_gl->setFbo(fbo); - flip_y = true; - } - if (emulator->shell->hasInterface(hydra::InterfaceType::IFrontendDriven)) + if (!bot) { - hydra::IFrontendDriven* shell = emulator->shell->asIFrontendDriven(); - shell->runFrame(); + if (emulator->shell->hasInterface(hydra::InterfaceType::IOpenGlRendered)) + { + hydra::IOpenGlRendered* shell_gl = emulator->shell->asIOpenGlRendered(); + shell_gl->setFbo(fbo); + flip_y = true; + } + if (emulator->shell->hasInterface(hydra::InterfaceType::IFrontendDriven)) + { + hydra::IFrontendDriven* shell = emulator->shell->asIFrontendDriven(); + // If there's an active bot, it runs the frame instead of the frontend + shell->runFrame(); + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); UpdateResult result = UpdateResult::None; ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); diff --git a/src/mainwindow.cxx b/src/mainwindow.cxx index 1e11fdff..d3fd8053 100644 --- a/src/mainwindow.cxx +++ b/src/mainwindow.cxx @@ -16,16 +16,22 @@ CMRC_DECLARE(hydra); extern ImFont* small_font; extern ImFont* big_font; -constexpr size_t tab_count = 4; -constexpr size_t games_tab = 0; -constexpr size_t cores_tab = 1; -constexpr size_t settings_tab = 2; -constexpr size_t about_tab = 3; - constexpr size_t recent_max = 50; -constexpr const char* names[tab_count] = {"Games", "Cores", "Settings", "About"}; -constexpr const char* icons[tab_count] = {ICON_MD_GAMES, ICON_MD_MEMORY, ICON_MD_SETTINGS, - ICON_MD_INFO}; + +struct Tab +{ + std::string name; + std::string icon; +}; + +constexpr Tab tabs[] = {{"Games", ICON_MD_GAMES}, + {"Cores", ICON_MD_MEMORY}, + {"Settings", ICON_MD_SETTINGS}, +#ifdef HYDRA_DISCORD_BOT + {"Bot", ICON_MD_CHAT_BUBBLE}, +#endif + {"About", ICON_MD_INFO}}; +constexpr size_t tab_count = sizeof(tabs) / sizeof(Tab); MainWindow::MainWindow() : rom_picker("rom_picker", "Load ROM", [this](const char* path) { @@ -115,8 +121,8 @@ void MainWindow::update_impl() #endif if (hovered) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0, 1, 1, 0.75)); - if (ImGui::Selectable(use_icons ? icons[i] : names[i], selected_tab == i, - ImGuiSelectableFlags_SpanAllColumns, tab_size)) + if (ImGui::Selectable(use_icons ? tabs[i].icon.c_str() : tabs[i].name.c_str(), + selected_tab == i, ImGuiSelectableFlags_SpanAllColumns, tab_size)) { selected_tab = i; } @@ -136,18 +142,21 @@ void MainWindow::update_impl() ImVec2(cursor_x, ImGui::GetIO().DisplaySize.y - ImGui::GetStyle().WindowPadding.y), 0x80FFFFFF, 0.5f); ImGui::BeginGroup(); - switch (selected_tab) + switch (hydra::str_hash(tabs[selected_tab].name)) { - case games_tab: + case hydra::str_hash("Games"): draw_games(); break; - case cores_tab: + case hydra::str_hash("Cores"): draw_cores(); break; - case settings_tab: + case hydra::str_hash("Settings"): draw_settings(); break; - case about_tab: + case hydra::str_hash("Bot"): + draw_bot(); + break; + case hydra::str_hash("About"): draw_about(); break; } @@ -317,8 +326,9 @@ void MainWindow::draw_games() float shake_y = (rand() % 10 - 5) / 40.0f; ImGui::SetCursorPos( ImVec2(ImGui::GetCursorPos().x + shake_x, ImGui::GetCursorPos().y + shake_y)); - auto [clicked, min, max] = draw_custom_button( - ICON_MD_DELETE " " + it->filename().string(), 0x40000040, 0x80000080); + auto [clicked, min, max] = + draw_custom_button(ICON_MD_DELETE " " + it->filename().string(), 0x40000040, + 0x80000080); // TODO: put all colors in a global header if (clicked) { it = recent_roms.erase(it); @@ -441,7 +451,14 @@ void MainWindow::draw_cores() hydra::ImGuiHelper::IconWidth() - padding); if (ImGui::Button(ICON_MD_SETTINGS)) { - selected_tab = settings_tab; + for (size_t j = 0; j < tab_count; j++) + { + if (hydra::str_hash(tabs[j].name) == hydra::str_hash("Settings")) + { + selected_tab = j; + break; + } + } open_core_settings = i; } ImGui::Separator(); @@ -490,6 +507,40 @@ void MainWindow::draw_cores() void MainWindow::draw_about() {} +void MainWindow::draw_bot() +{ +#ifdef HYDRA_DISCORD_BOT + if (bot_token.size() == 0) + { + bot_token.resize(128); + std::string token = Settings::Get("bot_token"); + strncpy(bot_token.data(), token.c_str(), bot_token.size()); + } + if (!game_window) + ImGui::BeginDisabled(); + ImGui::InputText("Token", bot_token.data(), bot_token.size()); + ImGui::SameLine(); + if (game_window && !game_window->bot) + { + if (ImGui::Button("Connect")) + { + Settings::Set("bot_token", bot_token); + game_window->bot = + std::make_unique(game_window->emulator, game_window->fbo, bot_token, 10, 10); + } + } + else + { + if (ImGui::Button("Disconnect")) + { + game_window->bot.reset(); + } + } + if (!game_window) + ImGui::EndDisabled(); +#endif +} + void MainWindow::draw_settings() { ImGui::Text(ICON_MD_SETTINGS " Settings");