From 0949a16e6f8aa46182cefada7c0a2395206651f7 Mon Sep 17 00:00:00 2001 From: offtkp <parisoplop@gmail.com> Date: Sun, 9 Jul 2023 23:36:27 +0300 Subject: [PATCH 1/6] Add initial http server stuff --- .gitmodules | 6 +++ CMakeLists.txt | 2 + include/emulator.hpp | 13 +++++++ src/emulator.cpp | 90 +++++++++++++++++++++++++++++++++++++++++--- third_party/httplib | 1 + third_party/stb | 1 + 6 files changed, 107 insertions(+), 6 deletions(-) create mode 160000 third_party/httplib create mode 160000 third_party/stb diff --git a/.gitmodules b/.gitmodules index f8c67366..a2cac3f2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,9 @@ [submodule "third_party/toml11"] path = third_party/toml11 url = https://github.com/ToruNiina/toml11 +[submodule "cpp-httplib"] + path = third_party/httplib + url = https://github.com/yhirose/cpp-httplib +[submodule "stb"] + path = third_party/stb + url = https://github.com/nothings/stb diff --git a/CMakeLists.txt b/CMakeLists.txt index bea18041..bfbf8ae9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,8 @@ include_directories(third_party/cryptopp/) include_directories(third_party/cityhash/include) include_directories(third_party/result/include) include_directories(third_party/xxhash/include) +include_directories(third_party/httplib) +include_directories(third_party/stb) add_compile_definitions(NOMINMAX) add_compile_definitions(SDL_MAIN_HANDLED) diff --git a/include/emulator.hpp b/include/emulator.hpp index e1684085..63fe468c 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -17,6 +17,8 @@ enum class ROMType { None, ELF, NCSD, CXI }; +enum class HttpAction { None, Screenshot }; + class Emulator { CPU cpu; GPU gpu; @@ -46,6 +48,12 @@ class Emulator { ROMType romType = ROMType::None; bool running = true; +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + std::atomic_bool pendingAction = false; + HttpAction action = HttpAction::None; + std::mutex actionMutex = {}; +#endif + // Keep the handle for the ROM here to reload when necessary and to prevent deleting it // This is currently only used for ELFs, NCSDs use the IOFile API instead std::ifstream loadedELF; @@ -62,10 +70,15 @@ class Emulator { void reset(); void run(); void runFrame(); + void screenshot(const std::string& name); bool loadROM(const std::filesystem::path& path); bool loadNCSD(const std::filesystem::path& path, ROMType type); bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); void initGraphicsContext(); + +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + void startHttpServer(); +#endif }; diff --git a/src/emulator.cpp b/src/emulator.cpp index 198c573d..2981505e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,4 +1,9 @@ #include "emulator.hpp" +#ifdef PANDA3DS_ENABLE_HTTP_SERVER +#include <httplib.h> +#endif +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include <stb_image_write.h> #ifdef _WIN32 #include <windows.h> @@ -10,6 +15,10 @@ _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"); @@ -67,7 +76,7 @@ void Emulator::reset() { // Reloading r13 and r15 needs to happen after everything has been reset // Otherwise resetting the kernel or cpu might nuke them cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP - + // If a ROM is active and we reset, reload it. This is necessary to set up stack, executable memory, .data/.rodata/.bss all over again if (romType != ROMType::None && romPath.has_value()) { bool success = loadROM(romPath.value()); @@ -80,13 +89,55 @@ void Emulator::reset() { } } +void Emulator::screenshot(const std::string& name) { + std::vector<uint8_t> pixels, flippedPixels; + pixels.resize(width * height * 4); + flippedPixels.resize(width * height * 4); + + glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels.data()); + + // Flip the image vertically + for (int y = 0; y < height; y++) { + memcpy(&flippedPixels[y * width * 4], &pixels[(height - y - 1) * width * 4], width * 4); + // Swap R and B channels + for (int x = 0; x < width; x++) { + std::swap(flippedPixels[y * width * 4 + x * 4 + 0], flippedPixels[y * width * 4 + x * 4 + 2]); + // Set alpha to 0xFF + flippedPixels[y * width * 4 + x * 4 + 3] = 0xFF; + } + } + + stbi_write_png(name.c_str(), width, height, 4, flippedPixels.data(), 0); +} + void Emulator::step() {} void Emulator::render() {} void Emulator::run() { - while (running) { - runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + startHttpServer(); +#endif + while (running) { +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + { + std::scoped_lock lock(actionMutex); + if (pendingAction) { + switch (action) { + case HttpAction::Screenshot: { + screenshot(httpServerScreenshotPath); + break; + } + case HttpAction::None: { + break; + } + } + pendingAction = false; + pendingAction.notify_all(); + } + } +#endif + runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics ServiceManager& srv = kernel.getServiceManager(); @@ -337,12 +388,12 @@ bool Emulator::loadROM(const std::filesystem::path& path) { romPath = std::nullopt; romType = ROMType::None; } - + return success; } // Used for loading both CXI and NCSD files since they are both so similar and use the same interface -// (We promote CXI files to NCSD internally for ease) +// (We promote CXI files to NCSD internally for ease) bool Emulator::loadNCSD(const std::filesystem::path& path, ROMType type) { romType = type; std::optional<NCSD> opt = (type == ROMType::NCSD) ? memory.loadNCSD(aesEngine, path) : memory.loadCXI(aesEngine, path); @@ -390,3 +441,30 @@ void Emulator::initGraphicsContext() { gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL gpu.initGraphicsContext(); } + +#ifdef PANDA3DS_ENABLE_HTTP_SERVER +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; + } + // wait until the screenshot is ready + pendingAction.wait(true); + std::ifstream image(httpServerScreenshotPath, std::ios::binary); + std::vector<char> buffer(std::istreambuf_iterator<char>(image), {}); + response.set_content(buffer.data(), buffer.size(), "image/png"); + }); + // TODO: ability to specify host and port + printf("Starting HTTP server on port 1234\n"); + server.listen("localhost", 1234); + }); + http_thread.detach(); +} +#endif \ No newline at end of file diff --git a/third_party/httplib b/third_party/httplib new file mode 160000 index 00000000..be07d2d7 --- /dev/null +++ b/third_party/httplib @@ -0,0 +1 @@ +Subproject commit be07d2d7a99c0a54b00526f30f175e93c3588f34 diff --git a/third_party/stb b/third_party/stb new file mode 160000 index 00000000..5736b15f --- /dev/null +++ b/third_party/stb @@ -0,0 +1 @@ +Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 From 77ea84373fe064f59b69df8d08263e03582eb758 Mon Sep 17 00:00:00 2001 From: offtkp <parisoplop@gmail.com> Date: Mon, 10 Jul 2023 00:57:59 +0300 Subject: [PATCH 2/6] Add /input command in http server --- include/emulator.hpp | 3 +- src/emulator.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 63fe468c..918c1d4a 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -17,7 +17,7 @@ enum class ROMType { None, ELF, NCSD, CXI }; -enum class HttpAction { None, Screenshot }; +enum class HttpAction { None, Screenshot, PressKey, ReleaseKey }; class Emulator { CPU cpu; @@ -52,6 +52,7 @@ class Emulator { std::atomic_bool pendingAction = false; HttpAction action = HttpAction::None; std::mutex actionMutex = {}; + u32 pendingKey = 0; #endif // Keep the handle for the ROM here to reload when necessary and to prevent deleting it diff --git a/src/emulator.cpp b/src/emulator.cpp index 2981505e..525a8718 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -118,6 +118,11 @@ void Emulator::run() { startHttpServer(); #endif while (running) { + runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics + + ServiceManager& srv = kernel.getServiceManager(); + #ifdef PANDA3DS_ENABLE_HTTP_SERVER { std::scoped_lock lock(actionMutex); @@ -127,19 +132,30 @@ void Emulator::run() { 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 - runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics - - ServiceManager& srv = kernel.getServiceManager(); // Send VBlank interrupts srv.sendGPUInterrupt(GPUInterrupt::VBlank0); @@ -443,12 +459,42 @@ void Emulator::initGraphicsContext() { } #ifdef PANDA3DS_ENABLE_HTTP_SERVER +u32 stringToKey(const std::string& key_name) { + namespace Keys = HID::Keys; + static std::map<std::string, u32> 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); @@ -461,6 +507,41 @@ void Emulator::startHttpServer() { std::vector<char> buffer(std::istreambuf_iterator<char>(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); From b8fa5fc86dc381272ce7d0e52e14d7269b4cedd8 Mon Sep 17 00:00:00 2001 From: offtkp <parisoplop@gmail.com> Date: Mon, 10 Jul 2023 01:32:47 +0300 Subject: [PATCH 3/6] Separate http server to a new file --- CMakeLists.txt | 6 +- include/emulator.hpp | 12 ++-- include/httpserver.hpp | 19 +++++ src/emulator.cpp | 159 +++++++++-------------------------------- src/httpserver.cpp | 100 ++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 134 deletions(-) create mode 100644 include/httpserver.hpp create mode 100644 src/httpserver.cpp 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 <atomic> +#include <mutex> +#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 <httplib.h> -#endif #define STB_IMAGE_WRITE_IMPLEMENTATION #include <stb_image_write.h> @@ -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<std::string, u32> 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<char> buffer(std::istreambuf_iterator<char>(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 <vector> +#include <map> +#include <thread> +#include <fstream> +#include <string> + +#include "httplib.h" +#include "services/hid.hpp" + +u32 stringToKey(const std::string& key_name) { + namespace Keys = HID::Keys; + static std::map<std::string, u32> 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<char> buffer(std::istreambuf_iterator<char>(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 From 57c45cf58a14638a1275aeed729b2f49c8833813 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 10 Jul 2023 02:29:08 +0300 Subject: [PATCH 4/6] Remove globals, change conditional compilation, doormat --- CMakeLists.txt | 7 ++----- include/httpserver.hpp | 13 ++++++++----- src/emulator.cpp | 2 +- src/httpserver.cpp | 5 ++++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 197709f7..7443075b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/gl_state.cpp src/config.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp + src/httpserver.cpp ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -145,7 +146,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp include/crypto/aes_engine.hpp include/metaprogramming.hpp include/PICA/pica_vertex.hpp include/gl_state.hpp - include/config.hpp include/services/ir_user.hpp + include/config.hpp include/services/ir_user.hpp include/httpserver.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp @@ -158,10 +159,6 @@ 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}) diff --git a/include/httpserver.hpp b/include/httpserver.hpp index 2f342920..0526045a 100644 --- a/include/httpserver.hpp +++ b/include/httpserver.hpp @@ -1,19 +1,22 @@ +#ifdef PANDA3DS_ENABLE_HTTP_SERVER #pragma once #include <atomic> #include <mutex> + #include "helpers.hpp" enum class HttpAction { None, Screenshot, PressKey, ReleaseKey }; -constexpr const char* httpServerScreenshotPath = "screenshot.png"; +struct HttpServer { + static constexpr const char* httpServerScreenshotPath = "screenshot.png"; -struct HttpServer -{ - std::atomic_bool pendingAction = false; + std::atomic_bool pendingAction = false; HttpAction action = HttpAction::None; std::mutex actionMutex = {}; u32 pendingKey = 0; void startHttpServer(); -}; \ No newline at end of file +}; + +#endif // PANDA3DS_ENABLE_HTTP_SERVER \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 3ffe2829..ef30088f 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -429,7 +429,7 @@ void Emulator::pollHttpServer() { if (httpServer.pendingAction) { switch (httpServer.action) { case HttpAction::Screenshot: { - screenshot(httpServerScreenshotPath); + screenshot(HttpServer::httpServerScreenshotPath); break; } case HttpAction::PressKey: { diff --git a/src/httpserver.cpp b/src/httpserver.cpp index f5b1d0f6..570a3987 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -1,3 +1,4 @@ +#ifdef PANDA3DS_ENABLE_HTTP_SERVER #include "httpserver.hpp" #include <vector> @@ -97,4 +98,6 @@ void HttpServer::startHttpServer() { server.listen("localhost", 1234); }); http_thread.detach(); -} \ No newline at end of file +} + +#endif // PANDA3DS_ENABLE_HTTP_SERVER \ No newline at end of file From 536e4566db58c7db1f9af1463719907199261f89 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:04:15 +0300 Subject: [PATCH 5/6] Fix Windows build --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7443075b..4f844a61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,8 @@ include_directories(third_party/xxhash/include) include_directories(third_party/httplib) include_directories(third_party/stb) -add_compile_definitions(NOMINMAX) +add_compile_definitions(NOMINMAX) # Make windows.h not define min/max macros because third-party deps don't like it +add_compile_definitions(WIN32_LEAN_AND_MEAN) # Make windows.h not include literally everything add_compile_definitions(SDL_MAIN_HANDLED) set(SDL_STATIC ON CACHE BOOL "" FORCE) @@ -171,7 +172,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} ${HTTPSERVER_SOURCE_FILES} ${HEADER_FILES}) +${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) if(ENABLE_LTO OR ENABLE_USER_BUILD) set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) From ba1c3a8ec57dd48b7bfeda4d5719d33ba6a6d521 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:30:09 +0300 Subject: [PATCH 6/6] Moar HTTP --- include/PICA/gpu.hpp | 1 + include/emulator.hpp | 1 - include/renderer_gl/renderer_gl.hpp | 4 +++ src/core/renderer_gl/renderer_gl.cpp | 27 ++++++++++++++++++- src/emulator.cpp | 40 +++++++--------------------- src/httpserver.cpp | 14 +++++----- 6 files changed, 47 insertions(+), 40 deletions(-) diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 5f74bdcc..2de48c01 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -88,6 +88,7 @@ class GPU { void initGraphicsContext() { renderer.initGraphicsContext(); } void getGraphicsContext() { renderer.getGraphicsContext(); } void display() { renderer.display(); } + void screenshot(const std::string& name) { renderer.screenshot(name); } void fireDMA(u32 dest, u32 source, u32 size); void reset(); diff --git a/include/emulator.hpp b/include/emulator.hpp index 27c23bad..3985c613 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -69,7 +69,6 @@ class Emulator { void reset(); void run(); void runFrame(); - void screenshot(const std::string& name); bool loadROM(const std::filesystem::path& path); bool loadNCSD(const std::filesystem::path& path, ROMType type); diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 8258c4c7..07f8a63c 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -1,6 +1,7 @@ #pragma once #include <array> #include <span> +#include <stb_image_write.h> #include "PICA/float_types.hpp" #include "gl_state.hpp" @@ -88,6 +89,9 @@ class Renderer { void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags); // Perform display transfer void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices); // Draw the given vertices + // Take a screenshot of the screen and store it in a file + void screenshot(const std::string& name); + void setFBSize(u32 width, u32 height) { fbSize.x() = width; fbSize.y() = height; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 72f346bc..73fb0919 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -669,7 +669,7 @@ void Renderer::initGraphicsContext() { dummyVAO.create(); // Create texture and framebuffer for the 3DS screen - const u32 screenTextureWidth = 2 * 400; // Top screen is 400 pixels wide, bottom is 320 + const u32 screenTextureWidth = 400; // Top screen is 400 pixels wide, bottom is 320 const u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall glGenTextures(1,&lightLUTTextureArray); @@ -1028,4 +1028,29 @@ void Renderer::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 } OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen +} + +void Renderer::screenshot(const std::string& name) { + constexpr uint width = 400; + constexpr uint height = 2 * 240; + + std::vector<uint8_t> pixels, flippedPixels; + pixels.resize(width * height * 4); + flippedPixels.resize(pixels.size());; + + OpenGL::bindScreenFramebuffer(); + glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels.data()); + + // Flip the image vertically + for (int y = 0; y < height; y++) { + memcpy(&flippedPixels[y * width * 4], &pixels[(height - y - 1) * width * 4], width * 4); + // Swap R and B channels + for (int x = 0; x < width; x++) { + std::swap(flippedPixels[y * width * 4 + x * 4 + 0], flippedPixels[y * width * 4 + x * 4 + 2]); + // Set alpha to 0xFF + flippedPixels[y * width * 4 + x * 4 + 3] = 0xFF; + } + } + + stbi_write_png(name.c_str(), width, height, 4, flippedPixels.data(), 0); } \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index ef30088f..1e30e073 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -82,27 +82,6 @@ void Emulator::reset() { } } -void Emulator::screenshot(const std::string& name) { - std::vector<uint8_t> pixels, flippedPixels; - pixels.resize(width * height * 4); - flippedPixels.resize(width * height * 4); - - glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels.data()); - - // Flip the image vertically - for (int y = 0; y < height; y++) { - memcpy(&flippedPixels[y * width * 4], &pixels[(height - y - 1) * width * 4], width * 4); - // Swap R and B channels - for (int x = 0; x < width; x++) { - std::swap(flippedPixels[y * width * 4 + x * 4 + 0], flippedPixels[y * width * 4 + x * 4 + 2]); - // Set alpha to 0xFF - flippedPixels[y * width * 4 + x * 4 + 3] = 0xFF; - } - } - - stbi_write_png(name.c_str(), width, height, 4, flippedPixels.data(), 0); -} - void Emulator::step() {} void Emulator::render() {} @@ -428,28 +407,27 @@ void Emulator::pollHttpServer() { if (httpServer.pendingAction) { switch (httpServer.action) { - case HttpAction::Screenshot: { - screenshot(HttpServer::httpServerScreenshotPath); + case HttpAction::Screenshot: + gpu.screenshot(HttpServer::httpServerScreenshotPath); break; - } - case HttpAction::PressKey: { + + case HttpAction::PressKey: if (httpServer.pendingKey != 0) { srv.pressKey(httpServer.pendingKey); httpServer.pendingKey = 0; } break; - } - case HttpAction::ReleaseKey: { + + case HttpAction::ReleaseKey: if (httpServer.pendingKey != 0) { srv.releaseKey(httpServer.pendingKey); httpServer.pendingKey = 0; } break; - } - case HttpAction::None: { - break; - } + + case HttpAction::None: break; } + httpServer.action = HttpAction::None; httpServer.pendingAction = false; httpServer.pendingAction.notify_all(); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 570a3987..fa7ad763 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -1,11 +1,11 @@ #ifdef PANDA3DS_ENABLE_HTTP_SERVER #include "httpserver.hpp" -#include <vector> -#include <map> -#include <thread> #include <fstream> +#include <map> #include <string> +#include <thread> +#include <vector> #include "httplib.h" #include "services/hid.hpp" @@ -42,9 +42,7 @@ 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("/ping", [](const httplib::Request&, httplib::Response& response) { response.set_content("pong", "text/plain"); }); server.Get("/screen", [this](const httplib::Request&, httplib::Response& response) { { @@ -61,9 +59,10 @@ void HttpServer::startHttpServer() { server.Get("/input", [this](const httplib::Request& request, httplib::Response& response) { bool ok = false; - for (auto& [keyStr, value]: request.params) { + 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; @@ -97,6 +96,7 @@ void HttpServer::startHttpServer() { printf("Starting HTTP server on port 1234\n"); server.listen("localhost", 1234); }); + http_thread.detach(); }