diff --git a/Common/Net/Sinks.cpp b/Common/Net/Sinks.cpp
index 0c8e485e85..3183ead35c 100644
--- a/Common/Net/Sinks.cpp
+++ b/Common/Net/Sinks.cpp
@@ -59,7 +59,7 @@ std::pair InputSink::BufferParts() const {
}
size_t InputSink::ReadBinaryUntilTerminator(char *dest, size_t bufSize, std::string_view terminator, bool *didReadTerminator) {
- Block();
+ Fill();
auto [part1, part2] = BufferParts();
size_t offset = SplitSearch(terminator, part1, part2);
diff --git a/Common/System/System.h b/Common/System/System.h
index 084614d4de..ce3a2b779d 100644
--- a/Common/System/System.h
+++ b/Common/System/System.h
@@ -145,6 +145,7 @@ enum SystemProperty {
SYSPROP_CLIPBOARD_TEXT,
SYSPROP_GPUDRIVER_VERSION,
SYSPROP_BUILD_VERSION,
+ SYSPROP_COMPUTER_NAME,
// Separate SD cards or similar.
// Need hacky solutions to get at this.
diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp
index cce8e42172..ab5891dcc0 100644
--- a/Core/WebServer.cpp
+++ b/Core/WebServer.cpp
@@ -32,6 +32,7 @@
#include "Common/File/VFS/VFS.h"
#include "Common/TimeUtil.h"
#include "Common/StringUtils.h"
+#include "Common/System/System.h"
#include "Core/Util/RecentFiles.h"
#include "Core/Config.h"
#include "Core/Debugger/WebSocket.h"
@@ -322,6 +323,45 @@ static void HandleListing(const http::ServerRequest &request) {
}
}
+std::string RenderTemplate(std::string_view input, const std::map& values) {
+ std::string output;
+ output.reserve(input.size());
+
+ size_t i = 0;
+ while (i < input.size()) {
+ if (i + 3 < input.size() && input[i] == '{' && input[i + 1] == '{') {
+ size_t end = input.find("}}", i + 2);
+ if (end != std::string_view::npos) {
+ // Trim whitespace around key
+ size_t startKey = i + 2;
+ while (startKey < end && std::isspace(static_cast(input[startKey])))
+ startKey++;
+ size_t endKey = end;
+ while (endKey > startKey && std::isspace(static_cast(input[endKey - 1])))
+ endKey--;
+
+ std::string key(input.substr(startKey, endKey - startKey));
+
+ auto it = values.find(key);
+ if (it != values.end()) {
+ output += it->second;
+ } else {
+ // Keep the original placeholder if key not found
+ output.append(input.substr(i, end + 2 - i));
+ }
+
+ i = end + 2;
+ continue;
+ }
+ }
+
+ output.push_back(input[i]);
+ ++i;
+ }
+
+ return output;
+}
+
static bool ServeAssetFile(const http::ServerRequest &request) {
// Skip the slash at the start of the resource path.
std::string_view filename = request.resource().substr(1);
@@ -330,20 +370,8 @@ static bool ServeAssetFile(const http::ServerRequest &request) {
return false;
}
- size_t size;
- // TODO: ReadFile should take a string_view.
- uint8_t *data = g_VFS.ReadFile(std::string(filename).c_str(), &size);
- if (!data) {
- // Try appending index.html
- data = g_VFS.ReadFile((std::string(filename) + "/index.html").c_str(), &size);
- if (!data) {
- return false;
- }
- INFO_LOG(Log::HTTP, "Redirected to /index.html");
- }
-
std::string ext = Path(filename).GetFileExtension();
- const char *mimeType = "text/plain";
+ std::string_view mimeType = "text/plain";
if (ext == ".html") {
mimeType = "text/html";
} else if (ext == ".ico") {
@@ -358,10 +386,43 @@ static bool ServeAssetFile(const http::ServerRequest &request) {
mimeType = "text/css";
}
- request.WriteHttpResponseHeader("1.0", 200, size, mimeType);
- request.Out()->Push((char *)data, size);
+ size_t size;
+ // TODO: ReadFile should take a string_view.
+ uint8_t *data = g_VFS.ReadFile(std::string(filename).c_str(), &size);
+ if (!data) {
+ // Try appending index.html
+ data = g_VFS.ReadFile((std::string(filename) + "/index.html").c_str(), &size);
+ mimeType = "text/html";
+ if (!data) {
+ return false;
+ }
+ INFO_LOG(Log::HTTP, "Redirected to /index.html");
+ }
+ std::string html = std::string((const char *)data, size);
delete[] data;
+
+ // This is a gross, gross hack to have here in ServeAssetFile, but oh well.
+ if (mimeType == "text/html") {
+ if (html.find("") != std::string::npos) {
+ std::string uploadPath = g_uploadPath.ToVisualString();
+ std::string deviceName = System_GetProperty(SYSPROP_NAME);
+ std::string computerName = System_GetProperty(SYSPROP_COMPUTER_NAME);
+ if (!computerName.empty()) {
+ deviceName = computerName;
+ }
+ std::map values = {
+ {"upload_path", uploadPath},
+ {"device_name", deviceName},
+ };
+ // Use templating to insert some information.
+ html = RenderTemplate(html, values);
+ }
+ }
+
+ request.WriteHttpResponseHeader("1.0", 200, html.size(), std::string(mimeType).c_str());
+ request.Out()->Push(html.data(), html.size());
+
return true;
}
@@ -392,7 +453,7 @@ static void HandleFallback(const http::ServerRequest &request) {
}
if (serverFlags & WebServerFlags::FILE_UPLOAD) {
- if (startsWith(request.resource(), "/upload/")) {
+ if (startsWith(request.resource(), "/upload")) {
if (ServeAssetFile(request)) {
return;
}
diff --git a/Windows/main.cpp b/Windows/main.cpp
index db8050dcea..1240f65f98 100644
--- a/Windows/main.cpp
+++ b/Windows/main.cpp
@@ -115,6 +115,7 @@ static std::string langRegion;
static std::string osName;
static std::string osVersion;
static std::string gpuDriverVersion;
+static std::string computerName;
static std::string restartArgs;
@@ -253,6 +254,17 @@ std::string System_GetProperty(SystemProperty prop) {
return PPSSPP_GIT_VERSION;
case SYSPROP_USER_DOCUMENTS_DIR:
return Path(W32Util::UserDocumentsPath()).ToString(); // this'll reverse the slashes.
+ case SYSPROP_COMPUTER_NAME:
+ if (computerName.empty()) {
+ wchar_t nameBuf[MAX_COMPUTERNAME_LENGTH + 1];
+ DWORD size = ARRAY_SIZE(nameBuf);
+ if (GetComputerNameW(nameBuf, &size)) {
+ computerName = ConvertWStringToUTF8(std::wstring(nameBuf, size));
+ } else {
+ computerName = "(N/A)";
+ }
+ }
+ return computerName;
default:
return "";
}
diff --git a/assets/upload/index.html b/assets/upload/index.html
index 0f0522ed6c..b17a35c7fa 100644
--- a/assets/upload/index.html
+++ b/assets/upload/index.html
@@ -1,208 +1,261 @@
+
+
-
- File/Folder Uploader
+
+ File Uploader
-
- Upload Files or Folders
+ Upload files to {{ device_name }}
-
-
-
-
-
-
-
+ To folder:
{{ upload_path }}
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+