mirror of https://github.com/OpenRCT2/OpenRCT2.git
Implement import / export packed objects
This commit is contained in:
parent
3f55053d9c
commit
341f716c9d
|
@ -170,7 +170,7 @@ private:
|
||||||
auto dataLen = response.body.size();
|
auto dataLen = response.body.size();
|
||||||
|
|
||||||
auto& objRepo = OpenRCT2::GetContext()->GetObjectRepository();
|
auto& objRepo = OpenRCT2::GetContext()->GetObjectRepository();
|
||||||
objRepo.AddObjectFromFile(name, data, dataLen);
|
objRepo.AddObjectFromFile(ObjectGeneration::DAT, name, data, dataLen);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(_downloadedEntriesMutex);
|
std::lock_guard<std::mutex> guard(_downloadedEntriesMutex);
|
||||||
_downloadedEntries.push_back(entry);
|
_downloadedEntries.push_back(entry);
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
#include "OpenRCT2.h"
|
#include "OpenRCT2.h"
|
||||||
#include "ParkImporter.h"
|
#include "ParkImporter.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
#include "core/Console.hpp"
|
||||||
#include "core/Crypt.h"
|
#include "core/Crypt.h"
|
||||||
#include "core/DataSerialiser.h"
|
#include "core/DataSerialiser.h"
|
||||||
|
#include "core/File.h"
|
||||||
#include "core/OrcaStream.hpp"
|
#include "core/OrcaStream.hpp"
|
||||||
|
#include "core/Path.hpp"
|
||||||
#include "drawing/Drawing.h"
|
#include "drawing/Drawing.h"
|
||||||
#include "interface/Viewport.h"
|
#include "interface/Viewport.h"
|
||||||
#include "interface/Window.h"
|
#include "interface/Window.h"
|
||||||
|
@ -81,6 +84,7 @@ namespace OpenRCT2
|
||||||
constexpr uint32_t BANNERS = 0x33;
|
constexpr uint32_t BANNERS = 0x33;
|
||||||
// constexpr uint32_t STAFF = 0x35;
|
// constexpr uint32_t STAFF = 0x35;
|
||||||
constexpr uint32_t CHEATS = 0x36;
|
constexpr uint32_t CHEATS = 0x36;
|
||||||
|
constexpr uint32_t PACKED_OBJECTS = 0x80;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}; // namespace ParkFileChunkType
|
}; // namespace ParkFileChunkType
|
||||||
|
|
||||||
|
@ -88,6 +92,7 @@ namespace OpenRCT2
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ObjectList RequiredObjects;
|
ObjectList RequiredObjects;
|
||||||
|
std::vector<const ObjectRepositoryItem*> ExportObjectsList;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<OrcaStream> _os;
|
std::unique_ptr<OrcaStream> _os;
|
||||||
|
@ -104,6 +109,7 @@ namespace OpenRCT2
|
||||||
_os = std::make_unique<OrcaStream>(stream, OrcaStream::Mode::READING);
|
_os = std::make_unique<OrcaStream>(stream, OrcaStream::Mode::READING);
|
||||||
RequiredObjects = {};
|
RequiredObjects = {};
|
||||||
ReadWriteObjectsChunk(*_os);
|
ReadWriteObjectsChunk(*_os);
|
||||||
|
ReadWritePackedObjectsChunk(*_os);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Import()
|
void Import()
|
||||||
|
@ -149,6 +155,7 @@ namespace OpenRCT2
|
||||||
ReadWriteNotificationsChunk(os);
|
ReadWriteNotificationsChunk(os);
|
||||||
ReadWriteInterfaceChunk(os);
|
ReadWriteInterfaceChunk(os);
|
||||||
ReadWriteCheatsChunk(os);
|
ReadWriteCheatsChunk(os);
|
||||||
|
ReadWritePackedObjectsChunk(os);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Save(const std::string_view& path)
|
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<uint32_t>();
|
||||||
|
for (uint32_t i = 0; i < numObjects; i++)
|
||||||
|
{
|
||||||
|
auto type = cs.Read<uint8_t>();
|
||||||
|
if (type == DESCRIPTOR_DAT)
|
||||||
|
{
|
||||||
|
rct_object_entry entry;
|
||||||
|
cs.Read(&entry, sizeof(entry));
|
||||||
|
auto size = cs.Read<uint32_t>();
|
||||||
|
std::vector<uint8_t> 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<std::string>();
|
||||||
|
auto size = cs.Read<uint32_t>();
|
||||||
|
std::vector<uint8_t> 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<uint32_t>(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)
|
void ReadWriteClimateChunk(OrcaStream& os)
|
||||||
{
|
{
|
||||||
os.ReadWriteChunk(ParkFileChunkType::CLIMATE, [](OrcaStream::ChunkStream& cs) {
|
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<OpenRCT2::ParkFile>();
|
auto parkFile = std::make_unique<OpenRCT2::ParkFile>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// if (flags & S6_SAVE_FLAG_EXPORT)
|
if (flags & S6_SAVE_FLAG_EXPORT)
|
||||||
// {
|
{
|
||||||
// auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
|
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
|
||||||
// s6exporter->ExportObjectsList = objManager.GetPackableObjects();
|
parkFile->ExportObjectsList = objManager.GetPackableObjects();
|
||||||
// }
|
}
|
||||||
// s6exporter->RemoveTracklessRides = true;
|
// s6exporter->RemoveTracklessRides = true;
|
||||||
// s6exporter->Export();
|
// s6exporter->Export();
|
||||||
if (flags & S6_SAVE_FLAG_SCENARIO)
|
if (flags & S6_SAVE_FLAG_SCENARIO)
|
||||||
|
|
|
@ -216,8 +216,7 @@ public:
|
||||||
for (size_t i = 0; i < numObjects; i++)
|
for (size_t i = 0; i < numObjects; i++)
|
||||||
{
|
{
|
||||||
const ObjectRepositoryItem* item = &_objectRepository.GetObjects()[i];
|
const ObjectRepositoryItem* item = &_objectRepository.GetObjects()[i];
|
||||||
if (item->LoadedObject != nullptr && IsObjectCustom(item) && item->LoadedObject->GetLegacyData() != nullptr
|
if (item->LoadedObject != nullptr && IsObjectCustom(item))
|
||||||
&& !item->LoadedObject->IsJsonObject())
|
|
||||||
{
|
{
|
||||||
objects.push_back(item);
|
objects.push_back(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,7 +297,7 @@ public:
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log_verbose("Adding object: [%s]", objectName);
|
log_verbose("Adding object: [%s]", objectName);
|
||||||
auto path = GetPathForNewObject(objectName);
|
auto path = GetPathForNewObject(ObjectGeneration::DAT, objectName);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SaveObject(path, objectEntry, data, dataSize);
|
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());
|
log_verbose("Adding object: [%s]", std::string(objectName).c_str());
|
||||||
auto path = GetPathForNewObject(objectName);
|
auto path = GetPathForNewObject(generation, objectName);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File::WriteAllBytes(path, data, dataSize);
|
File::WriteAllBytes(path, data, dataSize);
|
||||||
|
@ -554,45 +554,53 @@ private:
|
||||||
return salt;
|
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
|
// Get object directory and create it if it doesn't exist
|
||||||
auto userObjPath = _env->GetDirectoryPath(DIRBASE::USER, DIRID::OBJECT);
|
auto userObjPath = _env->GetDirectoryPath(DIRBASE::USER, DIRID::OBJECT);
|
||||||
Path::CreateDirectory(userObjPath);
|
Path::CreateDirectory(userObjPath);
|
||||||
|
|
||||||
// Find a unique file name
|
// Find a unique file name
|
||||||
auto fileName = GetFileNameForNewObject(name);
|
auto fileName = GetFileNameForNewObject(generation, name);
|
||||||
auto fullPath = Path::Combine(userObjPath, fileName + ".DAT");
|
auto extension = (generation == ObjectGeneration::DAT ? ".DAT" : ".parkobj");
|
||||||
|
auto fullPath = Path::Combine(userObjPath, fileName + extension);
|
||||||
auto counter = 1U;
|
auto counter = 1U;
|
||||||
while (File::Exists(fullPath))
|
while (File::Exists(fullPath))
|
||||||
{
|
{
|
||||||
counter++;
|
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;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetFileNameForNewObject(std::string_view name)
|
std::string GetFileNameForNewObject(ObjectGeneration generation, std::string_view name)
|
||||||
{
|
{
|
||||||
// Trim name
|
if (generation == ObjectGeneration::DAT)
|
||||||
char normalisedName[9] = { 0 };
|
|
||||||
auto maxLength = std::min<size_t>(name.size(), 8);
|
|
||||||
for (size_t i = 0; i < maxLength; i++)
|
|
||||||
{
|
{
|
||||||
if (name[i] != ' ')
|
// Trim name
|
||||||
|
char normalisedName[9] = { 0 };
|
||||||
|
auto maxLength = std::min<size_t>(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
|
// Convert to UTF-8 filename
|
||||||
return String::Convert(normalisedName, CODE_PAGE::CP_1252, CODE_PAGE::CP_UTF8);
|
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)
|
void WritePackedObject(OpenRCT2::IStream* stream, const rct_object_entry* entry)
|
||||||
|
|
|
@ -82,7 +82,7 @@ struct IObjectRepository
|
||||||
virtual void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) abstract;
|
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 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 ExportPackedObject(OpenRCT2::IStream* stream) abstract;
|
||||||
virtual void WritePackedObjects(OpenRCT2::IStream* stream, std::vector<const ObjectRepositoryItem*>& objects) abstract;
|
virtual void WritePackedObjects(OpenRCT2::IStream* stream, std::vector<const ObjectRepositoryItem*>& objects) abstract;
|
||||||
|
|
Loading…
Reference in New Issue