From 8c81cacc6f9ce5b4da3d171e513085c18d33b5a4 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 9 Jul 2020 22:33:45 +0100 Subject: [PATCH] Add sea decryption --- distribution/changelog.txt | 1 + src/openrct2-ui/windows/LoadSave.cpp | 6 +- src/openrct2/Context.cpp | 13 +++- src/openrct2/libopenrct2.vcxproj | 1 + src/openrct2/rct2/RCT2.h | 5 ++ src/openrct2/rct2/SeaDecrypt.cpp | 109 +++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 src/openrct2/rct2/SeaDecrypt.cpp diff --git a/distribution/changelog.txt b/distribution/changelog.txt index eb1ae12eba..dfa2089e97 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -13,6 +13,7 @@ - Feature: [#11788] Command to extract images from a .DAT file. - Feature: [#11959] Hacked go-kart tracks can now use 2x2 bends, 3x3 bends and S-bends. - Feature: [#12090] Boosters for the Wooden Roller Coaster (if the "Show all track pieces" cheat is enabled). +- Feature: [#12184] .sea (RCT Classic) scenario files can now be imported. - Change: [#11209] Warn when user is running OpenRCT2 through Wine. - Change: [#11358] Switch copy and paste button positions in tile inspector. - Change: [#11449] Remove complete circuit requirement from Air Powered Vertical Coaster (for RCT1 parity). diff --git a/src/openrct2-ui/windows/LoadSave.cpp b/src/openrct2-ui/windows/LoadSave.cpp index c0b0a5bc7c..efd9f12ad5 100644 --- a/src/openrct2-ui/windows/LoadSave.cpp +++ b/src/openrct2-ui/windows/LoadSave.cpp @@ -150,7 +150,7 @@ static std::vector _listItems; static char _directory[MAX_PATH]; static char _shortenedDirectory[MAX_PATH]; static char _parentDirectory[MAX_PATH]; -static char _extension[32]; +static char _extension[256]; static char _defaultName[MAX_PATH]; static int32_t _type; @@ -218,10 +218,10 @@ static const char* getFilterPatternByType(const int32_t type, const bool isSave) switch (type & 0x0E) { case LOADSAVETYPE_GAME: - return isSave ? "*.sv6" : "*.sv6;*.sc6;*.sc4;*.sv4;*.sv7"; + return isSave ? "*.sv6" : "*.sv6;*.sc6;*.sc4;*.sv4;*.sv7;*.sea;"; case LOADSAVETYPE_LANDSCAPE: - return isSave ? "*.sc6" : "*.sc6;*.sv6;*.sc4;*.sv4;*.sv7"; + return isSave ? "*.sc6" : "*.sc6;*.sv6;*.sc4;*.sv4;*.sv7;*.sea;"; case LOADSAVETYPE_SCENARIO: return "*.sc6"; diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 8d8abe7aac..2aaf46c044 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -541,8 +541,17 @@ namespace OpenRCT2 log_verbose("Context::LoadParkFromFile(%s)", path.c_str()); try { - auto fs = FileStream(path, FILE_MODE_OPEN); - return LoadParkFromStream(&fs, path, loadTitleScreenOnFail); + if (String::Equals(Path::GetExtension(path), ".sea", true)) + { + auto data = DecryptSea(fs::u8path(path)); + auto ms = MemoryStream(data.data(), data.size(), MEMORY_ACCESS::READ); + return LoadParkFromStream(&ms, path, loadTitleScreenOnFail); + } + else + { + auto fs = FileStream(path, FILE_MODE_OPEN); + return LoadParkFromStream(&fs, path, loadTitleScreenOnFail); + } } catch (const std::exception& e) { diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 70d0434993..b65f8a4704 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -629,6 +629,7 @@ + diff --git a/src/openrct2/rct2/RCT2.h b/src/openrct2/rct2/RCT2.h index 85282def1d..09598ea66f 100644 --- a/src/openrct2/rct2/RCT2.h +++ b/src/openrct2/rct2/RCT2.h @@ -10,11 +10,14 @@ #pragma once #include "../common.h" +#include "../core/FileSystem.hpp" #include "../object/Object.h" #include "../rct12/RCT12.h" #include "../ride/RideRatings.h" #include "../ride/Vehicle.h" +#include + constexpr const uint8_t RCT2_MAX_STAFF = 200; constexpr const uint8_t RCT2_MAX_BANNERS_IN_PARK = 250; constexpr const uint8_t RCT2_MAX_VEHICLES_PER_RIDE = 31; @@ -760,3 +763,5 @@ struct RCT2RideRatingCalculationData assert_struct_size(RCT2RideRatingCalculationData, 76); #pragma pack(pop) + +std::vector DecryptSea(const fs::path& path); diff --git a/src/openrct2/rct2/SeaDecrypt.cpp b/src/openrct2/rct2/SeaDecrypt.cpp new file mode 100644 index 0000000000..8fc02708de --- /dev/null +++ b/src/openrct2/rct2/SeaDecrypt.cpp @@ -0,0 +1,109 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "../common.h" +#include "../core/File.h" +#include "../core/Path.hpp" +#include "RCT2.h" + +#include +#include +#include + +constexpr int32_t MASK_SIZE = 0x1000; + +struct EncryptionKey +{ + uint32_t Seed0{}; + uint32_t Seed1{}; +}; + +static EncryptionKey GetEncryptionKey(const std::string_view& fileName) +{ + auto fileNameLen = static_cast(fileName.size()); + uint32_t s0 = 0; + for (int i = fileNameLen - 1; i >= 0; i--) + { + s0 = (s0 + (s0 << 5)) ^ fileName[i]; + } + + uint32_t s1 = 0; + for (int i = 0; i < fileNameLen; i++) + { + s1 = (s1 + (s1 << 5)) ^ fileName[i]; + } + + return EncryptionKey{ s0, s1 }; +} + +static std::unique_ptr CreateMask(const EncryptionKey& key) +{ + auto result = std::make_unique(MASK_SIZE); + auto dst8 = result.get(); + uint32_t seed0 = key.Seed0; + uint32_t seed1 = key.Seed1; + int32_t i = MASK_SIZE; + while (i > 0) + { + uint32_t s0 = seed0; + uint32_t s1 = seed1 ^ 0xF7654321; + seed0 = rol32(s1, 25) + s0; + seed1 = rol32(s0, 29); + *dst8++ = (s0 >> 3) & 0xFF; + if (i >= 2) + { + *dst8++ = (s0 >> 11) & 0xFF; + if (i >= 3) + { + *dst8++ = (s0 >> 19) & 0xFF; + if (i >= 4) + { + *dst8++ = (seed1 >> 24) & 0xFF; + i -= 4; + } + } + } + } + return result; +} + +static void Decrypt(std::vector& data, const EncryptionKey& key) +{ + auto mask = CreateMask(key); + const uint8_t* mask8 = (const uint8_t*)mask.get(); + + uint32_t b = 0; + uint32_t c = 0; + for (size_t i = 0; i < data.size(); i++) + { + auto a = b % MASK_SIZE; + c = c % MASK_SIZE; + b = (a + 1) % MASK_SIZE; + + data[i] = (((data[i] - mask8[b]) ^ mask8[c]) + mask8[a]) & 0xFF; + + c += 3; + b = a + 7; + } +} + +std::vector DecryptSea(const fs::path& path) +{ + auto key = GetEncryptionKey(path.filename().u8string()); + auto data = File::ReadAllBytes(path.u8string()); + + // Last 4 bytes is the checksum + size_t inputSize = data.size() - 4; + uint32_t checksum; + std::memcpy(&checksum, data.data() + inputSize, sizeof(checksum)); + data.resize(inputSize); + + Decrypt(data, key); + return data; +}