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();
 }