diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 78fa044bbc..f63bd2b3c1 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 00EFEE721CF1D80B0035213B /* NetworkKey.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00EFEE701CF1D80B0035213B /* NetworkKey.cpp */; }; 652076321E22EFE7000D0C04 /* Imaging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 652076301E22EFE7000D0C04 /* Imaging.cpp */; }; 652747EC1E41CE1B000F36FD /* SawyerEncoding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 652747EA1E41CE1B000F36FD /* SawyerEncoding.cpp */; }; + 658F3D911E44A6C200388550 /* ParkImporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 658F3D8F1E44A6C200388550 /* ParkImporter.cpp */; }; 791166FB1D7486EF005912EA /* NetworkServerAdvertiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 791166F91D7486EF005912EA /* NetworkServerAdvertiser.cpp */; }; 85060FD31D8C17CC00DFA2B3 /* track_data_old.c in Sources */ = {isa = PBXBuildFile; fileRef = 8594C05F1D885CF600235E93 /* track_data_old.c */; }; 8594C0601D885CF600235E93 /* track_data_old.c in Sources */ = {isa = PBXBuildFile; fileRef = 8594C05F1D885CF600235E93 /* track_data_old.c */; }; @@ -521,6 +522,8 @@ 652076311E22EFE7000D0C04 /* Imaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Imaging.h; sourceTree = ""; }; 652747EA1E41CE1B000F36FD /* SawyerEncoding.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerEncoding.cpp; path = rct12/SawyerEncoding.cpp; sourceTree = ""; }; 652747EB1E41CE1B000F36FD /* SawyerEncoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerEncoding.h; path = rct12/SawyerEncoding.h; sourceTree = ""; }; + 658F3D8F1E44A6C200388550 /* ParkImporter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParkImporter.cpp; sourceTree = ""; }; + 658F3D901E44A6C200388550 /* ParkImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParkImporter.h; sourceTree = ""; }; 791166F91D7486EF005912EA /* NetworkServerAdvertiser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NetworkServerAdvertiser.cpp; sourceTree = ""; }; 791166FA1D7486EF005912EA /* NetworkServerAdvertiser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkServerAdvertiser.h; sourceTree = ""; }; 8594C05F1D885CF600235E93 /* track_data_old.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = track_data_old.c; sourceTree = ""; }; @@ -562,7 +565,6 @@ C649B3D31DF04ED2008AC826 /* format_codes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = format_codes.c; sourceTree = ""; }; C64FDA5D1D6D99F400F259B9 /* PaintTest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PaintTest; sourceTree = BUILT_PRODUCTS_DIR; }; C650B2151CCABBDD00B4D91C /* S4Importer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S4Importer.cpp; sourceTree = ""; usesTabs = 0; }; - C650B2161CCABBDD00B4D91C /* S4Importer.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = S4Importer.h; sourceTree = ""; usesTabs = 0; }; C650B2171CCABBDD00B4D91C /* tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tables.cpp; sourceTree = ""; usesTabs = 0; }; C650B2181CCABBDD00B4D91C /* Tables.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = Tables.h; sourceTree = ""; usesTabs = 0; }; C650B21B1CCABC4400B4D91C /* ConvertCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConvertCommand.cpp; sourceTree = ""; usesTabs = 0; }; @@ -676,7 +678,6 @@ C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S6Exporter.cpp; sourceTree = ""; }; C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = S6Exporter.h; sourceTree = ""; }; C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = S6Importer.cpp; sourceTree = ""; }; - C6B5A7D31CDFE4CB00C9C006 /* S6Importer.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = S6Importer.h; sourceTree = ""; }; C6CABA801E1466D600D33A6B /* FileClassifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileClassifier.cpp; sourceTree = ""; }; C6CABA811E1466D600D33A6B /* FileClassifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileClassifier.h; sourceTree = ""; }; C6E96E101E04067A0076A04F /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = File.cpp; sourceTree = ""; }; @@ -1294,7 +1295,6 @@ isa = PBXGroup; children = ( C650B2151CCABBDD00B4D91C /* S4Importer.cpp */, - C650B2161CCABBDD00B4D91C /* S4Importer.h */, C650B2171CCABBDD00B4D91C /* tables.cpp */, C650B2181CCABBDD00B4D91C /* Tables.h */, ); @@ -1475,7 +1475,6 @@ C6B5A7D01CDFE4CB00C9C006 /* S6Exporter.cpp */, C6B5A7D11CDFE4CB00C9C006 /* S6Exporter.h */, C6B5A7D21CDFE4CB00C9C006 /* S6Importer.cpp */, - C6B5A7D31CDFE4CB00C9C006 /* S6Importer.h */, ); path = rct2; sourceTree = ""; @@ -1557,6 +1556,8 @@ D44271581CC81B3200D84D28 /* object.h */, D460DFD01E01239D007BA2FE /* OpenRCT2.cpp */, D460DFD21E0123B5007BA2FE /* OpenRCT2.h */, + 658F3D8F1E44A6C200388550 /* ParkImporter.cpp */, + 658F3D901E44A6C200388550 /* ParkImporter.h */, D460DFD31E0123D1007BA2FE /* PlatformEnvironment.cpp */, D460DFD51E0123DB007BA2FE /* PlatformEnvironment.h */, D44271691CC81B3200D84D28 /* rct1.c */, @@ -2661,6 +2662,7 @@ D44272211CC81B3200D84D28 /* viewport_interaction.c in Sources */, D442721B1CC81B3200D84D28 /* graph.c in Sources */, C686F9581CDBC4C7009F9BFC /* vehicle_paint.c in Sources */, + 658F3D911E44A6C200388550 /* ParkImporter.cpp in Sources */, 007A05D11CFB2C8B00F419C3 /* NetworkPacket.cpp in Sources */, D44272101CC81B3200D84D28 /* sprite.c in Sources */, 007A05CD1CFB2C8B00F419C3 /* NetworkAction.cpp in Sources */, diff --git a/src/openrct2/ParkImporter.cpp b/src/openrct2/ParkImporter.cpp new file mode 100644 index 0000000000..72d5bbd41a --- /dev/null +++ b/src/openrct2/ParkImporter.cpp @@ -0,0 +1,79 @@ +#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 +#include "core/Path.hpp" +#include "core/String.hpp" +#include "ParkImporter.h" + +namespace ParkImporter +{ + IParkImporter * Create(const std::string &hintPath) + { + IParkImporter * parkImporter = nullptr; + std::string extension = Path::GetExtension(hintPath); + if (ExtensionIsRCT1(extension)) + { + parkImporter = CreateS4(); + } + else + { + parkImporter = CreateS6(); + } + return parkImporter; + } + + bool ExtensionIsRCT1(const std::string &extension) + { + if (String::Equals(extension, ".sc4", true) || + String::Equals(extension, ".sv4", true)) + { + return true; + } + return false; + } + + bool ExtensionIsScenario(const std::string &extension) + { + if (String::Equals(extension, ".sc4", true) || + String::Equals(extension, ".sc6", true)) + { + return true; + } + return false; + } +} + +extern "C" +{ + void park_importer_load_from_stream(void * stream_c, const utf8 * hintPath_c) + { + IStream * stream = (IStream *)stream_c; + std::string hintPath = String::ToStd(hintPath_c); + + std::string extension = Path::GetExtension(hintPath); + bool isScenario = ParkImporter::ExtensionIsScenario(hintPath); + + auto parkImporter = std::unique_ptr(ParkImporter::Create(hintPath)); + parkImporter->LoadFromStream((IStream *)stream, isScenario); + parkImporter->Import(); + } + + bool park_importer_extension_is_scenario(const utf8 * extension) + { + return ParkImporter::ExtensionIsScenario(String::ToStd(extension)); + } +} diff --git a/src/openrct2/rct1/S4Importer.h b/src/openrct2/ParkImporter.h similarity index 55% rename from src/openrct2/rct1/S4Importer.h rename to src/openrct2/ParkImporter.h index 64bb5df230..eb9957fa46 100644 --- a/src/openrct2/rct1/S4Importer.h +++ b/src/openrct2/ParkImporter.h @@ -16,20 +16,49 @@ #pragma once -#include "../common.h" -#include "../scenario/ScenarioRepository.h" +#include "common.h" + +#ifdef __cplusplus + +#include +#include "scenario/ScenarioRepository.h" + +interface IStream; /** - * Interface to import RollerCoaster Tycoon 1 scenarios (*.SC4) and saved games (*.SV4). + * Interface to import scenarios and saved games. */ -interface IS4Importer +interface IParkImporter { public: - virtual ~IS4Importer() { } + virtual ~IParkImporter() = default; + virtual void Load(const utf8 * path) abstract; virtual void LoadSavedGame(const utf8 * path) abstract; virtual void LoadScenario(const utf8 * path) abstract; + virtual void LoadFromStream(IStream * stream, bool isScenario) abstract; virtual void Import() abstract; virtual bool GetDetails(scenario_index_entry * dst) abstract; }; -IS4Importer * CreateS4Importer(); +namespace ParkImporter +{ + IParkImporter * Create(const std::string &hintPath); + IParkImporter * CreateS4(); + IParkImporter * CreateS6(); + + bool ExtensionIsRCT1(const std::string &extension); + bool ExtensionIsScenario(const std::string &extension); +} + +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + void park_importer_load_from_stream(void * stream, const utf8 * hintPath); + bool park_importer_extension_is_scenario(const utf8 * extension); + +#ifdef __cplusplus +} +#endif diff --git a/src/openrct2/cmdline/ConvertCommand.cpp b/src/openrct2/cmdline/ConvertCommand.cpp index 77d86057d5..9baecaee3b 100644 --- a/src/openrct2/cmdline/ConvertCommand.cpp +++ b/src/openrct2/cmdline/ConvertCommand.cpp @@ -14,12 +14,13 @@ *****************************************************************************/ #pragma endregion +#include #include "../common.h" #include "../core/Console.hpp" #include "../core/Exception.hpp" #include "../core/Guard.hpp" #include "../core/Path.hpp" -#include "../rct1/S4Importer.h" +#include "../ParkImporter.h" #include "CommandLine.hpp" extern "C" @@ -109,9 +110,9 @@ exitcode_t CommandLine::HandleCommandConvert(CommandLineArgEnumerator * enumerat if (sourceFileType == FILE_EXTENSION_SV4 || sourceFileType == FILE_EXTENSION_SC4) { - auto s4Importer = CreateS4Importer(); try { + auto s4Importer = std::unique_ptr(ParkImporter::CreateS4()); if (sourceFileType == FILE_EXTENSION_SC4) { s4Importer->LoadScenario(sourcePath); diff --git a/src/openrct2/core/FileStream.hpp b/src/openrct2/core/FileStream.hpp index f9a0660e0b..0b17273324 100644 --- a/src/openrct2/core/FileStream.hpp +++ b/src/openrct2/core/FileStream.hpp @@ -33,11 +33,12 @@ enum class FileStream final : public IStream { private: - SDL_RWops * _file; - bool _canRead; - bool _canWrite; - bool _disposed; - uint64 _fileSize; + SDL_RWops * _file = 0; + bool _ownsFilePtr = false; + bool _canRead = false; + bool _canWrite = false; + bool _disposed = false; + uint64 _fileSize = 0; public: FileStream(const std::string &path, sint32 fileMode) : @@ -68,8 +69,25 @@ public: { throw IOException(SDL_GetError()); } + _fileSize = SDL_RWsize(_file); + _ownsFilePtr = true; + } - _disposed = false; + FileStream(SDL_RWops * ops, sint32 fileMode) + { + _file = ops; + switch (fileMode) { + case FILE_MODE_OPEN: + _canRead = true; + _canWrite = false; + break; + case FILE_MODE_WRITE: + _canRead = false; + _canWrite = true; + break; + default: + throw; + } _fileSize = SDL_RWsize(_file); } @@ -78,7 +96,10 @@ public: if (!_disposed) { _disposed = true; - SDL_RWclose(_file); + if (_ownsFilePtr) + { + SDL_RWclose(_file); + } } } diff --git a/src/openrct2/core/Zip.cpp b/src/openrct2/core/Zip.cpp index cb98bb4851..161c22116e 100644 --- a/src/openrct2/core/Zip.cpp +++ b/src/openrct2/core/Zip.cpp @@ -16,6 +16,7 @@ #include #include "IStream.hpp" +#include "MemoryStream.h" #include "Zip.h" class ZipArchive final : public IZipArchive @@ -99,6 +100,18 @@ public: return data; } + IStream * GetFileStream(const utf8 * path) const override + { + IStream * stream = nullptr; + size_t dataSize; + void * data = GetFileData(path, &dataSize); + if (data != nullptr) + { + stream = new MemoryStream(data, dataSize, MEMORY_ACCESS_READ | MEMORY_ACCESS_OWNER); + } + return stream; + } + void SetFileData(const utf8 * path, void * data, size_t dataSize) override { zip_source_t * source = zip_source_buffer(_zip, data, dataSize, 0); diff --git a/src/openrct2/core/Zip.h b/src/openrct2/core/Zip.h index 8e3c8798ca..677110e78c 100644 --- a/src/openrct2/core/Zip.h +++ b/src/openrct2/core/Zip.h @@ -29,6 +29,7 @@ interface IZipArchive virtual const utf8 * GetFileName(size_t index) const abstract; virtual uint64 GetFileSize(size_t index) const abstract; virtual void * GetFileData(const utf8 * path, size_t * outSize) const abstract; + virtual IStream * GetFileStream(const utf8 * path) const abstract; /** * Creates or overwrites a file within the zip archive to the given data buffer. diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 9cf193573e..3d9c3089a1 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -73,6 +73,7 @@ + @@ -415,6 +416,7 @@ + @@ -543,11 +545,9 @@ - - diff --git a/src/openrct2/rct1.c b/src/openrct2/rct1.c index 3efc42f7a5..087dbfc049 100644 --- a/src/openrct2/rct1.c +++ b/src/openrct2/rct1.c @@ -21,62 +21,6 @@ #include "util/sawyercoding.h" #include "util/util.h" -bool rct1_read_sc4(const char *path, rct1_s4 *s4) -{ - uint8 *buffer, *decodedBuffer; - size_t length, decodedLength; - bool success; - - if (!readentirefile(path, (void**)&buffer, &length)) { - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; - return 0; - } - - sint32 fileType = sawyercoding_detect_file_type(buffer, length); - - decodedBuffer = malloc(sizeof(rct1_s4)); - decodedLength = (fileType & FILE_VERSION_MASK) == FILE_VERSION_RCT1 ? - sawyercoding_decode_sv4(buffer, decodedBuffer, length, sizeof(rct1_s4)) : - sawyercoding_decode_sc4(buffer, decodedBuffer, length, sizeof(rct1_s4)); - if (decodedLength == sizeof(rct1_s4)) { - memcpy(s4, decodedBuffer, sizeof(rct1_s4)); - success = true; - } else { - success = false; - } - - free(buffer); - free(decodedBuffer); - return success; -} - -bool rct1_read_sv4(const char *path, rct1_s4 *s4) -{ - uint8 *buffer, *decodedBuffer; - size_t length, decodedLength; - bool success; - - if (!readentirefile(path, (void**)&buffer, &length)) { - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; - return 0; - } - - decodedBuffer = malloc(sizeof(rct1_s4)); - decodedLength = sawyercoding_decode_sv4(buffer, decodedBuffer, length, sizeof(rct1_s4)); - if (decodedLength == sizeof(rct1_s4)) { - memcpy(s4, decodedBuffer, sizeof(rct1_s4)); - success = true; - } else { - success = false; - } - - free(buffer); - free(decodedBuffer); - return success; -} - bool rideTypeShouldLoseSeparateFlag(const rct_ride_entry *rideEntry) { if (!gConfigInterface.select_by_track_type) { diff --git a/src/openrct2/rct1.h b/src/openrct2/rct1.h index d67e1fc677..da53723295 100644 --- a/src/openrct2/rct1.h +++ b/src/openrct2/rct1.h @@ -1189,10 +1189,6 @@ enum { extern const uint8 gRideCategories[0x60]; -bool rct1_read_sc4(const char *path, rct1_s4 *s4); -bool rct1_read_sv4(const char *path, rct1_s4 *s4); -void rct1_import_s4(rct1_s4 *s4); -void rct1_fix_landscape(); sint32 vehicle_preference_compare(uint8 rideType, const char * a, const char * b); bool rideTypeShouldLoseSeparateFlag(const rct_ride_entry *rideEntry); diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index 389e507cf5..f7c662e184 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -14,18 +14,21 @@ *****************************************************************************/ #pragma endregion +#include #include #include "../core/Collections.hpp" #include "../core/Console.hpp" #include "../core/Exception.hpp" +#include "../core/FileStream.hpp" #include "../core/Guard.hpp" +#include "../core/IStream.hpp" #include "../core/Memory.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../core/Util.hpp" #include "../object/ObjectManager.h" +#include "../ParkImporter.h" #include "../scenario/ScenarioSources.h" -#include "S4Importer.h" #include "Tables.h" extern "C" @@ -88,12 +91,12 @@ public: } }; -class S4Importer final : public IS4Importer +class S4Importer final : public IParkImporter { private: - const utf8 * _s4Path; - rct1_s4 _s4; - uint8 _gameVersion; + const utf8 * _s4Path = nullptr; + rct1_s4 _s4 = { 0 }; + uint8 _gameVersion = 0; // Lists of dynamic object entries EntryList _rideEntries; @@ -120,24 +123,65 @@ private: uint8 _researchRideTypeUsed[128]; public: + void Load(const utf8 * path) override + { + const utf8 * extension = Path::GetExtension(path); + if (String::Equals(extension, ".sc4", true)) + { + LoadScenario(path); + } + else if (String::Equals(extension, ".sv4", true)) + { + LoadSavedGame(path); + } + else + { + throw Exception("Invalid RCT1 park extension."); + } + } + void LoadSavedGame(const utf8 * path) override { - if (!rct1_read_sv4(path, &_s4)) - { - throw Exception("Unable to load SV4."); - } + auto fs = FileStream(path, FILE_MODE_OPEN); + LoadFromStream(&fs, false); _s4Path = path; } void LoadScenario(const utf8 * path) override { - if (!rct1_read_sc4(path, &_s4)) - { - throw Exception("Unable to load SC4."); - } + auto fs = FileStream(path, FILE_MODE_OPEN); + LoadFromStream(&fs, true); _s4Path = path; } + void LoadFromStream(IStream * stream, bool isScenario) override + { + size_t dataSize = stream->GetLength() - stream->GetPosition(); + std::unique_ptr data = std::unique_ptr(stream->ReadArray(dataSize)); + std::unique_ptr decodedData = std::unique_ptr(Memory::Allocate(sizeof(rct1_s4))); + + size_t decodedSize; + sint32 fileType = sawyercoding_detect_file_type(data.get(), dataSize); + if (isScenario && (fileType & FILE_VERSION_MASK) != FILE_VERSION_RCT1) + { + decodedSize = sawyercoding_decode_sc4(data.get(), decodedData.get(), dataSize, sizeof(rct1_s4)); + } + else + { + decodedSize = sawyercoding_decode_sv4(data.get(), decodedData.get(), dataSize, sizeof(rct1_s4)); + } + + if (decodedSize == sizeof(rct1_s4)) + { + Memory::Copy(&_s4, decodedData.get(), sizeof(rct1_s4)); + _s4Path = ""; + } + else + { + throw Exception("Unable to decode park."); + } + } + void Import() override { Initialise(); @@ -2436,7 +2480,7 @@ private: } }; -IS4Importer * CreateS4Importer() +IParkImporter * ParkImporter::CreateS4() { return new S4Importer(); } diff --git a/src/openrct2/rct12/SawyerEncoding.cpp b/src/openrct2/rct12/SawyerEncoding.cpp index 68971a2b59..956ff32f17 100644 --- a/src/openrct2/rct12/SawyerEncoding.cpp +++ b/src/openrct2/rct12/SawyerEncoding.cpp @@ -14,7 +14,9 @@ *****************************************************************************/ #pragma endregion +#include #include "../core/IStream.hpp" +#include "../core/Math.hpp" #include "SawyerEncoding.h" extern "C" @@ -24,6 +26,47 @@ extern "C" namespace SawyerEncoding { + 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(); @@ -54,4 +97,41 @@ namespace SawyerEncoding } 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 + { + 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); + + // 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 188ebed2db..bf25b6824b 100644 --- a/src/openrct2/rct12/SawyerEncoding.h +++ b/src/openrct2/rct12/SawyerEncoding.h @@ -22,6 +22,8 @@ interface IStream; namespace SawyerEncoding { + 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 @@ -29,4 +31,6 @@ namespace SawyerEncoding { return TryReadChunk(dst, sizeof(T), stream); } + + bool ValidateChecksum(IStream * stream); } diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index dddb46ed79..6ba9e2ce2d 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -14,11 +14,16 @@ *****************************************************************************/ #pragma endregion +#include "../core/Console.hpp" #include "../core/Exception.hpp" +#include "../core/FileStream.hpp" #include "../core/IStream.hpp" +#include "../core/Path.hpp" +#include "../core/String.hpp" #include "../management/award.h" #include "../network/network.h" -#include "S6Importer.h" +#include "../ParkImporter.h" +#include "../rct12/SawyerEncoding.h" extern "C" { @@ -50,349 +55,351 @@ public: explicit ObjectLoadException(const char * message) : Exception(message) { } }; -S6Importer::S6Importer() +/** + * Class to import RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). + */ +class S6Importer final : public IParkImporter { - FixIssues = false; - memset(&_s6, 0, sizeof(_s6)); -} +private: + const utf8 * _s6Path = nullptr; + rct_s6_data _s6; + uint8 _gameVersion = 0; -void S6Importer::LoadSavedGame(const utf8 * path) +public: + S6Importer() + { + Memory::Set(&_s6, 0, sizeof(_s6)); + } + + void Load(const utf8 * path) override + { + const utf8 * extension = Path::GetExtension(path); + if (String::Equals(extension, ".sc6", true)) + { + LoadScenario(path); + } + else if (String::Equals(extension, ".sv6", true)) + { + LoadSavedGame(path); + } + else + { + throw Exception("Invalid RCT2 park extension."); + } + } + + void LoadSavedGame(const utf8 * path) override + { + auto fs = FileStream(path, FILE_MODE_OPEN); + LoadFromStream(&fs, false); + _s6Path = path; + } + + void LoadScenario(const utf8 * path) override + { + auto fs = FileStream(path, FILE_MODE_OPEN); + LoadFromStream(&fs, true); + _s6Path = path; + } + + void LoadFromStream(IStream * stream, bool isScenario) override + { + if (!gConfigGeneral.allow_loading_with_incorrect_checksum && !SawyerEncoding::ValidateChecksum(stream)) + { + throw IOException("Invalid checksum."); + } + + SawyerEncoding::ReadChunkTolerant(&_s6.header, sizeof(_s6.header), stream); + + log_verbose("saved game classic_flag = 0x%02x\n", _s6.header.classic_flag); + if (isScenario) + { + if (_s6.header.type != S6_TYPE_SCENARIO) + { + throw Exception("Park is not a scenario."); + } + SawyerEncoding::ReadChunkTolerant(&_s6.info, sizeof(_s6.info), stream); + } + else + { + if (_s6.header.type != S6_TYPE_SAVEDGAME) + { + throw Exception("Park is not a saved game."); + } + } + + // Read packed objects + // TODO try to contain this more and not store objects until later + for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) + { + // object_load_packed(rw); + } + + 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); + } + 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); + } + } + + bool GetDetails(scenario_index_entry * dst) override + { + Memory::Set(dst, 0, sizeof(scenario_index_entry)); + return false; + } + + void Import() override + { + Initialise(); + + // _s6.header + gS6Info = _s6.info; + + gDateMonthsElapsed = _s6.elapsed_months; + gDateMonthTicks = _s6.current_day; + gScenarioTicks = _s6.scenario_ticks; + gScenarioSrand0 = _s6.scenario_srand_0; + gScenarioSrand1 = _s6.scenario_srand_1; + + memcpy(gMapElements, _s6.map_elements, sizeof(_s6.map_elements)); + + gNextFreeMapElementPointerIndex = _s6.next_free_map_element_pointer_index; + for (sint32 i = 0; i < MAX_SPRITES; i++) + { + memcpy(get_sprite(i), &_s6.sprites[i], sizeof(rct_sprite)); + } + + for (sint32 i = 0; i < NUM_SPRITE_LISTS; i++) + { + gSpriteListHead[i] = _s6.sprite_lists_head[i]; + gSpriteListCount[i] = _s6.sprite_lists_count[i]; + } + gParkName = _s6.park_name; + // pad_013573D6 + gParkNameArgs = _s6.park_name_args; + gInitialCash = _s6.initial_cash; + gBankLoan = _s6.current_loan; + gParkFlags = _s6.park_flags; + gParkEntranceFee = _s6.park_entrance_fee; + // rct1_park_entrance_x + // rct1_park_entrance_y + // pad_013573EE + // rct1_park_entrance_z + memcpy(gPeepSpawns, _s6.peep_spawns, sizeof(_s6.peep_spawns)); + gGuestChangeModifier = _s6.guest_count_change_modifier; + gResearchFundingLevel = _s6.current_research_level; + // pad_01357400 + memcpy(gResearchedRideTypes, _s6.researched_ride_types, sizeof(_s6.researched_ride_types)); + memcpy(gResearchedRideEntries, _s6.researched_ride_entries, sizeof(_s6.researched_ride_entries)); + memcpy(gResearchedTrackTypesA, _s6.researched_track_types_a, sizeof(_s6.researched_track_types_a)); + memcpy(gResearchedTrackTypesB, _s6.researched_track_types_b, sizeof(_s6.researched_track_types_b)); + + gNumGuestsInPark = _s6.guests_in_park; + gNumGuestsHeadingForPark = _s6.guests_heading_for_park; + + memcpy(gExpenditureTable, _s6.expenditure_table, sizeof(_s6.expenditure_table)); + + gNumGuestsInParkLastWeek = _s6.last_guests_in_park; + // pad_01357BCA + gStaffHandymanColour = _s6.handyman_colour; + gStaffMechanicColour = _s6.mechanic_colour; + gStaffSecurityColour = _s6.security_colour; + + memcpy(gResearchedSceneryItems, _s6.researched_scenery_items, sizeof(_s6.researched_scenery_items)); + + gParkRating = _s6.park_rating; + + memcpy(gParkRatingHistory, _s6.park_rating_history, sizeof(_s6.park_rating_history)); + memcpy(gGuestsInParkHistory, _s6.guests_in_park_history, sizeof(_s6.guests_in_park_history)); + + gResearchPriorities = _s6.active_research_types; + gResearchProgressStage = _s6.research_progress_stage; + gResearchLastItemSubject = _s6.last_researched_item_subject; + // pad_01357CF8 + gResearchNextItem = _s6.next_research_item; + gResearchProgress = _s6.research_progress; + gResearchNextCategory = _s6.next_research_category; + gResearchExpectedDay = _s6.next_research_expected_day; + gResearchExpectedMonth = _s6.next_research_expected_month; + gGuestInitialHappiness = _s6.guest_initial_happiness; + gParkSize = _s6.park_size; + _guestGenerationProbability = _s6.guest_generation_probability; + gTotalRideValue = _s6.total_ride_value; + gMaxBankLoan = _s6.maximum_loan; + gGuestInitialCash = _s6.guest_initial_cash; + gGuestInitialHunger = _s6.guest_initial_hunger; + gGuestInitialThirst = _s6.guest_initial_thirst; + gScenarioObjectiveType = _s6.objective_type; + gScenarioObjectiveYear = _s6.objective_year; + // pad_013580FA + gScenarioObjectiveCurrency = _s6.objective_currency; + gScenarioObjectiveNumGuests = _s6.objective_guests; + memcpy(gMarketingCampaignDaysLeft, _s6.campaign_weeks_left, sizeof(_s6.campaign_weeks_left)); + memcpy(gMarketingCampaignRideIndex, _s6.campaign_ride_index, sizeof(_s6.campaign_ride_index)); + + memcpy(gCashHistory, _s6.balance_history, sizeof(_s6.balance_history)); + + gCurrentExpenditure = _s6.current_expenditure; + gCurrentProfit = _s6.current_profit; + gWeeklyProfitAverageDividend = _s6.weekly_profit_average_dividend; + gWeeklyProfitAverageDivisor = _s6.weekly_profit_average_divisor; + // pad_0135833A + + memcpy(gWeeklyProfitHistory, _s6.weekly_profit_history, sizeof(_s6.weekly_profit_history)); + + gParkValue = _s6.park_value; + + memcpy(gParkValueHistory, _s6.park_value_history, sizeof(_s6.park_value_history)); + + gScenarioCompletedCompanyValue = _s6.completed_company_value; + gTotalAdmissions = _s6.total_admissions; + gTotalIncomeFromAdmissions = _s6.income_from_admissions; + gCompanyValue = _s6.company_value; + memcpy(gPeepWarningThrottle, _s6.peep_warning_throttle, sizeof(_s6.peep_warning_throttle)); + + // Awards + for (sint32 i = 0; i < RCT12_MAX_AWARDS; i++) + { + rct12_award * src = &_s6.awards[i]; + Award * dst = &gCurrentAwards[i]; + dst->Time = src->time; + dst->Type = src->type; + } + + gLandPrice = _s6.land_price; + gConstructionRightsPrice = _s6.construction_rights_price; + // unk_01358774 + // pad_01358776 + // _s6.cd_key + _gameVersion = _s6.game_version_number; + gScenarioCompanyValueRecord = _s6.completed_company_value_record; + // _s6.loan_hash; + gRideCount = _s6.ride_count; + // pad_013587CA + gHistoricalProfit = _s6.historical_profit; + // pad_013587D4 + memcpy(gScenarioCompletedBy, _s6.scenario_completed_name, sizeof(_s6.scenario_completed_name)); + gCashEncrypted = _s6.cash; + // pad_013587FC + gParkRatingCasualtyPenalty = _s6.park_rating_casualty_penalty; + gMapSizeUnits = _s6.map_size_units; + gMapSizeMinus2 = _s6.map_size_minus_2; + gMapSize = _s6.map_size; + gMapSizeMaxXY = _s6.map_max_xy; + gSamePriceThroughoutParkA = _s6.same_price_throughout; + _suggestedGuestMaximum = _s6.suggested_max_guests; + gScenarioParkRatingWarningDays = _s6.park_rating_warning_days; + gLastEntranceStyle = _s6.last_entrance_style; + // rct1_water_colour + // pad_01358842 + memcpy(gResearchItems, _s6.research_items, sizeof(_s6.research_items)); + gMapBaseZ = _s6.map_base_z; + memcpy(gScenarioName, _s6.scenario_name, sizeof(_s6.scenario_name)); + memcpy(gScenarioDetails, _s6.scenario_description, sizeof(_s6.scenario_description)); + gBankLoanInterestRate = _s6.current_interest_rate; + // pad_0135934B + gSamePriceThroughoutParkB = _s6.same_price_throughout_extended; + memcpy(gParkEntranceX, _s6.park_entrance_x, sizeof(_s6.park_entrance_x)); + memcpy(gParkEntranceY, _s6.park_entrance_y, sizeof(_s6.park_entrance_y)); + memcpy(gParkEntranceZ, _s6.park_entrance_z, sizeof(_s6.park_entrance_z)); + memcpy(gParkEntranceDirection, _s6.park_entrance_direction, sizeof(_s6.park_entrance_direction)); + scenario_set_filename(_s6.scenario_filename); + memcpy(gScenarioExpansionPacks, _s6.saved_expansion_pack_names, sizeof(_s6.saved_expansion_pack_names)); + memcpy(gBanners, _s6.banners, sizeof(_s6.banners)); + memcpy(gUserStrings, _s6.custom_strings, sizeof(_s6.custom_strings)); + gCurrentTicks = _s6.game_ticks_1; + memcpy(gRideList, _s6.rides, sizeof(_s6.rides)); + gSavedAge = _s6.saved_age; + gSavedViewX = _s6.saved_view_x; + gSavedViewY = _s6.saved_view_y; + gSavedViewZoom = _s6.saved_view_zoom; + gSavedViewRotation = _s6.saved_view_rotation; + memcpy(gAnimatedObjects, _s6.map_animations, sizeof(_s6.map_animations)); + gNumMapAnimations = _s6.num_map_animations; + // pad_0138B582 + + gRideRatingsCalcData = _s6.ride_ratings_calc_data; + memcpy(gRideMeasurements, _s6.ride_measurements, sizeof(_s6.ride_measurements)); + gNextGuestNumber = _s6.next_guest_index; + gGrassSceneryTileLoopPosition = _s6.grass_and_scenery_tilepos; + memcpy(gStaffPatrolAreas, _s6.patrol_areas, sizeof(_s6.patrol_areas)); + memcpy(gStaffModes, _s6.staff_modes, sizeof(_s6.staff_modes)); + // unk_13CA73E + // pad_13CA73F + gUnk13CA740 = _s6.byte_13CA740; + gClimate = _s6.climate; + // pad_13CA741; + // byte_13CA742 + // pad_013CA747 + gClimateUpdateTimer = _s6.climate_update_timer; + gClimateCurrentWeather = _s6.current_weather; + gClimateNextWeather = _s6.next_weather; + gClimateCurrentTemperature = _s6.temperature; + gClimateNextTemperature = _s6.next_temperature; + gClimateCurrentWeatherEffect = _s6.current_weather_effect; + gClimateNextWeatherEffect = _s6.next_weather_effect; + gClimateCurrentWeatherGloom = _s6.current_weather_gloom; + gClimateNextWeatherGloom = _s6.next_weather_gloom; + gClimateCurrentRainLevel = _s6.current_rain_level; + gClimateNextRainLevel = _s6.next_rain_level; + + // News items + for (size_t i = 0; i < RCT12_MAX_NEWS_ITEMS; i++) + { + const rct12_news_item * src = &_s6.news_items[i]; + NewsItem * dst = &gNewsItems[i]; + + dst->Type = src->Type; + dst->Flags = src->Flags; + dst->Assoc = src->Assoc; + dst->Ticks = src->Ticks; + dst->MonthYear = src->MonthYear; + dst->Day = src->Day; + memcpy(dst->Text, src->Text, sizeof(src->Text)); + } + + // pad_13CE730 + // rct1_scenario_flags + gWidePathTileLoopX = _s6.wide_path_tile_loop_x; + gWidePathTileLoopY = _s6.wide_path_tile_loop_y; + // pad_13CE778 + + // Fix and set dynamic variables + if (!object_load_entries(_s6.objects)) + { + throw ObjectLoadException(); + } + map_strip_ghost_flag_from_elements(); + map_update_tile_pointers(); + game_convert_strings_to_utf8(); + map_count_remaining_land_rights(); + } + + void Initialise() + { + game_init_all(_s6.map_size); + } +}; + +IParkImporter * ParkImporter::CreateS6() { - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw == nullptr) - { - throw IOException("Unable to open SV6."); - } - - if (!sawyercoding_validate_checksum(rw)) - { - gErrorType = ERROR_TYPE_FILE_LOAD; - gGameCommandErrorTitle = STR_FILE_CONTAINS_INVALID_DATA; - - log_error("failed to load saved game, invalid checksum"); - throw IOException("Invalid SV6 checksum."); - } - - LoadSavedGame(rw); - - SDL_RWclose(rw); - - _s6Path = path; -} - -void S6Importer::LoadScenario(const utf8 * path) -{ - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw == nullptr) - { - throw IOException("Unable to open SV6."); - } - - if (!gConfigGeneral.allow_loading_with_incorrect_checksum && !sawyercoding_validate_checksum(rw)) - { - SDL_RWclose(rw); - - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_FILE_CONTAINS_INVALID_DATA; - - log_error("failed to load scenario, invalid checksum"); - throw IOException("Invalid SC6 checksum."); - } - - LoadScenario(rw); - - SDL_RWclose(rw); - - _s6Path = path; -} - -void S6Importer::LoadSavedGame(SDL_RWops *rw) -{ - sawyercoding_read_chunk_safe(rw, &_s6.header, sizeof(_s6.header)); - if (_s6.header.type != S6_TYPE_SAVEDGAME) - { - throw Exception("Data is not a saved game."); - } - log_verbose("saved game classic_flag = 0x%02x\n", _s6.header.classic_flag); - - // Read packed objects - // TODO try to contain this more and not store objects until later - for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) - { - object_load_packed(rw); - } - - sawyercoding_read_chunk_safe(rw, &_s6.objects, sizeof(_s6.objects)); - sawyercoding_read_chunk_safe(rw, &_s6.elapsed_months, 16); - sawyercoding_read_chunk_safe(rw, &_s6.map_elements, sizeof(_s6.map_elements)); - sawyercoding_read_chunk_safe(rw, &_s6.next_free_map_element_pointer_index, 3048816); -} - -void S6Importer::LoadScenario(SDL_RWops *rw) -{ - sawyercoding_read_chunk_safe(rw, &_s6.header, sizeof(_s6.header)); - if (_s6.header.type != S6_TYPE_SCENARIO) - { - throw Exception("Data is not a scenario."); - } - log_verbose("scenario classic_flag = 0x%02x\n", _s6.header.classic_flag); - - sawyercoding_read_chunk_safe(rw, &_s6.info, sizeof(_s6.info)); - - // Read packed objects - // TODO try to contain this more and not store objects until later - for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) - { - object_load_packed(rw); - } - - sawyercoding_read_chunk_safe(rw, &_s6.objects, sizeof(_s6.objects)); - sawyercoding_read_chunk_safe(rw, &_s6.elapsed_months, 16); - sawyercoding_read_chunk_safe(rw, &_s6.map_elements, sizeof(_s6.map_elements)); - sawyercoding_read_chunk_safe(rw, &_s6.next_free_map_element_pointer_index, 2560076); - sawyercoding_read_chunk_safe(rw, &_s6.guests_in_park, 4); - sawyercoding_read_chunk_safe(rw, &_s6.last_guests_in_park, 8); - sawyercoding_read_chunk_safe(rw, &_s6.park_rating, 2); - sawyercoding_read_chunk_safe(rw, &_s6.active_research_types, 1082); - sawyercoding_read_chunk_safe(rw, &_s6.current_expenditure, 16); - sawyercoding_read_chunk_safe(rw, &_s6.park_value, 4); - sawyercoding_read_chunk_safe(rw, &_s6.completed_company_value, 483816); -} - -void S6Importer::Import() -{ - Initialise(); - - // _s6.header - gS6Info = _s6.info; - - gDateMonthsElapsed = _s6.elapsed_months; - gDateMonthTicks = _s6.current_day; - gScenarioTicks = _s6.scenario_ticks; - gScenarioSrand0 = _s6.scenario_srand_0; - gScenarioSrand1 = _s6.scenario_srand_1; - - memcpy(gMapElements, _s6.map_elements, sizeof(_s6.map_elements)); - - gNextFreeMapElementPointerIndex = _s6.next_free_map_element_pointer_index; - for (sint32 i = 0; i < MAX_SPRITES; i++) - { - memcpy(get_sprite(i), &_s6.sprites[i], sizeof(rct_sprite)); - } - - for (sint32 i = 0; i < NUM_SPRITE_LISTS; i++) - { - gSpriteListHead[i] = _s6.sprite_lists_head[i]; - gSpriteListCount[i] = _s6.sprite_lists_count[i]; - } - gParkName = _s6.park_name; - // pad_013573D6 - gParkNameArgs = _s6.park_name_args; - gInitialCash = _s6.initial_cash; - gBankLoan = _s6.current_loan; - gParkFlags = _s6.park_flags; - gParkEntranceFee = _s6.park_entrance_fee; - // rct1_park_entrance_x - // rct1_park_entrance_y - // pad_013573EE - // rct1_park_entrance_z - memcpy(gPeepSpawns, _s6.peep_spawns, sizeof(_s6.peep_spawns)); - gGuestChangeModifier = _s6.guest_count_change_modifier; - gResearchFundingLevel = _s6.current_research_level; - // pad_01357400 - memcpy(gResearchedRideTypes, _s6.researched_ride_types, sizeof(_s6.researched_ride_types)); - memcpy(gResearchedRideEntries, _s6.researched_ride_entries, sizeof(_s6.researched_ride_entries)); - memcpy(gResearchedTrackTypesA, _s6.researched_track_types_a, sizeof(_s6.researched_track_types_a)); - memcpy(gResearchedTrackTypesB, _s6.researched_track_types_b, sizeof(_s6.researched_track_types_b)); - - gNumGuestsInPark = _s6.guests_in_park; - gNumGuestsHeadingForPark = _s6.guests_heading_for_park; - - memcpy(gExpenditureTable, _s6.expenditure_table, sizeof(_s6.expenditure_table)); - - gNumGuestsInParkLastWeek = _s6.last_guests_in_park; - // pad_01357BCA - gStaffHandymanColour = _s6.handyman_colour; - gStaffMechanicColour = _s6.mechanic_colour; - gStaffSecurityColour = _s6.security_colour; - - memcpy(gResearchedSceneryItems, _s6.researched_scenery_items, sizeof(_s6.researched_scenery_items)); - - gParkRating = _s6.park_rating; - - memcpy(gParkRatingHistory, _s6.park_rating_history, sizeof(_s6.park_rating_history)); - memcpy(gGuestsInParkHistory, _s6.guests_in_park_history, sizeof(_s6.guests_in_park_history)); - - gResearchPriorities = _s6.active_research_types; - gResearchProgressStage = _s6.research_progress_stage; - gResearchLastItemSubject = _s6.last_researched_item_subject; - // pad_01357CF8 - gResearchNextItem = _s6.next_research_item; - gResearchProgress = _s6.research_progress; - gResearchNextCategory = _s6.next_research_category; - gResearchExpectedDay = _s6.next_research_expected_day; - gResearchExpectedMonth = _s6.next_research_expected_month; - gGuestInitialHappiness = _s6.guest_initial_happiness; - gParkSize = _s6.park_size; - _guestGenerationProbability = _s6.guest_generation_probability; - gTotalRideValue = _s6.total_ride_value; - gMaxBankLoan = _s6.maximum_loan; - gGuestInitialCash = _s6.guest_initial_cash; - gGuestInitialHunger = _s6.guest_initial_hunger; - gGuestInitialThirst = _s6.guest_initial_thirst; - gScenarioObjectiveType = _s6.objective_type; - gScenarioObjectiveYear = _s6.objective_year; - // pad_013580FA - gScenarioObjectiveCurrency = _s6.objective_currency; - gScenarioObjectiveNumGuests = _s6.objective_guests; - memcpy(gMarketingCampaignDaysLeft, _s6.campaign_weeks_left, sizeof(_s6.campaign_weeks_left)); - memcpy(gMarketingCampaignRideIndex, _s6.campaign_ride_index, sizeof(_s6.campaign_ride_index)); - - memcpy(gCashHistory, _s6.balance_history, sizeof(_s6.balance_history)); - - gCurrentExpenditure = _s6.current_expenditure; - gCurrentProfit = _s6.current_profit; - gWeeklyProfitAverageDividend = _s6.weekly_profit_average_dividend; - gWeeklyProfitAverageDivisor = _s6.weekly_profit_average_divisor; - // pad_0135833A - - memcpy(gWeeklyProfitHistory, _s6.weekly_profit_history, sizeof(_s6.weekly_profit_history)); - - gParkValue = _s6.park_value; - - memcpy(gParkValueHistory, _s6.park_value_history, sizeof(_s6.park_value_history)); - - gScenarioCompletedCompanyValue = _s6.completed_company_value; - gTotalAdmissions = _s6.total_admissions; - gTotalIncomeFromAdmissions = _s6.income_from_admissions; - gCompanyValue = _s6.company_value; - memcpy(gPeepWarningThrottle, _s6.peep_warning_throttle, sizeof(_s6.peep_warning_throttle)); - - // Awards - for (sint32 i = 0; i < RCT12_MAX_AWARDS; i++) - { - rct12_award * src = &_s6.awards[i]; - Award * dst = &gCurrentAwards[i]; - dst->Time = src->time; - dst->Type = src->type; - } - - gLandPrice = _s6.land_price; - gConstructionRightsPrice = _s6.construction_rights_price; - // unk_01358774 - // pad_01358776 - // _s6.cd_key - _gameVersion = _s6.game_version_number; - gScenarioCompanyValueRecord = _s6.completed_company_value_record; - // _s6.loan_hash; - gRideCount = _s6.ride_count; - // pad_013587CA - gHistoricalProfit = _s6.historical_profit; - // pad_013587D4 - memcpy(gScenarioCompletedBy, _s6.scenario_completed_name, sizeof(_s6.scenario_completed_name)); - gCashEncrypted = _s6.cash; - // pad_013587FC - gParkRatingCasualtyPenalty = _s6.park_rating_casualty_penalty; - gMapSizeUnits = _s6.map_size_units; - gMapSizeMinus2 = _s6.map_size_minus_2; - gMapSize = _s6.map_size; - gMapSizeMaxXY = _s6.map_max_xy; - gSamePriceThroughoutParkA = _s6.same_price_throughout; - _suggestedGuestMaximum = _s6.suggested_max_guests; - gScenarioParkRatingWarningDays = _s6.park_rating_warning_days; - gLastEntranceStyle = _s6.last_entrance_style; - // rct1_water_colour - // pad_01358842 - memcpy(gResearchItems, _s6.research_items, sizeof(_s6.research_items)); - gMapBaseZ = _s6.map_base_z; - memcpy(gScenarioName, _s6.scenario_name, sizeof(_s6.scenario_name)); - memcpy(gScenarioDetails, _s6.scenario_description, sizeof(_s6.scenario_description)); - gBankLoanInterestRate = _s6.current_interest_rate; - // pad_0135934B - gSamePriceThroughoutParkB = _s6.same_price_throughout_extended; - memcpy(gParkEntranceX, _s6.park_entrance_x, sizeof(_s6.park_entrance_x)); - memcpy(gParkEntranceY, _s6.park_entrance_y, sizeof(_s6.park_entrance_y)); - memcpy(gParkEntranceZ, _s6.park_entrance_z, sizeof(_s6.park_entrance_z)); - memcpy(gParkEntranceDirection, _s6.park_entrance_direction, sizeof(_s6.park_entrance_direction)); - scenario_set_filename(_s6.scenario_filename); - memcpy(gScenarioExpansionPacks, _s6.saved_expansion_pack_names, sizeof(_s6.saved_expansion_pack_names)); - memcpy(gBanners, _s6.banners, sizeof(_s6.banners)); - memcpy(gUserStrings, _s6.custom_strings, sizeof(_s6.custom_strings)); - gCurrentTicks = _s6.game_ticks_1; - memcpy(gRideList, _s6.rides, sizeof(_s6.rides)); - gSavedAge = _s6.saved_age; - gSavedViewX = _s6.saved_view_x; - gSavedViewY = _s6.saved_view_y; - gSavedViewZoom = _s6.saved_view_zoom; - gSavedViewRotation = _s6.saved_view_rotation; - memcpy(gAnimatedObjects, _s6.map_animations, sizeof(_s6.map_animations)); - gNumMapAnimations = _s6.num_map_animations; - // pad_0138B582 - - gRideRatingsCalcData = _s6.ride_ratings_calc_data; - memcpy(gRideMeasurements, _s6.ride_measurements, sizeof(_s6.ride_measurements)); - gNextGuestNumber = _s6.next_guest_index; - gGrassSceneryTileLoopPosition = _s6.grass_and_scenery_tilepos; - memcpy(gStaffPatrolAreas, _s6.patrol_areas, sizeof(_s6.patrol_areas)); - memcpy(gStaffModes, _s6.staff_modes, sizeof(_s6.staff_modes)); - // unk_13CA73E - // pad_13CA73F - gUnk13CA740 = _s6.byte_13CA740; - gClimate = _s6.climate; - // pad_13CA741; - // byte_13CA742 - // pad_013CA747 - gClimateUpdateTimer = _s6.climate_update_timer; - gClimateCurrentWeather = _s6.current_weather; - gClimateNextWeather = _s6.next_weather; - gClimateCurrentTemperature = _s6.temperature; - gClimateNextTemperature = _s6.next_temperature; - gClimateCurrentWeatherEffect = _s6.current_weather_effect; - gClimateNextWeatherEffect = _s6.next_weather_effect; - gClimateCurrentWeatherGloom = _s6.current_weather_gloom; - gClimateNextWeatherGloom = _s6.next_weather_gloom; - gClimateCurrentRainLevel = _s6.current_rain_level; - gClimateNextRainLevel = _s6.next_rain_level; - - // News items - for (size_t i = 0; i < RCT12_MAX_NEWS_ITEMS; i++) - { - const rct12_news_item * src = &_s6.news_items[i]; - NewsItem * dst = &gNewsItems[i]; - - dst->Type = src->Type; - dst->Flags = src->Flags; - dst->Assoc = src->Assoc; - dst->Ticks = src->Ticks; - dst->MonthYear = src->MonthYear; - dst->Day = src->Day; - memcpy(dst->Text, src->Text, sizeof(src->Text)); - } - - // pad_13CE730 - // rct1_scenario_flags - gWidePathTileLoopX = _s6.wide_path_tile_loop_x; - gWidePathTileLoopY = _s6.wide_path_tile_loop_y; - // pad_13CE778 - - // Fix and set dynamic variables - if (!object_load_entries(_s6.objects)) - { - throw ObjectLoadException(); - } - map_strip_ghost_flag_from_elements(); - map_update_tile_pointers(); - game_convert_strings_to_utf8(); - map_count_remaining_land_rights(); - if (FixIssues) - { - game_fix_save_vars(); - } -} - -void S6Importer::Initialise() -{ - game_init_all(_s6.map_size); + return new S6Importer(); } extern "C" @@ -413,21 +420,24 @@ extern "C" } bool result = false; + auto stream = FileStream(rw, FILE_MODE_OPEN); auto s6Importer = new S6Importer(); try { - s6Importer->FixIssues = true; - s6Importer->LoadSavedGame(rw); + s6Importer->LoadFromStream(&stream, false); s6Importer->Import(); + game_fix_save_vars(); sprite_position_tween_reset(); result = true; } - catch (const ObjectLoadException &) + catch (const ObjectLoadException &ex) { + Console::Error::WriteLine(ex.GetMessage()); } - catch (const Exception &) + catch (const Exception &ex) { + Console::Error::WriteLine(ex.GetMessage()); } delete s6Importer; @@ -443,10 +453,10 @@ extern "C" auto s6Importer = new S6Importer(); try { - s6Importer->FixIssues = true; s6Importer->LoadSavedGame(path); s6Importer->Import(); + game_fix_save_vars(); sprite_position_tween_reset(); result = true; } @@ -475,13 +485,14 @@ extern "C" bool scenario_load_rw(SDL_RWops * rw) { bool result = false; + auto stream = FileStream(rw, FILE_MODE_OPEN); auto s6Importer = new S6Importer(); try { - s6Importer->FixIssues = true; - s6Importer->LoadScenario(rw); + s6Importer->LoadFromStream(&stream, true); s6Importer->Import(); + game_fix_save_vars(); sprite_position_tween_reset(); result = true; } @@ -518,10 +529,10 @@ extern "C" auto s6Importer = new S6Importer(); try { - s6Importer->FixIssues = true; s6Importer->LoadScenario(path); s6Importer->Import(); + game_fix_save_vars(); sprite_position_tween_reset(); result = true; } @@ -550,10 +561,11 @@ extern "C" sint32 game_load_network(SDL_RWops* rw) { bool result = false; + auto stream = FileStream(rw, FILE_MODE_OPEN); auto s6Importer = new S6Importer(); try { - s6Importer->LoadSavedGame(rw); + s6Importer->LoadFromStream(&stream, false); s6Importer->Import(); sprite_position_tween_reset(); diff --git a/src/openrct2/rct2/S6Importer.h b/src/openrct2/rct2/S6Importer.h deleted file mode 100644 index 29e1f0f423..0000000000 --- a/src/openrct2/rct2/S6Importer.h +++ /dev/null @@ -1,48 +0,0 @@ -#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" - -extern "C" -{ - #include "../scenario/scenario.h" -} - -/** - * Class to import RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). - */ -class S6Importer final -{ -public: - bool FixIssues; - - S6Importer(); - - void LoadSavedGame(const utf8 * path); - void LoadSavedGame(SDL_RWops *rw); - void LoadScenario(const utf8 * path); - void LoadScenario(SDL_RWops *rw); - void Import(); - -private: - const utf8 * _s6Path = nullptr; - rct_s6_data _s6; - uint8 _gameVersion = 0; - - void Initialise(); -}; diff --git a/src/openrct2/scenario/ScenarioRepository.cpp b/src/openrct2/scenario/ScenarioRepository.cpp index c37881ec27..a5f394ed16 100644 --- a/src/openrct2/scenario/ScenarioRepository.cpp +++ b/src/openrct2/scenario/ScenarioRepository.cpp @@ -24,8 +24,8 @@ #include "../core/Path.hpp" #include "../core/String.hpp" #include "../core/Util.hpp" +#include "../ParkImporter.h" #include "../PlatformEnvironment.h" -#include "../rct1/S4Importer.h" #include "../rct12/SawyerEncoding.h" #include "ScenarioRepository.h" #include "ScenarioSources.h" @@ -308,7 +308,7 @@ private: */ bool GetScenarioInfo(const std::string &path, uint64 timestamp, scenario_index_entry * entry) { - log_verbose("GetScenarioInfo(%s, ...)", path.c_str()); + log_verbose("GetScenarioInfo(%s, %d, ...)", path.c_str(), timestamp); try { std::string extension = Path::GetExtension(path); @@ -316,15 +316,20 @@ private: { // RCT1 scenario bool result = false; - IS4Importer * s4Importer = CreateS4Importer(); - s4Importer->LoadScenario(path.c_str()); - if (s4Importer->GetDetails(entry)) + try + { + auto s4Importer = std::unique_ptr(ParkImporter::CreateS4()); + s4Importer->LoadScenario(path.c_str()); + if (s4Importer->GetDetails(entry)) + { + String::Set(entry->path, sizeof(entry->path), path.c_str()); + entry->timestamp = timestamp; + result = true; + } + } + catch (Exception) { - String::Set(entry->path, sizeof(entry->path), path.c_str()); - entry->timestamp = timestamp; - result = true; } - delete s4Importer; return result; } else diff --git a/src/openrct2/title/TitleSequence.cpp b/src/openrct2/title/TitleSequence.cpp index 68cccd8993..2181254a74 100644 --- a/src/openrct2/title/TitleSequence.cpp +++ b/src/openrct2/title/TitleSequence.cpp @@ -25,6 +25,7 @@ #include "../core/Guard.hpp" #include "../core/Math.hpp" #include "../core/Memory.hpp" +#include "../core/MemoryStream.h" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../core/StringBuilder.hpp" @@ -140,9 +141,8 @@ extern "C" if (zip != nullptr) { handle = Memory::Allocate(); - handle->Data = zip->GetFileData(filename, &handle->DataSize); - handle->RWOps = SDL_RWFromMem(handle->Data, (sint32)handle->DataSize); - handle->IsScenario = String::Equals(Path::GetExtension(filename), ".sc6", true); + handle->Stream = zip->GetFileStream(filename); + handle->HintPath = String::Duplicate(filename); delete zip; } } @@ -153,9 +153,8 @@ extern "C" Path::Append(absolutePath, sizeof(absolutePath), filename); handle = Memory::Allocate(); - handle->Data = nullptr; - handle->RWOps = SDL_RWFromFile(absolutePath, "rb"); - handle->IsScenario = String::Equals(Path::GetExtension(filename), ".sc6", true); + handle->Stream = new FileStream(absolutePath, FILE_MODE_OPEN); + handle->HintPath = String::Duplicate(filename); } } return handle; @@ -165,9 +164,8 @@ extern "C" { if (handle != nullptr) { - SDL_RWclose(handle->RWOps); - Memory::Free(handle->Data); - Memory::Free(handle); + Memory::Free(handle->HintPath); + delete ((IStream *)handle->Stream); } } diff --git a/src/openrct2/title/TitleSequence.h b/src/openrct2/title/TitleSequence.h index c6237cb492..924adb687e 100644 --- a/src/openrct2/title/TitleSequence.h +++ b/src/openrct2/title/TitleSequence.h @@ -53,10 +53,8 @@ typedef struct TitleSequence typedef struct TitleSequenceParkHandle { - size_t DataSize; - void * Data; - struct SDL_RWops * RWOps; - bool IsScenario; + const utf8 * HintPath; + void * Stream; } TitleSequenceParkHandle; enum TITLE_SCRIPT diff --git a/src/openrct2/title/TitleSequencePlayer.cpp b/src/openrct2/title/TitleSequencePlayer.cpp index 0505b531ef..c113fe55ce 100644 --- a/src/openrct2/title/TitleSequencePlayer.cpp +++ b/src/openrct2/title/TitleSequencePlayer.cpp @@ -14,15 +14,15 @@ *****************************************************************************/ #pragma endregion +#include #include "../common.h" -#include #include "../core/Console.hpp" #include "../core/Exception.hpp" #include "../core/Guard.hpp" #include "../core/Math.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" -#include "../rct1/S4Importer.h" +#include "../ParkImporter.h" #include "../scenario/ScenarioRepository.h" #include "../scenario/ScenarioSources.h" #include "TitleSequence.h" @@ -285,7 +285,7 @@ private: TitleSequenceParkHandle * parkHandle = TitleSequenceGetParkHandle(_sequence, saveIndex); if (parkHandle != nullptr) { - loadSuccess = LoadParkFromRW(parkHandle->RWOps, parkHandle->IsScenario); + loadSuccess = LoadParkFromStream((IStream *)parkHandle->Stream, parkHandle->HintPath); TitleSequenceCloseParkHandle(parkHandle); } if (!loadSuccess) @@ -352,57 +352,49 @@ private: bool LoadParkFromFile(const utf8 * path) { + log_verbose("TitleSequencePlayer::LoadParkFromFile(%s)", path); bool success = false; - const utf8 * extension = Path::GetExtension(path); - if (String::Equals(extension, ".sc4", true) || - String::Equals(extension, ".sv4", true)) + try { - try - { - bool isScenario = String::Equals(extension, ".sc4", true); - IS4Importer * s4Importer = CreateS4Importer(); - if (isScenario) - { - s4Importer->LoadScenario(path); - s4Importer->Import(); - } - else - { - s4Importer->LoadSavedGame(path); - s4Importer->Import(); - } - PrepareParkForPlayback(isScenario); - success = true; - } - catch (Exception) - { - } + auto parkImporter = std::unique_ptr(ParkImporter::Create(path)); + parkImporter->Load(path); + parkImporter->Import(); + PrepareParkForPlayback(); + success = true; } - else + catch (Exception) { - bool isScenario = String::Equals(extension, ".sc6", true); - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw != nullptr) - { - success = LoadParkFromRW(rw, isScenario); - SDL_RWclose(rw); - } + Console::Error::WriteLine("Unable to load park: %s", path); } return success; } - bool LoadParkFromRW(SDL_RWops * rw, bool isScenario) + /** + * @param stream The stream to read the park data from. + * @param pathHint Hint path, the extension is grabbed to determine what importer to use. + */ + bool LoadParkFromStream(IStream * stream, const std::string &hintPath) { - bool successfulLoad = isScenario ? scenario_load_rw(rw) : - game_load_sv6(rw); - if (successfulLoad) + log_verbose("TitleSequencePlayer::LoadParkFromStream(%s)", hintPath.c_str()); + bool success = false; + try { - PrepareParkForPlayback(isScenario); + std::string extension = Path::GetExtension(hintPath); + bool isScenario = ParkImporter::ExtensionIsScenario(hintPath); + auto parkImporter = std::unique_ptr(ParkImporter::Create(hintPath)); + parkImporter->LoadFromStream(stream, isScenario); + parkImporter->Import(); + PrepareParkForPlayback(); + success = true; } - return successfulLoad; + catch (Exception) + { + Console::Error::WriteLine("Unable to load park: %s", hintPath.c_str()); + } + return success; } - void PrepareParkForPlayback(bool isScenario) + void PrepareParkForPlayback() { rct_window * w = window_get_main(); w->viewport_target_sprite = -1; @@ -433,10 +425,6 @@ private: reset_sprite_spatial_index(); reset_all_sprite_quadrant_placements(); window_new_ride_init_vars(); - if (!isScenario) - { - sub_684AC3(); - } scenery_set_default_placement_configuration(); news_item_init_queue(); load_palette(); diff --git a/src/openrct2/title/TitleSequencePlayer.h b/src/openrct2/title/TitleSequencePlayer.h index 15ab54f83d..2ac2f1b6d7 100644 --- a/src/openrct2/title/TitleSequencePlayer.h +++ b/src/openrct2/title/TitleSequencePlayer.h @@ -16,8 +16,6 @@ #pragma once -#include - #ifdef __cplusplus interface IScenarioRepository; diff --git a/src/openrct2/windows/title_editor.c b/src/openrct2/windows/title_editor.c index fbf6124264..76c0e5f188 100644 --- a/src/openrct2/windows/title_editor.c +++ b/src/openrct2/windows/title_editor.c @@ -23,6 +23,7 @@ #include "../interface/widget.h" #include "../interface/window.h" #include "../localisation/localisation.h" +#include "../ParkImporter.h" #include "../peep/peep.h" #include "../peep/staff.h" #include "../scenario/scenario.h" @@ -358,11 +359,12 @@ static void window_title_editor_mouseup(rct_window *w, sint32 widgetIndex) case WIDX_TITLE_EDITOR_LOAD_SAVE: if (w->selected_list_item >= 0 && w->selected_list_item < (sint16)_editingTitleSequence->NumSaves) { TitleSequenceParkHandle * handle = TitleSequenceGetParkHandle(_editingTitleSequence, w->selected_list_item); - if (handle->IsScenario) { - scenario_load_rw(handle->RWOps); + const utf8 * extension = path_get_extension(handle->HintPath); + bool isScenario = park_importer_extension_is_scenario(extension); + park_importer_load_from_stream(handle->Stream, handle->HintPath); + if (isScenario) { scenario_begin(); } else { - game_load_sv6(handle->RWOps); game_load_init(); } TitleSequenceCloseParkHandle(handle);