Fix #11407: Setting an RCT1 path needs better error messages (#11418)

This commit is contained in:
Michael Steenbeek 2020-04-25 15:36:44 +02:00 committed by GitHub
parent b417da7905
commit 0ecc64f781
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 145 additions and 97 deletions

View File

@ -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 #

View File

@ -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
{

View File

@ -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<uint32_t>(fileHeaderSize / sizeof(rct_g1_element_32bit));
csg.header.total_size = static_cast<uint32_t>(fileDataSize);
return CsgIsUsable(csg);
}

View File

@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../drawing/Drawing.h"
#include <string>
@ -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);

View File

@ -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<rct_g1_element> 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<uint32_t>(fileHeaderSize / sizeof(rct_g1_element_32bit));
_csg.header.total_size = static_cast<uint32_t>(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;

View File

@ -14,6 +14,8 @@
#include "../interface/Colour.h"
#include "../interface/ZoomLevel.hpp"
#include <vector>
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<rct_g1_element> elements;
void* data;
};
struct rct_drawpixelinfo
{
uint8_t* bits{};

View File

@ -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
};

View File

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

View File

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

View File

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

View File

@ -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;

View File

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