1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-02-01 11:15:13 +01:00

Merge branch 'develop' into Water-rides-breakdown-fix

This commit is contained in:
MarcelVos96
2025-12-01 20:14:44 +01:00
committed by GitHub
6 changed files with 136 additions and 96 deletions

View File

@@ -11,6 +11,7 @@
- Fix: [#25569] Placing park entrances in multiplayer does not show for other players, causing desyncs.
- Fix: [#25571] Potential crash due to drawing a Crooked House ride.
- Fix: [#25592] Log flume, river rapids, & splash boats can get control failure breakdown instead of brakes failure
- Fix: [#25588] When the master server becomes unreachable the server would not register again until a restart.
0.4.29 (2025-11-22)
------------------------------------------------------------------------

View File

@@ -236,7 +236,11 @@ namespace OpenRCT2::Http
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
throw;
Response response;
response.status = Status::Error;
response.error = e.what();
return response;
}
}
} // namespace OpenRCT2::Http

View File

@@ -84,81 +84,91 @@ namespace OpenRCT2::Http
Response Do(const Request& req)
{
CURL* curl = curl_easy_init();
std::shared_ptr<void> _(nullptr, [curl](...) { curl_easy_cleanup(curl); });
if (!curl)
throw std::runtime_error("Failed to initialize curl");
Response res;
WriteThis wt;
if (req.method == Method::POST || req.method == Method::PUT)
try
{
wt.readptr = req.body.c_str();
wt.sizeleft = req.body.size();
CURL* curl = curl_easy_init();
std::shared_ptr<void> _(nullptr, [curl](...) { curl_easy_cleanup(curl); });
curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadCallback);
curl_easy_setopt(curl, CURLOPT_READDATA, &wt);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(wt.sizeleft));
}
if (!curl)
throw std::runtime_error("Failed to initialize curl");
if (req.forceIPv4)
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
Response res;
WriteThis wt;
if (req.method == Method::POST)
curl_easy_setopt(curl, CURLOPT_POST, 1L);
if (req.method == Method::PUT)
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(&res));
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast<void*>(&res));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_USERAGENT, kOpenRCT2UserAgent);
curl_slist* chunk = nullptr;
std::shared_ptr<void> __(nullptr, [chunk](...) { curl_slist_free_all(chunk); });
for (auto header : req.header)
{
std::string hs = header.first + ": " + header.second;
chunk = curl_slist_append(chunk, hs.c_str());
}
if (req.header.size() != 0)
{
if (chunk == nullptr)
if (req.method == Method::POST || req.method == Method::PUT)
{
throw std::runtime_error("Failed to set headers");
wt.readptr = req.body.c_str();
wt.sizeleft = req.body.size();
curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadCallback);
curl_easy_setopt(curl, CURLOPT_READDATA, &wt);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(wt.sizeleft));
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
}
CURLcode curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK)
if (req.forceIPv4)
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
if (req.method == Method::POST)
curl_easy_setopt(curl, CURLOPT_POST, 1L);
if (req.method == Method::PUT)
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(&res));
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast<void*>(&res));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_USERAGENT, kOpenRCT2UserAgent);
curl_slist* chunk = nullptr;
std::shared_ptr<void> __(nullptr, [chunk](...) { curl_slist_free_all(chunk); });
for (auto header : req.header)
{
std::string hs = header.first + ": " + header.second;
chunk = curl_slist_append(chunk, hs.c_str());
}
if (req.header.size() != 0)
{
if (chunk == nullptr)
{
throw std::runtime_error("Failed to set headers");
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
}
CURLcode curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK)
{
using namespace std::literals;
throw std::runtime_error(
"Failed to perform request. curl error code: "s + std::to_string(curl_code) + ": "
+ curl_easy_strerror(curl_code));
}
// gets freed by curl_easy_cleanup
char* content_type;
long code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
res.status = static_cast<Status>(code);
if (content_type != nullptr)
{
res.content_type = std::string(content_type);
}
return res;
}
catch (const std::exception& e)
{
using namespace std::literals;
throw std::runtime_error(
"Failed to perform request. curl error code: "s + std::to_string(curl_code) + ": "
+ curl_easy_strerror(curl_code));
Response response;
response.status = Status::Error;
response.error = e.what();
return response;
}
// gets freed by curl_easy_cleanup
char* content_type;
long code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
res.status = static_cast<Status>(code);
if (content_type != nullptr)
{
res.content_type = std::string(content_type);
}
return res;
}
} // namespace OpenRCT2::Http

View File

@@ -12,15 +12,16 @@
#ifndef DISABLE_HTTP
#include <functional>
#include <future>
#include <map>
#include <string>
#include <thread>
namespace OpenRCT2::Http
{
enum class Status
{
Invalid = 0,
Error = 1,
Ok = 200,
NotFound = 404
};
@@ -52,9 +53,9 @@ namespace OpenRCT2::Http
Response Do(const Request& req);
inline void DoAsync(const Request& req, std::function<void(Response& res)> fn)
inline auto DoAsync(const Request& req, std::function<void(Response& res)> fn)
{
auto thread = std::thread([=]() {
return std::async(std::launch::async, [=]() {
Response res{};
try
{
@@ -67,7 +68,6 @@ namespace OpenRCT2::Http
}
fn(res);
});
thread.detach();
}
} // namespace OpenRCT2::Http

View File

@@ -46,7 +46,7 @@ namespace OpenRCT2::Network
#ifndef DISABLE_HTTP
using namespace std::chrono_literals;
constexpr int32_t kMasterServerRegisterTime = std::chrono::milliseconds(2min).count();
constexpr int32_t kMasterServerRegisterTime = std::chrono::milliseconds(30s).count();
constexpr int32_t kMasterServerHeartbeatTime = std::chrono::milliseconds(1min).count();
#endif
@@ -56,6 +56,7 @@ namespace OpenRCT2::Network
uint16_t _port;
std::unique_ptr<IUdpSocket> _lanListener;
std::shared_future<void> _currentRequest;
uint32_t _lastListenTime{};
AdvertiseStatus _status = AdvertiseStatus::unregistered;
@@ -84,6 +85,17 @@ namespace OpenRCT2::Network
#endif
}
~NetworkServerAdvertiser() final
{
_lanListener->Close();
auto currentRequest = _currentRequest;
if (currentRequest.valid())
{
currentRequest.wait();
}
}
AdvertiseStatus GetStatus() const override
{
return _status;
@@ -149,13 +161,13 @@ namespace OpenRCT2::Network
case AdvertiseStatus::unregistered:
if (_lastAdvertiseTime == 0 || Platform::GetTicks() > _lastAdvertiseTime + kMasterServerRegisterTime)
{
if (_lastAdvertiseTime == 0)
{
Console::WriteLine("Registering server on master server");
}
Console::WriteLine("Registering server on master server...");
SendRegistration(_forceIPv4);
}
break;
case AdvertiseStatus::registering:
// Waiting for registration response.
break;
case AdvertiseStatus::registered:
if (Platform::GetTicks() > _lastHeartbeatTime + kMasterServerHeartbeatTime)
{
@@ -171,6 +183,7 @@ namespace OpenRCT2::Network
void SendRegistration(bool forceIPv4)
{
_lastAdvertiseTime = Platform::GetTicks();
_status = AdvertiseStatus::registering;
// Send the registration request
Http::Request request;
@@ -191,17 +204,21 @@ namespace OpenRCT2::Network
request.body = body.dump();
request.header["Content-Type"] = "application/json";
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::Ok)
{
Console::Error::WriteLine("Unable to connect to master server");
return;
}
_currentRequest = Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::Ok)
{
Console::Error::WriteLine(
"Unable to connect to master server, retrying in %d seconds",
kMasterServerRegisterTime / 1000);
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnRegistrationResponse(root);
});
_status = AdvertiseStatus::unregistered;
return;
}
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnRegistrationResponse(root);
}).share();
}
void SendHeartbeat()
@@ -215,17 +232,24 @@ namespace OpenRCT2::Network
request.header["Content-Type"] = "application/json";
_lastHeartbeatTime = Platform::GetTicks();
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::Ok)
{
Console::Error::WriteLine("Unable to connect to master server");
return;
}
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnHeartbeatResponse(root);
});
_currentRequest = Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::Ok)
{
Console::Error::WriteLine(
"Unable to connect to master server, retrying in %d seconds",
kMasterServerRegisterTime / 1000);
_status = AdvertiseStatus::unregistered;
// Don't immediately retry advertising, wait for kMasterServerRegisterTime.
_lastAdvertiseTime = Platform::GetTicks();
return;
}
json_t root = Json::FromString(response.body);
root = Json::AsObject(root);
this->OnHeartbeatResponse(root);
}).share();
}
/**

View File

@@ -18,6 +18,7 @@ namespace OpenRCT2::Network
{
disabled,
unregistered,
registering,
registered,
};