mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge branch 'Overv-download-saved-park' into develop
This commit is contained in:
commit
2892a7ad3f
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,8 +121,13 @@ static void PrintLaunchInformation();
|
|||
const CommandLineCommand CommandLine::RootCommands[]
|
||||
{
|
||||
// Main commands
|
||||
#ifndef DISABLE_HTTP
|
||||
DefineCommand("", "<uri>", StandardOptions, HandleNoCommand ),
|
||||
DefineCommand("edit", "<uri>", StandardOptions, HandleCommandEdit ),
|
||||
#else
|
||||
DefineCommand("", "<path>", StandardOptions, HandleNoCommand ),
|
||||
DefineCommand("edit", "<path>", StandardOptions, HandleCommandEdit ),
|
||||
#endif
|
||||
DefineCommand("intro", "", StandardOptions, HandleCommandIntro ),
|
||||
#ifndef DISABLE_NETWORK
|
||||
DefineCommand("host", "<uri>", 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
|
||||
|
|
|
@ -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<NetworkServerAdvertiser*>(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<NetworkServerAdvertiser*>(response->tag);
|
||||
advertiser->OnHeartbeatResponse(response->root);
|
||||
http_request_json_dispose(response);
|
||||
http_request_dispose(response);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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 <curl/curl.h>
|
||||
|
||||
|
@ -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<size_t>(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<size_t>(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
|
||||
|
|
|
@ -21,27 +21,48 @@
|
|||
#include <jansson.h>
|
||||
#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
|
||||
|
|
|
@ -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<AudienceMember> 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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue