diff --git a/openrct2.common.props b/openrct2.common.props index 6c38bd49b4..205501951b 100644 --- a/openrct2.common.props +++ b/openrct2.common.props @@ -53,7 +53,7 @@ C4549: 'operator': operator before comma has no effect; did you intend 'operator'? C4555: expression has no effect; expected expression with side-effect --> - __AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions) + __AVX2__;__SSE4_1__;OPENGL_NO_LINK;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;NOMINMAX;%(PreprocessorDefinitions) MultiThreaded MultiThreadedDLL true @@ -61,7 +61,7 @@ /utf-8 /std:c++17 /permissive- /Zc:externConstexpr - wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;bcrypt.lib;%(AdditionalDependencies) + wininet.lib;imm32.lib;version.lib;winmm.lib;crypt32.lib;wldap32.lib;shlwapi.lib;setupapi.lib;bcrypt.lib;winhttp.lib;%(AdditionalDependencies) /OPT:NOLBR /ignore:4099 %(AdditionalOptions) @@ -76,7 +76,7 @@ DebugFull - benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;freetyped.lib;jansson_d.lib;libcurl-d.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies) + benchmarkd.lib;libbreakpadd.lib;libbreakpad_clientd.lib;bz2d.lib;discord-rpc.lib;freetyped.lib;jansson_d.lib;libpng16d.lib;libspeexdsp.lib;SDL2d.lib;zip.lib;zlibd.lib;%(AdditionalDependencies) @@ -94,7 +94,7 @@ DebugFull true true - benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;freetype.lib;jansson.lib;libcurl.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies) + benchmark.lib;libbreakpad.lib;libbreakpad_client.lib;bz2.lib;discord-rpc.lib;freetype.lib;jansson.lib;libpng16.lib;libspeexdsp.lib;SDL2.lib;zip.lib;zlib.lib;%(AdditionalDependencies) diff --git a/src/openrct2-ui/windows/ObjectLoadError.cpp b/src/openrct2-ui/windows/ObjectLoadError.cpp index 9cd93d4af9..ca799c2f6a 100644 --- a/src/openrct2-ui/windows/ObjectLoadError.cpp +++ b/src/openrct2-ui/windows/ObjectLoadError.cpp @@ -11,10 +11,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -151,7 +151,6 @@ private: void DownloadObject(const rct_object_entry& entry, const std::string name, const std::string url) { - using namespace OpenRCT2::Networking; try { std::printf("Downloading %s\n", url.c_str()); @@ -190,8 +189,6 @@ private: void NextDownload() { - using namespace OpenRCT2::Networking; - if (!_downloadingObjects || _currentDownloadIndex >= _entries.size()) { // Finished... diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index fcf7e20e74..8f9bc01b1e 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -21,7 +21,6 @@ # include # include # include -# include # include # include # include diff --git a/src/openrct2/CMakeLists.txt b/src/openrct2/CMakeLists.txt index 5353df0d5f..f9a5ecb195 100644 --- a/src/openrct2/CMakeLists.txt +++ b/src/openrct2/CMakeLists.txt @@ -50,19 +50,18 @@ if (NOT DISABLE_NETWORK AND WIN32) endif () if (NOT DISABLE_HTTP) - if (MSVC) - find_package(curl REQUIRED) - set(LIBCURL_LIBRARIES ${CURL_LIBRARIES}) + if (WIN32) + target_link_libraries(${PROJECT_NAME} winhttp) else () PKG_CHECK_MODULES(LIBCURL REQUIRED libcurl) - endif () - - target_include_directories(${PROJECT_NAME} PRIVATE ${LIBCURL_INCLUDE_DIRS}) - - if (STATIC) - target_link_libraries(${PROJECT_NAME} ${LIBCURL_STATIC_LIBRARIES}) - else () - target_link_libraries(${PROJECT_NAME} ${LIBCURL_LIBRARIES}) + + target_include_directories(${PROJECT_NAME} PRIVATE ${LIBCURL_INCLUDE_DIRS}) + + if (STATIC) + target_link_libraries(${PROJECT_NAME} ${LIBCURL_STATIC_LIBRARIES}) + else () + target_link_libraries(${PROJECT_NAME} ${LIBCURL_LIBRARIES}) + endif () endif () endif () diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 34acc7788d..72de7259f7 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -33,6 +33,7 @@ #include "core/FileScanner.h" #include "core/FileStream.hpp" #include "core/Guard.hpp" +#include "core/Http.h" #include "core/MemoryStream.h" #include "core/Path.hpp" #include "core/String.hpp" @@ -45,7 +46,6 @@ #include "localisation/Localisation.h" #include "localisation/LocalisationService.h" #include "network/DiscordService.h" -#include "network/Http.h" #include "network/Twitch.h" #include "network/network.h" #include "object/ObjectManager.h" @@ -99,9 +99,6 @@ namespace OpenRCT2 std::unique_ptr _discordService; #endif StdInOutConsole _stdInOutConsole; -#ifndef DISABLE_HTTP - Networking::Http::Http _http; -#endif // Game states std::unique_ptr _titleScreen; @@ -721,15 +718,14 @@ namespace OpenRCT2 { #ifndef DISABLE_HTTP // Download park and open it using its temporary filename - void* data; - size_t dataSize = Networking::Http::DownloadPark(gOpenRCT2StartupActionPath, &data); - if (dataSize == 0) + auto data = DownloadPark(gOpenRCT2StartupActionPath); + if (data.empty()) { title_load(); break; } - auto ms = MemoryStream(data, dataSize, MEMORY_ACCESS::OWNER); + auto ms = MemoryStream(data.data(), data.size(), MEMORY_ACCESS::READ); if (!LoadParkFromStream(&ms, gOpenRCT2StartupActionPath, true)) { Console::Error::WriteLine("Failed to load '%s'", gOpenRCT2StartupActionPath); @@ -1083,6 +1079,34 @@ namespace OpenRCT2 } delete scanner; } + +#ifndef DISABLE_HTTP + std::vector DownloadPark(const std::string& url) + { + // Download park to buffer in memory + Http::Request request; + request.url = url; + request.method = Http::Method::GET; + + Http::Response res; + try + { + res = Do(request); + if (res.status != Http::Status::OK) + throw std::runtime_error("bad http status"); + } + catch (std::exception& e) + { + Console::Error::WriteLine("Failed to download '%s', cause %s", request.url.c_str(), e.what()); + return {}; + } + + std::vector parkData; + parkData.resize(res.body.size()); + std::memcpy(parkData.data(), res.body.c_str(), parkData.size()); + return parkData; + } +#endif }; Context* Context::Instance = nullptr; diff --git a/src/openrct2/core/Http.WinHttp.cpp b/src/openrct2/core/Http.WinHttp.cpp new file mode 100644 index 0000000000..9d30365cbc --- /dev/null +++ b/src/openrct2/core/Http.WinHttp.cpp @@ -0,0 +1,223 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#if !defined(DISABLE_HTTP) && defined(_WIN32) + +# include "Http.h" + +# include "../Version.h" +# include "String.hpp" + +# include +# include +# define WIN32_LEAN_AND_MEAN +# include +# include + +namespace Http +{ + static constexpr char OPENRCT2_USER_AGENT[] = "OpenRCT2/" OPENRCT2_VERSION; + + static void ThrowWin32Exception(const char* methodName) + { + auto errorCode = GetLastError(); + auto msg = String::StdFormat("%s failed, error: %d.", methodName, errorCode); + throw std::runtime_error(msg); + } + + static const wchar_t* GetVerb(Method method) + { + switch (method) + { + case Method::GET: + return L"GET"; + case Method::POST: + return L"POST"; + case Method::PUT: + return L"PUT"; + default: + throw std::runtime_error("Unsupported verb."); + } + } + + static std::wstring ReadHeader(HINTERNET hRequest, DWORD infoLevel, const wchar_t* name) + { + wchar_t headerBuffer[256]{}; + auto headerBufferLen = (DWORD)std::size(headerBuffer); + if (!WinHttpQueryHeaders(hRequest, infoLevel, name, headerBuffer, &headerBufferLen, WINHTTP_NO_HEADER_INDEX)) + ThrowWin32Exception("WinHttpQueryHeaders"); + return std::wstring(headerBuffer, headerBufferLen); + } + + static int32_t ReadStatusCode(HINTERNET hRequest) + { + auto wStatusCode = ReadHeader(hRequest, WINHTTP_QUERY_STATUS_CODE, L"StatusCode"); + return std::stoi(wStatusCode); + } + + static std::map ReadHeaders(HINTERNET hRequest) + { + DWORD dwSize{}; + WinHttpQueryHeaders( + hRequest, WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwSize, WINHTTP_NO_HEADER_INDEX); + + std::wstring buffer; + buffer.resize(dwSize); + if (!WinHttpQueryHeaders( + hRequest, WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, buffer.data(), &dwSize, + WINHTTP_NO_HEADER_INDEX)) + ThrowWin32Exception("WinHttpQueryHeaders"); + + std::map headers; + std::wstring wKey, wValue; + int state = 0; + int index = 0; + for (auto c : buffer) + { + if (c == '\0') + { + state = 0; + if (index != 0 && wKey.size() != 0) + { + auto key = String::ToUtf8(wKey); + auto value = String::ToUtf8(wValue); + headers[key] = value; + } + wKey.clear(); + wValue.clear(); + index++; + continue; + } + if (state == 0 && c == ':') + { + state = 1; + } + else if (state == 1 && c == ' ') + { + state = 2; + } + else if (state == 0) + { + wKey.push_back(c); + } + else + { + state = 2; + wValue.push_back(c); + } + } + return headers; + } + + static std::string ReadBody(HINTERNET hRequest) + { + DWORD dwSize{}; + std::string body; + do + { + // Check for available data. + if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) + ThrowWin32Exception("WinHttpQueryDataAvailable"); + + // No more available data. + if (dwSize == 0) + break; + + auto offset = body.size(); + body.resize(offset + dwSize); + auto dst = (LPVOID)&body[offset]; + + DWORD dwDownloaded{}; + if (!WinHttpReadData(hRequest, dst, dwSize, &dwDownloaded)) + ThrowWin32Exception("WinHttpReadData"); + + // This condition should never be reached since WinHttpQueryDataAvailable + // reported that there are bits to read. + if (dwDownloaded == 0) + break; + } while (dwSize > 0); + return body; + } + + Response Do(const Request& req) + { + HINTERNET hSession{}, hConnect{}, hRequest{}; + try + { + URL_COMPONENTS url; + ZeroMemory(&url, sizeof(url)); + url.dwStructSize = sizeof(url); + url.dwSchemeLength = (DWORD)-1; + url.dwHostNameLength = (DWORD)-1; + url.dwUrlPathLength = (DWORD)-1; + url.dwExtraInfoLength = (DWORD)-1; + + auto wUrl = String::ToWideChar(req.url); + if (!WinHttpCrackUrl(wUrl.c_str(), 0, 0, &url)) + throw std::invalid_argument("Unable to parse URI."); + + auto userAgent = String::ToWideChar(OPENRCT2_USER_AGENT); + hSession = WinHttpOpen( + userAgent.c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (hSession == nullptr) + ThrowWin32Exception("WinHttpOpen"); + + auto wHostName = std::wstring(url.lpszHostName, url.dwHostNameLength); + hConnect = WinHttpConnect(hSession, wHostName.c_str(), url.nPort, 0); + if (hConnect == nullptr) + ThrowWin32Exception("WinHttpConnect"); + + auto wVerb = GetVerb(req.method); + auto wQuery = std::wstring(url.lpszUrlPath, url.dwUrlPathLength); + hRequest = WinHttpOpenRequest( + hConnect, wVerb, wQuery.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + if (hRequest == nullptr) + ThrowWin32Exception("WinHttpOpenRequest"); + + for (auto header : req.header) + { + auto fullHeader = String::ToWideChar(header.first) + L": " + String::ToWideChar(header.second); + if (!WinHttpAddRequestHeaders(hRequest, fullHeader.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD)) + ThrowWin32Exception("WinHttpAddRequestHeaders"); + } + + auto reqBody = (LPVOID)req.body.data(); + auto reqBodyLen = (DWORD)req.body.size(); + if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, reqBody, reqBodyLen, reqBodyLen, 0)) + ThrowWin32Exception("WinHttpSendRequest"); + + if (!WinHttpReceiveResponse(hRequest, NULL)) + ThrowWin32Exception("WinHttpReceiveResponse"); + + auto statusCode = ReadStatusCode(hRequest); + auto contentType = ReadHeader(hRequest, WINHTTP_QUERY_CONTENT_TYPE, L"Content-Type"); + auto headers = ReadHeaders(hRequest); + auto body = ReadBody(hRequest); + + Response response; + response.body = std::move(body); + response.status = (Status)statusCode; + response.content_type = String::ToUtf8(contentType); + response.header = headers; + return response; + } + catch ([[maybe_unused]] const std::exception& e) + { +# ifdef DEBUG + std::fprintf(stderr, "HTTP request failed: %s\n", e.what()); +# endif + WinHttpCloseHandle(hSession); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hRequest); + throw; + } + } +} // namespace Http + +#endif diff --git a/src/openrct2/network/Http.cpp b/src/openrct2/core/Http.cURL.cpp similarity index 73% rename from src/openrct2/network/Http.cpp rename to src/openrct2/core/Http.cURL.cpp index 926f097423..3a1f690851 100644 --- a/src/openrct2/network/Http.cpp +++ b/src/openrct2/core/Http.cURL.cpp @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers + * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -7,17 +7,16 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#include "Http.h" - -#include -#include -#include -#include - -#ifndef DISABLE_HTTP +#if !defined(DISABLE_HTTP) && !defined(_WIN32) # include "../Version.h" # include "../core/Console.hpp" +# include "Http.h" + +# include +# include +# include +# include # ifdef _WIN32 // cURL includes windows.h, but we don't need all of it. @@ -27,18 +26,8 @@ # define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION -namespace OpenRCT2::Networking::Http +namespace Http { - Http::Http() - { - curl_global_init(CURL_GLOBAL_DEFAULT); - } - - Http::~Http() - { - curl_global_cleanup(); - } - static size_t writeData(const char* src, size_t size, size_t nmemb, void* userdata) { size_t realsize = size * nmemb; @@ -168,59 +157,6 @@ namespace OpenRCT2::Networking::Http return res; } - void DoAsync(const Request& req, std::function fn) - { - auto thread = std::thread([=]() { - Response res; - try - { - res = Do(req); - } - catch (std::exception& e) - { - res.error = e.what(); - return; - } - fn(res); - }); - thread.detach(); - } - - size_t DownloadPark(const char* url, void** outData) - { - // Download park to buffer in memory - Request request; - request.url = url; - request.method = Method::GET; - - Response res; - try - { - res = Do(request); - if (res.status != Status::OK) - std::runtime_error("bad http status"); - } - catch (std::exception& e) - { - Console::Error::WriteLine("Failed to download '%s', cause %s", request.url.c_str(), e.what()); - *outData = nullptr; - return 0; - } - - size_t dataSize = res.body.size() - 1; - void* data = malloc(dataSize); - if (data == nullptr) - { - Console::Error::WriteLine("Failed to allocate memory for downloaded park."); - return 0; - } - - std::memcpy(data, res.body.c_str(), dataSize); - *outData = data; - - return dataSize; - } - -} // namespace OpenRCT2::Networking::Http +} // namespace Http #endif diff --git a/src/openrct2/network/Http.h b/src/openrct2/core/Http.h similarity index 65% rename from src/openrct2/network/Http.h rename to src/openrct2/core/Http.h index 1d6f08eb8a..969446a362 100644 --- a/src/openrct2/network/Http.h +++ b/src/openrct2/core/Http.h @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers + * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -16,8 +16,9 @@ # include # include # include +# include -namespace OpenRCT2::Networking::Http +namespace Http { enum class Status { @@ -50,24 +51,25 @@ namespace OpenRCT2::Networking::Http bool forceIPv4 = false; }; - struct Http - { - Http(); - ~Http(); - }; - Response Do(const Request& req); - void DoAsync(const Request& req, std::function fn); - /** - * Download a park via HTTP/S from the given URL into a memory buffer. This is - * a blocking operation. - * @param url The URL to download the park from. - * @param outData The data returned. - * @returns The size of the data or 0 if the download failed. - */ - size_t DownloadPark(const char* url, void** outData); - -} // namespace OpenRCT2::Networking::Http + inline void DoAsync(const Request& req, std::function fn) + { + auto thread = std::thread([=]() { + Response res; + try + { + res = Do(req); + } + catch (std::exception& e) + { + res.error = e.what(); + return; + } + fn(res); + }); + thread.detach(); + } +} // namespace Http #endif // DISABLE_HTTP diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index a18865d469..9086c06196 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -13,6 +13,7 @@ # include "../config/Config.h" # include "../core/Console.hpp" +# include "../core/Http.h" # include "../core/Json.hpp" # include "../core/String.hpp" # include "../localisation/Date.h" @@ -23,7 +24,6 @@ # include "../util/Util.h" # include "../world/Map.h" # include "../world/Park.h" -# include "Http.h" # include "Socket.h" # include "network.h" @@ -164,8 +164,6 @@ private: void SendRegistration(bool forceIPv4) { - using namespace OpenRCT2::Networking; - _lastAdvertiseTime = platform_get_ticks(); // Send the registration request @@ -199,8 +197,6 @@ private: void SendHeartbeat() { - using namespace OpenRCT2::Networking; - Http::Request request; request.url = GetMasterServerUrl(); request.method = Http::Method::PUT; diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 951254b572..f57811fa8f 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -15,11 +15,11 @@ # include "../PlatformEnvironment.h" # include "../config/Config.h" # include "../core/FileStream.hpp" +# include "../core/Http.h" # include "../core/Json.hpp" # include "../core/Memory.hpp" # include "../core/Path.hpp" # include "../core/String.hpp" -# include "../network/Http.h" # include "../platform/platform.h" # include "Socket.h" # include "network.h" @@ -331,7 +331,6 @@ std::future> ServerList::FetchOnlineServerListAsync # ifdef DISABLE_HTTP return {}; # else - using namespace OpenRCT2::Networking; auto p = std::make_shared>>(); auto f = p->get_future(); diff --git a/src/openrct2/network/Twitch.cpp b/src/openrct2/network/Twitch.cpp index 1bbb234684..32457a3087 100644 --- a/src/openrct2/network/Twitch.cpp +++ b/src/openrct2/network/Twitch.cpp @@ -29,6 +29,7 @@ namespace Twitch # include "../OpenRCT2.h" # include "../actions/GuestSetFlagsAction.hpp" # include "../config/Config.h" +# include "../core/Http.h" # include "../core/Json.hpp" # include "../core/String.hpp" # include "../drawing/Drawing.h" @@ -39,7 +40,6 @@ namespace Twitch # include "../platform/platform.h" # include "../util/Util.h" # include "../world/Sprite.h" -# include "Http.h" # include "Twitch.h" # include @@ -47,7 +47,6 @@ namespace Twitch # include using namespace OpenRCT2; -using namespace OpenRCT2::Networking; bool gTwitchEnable = false;