From 7ab27239367bda54b20a5b09be931b77715c9790 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 Feb 2017 02:33:15 +0000 Subject: [PATCH 1/7] Create a reader class for sawyer encoded chunks. --- src/openrct2/libopenrct2.vcxproj | 2 + src/openrct2/rct12/SawyerChunkReader.cpp | 137 +++++++++++++++++++++++ src/openrct2/rct12/SawyerChunkReader.h | 86 ++++++++++++++ src/openrct2/rct2/S6Importer.cpp | 36 +++--- 4 files changed, 244 insertions(+), 17 deletions(-) create mode 100644 src/openrct2/rct12/SawyerChunkReader.cpp create mode 100644 src/openrct2/rct12/SawyerChunkReader.h diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 3d9c3089a1..b3877904ce 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -74,6 +74,7 @@ + @@ -417,6 +418,7 @@ + diff --git a/src/openrct2/rct12/SawyerChunkReader.cpp b/src/openrct2/rct12/SawyerChunkReader.cpp new file mode 100644 index 0000000000..a76d25cd46 --- /dev/null +++ b/src/openrct2/rct12/SawyerChunkReader.cpp @@ -0,0 +1,137 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Exception.hpp" +#include "../core/IStream.hpp" +#include "../core/Math.hpp" +#include "SawyerChunkReader.h" +#include "SawyerEncoding.h" + +extern "C" +{ + #include "../util/sawyercoding.h" +} + +// Allow chunks to be uncompressed to a maximum of 16 MiB +constexpr size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024; + +class SawyerChunkException : public IOException +{ +public: + explicit SawyerChunkException(const char * message) : IOException(message) { } + explicit SawyerChunkException(const std::string &message) : IOException(message) { } +}; + +SawyerChunk::SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length) +{ + _encoding = encoding; + _data = data; + _length = length; +} + +SawyerChunk::~SawyerChunk() +{ + Memory::Free(_data); +} + +SawyerChunkReader::SawyerChunkReader(IStream * stream) + : _stream(stream) +{ +} + +void SawyerChunkReader::SkipChunk() +{ + uint64 originalPosition = _stream->GetPosition(); + try + { + auto header = _stream->ReadValue(); + _stream->Seek(header.length, STREAM_SEEK_CURRENT); + } + catch (Exception) + { + // Rewind stream back to original position + _stream->SetPosition(originalPosition); + throw; + } +} + +std::shared_ptr SawyerChunkReader::ReadChunk() +{ + uint64 originalPosition = _stream->GetPosition(); + try + { + auto header = _stream->ReadValue(); + switch (header.encoding) { + case CHUNK_ENCODING_NONE: + case CHUNK_ENCODING_RLE: + case CHUNK_ENCODING_RLECOMPRESSED: + case CHUNK_ENCODING_ROTATE: + { + std::unique_ptr compressedData = std::unique_ptr(Memory::Allocate(header.length)); + if (_stream->TryRead(compressedData.get(), header.length) != header.length) + { + throw SawyerChunkException("Corrupt chunk size."); + } + + // Allow 16MiB for chunk data + size_t bufferSize = MAX_UNCOMPRESSED_CHUNK_SIZE; + uint8 * buffer = Memory::Allocate(bufferSize); + if (buffer == nullptr) + { + throw Exception("Unable to allocate buffer."); + } + + size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer, compressedData.get(), header, bufferSize); + buffer = Memory::Reallocate(buffer, uncompressedLength); + if (buffer == nullptr) + { + throw Exception("Unable to reallocate buffer."); + } + + return std::make_shared((SAWYER_ENCODING)header.encoding, buffer, uncompressedLength); + } + default: + throw SawyerChunkException("Invalid chunk encoding."); + } + } + catch (Exception) + { + // Rewind stream back to original position + _stream->SetPosition(originalPosition); + throw; + } +} + +void SawyerChunkReader::ReadChunk(void * dst, size_t length) +{ + auto chunk = ReadChunk(); + const void * chunkData = chunk->GetData(); + size_t chunkLength = chunk->GetLength(); + if (chunkLength > length) + { + Memory::Copy(dst, chunkData, length); + } + else + { + Memory::Copy(dst, chunkData, chunkLength); + size_t remainingLength = length - chunkLength; + if (remainingLength > 0) + { + void * offset = (void *)((uintptr_t)dst + chunkLength); + Memory::Set(offset, 0, remainingLength); + } + } +} diff --git a/src/openrct2/rct12/SawyerChunkReader.h b/src/openrct2/rct12/SawyerChunkReader.h new file mode 100644 index 0000000000..4448f1e816 --- /dev/null +++ b/src/openrct2/rct12/SawyerChunkReader.h @@ -0,0 +1,86 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include "../common.h" + +interface IStream; + +/** + * The type of encoding / compression for a sawyer encoded chunk. + */ +enum class SAWYER_ENCODING : uint8 +{ + NONE, + RLE, + RLECOMPRESSED, + ROTATE, +}; + +/** + * Represents a sawyer encoded chunk. + */ +class SawyerChunk final +{ +private: + void * _data = nullptr; + size_t _length = 0; + SAWYER_ENCODING _encoding = SAWYER_ENCODING::NONE; + +public: + const void * GetData() { return _data; } + size_t GetLength() { return _length; } + SAWYER_ENCODING GetEncoding() { return _encoding; } + + SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length); + ~SawyerChunk(); +}; + +/** + * Reads sawyer encoding chunks from a data stream. This can be used to read + * SC6, SV6 and RCT2 objects. + */ +class SawyerChunkReader final +{ +private: + IStream * const _stream = nullptr; + +public: + SawyerChunkReader(IStream * stream); + + /** + * Skips the next chunk in the stream without decoding or reading its data + * into RAM. + */ + void SkipChunk(); + + /** + * Reads the next chunk from the stream. + */ + std::shared_ptr ReadChunk(); + + /** + * Reads the next chunk from the stream and copies it directly to the + * destination buffer. If the chunk is larger than length, only length + * is copied. If the chunk is smaller than length, the remaining space + * is padded with zero. + * @param dst The destination buffer. + * @param length The size of the destination buffer. + */ + void ReadChunk(void * dst, size_t length); +}; diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 12b5a89e22..e002e25e14 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -25,6 +25,7 @@ #include "../object/ObjectRepository.h" #include "../ParkImporter.h" #include "../rct12/SawyerEncoding.h" +#include "../rct12/SawyerChunkReader.h" extern "C" { @@ -110,7 +111,8 @@ public: throw IOException("Invalid checksum."); } - SawyerEncoding::ReadChunkTolerant(&_s6.header, sizeof(_s6.header), stream); + auto chunkReader = SawyerChunkReader(stream); + chunkReader.ReadChunk(&_s6.header, sizeof(_s6.header)); log_verbose("saved game classic_flag = 0x%02x\n", _s6.header.classic_flag); if (isScenario) @@ -119,7 +121,7 @@ public: { throw Exception("Park is not a scenario."); } - SawyerEncoding::ReadChunkTolerant(&_s6.info, sizeof(_s6.info), stream); + chunkReader.ReadChunk(&_s6.info, sizeof(_s6.info)); } else { @@ -139,24 +141,24 @@ public: if (isScenario) { - SawyerEncoding::ReadChunkTolerant(&_s6.objects, sizeof(_s6.objects), stream); - SawyerEncoding::ReadChunkTolerant(&_s6.elapsed_months, 16, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.map_elements, sizeof(_s6.map_elements), stream); - SawyerEncoding::ReadChunkTolerant(&_s6.next_free_map_element_pointer_index, 2560076, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.guests_in_park, 4, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.last_guests_in_park, 8, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.park_rating, 2, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.active_research_types, 1082, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.current_expenditure, 16, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.park_value, 4, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.completed_company_value, 483816, stream); + chunkReader.ReadChunk(&_s6.objects, sizeof(_s6.objects)); + chunkReader.ReadChunk(&_s6.elapsed_months, 16); + chunkReader.ReadChunk(&_s6.map_elements, sizeof(_s6.map_elements)); + chunkReader.ReadChunk(&_s6.next_free_map_element_pointer_index, 2560076); + chunkReader.ReadChunk(&_s6.guests_in_park, 4); + chunkReader.ReadChunk(&_s6.last_guests_in_park, 8); + chunkReader.ReadChunk(&_s6.park_rating, 2); + chunkReader.ReadChunk(&_s6.active_research_types, 1082); + chunkReader.ReadChunk(&_s6.current_expenditure, 16); + chunkReader.ReadChunk(&_s6.park_value, 4); + chunkReader.ReadChunk(&_s6.completed_company_value, 483816); } else { - SawyerEncoding::ReadChunkTolerant(&_s6.objects, sizeof(_s6.objects), stream); - SawyerEncoding::ReadChunkTolerant(&_s6.elapsed_months, 16, stream); - SawyerEncoding::ReadChunkTolerant(&_s6.map_elements, sizeof(_s6.map_elements), stream); - SawyerEncoding::ReadChunkTolerant(&_s6.next_free_map_element_pointer_index, 3048816, stream); + chunkReader.ReadChunk(&_s6.objects, sizeof(_s6.objects)); + chunkReader.ReadChunk(&_s6.elapsed_months, 16); + chunkReader.ReadChunk(&_s6.map_elements, sizeof(_s6.map_elements)); + chunkReader.ReadChunk(&_s6.next_free_map_element_pointer_index, 3048816); } } From 5eee31f69a7b90703fcb4b7f67bf44f373b2b563 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 Feb 2017 12:18:07 +0000 Subject: [PATCH 2/7] Use SawyerChunkReader instead of SawyerEncoding --- src/openrct2/FileClassifier.cpp | 11 ++++--- src/openrct2/object/ObjectRepository.cpp | 18 ++++------- src/openrct2/rct12/SawyerChunkReader.cpp | 1 - src/openrct2/rct12/SawyerChunkReader.h | 13 ++++++++ src/openrct2/scenario/ScenarioRepository.cpp | 32 ++++++++------------ 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/openrct2/FileClassifier.cpp b/src/openrct2/FileClassifier.cpp index 897328a662..b1d978f641 100644 --- a/src/openrct2/FileClassifier.cpp +++ b/src/openrct2/FileClassifier.cpp @@ -16,7 +16,7 @@ #include "core/FileStream.hpp" #include "FileClassifier.h" -#include "rct12/SawyerEncoding.h" +#include "rct12/SawyerChunkReader.h" extern "C" { @@ -71,9 +71,10 @@ bool TryClassifyFile(IStream * stream, ClassifiedFile * result) static bool TryClassifyAsS6(IStream * stream, ClassifiedFile * result) { - rct_s6_header s6Header; - if (SawyerEncoding::TryReadChunk(&s6Header, stream)) + try { + auto chunkReader = SawyerChunkReader(stream); + auto s6Header = chunkReader.ReadChunkAs(); if (s6Header.type == S6_TYPE_SAVEDGAME) { result->Type = FILE_TYPE::SAVED_GAME; @@ -85,7 +86,9 @@ static bool TryClassifyAsS6(IStream * stream, ClassifiedFile * result) result->Version = s6Header.version; return true; } - + catch (Exception) + { + } return false; } diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index 4486075fa1..c498ce4367 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -31,7 +31,7 @@ #include "../core/Stopwatch.hpp" #include "../core/String.hpp" #include "../PlatformEnvironment.h" -#include "../rct12/SawyerEncoding.h" +#include "../rct12/SawyerChunkReader.h" #include "../scenario/ScenarioRepository.h" #include "Object.h" #include "ObjectFactory.h" @@ -226,25 +226,19 @@ public: bool TryExportPackedObject(IStream * stream) override { + auto chunkReader = SawyerChunkReader(stream); + // Check if we already have this object rct_object_entry entry = stream->ReadValue(); if (FindObject(&entry) != nullptr) { - SawyerEncoding::SkipChunk(stream); + chunkReader.SkipChunk(); } else { // Read object and save to new file - size_t chunkSize; - void * chunk = SawyerEncoding::ReadChunk(stream, &chunkSize); - if (chunk == nullptr) - { - log_error("Failed to reallocate buffer for packed object."); - return false; - } - - AddObject(&entry, chunk, chunkSize); - Memory::Free(chunk); + std::shared_ptr chunk = chunkReader.ReadChunk(); + AddObject(&entry, chunk->GetData(), chunk->GetLength()); } return true; } diff --git a/src/openrct2/rct12/SawyerChunkReader.cpp b/src/openrct2/rct12/SawyerChunkReader.cpp index a76d25cd46..62fbe3326a 100644 --- a/src/openrct2/rct12/SawyerChunkReader.cpp +++ b/src/openrct2/rct12/SawyerChunkReader.cpp @@ -18,7 +18,6 @@ #include "../core/IStream.hpp" #include "../core/Math.hpp" #include "SawyerChunkReader.h" -#include "SawyerEncoding.h" extern "C" { diff --git a/src/openrct2/rct12/SawyerChunkReader.h b/src/openrct2/rct12/SawyerChunkReader.h index 4448f1e816..3217feb0b2 100644 --- a/src/openrct2/rct12/SawyerChunkReader.h +++ b/src/openrct2/rct12/SawyerChunkReader.h @@ -83,4 +83,17 @@ public: * @param length The size of the destination buffer. */ void ReadChunk(void * dst, size_t length); + + /** + * Reads the next chunk from the stream into a buffer returned as the + * specified type. If the chunk is smaller than the size of the type + * then the remaining space is padded with zero. + */ + template + T ReadChunkAs() + { + T result; + ReadChunk(&result, sizeof(result)); + return result; + } }; diff --git a/src/openrct2/scenario/ScenarioRepository.cpp b/src/openrct2/scenario/ScenarioRepository.cpp index a5f394ed16..a060df524e 100644 --- a/src/openrct2/scenario/ScenarioRepository.cpp +++ b/src/openrct2/scenario/ScenarioRepository.cpp @@ -26,7 +26,7 @@ #include "../core/Util.hpp" #include "../ParkImporter.h" #include "../PlatformEnvironment.h" -#include "../rct12/SawyerEncoding.h" +#include "../rct12/SawyerChunkReader.h" #include "ScenarioRepository.h" #include "ScenarioSources.h" @@ -336,26 +336,18 @@ private: { // RCT2 scenario auto fs = FileStream(path, FILE_MODE_OPEN); - rct_s6_header header; - if (SawyerEncoding::TryReadChunk(&header, &fs)) + auto chunkReader = SawyerChunkReader(&fs); + + rct_s6_header header = chunkReader.ReadChunkAs(); + if (header.type == S6_TYPE_SCENARIO) { - if (header.type == S6_TYPE_SCENARIO) - { - rct_s6_info info; - if (SawyerEncoding::TryReadChunk(&info, &fs)) - { - *entry = CreateNewScenarioEntry(path, timestamp, &info); - return true; - } - else - { - throw new IOException("Unable to read S6_INFO chunk."); - } - } - else - { - log_verbose("%s is not a scenario", path.c_str()); - } + rct_s6_info info = chunkReader.ReadChunkAs(); + *entry = CreateNewScenarioEntry(path, timestamp, &info); + return true; + } + else + { + log_verbose("%s is not a scenario", path.c_str()); } } } From 0dcab94411f101b02fc84409975a979603b28dbf Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 Feb 2017 12:26:52 +0000 Subject: [PATCH 3/7] Remove chunk code from SawyerEncoding --- src/openrct2/rct12/SawyerEncoding.cpp | 173 ++++---------------------- src/openrct2/rct12/SawyerEncoding.h | 12 -- 2 files changed, 27 insertions(+), 158 deletions(-) diff --git a/src/openrct2/rct12/SawyerEncoding.cpp b/src/openrct2/rct12/SawyerEncoding.cpp index edc55ce3ab..a25227247e 100644 --- a/src/openrct2/rct12/SawyerEncoding.cpp +++ b/src/openrct2/rct12/SawyerEncoding.cpp @@ -14,171 +14,52 @@ *****************************************************************************/ #pragma endregion -#include #include "../core/IStream.hpp" #include "../core/Math.hpp" #include "SawyerEncoding.h" -extern "C" -{ - #include "../util/sawyercoding.h" -} - namespace SawyerEncoding { - void SkipChunk(IStream * stream) - { - auto header = stream->ReadValue(); - stream->Seek(header.length, STREAM_SEEK_CURRENT); - } - - void * ReadChunk(IStream * stream, size_t * outSize) - { - uint64 originalPosition = stream->GetPosition(); - - auto header = stream->ReadValue(); - switch (header.encoding) { - case CHUNK_ENCODING_NONE: - case CHUNK_ENCODING_RLE: - case CHUNK_ENCODING_RLECOMPRESSED: - case CHUNK_ENCODING_ROTATE: - { - std::unique_ptr compressedData = std::unique_ptr(Memory::Allocate(header.length)); - if (stream->TryRead(compressedData.get(), header.length) != header.length) - { - throw IOException("Corrupt chunk size."); - } - - // Allow 16MiB for chunk data - size_t bufferSize = 16 * 1024 * 1024; - uint8 * buffer = Memory::Allocate(bufferSize); - if (buffer == nullptr) - { - throw Exception("Unable to allocate buffer."); - } - - size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer, compressedData.get(), header, bufferSize); - buffer = Memory::Reallocate(buffer, uncompressedLength); - if (buffer == nullptr) - { - throw Exception("Unable to reallocate buffer."); - } - - if (outSize != nullptr) *outSize = uncompressedLength; - return buffer; - } - default: - stream->SetPosition(originalPosition); - throw IOException("Invalid chunk encoding."); - } - } - - void ReadChunk(void * dst, size_t expectedSize, IStream * stream) - { - if (!TryReadChunk(dst, expectedSize, stream)) - { - throw IOException("Invalid or incorrect chunk size."); - } - } - - void ReadChunkTolerant(void * dst, size_t expectedSize, IStream * stream) - { - uint64 originalPosition = stream->GetPosition(); - - auto header = stream->ReadValue(); - switch (header.encoding) { - case CHUNK_ENCODING_NONE: - case CHUNK_ENCODING_RLE: - case CHUNK_ENCODING_RLECOMPRESSED: - case CHUNK_ENCODING_ROTATE: - { - std::unique_ptr compressedData = std::unique_ptr(Memory::Allocate(header.length)); - if (stream->TryRead(compressedData.get(), header.length) != header.length) - { - throw IOException("Corrupt chunk size."); - } - - // Allow 16MiB for chunk data - size_t bufferSize = 16 * 1024 * 1024; - std::unique_ptr buffer = std::unique_ptr(Memory::Allocate(bufferSize)); - size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer.get(), compressedData.get(), header, bufferSize); - size_t copyLength = Math::Min(uncompressedLength, expectedSize); - - Memory::Set(dst, 0, expectedSize); - Memory::Copy(dst, buffer.get(), copyLength); - break; - } - default: - stream->SetPosition(originalPosition); - throw IOException("Invalid chunk encoding."); - } - } - - bool TryReadChunk(void * dst, size_t expectedSize, IStream * stream) - { - uint64 originalPosition = stream->GetPosition(); - - bool success = false; - auto header = stream->ReadValue(); - switch (header.encoding) { - case CHUNK_ENCODING_NONE: - case CHUNK_ENCODING_RLE: - case CHUNK_ENCODING_RLECOMPRESSED: - case CHUNK_ENCODING_ROTATE: - uint8 * compressedData = Memory::Allocate(header.length); - if (stream->TryRead(compressedData, header.length) == header.length) - { - size_t uncompressedLength = sawyercoding_read_chunk_buffer((uint8 *)dst, compressedData, header, expectedSize); - if (uncompressedLength == expectedSize) - { - success = true; - } - } - Memory::Free(compressedData); - break; - } - - if (!success) - { - stream->SetPosition(originalPosition); - } - return success; - } - bool ValidateChecksum(IStream * stream) { - // Get data size uint64 initialPosition = stream->GetPosition(); uint64 dataSize = stream->GetLength() - initialPosition; if (dataSize < 8) { return false; } - dataSize -= 4; - // Calculate checksum - uint32 checksum = 0; - do + try { - uint8 buffer[4096]; - uint64 bufferSize = Math::Min(dataSize, sizeof(buffer)); - stream->Read(buffer, bufferSize); - - for (uint64 i = 0; i < bufferSize; i++) + // Calculate checksum + uint32 checksum = 0; + do { - checksum += buffer[i]; + uint8 buffer[4096]; + uint64 bufferSize = Math::Min(dataSize, sizeof(buffer)); + stream->Read(buffer, bufferSize); + + for (uint64 i = 0; i < bufferSize; i++) + { + checksum += buffer[i]; + } + + dataSize -= bufferSize; } + while (dataSize != 0); - dataSize -= bufferSize; + // Read file checksum + uint32 fileChecksum = stream->ReadValue(); + + // Rewind back to original position + stream->SetPosition(initialPosition); + return checksum == fileChecksum; + } + catch (Exception) + { + // Rewind back to original position + stream->SetPosition(initialPosition); + return false; } - while (dataSize != 0); - - // Read file checksum - uint32 fileChecksum = stream->ReadValue(); - - // Rewind - stream->SetPosition(initialPosition); - - return checksum == fileChecksum; } } diff --git a/src/openrct2/rct12/SawyerEncoding.h b/src/openrct2/rct12/SawyerEncoding.h index 10ba234a28..104106b514 100644 --- a/src/openrct2/rct12/SawyerEncoding.h +++ b/src/openrct2/rct12/SawyerEncoding.h @@ -22,17 +22,5 @@ interface IStream; namespace SawyerEncoding { - void SkipChunk(IStream * stream); - void * ReadChunk(IStream * stream, size_t * outSize); - void ReadChunk(void * dst, size_t expectedSize, IStream * stream); - void ReadChunkTolerant(void * dst, size_t expectedSize, IStream * stream); - bool TryReadChunk(void * dst, size_t expectedSize, IStream * stream); - - template - bool TryReadChunk(T * dst, IStream * stream) - { - return TryReadChunk(dst, sizeof(T), stream); - } - bool ValidateChecksum(IStream * stream); } From 2518362112fea185f0f6d2e89b0d1acb6c5de4da Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 Feb 2017 15:45:23 +0000 Subject: [PATCH 4/7] Use streams for SV6 export --- src/openrct2/core/FileStream.hpp | 8 +- src/openrct2/libopenrct2.vcxproj | 4 + src/openrct2/network/network.cpp | 8 +- src/openrct2/object/ObjectManager.cpp | 17 ++ src/openrct2/object/ObjectManager.h | 9 +- src/openrct2/object/ObjectRepository.cpp | 94 ++++++---- src/openrct2/object/ObjectRepository.h | 9 +- src/openrct2/rct12/SawyerChunk.cpp | 30 +++ src/openrct2/rct12/SawyerChunk.h | 49 +++++ src/openrct2/rct12/SawyerChunkReader.cpp | 12 -- src/openrct2/rct12/SawyerChunkReader.h | 31 +--- src/openrct2/rct12/SawyerChunkWriter.cpp | 50 +++++ src/openrct2/rct12/SawyerChunkWriter.h | 57 ++++++ src/openrct2/rct12/SawyerEncoding.cpp | 1 + src/openrct2/rct2/S6Exporter.cpp | 227 +++++------------------ src/openrct2/rct2/S6Exporter.h | 11 +- src/openrct2/rct2/S6Importer.cpp | 2 +- 17 files changed, 344 insertions(+), 275 deletions(-) create mode 100644 src/openrct2/rct12/SawyerChunk.cpp create mode 100644 src/openrct2/rct12/SawyerChunk.h create mode 100644 src/openrct2/rct12/SawyerChunkWriter.cpp create mode 100644 src/openrct2/rct12/SawyerChunkWriter.h diff --git a/src/openrct2/core/FileStream.hpp b/src/openrct2/core/FileStream.hpp index 0b17273324..d8ff49cd95 100644 --- a/src/openrct2/core/FileStream.hpp +++ b/src/openrct2/core/FileStream.hpp @@ -20,6 +20,7 @@ #include #include "IStream.hpp" +#include "Math.hpp" enum { @@ -57,7 +58,7 @@ public: break; case FILE_MODE_WRITE: mode = "wb"; - _canRead = false; + _canRead = true; _canWrite = true; break; default: @@ -82,7 +83,7 @@ public: _canWrite = false; break; case FILE_MODE_WRITE: - _canRead = false; + _canRead = true; _canWrite = true; break; default: @@ -148,6 +149,9 @@ public: { throw IOException("Unable to write to file."); } + + uint64 position = GetPosition(); + _fileSize = Math::Max(_fileSize, position); } uint64 TryRead(void * buffer, uint64 length) override diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index b3877904ce..37d047f3b1 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -74,7 +74,9 @@ + + @@ -418,7 +420,9 @@ + + diff --git a/src/openrct2/network/network.cpp b/src/openrct2/network/network.cpp index ba227e0f7b..0b09f1f0c5 100644 --- a/src/openrct2/network/network.cpp +++ b/src/openrct2/network/network.cpp @@ -50,6 +50,7 @@ sint32 _pickup_peep_old_x = SPRITE_LOCATION_NULL; #include "../core/Path.hpp" #include "../core/String.hpp" #include "../core/Util.hpp" +#include "../object/ObjectManager.h" #include "../object/ObjectRepository.h" #include "../rct2/S6Exporter.h" @@ -923,7 +924,8 @@ void Network::Server_Send_MAP(NetworkConnection* connection) } else { // This will send all custom objects to connected clients // TODO: fix it so custom objects negotiation is performed even in this case. - objects = scenario_get_packable_objects(); + IObjectManager * objManager = GetObjectManager(); + objects = objManager->GetPackableObjects(); } header = save_for_network(rw, out_size, objects); SDL_RWclose(rw); @@ -1458,7 +1460,9 @@ void Network::Server_Client_Joined(const char* name, const std::string &keyhash, const char * player_name = (const char *) player->Name.c_str(); format_string(text, 256, STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, &player_name); chat_history_add(text); - std::vector objects = scenario_get_packable_objects(); + + IObjectManager * objManager = GetObjectManager(); + auto objects = objManager->GetPackableObjects(); Server_Send_OBJECTS(connection, objects); } } diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index c63e159fb6..ce351b3560 100644 --- a/src/openrct2/object/ObjectManager.cpp +++ b/src/openrct2/object/ObjectManager.cpp @@ -215,6 +215,23 @@ public: } } + std::vector GetPackableObjects() override + { + std::vector objects; + size_t numObjects = _objectRepository->GetNumObjects(); + for (size_t i = 0; i < numObjects; i++) + { + const ObjectRepositoryItem * item = &_objectRepository->GetObjects()[i]; + if (item->LoadedObject != nullptr && + item->LoadedObject->GetLegacyData() != nullptr && + IsObjectCustom(item)) + { + objects.push_back(item); + } + } + return objects; + } + private: sint32 FindSpareSlot(uint8 objectType) { diff --git a/src/openrct2/object/ObjectManager.h b/src/openrct2/object/ObjectManager.h index 49c8f711cf..6de0d1c559 100644 --- a/src/openrct2/object/ObjectManager.h +++ b/src/openrct2/object/ObjectManager.h @@ -16,6 +16,10 @@ #pragma once +#ifdef __cplusplus + #include +#endif + #include "../common.h" #ifdef __cplusplus @@ -29,7 +33,8 @@ extern "C" #ifdef __cplusplus -class Object; +class Object; +struct ObjectRepositoryItem; interface IObjectManager { @@ -45,6 +50,8 @@ interface IObjectManager virtual void UnloadAll() abstract; virtual void ResetObjects() abstract; + + virtual std::vector GetPackableObjects() abstract; }; IObjectManager * GetObjectManager(); diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index c498ce4367..a089805fce 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -32,6 +32,7 @@ #include "../core/String.hpp" #include "../PlatformEnvironment.h" #include "../rct12/SawyerChunkReader.h" +#include "../rct12/SawyerChunkWriter.h" #include "../scenario/ScenarioRepository.h" #include "Object.h" #include "ObjectFactory.h" @@ -224,7 +225,7 @@ public: } } - bool TryExportPackedObject(IStream * stream) override + void ExportPackedObject(IStream * stream) override { auto chunkReader = SawyerChunkReader(stream); @@ -240,7 +241,25 @@ public: std::shared_ptr chunk = chunkReader.ReadChunk(); AddObject(&entry, chunk->GetData(), chunk->GetLength()); } - return true; + } + + void WritePackedObjects(IStream * stream, std::vector &objects) override + { + log_verbose("packing %u objects", objects.size()); + for (const auto &object : objects) + { + Guard::ArgumentNotNull(object); + + log_verbose("exporting object %.8s", object->ObjectEntry.name); + if (IsObjectCustom(object)) + { + WritePackedObject(stream, &object->ObjectEntry); + } + else + { + log_warning("Refusing to pack vanilla/expansion object \"%s\"", object->ObjectEntry.name); + } + } } private: @@ -648,6 +667,30 @@ private: String::Append(buffer, bufferSize, ".DAT"); } } + + void WritePackedObject(IStream * stream, const rct_object_entry * entry) + { + const ObjectRepositoryItem * item = FindObject(entry); + if (item == nullptr) + { + throw Exception(String::StdFormat("Unable to find object '%.8s'", entry->name)); + } + + // Read object data from file + auto fs = FileStream(item->Path, FILE_MODE_OPEN); + auto fileEntry = fs.ReadValue(); + if (!object_entry_compare(entry, &fileEntry)) + { + throw Exception("Header found in object file does not match object to pack."); + } + auto chunkReader = SawyerChunkReader(&fs); + auto chunk = chunkReader.ReadChunk(); + + // Write object data to stream + auto chunkWriter = SawyerChunkWriter(stream); + stream->WriteValue(*entry); + chunkWriter.WriteChunk(chunk.get()); + } }; static std::unique_ptr _objectRepository; @@ -663,6 +706,14 @@ IObjectRepository * GetObjectRepository() return _objectRepository.get(); } +bool IsObjectCustom(const ObjectRepositoryItem * object) +{ + Guard::ArgumentNotNull(object); + + // Validate the object is not one from base game or expansion pack + return !(object->ObjectEntry.flags & 0xF0); +} + extern "C" { rct_object_entry * object_list_find(rct_object_entry * entry) @@ -755,45 +806,6 @@ extern "C" } } - bool object_saved_packed(SDL_RWops * rw, const rct_object_entry * entry) - { - IObjectRepository * objectRepository = GetObjectRepository(); - const ObjectRepositoryItem * item = objectRepository->FindObject(entry); - if (item == nullptr) - { - return false; - } - - auto fs = FileStream(item->Path, FILE_MODE_OPEN); - rct_object_entry fileEntry = fs.ReadValue(); - if (!object_entry_compare(entry, &fileEntry)) - { - return false; - } - - sawyercoding_chunk_header chunkHeader = fs.ReadValue(); - uint8 * chunkData = fs.ReadArray(chunkHeader.length); - - if (SDL_RWwrite(rw, entry, sizeof(rct_object_entry), 1) != 1) - { - Memory::Free(chunkData); - return false; - } - if (SDL_RWwrite(rw, &chunkHeader, sizeof(sawyercoding_chunk_header), 1) != 1) - { - Memory::Free(chunkData); - return false; - } - if (SDL_RWwrite(rw, chunkData, chunkHeader.length, 1) != 1) - { - Memory::Free(chunkData); - return false; - } - Memory::Free(chunkData); - - return true; - } - size_t object_repository_get_items_count() { IObjectRepository * objectRepository = GetObjectRepository(); diff --git a/src/openrct2/object/ObjectRepository.h b/src/openrct2/object/ObjectRepository.h index 7cd23f4758..9cbdb58820 100644 --- a/src/openrct2/object/ObjectRepository.h +++ b/src/openrct2/object/ObjectRepository.h @@ -16,6 +16,10 @@ #pragma once +#ifdef __cplusplus + #include +#endif + #include "../common.h" #ifdef __cplusplus @@ -79,12 +83,15 @@ interface IObjectRepository const void * data, size_t dataSize) abstract; - virtual bool TryExportPackedObject(IStream * stream) abstract; + virtual void ExportPackedObject(IStream * stream) abstract; + virtual void WritePackedObjects(IStream * stream, std::vector &objects) abstract; }; IObjectRepository * CreateObjectRepository(IPlatformEnvironment * env); IObjectRepository * GetObjectRepository(); +bool IsObjectCustom(const ObjectRepositoryItem * object); + #endif #ifdef __cplusplus diff --git a/src/openrct2/rct12/SawyerChunk.cpp b/src/openrct2/rct12/SawyerChunk.cpp new file mode 100644 index 0000000000..b26d1cf987 --- /dev/null +++ b/src/openrct2/rct12/SawyerChunk.cpp @@ -0,0 +1,30 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Memory.hpp" +#include "SawyerChunk.h" + +SawyerChunk::SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length) +{ + _encoding = encoding; + _data = data; + _length = length; +} + +SawyerChunk::~SawyerChunk() +{ + Memory::Free(_data); +} diff --git a/src/openrct2/rct12/SawyerChunk.h b/src/openrct2/rct12/SawyerChunk.h new file mode 100644 index 0000000000..e425152d66 --- /dev/null +++ b/src/openrct2/rct12/SawyerChunk.h @@ -0,0 +1,49 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" + +/** + * The type of encoding / compression for a sawyer encoded chunk. + */ +enum class SAWYER_ENCODING : uint8 +{ + NONE, + RLE, + RLECOMPRESSED, + ROTATE, +}; + +/** + * Represents a sawyer encoded chunk. + */ +class SawyerChunk final +{ +private: + void * _data = nullptr; + size_t _length = 0; + SAWYER_ENCODING _encoding = SAWYER_ENCODING::NONE; + +public: + const void * GetData() const { return _data; } + size_t GetLength() const { return _length; } + SAWYER_ENCODING GetEncoding() const { return _encoding; } + + SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length); + ~SawyerChunk(); +}; diff --git a/src/openrct2/rct12/SawyerChunkReader.cpp b/src/openrct2/rct12/SawyerChunkReader.cpp index 62fbe3326a..65e3269f22 100644 --- a/src/openrct2/rct12/SawyerChunkReader.cpp +++ b/src/openrct2/rct12/SawyerChunkReader.cpp @@ -34,18 +34,6 @@ public: explicit SawyerChunkException(const std::string &message) : IOException(message) { } }; -SawyerChunk::SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length) -{ - _encoding = encoding; - _data = data; - _length = length; -} - -SawyerChunk::~SawyerChunk() -{ - Memory::Free(_data); -} - SawyerChunkReader::SawyerChunkReader(IStream * stream) : _stream(stream) { diff --git a/src/openrct2/rct12/SawyerChunkReader.h b/src/openrct2/rct12/SawyerChunkReader.h index 3217feb0b2..be616a5fb9 100644 --- a/src/openrct2/rct12/SawyerChunkReader.h +++ b/src/openrct2/rct12/SawyerChunkReader.h @@ -18,39 +18,10 @@ #include #include "../common.h" +#include "SawyerChunk.h" interface IStream; -/** - * The type of encoding / compression for a sawyer encoded chunk. - */ -enum class SAWYER_ENCODING : uint8 -{ - NONE, - RLE, - RLECOMPRESSED, - ROTATE, -}; - -/** - * Represents a sawyer encoded chunk. - */ -class SawyerChunk final -{ -private: - void * _data = nullptr; - size_t _length = 0; - SAWYER_ENCODING _encoding = SAWYER_ENCODING::NONE; - -public: - const void * GetData() { return _data; } - size_t GetLength() { return _length; } - SAWYER_ENCODING GetEncoding() { return _encoding; } - - SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length); - ~SawyerChunk(); -}; - /** * Reads sawyer encoding chunks from a data stream. This can be used to read * SC6, SV6 and RCT2 objects. diff --git a/src/openrct2/rct12/SawyerChunkWriter.cpp b/src/openrct2/rct12/SawyerChunkWriter.cpp new file mode 100644 index 0000000000..9498316148 --- /dev/null +++ b/src/openrct2/rct12/SawyerChunkWriter.cpp @@ -0,0 +1,50 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Exception.hpp" +#include "../core/IStream.hpp" +#include "../core/Math.hpp" +#include "SawyerChunkWriter.h" + +extern "C" +{ + #include "../util/sawyercoding.h" +} + +// Maximum buffer size to store compressed data, maximum of 16 MiB +constexpr size_t MAX_COMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024; + +SawyerChunkWriter::SawyerChunkWriter(IStream * stream) + : _stream(stream) +{ +} + +void SawyerChunkWriter::WriteChunk(const SawyerChunk * chunk) +{ + WriteChunk(chunk->GetData(), chunk->GetLength(), chunk->GetEncoding()); +} + +void SawyerChunkWriter::WriteChunk(const void * src, size_t length, SAWYER_ENCODING encoding) +{ + sawyercoding_chunk_header header; + header.encoding = (uint8)encoding; + header.length = (uint32)length; + + auto data = std::make_unique(MAX_COMPRESSED_CHUNK_SIZE); + size_t dataLength = sawyercoding_write_chunk_buffer(data.get(), (const uint8 *)src, header); + + _stream->Write(data.get(), dataLength); +} diff --git a/src/openrct2/rct12/SawyerChunkWriter.h b/src/openrct2/rct12/SawyerChunkWriter.h new file mode 100644 index 0000000000..18f31200b0 --- /dev/null +++ b/src/openrct2/rct12/SawyerChunkWriter.h @@ -0,0 +1,57 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include "../common.h" +#include "SawyerChunk.h" + +interface IStream; + +/** + * Writes sawyer encoding chunks to a data stream. This can be used to write + * SC6 and SV6 files. + */ +class SawyerChunkWriter final +{ +private: + IStream * const _stream = nullptr; + +public: + SawyerChunkWriter(IStream * stream); + + /** + * Writes a chunk to the stream. + */ + void WriteChunk(const SawyerChunk * chunk); + + /** + * Writes a chunk to the stream containing the given buffer. + * @param dst The source buffer. + * @param length The size of the source buffer. + */ + void WriteChunk(const void * src, size_t length, SAWYER_ENCODING encoding); + + /** + * Writes a chunk to the stream containing the given type. + */ + template + void WriteChunk(const T * src, SAWYER_ENCODING encoding) + { + WriteChunk(src, sizeof(T), encoding); + } +}; diff --git a/src/openrct2/rct12/SawyerEncoding.cpp b/src/openrct2/rct12/SawyerEncoding.cpp index a25227247e..0a8d16c9a9 100644 --- a/src/openrct2/rct12/SawyerEncoding.cpp +++ b/src/openrct2/rct12/SawyerEncoding.cpp @@ -28,6 +28,7 @@ namespace SawyerEncoding { return false; } + dataSize -= 4; try { diff --git a/src/openrct2/rct2/S6Exporter.cpp b/src/openrct2/rct2/S6Exporter.cpp index 9c16838dc5..2c4bef975c 100644 --- a/src/openrct2/rct2/S6Exporter.cpp +++ b/src/openrct2/rct2/S6Exporter.cpp @@ -15,11 +15,14 @@ #pragma endregion #include "../core/Exception.hpp" +#include "../core/FileStream.hpp" #include "../core/IStream.hpp" #include "../core/String.hpp" #include "../management/award.h" #include "../object/Object.h" +#include "../object/ObjectManager.h" #include "../object/ObjectRepository.h" +#include "../rct12/SawyerChunkWriter.h" #include "S6Exporter.h" extern "C" @@ -56,178 +59,91 @@ S6Exporter::S6Exporter() void S6Exporter::SaveGame(const utf8 * path) { - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw == nullptr) - { - throw IOException("Unable to write to destination file."); - } - - SaveGame(rw); - - SDL_RWclose(rw); + auto fs = FileStream(path, FILE_MODE_WRITE); + SaveGame(&fs); } -void S6Exporter::SaveGame(SDL_RWops *rw) +void S6Exporter::SaveGame(IStream * stream) { - Save(rw, false); + Save(stream, false); } void S6Exporter::SaveScenario(const utf8 * path) { - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw == nullptr) - { - throw IOException("Unable to write to destination file."); - } - - SaveGame(rw); - - SDL_RWclose(rw); + auto fs = FileStream(path, FILE_MODE_WRITE); + SaveScenario(&fs); } -void S6Exporter::SaveScenario(SDL_RWops *rw) +void S6Exporter::SaveScenario(IStream * stream) { - Save(rw, true); + Save(stream, true); } -void S6Exporter::Save(SDL_RWops * rw, bool isScenario) +void S6Exporter::Save(IStream * stream, bool isScenario) { _s6.header.type = isScenario ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME; _s6.header.classic_flag = 0; _s6.header.num_packed_objects = uint16(ExportObjectsList.size()); _s6.header.version = S6_RCT2_VERSION; _s6.header.magic_number = S6_MAGIC_NUMBER; - _s6.game_version_number = 201028; - uint8 * buffer = (uint8 *)malloc(0x600000); - if (buffer == NULL) - { - log_error("Unable to allocate enough space for a write buffer."); - throw Exception("Unable to allocate memory."); - } - - sawyercoding_chunk_header chunkHeader; - size_t encodedLength; + auto chunkWriter = SawyerChunkWriter(stream); // 0: Write header chunk - chunkHeader.encoding = CHUNK_ENCODING_ROTATE; - chunkHeader.length = sizeof(rct_s6_header); - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.header, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + chunkWriter.WriteChunk(&_s6.header, SAWYER_ENCODING::ROTATE); // 1: Write scenario info chunk if (_s6.header.type == S6_TYPE_SCENARIO) { - chunkHeader.encoding = CHUNK_ENCODING_ROTATE; - chunkHeader.length = sizeof(rct_s6_info); - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.info, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + chunkWriter.WriteChunk(&_s6.info, SAWYER_ENCODING::ROTATE); } - log_verbose("exporting %u objects", _s6.header.num_packed_objects); // 2: Write packed objects if (_s6.header.num_packed_objects > 0) { - if (!scenario_write_packed_objects(rw, ExportObjectsList)) - { - free(buffer); - throw Exception("Unable to pack objects."); - } + IObjectRepository * objRepo = GetObjectRepository(); + objRepo->WritePackedObjects(stream, ExportObjectsList); } // 3: Write available objects chunk - chunkHeader.encoding = CHUNK_ENCODING_ROTATE; - chunkHeader.length = OBJECT_ENTRY_COUNT * sizeof(rct_object_entry); - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.objects, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + chunkWriter.WriteChunk(_s6.objects, sizeof(_s6.objects), SAWYER_ENCODING::ROTATE); // 4: Misc fields (data, rand...) chunk - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 16; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.elapsed_months, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + chunkWriter.WriteChunk(&_s6.elapsed_months, 16, SAWYER_ENCODING::RLECOMPRESSED); // 5: Map elements + sprites and other fields chunk - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 0x180000; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.map_elements, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + chunkWriter.WriteChunk(&_s6.map_elements, 0x180000, SAWYER_ENCODING::RLECOMPRESSED); if (_s6.header.type == S6_TYPE_SCENARIO) { - // 6: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 0x27104C; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.next_free_map_element_pointer_index, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 7: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 4; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.guests_in_park, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 8: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 8; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.last_guests_in_park, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 9: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 2; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.park_rating, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 10: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 1082; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.active_research_types, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 11: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 16; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.current_expenditure, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 12: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 4; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.park_value, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); - - // 13: - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 0x761E8; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.completed_company_value, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + // 6 to 13: + chunkWriter.WriteChunk(&_s6.next_free_map_element_pointer_index, 0x27104C, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.guests_in_park, 4, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.last_guests_in_park, 8, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.park_rating, 2, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.active_research_types, 1082, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.current_expenditure, 16, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.park_value, 4, SAWYER_ENCODING::RLECOMPRESSED); + chunkWriter.WriteChunk(&_s6.completed_company_value, 0x761E8, SAWYER_ENCODING::RLECOMPRESSED); } else { // 6: Everything else... - chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; - chunkHeader.length = 0x2E8570; - encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.next_free_map_element_pointer_index, chunkHeader); - SDL_RWwrite(rw, buffer, encodedLength, 1); + chunkWriter.WriteChunk(&_s6.next_free_map_element_pointer_index, 0x2E8570, SAWYER_ENCODING::RLECOMPRESSED); } - free(buffer); - // Determine number of bytes written - size_t fileSize = (size_t)SDL_RWtell(rw); - SDL_RWseek(rw, 0, RW_SEEK_SET); + size_t fileSize = stream->GetLength(); // Read all written bytes back into a single buffer - buffer = (uint8 *)malloc(fileSize); - SDL_RWread(rw, buffer, fileSize, 1); - uint32 checksum = sawyercoding_calculate_checksum(buffer, fileSize); - free(buffer); + stream->SetPosition(0); + auto data = std::unique_ptr(stream->ReadArray(fileSize)); + uint32 checksum = sawyercoding_calculate_checksum(data.get(), fileSize); - // Append the checksum - SDL_RWseek(rw, fileSize, RW_SEEK_SET); - SDL_RWwrite(rw, &checksum, sizeof(uint32), 1); + // Write the checksum on the end + stream->SetPosition(fileSize); + stream->WriteValue(checksum); } void S6Exporter::Export() @@ -480,7 +396,6 @@ uint32 S6Exporter::GetLoanHash(money32 initialCash, money32 bankLoan, uint32 max return value; } - // Save game state without modifying any of the state for multiplayer sint32 scenario_save_network(SDL_RWops * rw, const std::vector &objects) { @@ -490,9 +405,11 @@ sint32 scenario_save_network(SDL_RWops * rw, const std::vectorExportObjectsList = objects; s6exporter->Export(); - s6exporter->SaveGame(rw); + s6exporter->SaveGame(&rwStream); result = true; } catch (const Exception &) @@ -534,58 +451,6 @@ sint32 scenario_save_network(SDL_RWops * rw, const std::vectorLoadedObject != nullptr && - object->LoadedObject->GetLegacyData() != nullptr - && !(object->ObjectEntry.flags & 0xF0)); -} - -sint32 scenario_write_packed_objects(SDL_RWops* rw, std::vector &objects) -{ - log_verbose("exporting packed objects"); - for (const auto &object : objects) - { - Guard::ArgumentNotNull(object); - log_verbose("exporting object %.8s", object->ObjectEntry.name); - if (object_is_custom(object)) - { - if (!object_saved_packed(rw, &object->ObjectEntry)) - { - return 0; - } - } - else - { - log_warning("Refusing to pack vanilla/expansion object \"%s\"", object->ObjectEntry.name); - } - } - return 1; -} - -/** - * - * rct2: 0x006AA244 - */ -std::vector scenario_get_packable_objects() -{ - std::vector objects; - IObjectRepository * repo = GetObjectRepository(); - for (size_t i = 0; i < repo->GetNumObjects(); i++) - { - const ObjectRepositoryItem *item = &repo->GetObjects()[i]; - // Validate the object is not one from base game or expansion pack - if (object_is_custom(item)) - { - objects.push_back(item); - } - } - return objects; -} - extern "C" { enum { @@ -624,18 +489,22 @@ extern "C" auto s6exporter = new S6Exporter(); try { - if (flags & S6_SAVE_FLAG_EXPORT) { - s6exporter->ExportObjectsList = scenario_get_packable_objects(); + auto rwStream = FileStream(rw, FILE_MODE_WRITE); + + if (flags & S6_SAVE_FLAG_EXPORT) + { + IObjectManager * objManager = GetObjectManager(); + s6exporter->ExportObjectsList = objManager->GetPackableObjects(); } s6exporter->RemoveTracklessRides = true; s6exporter->Export(); if (flags & S6_SAVE_FLAG_SCENARIO) { - s6exporter->SaveScenario(rw); + s6exporter->SaveScenario(&rwStream); } else { - s6exporter->SaveGame(rw); + s6exporter->SaveGame(&rwStream); } result = true; } diff --git a/src/openrct2/rct2/S6Exporter.h b/src/openrct2/rct2/S6Exporter.h index 30b8a904f3..a0dc5a908b 100644 --- a/src/openrct2/rct2/S6Exporter.h +++ b/src/openrct2/rct2/S6Exporter.h @@ -27,11 +27,10 @@ extern "C" #include "../object_list.h" } -struct ObjectRepositoryItem; +interface IStream; +struct ObjectRepositoryItem; sint32 scenario_save_network(SDL_RWops* rw, const std::vector &objects); -sint32 scenario_write_packed_objects(SDL_RWops* rw, std::vector &objects); -std::vector scenario_get_packable_objects(); /** * Class to export RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). @@ -45,14 +44,14 @@ public: S6Exporter(); void SaveGame(const utf8 * path); - void SaveGame(SDL_RWops *rw); + void SaveGame(IStream * stream); void SaveScenario(const utf8 * path); - void SaveScenario(SDL_RWops *rw); + void SaveScenario(IStream * stream); void Export(); private: rct_s6_data _s6; - void Save(SDL_RWops *rw, bool isScenario); + void Save(IStream * stream, bool isScenario); static uint32 GetLoanHash(money32 initialCash, money32 bankLoan, uint32 maxBankLoan); }; diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index e002e25e14..e5db2d1309 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -136,7 +136,7 @@ public: IObjectRepository * objectRepo = GetObjectRepository(); for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) { - objectRepo->TryExportPackedObject(stream); + objectRepo->ExportPackedObject(stream); } if (isScenario) From e09fb6533f84b7cbb13f0b130265d2477b14b9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Mon, 6 Feb 2017 23:24:55 +0100 Subject: [PATCH 5/7] Update C++ standard version to C++14 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fb279cbe5..f71324f85f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,7 +290,7 @@ endif () # set necessary flags to compile code as is set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TARGET_M} -std=gnu99 ${COMMON_COMPILE_OPTIONS} -Wimplicit") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TARGET_M} -std=gnu++11 ${COMMON_COMPILE_OPTIONS} -Wnon-virtual-dtor") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TARGET_M} -std=gnu++14 ${COMMON_COMPILE_OPTIONS} -Wnon-virtual-dtor") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TARGET_M}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS} ${PIE_FLAG}") From 629ccec4665f9756d281671fa3e16b7bed1badd3 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 7 Feb 2017 20:14:31 +0000 Subject: [PATCH 6/7] Use write extended mode to fix checksum calculation [ci skip] --- src/openrct2/core/FileStream.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2/core/FileStream.hpp b/src/openrct2/core/FileStream.hpp index d8ff49cd95..a0c3821896 100644 --- a/src/openrct2/core/FileStream.hpp +++ b/src/openrct2/core/FileStream.hpp @@ -57,7 +57,7 @@ public: _canWrite = false; break; case FILE_MODE_WRITE: - mode = "wb"; + mode = "w+b"; _canRead = true; _canWrite = true; break; From b1c7caf3159fab8069a05fc863d69426ae77ef65 Mon Sep 17 00:00:00 2001 From: LRFLEW Date: Tue, 7 Feb 2017 16:48:52 -0600 Subject: [PATCH 7/7] Update Xcode Project --- OpenRCT2.xcodeproj/project.pbxproj | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index f63bd2b3c1..fa9eee4bf4 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -242,6 +242,9 @@ D41B73EF1C2101890080A7B9 /* libcurl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73EE1C2101890080A7B9 /* libcurl.tbd */; }; D41B741D1C210A7A0080A7B9 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B741C1C210A7A0080A7B9 /* libiconv.tbd */; }; D41B74731C2125E50080A7B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D41B74721C2125E50080A7B9 /* Assets.xcassets */; }; + D433A5001E4A861F00D9A6DF /* SawyerChunk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D433A4FA1E4A861F00D9A6DF /* SawyerChunk.cpp */; }; + D433A5011E4A861F00D9A6DF /* SawyerChunkReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D433A4FC1E4A861F00D9A6DF /* SawyerChunkReader.cpp */; }; + D433A5021E4A861F00D9A6DF /* SawyerChunkWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D433A4FE1E4A861F00D9A6DF /* SawyerChunkWriter.cpp */; }; D43407D61D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D43407C01D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp */; }; D43407D81D0E14BE00C2B3D4 /* DrawImageShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D43407C41D0E14BE00C2B3D4 /* DrawImageShader.cpp */; }; D43407D91D0E14BE00C2B3D4 /* DrawLineShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D43407C61D0E14BE00C2B3D4 /* DrawLineShader.cpp */; }; @@ -709,6 +712,13 @@ D41B73EE1C2101890080A7B9 /* libcurl.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurl.tbd; path = usr/lib/libcurl.tbd; sourceTree = SDKROOT; }; D41B741C1C210A7A0080A7B9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; D41B74721C2125E50080A7B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = distribution/macos/Assets.xcassets; sourceTree = SOURCE_ROOT; }; + D433A4FA1E4A861F00D9A6DF /* SawyerChunk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerChunk.cpp; path = rct12/SawyerChunk.cpp; sourceTree = ""; }; + D433A4FB1E4A861F00D9A6DF /* SawyerChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerChunk.h; path = rct12/SawyerChunk.h; sourceTree = ""; }; + D433A4FC1E4A861F00D9A6DF /* SawyerChunkReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerChunkReader.cpp; path = rct12/SawyerChunkReader.cpp; sourceTree = ""; }; + D433A4FD1E4A861F00D9A6DF /* SawyerChunkReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerChunkReader.h; path = rct12/SawyerChunkReader.h; sourceTree = ""; }; + D433A4FE1E4A861F00D9A6DF /* SawyerChunkWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerChunkWriter.cpp; path = rct12/SawyerChunkWriter.cpp; sourceTree = ""; }; + D433A4FF1E4A861F00D9A6DF /* SawyerChunkWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerChunkWriter.h; path = rct12/SawyerChunkWriter.h; sourceTree = ""; }; + D433A5031E4A862F00D9A6DF /* rct12.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rct12.h; sourceTree = ""; }; D43407C01D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CopyFramebufferShader.cpp; sourceTree = ""; }; D43407C11D0E14BE00C2B3D4 /* CopyFramebufferShader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CopyFramebufferShader.h; sourceTree = ""; }; D43407C41D0E14BE00C2B3D4 /* DrawImageShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DrawImageShader.cpp; sourceTree = ""; }; @@ -1242,6 +1252,12 @@ 652747E91E41CDFE000F36FD /* rct12 */ = { isa = PBXGroup; children = ( + D433A4FA1E4A861F00D9A6DF /* SawyerChunk.cpp */, + D433A4FB1E4A861F00D9A6DF /* SawyerChunk.h */, + D433A4FC1E4A861F00D9A6DF /* SawyerChunkReader.cpp */, + D433A4FD1E4A861F00D9A6DF /* SawyerChunkReader.h */, + D433A4FE1E4A861F00D9A6DF /* SawyerChunkWriter.cpp */, + D433A4FF1E4A861F00D9A6DF /* SawyerChunkWriter.h */, 652747EA1E41CE1B000F36FD /* SawyerEncoding.cpp */, 652747EB1E41CE1B000F36FD /* SawyerEncoding.h */, ); @@ -1524,8 +1540,8 @@ D442715B1CC81B3200D84D28 /* peep */, D44271601CC81B3200D84D28 /* platform */, C650B2141CCABBDD00B4D91C /* rct1 */, - 652747E91E41CDFE000F36FD /* rct12 */, C6B5A7CF1CDFE4CB00C9C006 /* rct2 */, + 652747E91E41CDFE000F36FD /* rct12 */, D442716E1CC81B3200D84D28 /* ride */, C6E96E1C1E04070E0076A04F /* scenario */, C6E96E261E04072F0076A04F /* title */, @@ -1564,6 +1580,7 @@ D442716A1CC81B3200D84D28 /* rct1.h */, D442716B1CC81B3200D84D28 /* rct2.c */, D442716C1CC81B3200D84D28 /* rct2.h */, + D433A5031E4A862F00D9A6DF /* rct12.h */, D44271851CC81B3200D84D28 /* sprites.h */, D4CA88651D4E64C800060C11 /* version.c */, D442718D1CC81B3200D84D28 /* version.h */, @@ -2896,6 +2913,7 @@ C6E96E2F1E04072F0076A04F /* TitleScreen.cpp in Sources */, D442728E1CC81B3200D84D28 /* title_logo.c in Sources */, C686F8B21CDBC37E009F9BFC /* scenery_multiple.c in Sources */, + D433A5011E4A861F00D9A6DF /* SawyerChunkReader.cpp in Sources */, D442725E1CC81B3200D84D28 /* editor_objective_options.c in Sources */, D44272531CC81B3200D84D28 /* about.c in Sources */, C686F9311CDBC3B7009F9BFC /* ghost_train.c in Sources */, @@ -2923,6 +2941,7 @@ D44272761CC81B3200D84D28 /* options.c in Sources */, C686F93D1CDBC3B7009F9BFC /* shop.c in Sources */, C686F8AD1CDBC37E009F9BFC /* entrance.c in Sources */, + D433A5001E4A861F00D9A6DF /* SawyerChunk.cpp in Sources */, D44272111CC81B3200D84D28 /* string.c in Sources */, C686F93E1CDBC3B7009F9BFC /* 3d_cinema.c in Sources */, D44272771CC81B3200D84D28 /* park.c in Sources */, @@ -2968,6 +2987,7 @@ D442726C1CC81B3200D84D28 /* map_tooltip.c in Sources */, C686F9281CDBC3B7009F9BFC /* wild_mouse.c in Sources */, D44272701CC81B3200D84D28 /* music_credits.c in Sources */, + D433A5021E4A861F00D9A6DF /* SawyerChunkWriter.cpp in Sources */, C686F9511CDBC3B7009F9BFC /* river_rafts.c in Sources */, C686F90E1CDBC3B7009F9BFC /* corkscrew_roller_coaster.c in Sources */, D44272A61CC81B3200D84D28 /* scenery.c in Sources */, @@ -3084,7 +3104,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; @@ -3132,7 +3152,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO;