mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 19:13:07 +01:00
443 lines
14 KiB
C++
443 lines
14 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2018 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "ObjectFactory.h"
|
|
|
|
#include "../OpenRCT2.h"
|
|
#include "../core/Console.hpp"
|
|
#include "../core/File.h"
|
|
#include "../core/FileStream.hpp"
|
|
#include "../core/Json.hpp"
|
|
#include "../core/Memory.hpp"
|
|
#include "../core/MemoryStream.h"
|
|
#include "../core/Path.hpp"
|
|
#include "../core/String.hpp"
|
|
#include "../core/Zip.h"
|
|
#include "../rct12/SawyerChunkReader.h"
|
|
#include "BannerObject.h"
|
|
#include "EntranceObject.h"
|
|
#include "FootpathItemObject.h"
|
|
#include "FootpathObject.h"
|
|
#include "LargeSceneryObject.h"
|
|
#include "Object.h"
|
|
#include "ObjectLimits.h"
|
|
#include "ObjectList.h"
|
|
#include "RideObject.h"
|
|
#include "SceneryGroupObject.h"
|
|
#include "SmallSceneryObject.h"
|
|
#include "WallObject.h"
|
|
#include "WaterObject.h"
|
|
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
|
|
interface IFileDataRetriever
|
|
{
|
|
virtual ~IFileDataRetriever() = default;
|
|
virtual std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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;
|
|
std::string _basePath;
|
|
bool _wasWarning = false;
|
|
bool _wasError = false;
|
|
|
|
public:
|
|
bool WasWarning() const
|
|
{
|
|
return _wasWarning;
|
|
}
|
|
bool WasError() const
|
|
{
|
|
return _wasError;
|
|
}
|
|
|
|
ReadObjectContext(
|
|
IObjectRepository& objectRepository, const std::string& objectName, bool loadImages,
|
|
const IFileDataRetriever* fileDataRetriever)
|
|
: _objectRepository(objectRepository)
|
|
, _fileDataRetriever(fileDataRetriever)
|
|
, _objectName(objectName)
|
|
, _loadImages(loadImages)
|
|
{
|
|
}
|
|
|
|
IObjectRepository& GetObjectRepository() override
|
|
{
|
|
return _objectRepository;
|
|
}
|
|
|
|
bool ShouldLoadImages() override
|
|
{
|
|
return _loadImages;
|
|
}
|
|
|
|
std::vector<uint8_t> GetData(const std::string_view& path) override
|
|
{
|
|
if (_fileDataRetriever != nullptr)
|
|
{
|
|
return _fileDataRetriever->GetData(path);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void LogWarning(uint32_t code, const utf8* text) override
|
|
{
|
|
_wasWarning = true;
|
|
|
|
if (!String::IsNullOrEmpty(text))
|
|
{
|
|
Console::Error::WriteLine("[%s] Warning (%d): %s", _objectName.c_str(), code, text);
|
|
}
|
|
}
|
|
|
|
void LogError(uint32_t code, const utf8* text) override
|
|
{
|
|
_wasError = true;
|
|
|
|
if (!String::IsNullOrEmpty(text))
|
|
{
|
|
Console::Error::WriteLine("[%s] Error (%d): %s", _objectName.c_str(), code, text);
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace ObjectFactory
|
|
{
|
|
static Object* CreateObjectFromJson(
|
|
IObjectRepository& objectRepository, const json_t* jRoot, const IFileDataRetriever* fileRetriever);
|
|
|
|
static uint8_t ParseSourceGame(const std::string& s)
|
|
{
|
|
static const std::unordered_map<std::string, uint8_t> LookupTable{
|
|
{ "rct1", OBJECT_SOURCE_RCT1 },
|
|
{ "rct1aa", OBJECT_SOURCE_ADDED_ATTRACTIONS },
|
|
{ "rct1ll", OBJECT_SOURCE_LOOPY_LANDSCAPES },
|
|
{ "rct2", OBJECT_SOURCE_RCT2 },
|
|
{ "rct2ww", OBJECT_SOURCE_WACKY_WORLDS },
|
|
{ "rct2tt", OBJECT_SOURCE_TIME_TWISTER },
|
|
{ "official", OBJECT_SOURCE_OPENRCT2_OFFICIAL },
|
|
{ "custom", OBJECT_SOURCE_CUSTOM },
|
|
};
|
|
auto result = LookupTable.find(s);
|
|
return (result != LookupTable.end()) ? result->second : OBJECT_SOURCE_CUSTOM;
|
|
}
|
|
|
|
static void ReadObjectLegacy(Object* object, IReadObjectContext* context, IStream* stream)
|
|
{
|
|
try
|
|
{
|
|
object->ReadLegacy(context, stream);
|
|
}
|
|
catch (const IOException&)
|
|
{
|
|
// TODO check that ex is really EOF and not some other error
|
|
context->LogError(OBJECT_ERROR_UNEXPECTED_EOF, "Unexpectedly reached end of file.");
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
context->LogError(OBJECT_ERROR_UNKNOWN, nullptr);
|
|
}
|
|
}
|
|
|
|
Object* CreateObjectFromLegacyFile(IObjectRepository& objectRepository, const utf8* path)
|
|
{
|
|
log_verbose("CreateObjectFromLegacyFile(..., \"%s\")", path);
|
|
|
|
Object* result = nullptr;
|
|
try
|
|
{
|
|
auto fs = FileStream(path, FILE_MODE_OPEN);
|
|
auto chunkReader = SawyerChunkReader(&fs);
|
|
|
|
rct_object_entry entry = fs.ReadValue<rct_object_entry>();
|
|
|
|
if (object_entry_get_type(&entry) != OBJECT_TYPE_SCENARIO_TEXT)
|
|
{
|
|
result = CreateObject(entry);
|
|
|
|
utf8 objectName[DAT_NAME_LENGTH + 1] = { 0 };
|
|
object_entry_get_name_fixed(objectName, sizeof(objectName), &entry);
|
|
log_verbose(" entry: { 0x%08X, \"%s\", 0x%08X }", entry.flags, objectName, entry.checksum);
|
|
|
|
auto chunk = chunkReader.ReadChunk();
|
|
log_verbose(" size: %zu", chunk->GetLength());
|
|
|
|
auto chunkStream = MemoryStream(chunk->GetData(), chunk->GetLength());
|
|
auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2NoGraphics, nullptr);
|
|
ReadObjectLegacy(result, &readContext, &chunkStream);
|
|
if (readContext.WasError())
|
|
{
|
|
throw std::runtime_error("Object has errors");
|
|
}
|
|
result->SetSourceGames({ object_entry_get_source_game_legacy(&entry) });
|
|
}
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
delete result;
|
|
result = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Object* CreateObjectFromLegacyData(
|
|
IObjectRepository& objectRepository, const rct_object_entry* entry, const void* data, size_t dataSize)
|
|
{
|
|
Guard::ArgumentNotNull(entry, GUARD_LINE);
|
|
Guard::ArgumentNotNull(data, GUARD_LINE);
|
|
|
|
Object* result = CreateObject(*entry);
|
|
if (result != nullptr)
|
|
{
|
|
utf8 objectName[DAT_NAME_LENGTH + 1];
|
|
object_entry_get_name_fixed(objectName, sizeof(objectName), entry);
|
|
|
|
auto readContext = ReadObjectContext(objectRepository, objectName, !gOpenRCT2NoGraphics, nullptr);
|
|
auto chunkStream = MemoryStream(data, dataSize);
|
|
ReadObjectLegacy(result, &readContext, &chunkStream);
|
|
|
|
if (readContext.WasError())
|
|
{
|
|
delete result;
|
|
result = nullptr;
|
|
}
|
|
else
|
|
{
|
|
result->SetSourceGames({ object_entry_get_source_game_legacy(entry) });
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Object* CreateObject(const rct_object_entry& entry)
|
|
{
|
|
Object* result;
|
|
uint8_t objectType = object_entry_get_type(&entry);
|
|
switch (objectType)
|
|
{
|
|
case OBJECT_TYPE_RIDE:
|
|
result = new RideObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_SMALL_SCENERY:
|
|
result = new SmallSceneryObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_LARGE_SCENERY:
|
|
result = new LargeSceneryObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_WALLS:
|
|
result = new WallObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_BANNERS:
|
|
result = new BannerObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_PATHS:
|
|
result = new FootpathObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_PATH_BITS:
|
|
result = new FootpathItemObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_SCENERY_GROUP:
|
|
result = new SceneryGroupObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_PARK_ENTRANCE:
|
|
result = new EntranceObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_WATER:
|
|
result = new WaterObject(entry);
|
|
break;
|
|
case OBJECT_TYPE_SCENARIO_TEXT:
|
|
result = nullptr;
|
|
break;
|
|
default:
|
|
throw std::runtime_error("Invalid object type");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static uint8_t ParseObjectType(const std::string& s)
|
|
{
|
|
if (s == "ride")
|
|
return OBJECT_TYPE_RIDE;
|
|
if (s == "footpath")
|
|
return OBJECT_TYPE_PATHS;
|
|
if (s == "footpath_banner")
|
|
return OBJECT_TYPE_BANNERS;
|
|
if (s == "footpath_item")
|
|
return OBJECT_TYPE_PATH_BITS;
|
|
if (s == "scenery_small")
|
|
return OBJECT_TYPE_SMALL_SCENERY;
|
|
if (s == "scenery_large")
|
|
return OBJECT_TYPE_LARGE_SCENERY;
|
|
if (s == "scenery_wall")
|
|
return OBJECT_TYPE_WALLS;
|
|
if (s == "scenery_group")
|
|
return OBJECT_TYPE_SCENERY_GROUP;
|
|
if (s == "park_entrance")
|
|
return OBJECT_TYPE_PARK_ENTRANCE;
|
|
if (s == "water")
|
|
return OBJECT_TYPE_WATER;
|
|
return 0xFF;
|
|
}
|
|
|
|
Object* CreateObjectFromZipFile(IObjectRepository& objectRepository, const std::string_view& path)
|
|
{
|
|
Object* result = nullptr;
|
|
try
|
|
{
|
|
auto archive = Zip::Open(path, ZIP_ACCESS::READ);
|
|
auto jsonBytes = archive->GetFileData("object.json");
|
|
if (jsonBytes.empty())
|
|
{
|
|
throw std::runtime_error("Unable to open object.json.");
|
|
}
|
|
|
|
json_error_t jsonLoadError;
|
|
auto jRoot = json_loadb((const char*)jsonBytes.data(), jsonBytes.size(), 0, &jsonLoadError);
|
|
if (jRoot == nullptr)
|
|
{
|
|
throw JsonException(&jsonLoadError);
|
|
}
|
|
|
|
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.data(), e.what());
|
|
|
|
delete result;
|
|
result = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Object* CreateObjectFromJsonFile(IObjectRepository& objectRepository, const std::string& path)
|
|
{
|
|
log_verbose("CreateObjectFromJsonFile(\"%s\")", path.c_str());
|
|
|
|
Object* result = nullptr;
|
|
try
|
|
{
|
|
auto jRoot = Json::ReadFromFile(path.c_str());
|
|
auto fileDataRetriever = FileSystemDataRetriever(Path::GetDirectory(path));
|
|
result = CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
|
|
json_decref(jRoot);
|
|
}
|
|
catch (const std::runtime_error& err)
|
|
{
|
|
Console::Error::WriteLine("Unable to open or read '%s': %s", path.c_str(), err.what());
|
|
|
|
delete result;
|
|
result = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Object* CreateObjectFromJson(
|
|
IObjectRepository& objectRepository, const json_t* jRoot, const IFileDataRetriever* fileRetriever)
|
|
{
|
|
log_verbose("CreateObjectFromJson(...)");
|
|
|
|
Object* result = nullptr;
|
|
auto jObjectType = json_object_get(jRoot, "objectType");
|
|
if (json_is_string(jObjectType))
|
|
{
|
|
auto objectType = ParseObjectType(json_string_value(jObjectType));
|
|
if (objectType != 0xFF)
|
|
{
|
|
auto id = json_string_value(json_object_get(jRoot, "id"));
|
|
|
|
rct_object_entry entry = {};
|
|
auto originalId = String::ToStd(json_string_value(json_object_get(jRoot, "originalId")));
|
|
auto originalName = originalId;
|
|
if (originalId.length() == 8 + 1 + 8 + 1 + 8)
|
|
{
|
|
entry.flags = std::stoul(originalId.substr(0, 8), nullptr, 16);
|
|
originalName = originalId.substr(9, 8);
|
|
entry.checksum = std::stoul(originalId.substr(18, 8), nullptr, 16);
|
|
}
|
|
auto minLength = std::min<size_t>(8, originalName.length());
|
|
memcpy(entry.name, originalName.c_str(), minLength);
|
|
|
|
result = CreateObject(entry);
|
|
auto readContext = ReadObjectContext(objectRepository, id, !gOpenRCT2NoGraphics, fileRetriever);
|
|
result->ReadJson(&readContext, jRoot);
|
|
if (readContext.WasError())
|
|
{
|
|
throw std::runtime_error("Object has errors");
|
|
}
|
|
auto sourceGames = json_object_get(jRoot, "sourceGame");
|
|
if (json_is_array(sourceGames))
|
|
{
|
|
std::vector<uint8_t> sourceGameVector;
|
|
for (size_t j = 0; j < json_array_size(sourceGames); j++)
|
|
{
|
|
sourceGameVector.push_back(ParseSourceGame(json_string_value(json_array_get(sourceGames, j))));
|
|
}
|
|
result->SetSourceGames(sourceGameVector);
|
|
}
|
|
else if (json_is_string(sourceGames))
|
|
{
|
|
auto sourceGame = json_string_value(sourceGames);
|
|
result->SetSourceGames({ ParseSourceGame(sourceGame) });
|
|
}
|
|
else
|
|
{
|
|
log_error("Object %s has an incorrect sourceGame parameter.", id);
|
|
result->SetSourceGames({ OBJECT_SOURCE_CUSTOM });
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
} // namespace ObjectFactory
|