diff --git a/CMakeLists.txt b/CMakeLists.txt index bfbf8ae9..197709f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,10 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp third_party/xxhash/xxhash.c ) +if (ENABLE_HTTP_SERVER) + set(HTTPSERVER_SOURCE_FILES src/httpserver.cpp) +endif() + source_group("Header Files\\Core" FILES ${HEADER_FILES}) source_group("Source Files\\Core" FILES ${SOURCE_FILES}) source_group("Source Files\\Core\\Crypto" FILES ${CRYPTO_SOURCE_FILES}) @@ -170,7 +174,7 @@ source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FI source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} -${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) +${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HTTPSERVER_SOURCE_FILES} ${HEADER_FILES}) if(ENABLE_LTO OR ENABLE_USER_BUILD) set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) diff --git a/include/emulator.hpp b/include/emulator.hpp index 918c1d4a..27c23bad 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -14,11 +14,12 @@ #include "io_file.hpp" #include "memory.hpp" #include "gl_state.hpp" +#ifdef PANDA3DS_ENABLE_HTTP_SERVER +#include "httpserver.hpp" +#endif enum class ROMType { None, ELF, NCSD, CXI }; -enum class HttpAction { None, Screenshot, PressKey, ReleaseKey }; - class Emulator { CPU cpu; GPU gpu; @@ -49,10 +50,7 @@ class Emulator { bool running = true; #ifdef PANDA3DS_ENABLE_HTTP_SERVER - std::atomic_bool pendingAction = false; - HttpAction action = HttpAction::None; - std::mutex actionMutex = {}; - u32 pendingKey = 0; + HttpServer httpServer; #endif // Keep the handle for the ROM here to reload when necessary and to prevent deleting it @@ -80,6 +78,6 @@ class Emulator { void initGraphicsContext(); #ifdef PANDA3DS_ENABLE_HTTP_SERVER - void startHttpServer(); + void pollHttpServer(); #endif }; diff --git a/include/httpserver.hpp b/include/httpserver.hpp new file mode 100644 index 00000000..2f342920 --- /dev/null +++ b/include/httpserver.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include "helpers.hpp" + +enum class HttpAction { None, Screenshot, PressKey, ReleaseKey }; + +constexpr const char* httpServerScreenshotPath = "screenshot.png"; + +struct HttpServer +{ + std::atomic_bool pendingAction = false; + HttpAction action = HttpAction::None; + std::mutex actionMutex = {}; + u32 pendingKey = 0; + + void startHttpServer(); +}; \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 525a8718..3ffe2829 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,7 +1,4 @@ #include "emulator.hpp" -#ifdef PANDA3DS_ENABLE_HTTP_SERVER -#include -#endif #define STB_IMAGE_WRITE_IMPLEMENTATION #include @@ -15,10 +12,6 @@ _declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; } #endif -#ifdef PANDA3DS_ENABLE_HTTP_SERVER -constexpr const char* httpServerScreenshotPath = "screenshot.png"; -#endif - Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, gl, config), memory(cpu.getTicksRef()) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { Helpers::panic("Failed to initialize SDL2"); @@ -115,48 +108,17 @@ void Emulator::render() {} void Emulator::run() { #ifdef PANDA3DS_ENABLE_HTTP_SERVER - startHttpServer(); + httpServer.startHttpServer(); #endif while (running) { +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + pollHttpServer(); +#endif runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics ServiceManager& srv = kernel.getServiceManager(); -#ifdef PANDA3DS_ENABLE_HTTP_SERVER - { - std::scoped_lock lock(actionMutex); - if (pendingAction) { - switch (action) { - case HttpAction::Screenshot: { - screenshot(httpServerScreenshotPath); - break; - } - case HttpAction::PressKey: { - if (pendingKey != 0) { - srv.pressKey(pendingKey); - pendingKey = 0; - } - break; - } - case HttpAction::ReleaseKey: { - if (pendingKey != 0) { - srv.releaseKey(pendingKey); - pendingKey = 0; - } - break; - } - case HttpAction::None: { - break; - } - } - action = HttpAction::None; - pendingAction = false; - pendingAction.notify_all(); - } - } -#endif - // Send VBlank interrupts srv.sendGPUInterrupt(GPUInterrupt::VBlank0); srv.sendGPUInterrupt(GPUInterrupt::VBlank1); @@ -459,93 +421,38 @@ void Emulator::initGraphicsContext() { } #ifdef PANDA3DS_ENABLE_HTTP_SERVER -u32 stringToKey(const std::string& key_name) { - namespace Keys = HID::Keys; - static std::map keyMap = { - {"A", Keys::A}, - {"B", Keys::B}, - {"Select", Keys::Select}, - {"Start", Keys::Start}, - {"Right", Keys::Right}, - {"Left", Keys::Left}, - {"Up", Keys::Up}, - {"Down", Keys::Down}, - {"R", Keys::R}, - {"L", Keys::L}, - {"X", Keys::X}, - {"Y", Keys::Y}, - {"CirclePadRight", Keys::CirclePadRight}, - {"CirclePadLeft", Keys::CirclePadLeft}, - {"CirclePadUp", Keys::CirclePadUp}, - {"CirclePadDown", Keys::CirclePadDown}, - }; - - if (keyMap.find(key_name) != keyMap.end()) { - return keyMap[key_name]; - } - - return 0; -} - -void Emulator::startHttpServer() { - std::thread http_thread([this]() { - httplib::Server server; - - server.Get("/ping", [](const httplib::Request&, httplib::Response& response) { - response.set_content("pong", "text/plain"); - }); - - server.Get("/screen", [this](const httplib::Request&, httplib::Response& response) { - { - std::scoped_lock lock(actionMutex); - pendingAction = true; - action = HttpAction::Screenshot; +void Emulator::pollHttpServer() { + std::scoped_lock lock(httpServer.actionMutex); + + ServiceManager& srv = kernel.getServiceManager(); + + if (httpServer.pendingAction) { + switch (httpServer.action) { + case HttpAction::Screenshot: { + screenshot(httpServerScreenshotPath); + break; } - // wait until the screenshot is ready - pendingAction.wait(true); - std::ifstream image(httpServerScreenshotPath, std::ios::binary); - std::vector buffer(std::istreambuf_iterator(image), {}); - response.set_content(buffer.data(), buffer.size(), "image/png"); - }); - - server.Get("/input", [this](const httplib::Request& request, httplib::Response& response) { - bool ok = false; - for (auto& [keyStr, value]: request.params) { - auto key = stringToKey(keyStr); - printf("Param: %s\n", keyStr.c_str()); - if (key != 0) { - std::scoped_lock lock(actionMutex); - pendingAction = true; - pendingKey = key; - ok = true; - if (value == "1") { - action = HttpAction::PressKey; - } else if (value == "0") { - action = HttpAction::ReleaseKey; - } else { - // Should not happen but just in case - pendingAction = false; - ok = false; - } - // Not supporting multiple keys at once for now (ever?) - break; + case HttpAction::PressKey: { + if (httpServer.pendingKey != 0) { + srv.pressKey(httpServer.pendingKey); + httpServer.pendingKey = 0; } + break; } - - if (ok) { - response.set_content("ok", "text/plain"); + case HttpAction::ReleaseKey: { + if (httpServer.pendingKey != 0) { + srv.releaseKey(httpServer.pendingKey); + httpServer.pendingKey = 0; + } + break; } - }); - - server.Get("/step", [this](const httplib::Request&, httplib::Response& response) { - // TODO: implement /step - response.set_content("ok", "text/plain"); - }); - - // TODO: ability to specify host and port - printf("Starting HTTP server on port 1234\n"); - server.listen("localhost", 1234); - }); - http_thread.detach(); + case HttpAction::None: { + break; + } + } + httpServer.action = HttpAction::None; + httpServer.pendingAction = false; + httpServer.pendingAction.notify_all(); + } } #endif \ No newline at end of file diff --git a/src/httpserver.cpp b/src/httpserver.cpp new file mode 100644 index 00000000..f5b1d0f6 --- /dev/null +++ b/src/httpserver.cpp @@ -0,0 +1,100 @@ +#include "httpserver.hpp" + +#include +#include +#include +#include +#include + +#include "httplib.h" +#include "services/hid.hpp" + +u32 stringToKey(const std::string& key_name) { + namespace Keys = HID::Keys; + static std::map keyMap = { + {"A", Keys::A}, + {"B", Keys::B}, + {"Select", Keys::Select}, + {"Start", Keys::Start}, + {"Right", Keys::Right}, + {"Left", Keys::Left}, + {"Up", Keys::Up}, + {"Down", Keys::Down}, + {"R", Keys::R}, + {"L", Keys::L}, + {"X", Keys::X}, + {"Y", Keys::Y}, + {"CirclePadRight", Keys::CirclePadRight}, + {"CirclePadLeft", Keys::CirclePadLeft}, + {"CirclePadUp", Keys::CirclePadUp}, + {"CirclePadDown", Keys::CirclePadDown}, + }; + + if (keyMap.find(key_name) != keyMap.end()) { + return keyMap[key_name]; + } + + return 0; +} + +void HttpServer::startHttpServer() { + std::thread http_thread([this]() { + httplib::Server server; + + server.Get("/ping", [](const httplib::Request&, httplib::Response& response) { + response.set_content("pong", "text/plain"); + }); + + server.Get("/screen", [this](const httplib::Request&, httplib::Response& response) { + { + std::scoped_lock lock(actionMutex); + pendingAction = true; + action = HttpAction::Screenshot; + } + // wait until the screenshot is ready + pendingAction.wait(true); + std::ifstream image(httpServerScreenshotPath, std::ios::binary); + std::vector buffer(std::istreambuf_iterator(image), {}); + response.set_content(buffer.data(), buffer.size(), "image/png"); + }); + + server.Get("/input", [this](const httplib::Request& request, httplib::Response& response) { + bool ok = false; + for (auto& [keyStr, value]: request.params) { + auto key = stringToKey(keyStr); + printf("Param: %s\n", keyStr.c_str()); + if (key != 0) { + std::scoped_lock lock(actionMutex); + pendingAction = true; + pendingKey = key; + ok = true; + if (value == "1") { + action = HttpAction::PressKey; + } else if (value == "0") { + action = HttpAction::ReleaseKey; + } else { + // Should not happen but just in case + pendingAction = false; + ok = false; + } + // Not supporting multiple keys at once for now (ever?) + break; + } + } + + if (ok) { + response.set_content("ok", "text/plain"); + } + }); + + server.Get("/step", [this](const httplib::Request&, httplib::Response& response) { + // TODO: implement /step + response.set_content("ok", "text/plain"); + }); + + // TODO: ability to specify host and port + printf("Starting HTTP server on port 1234\n"); + server.listen("localhost", 1234); + }); + http_thread.detach(); +} \ No newline at end of file