diff --git a/.github/workflows/build_http.yml b/.github/workflows/build_http.yml new file mode 100644 index 00000000..46210509 --- /dev/null +++ b/.github/workflows/build_http.yml @@ -0,0 +1,40 @@ +name: HTTP server build + +on: + push: + branches: + - master + pull_request: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Fetch submodules + run: git submodule update --init --recursive + + - name: Setup Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: latest + vulkan-use-cache: true + vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_USER_BUILD=ON -DENABLE_HTTP_SERVER=ON + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/include/emulator.hpp b/include/emulator.hpp index b508338e..0e03bbba 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -61,7 +61,7 @@ class Emulator { #ifdef PANDA3DS_ENABLE_HTTP_SERVER HttpServer httpServer; - friend class HttpServer; + friend struct HttpServer; #endif // Keep the handle for the ROM here to reload when necessary and to prevent deleting it diff --git a/include/httpserver.hpp b/include/httpserver.hpp index 7c70be14..08e98bbf 100644 --- a/include/httpserver.hpp +++ b/include/httpserver.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -12,12 +13,12 @@ #include "helpers.hpp" -enum class HttpActionType { None, Screenshot, Key, TogglePause, Reset }; +enum class HttpActionType { None, Screenshot, Key, TogglePause, Reset, LoadRom }; class Emulator; namespace httplib { class Server; - class Response; + struct Response; } // Wrapper for httplib::Response that allows the HTTP server to wait for the response to be ready @@ -41,7 +42,8 @@ class HttpAction { HttpActionType getType() const { return type; } static std::unique_ptr createScreenshotAction(DeferredResponseWrapper& response); - static std::unique_ptr createKeyAction(uint32_t key, bool state); + static std::unique_ptr createKeyAction(u32 key, bool state); + static std::unique_ptr createLoadRomAction(DeferredResponseWrapper& response, const std::filesystem::path& path, bool paused); static std::unique_ptr createTogglePauseAction(); static std::unique_ptr createResetAction(); }; diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index c1ce1b98..9e07f228 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -159,8 +159,9 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn // Read ExeFS if (hasExeFS()) { - u64 exeFSOffset = fileOffset + exeFS.offset; // Offset of ExeFS in the file = exeFS offset + ncch offset - printf("ExeFS offset: %08llX, size: %08llX (Offset in file = %08llX)\n", exeFS.offset, exeFS.size, exeFSOffset); + // Offset of ExeFS in the file = exeFS offset + NCCH offset + // exeFS.offset has already been offset by the NCCH offset + printf("ExeFS offset: %08llX, size: %08llX (Offset in file = %08llX)\n", exeFS.offset - info.offset, exeFS.size, exeFS.offset); constexpr size_t exeFSHeaderSize = 0x200; u8 exeFSHeader[exeFSHeaderSize]; diff --git a/src/emulator.cpp b/src/emulator.cpp index abc66a53..fd5efe6b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -118,6 +118,9 @@ void Emulator::run() { programRunning = true; while (programRunning) { +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + httpServer.processActions(); +#endif runFrame(); HIDService& hid = kernel.getServiceManager().getHID(); @@ -367,11 +370,8 @@ void Emulator::togglePause() { running ? pause() : resume(); } void Emulator::runFrame() { if (running) { -#ifdef PANDA3DS_ENABLE_HTTP_SERVER - httpServer.processActions(); -#endif - cpu.runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics + cpu.runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics // Send VBlank interrupts ServiceManager& srv = kernel.getServiceManager(); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index d3dc7938..cccd69d9 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,20 @@ class HttpActionKey : public HttpAction { bool getState() const { return state; } }; +class HttpActionLoadRom : public HttpAction { + DeferredResponseWrapper& response; + const std::filesystem::path& path; + bool paused; + + public: + HttpActionLoadRom(DeferredResponseWrapper& response, const std::filesystem::path& path, bool paused) + : HttpAction(HttpActionType::LoadRom), response(response), path(path), paused(paused) {} + + DeferredResponseWrapper& getResponse() { return response; } + const std::filesystem::path& getPath() const { return path; } + bool getPaused() const { return paused; } +}; + std::unique_ptr HttpAction::createScreenshotAction(DeferredResponseWrapper& response) { return std::make_unique(response); } @@ -48,6 +63,10 @@ std::unique_ptr HttpAction::createKeyAction(u32 key, bool state) { r std::unique_ptr HttpAction::createTogglePauseAction() { return std::make_unique(); } std::unique_ptr HttpAction::createResetAction() { return std::make_unique(); } +std::unique_ptr HttpAction::createLoadRomAction(DeferredResponseWrapper& response, const std::filesystem::path& path, bool paused) { + return std::make_unique(response, path, paused); +} + HttpServer::HttpServer(Emulator* emulator) : emulator(emulator), server(std::make_unique()), keyMap({ {"A", {HID::Keys::A}}, @@ -83,6 +102,7 @@ void HttpServer::startHttpServer() { 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) { + // TODO: make the below a DeferredResponseWrapper function DeferredResponseWrapper wrapper(response); // Lock the mutex before pushing the action to ensure that the condition variable is not notified before we wait on it std::unique_lock lock(wrapper.mutex); @@ -122,6 +142,39 @@ void HttpServer::startHttpServer() { server->Get("/status", [this](const httplib::Request&, httplib::Response& response) { response.set_content(status(), "text/plain"); }); + server->Get("/load_rom", [this](const httplib::Request& request, httplib::Response& response) { + auto it = request.params.find("path"); + if (it == request.params.end()) { + response.set_content("error", "text/plain"); + return; + } + + std::filesystem::path romPath = it->second; + if (romPath.empty()) { + response.set_content("error", "text/plain"); + return; + } else { + std::error_code error; + if (!std::filesystem::is_regular_file(romPath, error)) { + std::string message = "error: " + error.message(); + response.set_content(message, "text/plain"); + return; + } + } + + bool paused = false; + it = request.params.find("paused"); + if (it != request.params.end()) { + paused = (it->second == "1"); + } + + DeferredResponseWrapper wrapper(response); + std::unique_lock lock(wrapper.mutex); + pushAction(HttpAction::createLoadRomAction(wrapper, romPath, paused)); + response.set_content("ok", "text/plain"); + wrapper.cv.wait(lock, [&wrapper] { return wrapper.ready; }); + }); + server->Get("/togglepause", [this](const httplib::Request&, httplib::Response& response) { pushAction(HttpAction::createTogglePauseAction()); response.set_content("ok", "text/plain"); @@ -187,7 +240,32 @@ void HttpServer::processActions() { break; } - case HttpActionType::TogglePause: emulator->togglePause(); break; + case HttpActionType::LoadRom: { + HttpActionLoadRom* loadRomAction = static_cast(action.get()); + DeferredResponseWrapper& response = loadRomAction->getResponse(); + bool loaded = emulator->loadROM(loadRomAction->getPath()); + + response.inner_response.set_content(loaded ? "ok" : "error", "text/plain"); + + std::unique_lock lock(response.mutex); + response.ready = true; + response.cv.notify_one(); + + if (loaded) { + paused = loadRomAction->getPaused(); + if (paused) { + emulator->pause(); + } else { + emulator->resume(); + } + } + break; + } + + case HttpActionType::TogglePause: + emulator->togglePause(); + paused = !paused; + break; case HttpActionType::Reset: emulator->reset(Emulator::ReloadOption::Reload); break; default: break;