mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-20 11:47:47 +00:00
325 lines
10 KiB
C++
325 lines
10 KiB
C++
#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::dat
|
|
{
|
|
GameData::GameData( const std::filesystem::path& path ) try :
|
|
m_path( path )
|
|
{
|
|
int maxExLevel = 0;
|
|
|
|
// msvc has retarded stdlib implementation
|
|
#ifdef _WIN32
|
|
static constexpr auto sep = "\\";
|
|
#else
|
|
static constexpr auto sep = std::filesystem::path::preferred_separator;
|
|
#endif
|
|
|
|
// Determine which expansions are available
|
|
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" ) ) )
|
|
{
|
|
maxExLevel++;
|
|
}
|
|
|
|
|
|
// Iterate over the files in path
|
|
for( auto it = std::filesystem::directory_iterator( m_path.string() + "//ffxiv" );
|
|
it != std::filesystem::directory_iterator(); ++it )
|
|
{
|
|
// 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::make_unique< std::mutex >();
|
|
|
|
// Check for expansion
|
|
for( int exNum = 1; exNum <= maxExLevel; exNum++ )
|
|
{
|
|
const std::string path =
|
|
m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" );
|
|
|
|
if( std::filesystem::exists( std::filesystem::path( path ) ) )
|
|
{
|
|
|
|
int chunkCount = 0;
|
|
|
|
for( int chunkTest = 0; chunkTest < 256; chunkTest++ )
|
|
{
|
|
if( std::filesystem::exists( m_path.string() + sep +
|
|
buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32",
|
|
"index" ) ) )
|
|
{
|
|
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::make_unique< 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 ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|