diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index defd8c2c..47c32ae2 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -49,6 +49,7 @@ GameData::~GameData() void GameData::load() { + index.indexGameDirectory(datpath); index.indexTree(datpath); parseDAT(datpath+"/data/default.dat"); @@ -109,9 +110,9 @@ void GameData::parseDAT(const std::string& path) } else if(cmd == "IPL") { - std::string fixedpath = fixPath(line.substr(space+1)); - fixedpath = findPathRealCase(datpath, fixedpath); - loadIPL(fixedpath); + auto path = line.substr(space+1); + auto systempath = index.findFilePath(path); + loadIPL(systempath.native()); } else if(cmd == "TEXDICTION") { @@ -182,10 +183,9 @@ void GameData::loadCOL(const size_t zone, const std::string& name) LoaderCOL col; - std::string realPath = fixPath(name); - realPath = findPathRealCase(datpath, realPath); + auto systempath = index.findFilePath(name).native(); - if(col.load(realPath)) { + if(col.load(systempath)) { for( size_t i = 0; i < col.instances.size(); ++i ) { collisions[col.instances[i]->name] = std::move(col.instances[i]); } @@ -434,13 +434,13 @@ void GameData::loadWeaponDAT(const std::string &name) bool GameData::loadAudioStream(const std::string &name) { - auto filePath = findPathRealCase(datpath + "/audio/", name); + auto systempath = index.findFilePath("audio/" + name).native(); if (engine->cutsceneAudio.length() > 0) { engine->sound.stopMusic(engine->cutsceneAudio); } - if (engine->sound.loadMusic(name, filePath)) { + if (engine->sound.loadMusic(name, systempath)) { engine->cutsceneAudio = name; return true; } @@ -450,18 +450,18 @@ bool GameData::loadAudioStream(const std::string &name) bool GameData::loadAudioClip(const std::string& name, const std::string& fileName) { - auto filePath = findPathRealCase(datpath + "/audio/", fileName); - - if (fileName.find(".mp3") != fileName.npos) + auto systempath = index.findFilePath("audio/" + fileName).native(); + + if (systempath.find(".mp3") != std::string::npos) { logger->error("Data", "MP3 Audio unsupported outside cutscenes"); return false; } - bool loaded = engine->sound.loadSound(name, filePath); + bool loaded = engine->sound.loadSound(name, systempath); if ( ! loaded) { - logger->error("Data", "Error loading audio clip "+ filePath); + logger->error("Data", "Error loading audio clip "+ systempath); return false; } diff --git a/rwlib/source/platform/FileIndex.cpp b/rwlib/source/platform/FileIndex.cpp index 07109bfd..a794b0c5 100644 --- a/rwlib/source/platform/FileIndex.cpp +++ b/rwlib/source/platform/FileIndex.cpp @@ -1,47 +1,23 @@ #include #include -#include #include #include #include using namespace boost::filesystem; -/** - * Finds the 'real' case for a path, to get around the fact that Rockstar's data is usually the wrong case. - * @param base The base of the path to start looking from. - * @param path the lowercase path. - */ -std::string findPathRealCase(const std::string& base_src, const std::string& path_src) +void FileIndex::indexGameDirectory(const fs::path& base_path) { - path base(base_src); - path searchpath(path_src); + gamedatapath_ = base_path; - // Iterate over each component of the path - for(const path& path_component : boost::make_iterator_range(searchpath.begin(), searchpath.end())) { - std::string cmpLower = path_component.string(); - std::transform(cmpLower.begin(), cmpLower.end(), cmpLower.begin(), ::tolower); + for(const path& entry : boost::make_iterator_range(recursive_directory_iterator(base_path), {})) { + if(is_regular_file(entry)) { + std::string name = entry.native(); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); - // Search the current base path for a filename matching the component we're searching for - bool found = false; - for(const path& entry : boost::make_iterator_range(directory_iterator(base), {})) { - std::string lowerName = entry.filename().string(); - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - - if(lowerName == cmpLower) { - // We got a match, so add it to base and continue - base /= entry.filename(); - found = true; - break; - } - } - - if(!found) { - throw std::runtime_error("Can't find real path case of " + path_src); + filesystemfiles_[name] = entry; } } - - return base.string(); } void FileIndex::indexTree(const std::string& root) diff --git a/rwlib/source/platform/FileIndex.hpp b/rwlib/source/platform/FileIndex.hpp index bb175ade..e9737316 100644 --- a/rwlib/source/platform/FileIndex.hpp +++ b/rwlib/source/platform/FileIndex.hpp @@ -2,15 +2,66 @@ #define RWENGINE_FILEINDEX_HPP #include "FileHandle.hpp" +#include +#include #include #include +#include -std::string findPathRealCase(const std::string& base_src, const std::string& path_src); +namespace fs = boost::filesystem; + +namespace std +{ + template<> struct hash + { + size_t operator()(const fs::path& p) const + { + return fs::hash_value(p); + } + }; +} class FileIndex { +private: + /** + * Mapping type (lower case name) => (on disk name) + */ + using FileSystemMap = std::unordered_map; + + fs::path gamedatapath_; + FileSystemMap filesystemfiles_; + public: + /** + * @brief indexDirectory finds the true case for each file in the tree + * @param base_path + * + * This is used to build the mapping of lower-case file paths to the + * true case on the file system for platforms where this is an issue. + * + */ + void indexGameDirectory(const fs::path& base_path); + + /** + * @brief findFilePath finds disk path for a game data file + * @param path + * @return The file path as it exists on disk + */ + fs::path findFilePath(std::string path) + { + auto backslash = std::string::npos; + while ((backslash = path.find("\\")) != std::string::npos) { + path.replace(backslash, 1, "/"); + } + auto realpath = gamedatapath_ / path; + std::string name = realpath.native(); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + return filesystemfiles_[name]; + } + struct IndexData { /// Lowercase identifying filename diff --git a/tests/test_FileIndex.cpp b/tests/test_FileIndex.cpp index 9cb8012c..07e7cc9b 100644 --- a/tests/test_FileIndex.cpp +++ b/tests/test_FileIndex.cpp @@ -5,6 +5,33 @@ BOOST_AUTO_TEST_SUITE(FileIndexTests) #if RW_TEST_WITH_DATA +BOOST_AUTO_TEST_CASE(test_directory_paths) +{ + FileIndex index; + + index.indexGameDirectory(Global::getGamePath()); + + { + std::string upperpath { "DATA/CULLZONE.DAT" }; + auto truepath = index.findFilePath(upperpath); + BOOST_ASSERT(! truepath.empty()); + BOOST_CHECK(upperpath != truepath); + fs::path expected {Global::getGamePath()}; + expected /= "data/CULLZONE.DAT"; + BOOST_CHECK_EQUAL(truepath.native(), expected.native()); + } + { + std::string upperpath { "DATA/MAPS/COMNBTM/COMNBTM.IPL" }; + auto truepath = index.findFilePath(upperpath); + BOOST_ASSERT(! truepath.empty()); + BOOST_CHECK(upperpath != truepath); + fs::path expected {Global::getGamePath()}; + expected /= "data/maps/comnbtm/comNbtm.ipl"; + BOOST_CHECK_EQUAL(truepath.native(), expected.native()); + } +} + + BOOST_AUTO_TEST_CASE(test_index) { FileIndex index;