diff --git a/src/openrct2/core/Zip.cpp b/src/openrct2/core/Zip.cpp index 73dad5268a..9f1df47751 100644 --- a/src/openrct2/core/Zip.cpp +++ b/src/openrct2/core/Zip.cpp @@ -52,7 +52,7 @@ public: size_t GetNumFiles() const override { - return zip_get_num_files(_zip); + return zip_get_num_entries(_zip, 0); } std::string GetFileName(size_t index) const override @@ -82,11 +82,11 @@ public: std::vector GetFileData(const std::string_view& path) const override { std::vector result; - auto index = (size_t)zip_name_locate(_zip, path.data(), 0); + auto index = GetIndexFromPath(path); auto dataSize = GetFileSize(index); if (dataSize > 0 && dataSize < SIZE_MAX) { - auto zipFile = zip_fopen(_zip, path.data(), 0); + auto zipFile = zip_fopen_index(_zip, index, 0); if (zipFile != nullptr) { result.resize((size_t)dataSize); @@ -110,7 +110,7 @@ public: const auto& writeBuffer = *_writeBuffers.rbegin(); auto source = zip_source_buffer(_zip, writeBuffer.data(), writeBuffer.size(), 0); - auto index = zip_name_locate(_zip, path.data(), 0); + auto index = GetIndexFromPath(path); if (index == -1) { zip_add(_zip, path.data(), source); @@ -123,15 +123,55 @@ public: void DeleteFile(const std::string_view& path) override { - auto index = zip_name_locate(_zip, path.data(), 0); + auto index = GetIndexFromPath(path); zip_delete(_zip, index); } void RenameFile(const std::string_view& path, const std::string_view& newPath) override { - auto index = zip_name_locate(_zip, path.data(), 0); + auto index = GetIndexFromPath(path); zip_file_rename(_zip, index, newPath.data(), ZIP_FL_ENC_GUESS); } + +private: + /** + * Normalises both the given path and the stored paths and finds the first match. + */ + zip_int64_t GetIndexFromPath(const std::string_view& path) const + { + auto normalisedPath = NormalisePath(path); + if (!normalisedPath.empty()) + { + auto numFiles = zip_get_num_entries(_zip, 0); + for (zip_int64_t i = 0; i < numFiles; i++) + { + auto normalisedZipPath = NormalisePath(zip_get_name(_zip, i, ZIP_FL_ENC_GUESS)); + if (normalisedZipPath == normalisedPath) + { + return i; + } + } + } + return -1; + } + + static std::string NormalisePath(const std::string_view& path) + { + std::string result; + if (!path.empty()) + { + // Convert back slashes to forward slashes + result = std::move(std::string(path)); + for (auto ch = result.data(); *ch != '\0'; ch++) + { + if (*ch == '\\') + { + *ch = '/'; + } + } + } + return result; + } }; namespace Zip diff --git a/src/openrct2/object/ObjectFactory.cpp b/src/openrct2/object/ObjectFactory.cpp index 1081c46242..3e71e074eb 100644 --- a/src/openrct2/object/ObjectFactory.cpp +++ b/src/openrct2/object/ObjectFactory.cpp @@ -41,10 +41,52 @@ #include "WallObject.h" #include "WaterObject.h" +interface IFileDataRetriever +{ + virtual ~IFileDataRetriever() = default; + virtual std::vector GetData(const std::string_view& path) const abstract; +}; + +class FileSystemDataRetriever : public IFileDataRetriever +{ +private: + std::string _basePath; + +public: + FileSystemDataRetriever(const std::string_view& basePath) + : _basePath(basePath) + { + } + + std::vector GetData(const std::string_view& path) const override + { + auto absolutePath = Path::Combine(_basePath, path.data()); + return File::ReadAllBytes(absolutePath); + } +}; + +class ZipDataRetriever : public IFileDataRetriever +{ +private: + const IZipArchive& _zipArchive; + +public: + ZipDataRetriever(const IZipArchive& zipArchive) + : _zipArchive(zipArchive) + { + } + + std::vector GetData(const std::string_view& path) const override + { + return _zipArchive.GetFileData(path); + } +}; + class ReadObjectContext : public IReadObjectContext { private: IObjectRepository& _objectRepository; + const IFileDataRetriever * _fileDataRetriever; std::string _objectName; bool _loadImages; @@ -56,11 +98,15 @@ public: bool WasWarning() const { return _wasWarning; } bool WasError() const { return _wasError; } - ReadObjectContext(IObjectRepository& objectRepository, const std::string &objectName, bool loadImages, const std::string& basePath) + ReadObjectContext( + IObjectRepository& objectRepository, + const std::string &objectName, + bool loadImages, + const IFileDataRetriever * fileDataRetriever) : _objectRepository(objectRepository), _objectName(objectName), _loadImages(loadImages), - _basePath(basePath) + _fileDataRetriever(fileDataRetriever) { } @@ -76,8 +122,11 @@ public: std::vector GetData(const std::string_view& path) override { - auto absolutePath = Path::Combine(_basePath, path.data()); - return File::ReadAllBytes(absolutePath); + if (_fileDataRetriever != nullptr) + { + return _fileDataRetriever->GetData(path); + } + return {}; } void LogWarning(uint32 code, const utf8 * text) override @@ -103,7 +152,7 @@ public: namespace ObjectFactory { - static Object * CreateObjectFromJson(IObjectRepository& objectRepository, const json_t * jRoot, const std::string_view& basePath); + static Object * CreateObjectFromJson(IObjectRepository& objectRepository, const json_t * jRoot, const IFileDataRetriever * fileRetriever); static void ReadObjectLegacy(Object * object, IReadObjectContext * context, IStream * stream) { @@ -143,7 +192,7 @@ namespace ObjectFactory log_verbose(" size: %zu", chunk->GetLength()); auto chunkStream = MemoryStream(chunk->GetData(), chunk->GetLength()); - auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless, ""); + auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless, nullptr); ReadObjectLegacy(result, &readContext, &chunkStream); if (readContext.WasError()) { @@ -169,7 +218,7 @@ namespace ObjectFactory utf8 objectName[DAT_NAME_LENGTH + 1]; object_entry_get_name_fixed(objectName, sizeof(objectName), entry); - auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless, ""); + auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2Headless, nullptr); auto chunkStream = MemoryStream(data, dataSize); ReadObjectLegacy(result, &readContext, &chunkStream); @@ -241,12 +290,12 @@ namespace ObjectFactory return 0xFF; } - Object * CreateObjectFromZipFile(IObjectRepository& objectRepository, const std::string &path) + Object * CreateObjectFromZipFile(IObjectRepository& objectRepository, const std::string_view& path) { Object * result = nullptr; try { - auto archive = Zip::Open(path.c_str(), ZIP_ACCESS::READ); + auto archive = Zip::Open(path, ZIP_ACCESS::READ); auto jsonBytes = archive->GetFileData("object.json"); if (jsonBytes.empty()) { @@ -254,17 +303,18 @@ namespace ObjectFactory } json_error_t jsonLoadError; - auto jRoot = json_loads((const char *)jsonBytes.data(), 0, &jsonLoadError); + auto jRoot = json_loadb((const char *)jsonBytes.data(), jsonBytes.size(), 0, &jsonLoadError); if (jRoot == nullptr) { throw JsonException(&jsonLoadError); } - return CreateObjectFromJson(objectRepository, jRoot, ""); + auto fileDataRetriever = ZipDataRetriever(*archive); + return CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever); } catch (const std::exception& e) { - Console::Error::WriteLine("Unable to open or read '%s': %s", path.c_str(), e.what()); + Console::Error::WriteLine("Unable to open or read '%s': %s", path.data(), e.what()); delete result; result = nullptr; @@ -280,7 +330,8 @@ namespace ObjectFactory try { auto jRoot = Json::ReadFromFile(path.c_str()); - result = CreateObjectFromJson(objectRepository, jRoot, Path::GetDirectory(path)); + auto fileDataRetriever = FileSystemDataRetriever(Path::GetDirectory(path)); + result = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever); json_decref(jRoot); } catch (const std::runtime_error &err) @@ -293,7 +344,7 @@ namespace ObjectFactory return result; } - Object * CreateObjectFromJson(IObjectRepository& objectRepository, const json_t * jRoot, const std::string_view& basePath) + Object * CreateObjectFromJson(IObjectRepository& objectRepository, const json_t * jRoot, const IFileDataRetriever * fileRetriever) { log_verbose("CreateObjectFromJson(...)"); @@ -319,7 +370,7 @@ namespace ObjectFactory memcpy(entry.name, originalName.c_str(), minLength); result = CreateObject(entry); - auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2Headless, basePath.data()); + auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2Headless, fileRetriever); result->ReadJson(&readContext, jRoot); if (readContext.WasError()) { diff --git a/src/openrct2/object/ObjectFactory.h b/src/openrct2/object/ObjectFactory.h index 4a05b87bef..85b1009c61 100644 --- a/src/openrct2/object/ObjectFactory.h +++ b/src/openrct2/object/ObjectFactory.h @@ -16,6 +16,7 @@ #pragma once +#include #include "../common.h" interface IObjectRepository; @@ -26,6 +27,7 @@ namespace ObjectFactory { Object * CreateObjectFromLegacyFile(IObjectRepository& objectRepository, const utf8 * path); Object * CreateObjectFromLegacyData(IObjectRepository& objectRepository, const rct_object_entry * entry, const void * data, size_t dataSize); + Object * CreateObjectFromZipFile(IObjectRepository& objectRepository, const std::string_view& path); Object * CreateObject(const rct_object_entry &entry); Object * CreateObjectFromJsonFile(IObjectRepository& objectRepository, const std::string &path); diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index 6f2fa9baf9..16dc9a9851 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -103,34 +103,29 @@ public: public: std::tuple Create(sint32 language, const std::string &path) const override { + Object * object = nullptr; auto extension = Path::GetExtension(path); if (String::Equals(extension, ".json", true)) { - auto object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path); - if (object != nullptr) - { - ObjectRepositoryItem item = { 0 }; - item.ObjectEntry = *object->GetObjectEntry(); - item.Path = String::Duplicate(path); - item.Name = String::Duplicate(object->GetName(language)); - object->SetRepositoryItem(&item); - delete object; - return std::make_tuple(true, item); - } + object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path); + } + else if (String::Equals(extension, ".parkobj", true)) + { + object = ObjectFactory::CreateObjectFromZipFile(_objectRepository, path); } else { - auto object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str()); - if (object != nullptr) - { - ObjectRepositoryItem item = { 0 }; - item.ObjectEntry = *object->GetObjectEntry(); - item.Path = String::Duplicate(path); - item.Name = String::Duplicate(object->GetName()); - object->SetRepositoryItem(&item); - delete object; - return std::make_tuple(true, item); - } + object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str()); + } + if (object != nullptr) + { + ObjectRepositoryItem item = { 0 }; + item.ObjectEntry = *object->GetObjectEntry(); + item.Path = String::Duplicate(path); + item.Name = String::Duplicate(object->GetName()); + object->SetRepositoryItem(&item); + delete object; + return std::make_tuple(true, item); } return std::make_tuple(false, ObjectRepositoryItem()); } @@ -285,6 +280,10 @@ public: { return ObjectFactory::CreateObjectFromJsonFile(*this, ori->Path); } + else if (String::Equals(extension, ".parkobj", true)) + { + return ObjectFactory::CreateObjectFromZipFile(*this, ori->Path); + } else { return ObjectFactory::CreateObjectFromLegacyFile(*this, ori->Path);