From 61f44525260933455b5474d5e5e12781b54a74c4 Mon Sep 17 00:00:00 2001 From: Alexander Overvoorde Date: Sun, 30 Oct 2016 03:15:27 +0100 Subject: [PATCH 1/2] Fix #3355: Implement loading of parks from URLs The help text for the command line options already referenced the possibility of opening a saved park directly from a URL, but this was not yet implemented. This commit changes all path handling for command line options to accept both local paths and URLs. If a URL is specified instead of a local path, the program will download the file to the operating system's temp directory. It will then proceed to load this file just like local files would be loaded. The program will try to derive the extension of the temp file from the original URL and defaults to sv6 (a save file) if it is unable to do so. --- src/cmdline/RootCommands.cpp | 7 + src/network/NetworkServerAdvertiser.cpp | 18 +- src/network/http.cpp | 410 ++++++++------ src/network/http.h | 39 +- src/network/twitch.cpp | 37 +- src/openrct2.c | 694 ++++++++++++++++++++++++ src/windows/server_list.c | 17 +- 7 files changed, 1026 insertions(+), 196 deletions(-) create mode 100644 src/openrct2.c diff --git a/src/cmdline/RootCommands.cpp b/src/cmdline/RootCommands.cpp index 2d684229b6..78511f1659 100644 --- a/src/cmdline/RootCommands.cpp +++ b/src/cmdline/RootCommands.cpp @@ -121,8 +121,13 @@ static void PrintLaunchInformation(); const CommandLineCommand CommandLine::RootCommands[] { // Main commands +#ifndef DISABLE_HTTP DefineCommand("", "", StandardOptions, HandleNoCommand ), DefineCommand("edit", "", StandardOptions, HandleCommandEdit ), +#else + DefineCommand("", "", StandardOptions, HandleNoCommand ), + DefineCommand("edit", "", StandardOptions, HandleCommandEdit ), +#endif DefineCommand("intro", "", StandardOptions, HandleCommandIntro ), #ifndef DISABLE_NETWORK DefineCommand("host", "", StandardOptions, HandleCommandHost ), @@ -148,7 +153,9 @@ const CommandLineExample CommandLine::RootExamples[] { "./my_park.sv6", "open a saved park" }, { "./SnowyPark.sc6", "install and open a scenario" }, { "./ShuttleLoop.td6", "install a track" }, +#ifndef DISABLE_HTTP { "https://openrct2.website/files/SnowyPark.sv6", "download and open a saved park" }, +#endif #ifndef DISABLE_NETWORK { "host ./my_park.sv6 --port 11753 --headless", "run a headless server for a saved park" }, #endif diff --git a/src/network/NetworkServerAdvertiser.cpp b/src/network/NetworkServerAdvertiser.cpp index 1dd52eca6d..64bcf6071e 100644 --- a/src/network/NetworkServerAdvertiser.cpp +++ b/src/network/NetworkServerAdvertiser.cpp @@ -100,7 +100,7 @@ private: _lastAdvertiseTime = SDL_GetTicks(); // Send the registration request - http_json_request request; + http_request_t request; request.tag = this; request.url = GetMasterServerUrl(); request.method = HTTP_METHOD_POST; @@ -108,9 +108,10 @@ private: json_t *body = json_object(); json_object_set_new(body, "key", json_string(_key.c_str())); json_object_set_new(body, "port", json_integer(_port)); - request.body = body; + request.root = body; + request.type = HTTP_DATA_JSON; - http_request_json_async(&request, [](http_json_response * response) -> void + http_request_async(&request, [](http_response_t * response) -> void { if (response == nullptr) { @@ -120,7 +121,7 @@ private: { auto advertiser = static_cast(response->tag); advertiser->OnRegistrationResponse(response->root); - http_request_json_dispose(response); + http_request_dispose(response); } }); @@ -129,16 +130,17 @@ private: void SendHeartbeat() { - http_json_request request; + http_request_t request; request.tag = this; request.url = GetMasterServerUrl(); request.method = HTTP_METHOD_PUT; json_t * jsonBody = GetHeartbeatJson(); - request.body = jsonBody; + request.root = jsonBody; + request.type = HTTP_DATA_JSON; _lastHeartbeatTime = SDL_GetTicks(); - http_request_json_async(&request, [](http_json_response *response) -> void + http_request_async(&request, [](http_response_t *response) -> void { if (response == nullptr) { @@ -148,7 +150,7 @@ private: { auto advertiser = static_cast(response->tag); advertiser->OnHeartbeatResponse(response->root); - http_request_json_dispose(response); + http_request_dispose(response); } }); diff --git a/src/network/http.cpp b/src/network/http.cpp index 5fd1b43937..f5949f42ea 100644 --- a/src/network/http.cpp +++ b/src/network/http.cpp @@ -15,8 +15,8 @@ #pragma endregion extern "C" { - #include "http.h" - #include "../platform/platform.h" + #include "http.h" + #include "../platform/platform.h" } #ifdef DISABLE_HTTP @@ -29,10 +29,11 @@ void http_dispose() { } #include "../core/Math.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" +#include "../core/Console.hpp" #ifdef __WINDOWS__ - // cURL includes windows.h, but we don't need all of it. - #define WIN32_LEAN_AND_MEAN + // cURL includes windows.h, but we don't need all of it. + #define WIN32_LEAN_AND_MEAN #endif #include @@ -40,189 +41,290 @@ void http_dispose() { } #define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION typedef struct read_buffer { - char *ptr; - size_t length; - size_t position; + char *ptr; + size_t length; + size_t position; } read_buffer; typedef struct write_buffer { - char *ptr; - size_t length; - size_t capacity; + char *ptr; + size_t length; + size_t capacity; } write_buffer; void http_init() { - curl_global_init(CURL_GLOBAL_DEFAULT); + curl_global_init(CURL_GLOBAL_DEFAULT); } void http_dispose() { - curl_global_cleanup(); + curl_global_cleanup(); } static size_t http_request_read_func(void *ptr, size_t size, size_t nmemb, void *userdata) { - read_buffer *readBuffer = (read_buffer*)userdata; + read_buffer *readBuffer = (read_buffer*)userdata; - size_t remainingBytes = readBuffer->length - readBuffer->position; - size_t readBytes = size * nmemb; - if (readBytes > remainingBytes) { - readBytes = remainingBytes; - } + size_t remainingBytes = readBuffer->length - readBuffer->position; + size_t readBytes = size * nmemb; + if (readBytes > remainingBytes) { + readBytes = remainingBytes; + } - memcpy(ptr, readBuffer->ptr + readBuffer->position, readBytes); + memcpy(ptr, readBuffer->ptr + readBuffer->position, readBytes); - readBuffer->position += readBytes; - return readBytes; + readBuffer->position += readBytes; + return readBytes; } static size_t http_request_write_func(void *ptr, size_t size, size_t nmemb, void *userdata) { - write_buffer *writeBuffer = (write_buffer*)userdata; + write_buffer *writeBuffer = (write_buffer*)userdata; - size_t newBytesLength = size * nmemb; - if (newBytesLength > 0) { - size_t newCapacity = writeBuffer->capacity; - size_t newLength = writeBuffer->length + newBytesLength; - while (newLength > newCapacity) { - newCapacity = Math::Max(4096, newCapacity * 2); + size_t newBytesLength = size * nmemb; + if (newBytesLength > 0) { + size_t newCapacity = writeBuffer->capacity; + size_t newLength = writeBuffer->length + newBytesLength; + while (newLength > newCapacity) { + newCapacity = Math::Max(4096, newCapacity * 2); + } + if (newCapacity != writeBuffer->capacity) { + writeBuffer->ptr = (char*)realloc(writeBuffer->ptr, newCapacity); + writeBuffer->capacity = newCapacity; + } + + memcpy(writeBuffer->ptr + writeBuffer->length, ptr, newBytesLength); + writeBuffer->length = newLength; + } + return newBytesLength; +} + +http_response_t *http_request(const http_request_t *request) +{ + CURL *curl; + CURLcode curlResult; + http_response_t *response; + read_buffer readBuffer = { 0 }; + write_buffer writeBuffer; + + curl = curl_easy_init(); + if (curl == NULL) + return NULL; + + if (request->type == HTTP_DATA_JSON && request->root != NULL) { + readBuffer.ptr = json_dumps(request->root, JSON_COMPACT); + readBuffer.length = strlen(readBuffer.ptr); + readBuffer.position = 0; + } else if (request->type == HTTP_DATA_RAW && request->body != NULL) { + readBuffer.ptr = request->body; + readBuffer.length = request->size; + readBuffer.position = 0; + } + + writeBuffer.ptr = NULL; + writeBuffer.length = 0; + writeBuffer.capacity = 0; + + curl_slist *headers = NULL; + + if (request->type == HTTP_DATA_JSON) { + headers = curl_slist_append(headers, "Accept: " MIME_TYPE_APPLICATION_JSON); + + if (request->root != NULL) { + headers = curl_slist_append(headers, "Content-Type: " MIME_TYPE_APPLICATION_JSON); + } + } + + if (readBuffer.ptr != NULL) { + char contentLengthHeaderValue[64]; + snprintf(contentLengthHeaderValue, sizeof(contentLengthHeaderValue), "Content-Length: %zu", readBuffer.length); + headers = curl_slist_append(headers, contentLengthHeaderValue); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, readBuffer.ptr); + } + + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request->method); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); + curl_easy_setopt(curl, CURLOPT_USERAGENT, OPENRCT2_USER_AGENT); + curl_easy_setopt(curl, CURLOPT_URL, request->url); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeBuffer); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_request_write_func); + + curlResult = curl_easy_perform(curl); + + if (request->type == HTTP_DATA_JSON && request->root != NULL) { + free(readBuffer.ptr); + } + + if (curlResult != CURLE_OK) { + log_error("HTTP request failed: %s.", curl_easy_strerror(curlResult)); + if (writeBuffer.ptr != NULL) + free(writeBuffer.ptr); + + return NULL; + } + + long httpStatusCode; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpStatusCode); + + char* contentType; + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); + + // Null terminate the response buffer + writeBuffer.length++; + writeBuffer.ptr = (char*)realloc(writeBuffer.ptr, writeBuffer.length); + writeBuffer.capacity = writeBuffer.length; + writeBuffer.ptr[writeBuffer.length - 1] = 0; + + response = NULL; + + // Parse as JSON if response is JSON + if (contentType != NULL && strstr(contentType, "json") != NULL) { + json_t *root; + json_error_t error; + root = json_loads(writeBuffer.ptr, 0, &error); + if (root != NULL) { + response = (http_response_t*) malloc(sizeof(http_response_t)); + response->tag = request->tag; + response->status_code = (int) httpStatusCode; + response->root = root; + response->type = HTTP_DATA_JSON; + response->size = writeBuffer.length; + } + free(writeBuffer.ptr); + } else { + response = (http_response_t*) malloc(sizeof(http_response_t)); + response->tag = request->tag; + response->status_code = (int) httpStatusCode; + response->body = writeBuffer.ptr; + response->type = HTTP_DATA_RAW; + response->size = writeBuffer.length; + } + + curl_easy_cleanup(curl); + + return response; +} + +void http_request_async(const http_request_t *request, void (*callback)(http_response_t*)) +{ + struct TempThreadArgs { + http_request_t request; + void (*callback)(http_response_t*); + }; + + TempThreadArgs *args = (TempThreadArgs*)malloc(sizeof(TempThreadArgs)); + args->request.url = _strdup(request->url); + args->request.method = request->method; + + if (request->type == HTTP_DATA_JSON) { + args->request.root = json_deep_copy(request->root); + } else { + char* bodyCopy = (char*) malloc(request->size); + memcpy(bodyCopy, request->body, request->size); + args->request.body = bodyCopy; + } + + args->request.type = request->type; + args->request.size = request->size; + args->request.tag = request->tag; + args->callback = callback; + + SDL_Thread *thread = SDL_CreateThread([](void *ptr) -> int { + TempThreadArgs *args = (TempThreadArgs*)ptr; + + http_response_t *response = http_request(&args->request); + args->callback(response); + + free((char*)args->request.url); + + if (args->request.type == HTTP_DATA_JSON) { + json_decref((json_t*) args->request.root); + } else { + free(args->request.body); + } + + free(args); + return 0; + }, NULL, args); + + if (thread == NULL) { + log_error("Unable to create thread!"); + callback(NULL); + } else { + SDL_DetachThread(thread); + } +} + +void http_request_dispose(http_response_t *response) +{ + if (response->type == HTTP_DATA_JSON && response->root != NULL) + json_decref(response->root); + else if (response->type == HTTP_DATA_RAW && response->body != NULL) + free(response->body); + + free(response); +} + +const char *http_get_extension_from_url(const char *url, const char *fallback) +{ + const char *extension = strrchr(url, '.'); + + // Assume a save file by default if no valid extension can be determined + if (extension == NULL || strchr(extension, '/') != NULL) { + return fallback; + } else { + return extension; + } +} + +bool http_download_park(const char *url, char tmpPath[L_tmpnam + 10]) +{ + // Download park to buffer in memory + http_request_t request; + request.url = url; + request.method = "GET"; + request.type = HTTP_DATA_NONE; + + http_response_t *response = http_request(&request); + + if (response == NULL || response->status_code != 200) { + Console::Error::WriteLine("Failed to download '%s'", request.url); + if (response != NULL) { + http_request_dispose(response); } - if (newCapacity != writeBuffer->capacity) { - writeBuffer->ptr = (char*)realloc(writeBuffer->ptr, newCapacity); - writeBuffer->capacity = newCapacity; - } - - memcpy(writeBuffer->ptr + writeBuffer->length, ptr, newBytesLength); - writeBuffer->length = newLength; - } - return newBytesLength; -} - -http_json_response *http_request_json(const http_json_request *request) -{ - CURL *curl; - CURLcode curlResult; - http_json_response *response; - read_buffer readBuffer = { 0 }; - write_buffer writeBuffer; - - curl = curl_easy_init(); - if (curl == NULL) - return NULL; - - if (request->body != NULL) { - readBuffer.ptr = json_dumps(request->body, JSON_COMPACT); - readBuffer.length = strlen(readBuffer.ptr); - readBuffer.position = 0; + return false; } - writeBuffer.ptr = NULL; - writeBuffer.length = 0; - writeBuffer.capacity = 0; + // Generate temporary filename that includes the original extension + if (tmpnam(tmpPath) == NULL) { + Console::Error::WriteLine("Failed to generate temporary filename for downloaded park '%s'", request.url); + http_request_dispose(response); + return false; + } + size_t remainingBytes = L_tmpnam + 10 - strlen(tmpPath); - curl_slist *headers = NULL; - headers = curl_slist_append(headers, "Accept: " MIME_TYPE_APPLICATION_JSON); - if (request->body != NULL) { - headers = curl_slist_append(headers, "Content-Type: " MIME_TYPE_APPLICATION_JSON); + const char *ext = http_get_extension_from_url(request.url, ".sv6"); + strncat(tmpPath, ext, remainingBytes); - char contentLengthHeaderValue[64]; - snprintf(contentLengthHeaderValue, sizeof(contentLengthHeaderValue), "Content-Length: %zu", readBuffer.length); - headers = curl_slist_append(headers, contentLengthHeaderValue); + // Store park in temporary file and load it (discard ending NUL in response body) + FILE* tmpFile = fopen(tmpPath, "wb"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, readBuffer.ptr); + if (tmpFile == NULL) { + Console::Error::WriteLine("Failed to write downloaded park '%s' to temporary file", request.url); + http_request_dispose(response); + return false; } - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request->method); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); - curl_easy_setopt(curl, CURLOPT_USERAGENT, OPENRCT2_USER_AGENT); - curl_easy_setopt(curl, CURLOPT_URL, request->url); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeBuffer); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_request_write_func); + fwrite(response->body, 1, response->size - 1, tmpFile); + fclose(tmpFile); - curlResult = curl_easy_perform(curl); + http_request_dispose(response); - if (request->body != NULL) { - free(readBuffer.ptr); - } - - if (curlResult != CURLE_OK) { - log_error("HTTP request failed: %s.", curl_easy_strerror(curlResult)); - if (writeBuffer.ptr != NULL) - free(writeBuffer.ptr); - - return NULL; - } - - long httpStatusCode; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpStatusCode); - - curl_easy_cleanup(curl); - - // Null terminate the response buffer - writeBuffer.length++; - writeBuffer.ptr = (char*)realloc(writeBuffer.ptr, writeBuffer.length); - writeBuffer.capacity = writeBuffer.length; - writeBuffer.ptr[writeBuffer.length - 1] = 0; - - response = NULL; - - // Parse as JSON - json_t *root; - json_error_t error; - root = json_loads(writeBuffer.ptr, 0, &error); - if (root != NULL) { - response = (http_json_response*)malloc(sizeof(http_json_response)); - response->tag = request->tag; - response->status_code = (int)httpStatusCode; - response->root = root; - } - free(writeBuffer.ptr); - return response; -} - -void http_request_json_async(const http_json_request *request, void (*callback)(http_json_response*)) -{ - struct TempThreadArgs { - http_json_request request; - void (*callback)(http_json_response*); - }; - - TempThreadArgs *args = (TempThreadArgs*)malloc(sizeof(TempThreadArgs)); - args->request.url = _strdup(request->url); - args->request.method = request->method; - args->request.body = json_deep_copy(request->body); - args->request.tag = request->tag; - args->callback = callback; - - SDL_Thread *thread = SDL_CreateThread([](void *ptr) -> int { - TempThreadArgs *args = (TempThreadArgs*)ptr; - - http_json_response *response = http_request_json(&args->request); - args->callback(response); - - free((char*)args->request.url); - json_decref((json_t*)args->request.body); - free(args); - return 0; - }, NULL, args); - - if (thread == NULL) { - log_error("Unable to create thread!"); - callback(NULL); - } else { - SDL_DetachThread(thread); - } -} - -void http_request_json_dispose(http_json_response *response) -{ - if (response->root != NULL) - json_decref(response->root); - - free(response); + return true; } #endif diff --git a/src/network/http.h b/src/network/http.h index 0a54635281..ac1f25b8aa 100644 --- a/src/network/http.h +++ b/src/network/http.h @@ -21,27 +21,48 @@ #include #include "../common.h" -typedef struct http_json_request { +typedef enum http_data_type_T { + HTTP_DATA_NONE, + HTTP_DATA_RAW, + HTTP_DATA_JSON +} http_data_type; + +typedef struct http_request_t { void *tag; const char *method; const char *url; - const json_t *body; -} http_json_request; + http_data_type type; + size_t size; + union { + const json_t *root; + char* body; + }; +} http_request_t; -typedef struct http_json_response { +typedef struct http_response_t { void *tag; int status_code; - json_t *root; -} http_json_response; + http_data_type type; + size_t size; + union { + json_t *root; + char* body; + }; +} http_response_t; #define HTTP_METHOD_GET "GET" #define HTTP_METHOD_POST "POST" #define HTTP_METHOD_PUT "PUT" #define HTTP_METHOD_DELETE "DELETE" -http_json_response *http_request_json(const http_json_request *request); -void http_request_json_async(const http_json_request *request, void (*callback)(http_json_response*)); -void http_request_json_dispose(http_json_response *response); +http_response_t *http_request(const http_request_t *request); +void http_request_async(const http_request_t *request, void (*callback)(http_response_t*)); +void http_request_dispose(http_response_t *response); + +const char *http_get_extension_from_url(const char *url, const char *fallback); + +// Padding for extension that is appended to temporary file name +bool http_download_park(const char *url, char tmpPath[L_tmpnam + 10]); #endif // DISABLE_HTTP // These callbacks are defined anyway, but are dummy if HTTP is disabled diff --git a/src/network/twitch.cpp b/src/network/twitch.cpp index 910f153e39..f835300b84 100644 --- a/src/network/twitch.cpp +++ b/src/network/twitch.cpp @@ -106,11 +106,11 @@ namespace Twitch constexpr uint32 PulseTime = 10 * 1000; constexpr const char * TwitchExtendedBaseUrl = "http://openrct.ursalabs.co/api/1/"; - static int _twitchState = TWITCH_STATE_LEFT; - static bool _twitchIdle = true; - static uint32 _twitchLastPulseTick = 0; - static int _twitchLastPulseOperation = 1; - static http_json_response * _twitchJsonResponse; + static int _twitchState = TWITCH_STATE_LEFT; + static bool _twitchIdle = true; + static uint32 _twitchLastPulseTick = 0; + static int _twitchLastPulseOperation = 1; + static http_response_t * _twitchJsonResponse; static void Join(); static void Leave(); @@ -199,11 +199,12 @@ namespace Twitch _twitchState = TWITCH_STATE_JOINING; _twitchIdle = false; - http_json_request request; + http_request_t request; request.url = url; request.method = HTTP_METHOD_GET; request.body = nullptr; - http_request_json_async(&request, [](http_json_response *jsonResponse) -> void + request.type = HTTP_DATA_JSON; + http_request_async(&request, [](http_response_t *jsonResponse) -> void { if (jsonResponse == nullptr) { @@ -222,7 +223,7 @@ namespace Twitch _twitchState = TWITCH_STATE_LEFT; } - http_request_json_dispose(jsonResponse); + http_request_dispose(jsonResponse); _twitchLastPulseTick = 0; console_writeline("Connected to twitch channel."); @@ -238,7 +239,7 @@ namespace Twitch { if (_twitchJsonResponse != nullptr) { - http_request_json_dispose(_twitchJsonResponse); + http_request_dispose(_twitchJsonResponse); _twitchJsonResponse = nullptr; } @@ -275,11 +276,12 @@ namespace Twitch _twitchState = TWITCH_STATE_WAITING; _twitchIdle = false; - http_json_request request; + http_request_t request; request.url = url; request.method = HTTP_METHOD_GET; request.body = NULL; - http_request_json_async(&request, [](http_json_response * jsonResponse) -> void + request.type = HTTP_DATA_JSON; + http_request_async(&request, [](http_response_t * jsonResponse) -> void { if (jsonResponse == nullptr) { @@ -305,11 +307,12 @@ namespace Twitch _twitchState = TWITCH_STATE_WAITING; _twitchIdle = false; - http_json_request request; + http_request_t request; request.url = url; request.method = HTTP_METHOD_GET; request.body = nullptr; - http_request_json_async(&request, [](http_json_response * jsonResponse) -> void + request.type = HTTP_DATA_JSON; + http_request_async(&request, [](http_response_t * jsonResponse) -> void { if (jsonResponse == nullptr) { @@ -326,7 +329,7 @@ namespace Twitch static void ParseFollowers() { - http_json_response *jsonResponse = _twitchJsonResponse; + http_response_t *jsonResponse = _twitchJsonResponse; if (json_is_array(jsonResponse->root)) { std::vector members; @@ -349,7 +352,7 @@ namespace Twitch ManageGuestNames(members); } - http_request_json_dispose(_twitchJsonResponse); + http_request_dispose(_twitchJsonResponse); _twitchJsonResponse = NULL; _twitchState = TWITCH_STATE_JOINED; @@ -358,7 +361,7 @@ namespace Twitch static void ParseMessages() { - http_json_response * jsonResponse = _twitchJsonResponse; + http_response_t * jsonResponse = _twitchJsonResponse; if (json_is_array(jsonResponse->root)) { size_t messageCount = json_array_size(jsonResponse->root); @@ -375,7 +378,7 @@ namespace Twitch } } - http_request_json_dispose(_twitchJsonResponse); + http_request_dispose(_twitchJsonResponse); _twitchJsonResponse = nullptr; _twitchState = TWITCH_STATE_JOINED; } diff --git a/src/openrct2.c b/src/openrct2.c new file mode 100644 index 0000000000..19d31f22c4 --- /dev/null +++ b/src/openrct2.c @@ -0,0 +1,694 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "audio/audio.h" +#include "audio/mixer.h" +#include "config.h" +#include "editor.h" +#include "game.h" +#include "hook.h" +#include "interface/chat.h" +#include "interface/themes.h" +#include "interface/window.h" +#include "interface/viewport.h" +#include "intro.h" +#include "localisation/localisation.h" +#include "network/http.h" +#include "network/network.h" +#include "object_list.h" +#include "openrct2.h" +#include "platform/crash.h" +#include "platform/platform.h" +#include "ride/ride.h" +#include "title.h" +#include "util/sawyercoding.h" +#include "util/util.h" +#include "version.h" +#include "world/mapgen.h" + +#if defined(__unix__) || defined(__MACOSX__) +#include +#include +#include +#include +#include +#include +#endif // defined(__unix__) || defined(__MACOSX__) + +int gExitCode; + +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) + static int fdData = -1; +#endif +#if defined(__unix__) && !defined(NO_RCT2) + static char * segments = (char *)(GOOD_PLACE_FOR_DATA_SEGMENT); +#endif + +int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE; +utf8 gOpenRCT2StartupActionPath[512] = { 0 }; +utf8 gExePath[MAX_PATH]; +utf8 gCustomUserDataPath[MAX_PATH] = { 0 }; +utf8 gCustomOpenrctDataPath[MAX_PATH] = { 0 }; +utf8 gCustomRCT2DataPath[MAX_PATH] = { 0 }; +utf8 gCustomPassword[MAX_PATH] = { 0 }; + +// This should probably be changed later and allow a custom selection of things to initialise like SDL_INIT +bool gOpenRCT2Headless = false; + +bool gOpenRCT2ShowChangelog; +bool gOpenRCT2SilentBreakpad; + +#ifndef DISABLE_NETWORK +// OpenSSL's message digest context used for calculating sprite checksums +EVP_MD_CTX *gHashCTX = NULL; +#endif // DISABLE_NETWORK + +/** If set, will end the OpenRCT2 game loop. Intentially private to this module so that the flag can not be set back to 0. */ +int _finished; + +// Used for object movement tweening +static rct_xyz16 _spritelocations1[MAX_SPRITES], _spritelocations2[MAX_SPRITES]; + +static void openrct2_loop(); +static void openrct2_setup_rct2_hooks(); + +void openrct2_write_full_version_info(utf8 *buffer, size_t bufferSize) +{ + utf8 *ch = buffer; + + // Name and version + safe_strcpy(ch, OPENRCT2_NAME ", v" OPENRCT2_VERSION, bufferSize - (ch - buffer)); + ch = strchr(ch, '\0'); + + // Build information + if (!str_is_null_or_empty(gGitBranch)) { + snprintf(ch, bufferSize - (ch - buffer), "-%s", gGitBranch); + ch = strchr(ch, '\0'); + } + if (!str_is_null_or_empty(gCommitSha1Short)) { + snprintf(ch, bufferSize - (ch - buffer), " build %s", gCommitSha1Short); + ch = strchr(ch, '\0'); + } + if (!str_is_null_or_empty(gBuildServer)) { + snprintf(ch, bufferSize - (ch - buffer), " provided by %s", gBuildServer); + ch = strchr(ch, '\0'); + } + +#if DEBUG + snprintf(ch, bufferSize - (ch - buffer), " (DEBUG)"); +#endif +} + +static void openrct2_copy_files_over(const utf8 *originalDirectory, const utf8 *newDirectory, const utf8 *extension) +{ + utf8 *ch, filter[MAX_PATH], oldPath[MAX_PATH], newPath[MAX_PATH]; + int fileEnumHandle; + file_info fileInfo; + + if (!platform_ensure_directory_exists(newDirectory)) { + log_error("Could not create directory %s.", newDirectory); + return; + } + + // Create filter path + safe_strcpy(filter, originalDirectory, sizeof(filter)); + ch = strchr(filter, '*'); + if (ch != NULL) + *ch = 0; + safe_strcat_path(filter, "*", sizeof(filter)); + path_append_extension(filter, extension, sizeof(filter)); + + fileEnumHandle = platform_enumerate_files_begin(filter); + while (platform_enumerate_files_next(fileEnumHandle, &fileInfo)) { + safe_strcpy(newPath, newDirectory, sizeof(newPath)); + safe_strcat_path(newPath, fileInfo.path, sizeof(newPath)); + + safe_strcpy(oldPath, originalDirectory, sizeof(oldPath)); + ch = strchr(oldPath, '*'); + if (ch != NULL) + *ch = 0; + safe_strcat_path(oldPath, fileInfo.path, sizeof(oldPath)); + + if (!platform_file_exists(newPath)) + platform_file_copy(oldPath, newPath, false); + } + platform_enumerate_files_end(fileEnumHandle); + + fileEnumHandle = platform_enumerate_directories_begin(originalDirectory); + while (platform_enumerate_directories_next(fileEnumHandle, filter)) { + safe_strcpy(newPath, newDirectory, sizeof(newPath)); + safe_strcat_path(newPath, filter, sizeof(newPath)); + + safe_strcpy(oldPath, originalDirectory, MAX_PATH); + ch = strchr(oldPath, '*'); + if (ch != NULL) + *ch = 0; + safe_strcat_path(oldPath, filter, sizeof(oldPath)); + + if (!platform_ensure_directory_exists(newPath)) { + log_error("Could not create directory %s.", newPath); + return; + } + openrct2_copy_files_over(oldPath, newPath, extension); + } + platform_enumerate_directories_end(fileEnumHandle); +} + +static void openrct2_set_exe_path() +{ + platform_get_exe_path(gExePath, sizeof(gExePath)); + log_verbose("Setting exe path to %s", gExePath); +} + +/** + * Copy saved games and landscapes to user directory + */ +static void openrct2_copy_original_user_files_over() +{ + utf8 path[MAX_PATH]; + + platform_get_user_directory(path, "save", sizeof(path)); + openrct2_copy_files_over((utf8*)gRCT2AddressSavedGamesPath, path, ".sv6"); + + platform_get_user_directory(path, "landscape", sizeof(path)); + openrct2_copy_files_over((utf8*)gRCT2AddressLandscapesPath, path, ".sc6"); +} + +bool openrct2_initialise() +{ + utf8 userPath[MAX_PATH]; + +#ifndef DISABLE_NETWORK + gHashCTX = EVP_MD_CTX_create(); + assert(gHashCTX != NULL); +#endif // DISABLE_NETWORK + + platform_resolve_openrct_data_path(); + platform_resolve_user_data_path(); + platform_get_user_directory(userPath, NULL, sizeof(userPath)); + if (!platform_ensure_directory_exists(userPath)) { + log_fatal("Could not create user directory (do you have write access to your documents folder?)"); + return false; + } + + crash_init(); + + if (!openrct2_setup_rct2_segment()) { + log_fatal("Unable to load RCT2 data sector"); + return false; + } + + openrct2_set_exe_path(); + + config_set_defaults(); + if (!config_open_default()) { + if (!config_find_or_browse_install_directory()) { + gConfigGeneral.last_run_version = strndup(OPENRCT2_VERSION, strlen(OPENRCT2_VERSION)); + config_save_default(); + utf8 path[MAX_PATH]; + config_get_default_path(path, sizeof(path)); + log_fatal("An RCT2 install directory must be specified! Please edit \"game_path\" in %s.", path); + return false; + } + } + + gOpenRCT2ShowChangelog = true; + if (gConfigGeneral.last_run_version != NULL && (strcmp(gConfigGeneral.last_run_version, OPENRCT2_VERSION) == 0)) + gOpenRCT2ShowChangelog = false; + gConfigGeneral.last_run_version = strndup(OPENRCT2_VERSION, strlen(OPENRCT2_VERSION)); + config_save_default(); + + // TODO add configuration option to allow multiple instances + // if (!gOpenRCT2Headless && !platform_lock_single_instance()) { + // log_fatal("OpenRCT2 is already running."); + // return false; + // } + + if (!rct2_init_directories()) { + return false; + } + if (!rct2_startup_checks()) { + return false; + } + + if (!gOpenRCT2Headless) { + audio_init(); + audio_populate_devices(); + } + + if (!language_open(gConfigGeneral.language)) { + log_error("Failed to open configured language..."); + + if (!language_open(LANGUAGE_ENGLISH_UK)) { + log_fatal("Failed to open fallback language..."); + return false; + } + } + http_init(); + + theme_manager_initialise(); + title_sequences_set_default(); + title_sequences_load_presets(); + + openrct2_setup_rct2_hooks(); + + if (!rct2_init()) + return false; + + chat_init(); + + openrct2_copy_original_user_files_over(); + return true; +} + +/** + * Launches the game, after command line arguments have been parsed and processed. + */ +void openrct2_launch() +{ + if (openrct2_initialise()) { + gIntroState = INTRO_STATE_NONE; + if((gOpenRCT2StartupAction == STARTUP_ACTION_TITLE) && gConfigGeneral.play_intro) + gOpenRCT2StartupAction = STARTUP_ACTION_INTRO; + + switch (gOpenRCT2StartupAction) { + case STARTUP_ACTION_INTRO: + gIntroState = INTRO_STATE_PUBLISHER_BEGIN; + title_load(); + break; + case STARTUP_ACTION_TITLE: + title_load(); + break; + case STARTUP_ACTION_OPEN: + assert(gOpenRCT2StartupActionPath != NULL); + +#ifndef DISABLE_HTTP + // A path that includes "://" is illegal with all common filesystems, so it is almost certainly a URL + // This way all cURL supported protocols, like http, ftp, scp and smb are automatically handled + if (strstr(gOpenRCT2StartupActionPath, "://") != NULL) { + // Download park and open it using its temporary filename + char tmpPath[L_tmpnam]; + + if (!http_download_park(gOpenRCT2StartupActionPath, tmpPath)) { + title_load(); + break; + } + + strcpy(gOpenRCT2StartupActionPath, tmpPath); + } +#endif + + if (!rct2_open_file(gOpenRCT2StartupActionPath)) { + fprintf(stderr, "Failed to load '%s'", gOpenRCT2StartupActionPath); + title_load(); + break; + } + + gScreenFlags = SCREEN_FLAGS_PLAYING; + +#ifndef DISABLE_NETWORK + if (gNetworkStart == NETWORK_MODE_SERVER) { + if (gNetworkStartPort == 0) { + gNetworkStartPort = gConfigNetwork.default_port; + } + + if (str_is_null_or_empty(gCustomPassword)) { + network_set_password(gConfigNetwork.default_password); + } + else { + network_set_password(gCustomPassword); + } + network_begin_server(gNetworkStartPort); + } +#endif // DISABLE_NETWORK + break; + case STARTUP_ACTION_EDIT: + if (strlen(gOpenRCT2StartupActionPath) == 0) { + editor_load(); + } else { + if (!editor_load_landscape(gOpenRCT2StartupActionPath)) { + title_load(); + } + } + break; + } + +#ifndef DISABLE_NETWORK + if (gNetworkStart == NETWORK_MODE_CLIENT) { + if (gNetworkStartPort == 0) { + gNetworkStartPort = gConfigNetwork.default_port; + } + + network_begin_client(gNetworkStartHost, gNetworkStartPort); + } +#endif // DISABLE_NETWORK + + openrct2_loop(); + } + openrct2_dispose(); + + // HACK Some threads are still running which causes the game to not terminate. Investigation required! + exit(gExitCode); +} + +void openrct2_dispose() +{ + network_close(); + http_dispose(); + language_close_all(); + rct2_dispose(); + config_release(); +#ifndef DISABLE_NETWORK + EVP_MD_CTX_destroy(gHashCTX); +#endif // DISABLE_NETWORK +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) + munmap(segments, 12079104); + close(fdData); +#endif + platform_free(); +} + +/** + * Determines whether its worth tweening a sprite or not when frame smoothing is on. + */ +static bool sprite_should_tween(rct_sprite *sprite) +{ + switch (sprite->unknown.linked_list_type_offset >> 1) { + case SPRITE_LIST_VEHICLE: + case SPRITE_LIST_PEEP: + case SPRITE_LIST_UNKNOWN: + return true; + } + return false; +} + +/** + * Run the main game loop until the finished flag is set at 40fps (25ms interval). + */ +static void openrct2_loop() +{ + uint32 currentTick, ticksElapsed, lastTick = 0; + static uint32 uncapTick = 0; + static int fps = 0; + static uint32 secondTick = 0; + + log_verbose("begin openrct2 loop"); + + _finished = 0; + do { + bool is_minimised = (SDL_GetWindowFlags(gWindow) & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_HIDDEN)) != 0; + if (gConfigGeneral.uncap_fps && gGameSpeed <= 4 && !gOpenRCT2Headless && !is_minimised) { + currentTick = SDL_GetTicks(); + if (uncapTick == 0) { + // Reset sprite locations + uncapTick = SDL_GetTicks(); + openrct2_reset_object_tween_locations(); + } + + // Limit number of updates per loop (any long pauses or debugging can make this update for a very long time) + if (currentTick - uncapTick > 25 * 60) { + uncapTick = currentTick - 25 - 1; + } + + platform_process_messages(); + + while (uncapTick <= currentTick && currentTick - uncapTick > 25) { + // Get the original position of each sprite + store_sprite_locations(_spritelocations1); + + // Update the game so the sprite positions update + rct2_update(); + + // Get the next position of each sprite + store_sprite_locations(_spritelocations2); + + uncapTick += 25; + } + + // Tween the position of each sprite from the last position to the new position based on the time between the last + // tick and the next tick. + float nudge = 1 - ((float)(currentTick - uncapTick) / 25); + for (uint16 i = 0; i < MAX_SPRITES; i++) { + if (!sprite_should_tween(get_sprite(i))) + continue; + + sprite_set_coordinates( + _spritelocations2[i].x + (sint16)((_spritelocations1[i].x - _spritelocations2[i].x) * nudge), + _spritelocations2[i].y + (sint16)((_spritelocations1[i].y - _spritelocations2[i].y) * nudge), + _spritelocations2[i].z + (sint16)((_spritelocations1[i].z - _spritelocations2[i].z) * nudge), + get_sprite(i) + ); + invalidate_sprite_2(get_sprite(i)); + } + + platform_draw(); + + fps++; + if (SDL_GetTicks() - secondTick >= 1000) { + fps = 0; + secondTick = SDL_GetTicks(); + } + + // Restore the real positions of the sprites so they aren't left at the mid-tween positions + for (uint16 i = 0; i < MAX_SPRITES; i++) { + if (!sprite_should_tween(get_sprite(i))) + continue; + + invalidate_sprite_2(get_sprite(i)); + sprite_set_coordinates(_spritelocations2[i].x, _spritelocations2[i].y, _spritelocations2[i].z, get_sprite(i)); + } + } else { + uncapTick = 0; + currentTick = SDL_GetTicks(); + ticksElapsed = currentTick - lastTick; + if (ticksElapsed < 25) { + SDL_Delay(25 - ticksElapsed); + lastTick += 25; + } else { + lastTick = currentTick; + } + + platform_process_messages(); + + rct2_update(); + + if (!is_minimised) { + platform_draw(); + } + } + } while (!_finished); +} + +/** + * Causes the OpenRCT2 game loop to finish. + */ +void openrct2_finish() +{ + _finished = 1; +} + +void openrct2_reset_object_tween_locations() +{ + for (uint16 i = 0; i < MAX_SPRITES; i++) { + _spritelocations1[i].x = _spritelocations2[i].x = get_sprite(i)->unknown.x; + _spritelocations1[i].y = _spritelocations2[i].y = get_sprite(i)->unknown.y; + _spritelocations1[i].z = _spritelocations2[i].z = get_sprite(i)->unknown.z; + } +} + +static void openrct2_get_segment_data_path(char * buffer, size_t bufferSize) +{ + platform_get_exe_path(buffer, bufferSize); + safe_strcat_path(buffer, "openrct2_data", bufferSize); +} + +/** + * Loads RCT2's data model and remaps the addresses. + * @returns true if the data integrity check succeeded, otherwise false. + */ +bool openrct2_setup_rct2_segment() +{ + // OpenRCT2 on Linux and macOS is wired to have the original Windows PE sections loaded + // necessary. Windows does not need to do this as OpenRCT2 runs as a DLL loaded from the Windows PE. + int len = 0x01429000 - 0x8a4000; // 0xB85000, 12079104 bytes or around 11.5MB + int err = 0; + // in some configurations err and len may be unused + UNUSED(err); + UNUSED(len); +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) && !defined(NO_RCT2) + #define RDATA_OFFSET 0x004A4000 + #define DATASEG_OFFSET 0x005E2000 + + // Using PE-bear I was able to figure out all the needed addresses to be filled. + // There are three sections to be loaded: .rdata, .data and .text, plus another + // one to be mapped: DATASEG. + // Out of the three, two can simply be mmapped into memory, while the third one, + // .data has a virtual size which is much completely different to its file size + // (even when taking page-alignment into consideration) + // + // The sections are as follows (dump from gdb) + // [0] 0x401000->0x6f7000 at 0x00001000: .text ALLOC LOAD READONLY CODE HAS_CONTENTS + // [1] 0x6f7000->0x8a325d at 0x002f7000: CODESEG ALLOC LOAD READONLY CODE HAS_CONTENTS + // [2] 0x8a4000->0x9a5894 at 0x004a4000: .rdata ALLOC LOAD DATA HAS_CONTENTS + // [3] 0x9a6000->0x9e2000 at 0x005a6000: .data ALLOC LOAD DATA HAS_CONTENTS + // [4] 0x1428000->0x14282bc at 0x005e2000: DATASEG ALLOC LOAD DATA HAS_CONTENTS + // [5] 0x1429000->0x1452000 at 0x005e3000: .cms_t ALLOC LOAD READONLY CODE HAS_CONTENTS + // [6] 0x1452000->0x14aaf3e at 0x0060c000: .cms_d ALLOC LOAD DATA HAS_CONTENTS + // [7] 0x14ab000->0x14ac58a at 0x00665000: .idata ALLOC LOAD READONLY DATA HAS_CONTENTS + // [8] 0x14ad000->0x14b512f at 0x00667000: .rsrc ALLOC LOAD DATA HAS_CONTENTS + // + // .data section, however, has virtual size of 0xA81C3C, and so + // 0x9a6000 + 0xA81C3C = 0x1427C3C, which after alignment to page size becomes + // 0x1428000, which can be seen as next section, DATASEG + // + // The data is now loaded into memory with a linker script, which proves to + // be more reliable, as mallocs that happen before we reach segment setup + // could have already taken the space we need. + + // TODO: UGLY, UGLY HACK! + //off_t file_size = 6750208; + + utf8 segmentDataPath[MAX_PATH]; + openrct2_get_segment_data_path(segmentDataPath, sizeof(segmentDataPath)); + fdData = open(segmentDataPath, O_RDONLY); + if (fdData < 0) + { + log_fatal("failed to load openrct2_data"); + exit(1); + } + log_warning("%p", GOOD_PLACE_FOR_DATA_SEGMENT); + segments = mmap((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE, fdData, 0); + log_warning("%p", segments); + if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { + perror("mmap"); + return false; + } +#endif // defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) + +#if defined(__unix__) && !defined(NO_RCT2) + int pageSize = getpagesize(); + int numPages = (len + pageSize - 1) / pageSize; + unsigned char *dummy = malloc(numPages); + + err = mincore((void *)segments, len, dummy); + bool pagesMissing = false; + if (err != 0) + { + err = errno; +#ifdef __LINUX__ + // On Linux ENOMEM means all requested range is unmapped + if (err != ENOMEM) + { + pagesMissing = true; + perror("mincore"); + } +#else + pagesMissing = true; + perror("mincore"); +#endif // __LINUX__ + } else { + for (int i = 0; i < numPages; i++) + { + if (dummy[i] != 1) + { + pagesMissing = true; + void *start = (void *)segments + i * pageSize; + void *end = (void *)segments + (i + 1) * pageSize - 1; + log_warning("required page %p - %p is not in memory!", start, end); + } + } + } + free(dummy); + if (pagesMissing) + { + log_error("At least one of required pages was not found in memory. This can cause segfaults later on."); + } +#if !defined(USE_MMAP) + // section: text + err = mprotect((void *)0x401000, 0x8a4000 - 0x401000, PROT_READ | PROT_EXEC | PROT_WRITE); + if (err != 0) + { + perror("mprotect"); + } +#endif // !defined(USE_MMAP) + // section: rw data + err = mprotect((void *)segments, 0x01429000 - 0x8a4000, PROT_READ | PROT_WRITE); + if (err != 0) + { + perror("mprotect"); + } +#endif // defined(__unix__) + +#if defined(USE_MMAP) && defined(__WINDOWS__) + segments = VirtualAlloc((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { + log_error("VirtualAlloc, segments = %p, GetLastError = 0x%x", segments, GetLastError()); + return false; + } + + utf8 segmentDataPath[MAX_PATH]; + openrct2_get_segment_data_path(segmentDataPath, sizeof(segmentDataPath)); + SDL_RWops * rw = SDL_RWFromFile(segmentDataPath, "rb"); + if (rw == NULL) + { + log_error("failed to load file"); + return false; + } + if (SDL_RWread(rw, segments, len, 1) != 1) { + log_error("Unable to read chunk header!"); + return false; + } + SDL_RWclose(rw); +#endif // defined(USE_MMAP) && defined(__WINDOWS__) + +#if !defined(NO_RCT2) && defined(USE_MMAP) + // Check that the expected data is at various addresses. + // Start at 0x9a6000, which is start of .data, to skip the region containing addresses to DLL + // calls, which can be changed by windows/wine loader. + const uint32 c1 = sawyercoding_calculate_checksum((const uint8*)(segments + (uintptr_t)(0x009A6000 - 0x8a4000)), 0x009E0000 - 0x009A6000); + const uint32 c2 = sawyercoding_calculate_checksum((const uint8*)(segments + (uintptr_t)(0x01428000 - 0x8a4000)), 0x014282BC - 0x01428000); + const uint32 exp_c1 = 10114815; + const uint32 exp_c2 = 23564; + if (c1 != exp_c1 || c2 != exp_c2) { + log_warning("c1 = %u, expected %u, match %d", c1, exp_c1, c1 == exp_c1); + log_warning("c2 = %u, expected %u, match %d", c2, exp_c2, c2 == exp_c2); + return false; + } +#endif + return true; +} + +/** + * Setup hooks to allow RCT2 to call OpenRCT2 functions instead. + */ +static void openrct2_setup_rct2_hooks() +{ + // None for now +} + +#if defined(_MSC_VER) && (_MSC_VER >= 1900) +/** + * Temporary fix for libraries not compiled with VS2015 + */ +FILE **__iob_func() +{ + static FILE* streams[3]; + streams[0] = stdin; + streams[1] = stdout; + streams[2] = stderr; + return streams; +} +#endif diff --git a/src/windows/server_list.c b/src/windows/server_list.c index b9a33b51e4..ae120eae94 100644 --- a/src/windows/server_list.c +++ b/src/windows/server_list.c @@ -141,7 +141,7 @@ static void sort_servers(); static void join_server(char *address); static void fetch_servers(); #ifndef DISABLE_HTTP -static void fetch_servers_callback(http_json_response* response); +static void fetch_servers_callback(http_response_t* response); #endif void window_server_list_open() @@ -775,16 +775,17 @@ static void fetch_servers() sort_servers(); SDL_UnlockMutex(_mutex); - http_json_request request; + http_request_t request; request.url = masterServerUrl; request.method = HTTP_METHOD_GET; request.body = NULL; - http_request_json_async(&request, fetch_servers_callback); + request.type = HTTP_DATA_JSON; + http_request_async(&request, fetch_servers_callback); #endif } #ifndef DISABLE_HTTP -static void fetch_servers_callback(http_json_response* response) +static void fetch_servers_callback(http_response_t* response) { if (response == NULL) { log_warning("Unable to connect to master server"); @@ -793,21 +794,21 @@ static void fetch_servers_callback(http_json_response* response) json_t *jsonStatus = json_object_get(response->root, "status"); if (!json_is_number(jsonStatus)) { - http_request_json_dispose(response); + http_request_dispose(response); log_warning("Invalid response from master server"); return; } int status = (int)json_integer_value(jsonStatus); if (status != 200) { - http_request_json_dispose(response); + http_request_dispose(response); log_warning("Master server failed to return servers"); return; } json_t *jsonServers = json_object_get(response->root, "servers"); if (!json_is_array(jsonServers)) { - http_request_json_dispose(response); + http_request_dispose(response); log_warning("Invalid response from master server"); return; } @@ -852,7 +853,7 @@ static void fetch_servers_callback(http_json_response* response) newserver->maxplayers = (uint8)json_integer_value(maxPlayers); SDL_UnlockMutex(_mutex); } - http_request_json_dispose(response); + http_request_dispose(response); sort_servers(); _numPlayersOnline = get_total_player_count(); From b0b3f9ac3cb1f5ef8a607f71d0c28cadb9e6e19a Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 28 Dec 2016 19:11:25 +0000 Subject: [PATCH 2/2] Use TryClassifyFile to auto load downloaded park --- src/OpenRCT2.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/src/OpenRCT2.cpp b/src/OpenRCT2.cpp index 3da18b0e33..8fa37a8943 100644 --- a/src/OpenRCT2.cpp +++ b/src/OpenRCT2.cpp @@ -18,6 +18,7 @@ #include "core/Console.hpp" #include "core/Guard.hpp" #include "core/String.hpp" +#include "FileClassifier.h" #include "network/network.h" #include "object/ObjectRepository.h" #include "OpenRCT2.h" @@ -41,6 +42,7 @@ extern "C" #include "network/http.h" #include "object_list.h" #include "platform/platform.h" + #include "rct1.h" #include "rct2/interop.h" #include "version.h" } @@ -88,6 +90,8 @@ namespace OpenRCT2 static void RunGameLoop(); static void RunFixedFrame(); static void RunVariableFrame(); + + static bool OpenParkAutoDetectFormat(const utf8 * path); } extern "C" @@ -216,9 +220,32 @@ extern "C" title_load(); break; case STARTUP_ACTION_OPEN: - if (!rct2_open_file(gOpenRCT2StartupActionPath)) + { + bool parkLoaded = false; + // A path that includes "://" is illegal with all common filesystems, so it is almost certainly a URL + // This way all cURL supported protocols, like http, ftp, scp and smb are automatically handled + if (strstr(gOpenRCT2StartupActionPath, "://") != nullptr) { - fprintf(stderr, "Failed to load '%s'", gOpenRCT2StartupActionPath); +#ifndef DISABLE_HTTP + // Download park and open it using its temporary filename + char tmpPath[MAX_PATH]; + if (!http_download_park(gOpenRCT2StartupActionPath, tmpPath)) + { + title_load(); + break; + } + + parkLoaded = OpenRCT2::OpenParkAutoDetectFormat(tmpPath); +#endif + } + else + { + parkLoaded = rct2_open_file(gOpenRCT2StartupActionPath); + } + + if (!parkLoaded) + { + Console::Error::WriteLine("Failed to load '%s'", gOpenRCT2StartupActionPath); title_load(); break; } @@ -245,6 +272,7 @@ extern "C" } #endif // DISABLE_NETWORK break; + } case STARTUP_ACTION_EDIT: if (String::SizeOf(gOpenRCT2StartupActionPath) == 0) { @@ -478,4 +506,62 @@ namespace OpenRCT2 sprite_position_tween_restore(); } + + static bool OpenParkAutoDetectFormat(const utf8 * path) + { + ClassifiedFile info; + if (TryClassifyFile(path, &info)) + { + if (info.Type == FILE_TYPE::SAVED_GAME) + { + if (info.Version <= 2) + { + if (rct1_load_saved_game(path)) + { + game_load_init(); + return true; + } + } + else + { + if (game_load_save(path)) + { + gFirstTimeSave = 0; + return true; + } + } + Console::Error::WriteLine("Error loading saved game."); + } + else if (info.Type == FILE_TYPE::SCENARIO) + { + if (info.Version <= 2) + { + + if (rct1_load_scenario(path)) + { + scenario_begin(); + return true; + } + } + else + { + if (scenario_load_and_play_from_path(path)) + { + return true; + } + } + Console::Error::WriteLine("Error loading scenario."); + } + else + { + Console::Error::WriteLine("Invalid file type."); + Console::Error::WriteLine("Invalid file type."); + } + } + else + { + Console::Error::WriteLine("Unable to detect file type."); + } + return false; + } }