From abf43f07c88a5a1febb73988ed857aeeb1c6e59d Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Thu, 26 Feb 2015 03:57:28 +0000 Subject: [PATCH] New File handling implementation - move FileHandle into a separate header - Implement FileIndex, a system to normalize filenames and sources --- rwengine/include/core/FileArchive.hpp | 0 rwengine/include/core/FileHandle.hpp | 18 +++ rwengine/include/core/FileIndex.hpp | 50 +++++++ rwengine/include/engine/RWTypes.hpp | 13 -- .../include/loaders/LoaderCutsceneDAT.hpp | 2 +- rwengine/include/loaders/LoaderDFF.hpp | 2 +- rwengine/include/loaders/LoaderGXT.hpp | 2 +- rwengine/src/core/FileIndex.cpp | 130 ++++++++++++++++++ rwengine/src/loaders/LoaderIMG.cpp | 6 + rwengine/tests/test_FileIndex.cpp | 53 +++++++ tests/CMakeLists.txt | 5 +- 11 files changed, 264 insertions(+), 17 deletions(-) create mode 100644 rwengine/include/core/FileArchive.hpp create mode 100644 rwengine/include/core/FileHandle.hpp create mode 100644 rwengine/include/core/FileIndex.hpp create mode 100644 rwengine/src/core/FileIndex.cpp create mode 100644 rwengine/tests/test_FileIndex.cpp diff --git a/rwengine/include/core/FileArchive.hpp b/rwengine/include/core/FileArchive.hpp new file mode 100644 index 00000000..e69de29b diff --git a/rwengine/include/core/FileHandle.hpp b/rwengine/include/core/FileHandle.hpp new file mode 100644 index 00000000..9b5d15c8 --- /dev/null +++ b/rwengine/include/core/FileHandle.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +/** + * @brief Contains a pointer to a file's contents. + */ +struct FileContentsInfo +{ + char* data; + size_t length; + + ~FileContentsInfo() { + delete[] data; + } +}; + +typedef std::shared_ptr FileHandle; diff --git a/rwengine/include/core/FileIndex.hpp b/rwengine/include/core/FileIndex.hpp new file mode 100644 index 00000000..67d7b8e6 --- /dev/null +++ b/rwengine/include/core/FileIndex.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "FileHandle.hpp" + +#include +#include + +class FileIndex +{ +public: + + struct IndexData + { + /// Lowercase identifying filename + std::string filename; + /// Original filename + std::string originalName; + /// Containing directory + std::string directory; + /// The archive filename (if applicable) + std::string archive; + }; + + /** + * Adds the files contained within the given directory to the + * file index. + */ + void indexDirectory(const std::string& directory); + + /** + * Adds the files contained within the given Archive file to the + * file index. + */ + void indexArchive(const std::string& archive); + + /** + * Returns true if the file identified by filename is found within + * the file index. If the file is found, the filedata parameter + * is populated. + */ + bool findFile(const std::string& filename, IndexData& filedata); + + /** + * Returns a FileHandle for the file if it can be found in the + * file index, otherwise an empty FileHandle is returned. + */ + FileHandle openFile(const std::string& filename); + +private: + std::map files; +}; \ No newline at end of file diff --git a/rwengine/include/engine/RWTypes.hpp b/rwengine/include/engine/RWTypes.hpp index ee975629..56af1a11 100644 --- a/rwengine/include/engine/RWTypes.hpp +++ b/rwengine/include/engine/RWTypes.hpp @@ -58,17 +58,4 @@ struct RGBA }; } -/** - * @brief Returned by GameData::loadFile() - */ -struct FileContentsInfo { - char* data; - size_t length; - - ~FileContentsInfo() { - delete[] data; - } -}; -typedef std::shared_ptr FileHandle; - #endif diff --git a/rwengine/include/loaders/LoaderCutsceneDAT.hpp b/rwengine/include/loaders/LoaderCutsceneDAT.hpp index 55fdb36f..54f9e45f 100644 --- a/rwengine/include/loaders/LoaderCutsceneDAT.hpp +++ b/rwengine/include/loaders/LoaderCutsceneDAT.hpp @@ -1,7 +1,7 @@ #pragma once #ifndef _LOADERCUTSCENEDAT_HPP_ #define _LOADERCUTSCENEDAT_HPP_ -#include +#include #include class LoaderCutsceneDAT diff --git a/rwengine/include/loaders/LoaderDFF.hpp b/rwengine/include/loaders/LoaderDFF.hpp index 5ce1804a..faaae83c 100644 --- a/rwengine/include/loaders/LoaderDFF.hpp +++ b/rwengine/include/loaders/LoaderDFF.hpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include class Model; class GameData; diff --git a/rwengine/include/loaders/LoaderGXT.hpp b/rwengine/include/loaders/LoaderGXT.hpp index 519e6e2a..547792a0 100644 --- a/rwengine/include/loaders/LoaderGXT.hpp +++ b/rwengine/include/loaders/LoaderGXT.hpp @@ -1,7 +1,7 @@ #pragma once #ifndef _LOADERGXT_HPP_ #define _LOADERGXT_HPP_ -#include +#include #include class LoaderGXT diff --git a/rwengine/src/core/FileIndex.cpp b/rwengine/src/core/FileIndex.cpp new file mode 100644 index 00000000..281cbe63 --- /dev/null +++ b/rwengine/src/core/FileIndex.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include +#include +#include +#include + +void FileIndex::indexDirectory(const std::string& directory) +{ + DIR* dp = opendir(directory.c_str()); + dirent* ep; + std::string realName, lowerName; + if ( dp == NULL ) { + throw std::runtime_error("Unable to open directory: " + directory); + } + while( (ep = readdir(dp)) ) + { + realName = ep->d_name; + lowerName = realName; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + if ( ep->d_type == DT_REG ) + { + files[ lowerName ] = { + lowerName, + realName, + directory, + "" + }; + } + } +} + +void FileIndex::indexArchive(const std::string& archive) +{ + // Split directory from archive name + auto slash = archive.find_last_of('/'); + auto directory = archive.substr(0, slash); + auto archivebasename = archive.substr(slash+1); + auto archivepath = directory + "/" + archivebasename; + + LoaderIMG img; + + if( ! img.load( archivepath ) ) + { + throw std::runtime_error("Failed to load IMG archive: " + archivepath); + } + + std::string lowerName; + for( size_t i = 0; i < img.getAssetCount(); ++i ) + { + auto& asset = img.getAssetInfoByIndex(i); + + if( asset.size == 0 ) continue; + + lowerName = asset.name; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + + files[ lowerName ] = { + lowerName, + asset.name, + directory, + archivebasename + }; + } +} + +bool FileIndex::findFile(const std::string& filename, FileIndex::IndexData& filedata) +{ + auto iterator = files.find( filename ); + if( iterator == files.end() ) + { + return false; + } + + filedata = iterator->second; + + return true; +} + +FileHandle FileIndex::openFile(const std::string& filename) +{ + auto iterator = files.find( filename ); + if( iterator == files.end() ) + { + return nullptr; + } + + IndexData& f = iterator->second; + bool isArchive = !f.archive.empty(); + + auto fsName = f.directory + "/" + f.originalName; + + char* data = nullptr; + size_t length = 0; + + if( isArchive ) + { + fsName = f.directory + "/" + f.archive; + + LoaderIMG img; + + if( ! img.load(fsName) ) + { + throw std::runtime_error("Failed to load IMG archive: " + fsName); + } + + LoaderIMGFile file; + if( img.findAssetInfo(f.originalName, file) ) + { + length = file.size * 2048; + data = img.loadToMemory(f.originalName); + } + } + else + { + std::ifstream dfile(fsName.c_str()); + if ( ! dfile.is_open()) { + throw std::runtime_error("Unable to open file: " + fsName); + } + + dfile.seekg(0, std::ios_base::end); + size_t length = dfile.tellg(); + dfile.seekg(0); + char *data = new char[length]; + dfile.read(data, length); + } + + return FileHandle( new FileContentsInfo{ data, length } ); +} diff --git a/rwengine/src/loaders/LoaderIMG.cpp b/rwengine/src/loaders/LoaderIMG.cpp index dea9b820..b2f214f6 100644 --- a/rwengine/src/loaders/LoaderIMG.cpp +++ b/rwengine/src/loaders/LoaderIMG.cpp @@ -12,6 +12,12 @@ LoaderIMG::LoaderIMG() bool LoaderIMG::load(const std::string& filename) { std::string dirName = filename; + auto extpos = dirName.find(".img"); + if( extpos != std::string::npos ) + { + dirName.erase(extpos); + } + dirName.append(".dir"); FILE* fp = fopen(dirName.c_str(), "rb"); diff --git a/rwengine/tests/test_FileIndex.cpp b/rwengine/tests/test_FileIndex.cpp new file mode 100644 index 00000000..4596aeea --- /dev/null +++ b/rwengine/tests/test_FileIndex.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(FileIndexTests) + +BOOST_AUTO_TEST_CASE(test_index) +{ + FileIndex index; + + index.indexDirectory(Global::getGamePath()+"/data"); + + FileIndex::IndexData data; + BOOST_CHECK( index.findFile("cullzone.dat", data) ); + BOOST_CHECK_EQUAL( data.filename, "cullzone.dat" ); + BOOST_CHECK_EQUAL( data.originalName, "CULLZONE.DAT" ); + BOOST_CHECK( data.archive.empty() ); +} + +BOOST_AUTO_TEST_CASE(test_file) +{ + FileIndex index; + + index.indexDirectory(Global::getGamePath()+"/data"); + + auto handle = index.openFile("cullzone.dat"); + BOOST_CHECK( handle != nullptr ); +} + +BOOST_AUTO_TEST_CASE(test_index_archive) +{ + FileIndex index; + + index.indexArchive(Global::getGamePath()+"/models/gta3.img"); + + FileIndex::IndexData data; + BOOST_CHECK( index.findFile("landstal.dff", data) ); + BOOST_CHECK_EQUAL( data.filename, "landstal.dff" ); + BOOST_CHECK_EQUAL( data.originalName, "landstal.dff" ); + BOOST_CHECK_EQUAL( data.archive, "gta3.img" ); +} + +BOOST_AUTO_TEST_CASE(test_file_archive) +{ + FileIndex index; + + index.indexArchive(Global::getGamePath()+"/models/gta3.img"); + + auto handle = index.openFile("landstal.dff"); + BOOST_CHECK( handle != nullptr ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b50c755..274e13e7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,9 +1,12 @@ -FILE(GLOB TEST_SOURCES "*.cpp") +FILE(GLOB TEST_SOURCES + "*.cpp" + "${CMAKE_SOURCE_DIR}/rwengine/tests/*.cpp") ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK) add_executable(run_tests ${TEST_SOURCES}) include_directories(include) +include_directories("${CMAKE_SOURCE_DIR}/tests") find_package(Boost COMPONENTS unit_test_framework REQUIRED)