/***************************************************************************** * Copyright (c) 2014-2024 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. *****************************************************************************/ #pragma once #include "../Cheats.h" #include "../core/MemoryStream.h" #include "../core/String.hpp" #include "../entity/Guest.h" #include "../localisation/Localisation.h" #include "../network/NetworkTypes.h" #include "../network/network.h" #include "../object/Object.h" #include "../ride/RideColour.h" #include "../ride/TrackDesign.h" #include "../world/Location.hpp" #include "../world/TileElement.h" #include "DataSerialiserTag.h" #include "Endianness.h" #include "MemoryStream.h" #include #include #include #include #include template struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const T& v) = delete; static void decode(OpenRCT2::IStream* stream, T& val) = delete; static void log(OpenRCT2::IStream* stream, const T& val) = delete; }; template struct DataSerializerTraitsEnum { using TUnderlying = std::underlying_type_t; static void encode(OpenRCT2::IStream* stream, const T& val) { TUnderlying temp = ByteSwapBE(static_cast(val)); stream->Write(&temp); } static void decode(OpenRCT2::IStream* stream, T& val) { TUnderlying temp; stream->Read(&temp); val = static_cast(ByteSwapBE(temp)); } static void log(OpenRCT2::IStream* stream, const T& val) { std::stringstream ss; ss << std::hex << std::setw(sizeof(TUnderlying) * 2) << std::setfill('0') << static_cast(val); std::string str = ss.str(); stream->Write(str.c_str(), str.size()); } }; template using DataSerializerTraits = std::conditional_t, DataSerializerTraitsEnum, DataSerializerTraitsT>; template struct DataSerializerTraitsIntegral { static void encode(OpenRCT2::IStream* stream, const T& val) { T temp = ByteSwapBE(val); stream->Write(&temp); } static void decode(OpenRCT2::IStream* stream, T& val) { T temp; stream->Read(&temp); val = ByteSwapBE(temp); } static void log(OpenRCT2::IStream* stream, const T& val) { std::stringstream ss; ss << std::hex << std::setw(sizeof(T) * 2) << std::setfill('0') << +val; std::string str = ss.str(); stream->Write(str.c_str(), str.size()); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const bool& val) { stream->Write(&val); } static void decode(OpenRCT2::IStream* stream, bool& val) { stream->Read(&val); } static void log(OpenRCT2::IStream* stream, const bool& val) { if (val) stream->Write("true", 4); else stream->Write("false", 5); } }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT : public DataSerializerTraitsIntegral { }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const std::string& str) { uint16_t len = static_cast(str.size()); uint16_t swapped = ByteSwapBE(len); stream->Write(&swapped); if (len == 0) { return; } stream->WriteArray(str.c_str(), len); } static void decode(OpenRCT2::IStream* stream, std::string& res) { uint16_t len; stream->Read(&len); len = ByteSwapBE(len); if (len == 0) { res.clear(); return; } auto str = stream->ReadArray(len); res.assign(str.get(), len); } static void log(OpenRCT2::IStream* stream, const std::string& str) { stream->Write("\"", 1); if (str.size() != 0) { stream->Write(str.data(), str.size()); } stream->Write("\"", 1); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const NetworkPlayerId_t& val) { uint32_t temp = static_cast(val.id); temp = ByteSwapBE(temp); stream->Write(&temp); } static void decode(OpenRCT2::IStream* stream, NetworkPlayerId_t& val) { uint32_t temp; stream->Read(&temp); val.id = static_cast(ByteSwapBE(temp)); } static void log(OpenRCT2::IStream* stream, const NetworkPlayerId_t& val) { char playerId[28] = {}; snprintf(playerId, sizeof(playerId), "%u", val.id); stream->Write(playerId, strlen(playerId)); int32_t playerIndex = NetworkGetPlayerIndex(val.id); if (playerIndex != -1) { const char* playerName = NetworkGetPlayerName(playerIndex); if (playerName != nullptr) { stream->Write(" \"", 2); stream->Write(playerName, strlen(playerName)); stream->Write("\"", 1); } } } }; template struct DataSerializerTraitsT> { static void encode(OpenRCT2::IStream* stream, const DataSerialiserTag& tag) { DataSerializerTraits s; s.encode(stream, tag.Data()); } static void decode(OpenRCT2::IStream* stream, DataSerialiserTag& tag) { DataSerializerTraits s; s.decode(stream, tag.Data()); } static void log(OpenRCT2::IStream* stream, const DataSerialiserTag& tag) { const char* name = tag.Name(); stream->Write(name, strlen(name)); stream->Write(" = ", 3); DataSerializerTraits s; s.log(stream, tag.Data()); stream->Write("; ", 2); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const OpenRCT2::MemoryStream& val) { DataSerializerTraits s; s.encode(stream, val.GetLength()); stream->Write(val.GetData(), val.GetLength()); } static void decode(OpenRCT2::IStream* stream, OpenRCT2::MemoryStream& val) { DataSerializerTraits s; uint32_t length = 0; s.decode(stream, length); auto buf = std::make_unique(length); stream->Read(buf.get(), length); val.Write(buf.get(), length); } static void log(OpenRCT2::IStream* stream, const OpenRCT2::MemoryStream& tag) { } }; template struct DataSerializerTraitsPODArray { static void encode(OpenRCT2::IStream* stream, const _Ty (&val)[_Size]) { uint16_t len = static_cast(_Size); uint16_t swapped = ByteSwapBE(len); stream->Write(&swapped); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.encode(stream, sub); } } static void decode(OpenRCT2::IStream* stream, _Ty (&val)[_Size]) { uint16_t len; stream->Read(&len); len = ByteSwapBE(len); if (len != _Size) throw std::runtime_error("Invalid size, can't decode"); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.decode(stream, sub); } } static void log(OpenRCT2::IStream* stream, const _Ty (&val)[_Size]) { stream->Write("{", 1); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.log(stream, sub); stream->Write("; ", 2); } stream->Write("}", 1); } }; template struct DataSerializerTraitsT : public DataSerializerTraitsPODArray { }; template struct DataSerializerTraitsT : public DataSerializerTraitsPODArray { }; template struct DataSerializerTraitsT : public DataSerializerTraitsPODArray { }; template struct DataSerializerTraitsT : public DataSerializerTraitsPODArray { }; template struct DataSerializerTraitsT : public DataSerializerTraitsPODArray { }; template struct DataSerializerTraitsT[_Size]> : public DataSerializerTraitsPODArray, _Size> { }; template struct DataSerializerTraitsT> { static void encode(OpenRCT2::IStream* stream, const std::array<_Ty, _Size>& val) { uint16_t len = static_cast(_Size); uint16_t swapped = ByteSwapBE(len); stream->Write(&swapped); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.encode(stream, sub); } } static void decode(OpenRCT2::IStream* stream, std::array<_Ty, _Size>& val) { uint16_t len; stream->Read(&len); len = ByteSwapBE(len); if (len != _Size) throw std::runtime_error("Invalid size, can't decode"); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.decode(stream, sub); } } static void log(OpenRCT2::IStream* stream, const std::array<_Ty, _Size>& val) { stream->Write("{", 1); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.log(stream, sub); stream->Write("; ", 2); } stream->Write("}", 1); } }; template struct DataSerializerTraitsT> { static void encode(OpenRCT2::IStream* stream, const std::vector<_Ty>& val) { uint16_t len = static_cast(val.size()); uint16_t swapped = ByteSwapBE(len); stream->Write(&swapped); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.encode(stream, sub); } } static void decode(OpenRCT2::IStream* stream, std::vector<_Ty>& val) { uint16_t len; stream->Read(&len); len = ByteSwapBE(len); DataSerializerTraits<_Ty> s; for (auto i = 0; i < len; ++i) { _Ty sub{}; s.decode(stream, sub); val.push_back(std::move(sub)); } } static void log(OpenRCT2::IStream* stream, const std::vector<_Ty>& val) { stream->Write("{", 1); DataSerializerTraits<_Ty> s; for (auto&& sub : val) { s.log(stream, sub); stream->Write("; ", 2); } stream->Write("}", 1); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const MapRange& v) { stream->WriteValue(ByteSwapBE(v.GetLeft())); stream->WriteValue(ByteSwapBE(v.GetTop())); stream->WriteValue(ByteSwapBE(v.GetRight())); stream->WriteValue(ByteSwapBE(v.GetBottom())); } static void decode(OpenRCT2::IStream* stream, MapRange& v) { auto l = ByteSwapBE(stream->ReadValue()); auto t = ByteSwapBE(stream->ReadValue()); auto r = ByteSwapBE(stream->ReadValue()); auto b = ByteSwapBE(stream->ReadValue()); v = MapRange(l, t, r, b); } static void log(OpenRCT2::IStream* stream, const MapRange& v) { char coords[128] = {}; snprintf( coords, sizeof(coords), "MapRange(l = %d, t = %d, r = %d, b = %d)", v.GetLeft(), v.GetTop(), v.GetRight(), v.GetBottom()); stream->Write(coords, strlen(coords)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TileElement& tileElement) { stream->WriteValue(tileElement.Type); stream->WriteValue(tileElement.Flags); stream->WriteValue(tileElement.BaseHeight); stream->WriteValue(tileElement.ClearanceHeight); stream->WriteValue(tileElement.Owner); for (auto v : tileElement.Pad05) { stream->WriteValue(v); } for (auto v : tileElement.Pad08) { stream->WriteValue(v); } } static void decode(OpenRCT2::IStream* stream, TileElement& tileElement) { tileElement.Type = stream->ReadValue(); tileElement.Flags = stream->ReadValue(); tileElement.BaseHeight = stream->ReadValue(); tileElement.ClearanceHeight = stream->ReadValue(); tileElement.Owner = stream->ReadValue(); for (auto& v : tileElement.Pad05) { v = stream->ReadValue(); } for (auto& v : tileElement.Pad08) { v = stream->ReadValue(); } } static void log(OpenRCT2::IStream* stream, const TileElement& tileElement) { char msg[128] = {}; snprintf( msg, sizeof(msg), "TileElement(type = %u, flags = %u, BaseHeight = %u)", tileElement.Type, tileElement.Flags, tileElement.BaseHeight); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TileCoordsXY& coords) { stream->WriteValue(ByteSwapBE(coords.x)); stream->WriteValue(ByteSwapBE(coords.y)); } static void decode(OpenRCT2::IStream* stream, TileCoordsXY& coords) { auto x = ByteSwapBE(stream->ReadValue()); auto y = ByteSwapBE(stream->ReadValue()); coords = TileCoordsXY{ x, y }; } static void log(OpenRCT2::IStream* stream, const TileCoordsXY& coords) { char msg[128] = {}; snprintf(msg, sizeof(msg), "TileCoordsXY(x = %d, y = %d)", coords.x, coords.y); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const CoordsXY& coords) { stream->WriteValue(ByteSwapBE(coords.x)); stream->WriteValue(ByteSwapBE(coords.y)); } static void decode(OpenRCT2::IStream* stream, CoordsXY& coords) { auto x = ByteSwapBE(stream->ReadValue()); auto y = ByteSwapBE(stream->ReadValue()); coords = CoordsXY{ x, y }; } static void log(OpenRCT2::IStream* stream, const CoordsXY& coords) { char msg[128] = {}; snprintf(msg, sizeof(msg), "CoordsXY(x = %d, y = %d)", coords.x, coords.y); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const CoordsXYZ& coord) { stream->WriteValue(ByteSwapBE(coord.x)); stream->WriteValue(ByteSwapBE(coord.y)); stream->WriteValue(ByteSwapBE(coord.z)); } static void decode(OpenRCT2::IStream* stream, CoordsXYZ& coord) { auto x = ByteSwapBE(stream->ReadValue()); auto y = ByteSwapBE(stream->ReadValue()); auto z = ByteSwapBE(stream->ReadValue()); coord = CoordsXYZ{ x, y, z }; } static void log(OpenRCT2::IStream* stream, const CoordsXYZ& coord) { char msg[128] = {}; snprintf(msg, sizeof(msg), "CoordsXYZ(x = %d, y = %d, z = %d)", coord.x, coord.y, coord.z); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const CoordsXYZD& coord) { stream->WriteValue(ByteSwapBE(coord.x)); stream->WriteValue(ByteSwapBE(coord.y)); stream->WriteValue(ByteSwapBE(coord.z)); stream->WriteValue(ByteSwapBE(coord.direction)); } static void decode(OpenRCT2::IStream* stream, CoordsXYZD& coord) { auto x = ByteSwapBE(stream->ReadValue()); auto y = ByteSwapBE(stream->ReadValue()); auto z = ByteSwapBE(stream->ReadValue()); auto d = ByteSwapBE(stream->ReadValue()); coord = CoordsXYZD{ x, y, z, d }; } static void log(OpenRCT2::IStream* stream, const CoordsXYZD& coord) { char msg[128] = {}; snprintf( msg, sizeof(msg), "CoordsXYZD(x = %d, y = %d, z = %d, direction = %d)", coord.x, coord.y, coord.z, coord.direction); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const NetworkCheatType_t& val) { uint32_t temp = ByteSwapBE(val.id); stream->Write(&temp); } static void decode(OpenRCT2::IStream* stream, NetworkCheatType_t& val) { uint32_t temp; stream->Read(&temp); val.id = ByteSwapBE(temp); } static void log(OpenRCT2::IStream* stream, const NetworkCheatType_t& val) { const char* cheatName = CheatsGetName(static_cast(val.id)); stream->Write(cheatName, strlen(cheatName)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const RCTObjectEntry& val) { uint32_t temp = ByteSwapBE(val.flags); stream->Write(&temp); stream->WriteArray(val.nameWOC, 12); } static void decode(OpenRCT2::IStream* stream, RCTObjectEntry& val) { uint32_t temp; stream->Read(&temp); val.flags = ByteSwapBE(temp); auto str = stream->ReadArray(12); memcpy(val.nameWOC, str.get(), 12); } static void log(OpenRCT2::IStream* stream, const RCTObjectEntry& val) { stream->WriteArray(val.name, 8); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const ObjectEntryDescriptor& val) { stream->WriteValue(static_cast(val.Generation)); if (val.Generation == ObjectGeneration::DAT) { DataSerializerTraits s; s.encode(stream, val.Entry); } else { stream->WriteValue(static_cast(val.GetType())); stream->WriteString(val.Identifier); } } static void decode(OpenRCT2::IStream* stream, ObjectEntryDescriptor& val) { auto generation = static_cast(stream->ReadValue()); if (generation == ObjectGeneration::DAT) { DataSerializerTraits s; RCTObjectEntry entry; s.decode(stream, entry); val = ObjectEntryDescriptor(entry); } else { auto type = static_cast(stream->ReadValue()); auto identifier = stream->ReadStdString(); val = ObjectEntryDescriptor(type, identifier); } } static void log(OpenRCT2::IStream* stream, const ObjectEntryDescriptor& val) { auto identifier = std::string(val.GetName()); char msg[128] = {}; snprintf(msg, sizeof(msg), "ObjectEntryDescriptor[%s]", identifier.c_str()); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TrackDesignTrackElement& val) { stream->Write(&val.Type); stream->Write(&val.Flags); stream->Write(&val.ColourScheme); stream->Write(&val.StationIndex); stream->Write(&val.BrakeBoosterSpeed); stream->Write(&val.SeatRotation); } static void decode(OpenRCT2::IStream* stream, TrackDesignTrackElement& val) { stream->Read(&val.Type); stream->Read(&val.Flags); stream->Read(&val.ColourScheme); stream->Read(&val.StationIndex); stream->Read(&val.BrakeBoosterSpeed); stream->Read(&val.SeatRotation); } static void log(OpenRCT2::IStream* stream, const TrackDesignTrackElement& val) { char msg[128] = {}; snprintf(msg, sizeof(msg), "TrackDesignTrackElement(type = %d, flags = %d)", val.Type, val.Flags); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TrackDesignMazeElement& val) { stream->Write(&val.location); stream->Write(&val.mazeEntry); } static void decode(OpenRCT2::IStream* stream, TrackDesignMazeElement& val) { stream->Read(&val.location); stream->Read(&val.mazeEntry); } static void log(OpenRCT2::IStream* stream, const TrackDesignMazeElement& val) { char msg[128] = {}; snprintf( msg, sizeof(msg), "TrackDesignMazeElement(x = %d, y = %d, entry = %d)", val.location.x, val.location.y, val.mazeEntry); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TrackDesignEntranceElement& val) { stream->Write(&val.location); stream->Write(&val.isExit); } static void decode(OpenRCT2::IStream* stream, TrackDesignEntranceElement& val) { stream->Read(&val.location); stream->Read(&val.isExit); } static void log(OpenRCT2::IStream* stream, const TrackDesignEntranceElement& val) { char msg[128] = {}; snprintf( msg, sizeof(msg), "TrackDesignEntranceElement(x = %d, y = %d, z = %d, dir = %d, isExit = %d)", val.location.x, val.location.y, val.location.z, val.location.direction, val.isExit); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TrackDesignSceneryElement& val) { stream->Write(&val.loc); stream->Write(&val.flags); stream->Write(&val.primary_colour); stream->Write(&val.secondary_colour); DataSerializerTraits s; s.encode(stream, val.scenery_object); } static void decode(OpenRCT2::IStream* stream, TrackDesignSceneryElement& val) { stream->Read(&val.loc); stream->Read(&val.flags); stream->Read(&val.primary_colour); stream->Read(&val.secondary_colour); DataSerializerTraits s; s.decode(stream, val.scenery_object); } static void log(OpenRCT2::IStream* stream, const TrackDesignSceneryElement& val) { char msg[128] = {}; snprintf( msg, sizeof(msg), "TrackDesignSceneryElement(x = %d, y = %d, z = %d, flags = %d, colour1 = %d, colour2 = %d)", val.loc.x, val.loc.y, val.loc.z, val.flags, val.primary_colour, val.secondary_colour); stream->Write(msg, strlen(msg)); auto identifier = val.scenery_object.GetName(); stream->WriteArray(identifier.data(), identifier.size()); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const VehicleColour& val) { stream->Write(&val.Body); stream->Write(&val.Trim); stream->Write(&val.Tertiary); } static void decode(OpenRCT2::IStream* stream, VehicleColour& val) { stream->Read(&val.Body); stream->Read(&val.Trim); stream->Read(&val.Tertiary); } static void log(OpenRCT2::IStream* stream, const VehicleColour& val) { char msg[128] = {}; snprintf(msg, sizeof(msg), "VehicleColour(Body = %d, Trim = %d, Tertiary = %d)", val.Body, val.Trim, val.Tertiary); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const IntensityRange& val) { uint8_t temp = uint8_t(val); stream->Write(&temp); } static void decode(OpenRCT2::IStream* stream, IntensityRange& val) { auto temp = stream->ReadValue(); val = IntensityRange(temp); } static void log(OpenRCT2::IStream* stream, const IntensityRange& val) { char msg[128] = {}; snprintf(msg, sizeof(msg), "IntensityRange(min = %d, max = %d)", val.GetMinimum(), val.GetMaximum()); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const PeepThought& val) { stream->Write(&val.type); stream->Write(&val.item); stream->Write(&val.freshness); stream->Write(&val.fresh_timeout); } static void decode(OpenRCT2::IStream* stream, PeepThought& val) { stream->Read(&val.type); stream->Read(&val.item); stream->Read(&val.freshness); stream->Read(&val.fresh_timeout); } static void log(OpenRCT2::IStream* stream, const PeepThought& val) { char msg[128] = {}; snprintf( msg, sizeof(msg), "PeepThought(type = %d, item = %u, freshness = %d, freshtimeout = %d)", static_cast(val.type), val.item, val.freshness, val.fresh_timeout); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const TileCoordsXYZD& coord) { stream->WriteValue(ByteSwapBE(coord.x)); stream->WriteValue(ByteSwapBE(coord.y)); stream->WriteValue(ByteSwapBE(coord.z)); stream->WriteValue(ByteSwapBE(coord.direction)); } static void decode(OpenRCT2::IStream* stream, TileCoordsXYZD& coord) { auto x = ByteSwapBE(stream->ReadValue()); auto y = ByteSwapBE(stream->ReadValue()); auto z = ByteSwapBE(stream->ReadValue()); auto d = ByteSwapBE(stream->ReadValue()); coord = TileCoordsXYZD{ x, y, z, d }; } static void log(OpenRCT2::IStream* stream, const TileCoordsXYZD& coord) { char msg[128] = {}; snprintf( msg, sizeof(msg), "TileCoordsXYZD(x = %d, y = %d, z = %d, direction = %d)", coord.x, coord.y, coord.z, coord.direction); stream->Write(msg, strlen(msg)); } }; template struct DataSerializerTraitsT> { static void encode(OpenRCT2::IStream* stream, const TIdentifier& id) { stream->WriteValue(ByteSwapBE(id.ToUnderlying())); } static void decode(OpenRCT2::IStream* stream, TIdentifier& id) { auto temp = ByteSwapBE(stream->ReadValue()); id = TIdentifier::FromUnderlying(temp); } static void log(OpenRCT2::IStream* stream, const TIdentifier& id) { char msg[128] = {}; snprintf(msg, sizeof(msg), "Id(%u)", static_cast(id.ToUnderlying())); stream->Write(msg, strlen(msg)); } }; template<> struct DataSerializerTraitsT { static void encode(OpenRCT2::IStream* stream, const Banner& banner) { DataSerializerTraits().encode(stream, banner.id); DataSerializerTraits().encode(stream, banner.type); stream->WriteValue(banner.flags); stream->WriteString(banner.text); stream->WriteValue(banner.colour); DataSerializerTraits().encode(stream, banner.ride_index); stream->WriteValue(banner.text_colour); DataSerializerTraits().encode(stream, banner.position); } static void decode(OpenRCT2::IStream* stream, Banner& banner) { DataSerializerTraits().decode(stream, banner.id); DataSerializerTraits().decode(stream, banner.type); stream->Read(&banner.flags); banner.text = stream->ReadStdString(); stream->Read(&banner.colour); DataSerializerTraits().decode(stream, banner.ride_index); stream->Read(&banner.text_colour); DataSerializerTraits().decode(stream, banner.position); } static void log(OpenRCT2::IStream* stream, const Banner& banner) { char msg[128] = {}; snprintf( msg, sizeof(msg), "Banner(x = %d, y = %d, text = %s)", banner.position.x, banner.position.y, banner.text.c_str()); stream->Write(msg, strlen(msg)); } };