mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-17 03:09:47 +12:00
Merge pull request #96 from wheremyfoodat/http-server
Add initial http server stuff
This commit is contained in:
commit
c8f4d41b47
11 changed files with 231 additions and 9 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -19,3 +19,9 @@
|
||||||
[submodule "third_party/toml11"]
|
[submodule "third_party/toml11"]
|
||||||
path = third_party/toml11
|
path = third_party/toml11
|
||||||
url = https://github.com/ToruNiina/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
|
||||||
|
|
|
@ -34,8 +34,11 @@ include_directories(third_party/cryptopp/)
|
||||||
include_directories(third_party/cityhash/include)
|
include_directories(third_party/cityhash/include)
|
||||||
include_directories(third_party/result/include)
|
include_directories(third_party/result/include)
|
||||||
include_directories(third_party/xxhash/include)
|
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)
|
add_compile_definitions(SDL_MAIN_HANDLED)
|
||||||
|
|
||||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
@ -88,6 +91,7 @@ endif()
|
||||||
|
|
||||||
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/gl_state.cpp src/config.cpp
|
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/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(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
||||||
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
||||||
|
@ -143,7 +147,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_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/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/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
|
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
||||||
|
|
|
@ -88,6 +88,7 @@ class GPU {
|
||||||
void initGraphicsContext() { renderer.initGraphicsContext(); }
|
void initGraphicsContext() { renderer.initGraphicsContext(); }
|
||||||
void getGraphicsContext() { renderer.getGraphicsContext(); }
|
void getGraphicsContext() { renderer.getGraphicsContext(); }
|
||||||
void display() { renderer.display(); }
|
void display() { renderer.display(); }
|
||||||
|
void screenshot(const std::string& name) { renderer.screenshot(name); }
|
||||||
|
|
||||||
void fireDMA(u32 dest, u32 source, u32 size);
|
void fireDMA(u32 dest, u32 source, u32 size);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
#include "io_file.hpp"
|
#include "io_file.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include "gl_state.hpp"
|
#include "gl_state.hpp"
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
#include "httpserver.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
enum class ROMType { None, ELF, NCSD, CXI };
|
enum class ROMType { None, ELF, NCSD, CXI };
|
||||||
|
|
||||||
|
@ -46,6 +49,10 @@ class Emulator {
|
||||||
ROMType romType = ROMType::None;
|
ROMType romType = ROMType::None;
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
HttpServer httpServer;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it
|
// 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
|
// This is currently only used for ELFs, NCSDs use the IOFile API instead
|
||||||
std::ifstream loadedELF;
|
std::ifstream loadedELF;
|
||||||
|
@ -68,4 +75,8 @@ class Emulator {
|
||||||
bool loadELF(const std::filesystem::path& path);
|
bool loadELF(const std::filesystem::path& path);
|
||||||
bool loadELF(std::ifstream& file);
|
bool loadELF(std::ifstream& file);
|
||||||
void initGraphicsContext();
|
void initGraphicsContext();
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
void pollHttpServer();
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
22
include/httpserver.hpp
Normal file
22
include/httpserver.hpp
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
enum class HttpAction { None, Screenshot, PressKey, ReleaseKey };
|
||||||
|
|
||||||
|
struct HttpServer {
|
||||||
|
static constexpr const char* httpServerScreenshotPath = "screenshot.png";
|
||||||
|
|
||||||
|
std::atomic_bool pendingAction = false;
|
||||||
|
HttpAction action = HttpAction::None;
|
||||||
|
std::mutex actionMutex = {};
|
||||||
|
u32 pendingKey = 0;
|
||||||
|
|
||||||
|
void startHttpServer();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <stb_image_write.h>
|
||||||
|
|
||||||
#include "PICA/float_types.hpp"
|
#include "PICA/float_types.hpp"
|
||||||
#include "gl_state.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 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
|
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) {
|
void setFBSize(u32 width, u32 height) {
|
||||||
fbSize.x() = width;
|
fbSize.x() = width;
|
||||||
fbSize.y() = height;
|
fbSize.y() = height;
|
||||||
|
|
|
@ -669,7 +669,7 @@ void Renderer::initGraphicsContext() {
|
||||||
dummyVAO.create();
|
dummyVAO.create();
|
||||||
|
|
||||||
// Create texture and framebuffer for the 3DS screen
|
// 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
|
const u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall
|
||||||
|
|
||||||
glGenTextures(1,&lightLUTTextureArray);
|
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
|
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);
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
#include "emulator.hpp"
|
#include "emulator.hpp"
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include <stb_image_write.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
@ -67,7 +69,7 @@ void Emulator::reset() {
|
||||||
// Reloading r13 and r15 needs to happen after everything has been reset
|
// Reloading r13 and r15 needs to happen after everything has been reset
|
||||||
// Otherwise resetting the kernel or cpu might nuke them
|
// Otherwise resetting the kernel or cpu might nuke them
|
||||||
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
|
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 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()) {
|
if (romType != ROMType::None && romPath.has_value()) {
|
||||||
bool success = loadROM(romPath.value());
|
bool success = loadROM(romPath.value());
|
||||||
|
@ -84,9 +86,15 @@ void Emulator::step() {}
|
||||||
void Emulator::render() {}
|
void Emulator::render() {}
|
||||||
|
|
||||||
void Emulator::run() {
|
void Emulator::run() {
|
||||||
while (running) {
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
runFrame(); // Run 1 frame of instructions
|
httpServer.startHttpServer();
|
||||||
gpu.display(); // Display graphics
|
#endif
|
||||||
|
while (running) {
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
pollHttpServer();
|
||||||
|
#endif
|
||||||
|
runFrame(); // Run 1 frame of instructions
|
||||||
|
gpu.display(); // Display graphics
|
||||||
|
|
||||||
ServiceManager& srv = kernel.getServiceManager();
|
ServiceManager& srv = kernel.getServiceManager();
|
||||||
|
|
||||||
|
@ -337,12 +345,12 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
romPath = std::nullopt;
|
romPath = std::nullopt;
|
||||||
romType = ROMType::None;
|
romType = ROMType::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for loading both CXI and NCSD files since they are both so similar and use the same interface
|
// 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) {
|
bool Emulator::loadNCSD(const std::filesystem::path& path, ROMType type) {
|
||||||
romType = type;
|
romType = type;
|
||||||
std::optional<NCSD> opt = (type == ROMType::NCSD) ? memory.loadNCSD(aesEngine, path) : memory.loadCXI(aesEngine, path);
|
std::optional<NCSD> opt = (type == ROMType::NCSD) ? memory.loadNCSD(aesEngine, path) : memory.loadCXI(aesEngine, path);
|
||||||
|
@ -390,3 +398,39 @@ void Emulator::initGraphicsContext() {
|
||||||
gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL
|
gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL
|
||||||
gpu.initGraphicsContext();
|
gpu.initGraphicsContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
void Emulator::pollHttpServer() {
|
||||||
|
std::scoped_lock lock(httpServer.actionMutex);
|
||||||
|
|
||||||
|
ServiceManager& srv = kernel.getServiceManager();
|
||||||
|
|
||||||
|
if (httpServer.pendingAction) {
|
||||||
|
switch (httpServer.action) {
|
||||||
|
case HttpAction::Screenshot:
|
||||||
|
gpu.screenshot(HttpServer::httpServerScreenshotPath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpAction::PressKey:
|
||||||
|
if (httpServer.pendingKey != 0) {
|
||||||
|
srv.pressKey(httpServer.pendingKey);
|
||||||
|
httpServer.pendingKey = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpAction::ReleaseKey:
|
||||||
|
if (httpServer.pendingKey != 0) {
|
||||||
|
srv.releaseKey(httpServer.pendingKey);
|
||||||
|
httpServer.pendingKey = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpAction::None: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpServer.action = HttpAction::None;
|
||||||
|
httpServer.pendingAction = false;
|
||||||
|
httpServer.pendingAction.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
103
src/httpserver.cpp
Normal file
103
src/httpserver.cpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
#include "httpserver.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
1
third_party/httplib
vendored
Submodule
1
third_party/httplib
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit be07d2d7a99c0a54b00526f30f175e93c3588f34
|
1
third_party/stb
vendored
Submodule
1
third_party/stb
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9
|
Loading…
Add table
Reference in a new issue