2017-08-08 13:53:47 +02:00
# include <stdio.h>
2017-10-16 15:47:48 +01:00
# include <cstdint>
# include <string>
2017-11-04 08:13:35 +00:00
# include <iostream>
# include <chrono>
# include <fstream>
2017-12-09 23:37:24 +00:00
# include <regex>
2018-01-24 00:15:46 +00:00
# include <map>
2018-02-04 22:19:08 +00:00
# include <vector>
2019-01-18 18:02:31 +00:00
# include <queue>
2018-02-04 22:19:08 +00:00
# include <set>
2019-01-18 18:02:31 +00:00
# include <thread>
2018-10-27 22:51:16 +02:00
# include <variant>
# include <Util/Util.h>
2017-08-08 13:53:47 +02:00
2019-01-20 17:47:39 +00:00
# include "exporter.h"
# include "exportmgr.h"
# include "cache.h"
2017-08-08 13:53:47 +02:00
# include "pcb.h"
2017-10-16 15:47:48 +01:00
# include "lgb.h"
2017-10-19 16:29:17 +01:00
# include "sgb.h"
2017-08-08 13:53:47 +02:00
2017-10-16 23:54:27 +02:00
# include <GameData.h>
# include <File.h>
# include <DatCat.h>
# include <ExdData.h>
# include <ExdCat.h>
# include <Exd.h>
2018-08-29 21:40:59 +02:00
2018-02-04 22:19:08 +00:00
// garbage to ignore models
2019-01-19 18:46:21 +00:00
bool noObj = false ;
2018-02-04 22:19:08 +00:00
2019-01-26 15:19:54 +11:00
std : : string gamePath ( " /mnt/c/Program Files (x86)/Steam/steamapps/common/FINAL FANTASY XIV Online/game/sqpack " ) ;
2018-02-04 20:09:55 +00:00
std : : unordered_map < uint16_t , std : : string > zoneNameMap ;
2018-02-04 22:19:08 +00:00
uint32_t zoneId ;
2019-01-20 20:14:40 +00:00
2018-02-04 22:19:08 +00:00
std : : set < std : : string > zoneDumpList ;
2018-02-04 20:09:55 +00:00
2019-01-20 17:47:39 +00:00
std : : shared_ptr < Cache > pCache ;
2018-01-24 15:34:01 +00:00
xiv : : dat : : GameData * data1 = nullptr ;
xiv : : exd : : ExdData * eData = nullptr ;
2018-01-24 00:15:46 +00:00
2019-01-19 18:46:21 +00:00
2018-08-29 21:40:59 +02:00
enum class TerritoryTypeExdIndexes :
size_t
2018-01-23 13:13:35 +00:00
{
2018-08-29 21:40:59 +02:00
TerritoryType = 0 ,
Path = 1
2018-01-23 13:13:35 +00:00
} ;
2017-10-17 21:49:01 +01:00
using namespace std : : chrono_literals ;
2017-10-18 15:06:10 +01:00
2018-01-24 15:34:01 +00:00
void initExd ( const std : : string & gamePath )
{
2018-08-29 21:40:59 +02:00
data1 = data1 ? data1 : new xiv : : dat : : GameData ( gamePath ) ;
eData = eData ? eData : new xiv : : exd : : ExdData ( * data1 ) ;
2019-01-20 17:47:39 +00:00
pCache = std : : make_shared < Cache > ( data1 ) ;
2018-01-24 15:34:01 +00:00
}
2017-08-08 13:53:47 +02:00
2017-10-17 12:15:46 +01:00
std : : string zoneNameToPath ( const std : : string & name )
{
2018-08-29 21:40:59 +02:00
std : : string path ;
bool found = false ;
2018-02-04 20:09:55 +00:00
2018-08-29 21:40:59 +02:00
auto & cat = eData - > get_category ( " TerritoryType " ) ;
auto exd = static_cast < xiv : : exd : : Exd > ( cat . get_data_ln ( xiv : : exd : : Language : : none ) ) ;
for ( auto & row : exd . get_rows ( ) )
{
auto & fields = row . second ;
2018-10-27 22:51:16 +02:00
auto teriName = std : : get < std : : string > (
fields . at ( static_cast < size_t > ( TerritoryTypeExdIndexes : : TerritoryType ) ) ) ;
2018-08-29 21:40:59 +02:00
if ( teriName . empty ( ) )
continue ;
2018-10-27 22:51:16 +02:00
auto teriPath = std : : get < std : : string > ( fields . at ( static_cast < size_t > ( TerritoryTypeExdIndexes : : Path ) ) ) ;
2018-11-29 16:55:48 +01:00
if ( ! found & & ( Sapphire : : Util : : toLowerCopy ( name ) = = Sapphire : : Util : : toLowerCopy ( teriName ) ) )
2018-08-29 21:40:59 +02:00
{
path = teriPath ;
found = true ;
zoneId = row . first ;
}
zoneNameMap [ row . first ] = teriName ;
}
2018-01-23 13:13:35 +00:00
2018-08-29 21:40:59 +02:00
if ( found )
{
//path = path.substr( path.find_first_of( "/" ) + 1, path.size() - path.find_first_of( "/" ));
//path = std::string( "ffxiv/" ) + path;
path = std : : string ( " bg/ " ) + path . substr ( 0 , path . find ( " /level/ " ) ) ;
2019-01-20 20:14:40 +00:00
printf ( " [Info] Found path for %s \n " , name . c_str ( ) ) ;
2018-08-29 21:40:59 +02:00
}
else
{
throw std : : runtime_error ( " Unable to find path for " + name +
2019-01-20 20:14:40 +00:00
" . \n \t Please double check spelling. " ) ;
2018-08-29 21:40:59 +02:00
}
return path ;
2017-10-17 12:15:46 +01:00
}
int main ( int argc , char * argv [ ] )
2017-08-08 13:53:47 +02:00
{
2019-01-19 18:46:21 +00:00
auto startTime = std : : chrono : : high_resolution_clock : : now ( ) ;
auto entryStartTime = std : : chrono : : high_resolution_clock : : now ( ) ;
2018-08-29 21:40:59 +02:00
std : : vector < std : : string > argVec ( argv + 1 , argv + argc ) ;
std : : string zoneName = " r2t2 " ;
2019-01-20 17:47:39 +00:00
2019-01-19 18:46:21 +00:00
noObj = std : : remove_if ( argVec . begin ( ) , argVec . end ( ) , [ ] ( auto arg )
{ return arg = = " --no-obj " ; } ) ! = argVec . end ( ) ;
2019-01-18 18:02:31 +00:00
bool dumpAllZones = std : : remove_if ( argVec . begin ( ) , argVec . end ( ) , [ ] ( auto arg )
{ return arg = = " --dump-all " ; } ) ! = argVec . end ( ) ;
bool generateNavmesh = std : : remove_if ( argVec . begin ( ) , argVec . end ( ) , [ ] ( auto arg )
{ return arg = = " --navmesh " ; } ) ! = argVec . end ( ) ;
2019-01-20 17:47:39 +00:00
int exportFileType = 0 ;
if ( ! noObj )
exportFileType | = ExportFileType : : WavefrontObj ;
if ( generateNavmesh )
exportFileType | = ExportFileType : : Navmesh ;
2018-08-29 21:40:59 +02:00
if ( argc > 1 )
{
zoneName = argv [ 1 ] ;
if ( argc > 2 )
{
std : : string tmpPath ( argv [ 2 ] ) ;
if ( ! tmpPath . empty ( ) )
gamePath = argv [ 2 ] ;
}
}
2019-01-20 17:47:39 +00:00
try
{
initExd ( gamePath ) ;
}
catch ( std : : exception & e )
{
2019-01-20 20:14:40 +00:00
printf ( " Unable to initialise EXD! Usage: pcb_reader <teri> \" path/to/FINAL FANTASY XIV - A REALM REBORN/game/sqpack \" [--no-obj, --dump-all, --navmesh] " ) ;
2019-01-20 17:47:39 +00:00
return - 1 ;
}
ExportMgr exportMgr ;
2019-01-18 18:02:31 +00:00
zoneNameToPath ( zoneName ) ;
if ( dumpAllZones )
2018-08-29 21:40:59 +02:00
{
2019-01-18 18:02:31 +00:00
for ( const auto & zone : zoneNameMap )
zoneDumpList . emplace ( zone . second ) ;
2018-08-29 21:40:59 +02:00
}
else
{
zoneDumpList . emplace ( zoneName ) ;
}
2019-01-20 17:47:39 +00:00
for ( const auto & zoneName : zoneDumpList )
2019-01-18 18:02:31 +00:00
{
2019-01-20 17:47:39 +00:00
try
2019-01-18 18:02:31 +00:00
{
2019-01-20 17:47:39 +00:00
ExportedZone exportedZone ;
exportedZone . name = zoneName ;
2019-01-19 18:46:21 +00:00
2019-01-20 17:47:39 +00:00
const auto & zonePath = zoneNameToPath ( zoneName ) ;
2019-01-19 18:46:21 +00:00
2019-01-20 17:47:39 +00:00
std : : string listPcbPath ( zonePath + " /collision/list.pcb " ) ;
std : : string bgLgbPath ( zonePath + " /level/bg.lgb " ) ;
std : : string planmapLgbPath ( zonePath + " /level/planmap.lgb " ) ;
std : : string collisionFilePath ( zonePath + " /collision/ " ) ;
std : : vector < char > section ;
std : : vector < char > section1 ;
std : : vector < char > section2 ;
2019-01-19 18:46:21 +00:00
2019-01-20 17:47:39 +00:00
const xiv : : dat : : Cat & test = data1 - > getCategory ( " bg " ) ;
2019-01-18 18:02:31 +00:00
2019-01-20 17:47:39 +00:00
auto test_file = data1 - > getFile ( bgLgbPath ) ;
section = test_file - > access_data_sections ( ) . at ( 0 ) ;
2017-11-03 20:22:11 +00:00
2019-01-20 17:47:39 +00:00
auto planmap_file = data1 - > getFile ( planmapLgbPath ) ;
section2 = planmap_file - > access_data_sections ( ) . at ( 0 ) ;
2017-10-16 23:54:27 +02:00
2019-01-20 17:47:39 +00:00
auto test_file1 = data1 - > getFile ( listPcbPath ) ;
section1 = test_file1 - > access_data_sections ( ) . at ( 0 ) ;
2017-11-03 20:22:11 +00:00
2019-01-20 17:47:39 +00:00
std : : vector < std : : string > stringList ;
2018-01-24 15:34:01 +00:00
2019-01-20 17:47:39 +00:00
int totalGroups = 0 ;
int totalEntries = 0 ;
2017-11-03 20:22:11 +00:00
2019-01-20 17:47:39 +00:00
uint32_t offset1 = 0x20 ;
2017-10-16 23:54:27 +02:00
2017-10-17 21:49:01 +01:00
{
2019-01-20 17:47:39 +00:00
for ( ; ; )
2019-01-18 18:02:31 +00:00
{
2019-01-20 17:47:39 +00:00
if ( offset1 > = section1 . size ( ) )
{
break ;
}
uint16_t trId = * ( uint16_t * ) & section1 [ offset1 ] ;
2017-10-16 23:54:27 +02:00
2019-01-20 17:47:39 +00:00
char someString [ 200 ] ;
sprintf ( someString , " %str%04d.pcb " , collisionFilePath . c_str ( ) , trId ) ;
stringList . push_back ( std : : string ( someString ) ) ;
//std::cout << someString << "\n";
offset1 + = 0x20 ;
2017-10-16 23:54:27 +02:00
2019-01-18 18:02:31 +00:00
2019-01-20 17:47:39 +00:00
}
2017-10-16 23:54:27 +02:00
}
2019-01-20 17:47:39 +00:00
LGB_FILE bgLgb ( & section [ 0 ] ) ;
LGB_FILE planmapLgb ( & section2 [ 0 ] ) ;
2018-08-29 21:40:59 +02:00
2019-01-20 17:47:39 +00:00
std : : vector < LGB_FILE > lgbList { bgLgb , planmapLgb } ;
uint32_t max_index = 0 ;
int totalModels = 0 ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
2019-01-19 18:46:21 +00:00
2019-01-20 20:14:40 +00:00
auto buildModelEntry = [ & ] ( std : : shared_ptr < PCB_FILE > pPcbFile , ExportedGroup & exportedGroup ,
const std : : string & name , const std : : string & groupName ,
const vec3 * scale = nullptr ,
const vec3 * rotation = nullptr ,
const vec3 * translation = nullptr ,
const SGB_MODEL_ENTRY * pSgbEntry = nullptr )
{
auto & pcb_file = * pPcbFile . get ( ) ;
2019-01-20 17:47:39 +00:00
2019-01-20 20:14:40 +00:00
ExportedModel model ;
model . name = name + " _ " + std : : to_string ( totalModels + + ) ;
model . meshes . resize ( pcb_file . entries . size ( ) ) ;
2019-01-20 17:47:39 +00:00
2019-01-20 20:14:40 +00:00
uint32_t meshCount = 0 ;
for ( const auto & entry : pcb_file . entries )
{
ExportedMesh mesh ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
mesh . verts . resize ( ( entry . header . num_vertices + entry . header . num_v16 ) * 3 ) ;
mesh . indices . resize ( entry . header . num_indices * 3 ) ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
float x_base = abs ( float ( entry . header . x1 - entry . header . x ) ) ;
float y_base = abs ( float ( entry . header . y1 - entry . header . y ) ) ;
float z_base = abs ( float ( entry . header . z1 - entry . header . z ) ) ;
2019-01-18 18:02:31 +00:00
2019-01-20 20:14:40 +00:00
auto makeTranslation = [ & ] ( vec3 & v )
{
if ( pSgbEntry )
2019-01-20 17:47:39 +00:00
{
2019-01-20 20:14:40 +00:00
v . x * = pSgbEntry - > header . scale . x ;
v . y * = pSgbEntry - > header . scale . y ;
v . z * = pSgbEntry - > header . scale . z ;
2019-01-18 18:02:31 +00:00
2019-01-20 20:14:40 +00:00
v = v * matrix4 : : rotateX ( pSgbEntry - > header . rotation . x ) ;
v = v * matrix4 : : rotateY ( pSgbEntry - > header . rotation . y ) ;
v = v * matrix4 : : rotateZ ( pSgbEntry - > header . rotation . z ) ;
2019-01-19 18:46:21 +00:00
2019-01-20 20:14:40 +00:00
v . x + = pSgbEntry - > header . translation . x ;
v . y + = pSgbEntry - > header . translation . y ;
v . z + = pSgbEntry - > header . translation . z ;
}
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
if ( scale )
{
v . x * = scale - > x ;
v . y * = scale - > y ;
v . z * = scale - > z ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
v = v * matrix4 : : rotateX ( rotation - > x ) ;
v = v * matrix4 : : rotateY ( rotation - > y ) ;
v = v * matrix4 : : rotateZ ( rotation - > z ) ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
v . x + = translation - > x ;
v . y + = translation - > y ;
v . z + = translation - > z ;
}
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
} ;
int verts = 0 ;
int indices = 0 ;
2017-10-17 15:14:48 +01:00
2019-01-20 20:14:40 +00:00
for ( auto & vertex : entry . data . vertices )
{
vec3 v ( vertex . x , vertex . y , vertex . z ) ;
makeTranslation ( v ) ;
2017-10-20 22:50:21 +01:00
2019-01-20 20:14:40 +00:00
mesh . verts [ verts + + ] = v . x ;
mesh . verts [ verts + + ] = v . y ;
mesh . verts [ verts + + ] = v . z ;
}
2017-10-17 15:14:48 +01:00
2019-01-20 20:14:40 +00:00
for ( const auto & link : entry . data . vertices_i16 )
{
vec3 v ( float ( link . x ) / 0xFFFF , float ( link . y ) / 0xFFFF , float ( link . z ) / 0xFFFF ) ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
v . x = v . x * x_base + entry . header . x ;
v . y = v . y * y_base + entry . header . y ;
v . z = v . z * z_base + entry . header . z ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
makeTranslation ( v ) ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
mesh . verts [ verts + + ] = v . x ;
mesh . verts [ verts + + ] = v . y ;
mesh . verts [ verts + + ] = v . z ;
}
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
for ( const auto & index : entry . data . indices )
{
mesh . indices [ indices + + ] = index . index [ 0 ] ;
mesh . indices [ indices + + ] = index . index [ 1 ] ;
mesh . indices [ indices + + ] = index . index [ 2 ] ;
// std::cout << std::to_string( index.unknown[0] )<< " " << std::to_string( index.unknown[1] )<< " " << std::to_string( index.unknown[2]) << std::endl;
2018-08-29 21:40:59 +02:00
}
2019-01-20 20:14:40 +00:00
max_index + = entry . data . vertices . size ( ) + entry . data . vertices_i16 . size ( ) ;
model . meshes [ meshCount + + ] = mesh ;
2019-01-19 18:46:21 +00:00
}
2019-01-20 20:14:40 +00:00
exportedGroup . models [ model . name ] = model ;
} ;
ExportedGroup exportedTerrainGroup ;
exportedTerrainGroup . name = zoneName + " _terrain " ;
for ( const auto & fileName : stringList )
{
if ( auto pPcbFile = pCache - > getPcbFile ( fileName ) )
buildModelEntry ( pPcbFile , exportedTerrainGroup , fileName , zoneName ) ;
}
exportMgr . exportGroup ( zoneName , exportedTerrainGroup , ( ExportFileType ) exportFileType ) ;
2019-01-26 15:19:54 +11:00
exportedZone . groups . emplace ( zoneName , exportedTerrainGroup ) ;
2019-01-20 20:14:40 +00:00
for ( const auto & lgb : lgbList )
{
for ( const auto & group : lgb . groups )
2018-08-29 21:40:59 +02:00
{
2019-01-20 20:14:40 +00:00
ExportedGroup exportedGroup ;
exportedGroup . name = group . name ;
2019-01-20 17:47:39 +00:00
2019-01-20 20:14:40 +00:00
max_index = 0 ;
2019-01-20 17:47:39 +00:00
2019-01-20 20:14:40 +00:00
//std::cout << "\t" << group.name << " Size " << group.header.entryCount << "\n";
for ( const auto & pEntry : group . entries )
{
std : : string fileName ( " " ) ;
fileName . resize ( 256 ) ;
2019-01-19 18:46:21 +00:00
2019-01-20 20:14:40 +00:00
// write files
auto pcbTransformModel = [ & ] ( const std : : string & fileName , const vec3 * scale , const vec3 * rotation ,
const vec3 * translation , const SGB_MODEL_ENTRY * pModel = nullptr ) - > bool
{
if ( auto pPcbFile = pCache - > getPcbFile ( fileName ) )
2018-08-29 21:40:59 +02:00
{
2019-01-20 20:14:40 +00:00
buildModelEntry ( pPcbFile , exportedGroup , fileName , group . name , scale , rotation , translation , pModel ) ;
}
return true ;
} ;
2018-08-29 21:40:59 +02:00
2019-01-20 20:14:40 +00:00
switch ( pEntry - > getType ( ) )
{
case LgbEntryType : : BgParts :
2018-08-29 21:40:59 +02:00
{
2019-01-20 20:14:40 +00:00
auto pBgParts = static_cast < LGB_BGPARTS_ENTRY * > ( pEntry . get ( ) ) ;
fileName = pBgParts - > collisionFileName ;
pcbTransformModel ( fileName , & pBgParts - > header . scale , & pBgParts - > header . rotation ,
& pBgParts - > header . translation ) ;
}
break ;
2019-01-19 18:46:21 +00:00
2019-01-20 20:14:40 +00:00
// gimmick entry
case LgbEntryType : : Gimmick :
{
auto pGimmick = static_cast < LGB_GIMMICK_ENTRY * > ( pEntry . get ( ) ) ;
if ( auto pSgbFile = pCache - > getSgbFile ( pGimmick - > gimmickFileName ) )
2018-08-29 21:40:59 +02:00
{
2019-01-20 20:14:40 +00:00
const auto & sgbFile = * pSgbFile ;
for ( const auto & group : sgbFile . entries )
2019-01-19 18:46:21 +00:00
{
2019-01-20 20:14:40 +00:00
for ( const auto & pEntry : group . entries )
2019-01-19 18:46:21 +00:00
{
2019-01-20 20:14:40 +00:00
auto pModel = dynamic_cast < SGB_MODEL_ENTRY * > ( pEntry . get ( ) ) ;
fileName = pModel - > collisionFileName ;
pcbTransformModel ( fileName , & pGimmick - > header . scale , & pGimmick - > header . rotation ,
& pGimmick - > header . translation , pModel ) ;
2019-01-19 18:46:21 +00:00
}
2017-10-19 16:29:17 +01:00
}
2018-08-29 21:40:59 +02:00
}
2019-01-20 20:14:40 +00:00
}
2018-01-24 00:15:46 +00:00
2019-01-20 20:14:40 +00:00
case LgbEntryType : : EventObject :
{
pcbTransformModel ( fileName , & pEntry - > header . scale , & pEntry - > header . rotation , & pEntry - > header . translation ) ;
2019-01-19 18:46:21 +00:00
}
2019-01-20 20:14:40 +00:00
break ;
default :
break ;
2017-10-17 15:14:48 +01:00
}
2019-01-19 18:46:21 +00:00
}
2019-01-20 20:14:40 +00:00
exportMgr . exportGroup ( zoneName , exportedGroup , ( ExportFileType ) exportFileType ) ;
2019-01-26 15:19:54 +11:00
exportedZone . groups . emplace ( group . name , exportedGroup ) ;
2018-08-29 21:40:59 +02:00
}
2017-10-16 15:47:48 +01:00
}
2019-01-26 15:19:54 +11:00
exportMgr . exportZone ( exportedZone , ( ExportFileType ) exportFileType ) ;
2019-01-20 20:14:40 +00:00
2019-01-19 18:46:21 +00:00
2019-01-24 19:04:37 +11:00
printf ( " Exported %s in %lu seconds \n " ,
2019-01-20 20:14:40 +00:00
zoneName . c_str ( ) ,
2019-01-24 19:04:37 +11:00
std : : chrono : : duration_cast < std : : chrono : : seconds > ( std : : chrono : : high_resolution_clock : : now ( ) - entryStartTime ) . count ( ) ) ;
2019-01-20 17:47:39 +00:00
}
catch ( std : : exception & e )
{
2019-01-24 19:04:37 +11:00
printf ( " %s " , ( std : : string ( e . what ( ) ) + " \n " ) . c_str ( ) ) ;
2019-01-20 20:14:40 +00:00
printf ( " Unable to extract collision data. \n " ) ;
printf ( " Usage: pcb_reader2 territory \" path/to/game/sqpack/ffxiv \" \n " ) ;
2019-01-20 17:47:39 +00:00
}
2018-08-29 21:40:59 +02:00
}
2019-01-20 17:47:39 +00:00
exportMgr . waitForTasks ( ) ;
2018-08-29 21:40:59 +02:00
std : : cout < < " \n \n \n " ;
2019-01-19 18:46:21 +00:00
2019-01-24 19:04:37 +11:00
printf ( " Finished all tasks in %lu seconds \n " ,
2019-01-20 20:14:40 +00:00
std : : chrono : : duration_cast < std : : chrono : : seconds > ( std : : chrono : : high_resolution_clock : : now ( ) - startTime ) . count ( ) ) ;
2018-08-29 21:40:59 +02:00
2019-01-24 19:04:37 +11:00
delete eData ;
delete data1 ;
2018-08-29 21:40:59 +02:00
return 0 ;
2017-10-18 15:06:10 +01:00
}