From 0ecc64f7816642ff22604abb4b27dee769450d7c Mon Sep 17 00:00:00 2001 From: Michael Steenbeek Date: Sat, 25 Apr 2020 15:36:44 +0200 Subject: [PATCH] Fix #11407: Setting an RCT1 path needs better error messages (#11418) --- data/language/en-GB.txt | 4 +- src/openrct2-ui/windows/Options.cpp | 28 ++++++-- src/openrct2/config/Config.cpp | 88 ++++++++++++++++++++++++- src/openrct2/config/Config.h | 9 +++ src/openrct2/drawing/Drawing.Sprite.cpp | 46 +------------ src/openrct2/drawing/Drawing.h | 18 +++++ src/openrct2/localisation/StringIds.h | 3 + src/openrct2/platform/Posix.cpp | 29 -------- src/openrct2/platform/Windows.cpp | 13 ---- src/openrct2/platform/platform.h | 1 - src/openrct2/rct1/RCT1.h | 1 + src/openrct2/sprites.h | 2 +- 12 files changed, 145 insertions(+), 97 deletions(-) diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index a7f9f0c82a..09d2798ea0 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -3350,7 +3350,7 @@ STR_6033 :Path to RCT1 installation: STR_6034 :{SMALLFONT}{BLACK}{STRING} STR_6035 :Please select your RCT1 directory STR_6036 :{SMALLFONT}{BLACK}Clear -STR_6037 :Please select a valid RCT1 directory +STR_6037 :The selected folder is not a valid RollerCoaster Tycoon 1 install. STR_6038 :{SMALLFONT}{BLACK}If you have RCT1 installed, set this option to its directory to load scenarios, music, etc. STR_6039 :{SMALLFONT}{BLACK}Quick demolish ride STR_6040 :Edit Scenario Options @@ -3684,6 +3684,8 @@ STR_6367 :{WINDOW_COLOUR_2}Animation frame: STR_6368 :For compatibility reasons, it is not recommended to run OpenRCT2 with Wine. OpenRCT2 has native support for macOS, Linux, FreeBSD and OpenBSD. STR_6369 :Allow building track at invalid heights STR_6370 :{SMALLFONT}{BLACK}Allows placing track pieces at any height interval +STR_6371 :The specified path contains a RollerCoaster Tycoon 1 installation, but the “csg1i.dat” file is missing. This file needs to be copied from the RollerCoaster Tycoon 1 CD to the “Data” folder of the RollerCoaster Tycoon 1 install on your hard drive. +STR_6372 :The specified path contains a RollerCoaster Tycoon 1 installation, but this version is not suitable. OpenRCT2 needs a Loopy Landscapes or RCT Deluxe install in order to use RollerCoaster Tycoon 1 assets. ############# # Scenarios # diff --git a/src/openrct2-ui/windows/Options.cpp b/src/openrct2-ui/windows/Options.cpp index fb66c5dc20..fa3704c154 100644 --- a/src/openrct2-ui/windows/Options.cpp +++ b/src/openrct2-ui/windows/Options.cpp @@ -973,13 +973,29 @@ static void window_options_mouseup(rct_window* w, rct_widgetindex widgetIndex) if (rct1path) { // Check if this directory actually contains RCT1 - if (platform_original_rct1_data_exists(rct1path)) + if (Csg1datPresentAtLocation(rct1path)) { - SafeFree(gConfigGeneral.rct1_path); - gConfigGeneral.rct1_path = rct1path; - gConfigInterface.scenarioselect_last_tab = 0; - config_save_default(); - context_show_error(STR_RESTART_REQUIRED, STR_NONE); + if (Csg1idatPresentAtLocation(rct1path)) + { + if (CsgAtLocationIsUsable(rct1path)) + { + SafeFree(gConfigGeneral.rct1_path); + gConfigGeneral.rct1_path = rct1path; + gConfigInterface.scenarioselect_last_tab = 0; + config_save_default(); + context_show_error(STR_RESTART_REQUIRED, STR_NONE); + } + else + { + SafeFree(rct1path); + context_show_error(STR_PATH_TO_RCT1_IS_WRONG_VERSION, STR_NONE); + } + } + else + { + SafeFree(rct1path); + context_show_error(STR_PATH_TO_RCT1_DOES_NOT_CONTAIN_CSG1I_DAT, STR_NONE); + } } else { diff --git a/src/openrct2/config/Config.cpp b/src/openrct2/config/Config.cpp index a082f78dc0..098d8fb292 100644 --- a/src/openrct2/config/Config.cpp +++ b/src/openrct2/config/Config.cpp @@ -26,8 +26,10 @@ #include "../paint/VirtualFloor.h" #include "../platform/Platform2.h" #include "../platform/platform.h" +#include "../rct1/RCT1.h" #include "../scenario/Scenario.h" #include "../ui/UiContext.h" +#include "../util/Util.h" #include "ConfigEnum.hpp" #include "IniReader.hpp" #include "IniWriter.hpp" @@ -634,7 +636,7 @@ namespace Config for (const utf8* location : searchLocations) { - if (platform_original_rct1_data_exists(location)) + if (RCT1DataPresentAtLocation(location)) { return location; } @@ -644,13 +646,13 @@ namespace Config if (platform_get_steam_path(steamPath, sizeof(steamPath))) { std::string location = Path::Combine(steamPath, platform_get_rct1_steam_dir()); - if (platform_original_rct1_data_exists(location.c_str())) + if (RCT1DataPresentAtLocation(location.c_str())) { return location; } } - if (platform_original_rct1_data_exists(gExePath)) + if (RCT1DataPresentAtLocation(gExePath)) { return gExePath; } @@ -837,3 +839,83 @@ bool config_find_or_browse_install_directory() return true; } + +std::string FindCsg1datAtLocation(const utf8* path) +{ + char buffer[MAX_PATH], checkPath1[MAX_PATH], checkPath2[MAX_PATH]; + safe_strcpy(buffer, path, MAX_PATH); + safe_strcat_path(buffer, "Data", MAX_PATH); + safe_strcpy(checkPath1, buffer, MAX_PATH); + safe_strcpy(checkPath2, buffer, MAX_PATH); + safe_strcat_path(checkPath1, "CSG1.DAT", MAX_PATH); + safe_strcat_path(checkPath2, "CSG1.1", MAX_PATH); + + // Since Linux is case sensitive (and macOS sometimes too), make sure we handle case properly. + std::string path1result = Path::ResolveCasing(checkPath1); + if (!path1result.empty()) + { + return path1result; + } + + std::string path2result = Path::ResolveCasing(checkPath2); + return path2result; +} + +bool Csg1datPresentAtLocation(const utf8* path) +{ + std::string location = FindCsg1datAtLocation(path); + return !location.empty(); +} + +std::string FindCsg1idatAtLocation(const utf8* path) +{ + char checkPath[MAX_PATH]; + safe_strcpy(checkPath, path, MAX_PATH); + safe_strcat_path(checkPath, "Data", MAX_PATH); + safe_strcat_path(checkPath, "CSG1I.DAT", MAX_PATH); + + // Since Linux is case sensitive (and macOS sometimes too), make sure we handle case properly. + std::string resolvedPath = Path::ResolveCasing(checkPath); + return resolvedPath; +} + +bool Csg1idatPresentAtLocation(const utf8* path) +{ + std::string location = FindCsg1idatAtLocation(path); + return !location.empty(); +} + +bool RCT1DataPresentAtLocation(const utf8* path) +{ + return Csg1datPresentAtLocation(path) && Csg1idatPresentAtLocation(path) && CsgAtLocationIsUsable(path); +} + +bool CsgIsUsable(rct_gx csg) +{ + return csg.header.num_entries == RCT1_NUM_LL_CSG_ENTRIES; +} + +bool CsgAtLocationIsUsable(const utf8* path) +{ + auto csg1HeaderPath = FindCsg1idatAtLocation(path); + if (csg1HeaderPath.empty()) + { + return false; + } + + auto csg1DataPath = FindCsg1datAtLocation(path); + if (csg1DataPath.empty()) + { + return false; + } + + auto fileHeader = FileStream(csg1HeaderPath, FILE_MODE_OPEN); + auto fileData = FileStream(csg1DataPath, FILE_MODE_OPEN); + size_t fileHeaderSize = fileHeader.GetLength(); + size_t fileDataSize = fileData.GetLength(); + + rct_gx csg = {}; + csg.header.num_entries = static_cast(fileHeaderSize / sizeof(rct_g1_element_32bit)); + csg.header.total_size = static_cast(fileDataSize); + return CsgIsUsable(csg); +} diff --git a/src/openrct2/config/Config.h b/src/openrct2/config/Config.h index ead4de909e..a001e44623 100644 --- a/src/openrct2/config/Config.h +++ b/src/openrct2/config/Config.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "../drawing/Drawing.h" #include @@ -245,3 +246,11 @@ void config_set_defaults(); void config_release(); bool config_save_default(); bool config_find_or_browse_install_directory(); + +bool RCT1DataPresentAtLocation(const utf8* path); +std::string FindCsg1datAtLocation(const utf8* path); +bool Csg1datPresentAtLocation(const utf8* path); +std::string FindCsg1idatAtLocation(const utf8* path); +bool Csg1idatPresentAtLocation(const utf8* path); +bool CsgIsUsable(rct_gx csg); +bool CsgAtLocationIsUsable(const utf8* path); diff --git a/src/openrct2/drawing/Drawing.Sprite.cpp b/src/openrct2/drawing/Drawing.Sprite.cpp index e268ef1b98..e309fd6c75 100644 --- a/src/openrct2/drawing/Drawing.Sprite.cpp +++ b/src/openrct2/drawing/Drawing.Sprite.cpp @@ -27,22 +27,6 @@ using namespace OpenRCT2; using namespace OpenRCT2::Ui; -#pragma pack(push, 1) -struct rct_g1_header -{ - uint32_t num_entries; - uint32_t total_size; -}; -assert_struct_size(rct_g1_header, 8); -#pragma pack(pop) - -struct rct_gx -{ - rct_g1_header header; - std::vector elements; - void* data; -}; - // clang-format off constexpr struct { @@ -193,30 +177,6 @@ void mask_scalar( } } -static std::string gfx_get_csg_header_path() -{ - auto path = Path::ResolveCasing(Path::Combine(gConfigGeneral.rct1_path, "Data", "csg1i.dat")); - if (path.empty()) - { - path = Path::ResolveCasing(Path::Combine(gConfigGeneral.rct1_path, "RCTdeluxe_install", "Data", "csg1i.dat")); - } - return path; -} - -static std::string gfx_get_csg_data_path() -{ - // csg1.1 and csg1.dat are the same file. - // In the CD version, it's called csg1.1 on the CD and csg1.dat on the disk. - // In the GOG version, it's always called csg1.1. - // In the Steam version, it's called csg1.dat in the "disk" folder and csg1.1 in the "CD" folder. - auto path = Path::ResolveCasing(Path::Combine(gConfigGeneral.rct1_path, "Data", "csg1.1")); - if (path.empty()) - { - path = Path::ResolveCasing(Path::Combine(gConfigGeneral.rct1_path, "Data", "csg1.dat")); - } - return path; -} - static rct_gx _g1 = {}; static rct_gx _g2 = {}; static rct_gx _csg = {}; @@ -350,8 +310,8 @@ bool gfx_load_csg() return false; } - auto pathHeaderPath = gfx_get_csg_header_path(); - auto pathDataPath = gfx_get_csg_data_path(); + auto pathHeaderPath = FindCsg1idatAtLocation(gConfigGeneral.rct1_path); + auto pathDataPath = FindCsg1datAtLocation(gConfigGeneral.rct1_path); try { auto fileHeader = FileStream(pathHeaderPath, FILE_MODE_OPEN); @@ -362,7 +322,7 @@ bool gfx_load_csg() _csg.header.num_entries = static_cast(fileHeaderSize / sizeof(rct_g1_element_32bit)); _csg.header.total_size = static_cast(fileDataSize); - if (_csg.header.num_entries < 69917) + if (!CsgIsUsable(_csg)) { log_warning("Cannot load CSG1.DAT, it has too few entries. Only CSG1.DAT from Loopy Landscapes will work."); return false; diff --git a/src/openrct2/drawing/Drawing.h b/src/openrct2/drawing/Drawing.h index 9e99b228c8..39b41edd77 100644 --- a/src/openrct2/drawing/Drawing.h +++ b/src/openrct2/drawing/Drawing.h @@ -14,6 +14,8 @@ #include "../interface/Colour.h" #include "../interface/ZoomLevel.hpp" +#include + namespace OpenRCT2 { interface IPlatformEnvironment; @@ -35,6 +37,22 @@ struct rct_g1_element int32_t zoomed_offset; // 0x0E }; +#pragma pack(push, 1) +struct rct_g1_header +{ + uint32_t num_entries; + uint32_t total_size; +}; +assert_struct_size(rct_g1_header, 8); +#pragma pack(pop) + +struct rct_gx +{ + rct_g1_header header; + std::vector elements; + void* data; +}; + struct rct_drawpixelinfo { uint8_t* bits{}; diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h index ea3dd6897f..41f9231f00 100644 --- a/src/openrct2/localisation/StringIds.h +++ b/src/openrct2/localisation/StringIds.h @@ -3922,6 +3922,9 @@ enum STR_CHEAT_ALLOW_TRACK_PLACE_INVALID_HEIGHTS = 6369, STR_CHEAT_ALLOW_TRACK_PLACE_INVALID_HEIGHTS_TIP = 6370, + STR_PATH_TO_RCT1_DOES_NOT_CONTAIN_CSG1I_DAT = 6371, + STR_PATH_TO_RCT1_IS_WRONG_VERSION = 6372, + // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working /* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings }; diff --git a/src/openrct2/platform/Posix.cpp b/src/openrct2/platform/Posix.cpp index 2596cb55b3..ca7b1dddf4 100644 --- a/src/openrct2/platform/Posix.cpp +++ b/src/openrct2/platform/Posix.cpp @@ -120,35 +120,6 @@ bool platform_original_game_data_exists(const utf8* path) return platform_file_exists(checkPath); } -bool platform_original_rct1_data_exists(const utf8* path) -{ - char buffer[MAX_PATH], checkPath1[MAX_PATH], checkPath2[MAX_PATH]; - safe_strcpy(buffer, path, MAX_PATH); - safe_strcat_path(buffer, "Data", MAX_PATH); - safe_strcpy(checkPath1, buffer, MAX_PATH); - safe_strcpy(checkPath2, buffer, MAX_PATH); - safe_strcat_path(checkPath1, "CSG1.DAT", MAX_PATH); - safe_strcat_path(checkPath2, "CSG1.1", MAX_PATH); - - // Since Linux is case sensitive (and macOS sometimes too), make sure we handle case properly. - std::string path1result = Path::ResolveCasing(checkPath1); - if (!path1result.empty()) - { - return true; - } - else - { - std::string path2result = Path::ResolveCasing(checkPath2); - - if (!path2result.empty()) - { - return true; - } - } - - return false; -} - // Implement our own version of getumask(), as it is documented being // "a vaporware GNU extension". static mode_t openrct2_getumask() diff --git a/src/openrct2/platform/Windows.cpp b/src/openrct2/platform/Windows.cpp index 1318af7ca6..957f53544f 100644 --- a/src/openrct2/platform/Windows.cpp +++ b/src/openrct2/platform/Windows.cpp @@ -111,19 +111,6 @@ bool platform_original_game_data_exists(const utf8* path) return platform_file_exists(checkPath); } -bool platform_original_rct1_data_exists(const utf8* path) -{ - char checkPath[MAX_PATH]; - char checkPath2[MAX_PATH]; - safe_strcpy(checkPath, path, MAX_PATH); - safe_strcpy(checkPath2, path, MAX_PATH); - safe_strcat_path(checkPath, "Data", MAX_PATH); - safe_strcat_path(checkPath2, "Data", MAX_PATH); - safe_strcat_path(checkPath, "csg1.dat", MAX_PATH); - safe_strcat_path(checkPath2, "csg1.1", MAX_PATH); - return platform_file_exists(checkPath) || platform_file_exists(checkPath2); -} - bool platform_ensure_directory_exists(const utf8* path) { if (platform_directory_exists(path)) diff --git a/src/openrct2/platform/platform.h b/src/openrct2/platform/platform.h index c2f9e297a6..b9a227b1d4 100644 --- a/src/openrct2/platform/platform.h +++ b/src/openrct2/platform/platform.h @@ -95,7 +95,6 @@ void platform_get_time_local(rct2_time* out_time); bool platform_file_exists(const utf8* path); bool platform_directory_exists(const utf8* path); bool platform_original_game_data_exists(const utf8* path); -bool platform_original_rct1_data_exists(const utf8* path); time_t platform_file_get_modified_time(const utf8* path); bool platform_ensure_directory_exists(const utf8* path); bool platform_directory_delete(const utf8* path); diff --git a/src/openrct2/rct1/RCT1.h b/src/openrct2/rct1/RCT1.h index a416dead03..9a3d3e201c 100644 --- a/src/openrct2/rct1/RCT1.h +++ b/src/openrct2/rct1/RCT1.h @@ -28,6 +28,7 @@ constexpr const uint8_t RCT1_RESEARCH_FLAGS_SEPARATOR = 0xFF; constexpr const uint16_t RCT1_MAX_ANIMATED_OBJECTS = 1000; constexpr const uint8_t RCT1_MAX_BANNERS = 100; constexpr int32_t RCT1_COORDS_Z_STEP = 4; +constexpr const uint32_t RCT1_NUM_LL_CSG_ENTRIES = 69917; struct ParkLoadResult; diff --git a/src/openrct2/sprites.h b/src/openrct2/sprites.h index 77c1212587..c0c29b65d1 100644 --- a/src/openrct2/sprites.h +++ b/src/openrct2/sprites.h @@ -968,7 +968,7 @@ enum SPR_G2_END = SPR_G2_CHAR_END, SPR_CSG_BEGIN = SPR_G2_END, - SPR_CSG_END = SPR_CSG_BEGIN + 69917, + SPR_CSG_END = SPR_CSG_BEGIN + RCT1_NUM_LL_CSG_ENTRIES, SPR_IMAGE_LIST_BEGIN = SPR_CSG_END, SPR_IMAGE_LIST_END = 0x7FFFE