2018-10-24 23:31:26 +11:00
|
|
|
#include "GameData.h"
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <sstream>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <map>
|
|
|
|
#include <zlib/zlib.h>
|
|
|
|
|
|
|
|
#include "bparse.h"
|
|
|
|
#include "DatCat.h"
|
|
|
|
#include "File.h"
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
// Relation between category number and category name
|
|
|
|
// These names are taken straight from the exe, it helps resolve dispatching when getting files by path
|
|
|
|
|
|
|
|
std::unordered_map< std::string, uint32_t > categoryNameToIdMap =
|
|
|
|
{{"common", 0x00},
|
|
|
|
{"bgcommon", 0x01},
|
|
|
|
{"bg", 0x02},
|
|
|
|
{"cut", 0x03},
|
|
|
|
{"chara", 0x04},
|
|
|
|
{"shader", 0x05},
|
|
|
|
{"ui", 0x06},
|
|
|
|
{"sound", 0x07},
|
|
|
|
{"vfx", 0x08},
|
|
|
|
{"ui_script", 0x09},
|
|
|
|
{"exd", 0x0A},
|
|
|
|
{"game_script", 0x0B},
|
|
|
|
{"music", 0x0C}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::unordered_map< uint32_t, std::string > categoryIdToNameMap =
|
|
|
|
{{0x00, "common"},
|
|
|
|
{0x01, "bgcommon"},
|
|
|
|
{0x02, "bg"},
|
|
|
|
{0x03, "cut"},
|
|
|
|
{0x04, "chara"},
|
|
|
|
{0x05, "shader"},
|
|
|
|
{0x06, "ui"},
|
|
|
|
{0x07, "sound"},
|
|
|
|
{0x08, "vfx"},
|
|
|
|
{0x09, "ui_script"},
|
|
|
|
{0x0A, "exd"},
|
|
|
|
{0x0B, "game_script"},
|
|
|
|
{0x0C, "music"}};
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace xiv
|
|
|
|
{
|
|
|
|
namespace dat
|
|
|
|
{
|
|
|
|
|
2019-10-14 18:41:16 +11:00
|
|
|
GameData::GameData(const std::filesystem::path& path) try :
|
2018-10-24 23:31:26 +11:00
|
|
|
m_path(path)
|
|
|
|
{
|
|
|
|
int maxExLevel = 0;
|
2019-01-26 22:33:00 +11:00
|
|
|
|
2019-01-26 23:17:42 +11:00
|
|
|
// msvc has retarded stdlib implementation
|
|
|
|
#ifdef _WIN32
|
|
|
|
static constexpr auto sep = "\\";
|
|
|
|
#else
|
2019-10-14 18:41:16 +11:00
|
|
|
static constexpr auto sep = std::filesystem::path::preferred_separator;
|
2019-01-26 23:17:42 +11:00
|
|
|
#endif
|
2018-10-25 12:17:40 +02:00
|
|
|
|
2018-10-24 23:31:26 +11:00
|
|
|
// Determine which expansions are available
|
2019-10-14 18:41:16 +11:00
|
|
|
while( std::filesystem::exists( std::filesystem::path( m_path.string() + sep + "ex" + std::to_string( maxExLevel + 1 ) + sep + "ex" + std::to_string( maxExLevel + 1 ) + ".ver" ) ) )
|
2018-10-24 23:31:26 +11:00
|
|
|
{
|
|
|
|
maxExLevel++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Iterate over the files in path
|
2019-10-14 18:41:16 +11:00
|
|
|
for( auto it = std::filesystem::directory_iterator( m_path.string() + "//ffxiv" ); it != std::filesystem::directory_iterator(); ++it )
|
2018-10-24 23:31:26 +11:00
|
|
|
{
|
|
|
|
// Get the filename of the current element
|
|
|
|
auto filename = it->path().filename().string();
|
|
|
|
|
|
|
|
// If it contains ".win32.index" this is most likely a hit for a category
|
|
|
|
if( filename.find( ".win32.index" ) != std::string::npos && filename.find( ".win32.index2" ) == std::string::npos )
|
|
|
|
{
|
|
|
|
// Format of indexes is XX0000.win32.index, so fetch the hex number for category number
|
|
|
|
std::istringstream iss( filename.substr( 0, 2 ) );
|
|
|
|
uint32_t cat_nb;
|
|
|
|
iss >> std::hex >> cat_nb;
|
|
|
|
|
|
|
|
|
|
|
|
// Add to the list of category number
|
|
|
|
// creates the empty category in the cats map
|
|
|
|
// instantiate the creation mutex for this category
|
|
|
|
m_catNums.push_back( cat_nb );
|
|
|
|
m_cats[cat_nb] = std::unique_ptr<Cat>();
|
|
|
|
m_catCreationMutexes[cat_nb] = std::unique_ptr<std::mutex>( new std::mutex() );
|
|
|
|
|
|
|
|
// Check for expansion
|
|
|
|
for( int exNum = 1; exNum <= maxExLevel; exNum++ )
|
|
|
|
{
|
2019-01-26 22:33:00 +11:00
|
|
|
const std::string path = m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" );
|
2018-10-24 23:31:26 +11:00
|
|
|
|
2019-10-14 18:41:16 +11:00
|
|
|
if( std::filesystem::exists( std::filesystem::path( path ) ) )
|
2018-10-24 23:31:26 +11:00
|
|
|
{
|
|
|
|
|
|
|
|
int chunkCount = 0;
|
|
|
|
|
|
|
|
for(int chunkTest = 0; chunkTest < 256; chunkTest++ )
|
|
|
|
{
|
2019-10-14 18:41:16 +11:00
|
|
|
if( std::filesystem::exists( m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32", "index" ) ) )
|
2018-10-24 23:31:26 +11:00
|
|
|
{
|
|
|
|
m_exCats[cat_nb].exNumToChunkMap[exNum].chunkToCatMap[chunkTest] = std::unique_ptr<Cat>();
|
|
|
|
chunkCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
catch( std::exception& e )
|
|
|
|
{
|
|
|
|
// In case of failure here, client is supposed to catch the exception because it is not recoverable on our side
|
|
|
|
throw std::runtime_error( "GameData initialization failed: " + std::string( e.what() ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
GameData::~GameData()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string GameData::buildDatStr( const std::string folder, const int cat, const int exNum, const int chunk, const std::string platform, const std::string type )
|
|
|
|
{
|
|
|
|
char dat[1024];
|
|
|
|
sprintf( dat, "%s/%02x%02x%02x.%s.%s", folder.c_str(), cat, exNum, chunk, platform.c_str(), type.c_str() );
|
|
|
|
return std::string( dat );
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<uint32_t>& GameData::getCatNumbers() const
|
|
|
|
{
|
|
|
|
return m_catNums;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<File> GameData::getFile(const std::string& path)
|
|
|
|
{
|
|
|
|
// Get the hashes, the category from the path then call the getFile of the category
|
|
|
|
uint32_t dirHash;
|
|
|
|
uint32_t filenameHash;
|
|
|
|
getHashes( path, dirHash, filenameHash );
|
|
|
|
|
|
|
|
return getCategoryFromPath( path ).getFile( dirHash, filenameHash );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GameData::doesFileExist(const std::string& path)
|
|
|
|
{
|
|
|
|
uint32_t dirHash;
|
|
|
|
uint32_t filenameHash;
|
|
|
|
getHashes( path, dirHash, filenameHash );
|
|
|
|
|
|
|
|
return getCategoryFromPath( path ).doesFileExist( dirHash, filenameHash );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GameData::doesDirExist(const std::string& i_path)
|
|
|
|
{
|
|
|
|
uint32_t dirHash;
|
|
|
|
uint32_t filenameHash;
|
|
|
|
getHashes( i_path, dirHash, filenameHash );
|
|
|
|
|
|
|
|
return getCategoryFromPath( i_path ).doesDirExist( dirHash );
|
|
|
|
}
|
|
|
|
|
|
|
|
const Cat& GameData::getCategory(uint32_t catNum)
|
|
|
|
{
|
|
|
|
// Check that the category number exists
|
|
|
|
auto catIt = m_cats.find( catNum );
|
|
|
|
if( catIt == m_cats.end() )
|
|
|
|
{
|
|
|
|
throw std::runtime_error( "Category not found: " + std::to_string( catNum ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it exists and already instantiated return it
|
|
|
|
if( catIt->second )
|
|
|
|
{
|
|
|
|
return *( catIt->second );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Else create it and return it
|
|
|
|
createCategory( catNum );
|
|
|
|
return *( m_cats[catNum] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const Cat& GameData::getCategory(const std::string& catName)
|
|
|
|
{
|
|
|
|
// Find the category number from the name
|
|
|
|
auto categoryNameToIdMapIt = ::categoryNameToIdMap.find( catName );
|
|
|
|
if( categoryNameToIdMapIt == ::categoryNameToIdMap.end() )
|
|
|
|
{
|
|
|
|
throw std::runtime_error( "Category not found: " + catName );
|
|
|
|
}
|
|
|
|
|
|
|
|
// From the category number return the category
|
|
|
|
return getCategory( categoryNameToIdMapIt->second );
|
|
|
|
}
|
|
|
|
|
|
|
|
const Cat& GameData::getExCategory( const std::string& catName, uint32_t exNum, const std::string& path )
|
|
|
|
{
|
|
|
|
// Find the category number from the name
|
|
|
|
auto categoryMapIt = ::categoryNameToIdMap.find( catName );
|
|
|
|
if( categoryMapIt == ::categoryNameToIdMap.end() )
|
|
|
|
{
|
|
|
|
throw std::runtime_error( "Category not found: " + catName );
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t dirHash;
|
|
|
|
uint32_t filenameHash;
|
|
|
|
getHashes( path, dirHash, filenameHash );
|
|
|
|
|
|
|
|
for( auto const& chunk : m_exCats[categoryMapIt->second].exNumToChunkMap[exNum].chunkToCatMap )
|
|
|
|
{
|
|
|
|
if( !chunk.second )
|
|
|
|
createExCategory( categoryMapIt->second );
|
|
|
|
|
|
|
|
if( chunk.second->doesFileExist( dirHash, filenameHash ) )
|
|
|
|
{
|
|
|
|
return *( chunk.second );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw std::runtime_error( "Chunk not found for path: " + path );
|
|
|
|
}
|
|
|
|
|
|
|
|
const Cat& GameData::getCategoryFromPath(const std::string& path)
|
|
|
|
{
|
|
|
|
// Find the first / in the string, paths are in the format CAT_NAME/..../.../../....
|
|
|
|
auto firstSlashPos = path.find( '/' );
|
|
|
|
if( firstSlashPos == std::string::npos )
|
|
|
|
{
|
|
|
|
throw std::runtime_error( "Path does not have a / char: " + path );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( path.substr( firstSlashPos + 1, 2) == "ex" )
|
|
|
|
{
|
|
|
|
return getExCategory( path.substr( 0, firstSlashPos ), std::stoi( path.substr( firstSlashPos + 3, 1 ) ), path );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// From the sub string found beforethe first / get the category
|
|
|
|
return getCategory( path.substr( 0, firstSlashPos ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GameData::getHashes(const std::string& path, uint32_t& dirHash, uint32_t& filenameHash) const
|
|
|
|
{
|
|
|
|
// Convert the path to lowercase before getting the hashes
|
|
|
|
std::string pathLower;
|
|
|
|
pathLower.resize( path.size() );
|
|
|
|
std::transform( path.begin(), path.end(), pathLower.begin(), ::tolower );
|
|
|
|
|
|
|
|
// Find last / to separate dir from filename
|
|
|
|
auto lastSlashPos = pathLower.rfind( '/' );
|
|
|
|
if( lastSlashPos == std::string::npos )
|
|
|
|
{
|
|
|
|
throw std::runtime_error( "Path does not have a / char: " + path );
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string dirPart = pathLower.substr( 0, lastSlashPos );
|
|
|
|
std::string filenamePart = pathLower.substr( lastSlashPos + 1 );
|
|
|
|
|
|
|
|
// Get the crc32 values from zlib, to compensate the final XOR 0xFFFFFFFF that isnot done in the exe we just reXOR
|
|
|
|
dirHash = crc32( 0, reinterpret_cast<const uint8_t*>( dirPart.data() ), dirPart.size() ) ^ 0xFFFFFFFF;
|
|
|
|
filenameHash = crc32( 0, reinterpret_cast<const uint8_t*>( filenamePart.data() ), filenamePart.size() ) ^ 0xFFFFFFFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GameData::createCategory(uint32_t catNum)
|
|
|
|
{
|
|
|
|
// Lock mutex in this scope
|
|
|
|
std::lock_guard<std::mutex> lock( *( m_catCreationMutexes[catNum] ) );
|
|
|
|
// Maybe after unlocking it has already been created, so check (most likely if it blocked)
|
|
|
|
if( !m_cats[catNum] )
|
|
|
|
{
|
|
|
|
// Get the category name if we have it
|
|
|
|
std::string catName;
|
|
|
|
auto categoryMapIt = ::categoryIdToNameMap.find( catNum );
|
|
|
|
if( categoryMapIt != ::categoryIdToNameMap.end() )
|
|
|
|
{
|
|
|
|
catName = categoryMapIt->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Actually creates the category
|
|
|
|
m_cats[catNum] = std::unique_ptr<Cat>( new Cat( m_path, catNum, catName ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GameData::createExCategory( uint32_t catNum )
|
|
|
|
{
|
|
|
|
// Maybe after unlocking it has already been created, so check (most likely if it blocked)
|
|
|
|
if( !m_exCats[catNum].exNumToChunkMap[1].chunkToCatMap[0] )
|
|
|
|
{
|
|
|
|
// Get the category name if we have it
|
|
|
|
std::string catName;
|
|
|
|
auto categoryMapIt = ::categoryIdToNameMap.find( catNum );
|
|
|
|
if( categoryMapIt != ::categoryIdToNameMap.end() )
|
|
|
|
{
|
|
|
|
catName = categoryMapIt->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
for( auto const& ex : m_exCats[catNum].exNumToChunkMap )
|
|
|
|
{
|
|
|
|
for( auto const& chunk : m_exCats[catNum].exNumToChunkMap[ex.first].chunkToCatMap )
|
|
|
|
{
|
|
|
|
// Actually creates the category
|
|
|
|
m_exCats[catNum].exNumToChunkMap[ex.first].chunkToCatMap[chunk.first] = std::unique_ptr<Cat>( new Cat( m_path, catNum, catName, ex.first, chunk.first ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|