diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj
index cddfd6f8d..dbb02a4a4 100644
--- a/src/frontend-common/frontend-common.vcxproj
+++ b/src/frontend-common/frontend-common.vcxproj
@@ -18,6 +18,9 @@
+
+ true
+
true
@@ -67,6 +70,9 @@
+
+ true
+
true
diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters
index 73d9bff4e..980b960cf 100644
--- a/src/frontend-common/frontend-common.vcxproj.filters
+++ b/src/frontend-common/frontend-common.vcxproj.filters
@@ -36,6 +36,7 @@
+
@@ -73,8 +74,9 @@
+
-
+
\ No newline at end of file
diff --git a/src/frontend-common/http_downloader_uwp.cpp b/src/frontend-common/http_downloader_uwp.cpp
new file mode 100644
index 000000000..4ea7b64e5
--- /dev/null
+++ b/src/frontend-common/http_downloader_uwp.cpp
@@ -0,0 +1,166 @@
+#include "http_downloader_uwp.h"
+#include "common/assert.h"
+#include "common/log.h"
+#include "common/string_util.h"
+#include "common/timer.h"
+#include
+Log_SetChannel(HTTPDownloaderWinHttp);
+
+#include
+#include
+#include
+
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Web::Http;
+
+namespace FrontendCommon {
+
+HTTPDownloaderUWP::HTTPDownloaderUWP(std::string user_agent) : HTTPDownloader(), m_user_agent(std::move(user_agent)) {}
+
+HTTPDownloaderUWP::~HTTPDownloaderUWP() = default;
+
+std::unique_ptr HTTPDownloader::Create(const char* user_agent)
+{
+ std::string user_agent_str;
+ if (user_agent)
+ user_agent_str = user_agent;
+
+ return std::make_unique(user_agent ? std::string(user_agent) : std::string());
+}
+
+HTTPDownloader::Request* HTTPDownloaderUWP::InternalCreateRequest()
+{
+ Request* req = new Request();
+ return req;
+}
+
+void HTTPDownloaderUWP::InternalPollRequests()
+{
+ // noop - uses async
+}
+
+bool HTTPDownloaderUWP::StartRequest(HTTPDownloader::Request* request)
+{
+ Request* req = static_cast(request);
+
+ try
+ {
+ const std::wstring url_wide(StringUtil::UTF8StringToWideString(req->url));
+ const Uri uri(url_wide);
+
+ if (!m_user_agent.empty() &&
+ !req->client.DefaultRequestHeaders().UserAgent().TryParseAdd(StringUtil::UTF8StringToWideString(m_user_agent)))
+ {
+ Log_WarningPrintf("Failed to set user agent to '%s'", m_user_agent.c_str());
+ }
+
+ if (req->type == Request::Type::Post)
+ {
+ const winrt::Windows::Storage::Streams::Buffer post_buf(static_cast(req->post_data.size()));
+ std::memcpy(post_buf.data(), req->post_data.data(), req->post_data.size());
+
+ const HttpBufferContent post_content(post_buf);
+ req->request_async = req->client.PostAsync(uri, post_content);
+ }
+ else
+ {
+ req->request_async = req->client.GetAsync(uri);
+ }
+
+ req->request_async.Completed(
+ [req](const IAsyncOperationWithProgress& operation, AsyncStatus status) {
+ if (status == AsyncStatus::Completed)
+ {
+ Log_DevPrintf("Request for '%s' completed start portion", req->url.c_str());
+ try
+ {
+ req->state.store(Request::State::Receiving);
+ req->start_time = Common::Timer::GetValue();
+
+ const HttpResponseMessage response(req->request_async.get());
+ req->status_code = static_cast(response.StatusCode());
+
+ const IHttpContent content(response.Content());
+ req->receive_async = content.ReadAsBufferAsync();
+ req->receive_async.Completed(
+ [req](
+ const IAsyncOperationWithProgress& inner_operation,
+ AsyncStatus inner_status) {
+ if (inner_status == AsyncStatus::Completed)
+ {
+ const winrt::Windows::Storage::Streams::IBuffer buffer(inner_operation.get());
+ if (buffer && buffer.Length() > 0)
+ {
+ req->data.resize(buffer.Length());
+ std::memcpy(req->data.data(), buffer.data(), req->data.size());
+ }
+
+ Log_DevPrintf("End of request '%s', %zu bytes received", req->url.c_str(), req->data.size());
+ req->state.store(Request::State::Complete);
+ }
+ else if (inner_status == AsyncStatus::Canceled)
+ {
+ // don't do anything, the request has been freed
+ }
+ else
+ {
+ Log_ErrorPrintf("Request for '%s' failed during recieve phase: %08X", req->url.c_str(),
+ inner_operation.ErrorCode().value);
+ req->status_code = -1;
+ req->state.store(Request::State::Complete);
+ }
+ });
+ }
+ catch (const winrt::hresult_error& err)
+ {
+ Log_ErrorPrintf("Failed to receive HTTP request for '%s': %08X %s", req->url.c_str(), err.code(),
+ StringUtil::WideStringToUTF8String(err.message()).c_str());
+ req->status_code = -1;
+ req->state.store(Request::State::Complete);
+ }
+
+ req->receive_async = nullptr;
+ }
+ else if (status == AsyncStatus::Canceled)
+ {
+ // don't do anything, the request has been freed
+ }
+ else
+ {
+ Log_ErrorPrintf("Request for '%s' failed during start phase: %08X", req->url.c_str(),
+ operation.ErrorCode().value);
+ req->status_code = -1;
+ req->state.store(Request::State::Complete);
+ }
+
+ req->request_async = nullptr;
+ });
+ }
+ catch (const winrt::hresult_error& err)
+ {
+ Log_ErrorPrintf("Failed to start HTTP request for '%s': %08X %s", req->url.c_str(), err.code(),
+ StringUtil::WideStringToUTF8String(err.message()).c_str());
+ req->callback(-1, req->data);
+ delete req;
+ return false;
+ }
+
+ Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
+ req->state = Request::State::Started;
+ req->start_time = Common::Timer::GetValue();
+ return true;
+}
+
+void HTTPDownloaderUWP::CloseRequest(HTTPDownloader::Request* request)
+{
+ Request* req = static_cast(request);
+ if (req->request_async)
+ req->request_async.Cancel();
+ if (req->receive_async)
+ req->receive_async.Cancel();
+
+ req->client.Close();
+ delete req;
+}
+
+} // namespace FrontendCommon
\ No newline at end of file
diff --git a/src/frontend-common/http_downloader_uwp.h b/src/frontend-common/http_downloader_uwp.h
new file mode 100644
index 000000000..28d82d9da
--- /dev/null
+++ b/src/frontend-common/http_downloader_uwp.h
@@ -0,0 +1,37 @@
+#pragma once
+#include "http_downloader.h"
+
+#include "common/windows_headers.h"
+
+#include
+
+namespace FrontendCommon {
+
+class HTTPDownloaderUWP final : public HTTPDownloader
+{
+public:
+ HTTPDownloaderUWP(std::string user_agent);
+ ~HTTPDownloaderUWP() override;
+
+protected:
+ Request* InternalCreateRequest() override;
+ void InternalPollRequests() override;
+ bool StartRequest(HTTPDownloader::Request* request) override;
+ void CloseRequest(HTTPDownloader::Request* request) override;
+
+private:
+ struct Request : HTTPDownloader::Request
+ {
+ std::wstring object_name;
+ winrt::Windows::Web::Http::HttpClient client;
+ winrt::Windows::Foundation::IAsyncOperationWithProgress
+ request_async{nullptr};
+ winrt::Windows::Foundation::IAsyncOperationWithProgress
+ receive_async{};
+ };
+
+ std::string m_user_agent;
+};
+
+} // namespace FrontendCommon
\ No newline at end of file