diff --git a/src/openrct2/FileClassifier.cpp b/src/openrct2/FileClassifier.cpp index b3b64bdf01..897328a662 100644 --- a/src/openrct2/FileClassifier.cpp +++ b/src/openrct2/FileClassifier.cpp @@ -16,6 +16,7 @@ #include "core/FileStream.hpp" #include "FileClassifier.h" +#include "rct12/SawyerEncoding.h" extern "C" { @@ -26,7 +27,6 @@ extern "C" static bool TryClassifyAsS6(IStream * stream, ClassifiedFile * result); static bool TryClassifyAsS4(IStream * stream, ClassifiedFile * result); static bool TryClassifyAsTD4_TD6(IStream * stream, ClassifiedFile * result); -static bool sawyercoding_try_read_chunk(void * dst, size_t expectedSize, IStream * stream); bool TryClassifyFile(const std::string &path, ClassifiedFile * result) { @@ -72,7 +72,7 @@ bool TryClassifyFile(IStream * stream, ClassifiedFile * result) static bool TryClassifyAsS6(IStream * stream, ClassifiedFile * result) { rct_s6_header s6Header; - if (sawyercoding_try_read_chunk(&s6Header, sizeof(rct_s6_header), stream)) + if (SawyerEncoding::TryReadChunk(&s6Header, stream)) { if (s6Header.type == S6_TYPE_SAVEDGAME) { @@ -145,31 +145,3 @@ static bool TryClassifyAsTD4_TD6(IStream * stream, ClassifiedFile * result) return success; } - -static bool sawyercoding_try_read_chunk(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; - } - - stream->SetPosition(originalPosition); - return success; -} diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index f83b8ea4a0..9cf193573e 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -73,6 +73,7 @@ + @@ -414,6 +415,7 @@ + @@ -596,4 +598,4 @@ - + \ No newline at end of file diff --git a/src/openrct2/rct12/SawyerEncoding.cpp b/src/openrct2/rct12/SawyerEncoding.cpp new file mode 100644 index 0000000000..68971a2b59 --- /dev/null +++ b/src/openrct2/rct12/SawyerEncoding.cpp @@ -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 + +#include "../core/IStream.hpp" +#include "SawyerEncoding.h" + +extern "C" +{ + #include "../util/sawyercoding.h" +} + +namespace SawyerEncoding +{ + 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; + } +} diff --git a/src/openrct2/rct12/SawyerEncoding.h b/src/openrct2/rct12/SawyerEncoding.h new file mode 100644 index 0000000000..188ebed2db --- /dev/null +++ b/src/openrct2/rct12/SawyerEncoding.h @@ -0,0 +1,32 @@ +#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" + +interface IStream; + +namespace SawyerEncoding +{ + bool TryReadChunk(void * dst, size_t expectedSize, IStream * stream); + + template + bool TryReadChunk(T * dst, IStream * stream) + { + return TryReadChunk(dst, sizeof(T), stream); + } +} diff --git a/src/openrct2/scenario/ScenarioRepository.cpp b/src/openrct2/scenario/ScenarioRepository.cpp index 0efe3d0928..5c4a0ecb8b 100644 --- a/src/openrct2/scenario/ScenarioRepository.cpp +++ b/src/openrct2/scenario/ScenarioRepository.cpp @@ -25,6 +25,7 @@ #include "../core/String.hpp" #include "../core/Util.hpp" #include "../PlatformEnvironment.h" +#include "../rct12/SawyerEncoding.h" #include "ScenarioRepository.h" #include "ScenarioSources.h" @@ -142,8 +143,10 @@ public: _scenarios.clear(); // Scan RCT2 directory + std::string rct1dir = _env->GetDirectoryPath(DIRBASE::RCT1, DIRID::SCENARIO); std::string rct2dir = _env->GetDirectoryPath(DIRBASE::RCT2, DIRID::SCENARIO); std::string openrct2dir = _env->GetDirectoryPath(DIRBASE::USER, DIRID::SCENARIO); + Scan(rct1dir); Scan(rct2dir); Scan(openrct2dir); @@ -253,7 +256,7 @@ private: { utf8 pattern[MAX_PATH]; String::Set(pattern, sizeof(pattern), directory.c_str()); - Path::Append(pattern, sizeof(pattern), "*.sc6"); + Path::Append(pattern, sizeof(pattern), "*.sc4;*.sc6"); IFileScanner * scanner = Path::ScanDirectory(pattern, true); while (scanner->Next()) @@ -265,43 +268,88 @@ private: delete scanner; } - void AddScenario(const utf8 * path, uint64 timestamp) + void AddScenario(const std::string &path, uint64 timestamp) { - rct_s6_header s6Header; rct_s6_info s6Info; - if (!scenario_load_basic(path, &s6Header, &s6Info)) + if (!GetScenarioInfo(path, &s6Info)) { - Console::Error::WriteLine("Unable to read scenario: '%s'", path); return; } - const utf8 * filename = Path::GetFileName(path); - scenario_index_entry * existingEntry = GetByFilename(filename); + const std::string filename = Path::GetFileName(path); + scenario_index_entry * existingEntry = GetByFilename(filename.c_str()); if (existingEntry != nullptr) { - const utf8 * conflictPath; + std::string conflictPath; if (existingEntry->timestamp > timestamp) { // Existing entry is more recent - conflictPath = existingEntry->path; + conflictPath = String::ToStd(existingEntry->path); // Overwrite existing entry with this one - *existingEntry = CreateNewScenarioEntry(path, timestamp, &s6Info); + *existingEntry = CreateNewScenarioEntry(path.c_str(), timestamp, &s6Info); } else { // This entry is more recent conflictPath = path; } - Console::WriteLine("Scenario conflict: '%s' ignored because it is newer.", conflictPath); + Console::WriteLine("Scenario conflict: '%s' ignored because it is newer.", conflictPath.c_str()); } else { - scenario_index_entry entry = CreateNewScenarioEntry(path, timestamp, &s6Info); + scenario_index_entry entry = CreateNewScenarioEntry(path.c_str(), timestamp, &s6Info); _scenarios.push_back(entry); } } + /** + * Reads basic information from a scenario file. + */ + bool GetScenarioInfo(const std::string &path, rct_s6_info * info) + { + log_verbose("GetScenarioInfo(%s, ...)", path); + try + { + auto fs = FileStream(path, FILE_MODE_OPEN); + + std::string extension = Path::GetExtension(path); + if (String::Equals(extension, ".sc4", true)) + { + // RCT1 scenario + log_verbose("%s is an RCT1 scenario, not yet supported", path); + } + else + { + // RCT2 scenario + rct_s6_header header; + if (SawyerEncoding::TryReadChunk(&header, &fs)) + { + if (header.type == S6_TYPE_SCENARIO) + { + if (SawyerEncoding::TryReadChunk(info, &fs)) + { + return true; + } + else + { + throw new IOException("Unable to read S6_INFO chunk."); + } + } + else + { + log_verbose("%s is not a scenario", path); + } + } + } + } + catch (Exception) + { + Console::Error::WriteLine("Unable to read scenario: '%s'", path); + } + return false; + } + scenario_index_entry CreateNewScenarioEntry(const utf8 * path, uint64 timestamp, rct_s6_info * s6Info) { scenario_index_entry entry = { 0 }; diff --git a/src/openrct2/scenario/scenario.c b/src/openrct2/scenario/scenario.c index 697edb81c5..fd13c0791e 100644 --- a/src/openrct2/scenario/scenario.c +++ b/src/openrct2/scenario/scenario.c @@ -88,43 +88,6 @@ money32 gScenarioCompanyValueRecord; static sint32 scenario_create_ducks(); static void scenario_objective_check(); -/** - * Loads only the basic information from a scenario. - * rct2: 0x006761D6 - */ -bool scenario_load_basic(const char *path, rct_s6_header *header, rct_s6_info *info) -{ - log_verbose("loading scenario details, %s", path); - - SDL_RWops* rw = SDL_RWFromFile(path, "rb"); - if (rw != NULL) { - // Read first chunk - size_t loaded_size = sawyercoding_read_chunk_with_size(rw, (uint8*)header, sizeof(rct_s6_header)); - if (loaded_size != sizeof(rct_s6_header)) { - log_error("Failed to read header from scenario %s", path); - SDL_RWclose(rw); - return false; - } - if (header->type == S6_TYPE_SCENARIO) { - // Read second chunk - loaded_size = sawyercoding_read_chunk_with_size(rw, (uint8*)info, sizeof(rct_s6_info)); - SDL_RWclose(rw); - if (loaded_size != sizeof(rct_s6_info)) { - log_error("Failed to read info from scenario %s", path); - return false; - } - return true; - } else { - log_error("invalid scenario, %s", path); - SDL_RWclose(rw); - return false; - } - } - - log_error("unable to open scenario, %s", path); - return false; -} - sint32 scenario_load_and_play_from_path(const char *path) { window_close_construction_windows(); diff --git a/src/openrct2/scenario/scenario.h b/src/openrct2/scenario/scenario.h index 020ff2b50a..7c4a2b4026 100644 --- a/src/openrct2/scenario/scenario.h +++ b/src/openrct2/scenario/scenario.h @@ -376,7 +376,6 @@ extern uint32 gLastAutoSaveUpdate; extern const char *_scenarioFileName; -bool scenario_load_basic(const char *path, rct_s6_header *header, rct_s6_info *info); bool scenario_load_rw(SDL_RWops * rw); sint32 scenario_load(const char *path); sint32 scenario_load_and_play_from_path(const char *path);