diff --git a/src/openrct2/rct12/ScenarioPatcher.cpp b/src/openrct2/rct12/ScenarioPatcher.cpp index 406fbf5b2b..41a4eda3f8 100644 --- a/src/openrct2/rct12/ScenarioPatcher.cpp +++ b/src/openrct2/rct12/ScenarioPatcher.cpp @@ -30,6 +30,8 @@ #include +static bool s_dryRun = false; + // Generic keys static const std::string s_scenarioNameKey = "scenario_name"; static const std::string s_fullSHAKey = "sha256"; @@ -129,6 +131,10 @@ static void ApplyLandOwnershipFixes(const json_t& landOwnershipFixes, int owners ? Json::GetBoolean(ownershipParameters[s_cannotDowngradeKey], false) : false; auto coordinatesVector = getCoordinates(ownershipParameters); + if (s_dryRun) + { + return; + } FixLandOwnershipTilesWithOwnership(coordinatesVector, ownershipType, cannotDowngrade); } @@ -174,6 +180,10 @@ static void ApplyWaterFixes(const json_t& scenarioPatch) Guard::Assert(0, "Water fix sub-array should set a height"); return; } + if (s_dryRun) + { + continue; + } auto waterHeight = waterFixes[i][s_heightKey]; auto coordinatesVector = getCoordinates(waterFixes[i]); @@ -237,6 +247,11 @@ static void ApplyTrackTypeFixes(const json_t& trackTilesFixes) auto destinationTrackType = toTrackType(Json::GetString(fixOperations[i][s_toKey])); auto coordinatesVector = getCoordinates(fixOperations[i]); + if (s_dryRun) + { + continue; + } + for (const auto& tile : coordinatesVector) { auto* tileElement = MapGetFirstElementAt(tile); @@ -370,6 +385,12 @@ static void ApplyRideFixes(const json_t& scenarioPatch) RideId rideId = RideId::FromUnderlying(Json::GetNumber(rideFixes[i][s_rideIdKey])); auto operation = Json::GetString(rideFixes[i][s_operationKey]); + + if (s_dryRun) + { + continue; + } + if (operation == "swap_entrance_exit") { SwapRideEntranceAndExit(rideId); @@ -401,6 +422,11 @@ static u8string GetPatchFileName(u8string_view scenarioHash) static bool ValidateSHA256(const json_t& scenarioPatch, u8string_view scenarioHash) { + if (s_dryRun) + { + return true; + } + if (!scenarioPatch.contains(s_scenarioNameKey)) { Guard::Assert(0, "All .parkpatch files should contain the name of the original scenario"); @@ -420,6 +446,24 @@ static bool ValidateSHA256(const json_t& scenarioPatch, u8string_view scenarioHa return scenarioSHA == scenarioHash; } +void RCT12::ApplyScenarioPatch(u8string_view scenarioPatchFile, u8string scenarioSHA, bool isScenario) +{ + auto scenarioPatch = Json::ReadFromFile(scenarioPatchFile); + if (!ValidateSHA256(scenarioPatch, scenarioSHA)) + { + Guard::Assert(0, "Invalid full SHA256. Check for shortened SHA collision"); + return; + } + // TODO: Land ownership is applied even when loading saved scenario. Should it? + ApplyLandOwnershipFixes(scenarioPatch); + if (isScenario) + { + ApplyWaterFixes(scenarioPatch); + ApplyTileFixes(scenarioPatch); + ApplyRideFixes(scenarioPatch); + } +} + void RCT12::FetchAndApplyScenarioPatch(u8string_view scenarioPath, bool isScenario) { auto scenarioSHA = getScenarioSHA256(scenarioPath); @@ -427,19 +471,11 @@ void RCT12::FetchAndApplyScenarioPatch(u8string_view scenarioPath, bool isScenar std::cout << "Patch is: " << patchPath << " full SHA" << scenarioSHA << std::endl; if (File::Exists(patchPath)) { - auto scenarioPatch = Json::ReadFromFile(patchPath); - if (!ValidateSHA256(scenarioPatch, scenarioSHA)) - { - Guard::Assert(0, "Invalid full SHA256. Check for shortened SHA collision"); - return; - } - // TODO: Land ownership is applied even when loading saved scenario. Should it? - ApplyLandOwnershipFixes(scenarioPatch); - if (isScenario) - { - ApplyWaterFixes(scenarioPatch); - ApplyTileFixes(scenarioPatch); - ApplyRideFixes(scenarioPatch); - } + ApplyScenarioPatch(patchPath, scenarioSHA, isScenario); } } + +void RCT12::SetDryRun(bool enable) +{ + s_dryRun = enable; +} diff --git a/src/openrct2/rct12/ScenarioPatcher.h b/src/openrct2/rct12/ScenarioPatcher.h index d89d0406e8..7833845a49 100644 --- a/src/openrct2/rct12/ScenarioPatcher.h +++ b/src/openrct2/rct12/ScenarioPatcher.h @@ -14,4 +14,7 @@ namespace RCT12 { void FetchAndApplyScenarioPatch(u8string_view scenarioPath, bool isScenario); -} + void ApplyScenarioPatch(u8string_view scenarioPatchFile, u8string scenarioSHA, bool isScenario); + /*SetDryRun should be used only for testing*/ + void SetDryRun(bool enable); +} // namespace RCT12 diff --git a/test/tests/CMakeLists.txt b/test/tests/CMakeLists.txt index 3681c890df..61b0446e60 100644 --- a/test/tests/CMakeLists.txt +++ b/test/tests/CMakeLists.txt @@ -28,6 +28,7 @@ set(test_files "${CMAKE_CURRENT_SOURCE_DIR}/RideRatings.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/S6ImportExportTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/SawyerCodingTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ScenarioPatcherTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/StringTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TestData.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TestData.h" diff --git a/test/tests/ScenarioPatcherTests.cpp b/test/tests/ScenarioPatcherTests.cpp new file mode 100644 index 0000000000..13d5da180e --- /dev/null +++ b/test/tests/ScenarioPatcherTests.cpp @@ -0,0 +1,45 @@ +/***************************************************************************** + * Copyright (c) 2014-2024 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 "TestData.h" + +#include +#include +#include +#include +#include +#include + +/* Test that all JSONs are with the expected formatting, otherwise the fetcher will abort + NOTE: This will *not* test that it actually applies the patch, due to the scenarios + not being available on the CI environment. +*/ +TEST(FetchAndApplyScenarioPatch, expected_json_format) +{ + auto context = OpenRCT2::CreateContext(); + bool initialised = context->Initialise(); + ASSERT_TRUE(initialised); + + auto env = context->GetPlatformEnvironment(); + auto scenarioPatches = env->GetDirectoryPath(OpenRCT2::DIRBASE::OPENRCT2, OpenRCT2::DIRID::SCENARIO_PATCHES); + + std::error_code ec; + RCT12::SetDryRun(true); + Guard::SetAssertBehaviour(ASSERT_BEHAVIOUR::ABORT); + static const u8string dummySHA; + for (const fs::directory_entry& entry : fs::directory_iterator(scenarioPatches, ec)) + { + auto path = entry.path().u8string(); + if (String::EndsWith(path, ".parkpatch")) + { + RCT12::ApplyScenarioPatch(path, dummySHA, true); + } + } + SUCCEED(); +} diff --git a/test/tests/tests.vcxproj b/test/tests/tests.vcxproj index 1a37d58ab8..53c7877c2a 100644 --- a/test/tests/tests.vcxproj +++ b/test/tests/tests.vcxproj @@ -97,6 +97,7 @@ +