1
0
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:
IntelOrca
2021-10-27 14:21:14 +02:00
committed by Gymnasiast
parent e9e8dceca7
commit 34128dc262
112 changed files with 7281 additions and 4243 deletions

View File

@@ -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;
}