From e9519d2d8bf5e08401020ca3a3a53a5d36ce0ca0 Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 30 Jun 2017 20:38:51 +0100 Subject: [PATCH] Fix #5507: RCT1 path check is case-sensitive on Linux If the csg path does not exist, find the first file in the directory that matches (case insensitive). --- src/openrct2/core/Path.cpp | 46 +++++++++++++++++++++++++ src/openrct2/core/Path.hpp | 8 +++++ src/openrct2/drawing/sprite.cpp | 59 ++++++++++++++++++++------------- 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/src/openrct2/core/Path.cpp b/src/openrct2/core/Path.cpp index 17149dc9b5..b7fa7afca5 100644 --- a/src/openrct2/core/Path.cpp +++ b/src/openrct2/core/Path.cpp @@ -14,6 +14,10 @@ *****************************************************************************/ #pragma endregion +#ifndef _WIN32 + #include +#endif + extern "C" { #include "../platform/platform.h" @@ -21,6 +25,7 @@ extern "C" #include "../util/util.h" } +#include "File.h" #include "Math.hpp" #include "Memory.hpp" #include "Path.hpp" @@ -218,4 +223,45 @@ namespace Path #endif return String::Equals(a, b, ignoreCase); } + + std::string ResolveCasing(const std::string &path) + { + std::string result; + if (File::Exists(path)) + { + // Windows is case insensitive so it will exist and that is all that matters + // for now. We can properly resolve the casing if we ever need to. + result = path; + } +#ifndef _WIN32 + else + { + std::string fileName = Path::GetFileName(path); + std::string directory = Path::GetDirectory(path); + + struct dirent * * files; + auto count = scandir(directory.c_str(), &files, nullptr, alphasort); + if (count != -1) + { + // Find a file which matches by name (case insensitive) + for (sint32 i = 0; i < count; i++) + { + if (String::Equals(files[i]->d_name, fileName.c_str(), true)) + { + result = Path::Combine(directory, std::string(files[i]->d_name)); + break; + } + } + + // Free memory + for (sint32 i = 0; i < count; i++) + { + free(files[i]); + } + free(files); + } + } +#endif + return result; + } } diff --git a/src/openrct2/core/Path.hpp b/src/openrct2/core/Path.hpp index ca986d8984..4c055fb48d 100644 --- a/src/openrct2/core/Path.hpp +++ b/src/openrct2/core/Path.hpp @@ -43,4 +43,12 @@ namespace Path utf8 * GetAbsolute(utf8 * buffer, size_t bufferSize, const utf8 * relativePath); bool Equals(const std::string &a, const std::string &b); bool Equals(const utf8 * a, const utf8 * b); + + /** + * Checks if the given path is a file. If not, checks to see if + * there are any files with different casing and selects the first + * one found based on a straight forward character sort. + * Note: This will not resolve the case for Windows. + */ + std::string ResolveCasing(const std::string &path); } diff --git a/src/openrct2/drawing/sprite.cpp b/src/openrct2/drawing/sprite.cpp index cb61da6082..05116a0a3d 100644 --- a/src/openrct2/drawing/sprite.cpp +++ b/src/openrct2/drawing/sprite.cpp @@ -21,6 +21,7 @@ #include "../core/File.h" #include "../core/FileStream.hpp" #include "../core/Memory.hpp" +#include "../core/Path.hpp" #include "../core/Util.hpp" #include "../OpenRCT2.h" #include "../sprites.h" @@ -188,6 +189,37 @@ extern "C" return false; } + static utf8 * gfx_get_csg_header_path() + { + char path[MAX_PATH]; + safe_strcpy(path, gConfigGeneral.rct1_path, sizeof(path)); + safe_strcat_path(path, "Data", sizeof(path)); + safe_strcat_path(path, "csg1i.dat", sizeof(path)); + return String::Duplicate(Path::ResolveCasing(path)); + } + + static utf8 * 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 always called csg1.dat. + char path[MAX_PATH]; + safe_strcpy(path, gConfigGeneral.rct1_path, sizeof(path)); + safe_strcat_path(path, "Data", sizeof(path)); + safe_strcat_path(path, "csg1.1", sizeof(path)); + + auto fixedPath = Path::ResolveCasing(path); + if (fixedPath.empty()) + { + safe_strcpy(path, gConfigGeneral.rct1_path, sizeof(path)); + safe_strcat_path(path, "Data", sizeof(path)); + safe_strcat_path(path, "csg1.dat", sizeof(path)); + fixedPath = Path::ResolveCasing(path); + } + return String::Duplicate(fixedPath); + } + bool gfx_load_csg() { log_verbose("gfx_load_csg()"); @@ -198,31 +230,12 @@ extern "C" return false; } - char pathHeader[MAX_PATH]; - safe_strcpy(pathHeader, gConfigGeneral.rct1_path, sizeof(pathHeader)); - safe_strcat_path(pathHeader, "Data", sizeof(pathHeader)); - safe_strcat_path(pathHeader, "csg1i.dat", sizeof(pathHeader)); - - // 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 always called csg1.dat. - char pathData[MAX_PATH]; - safe_strcpy(pathData, gConfigGeneral.rct1_path, sizeof(pathData)); - safe_strcat_path(pathData, "Data", sizeof(pathData)); - safe_strcat_path(pathData, "csg1.1", sizeof(pathData)); - - if (!File::Exists(pathData)) - { - safe_strcpy(pathData, gConfigGeneral.rct1_path, sizeof(pathData)); - safe_strcat_path(pathData, "Data", sizeof(pathData)); - safe_strcat_path(pathData, "csg1.dat", sizeof(pathData)); - } - + auto pathHeaderPath = std::unique_ptr(gfx_get_csg_header_path()); + auto pathDataPath = std::unique_ptr(gfx_get_csg_data_path()); try { - auto fileHeader = FileStream(pathHeader, FILE_MODE_OPEN); - auto fileData = FileStream(pathData, FILE_MODE_OPEN); + auto fileHeader = FileStream(pathHeaderPath.get(), FILE_MODE_OPEN); + auto fileData = FileStream(pathDataPath.get(), FILE_MODE_OPEN); size_t fileHeaderSize = fileHeader.GetLength(); size_t fileDataSize = fileData.GetLength();