1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-20 14:23:08 +01:00

Merge branch 'Overv-download-saved-park' into develop

This commit is contained in:
Ted John
2016-12-28 19:17:18 +00:00
7 changed files with 420 additions and 198 deletions

View File

@@ -18,6 +18,7 @@
#include "core/Console.hpp" #include "core/Console.hpp"
#include "core/Guard.hpp" #include "core/Guard.hpp"
#include "core/String.hpp" #include "core/String.hpp"
#include "FileClassifier.h"
#include "network/network.h" #include "network/network.h"
#include "object/ObjectRepository.h" #include "object/ObjectRepository.h"
#include "OpenRCT2.h" #include "OpenRCT2.h"
@@ -41,6 +42,7 @@ extern "C"
#include "network/http.h" #include "network/http.h"
#include "object_list.h" #include "object_list.h"
#include "platform/platform.h" #include "platform/platform.h"
#include "rct1.h"
#include "rct2/interop.h" #include "rct2/interop.h"
#include "version.h" #include "version.h"
} }
@@ -88,6 +90,8 @@ namespace OpenRCT2
static void RunGameLoop(); static void RunGameLoop();
static void RunFixedFrame(); static void RunFixedFrame();
static void RunVariableFrame(); static void RunVariableFrame();
static bool OpenParkAutoDetectFormat(const utf8 * path);
} }
extern "C" extern "C"
@@ -216,9 +220,32 @@ extern "C"
title_load(); title_load();
break; break;
case STARTUP_ACTION_OPEN: 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(); title_load();
break; break;
} }
@@ -245,6 +272,7 @@ extern "C"
} }
#endif // DISABLE_NETWORK #endif // DISABLE_NETWORK
break; break;
}
case STARTUP_ACTION_EDIT: case STARTUP_ACTION_EDIT:
if (String::SizeOf(gOpenRCT2StartupActionPath) == 0) if (String::SizeOf(gOpenRCT2StartupActionPath) == 0)
{ {
@@ -478,4 +506,62 @@ namespace OpenRCT2
sprite_position_tween_restore(); 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;
}
} }

View File

@@ -121,8 +121,13 @@ static void PrintLaunchInformation();
const CommandLineCommand CommandLine::RootCommands[] const CommandLineCommand CommandLine::RootCommands[]
{ {
// Main commands // Main commands
#ifndef DISABLE_HTTP
DefineCommand("", "<uri>", StandardOptions, HandleNoCommand ), DefineCommand("", "<uri>", StandardOptions, HandleNoCommand ),
DefineCommand("edit", "<uri>", StandardOptions, HandleCommandEdit ), DefineCommand("edit", "<uri>", StandardOptions, HandleCommandEdit ),
#else
DefineCommand("", "<path>", StandardOptions, HandleNoCommand ),
DefineCommand("edit", "<path>", StandardOptions, HandleCommandEdit ),
#endif
DefineCommand("intro", "", StandardOptions, HandleCommandIntro ), DefineCommand("intro", "", StandardOptions, HandleCommandIntro ),
#ifndef DISABLE_NETWORK #ifndef DISABLE_NETWORK
DefineCommand("host", "<uri>", StandardOptions, HandleCommandHost ), DefineCommand("host", "<uri>", StandardOptions, HandleCommandHost ),
@@ -148,7 +153,9 @@ const CommandLineExample CommandLine::RootExamples[]
{ "./my_park.sv6", "open a saved park" }, { "./my_park.sv6", "open a saved park" },
{ "./SnowyPark.sc6", "install and open a scenario" }, { "./SnowyPark.sc6", "install and open a scenario" },
{ "./ShuttleLoop.td6", "install a track" }, { "./ShuttleLoop.td6", "install a track" },
#ifndef DISABLE_HTTP
{ "https://openrct2.website/files/SnowyPark.sv6", "download and open a saved park" }, { "https://openrct2.website/files/SnowyPark.sv6", "download and open a saved park" },
#endif
#ifndef DISABLE_NETWORK #ifndef DISABLE_NETWORK
{ "host ./my_park.sv6 --port 11753 --headless", "run a headless server for a saved park" }, { "host ./my_park.sv6 --port 11753 --headless", "run a headless server for a saved park" },
#endif #endif

View File

@@ -100,7 +100,7 @@ private:
_lastAdvertiseTime = SDL_GetTicks(); _lastAdvertiseTime = SDL_GetTicks();
// Send the registration request // Send the registration request
http_json_request request; http_request_t request;
request.tag = this; request.tag = this;
request.url = GetMasterServerUrl(); request.url = GetMasterServerUrl();
request.method = HTTP_METHOD_POST; request.method = HTTP_METHOD_POST;
@@ -108,9 +108,10 @@ private:
json_t *body = json_object(); json_t *body = json_object();
json_object_set_new(body, "key", json_string(_key.c_str())); json_object_set_new(body, "key", json_string(_key.c_str()));
json_object_set_new(body, "port", json_integer(_port)); 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) if (response == nullptr)
{ {
@@ -120,7 +121,7 @@ private:
{ {
auto advertiser = static_cast<NetworkServerAdvertiser*>(response->tag); auto advertiser = static_cast<NetworkServerAdvertiser*>(response->tag);
advertiser->OnRegistrationResponse(response->root); advertiser->OnRegistrationResponse(response->root);
http_request_json_dispose(response); http_request_dispose(response);
} }
}); });
@@ -129,16 +130,17 @@ private:
void SendHeartbeat() void SendHeartbeat()
{ {
http_json_request request; http_request_t request;
request.tag = this; request.tag = this;
request.url = GetMasterServerUrl(); request.url = GetMasterServerUrl();
request.method = HTTP_METHOD_PUT; request.method = HTTP_METHOD_PUT;
json_t * jsonBody = GetHeartbeatJson(); json_t * jsonBody = GetHeartbeatJson();
request.body = jsonBody; request.root = jsonBody;
request.type = HTTP_DATA_JSON;
_lastHeartbeatTime = SDL_GetTicks(); _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) if (response == nullptr)
{ {
@@ -148,7 +150,7 @@ private:
{ {
auto advertiser = static_cast<NetworkServerAdvertiser*>(response->tag); auto advertiser = static_cast<NetworkServerAdvertiser*>(response->tag);
advertiser->OnHeartbeatResponse(response->root); advertiser->OnHeartbeatResponse(response->root);
http_request_json_dispose(response); http_request_dispose(response);
} }
}); });

View File

@@ -15,8 +15,8 @@
#pragma endregion #pragma endregion
extern "C" { extern "C" {
#include "http.h" #include "http.h"
#include "../platform/platform.h" #include "../platform/platform.h"
} }
#ifdef DISABLE_HTTP #ifdef DISABLE_HTTP
@@ -29,10 +29,11 @@ void http_dispose() { }
#include "../core/Math.hpp" #include "../core/Math.hpp"
#include "../core/Path.hpp" #include "../core/Path.hpp"
#include "../core/String.hpp" #include "../core/String.hpp"
#include "../core/Console.hpp"
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// cURL includes windows.h, but we don't need all of it. // cURL includes windows.h, but we don't need all of it.
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#include <curl/curl.h> #include <curl/curl.h>
@@ -40,189 +41,290 @@ void http_dispose() { }
#define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION #define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION
typedef struct read_buffer { typedef struct read_buffer {
char *ptr; char *ptr;
size_t length; size_t length;
size_t position; size_t position;
} read_buffer; } read_buffer;
typedef struct write_buffer { typedef struct write_buffer {
char *ptr; char *ptr;
size_t length; size_t length;
size_t capacity; size_t capacity;
} write_buffer; } write_buffer;
void http_init() void http_init()
{ {
curl_global_init(CURL_GLOBAL_DEFAULT); curl_global_init(CURL_GLOBAL_DEFAULT);
} }
void http_dispose() 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) 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 remainingBytes = readBuffer->length - readBuffer->position;
size_t readBytes = size * nmemb; size_t readBytes = size * nmemb;
if (readBytes > remainingBytes) { if (readBytes > remainingBytes) {
readBytes = remainingBytes; readBytes = remainingBytes;
} }
memcpy(ptr, readBuffer->ptr + readBuffer->position, readBytes); memcpy(ptr, readBuffer->ptr + readBuffer->position, readBytes);
readBuffer->position += readBytes; readBuffer->position += readBytes;
return readBytes; return readBytes;
} }
static size_t http_request_write_func(void *ptr, size_t size, size_t nmemb, void *userdata) 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; size_t newBytesLength = size * nmemb;
if (newBytesLength > 0) { if (newBytesLength > 0) {
size_t newCapacity = writeBuffer->capacity; size_t newCapacity = writeBuffer->capacity;
size_t newLength = writeBuffer->length + newBytesLength; size_t newLength = writeBuffer->length + newBytesLength;
while (newLength > newCapacity) { while (newLength > newCapacity) {
newCapacity = Math::Max<size_t>(4096, newCapacity * 2); 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) { return false;
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;
} }
writeBuffer.ptr = NULL; // Generate temporary filename that includes the original extension
writeBuffer.length = 0; if (tmpnam(tmpPath) == NULL) {
writeBuffer.capacity = 0; 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; const char *ext = http_get_extension_from_url(request.url, ".sv6");
headers = curl_slist_append(headers, "Accept: " MIME_TYPE_APPLICATION_JSON); strncat(tmpPath, ext, remainingBytes);
if (request->body != NULL) {
headers = curl_slist_append(headers, "Content-Type: " MIME_TYPE_APPLICATION_JSON);
char contentLengthHeaderValue[64]; // Store park in temporary file and load it (discard ending NUL in response body)
snprintf(contentLengthHeaderValue, sizeof(contentLengthHeaderValue), "Content-Length: %zu", readBuffer.length); FILE* tmpFile = fopen(tmpPath, "wb");
headers = curl_slist_append(headers, contentLengthHeaderValue);
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); fwrite(response->body, 1, response->size - 1, tmpFile);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); fclose(tmpFile);
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); http_request_dispose(response);
if (request->body != NULL) { return true;
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);
} }
#endif #endif

View File

@@ -21,27 +21,48 @@
#include <jansson.h> #include <jansson.h>
#include "../common.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; void *tag;
const char *method; const char *method;
const char *url; const char *url;
const json_t *body; http_data_type type;
} http_json_request; 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; void *tag;
int status_code; int status_code;
json_t *root; http_data_type type;
} http_json_response; size_t size;
union {
json_t *root;
char* body;
};
} http_response_t;
#define HTTP_METHOD_GET "GET" #define HTTP_METHOD_GET "GET"
#define HTTP_METHOD_POST "POST" #define HTTP_METHOD_POST "POST"
#define HTTP_METHOD_PUT "PUT" #define HTTP_METHOD_PUT "PUT"
#define HTTP_METHOD_DELETE "DELETE" #define HTTP_METHOD_DELETE "DELETE"
http_json_response *http_request_json(const http_json_request *request); http_response_t *http_request(const http_request_t *request);
void http_request_json_async(const http_json_request *request, void (*callback)(http_json_response*)); void http_request_async(const http_request_t *request, void (*callback)(http_response_t*));
void http_request_json_dispose(http_json_response *response); 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 #endif // DISABLE_HTTP
// These callbacks are defined anyway, but are dummy if HTTP is disabled // These callbacks are defined anyway, but are dummy if HTTP is disabled

View File

@@ -106,11 +106,11 @@ namespace Twitch
constexpr uint32 PulseTime = 10 * 1000; constexpr uint32 PulseTime = 10 * 1000;
constexpr const char * TwitchExtendedBaseUrl = "http://openrct.ursalabs.co/api/1/"; constexpr const char * TwitchExtendedBaseUrl = "http://openrct.ursalabs.co/api/1/";
static int _twitchState = TWITCH_STATE_LEFT; static int _twitchState = TWITCH_STATE_LEFT;
static bool _twitchIdle = true; static bool _twitchIdle = true;
static uint32 _twitchLastPulseTick = 0; static uint32 _twitchLastPulseTick = 0;
static int _twitchLastPulseOperation = 1; static int _twitchLastPulseOperation = 1;
static http_json_response * _twitchJsonResponse; static http_response_t * _twitchJsonResponse;
static void Join(); static void Join();
static void Leave(); static void Leave();
@@ -199,11 +199,12 @@ namespace Twitch
_twitchState = TWITCH_STATE_JOINING; _twitchState = TWITCH_STATE_JOINING;
_twitchIdle = false; _twitchIdle = false;
http_json_request request; http_request_t request;
request.url = url; request.url = url;
request.method = HTTP_METHOD_GET; request.method = HTTP_METHOD_GET;
request.body = nullptr; 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) if (jsonResponse == nullptr)
{ {
@@ -222,7 +223,7 @@ namespace Twitch
_twitchState = TWITCH_STATE_LEFT; _twitchState = TWITCH_STATE_LEFT;
} }
http_request_json_dispose(jsonResponse); http_request_dispose(jsonResponse);
_twitchLastPulseTick = 0; _twitchLastPulseTick = 0;
console_writeline("Connected to twitch channel."); console_writeline("Connected to twitch channel.");
@@ -238,7 +239,7 @@ namespace Twitch
{ {
if (_twitchJsonResponse != nullptr) if (_twitchJsonResponse != nullptr)
{ {
http_request_json_dispose(_twitchJsonResponse); http_request_dispose(_twitchJsonResponse);
_twitchJsonResponse = nullptr; _twitchJsonResponse = nullptr;
} }
@@ -275,11 +276,12 @@ namespace Twitch
_twitchState = TWITCH_STATE_WAITING; _twitchState = TWITCH_STATE_WAITING;
_twitchIdle = false; _twitchIdle = false;
http_json_request request; http_request_t request;
request.url = url; request.url = url;
request.method = HTTP_METHOD_GET; request.method = HTTP_METHOD_GET;
request.body = NULL; 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) if (jsonResponse == nullptr)
{ {
@@ -305,11 +307,12 @@ namespace Twitch
_twitchState = TWITCH_STATE_WAITING; _twitchState = TWITCH_STATE_WAITING;
_twitchIdle = false; _twitchIdle = false;
http_json_request request; http_request_t request;
request.url = url; request.url = url;
request.method = HTTP_METHOD_GET; request.method = HTTP_METHOD_GET;
request.body = nullptr; 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) if (jsonResponse == nullptr)
{ {
@@ -326,7 +329,7 @@ namespace Twitch
static void ParseFollowers() static void ParseFollowers()
{ {
http_json_response *jsonResponse = _twitchJsonResponse; http_response_t *jsonResponse = _twitchJsonResponse;
if (json_is_array(jsonResponse->root)) if (json_is_array(jsonResponse->root))
{ {
std::vector<AudienceMember> members; std::vector<AudienceMember> members;
@@ -349,7 +352,7 @@ namespace Twitch
ManageGuestNames(members); ManageGuestNames(members);
} }
http_request_json_dispose(_twitchJsonResponse); http_request_dispose(_twitchJsonResponse);
_twitchJsonResponse = NULL; _twitchJsonResponse = NULL;
_twitchState = TWITCH_STATE_JOINED; _twitchState = TWITCH_STATE_JOINED;
@@ -358,7 +361,7 @@ namespace Twitch
static void ParseMessages() static void ParseMessages()
{ {
http_json_response * jsonResponse = _twitchJsonResponse; http_response_t * jsonResponse = _twitchJsonResponse;
if (json_is_array(jsonResponse->root)) if (json_is_array(jsonResponse->root))
{ {
size_t messageCount = json_array_size(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; _twitchJsonResponse = nullptr;
_twitchState = TWITCH_STATE_JOINED; _twitchState = TWITCH_STATE_JOINED;
} }

View File

@@ -141,7 +141,7 @@ static void sort_servers();
static void join_server(char *address); static void join_server(char *address);
static void fetch_servers(); static void fetch_servers();
#ifndef DISABLE_HTTP #ifndef DISABLE_HTTP
static void fetch_servers_callback(http_json_response* response); static void fetch_servers_callback(http_response_t* response);
#endif #endif
void window_server_list_open() void window_server_list_open()
@@ -775,16 +775,17 @@ static void fetch_servers()
sort_servers(); sort_servers();
SDL_UnlockMutex(_mutex); SDL_UnlockMutex(_mutex);
http_json_request request; http_request_t request;
request.url = masterServerUrl; request.url = masterServerUrl;
request.method = HTTP_METHOD_GET; request.method = HTTP_METHOD_GET;
request.body = NULL; request.body = NULL;
http_request_json_async(&request, fetch_servers_callback); request.type = HTTP_DATA_JSON;
http_request_async(&request, fetch_servers_callback);
#endif #endif
} }
#ifndef DISABLE_HTTP #ifndef DISABLE_HTTP
static void fetch_servers_callback(http_json_response* response) static void fetch_servers_callback(http_response_t* response)
{ {
if (response == NULL) { if (response == NULL) {
log_warning("Unable to connect to master server"); 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"); json_t *jsonStatus = json_object_get(response->root, "status");
if (!json_is_number(jsonStatus)) { if (!json_is_number(jsonStatus)) {
http_request_json_dispose(response); http_request_dispose(response);
log_warning("Invalid response from master server"); log_warning("Invalid response from master server");
return; return;
} }
int status = (int)json_integer_value(jsonStatus); int status = (int)json_integer_value(jsonStatus);
if (status != 200) { if (status != 200) {
http_request_json_dispose(response); http_request_dispose(response);
log_warning("Master server failed to return servers"); log_warning("Master server failed to return servers");
return; return;
} }
json_t *jsonServers = json_object_get(response->root, "servers"); json_t *jsonServers = json_object_get(response->root, "servers");
if (!json_is_array(jsonServers)) { if (!json_is_array(jsonServers)) {
http_request_json_dispose(response); http_request_dispose(response);
log_warning("Invalid response from master server"); log_warning("Invalid response from master server");
return; return;
} }
@@ -852,7 +853,7 @@ static void fetch_servers_callback(http_json_response* response)
newserver->maxplayers = (uint8)json_integer_value(maxPlayers); newserver->maxplayers = (uint8)json_integer_value(maxPlayers);
SDL_UnlockMutex(_mutex); SDL_UnlockMutex(_mutex);
} }
http_request_json_dispose(response); http_request_dispose(response);
sort_servers(); sort_servers();
_numPlayersOnline = get_total_player_count(); _numPlayersOnline = get_total_player_count();