Merge pull request #12184 from IntelOrca/sea

Add support for reading .sea (RCT classic) files
This commit is contained in:
Michael Steenbeek 2020-07-13 17:23:16 +02:00 committed by GitHub
commit 1cf89362f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 142 additions and 7 deletions

View file

@ -70,6 +70,7 @@
932A211E22D73CFA00C57EDB /* GameActionCompat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 932A20CF22D73CEE00C57EDB /* GameActionCompat.cpp */; };
932A211F22D73CFA00C57EDB /* GameActionRegistration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 932A20D322D73CEF00C57EDB /* GameActionRegistration.cpp */; };
932A212022D73CFA00C57EDB /* GameAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 932A211C22D73CFA00C57EDB /* GameAction.cpp */; };
933C55B524B858490057E64B /* SeaDecrypt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933C55B424B858490057E64B /* SeaDecrypt.cpp */; };
933CBDB520CB1ACD00134678 /* Widget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933CBDB120CB1ACC00134678 /* Widget.cpp */; };
933CBDB620CB1ACD00134678 /* Theme.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933CBDB220CB1ACD00134678 /* Theme.cpp */; };
933CBDBB20CB1B3F00134678 /* TitleSequencePlayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 933CBDB920CB1B3F00134678 /* TitleSequencePlayer.cpp */; };
@ -1061,6 +1062,7 @@
932A211B22D73CFA00C57EDB /* TrackSetBrakeSpeedAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TrackSetBrakeSpeedAction.hpp; sourceTree = "<group>"; };
932A211C22D73CFA00C57EDB /* GameAction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameAction.cpp; sourceTree = "<group>"; };
932A211D22D73CFA00C57EDB /* LargeSceneryRemoveAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LargeSceneryRemoveAction.hpp; sourceTree = "<group>"; };
933C55B424B858490057E64B /* SeaDecrypt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SeaDecrypt.cpp; sourceTree = "<group>"; };
933CBDB120CB1ACC00134678 /* Widget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Widget.cpp; sourceTree = "<group>"; };
933CBDB220CB1ACD00134678 /* Theme.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Theme.cpp; sourceTree = "<group>"; };
933CBDB320CB1ACD00134678 /* Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Theme.h; sourceTree = "<group>"; };
@ -3158,6 +3160,7 @@
F76C847D1EC4E7CC00FA49E2 /* S6Exporter.cpp */,
F76C847E1EC4E7CC00FA49E2 /* S6Exporter.h */,
F76C847F1EC4E7CC00FA49E2 /* S6Importer.cpp */,
933C55B424B858490057E64B /* SeaDecrypt.cpp */,
01C6F0C522FD51FC0057E2F7 /* T6Exporter.cpp */,
01C6F0C722FD51FC0057E2F7 /* T6Exporter.h */,
01C6F0C622FD51FC0057E2F7 /* T6Importer.cpp */,
@ -4202,6 +4205,7 @@
C68878E220289B9B0084B384 /* Staff.cpp in Sources */,
F76C85CF1EC4E88300FA49E2 /* Console.cpp in Sources */,
C68878DC20289B9B0084B384 /* Painter.cpp in Sources */,
933C55B524B858490057E64B /* SeaDecrypt.cpp in Sources */,
C688790120289B9B0084B384 /* ReverserRollerCoaster.cpp in Sources */,
C688786120289A0A0084B384 /* MapAnimation.cpp in Sources */,
F76C85D11EC4E88300FA49E2 /* Diagnostics.cpp in Sources */,

View file

@ -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).

View file

@ -1205,7 +1205,7 @@ namespace OpenRCT2::Ui::Windows
{
if (customWidgetInfo->Name == name)
{
return i;
return static_cast<rct_widgetindex>(i);
}
}
}

View file

@ -150,7 +150,7 @@ static std::vector<LoadSaveListItem> _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";

View file

@ -18,6 +18,7 @@
// Then the rest
#include <algorithm>
#include <iterator>
#include <openrct2-ui/Ui.h>
#include <openrct2/core/String.hpp>
#include <stdio.h>

View file

@ -541,8 +541,22 @@ 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);
if (!LoadParkFromStream(&ms, path, loadTitleScreenOnFail))
{
Console::Error::WriteLine(".sea file may have been renamed.");
return false;
}
return true;
}
else
{
auto fs = FileStream(path, FILE_MODE_OPEN);
return LoadParkFromStream(&fs, path, loadTitleScreenOnFail);
}
}
catch (const std::exception& e)
{

View file

@ -43,6 +43,7 @@ namespace fs = std::filesystem;
# define WIN32_LEAN_AND_MEAN
# endif
# define BITMAP WIN32_BITMAP
# define PATTERN WIN32_PATTERN
# endif
# include <filesystem.hpp>
# ifdef _WIN32
@ -50,6 +51,7 @@ namespace fs = std::filesystem;
# undef CreateWindow
# undef GetMessage
# undef BITMAP
# undef PATTERN
# endif
namespace fs = ghc::filesystem;
#endif

View file

@ -629,6 +629,7 @@
<ClCompile Include="rct1\Tables.cpp" />
<ClCompile Include="rct2\S6Exporter.cpp" />
<ClCompile Include="rct2\S6Importer.cpp" />
<ClCompile Include="rct2\SeaDecrypt.cpp" />
<ClCompile Include="rct2\T6Exporter.cpp" />
<ClCompile Include="rct2\T6Importer.cpp" />
<ClCompile Include="ReplayManager.cpp" />

View file

@ -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 <vector>
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<uint8_t> DecryptSea(const fs::path& path);

View file

@ -0,0 +1,96 @@
/*****************************************************************************
* 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 <cstdint>
#include <memory>
#include <string_view>
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<int32_t>(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::vector<uint8_t> CreateMask(const EncryptionKey& key)
{
std::vector<uint8_t> result;
result.resize(MASK_SIZE);
uint32_t seed0 = key.Seed0;
uint32_t seed1 = key.Seed1;
for (size_t i = 0; i < MASK_SIZE; i += 4)
{
uint32_t s0 = seed0;
uint32_t s1 = seed1 ^ 0xF7654321;
seed0 = rol32(s1, 25) + s0;
seed1 = rol32(s0, 29);
result[i + 0] = (s0 >> 3) & 0xFF;
result[i + 1] = (s0 >> 11) & 0xFF;
result[i + 2] = (s0 >> 19) & 0xFF;
result[i + 3] = (seed1 >> 24) & 0xFF;
}
return result;
}
static void Decrypt(std::vector<uint8_t>& data, const EncryptionKey& key)
{
auto mask = CreateMask(key);
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] - mask[b]) ^ mask[c]) + mask[a]) & 0xFF;
c += 3;
b = a + 7;
}
}
std::vector<uint8_t> 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;
}

View file

@ -1,5 +1,5 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
* 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
@ -18,6 +18,7 @@
#include <openrct2/ParkImporter.h>
#include <openrct2/audio/AudioContext.h>
#include <openrct2/config/Config.h>
#include <openrct2/core/Crypt.h>
#include <openrct2/core/File.h>
#include <openrct2/core/MemoryStream.h>
#include <openrct2/core/Path.hpp>
@ -566,3 +567,13 @@ TEST(S6ImportExportAdvanceTicks, all)
SUCCEED();
}
TEST(SeaDecrypt, DecryptSea)
{
auto path = TestData::GetParkPath("volcania.sea");
auto decrypted = DecryptSea(path);
auto sha1 = Crypt::SHA1(decrypted.data(), decrypted.size());
std::array<uint8_t, 20> expected = { 0x1B, 0x85, 0xFC, 0xC0, 0xE8, 0x9B, 0xBE, 0x72, 0xD9, 0x1F,
0x6E, 0xC8, 0xB1, 0xFF, 0xEC, 0x70, 0x2A, 0x72, 0x05, 0xBB };
ASSERT_EQ(sha1, expected);
}

BIN
test/tests/testdata/parks/volcania.sea vendored Normal file

Binary file not shown.