diff --git a/src/tools/pcb_reader/lgb.h b/src/tools/pcb_reader/lgb.h index e29d517c..7a9b7bdc 100644 --- a/src/tools/pcb_reader/lgb.h +++ b/src/tools/pcb_reader/lgb.h @@ -13,6 +13,9 @@ #include "vec3.h" #include "sgb.h" +// garbage to skip model loading +extern bool ignoreModels; + // all credit to // https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Lgb/ // this is simply their work ported to c++ since we dont c# @@ -89,6 +92,10 @@ public: m_offset = offset; header = *reinterpret_cast< LGB_ENTRY_HEADER* >( buf + offset ); }; + const LgbEntryType getType() const + { + return header.type; + }; virtual ~LGB_ENTRY() {}; }; @@ -184,6 +191,26 @@ public: }; }; +struct LGB_MAPRANGE_HEADER : public LGB_ENTRY_HEADER +{ + uint32_t type; + uint32_t unknown2; + uint8_t unknown3[0x10]; +}; + +struct LGB_MAPRANGE_ENTRY : public LGB_ENTRY +{ +public: + LGB_MAPRANGE_HEADER header; + std::string name; + + LGB_MAPRANGE_ENTRY( char* buf, uint32_t offset ) : LGB_ENTRY( buf, offset ) + { + header = *reinterpret_cast< LGB_MAPRANGE_HEADER* >( buf + offset ); + name = std::string( buf + offset + header.nameOffset ); + }; +}; + struct LGB_GROUP_HEADER { uint32_t unknown; @@ -223,11 +250,12 @@ struct LGB_GROUP try { const auto type = *reinterpret_cast( buf + entryOffset ); - if( type == LgbEntryType::BgParts ) + // garbage to skip model loading + if( !ignoreModels && type == LgbEntryType::BgParts ) { entries.push_back( std::make_shared< LGB_BGPARTS_ENTRY >( buf, entryOffset ) ); } - else if( type == LgbEntryType::Gimmick ) + else if( !ignoreModels && type == LgbEntryType::Gimmick ) { entries.push_back( std::make_shared< LGB_GIMMICK_ENTRY >( buf, entryOffset ) ); } @@ -241,7 +269,7 @@ struct LGB_GROUP } else if( type == LgbEntryType::MapRange ) { - entries.push_back( std::make_shared< LGB_EOBJ_ENTRY >( buf, entryOffset ) ); + entries.push_back( std::make_shared< LGB_MAPRANGE_ENTRY >( buf, entryOffset ) ); } /* else @@ -249,7 +277,7 @@ struct LGB_GROUP entries[i] = nullptr; } */ - + } catch( std::exception& e ) { @@ -276,8 +304,9 @@ struct LGB_FILE { LGB_FILE_HEADER header; std::vector< LGB_GROUP > groups; + std::string name; - LGB_FILE( char* buf ) + LGB_FILE( char* buf, const std::string& name ) { header = *reinterpret_cast< LGB_FILE_HEADER* >( buf ); if( strncmp( &header.magic[0], "LGB1", 4 ) != 0 || strncmp( &header.magic2[0], "LGP1", 4 ) != 0 ) diff --git a/src/tools/pcb_reader/main.cpp b/src/tools/pcb_reader/main.cpp index fb9b5326..c6704856 100644 --- a/src/tools/pcb_reader/main.cpp +++ b/src/tools/pcb_reader/main.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "pcb.h" #include "lgb.h" @@ -21,9 +23,15 @@ #include #endif -std::string gamePath("C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack\\ffxiv"); +// garbage to ignore models +bool ignoreModels = false; + +std::string gamePath( "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack\\ffxiv" ); std::unordered_map< uint32_t, std::string > eobjNameMap; std::unordered_map< uint16_t, std::string > zoneNameMap; +uint32_t zoneId; + +std::set< std::string > zoneDumpList; xiv::dat::GameData* data1 = nullptr; xiv::exd::ExdData* eData = nullptr; @@ -145,7 +153,6 @@ void dumpLevelExdEntries( uint32_t zoneId, const std::string& name = std::string std::string zoneNameToPath( const std::string& name ) { std::string path; - uint32_t id; bool found = false; #ifdef STANDALONE @@ -159,13 +166,14 @@ std::string zoneNameToPath( const std::string& name ) std::smatch match; if( std::regex_match( line, match, re ) { + auto tmpId = std::stoul( match[1].str() ); if( !found && name == match[2].str() ) { - id = match[1].str(); + zoneId = tmpId; path = match[3].str(); found = true; } - zoneNameMap[std::stoul( match[1].str() )] = match[2].str(); + zoneNameMap[tmpId] = match[2].str(); } } inFile.close(); @@ -185,7 +193,7 @@ std::string zoneNameToPath( const std::string& name ) { path = teriPath; found = true; - id = row.first; + zoneId = row.first; } zoneNameMap[row.first] = teriName; } @@ -203,7 +211,7 @@ std::string zoneNameToPath( const std::string& name ) throw std::runtime_error( "Unable to find path for " + name + ".\n\tPlease double check spelling or open 0a0000.win32.index with FFXIV Explorer and extract territorytype.exh as CSV\n\tand copy territorytype.exh.csv into pcb_reader.exe directory if using standalone" ); } - dumpLevelExdEntries( id, name ); + return path; } @@ -220,21 +228,38 @@ void loadEobjNames() } } -void writeEobjEntry( std::ofstream& out, LGB_EOBJ_ENTRY* pEobj, const std::string& name ) +void writeEobjEntry( std::ofstream& out, LGB_ENTRY* pObj ) { static std::string mapRangeStr( "\"MapRange\", " ); static std::string eobjStr( "\"EObj\", " ); + uint32_t id; + std::string name; + std::string typeStr; + + if( pObj->getType() == LgbEntryType::EventObject ) + { + auto pEobj = reinterpret_cast< LGB_EOBJ_ENTRY* >( pObj ); + id = pEobj->header.eobjId; + name = eobjNameMap[id]; + typeStr = eobjStr; + } + else if( pObj->getType() == LgbEntryType::MapRange ) + { + auto pMapRange = reinterpret_cast< LGB_MAPRANGE_ENTRY* >( pObj ); + id = pMapRange->header.unknown; + typeStr = mapRangeStr; + } + std::string outStr( - std::to_string( pEobj->header.eobjId ) + ", \"" + name + "\", " + std::string( pEobj->header.type == LgbEntryType::MapRange ? mapRangeStr : eobjStr ) + - std::to_string( pEobj->header.translation.x ) + ", " + std::to_string( pEobj->header.translation.y ) + ", " + std::to_string( pEobj->header.translation.z ) + "\n" + std::to_string( id ) + ", " + typeStr + "\"" + name + "\", " + + std::to_string( pObj->header.translation.x ) + ", " + std::to_string( pObj->header.translation.y ) + ", " + std::to_string( pObj->header.translation.z ) + "\n" ); out.write( outStr.c_str(), outStr.size() ); } -void dumpAllInstanceContentEntries() +void loadAllInstanceContentEntries() { - auto& catInstance = eData->get_category( "InstanceContent" ); auto exdInstance = static_cast< xiv::exd::Exd >( catInstance.get_data_ln( xiv::exd::Language::en ) ); @@ -255,7 +280,7 @@ void dumpAllInstanceContentEntries() auto i = 0; while( ( i = name.find( ' ' ) ) != std::string::npos ) name = name.replace( name.begin() + i, name.begin() + i + 1, { '_' } ); - dumpLevelExdEntries( teri, name ); + zoneDumpList.emplace( zoneNameMap[teri] ); } } @@ -280,22 +305,47 @@ void readFileToBuffer( const std::string& path, std::vector< char >& buf ) int main( int argc, char* argv[] ) { auto startTime = std::chrono::system_clock::now(); + auto entryStartTime = std::chrono::system_clock::now(); + std::vector< std::string > argVec( argv + 1, argv + argc ); // todo: support expansions std::string zoneName = "r1f1"; + bool dumpInstances = ignoreModels = std::remove_if( argVec.begin(), argVec.end(), []( auto arg ){ return arg == "--instance-dump"; } ) != argVec.end(); + if( argc > 1 ) { zoneName = argv[1]; if( argc > 2 ) { - gamePath = argv[2]; + std::string tmpPath( argv[2] ); + if( !( tmpPath.empty() || tmpPath.find( '/' ) == std::string::npos ) ) + gamePath = argv[2]; } } initExd( gamePath ); + if( dumpInstances ) + { + loadAllInstanceContentEntries(); + } + else + { + zoneDumpList.emplace( zoneName ); + } + +LABEL_DUMP: + entryStartTime = std::chrono::system_clock::now(); + zoneName = *zoneDumpList.begin(); try { const auto& zonePath = zoneNameToPath( zoneName ); + + if( zonePath.find( "ex1/" ) != std::string::npos || zonePath.find( "ex2" ) != std::string::npos ) + { + std::cout << "[Error] Expansions are currently not supported " << zonePath << "\n"; + goto LABEL_NEXT_ZONE_ENTRY; + } + std::string listPcbPath( zonePath + "/collision/list.pcb" ); std::string bgLgbPath( zonePath + "/level/bg.lgb" ); std::string planmapLgbPath( zonePath + "/level/planmap.lgb" ); @@ -321,14 +371,13 @@ int main( int argc, char* argv[] ) readFileToBuffer( listPcbPath, section1 ); } #endif - if( argc > 3 && argv[3] == "--instanceDump" ) - dumpAllInstanceContentEntries(); std::vector< std::string > stringList; uint32_t offset1 = 0x20; loadEobjNames(); + dumpLevelExdEntries( zoneId, zoneName ); std::string eobjFileName( zoneName + "_eobj.csv" ); std::ofstream eobjOut( eobjFileName, std::ios::trunc ); if( !eobjOut.good() ) @@ -357,20 +406,20 @@ int main( int argc, char* argv[] ) } } - LGB_FILE bgLgb( §ion[0] ); - LGB_FILE planmapLgb( §ion2[0] ); + LGB_FILE bgLgb( §ion[0], "bg" ); + LGB_FILE planmapLgb( §ion2[0], "planmap" ); std::vector< LGB_FILE > lgbList { bgLgb, planmapLgb }; uint32_t max_index = 0; // dont bother if we cant write to a file - auto fp_out = fopen( ( zoneName + ".obj" ).c_str(), "w" ); + auto fp_out = ignoreModels ? ( FILE* )nullptr : fopen( ( zoneName + ".obj" ).c_str(), "w" ); if( fp_out ) { fprintf( fp_out, "\n" ); fclose( fp_out ); } - else + else if( !ignoreModels ) { std::string errorMessage( "Cannot create " + zoneName + ".obj\n" + " Check no programs have a handle to file and run as admin.\n" ); @@ -379,14 +428,15 @@ int main( int argc, char* argv[] ) return 0; } - fp_out = fopen( ( zoneName + ".obj" ).c_str(), "ab+" ); - if( fp_out ) + if( ignoreModels || ( fp_out = fopen( ( zoneName + ".obj" ).c_str(), "ab+" ) ) ) { std::map< std::string, PCB_FILE > pcbFiles; std::map< std::string, SGB_FILE > sgbFiles; std::map< std::string, uint32_t > objCount; auto loadPcbFile = [&]( const std::string& fileName ) -> bool { + if( ignoreModels ) + return false; try { if( fileName.find( '.' ) == std::string::npos ) @@ -474,6 +524,8 @@ int main( int argc, char* argv[] ) const vec3* translation = nullptr, const SGB_MODEL_ENTRY* pSgbEntry = nullptr) { + if( ignoreModels ) + return; char name2[0x100]; memset( name2, 0, 0x100 ); sprintf( &name2[0], "%s_%u", &name[0], objCount[name]++ ); @@ -556,14 +608,14 @@ int main( int argc, char* argv[] ) loadPcbFile( fileName ); pushVerts( pcbFiles[fileName], fileName ); } - std::cout << "[Info] " << "Writing obj file " << "\n"; - std::cout << "[Info] " << bgLgb.groups.size() << " groups " << "\n"; + + std::cout << "[Info] " << ( ignoreModels ? "Dumping MapRange and EObj" : "Writing obj file " ) << "\n"; uint32_t totalGroups = 0; uint32_t totalGroupEntries = 0; for( const auto& lgb : lgbList ) { - for( const auto& group : bgLgb.groups ) + for( const auto& group : lgb.groups ) { //std::cout << "\t" << group.name << " Size " << group.header.entryCount << "\n"; totalGroups++; @@ -571,7 +623,6 @@ int main( int argc, char* argv[] ) { auto pGimmick = dynamic_cast< LGB_GIMMICK_ENTRY* >( pEntry.get() ); auto pBgParts = dynamic_cast< LGB_BGPARTS_ENTRY* >( pEntry.get() ); - auto pEventObj = dynamic_cast< LGB_EOBJ_ENTRY* >( pEntry.get() ); std::string fileName( "" ); fileName.resize( 256 ); @@ -631,26 +682,19 @@ int main( int argc, char* argv[] ) } } - if( pEntry->header.type == LgbEntryType::EventObject || pEntry->header.type == LgbEntryType::EventNpc || - pEntry->header.type == LgbEntryType::MapRange ) + if( pEntry->getType() == LgbEntryType::EventObject || pEntry->getType() == LgbEntryType::MapRange ) { - std::cout << "HEADER SHIT " << std::to_string( ( int )pEntry->header.type ) << "\n"; - } - - if( pEventObj ) - { - fileName = pEventObj->name.empty() ? eobjNameMap[pEventObj->header.eobjId] : pEventObj->name; - writeEobjEntry( eobjOut, pEventObj, fileName ); + writeEobjEntry( eobjOut, pEntry.get() ); //writeOutput( fileName, &pEventObj->header.scale, &pEventObj->header.rotation, &pEventObj->header.translation ); } } } } - std::cout << "\n[Info] " << "Loaded " << pcbFiles.size() << " PCB Files \n"; + std::cout << "[Info] " << "Loaded " << pcbFiles.size() << " PCB Files \n"; std::cout << "[Info] " << "Total Groups " << totalGroups << " Total entries " << totalGroupEntries << "\n"; } - std::cout << "[Success] " << "Finished exporting " << zoneName << " in " << - std::chrono::duration_cast< std::chrono::seconds >( std::chrono::system_clock::now() - startTime ).count() << " seconds\n"; + std::cout << "[Success] " << "Exported " << zoneName << " in " << + std::chrono::duration_cast< std::chrono::seconds >( std::chrono::system_clock::now() - entryStartTime ).count() << " seconds\n"; } catch( std::exception& e ) { @@ -659,6 +703,14 @@ int main( int argc, char* argv[] ) std::cout << std::endl; std::cout << "[Info] " << "Usage: pcb_reader2 territory \"path/to/game/sqpack/ffxiv\" " << std::endl; } + std::cout << "\n\n\n"; + LABEL_NEXT_ZONE_ENTRY: + zoneDumpList.erase( zoneName ); + if( !zoneDumpList.empty() ) + goto LABEL_DUMP; + + std::cout << "\n\n\n[Success] Finished all tasks in " << + std::chrono::duration_cast< std::chrono::seconds >( std::chrono::system_clock::now() - startTime ).count() << " seconds\n"; if( eData ) delete eData; diff --git a/src/tools/pcb_reader/sgb.h b/src/tools/pcb_reader/sgb.h index edd6899a..2aa675aa 100644 --- a/src/tools/pcb_reader/sgb.h +++ b/src/tools/pcb_reader/sgb.h @@ -11,6 +11,9 @@ #include "vec3.h" +// garbage to skip model loading +extern bool ignoreModels; + // // ported from https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Sgb/SgbDataType.cs @@ -132,7 +135,7 @@ struct SGB_GROUP if( entryOffset > fileSize ) throw std::runtime_error( "SGB_GROUP entry offset was larger than SGB file size!" ); auto type = *reinterpret_cast< uint32_t* >( buf + entryOffset ); - if( type == SgbGroupEntryType::Model ) + if( type == SgbGroupEntryType::Model && !ignoreModels ) { entries.push_back( std::make_shared< SGB_MODEL_ENTRY >( buf, entryOffset ) ); }