diff --git a/src/openrct2-ui/windows/ObjectLoadError.cpp b/src/openrct2-ui/windows/ObjectLoadError.cpp index e69efb1b79..e82c992103 100644 --- a/src/openrct2-ui/windows/ObjectLoadError.cpp +++ b/src/openrct2-ui/windows/ObjectLoadError.cpp @@ -170,7 +170,7 @@ private: auto dataLen = response.body.size(); auto& objRepo = OpenRCT2::GetContext()->GetObjectRepository(); - objRepo.AddObjectFromFile(name, data, dataLen); + objRepo.AddObjectFromFile(ObjectGeneration::DAT, name, data, dataLen); std::lock_guard guard(_downloadedEntriesMutex); _downloadedEntries.push_back(entry); diff --git a/src/openrct2/ParkFile.cpp b/src/openrct2/ParkFile.cpp index 92cab1b672..770736f872 100644 --- a/src/openrct2/ParkFile.cpp +++ b/src/openrct2/ParkFile.cpp @@ -16,9 +16,12 @@ #include "OpenRCT2.h" #include "ParkImporter.h" #include "Version.h" +#include "core/Console.hpp" #include "core/Crypt.h" #include "core/DataSerialiser.h" +#include "core/File.h" #include "core/OrcaStream.hpp" +#include "core/Path.hpp" #include "drawing/Drawing.h" #include "interface/Viewport.h" #include "interface/Window.h" @@ -81,6 +84,7 @@ namespace OpenRCT2 constexpr uint32_t BANNERS = 0x33; // constexpr uint32_t STAFF = 0x35; constexpr uint32_t CHEATS = 0x36; + constexpr uint32_t PACKED_OBJECTS = 0x80; // clang-format on }; // namespace ParkFileChunkType @@ -88,6 +92,7 @@ namespace OpenRCT2 { public: ObjectList RequiredObjects; + std::vector ExportObjectsList; private: std::unique_ptr _os; @@ -104,6 +109,7 @@ namespace OpenRCT2 _os = std::make_unique(stream, OrcaStream::Mode::READING); RequiredObjects = {}; ReadWriteObjectsChunk(*_os); + ReadWritePackedObjectsChunk(*_os); } void Import() @@ -149,6 +155,7 @@ namespace OpenRCT2 ReadWriteNotificationsChunk(os); ReadWriteInterfaceChunk(os); ReadWriteCheatsChunk(os); + ReadWritePackedObjectsChunk(os); } void Save(const std::string_view& path) @@ -431,6 +438,101 @@ namespace OpenRCT2 }); } + void ReadWritePackedObjectsChunk(OrcaStream& os) + { + static constexpr uint8_t DESCRIPTOR_DAT = 0; + static constexpr uint8_t DESCRIPTOR_PARKOBJ = 1; + + if (os.GetMode() == OrcaStream::Mode::WRITING && ExportObjectsList.size() == 0) + { + // Do not emit chunk if there are no packed objects + return; + } + + os.ReadWriteChunk(ParkFileChunkType::PACKED_OBJECTS, [this](OrcaStream::ChunkStream& cs) { + if (cs.GetMode() == OrcaStream::Mode::READING) + { + auto& objRepository = GetContext()->GetObjectRepository(); + auto numObjects = cs.Read(); + for (uint32_t i = 0; i < numObjects; i++) + { + auto type = cs.Read(); + if (type == DESCRIPTOR_DAT) + { + rct_object_entry entry; + cs.Read(&entry, sizeof(entry)); + auto size = cs.Read(); + std::vector data; + data.resize(size); + cs.Read(data.data(), data.size()); + + auto legacyIdentifier = entry.GetName(); + if (objRepository.FindObjectLegacy(legacyIdentifier) == nullptr) + { + objRepository.AddObjectFromFile(ObjectGeneration::DAT, legacyIdentifier, data.data(), data.size()); + } + } + else if (type == DESCRIPTOR_PARKOBJ) + { + auto identifier = cs.Read(); + auto size = cs.Read(); + std::vector data; + data.resize(size); + cs.Read(data.data(), data.size()); + if (objRepository.FindObject(identifier) == nullptr) + { + objRepository.AddObjectFromFile(ObjectGeneration::JSON, identifier, data.data(), data.size()); + } + } + else + { + throw std::runtime_error("Unsupported packed object"); + } + } + } + else + { + auto& stream = cs.GetStream(); + auto countPosition = stream.GetPosition(); + + // Write placeholder count, update later + uint32_t count = 0; + cs.Write(count); + + // Write objects + for (const auto* ori : ExportObjectsList) + { + auto extension = Path::GetExtension(ori->Path); + if (String::Equals(extension, ".dat", true)) + { + cs.Write(DESCRIPTOR_DAT); + cs.Write(&ori->ObjectEntry, sizeof(rct_object_entry)); + } + else if (String::Equals(extension, ".parkobj", true)) + { + cs.Write(DESCRIPTOR_PARKOBJ); + cs.Write(ori->Identifier); + } + else + { + Console::WriteLine("%s not packed: unsupported extension.", ori->Identifier); + continue; + } + + auto data = File::ReadAllBytes(ori->Path); + cs.Write(data.size()); + cs.Write(data.data(), data.size()); + count++; + } + + auto backupPosition = stream.GetPosition(); + stream.SetPosition(countPosition); + cs.Write(count); + stream.SetPosition(backupPosition); + } + }); + } + void ReadWriteClimateChunk(OrcaStream& os) { os.ReadWriteChunk(ParkFileChunkType::CLIMATE, [](OrcaStream::ChunkStream& cs) { @@ -1503,11 +1605,11 @@ int32_t scenario_save(const utf8* path, int32_t flags) auto parkFile = std::make_unique(); try { - // if (flags & S6_SAVE_FLAG_EXPORT) - // { - // auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); - // s6exporter->ExportObjectsList = objManager.GetPackableObjects(); - // } + if (flags & S6_SAVE_FLAG_EXPORT) + { + auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + parkFile->ExportObjectsList = objManager.GetPackableObjects(); + } // s6exporter->RemoveTracklessRides = true; // s6exporter->Export(); if (flags & S6_SAVE_FLAG_SCENARIO) diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index fc636164a8..9404eee5e6 100644 --- a/src/openrct2/object/ObjectManager.cpp +++ b/src/openrct2/object/ObjectManager.cpp @@ -216,8 +216,7 @@ public: for (size_t i = 0; i < numObjects; i++) { const ObjectRepositoryItem* item = &_objectRepository.GetObjects()[i]; - if (item->LoadedObject != nullptr && IsObjectCustom(item) && item->LoadedObject->GetLegacyData() != nullptr - && !item->LoadedObject->IsJsonObject()) + if (item->LoadedObject != nullptr && IsObjectCustom(item)) { objects.push_back(item); } diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index e436e36563..d881ff54fe 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -297,7 +297,7 @@ public: else { log_verbose("Adding object: [%s]", objectName); - auto path = GetPathForNewObject(objectName); + auto path = GetPathForNewObject(ObjectGeneration::DAT, objectName); try { SaveObject(path, objectEntry, data, dataSize); @@ -310,10 +310,10 @@ public: } } - void AddObjectFromFile(std::string_view objectName, const void* data, size_t dataSize) override + void AddObjectFromFile(ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) override { log_verbose("Adding object: [%s]", std::string(objectName).c_str()); - auto path = GetPathForNewObject(objectName); + auto path = GetPathForNewObject(generation, objectName); try { File::WriteAllBytes(path, data, dataSize); @@ -554,45 +554,53 @@ private: return salt; } - std::string GetPathForNewObject(std::string_view name) + std::string GetPathForNewObject(ObjectGeneration generation, std::string_view name) { // Get object directory and create it if it doesn't exist auto userObjPath = _env->GetDirectoryPath(DIRBASE::USER, DIRID::OBJECT); Path::CreateDirectory(userObjPath); // Find a unique file name - auto fileName = GetFileNameForNewObject(name); - auto fullPath = Path::Combine(userObjPath, fileName + ".DAT"); + auto fileName = GetFileNameForNewObject(generation, name); + auto extension = (generation == ObjectGeneration::DAT ? ".DAT" : ".parkobj"); + auto fullPath = Path::Combine(userObjPath, fileName + extension); auto counter = 1U; while (File::Exists(fullPath)) { counter++; - fullPath = Path::Combine(userObjPath, String::StdFormat("%s-%02X.DAT", fileName.c_str(), counter)); + fullPath = Path::Combine(userObjPath, String::StdFormat("%s-%02X%s", fileName.c_str(), counter, extension)); } return fullPath; } - std::string GetFileNameForNewObject(std::string_view name) + std::string GetFileNameForNewObject(ObjectGeneration generation, std::string_view name) { - // Trim name - char normalisedName[9] = { 0 }; - auto maxLength = std::min(name.size(), 8); - for (size_t i = 0; i < maxLength; i++) + if (generation == ObjectGeneration::DAT) { - if (name[i] != ' ') + // Trim name + char normalisedName[9] = { 0 }; + auto maxLength = std::min(name.size(), 8); + for (size_t i = 0; i < maxLength; i++) { - normalisedName[i] = toupper(name[i]); + if (name[i] != ' ') + { + normalisedName[i] = toupper(name[i]); + } + else + { + normalisedName[i] = '\0'; + break; + } } - else - { - normalisedName[i] = '\0'; - break; - } - } - // Convert to UTF-8 filename - return String::Convert(normalisedName, CODE_PAGE::CP_1252, CODE_PAGE::CP_UTF8); + // Convert to UTF-8 filename + return String::Convert(normalisedName, CODE_PAGE::CP_1252, CODE_PAGE::CP_UTF8); + } + else + { + return std::string(name); + } } void WritePackedObject(OpenRCT2::IStream* stream, const rct_object_entry* entry) diff --git a/src/openrct2/object/ObjectRepository.h b/src/openrct2/object/ObjectRepository.h index bf2af60b25..d161fe4785 100644 --- a/src/openrct2/object/ObjectRepository.h +++ b/src/openrct2/object/ObjectRepository.h @@ -82,7 +82,7 @@ struct IObjectRepository virtual void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) abstract; virtual void AddObject(const rct_object_entry* objectEntry, const void* data, size_t dataSize) abstract; - virtual void AddObjectFromFile(std::string_view objectName, const void* data, size_t dataSize) abstract; + virtual void AddObjectFromFile(ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) abstract; virtual void ExportPackedObject(OpenRCT2::IStream* stream) abstract; virtual void WritePackedObjects(OpenRCT2::IStream* stream, std::vector& objects) abstract;