mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-23 15:52:55 +01:00
Add new .park save format
Co-authored-by: Gymnasiast <Gymnasiast@users.noreply.github.com> Co-authored-by: duncanspumpkin <duncanspumpkin@users.noreply.github.com> Co-authored-by: ZehMatt <Zehmatt@users.noreply.github.com> Co-authored-by: Broxzier <Broxzier@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "../Game.h"
|
||||
#include "../GameStateSnapshots.h"
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../ParkFile.h"
|
||||
#include "../PlatformEnvironment.h"
|
||||
#include "../actions/LoadOrQuitAction.h"
|
||||
#include "../actions/NetworkModifyGroupAction.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#include "../core/Json.hpp"
|
||||
#include "../localisation/Formatting.h"
|
||||
#include "../platform/Platform2.h"
|
||||
#include "../scenario/Scenario.h"
|
||||
#include "../scripting/ScriptEngine.h"
|
||||
#include "../ui/UiContext.h"
|
||||
#include "../ui/WindowManager.h"
|
||||
@@ -70,7 +72,6 @@ static constexpr uint32_t MaxPacketsPerUpdate = 100;
|
||||
# include "../localisation/Localisation.h"
|
||||
# include "../object/ObjectManager.h"
|
||||
# include "../object/ObjectRepository.h"
|
||||
# include "../rct2/S6Exporter.h"
|
||||
# include "../scenario/Scenario.h"
|
||||
# include "../util/Util.h"
|
||||
# include "../world/Park.h"
|
||||
@@ -1219,15 +1220,25 @@ void NetworkBase::Client_Send_AUTH(
|
||||
_serverConnection->QueuePacket(std::move(packet));
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Send_MAPREQUEST(const std::vector<std::string>& objects)
|
||||
void NetworkBase::Client_Send_MAPREQUEST(const std::vector<ObjectEntryDescriptor>& objects)
|
||||
{
|
||||
log_verbose("client requests %u objects", uint32_t(objects.size()));
|
||||
NetworkPacket packet(NetworkCommand::MapRequest);
|
||||
packet << static_cast<uint32_t>(objects.size());
|
||||
for (const auto& object : objects)
|
||||
{
|
||||
log_verbose("client requests object %s", object.c_str());
|
||||
packet.Write(reinterpret_cast<const uint8_t*>(object.c_str()), 8);
|
||||
std::string name(object.GetName());
|
||||
log_verbose("client requests object %s", name.c_str());
|
||||
if (object.Generation == ObjectGeneration::DAT)
|
||||
{
|
||||
packet << static_cast<uint8_t>(0);
|
||||
packet.Write(&object.Entry, sizeof(rct_object_entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
packet << static_cast<uint8_t>(1);
|
||||
packet.WriteString(name);
|
||||
}
|
||||
}
|
||||
_serverConnection->QueuePacket(std::move(packet));
|
||||
}
|
||||
@@ -1261,9 +1272,20 @@ void NetworkBase::Server_Send_OBJECTS_LIST(
|
||||
NetworkPacket packet(NetworkCommand::ObjectsList);
|
||||
packet << static_cast<uint32_t>(i) << static_cast<uint32_t>(objects.size());
|
||||
|
||||
log_verbose("Object %.8s (checksum %x)", object->ObjectEntry.name, object->ObjectEntry.checksum);
|
||||
packet.Write(reinterpret_cast<const uint8_t*>(object->ObjectEntry.name), 8);
|
||||
packet << object->ObjectEntry.checksum << object->ObjectEntry.flags;
|
||||
if (object->Identifier.empty())
|
||||
{
|
||||
// DAT
|
||||
log_verbose("Object %.8s (checksum %x)", object->ObjectEntry.name, object->ObjectEntry.checksum);
|
||||
packet << static_cast<uint8_t>(0);
|
||||
packet.Write(&object->ObjectEntry, sizeof(rct_object_entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
// JSON
|
||||
log_verbose("Object %s", object->Identifier.c_str());
|
||||
packet << static_cast<uint8_t>(1);
|
||||
packet.WriteString(object->Identifier);
|
||||
}
|
||||
|
||||
connection.QueuePacket(std::move(packet));
|
||||
}
|
||||
@@ -1401,37 +1423,18 @@ void NetworkBase::Server_Send_MAP(NetworkConnection* connection)
|
||||
|
||||
std::vector<uint8_t> NetworkBase::save_for_network(const std::vector<const ObjectRepositoryItem*>& objects) const
|
||||
{
|
||||
std::vector<uint8_t> header;
|
||||
bool RLEState = gUseRLE;
|
||||
gUseRLE = false;
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
auto ms = OpenRCT2::MemoryStream();
|
||||
if (!SaveMap(&ms, objects))
|
||||
if (SaveMap(&ms, objects))
|
||||
{
|
||||
log_warning("Failed to export map.");
|
||||
return header;
|
||||
}
|
||||
gUseRLE = RLEState;
|
||||
|
||||
const void* data = ms.GetData();
|
||||
int32_t size = ms.GetLength();
|
||||
|
||||
auto compressed = util_zlib_deflate(static_cast<const uint8_t*>(data), size);
|
||||
if (compressed.has_value())
|
||||
{
|
||||
std::string headerString = "open2_sv6_zlib";
|
||||
header.resize(headerString.size() + 1 + compressed->size());
|
||||
std::memcpy(&header[0], headerString.c_str(), headerString.size() + 1);
|
||||
std::memcpy(&header[headerString.size() + 1], compressed->data(), compressed->size());
|
||||
log_verbose("Sending map of size %u bytes, compressed to %u bytes", size, headerString.size() + 1 + compressed->size());
|
||||
result.resize(ms.GetLength());
|
||||
std::memcpy(result.data(), ms.GetData(), result.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
log_warning("Failed to compress the data, falling back to non-compressed sv6.");
|
||||
header.resize(size);
|
||||
std::memcpy(header.data(), data, size);
|
||||
log_warning("Failed to export map.");
|
||||
}
|
||||
return header;
|
||||
return result;
|
||||
}
|
||||
|
||||
void NetworkBase::Client_Send_CHAT(const char* text)
|
||||
@@ -2339,26 +2342,45 @@ void NetworkBase::Client_Handle_OBJECTS_LIST(NetworkConnection& connection, Netw
|
||||
intent.putExtra(INTENT_EXTRA_CALLBACK, []() -> void { ::GetContext()->GetNetwork().Close(); });
|
||||
context_open_intent(&intent);
|
||||
|
||||
char objectName[12]{};
|
||||
std::memcpy(objectName, packet.Read(8), 8);
|
||||
uint8_t objectType{};
|
||||
packet >> objectType;
|
||||
|
||||
uint32_t checksum = 0;
|
||||
uint32_t flags = 0;
|
||||
packet >> checksum >> flags;
|
||||
|
||||
const auto* object = repo.FindObjectLegacy(objectName);
|
||||
// This could potentially request the object if checksums don't match, but since client
|
||||
// won't replace its version with server-provided one, we don't do that.
|
||||
if (object == nullptr)
|
||||
if (objectType == 0)
|
||||
{
|
||||
log_verbose("Requesting object %s with checksum %x from server", objectName, checksum);
|
||||
_missingObjects.emplace_back(objectName);
|
||||
// DAT
|
||||
auto entry = reinterpret_cast<const rct_object_entry*>(packet.Read(sizeof(rct_object_entry)));
|
||||
if (entry != nullptr)
|
||||
{
|
||||
const auto* object = repo.FindObject(entry);
|
||||
if (object == nullptr)
|
||||
{
|
||||
auto objectName = std::string(entry->GetName());
|
||||
log_verbose("Requesting object %s with checksum %x from server", objectName.c_str(), entry->checksum);
|
||||
_missingObjects.push_back(ObjectEntryDescriptor(*entry));
|
||||
}
|
||||
else if (object->ObjectEntry.checksum != entry->checksum || object->ObjectEntry.flags != entry->flags)
|
||||
{
|
||||
auto objectName = std::string(entry->GetName());
|
||||
log_warning(
|
||||
"Object %s has different checksum/flags (%x/%x) than server (%x/%x).", objectName.c_str(),
|
||||
object->ObjectEntry.checksum, object->ObjectEntry.flags, entry->checksum, entry->flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (object->ObjectEntry.checksum != checksum || object->ObjectEntry.flags != flags)
|
||||
else
|
||||
{
|
||||
log_warning(
|
||||
"Object %s has different checksum/flags (%x/%x) than server (%x/%x).", objectName, object->ObjectEntry.checksum,
|
||||
object->ObjectEntry.flags, checksum, flags);
|
||||
// JSON
|
||||
auto identifier = packet.ReadString();
|
||||
if (!identifier.empty())
|
||||
{
|
||||
const auto* object = repo.FindObject(identifier);
|
||||
if (object == nullptr)
|
||||
{
|
||||
auto objectName = std::string(identifier);
|
||||
log_verbose("Requesting object %s from server", objectName.c_str());
|
||||
_missingObjects.push_back(ObjectEntryDescriptor(objectName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2490,13 +2512,28 @@ void NetworkBase::Server_Handle_MAPREQUEST(NetworkConnection& connection, Networ
|
||||
return;
|
||||
}
|
||||
|
||||
// This is required, as packet does not have null terminator
|
||||
std::string s(name, name + 8);
|
||||
log_verbose("Client requested object %s", s.c_str());
|
||||
const ObjectRepositoryItem* item = repo.FindObjectLegacy(s.c_str());
|
||||
uint8_t generation{};
|
||||
packet >> generation;
|
||||
|
||||
std::string objectName;
|
||||
const ObjectRepositoryItem* item{};
|
||||
if (generation == static_cast<uint8_t>(ObjectGeneration::DAT))
|
||||
{
|
||||
const auto* entry = reinterpret_cast<const rct_object_entry*>(packet.Read(sizeof(rct_object_entry)));
|
||||
objectName = std::string(entry->GetName());
|
||||
log_verbose("Client requested object %s", objectName.c_str());
|
||||
item = repo.FindObject(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
objectName = std::string(packet.ReadString());
|
||||
log_verbose("Client requested object %s", objectName.c_str());
|
||||
item = repo.FindObject(objectName);
|
||||
}
|
||||
|
||||
if (item == nullptr)
|
||||
{
|
||||
log_warning("Client tried getting non-existent object %s from us.", s.c_str());
|
||||
log_warning("Client tried getting non-existent object %s from us.", objectName.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2504,7 +2541,7 @@ void NetworkBase::Server_Handle_MAPREQUEST(NetworkConnection& connection, Networ
|
||||
}
|
||||
}
|
||||
|
||||
const char* player_name = static_cast<const char*>(connection.Player->Name.c_str());
|
||||
auto player_name = connection.Player->Name.c_str();
|
||||
Server_Send_MAP(&connection);
|
||||
Server_Send_EVENT_PLAYER_JOINED(player_name);
|
||||
Server_Send_GROUPLIST(connection);
|
||||
@@ -2673,25 +2710,6 @@ void NetworkBase::Client_Handle_MAP([[maybe_unused]] NetworkConnection& connecti
|
||||
bool has_to_free = false;
|
||||
uint8_t* data = &chunk_buffer[0];
|
||||
size_t data_size = size;
|
||||
// zlib-compressed
|
||||
if (strcmp("open2_sv6_zlib", reinterpret_cast<char*>(&chunk_buffer[0])) == 0)
|
||||
{
|
||||
log_verbose("Received zlib-compressed sv6 map");
|
||||
has_to_free = true;
|
||||
size_t header_len = strlen("open2_sv6_zlib") + 1;
|
||||
data = util_zlib_inflate(&chunk_buffer[header_len], size - header_len, &data_size);
|
||||
if (data == nullptr)
|
||||
{
|
||||
log_warning("Failed to decompress data sent from server.");
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_verbose("Assuming received map is in plain sv6 format");
|
||||
}
|
||||
|
||||
auto ms = MemoryStream(data, data_size);
|
||||
if (LoadMap(&ms))
|
||||
{
|
||||
@@ -2734,7 +2752,7 @@ bool NetworkBase::LoadMap(IStream* stream)
|
||||
{
|
||||
auto& context = GetContext();
|
||||
auto& objManager = context.GetObjectManager();
|
||||
auto importer = ParkImporter::CreateS6(context.GetObjectRepository());
|
||||
auto importer = ParkImporter::CreateParkFile(context.GetObjectRepository());
|
||||
auto loadResult = importer->LoadFromStream(stream, false);
|
||||
objManager.LoadObjects(loadResult.RequiredObjects);
|
||||
importer->Import();
|
||||
@@ -2742,43 +2760,12 @@ bool NetworkBase::LoadMap(IStream* stream)
|
||||
EntityTweener::Get().Reset();
|
||||
AutoCreateMapAnimations();
|
||||
|
||||
// Read checksum
|
||||
[[maybe_unused]] uint32_t checksum = stream->ReadValue<uint32_t>();
|
||||
|
||||
// Read other data not in normal save files
|
||||
gGamePaused = stream->ReadValue<uint32_t>();
|
||||
_guestGenerationProbability = stream->ReadValue<uint32_t>();
|
||||
_suggestedGuestMaximum = stream->ReadValue<uint32_t>();
|
||||
gCheatsAllowTrackPlaceInvalidHeights = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsEnableAllDrawableTrackPieces = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsSandboxMode = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableClearanceChecks = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableSupportLimits = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableTrainLengthLimit = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsEnableChainLiftOnAllTrack = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsShowAllOperatingModes = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsShowVehiclesFromOtherTrackTypes = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsUnlockOperatingLimits = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableBrakesFailure = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableAllBreakdowns = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsBuildInPauseMode = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsIgnoreRideIntensity = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableVandalism = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableLittering = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsNeverendingMarketing = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsFreezeWeather = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisablePlantAging = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsAllowArbitraryRideTypeChanges = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsDisableRideValueAging = stream->ReadValue<uint8_t>() != 0;
|
||||
gConfigGeneral.show_real_names_of_guests = stream->ReadValue<uint8_t>() != 0;
|
||||
gCheatsIgnoreResearchStatus = stream->ReadValue<uint8_t>() != 0;
|
||||
gAllowEarlyCompletionInNetworkPlay = stream->ReadValue<uint8_t>() != 0;
|
||||
|
||||
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
|
||||
result = true;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Console::Error::WriteLine("Unable to read map from server: %s", e.what());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -2789,44 +2776,14 @@ bool NetworkBase::SaveMap(IStream* stream, const std::vector<const ObjectReposit
|
||||
viewport_set_saved_view();
|
||||
try
|
||||
{
|
||||
auto s6exporter = std::make_unique<S6Exporter>();
|
||||
s6exporter->ExportObjectsList = objects;
|
||||
s6exporter->Export();
|
||||
s6exporter->SaveGame(stream);
|
||||
|
||||
// Write other data not in normal save files
|
||||
stream->WriteValue<uint32_t>(gGamePaused);
|
||||
stream->WriteValue<uint32_t>(_guestGenerationProbability);
|
||||
stream->WriteValue<uint32_t>(_suggestedGuestMaximum);
|
||||
stream->WriteValue<uint8_t>(gCheatsAllowTrackPlaceInvalidHeights);
|
||||
stream->WriteValue<uint8_t>(gCheatsEnableAllDrawableTrackPieces);
|
||||
stream->WriteValue<uint8_t>(gCheatsSandboxMode);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableClearanceChecks);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableSupportLimits);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableTrainLengthLimit);
|
||||
stream->WriteValue<uint8_t>(gCheatsEnableChainLiftOnAllTrack);
|
||||
stream->WriteValue<uint8_t>(gCheatsShowAllOperatingModes);
|
||||
stream->WriteValue<uint8_t>(gCheatsShowVehiclesFromOtherTrackTypes);
|
||||
stream->WriteValue<uint8_t>(gCheatsUnlockOperatingLimits);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableBrakesFailure);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableAllBreakdowns);
|
||||
stream->WriteValue<uint8_t>(gCheatsBuildInPauseMode);
|
||||
stream->WriteValue<uint8_t>(gCheatsIgnoreRideIntensity);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableVandalism);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableLittering);
|
||||
stream->WriteValue<uint8_t>(gCheatsNeverendingMarketing);
|
||||
stream->WriteValue<uint8_t>(gCheatsFreezeWeather);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisablePlantAging);
|
||||
stream->WriteValue<uint8_t>(gCheatsAllowArbitraryRideTypeChanges);
|
||||
stream->WriteValue<uint8_t>(gCheatsDisableRideValueAging);
|
||||
stream->WriteValue<uint8_t>(gConfigGeneral.show_real_names_of_guests);
|
||||
stream->WriteValue<uint8_t>(gCheatsIgnoreResearchStatus);
|
||||
stream->WriteValue<uint8_t>(gConfigGeneral.allow_early_completion);
|
||||
|
||||
auto exporter = std::make_unique<ParkFileExporter>();
|
||||
exporter->ExportObjectsList = objects;
|
||||
exporter->Export(*stream);
|
||||
result = true;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Console::Error::WriteLine("Unable to serialise map: %s", e.what());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user