2023-08-17 00:40:00 -06:00
#include "Companion.h"
2024-01-30 23:03:23 -06:00
#include "utils/Decompressor.h"
2024-03-24 21:27:07 -06:00
#include "utils/TorchUtils.h"
2025-01-09 00:15:16 +00:00
#include "archive/SWrapper.h"
#include "archive/ZWrapper.h"
2024-03-24 21:27:07 -06:00
#include "spdlog/spdlog.h"
#include "hj/sha1.h"
#include <regex>
#include <fstream>
#include <iostream>
#include <filesystem>
2024-03-12 11:25:51 -05:00
2024-04-01 02:37:33 +01:00
#include "factories/GenericArrayFactory.h"
2023-11-15 15:11:15 -06:00
#include "factories/VtxFactory.h"
2024-03-23 01:57:53 -06:00
#include "factories/MtxFactory.h"
#include "factories/FloatFactory.h"
2024-03-24 21:27:07 -06:00
#include "factories/IncludeFactory.h"
2023-08-17 00:40:00 -06:00
#include "factories/TextureFactory.h"
2023-11-15 15:11:15 -06:00
#include "factories/DisplayListFactory.h"
2024-03-09 23:12:39 -06:00
#include "factories/DisplayListOverrides.h"
2023-11-15 15:11:15 -06:00
#include "factories/BlobFactory.h"
#include "factories/LightsFactory.h"
2024-03-16 15:37:37 -05:00
#include "factories/Vec3fFactory.h"
2024-03-26 18:02:12 -05:00
#include "factories/Vec3sFactory.h"
2024-04-25 01:49:59 +01:00
#include "factories/AssetArrayFactory.h"
2024-10-21 16:49:32 +01:00
#include "factories/ViewportFactory.h"
2024-11-26 15:59:38 +00:00
#include "factories/CompressedTextureFactory.h"
2024-03-24 21:27:07 -06:00
2025-01-15 19:29:27 -06:00
#ifdef SM64_SUPPORT
2024-03-24 21:27:07 -06:00
#include "factories/sm64/AnimationFactory.h"
2024-04-25 17:32:49 +01:00
#include "factories/sm64/BehaviorScriptFactory.h"
2024-04-02 14:55:56 +01:00
#include "factories/sm64/CollisionFactory.h"
2024-03-24 21:27:07 -06:00
#include "factories/sm64/DialogFactory.h"
#include "factories/sm64/DictionaryFactory.h"
#include "factories/sm64/TextFactory.h"
#include "factories/sm64/GeoLayoutFactory.h"
2024-04-25 15:52:32 +01:00
#include "factories/sm64/LevelScriptFactory.h"
2024-04-23 14:38:15 +01:00
#include "factories/sm64/MacroFactory.h"
2024-04-08 11:47:11 +01:00
#include "factories/sm64/MovtexFactory.h"
#include "factories/sm64/MovtexQuadFactory.h"
2024-04-26 01:06:37 +01:00
#include "factories/sm64/PaintingFactory.h"
2024-04-23 17:38:58 +01:00
#include "factories/sm64/PaintingMapFactory.h"
2024-04-23 15:18:35 +01:00
#include "factories/sm64/TrajectoryFactory.h"
2024-04-25 22:52:09 +01:00
#include "factories/sm64/WaterDropletFactory.h"
2025-01-15 19:29:27 -06:00
#endif
2024-03-24 21:27:07 -06:00
2025-01-15 19:29:27 -06:00
#ifdef MK64_SUPPORT
2024-03-06 11:46:19 -07:00
#include "factories/mk64/CourseVtx.h"
2025-06-20 13:57:18 +00:00
#include "factories/mk64/Paths.h"
2024-03-06 11:46:19 -07:00
#include "factories/mk64/TrackSections.h"
#include "factories/mk64/SpawnData.h"
2024-08-13 20:56:03 +02:00
#include "factories/mk64/UnkSpawnData.h"
2024-03-23 01:57:53 -06:00
#include "factories/mk64/DrivingBehaviour.h"
2024-04-06 15:36:03 -06:00
#include "factories/mk64/ItemCurve.h"
#include "factories/mk64/CourseMetadata.h"
2025-01-15 19:29:27 -06:00
#endif
2023-08-17 00:40:00 -06:00
2025-01-15 19:29:27 -06:00
#ifdef SF64_SUPPORT
2024-03-13 15:56:30 -05:00
#include "factories/sf64/ColPolyFactory.h"
2024-03-10 20:15:27 -06:00
#include "factories/sf64/MessageFactory.h"
2024-03-10 21:22:26 -06:00
#include "factories/sf64/MessageLookupFactory.h"
2024-03-12 11:25:51 -05:00
#include "factories/sf64/SkeletonFactory.h"
#include "factories/sf64/AnimFactory.h"
#include "factories/sf64/ScriptFactory.h"
2024-03-12 17:16:29 -05:00
#include "factories/sf64/HitboxFactory.h"
2024-04-25 14:02:54 -05:00
#include "factories/sf64/EnvironmentFactory.h"
2024-03-12 21:43:59 -06:00
#include "factories/sf64/ObjInitFactory.h"
2024-03-13 17:49:25 -05:00
#include "factories/sf64/TriangleFactory.h"
2025-01-15 19:29:27 -06:00
#endif
2023-08-17 00:40:00 -06:00
2025-01-15 19:29:27 -06:00
#ifdef FZERO_SUPPORT
2025-01-02 05:02:43 +00:00
#include "factories/fzerox/CourseFactory.h"
2025-04-03 16:39:23 +01:00
#include "factories/fzerox/GhostRecordFactory.h"
2025-01-15 19:29:27 -06:00
#endif
2025-01-02 05:02:43 +00:00
2025-07-02 18:55:50 +01:00
#ifdef MARIO_ARTIST_SUPPORT
#include "factories/mario_artist/MA2D1Factory.h"
#endif
2025-01-15 19:29:27 -06:00
#ifdef NAUDIO_SUPPORT
2024-11-19 16:40:24 -06:00
#include "factories/naudio/v0/AudioHeaderFactory.h"
#include "factories/naudio/v0/BankFactory.h"
#include "factories/naudio/v0/SampleFactory.h"
#include "factories/naudio/v0/SequenceFactory.h"
#include "factories/naudio/v1/AudioContext.h"
#include "factories/naudio/v1/SoundFontFactory.h"
#include "factories/naudio/v1/AudioTableFactory.h"
#include "factories/naudio/v1/InstrumentFactory.h"
#include "factories/naudio/v1/SampleFactory.h"
#include "factories/naudio/v1/DrumFactory.h"
#include "factories/naudio/v1/EnvelopeFactory.h"
#include "factories/naudio/v1/LoopFactory.h"
#include "factories/naudio/v1/BookFactory.h"
2024-11-19 18:50:28 -06:00
#include "factories/naudio/v1/SequenceFactory.h"
2025-01-15 19:29:27 -06:00
#endif
2024-11-19 16:40:24 -06:00
2024-12-06 14:44:36 -06:00
#include "preprocess/CompTool.h"
2023-09-24 14:06:18 -06:00
using namespace std :: chrono ;
2023-11-15 15:11:15 -06:00
namespace fs = std :: filesystem ;
2023-08-17 00:40:00 -06:00
2023-11-15 15:11:15 -06:00
static const std :: string regular = "[%Y-%m-%d %H:%M:%S.%e] [%l] %v" ;
static const std :: string line = "[%Y-%m-%d %H:%M:%S.%e] [%l] > %v" ;
2025-07-08 21:56:08 +02:00
static std :: string ConvertType ( std :: string type ) {
int index = type . find ( ':' );
if ( index != std :: string :: npos ) {
type = type . substr ( index + 1 );
}
std :: transform ( type . begin (), type . end (), type . begin (), tolower );
return type ;
}
2025-07-09 01:17:02 +02:00
static std :: string GetTypeNode ( YAML :: Node & node ) {
2025-07-09 00:22:59 +02:00
auto type = GetSafeNode < std :: string > ( node , "type" );
std :: transform ( type . begin (), type . end (), type . begin (), toupper );
return type ;
}
2023-11-15 15:11:15 -06:00
void Companion :: Init ( const ExportType type ) {
2023-08-17 00:40:00 -06:00
2023-09-24 13:39:02 -06:00
spdlog :: set_level ( spdlog :: level :: debug );
spdlog :: set_pattern ( "[%Y-%m-%d %H:%M:%S.%e] [%l] %v" );
2024-01-30 01:25:46 -06:00
this -> gConfig . exporterType = type ;
2023-11-15 15:11:15 -06:00
this -> RegisterFactory ( "BLOB" , std :: make_shared < BlobFactory > ());
this -> RegisterFactory ( "TEXTURE" , std :: make_shared < TextureFactory > ());
this -> RegisterFactory ( "VTX" , std :: make_shared < VtxFactory > ());
2024-03-23 01:57:53 -06:00
this -> RegisterFactory ( "MTX" , std :: make_shared < MtxFactory > ());
this -> RegisterFactory ( "F32" , std :: make_shared < FloatFactory > ());
2024-03-24 21:27:07 -06:00
this -> RegisterFactory ( "INC" , std :: make_shared < IncludeFactory > ());
2023-11-15 15:11:15 -06:00
this -> RegisterFactory ( "LIGHTS" , std :: make_shared < LightsFactory > ());
this -> RegisterFactory ( "GFX" , std :: make_shared < DListFactory > ());
2024-03-16 15:37:37 -05:00
this -> RegisterFactory ( "VEC3F" , std :: make_shared < Vec3fFactory > ());
2024-03-26 18:02:12 -05:00
this -> RegisterFactory ( "VEC3S" , std :: make_shared < Vec3sFactory > ());
2024-04-01 02:37:33 +01:00
this -> RegisterFactory ( "ARRAY" , std :: make_shared < GenericArrayFactory > ());
2024-04-25 01:49:59 +01:00
this -> RegisterFactory ( "ASSET_ARRAY" , std :: make_shared < AssetArrayFactory > ());
2024-10-21 16:49:32 +01:00
this -> RegisterFactory ( "VP" , std :: make_shared < ViewportFactory > ());
2024-11-26 15:59:38 +00:00
this -> RegisterFactory ( "COMPRESSED_TEXTURE" , std :: make_shared < CompressedTextureFactory > ());
2023-09-17 23:55:59 -06:00
2025-01-15 19:29:27 -06:00
#ifdef SM64_SUPPORT
2023-11-15 15:11:15 -06:00
this -> RegisterFactory ( "SM64:DIALOG" , std :: make_shared < SM64 :: DialogFactory > ());
this -> RegisterFactory ( "SM64:TEXT" , std :: make_shared < SM64 :: TextFactory > ());
this -> RegisterFactory ( "SM64:DICTIONARY" , std :: make_shared < SM64 :: DictionaryFactory > ());
this -> RegisterFactory ( "SM64:ANIM" , std :: make_shared < SM64 :: AnimationFactory > ());
2024-04-25 18:28:44 +01:00
this -> RegisterFactory ( "SM64:BEHAVIOR_SCRIPT" , std :: make_shared < SM64 :: BehaviorScriptFactory > ());
2024-04-02 14:55:56 +01:00
this -> RegisterFactory ( "SM64:COLLISION" , std :: make_shared < SM64 :: CollisionFactory > ());
2023-11-21 00:27:23 -06:00
this -> RegisterFactory ( "SM64:GEO_LAYOUT" , std :: make_shared < SM64 :: GeoLayoutFactory > ());
2024-04-25 15:52:32 +01:00
this -> RegisterFactory ( "SM64:LEVEL_SCRIPT" , std :: make_shared < SM64 :: LevelScriptFactory > ());
2024-04-23 14:38:15 +01:00
this -> RegisterFactory ( "SM64:MACRO" , std :: make_shared < SM64 :: MacroFactory > ());
2024-04-08 11:47:11 +01:00
this -> RegisterFactory ( "SM64:MOVTEX_QUAD" , std :: make_shared < SM64 :: MovtexQuadFactory > ());
this -> RegisterFactory ( "SM64:MOVTEX" , std :: make_shared < SM64 :: MovtexFactory > ());
2024-04-26 01:06:37 +01:00
this -> RegisterFactory ( "SM64:PAINTING" , std :: make_shared < SM64 :: PaintingFactory > ());
2024-04-23 17:38:58 +01:00
this -> RegisterFactory ( "SM64:PAINTING_MAP" , std :: make_shared < SM64 :: PaintingMapFactory > ());
2024-04-23 15:18:35 +01:00
this -> RegisterFactory ( "SM64:TRAJECTORY" , std :: make_shared < SM64 :: TrajectoryFactory > ());
2024-04-25 22:52:09 +01:00
this -> RegisterFactory ( "SM64:WATER_DROPLET" , std :: make_shared < SM64 :: WaterDropletFactory > ());
2025-01-15 19:29:27 -06:00
#endif
2023-09-17 23:55:59 -06:00
2025-01-15 19:29:27 -06:00
#ifdef MK64_SUPPORT
2024-03-13 10:21:41 -06:00
this -> RegisterFactory ( "MK64:COURSE_VTX" , std :: make_shared < MK64 :: CourseVtxFactory > ());
2025-06-20 13:57:18 +00:00
this -> RegisterFactory ( "MK64:TRACK_PATH" , std :: make_shared < MK64 :: PathsFactory > ());
2024-03-13 10:21:41 -06:00
this -> RegisterFactory ( "MK64:TRACK_SECTIONS" , std :: make_shared < MK64 :: TrackSectionsFactory > ());
this -> RegisterFactory ( "MK64:SPAWN_DATA" , std :: make_shared < MK64 :: SpawnDataFactory > ());
2024-08-13 20:56:03 +02:00
this -> RegisterFactory ( "MK64:UNK_SPAWN_DATA" , std :: make_shared < MK64 :: UnkSpawnDataFactory > ());
2024-03-23 01:57:53 -06:00
this -> RegisterFactory ( "MK64:DRIVING_BEHAVIOUR" , std :: make_shared < MK64 :: DrivingBehaviourFactory > ());
2024-04-06 15:36:03 -06:00
this -> RegisterFactory ( "MK64:ITEM_CURVE" , std :: make_shared < MK64 :: ItemCurveFactory > ()); // Item curve for decomp only
this -> RegisterFactory ( "MK64:METADATA" , std :: make_shared < MK64 :: CourseMetadataFactory > ());
2025-01-15 19:29:27 -06:00
#endif
2023-09-21 09:52:11 -06:00
2025-01-15 19:29:27 -06:00
#ifdef SF64_SUPPORT
2024-03-10 20:43:44 -05:00
this -> RegisterFactory ( "SF64:ANIM" , std :: make_shared < SF64 :: AnimFactory > ());
2024-03-11 10:58:08 -05:00
this -> RegisterFactory ( "SF64:SKELETON" , std :: make_shared < SF64 :: SkeletonFactory > ());
2024-03-10 20:15:27 -06:00
this -> RegisterFactory ( "SF64:MESSAGE" , std :: make_shared < SF64 :: MessageFactory > ());
2024-03-10 21:22:26 -06:00
this -> RegisterFactory ( "SF64:MSG_TABLE" , std :: make_shared < SF64 :: MessageLookupFactory > ());
2024-03-12 11:25:51 -05:00
this -> RegisterFactory ( "SF64:SCRIPT" , std :: make_shared < SF64 :: ScriptFactory > ());
2024-03-12 17:16:29 -05:00
this -> RegisterFactory ( "SF64:HITBOX" , std :: make_shared < SF64 :: HitboxFactory > ());
2024-04-25 14:02:54 -05:00
this -> RegisterFactory ( "SF64:ENVIRONMENT" , std :: make_shared < SF64 :: EnvironmentFactory > ());
2024-03-12 21:43:59 -06:00
this -> RegisterFactory ( "SF64:OBJECT_INIT" , std :: make_shared < SF64 :: ObjInitFactory > ());
2024-03-13 15:56:30 -05:00
this -> RegisterFactory ( "SF64:COLPOLY" , std :: make_shared < SF64 :: ColPolyFactory > ());
2024-03-13 17:49:25 -05:00
this -> RegisterFactory ( "SF64:TRIANGLE" , std :: make_shared < SF64 :: TriangleFactory > ());
2025-01-15 19:29:27 -06:00
#endif
2024-03-12 11:25:51 -05:00
2025-01-15 19:29:27 -06:00
#ifdef FZERO_SUPPORT
2025-01-02 05:02:43 +00:00
this -> RegisterFactory ( "FZX:COURSE" , std :: make_shared < FZX :: CourseFactory > ());
2025-04-03 16:39:23 +01:00
this -> RegisterFactory ( "FZX:GHOST" , std :: make_shared < FZX :: GhostRecordFactory > ());
2025-01-15 19:29:27 -06:00
#endif
2025-01-02 05:02:43 +00:00
2025-07-02 18:55:50 +01:00
#ifdef MARIO_ARTIST_SUPPORT
this -> RegisterFactory ( "MA:MA2D1" , std :: make_shared < MA :: MA2D1Factory > ());
#endif
2025-01-15 19:29:27 -06:00
#ifdef NAUDIO_SUPPORT
2024-11-19 16:40:24 -06:00
this -> RegisterFactory ( "NAUDIO:V0:AUDIO_HEADER" , std :: make_shared < AudioHeaderFactory > ());
this -> RegisterFactory ( "NAUDIO:V0:SEQUENCE" , std :: make_shared < SequenceFactory > ());
this -> RegisterFactory ( "NAUDIO:V0:SAMPLE" , std :: make_shared < SampleFactory > ());
this -> RegisterFactory ( "NAUDIO:V0:BANK" , std :: make_shared < BankFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:AUDIO_SETUP" , std :: make_shared < AudioContextFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:AUDIO_TABLE" , std :: make_shared < AudioTableFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:SOUND_FONT" , std :: make_shared < SoundFontFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:INSTRUMENT" , std :: make_shared < InstrumentFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:DRUM" , std :: make_shared < DrumFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:SAMPLE" , std :: make_shared < NSampleFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:ENVELOPE" , std :: make_shared < EnvelopeFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:ADPCM_LOOP" , std :: make_shared < ADPCMLoopFactory > ());
this -> RegisterFactory ( "NAUDIO:V1:ADPCM_BOOK" , std :: make_shared < ADPCMBookFactory > ());
2024-11-19 18:50:28 -06:00
this -> RegisterFactory ( "NAUDIO:V1:SEQUENCE" , std :: make_shared < NSequenceFactory > ());
2025-01-15 19:29:27 -06:00
#endif
2024-11-19 16:40:24 -06:00
2023-09-17 23:55:59 -06:00
this -> Process ();
}
2024-03-12 21:43:59 -06:00
void Companion :: ParseEnums ( std :: string & header ) {
std :: ifstream file ( header );
if ( ! file . is_open ()) {
2024-04-06 15:36:03 -06:00
throw std :: runtime_error ( "Failed to open header files for enums node in config" );
2024-03-12 21:43:59 -06:00
}
std :: regex enumRegex ( R "(enum\s+(\w+)\s*(?:\s*:\s*(\w+))?[\s \n\r ]*\{)" );
std :: string line ;
std :: smatch match ;
std :: string enumName ;
bool inEnum = false ;
2024-03-13 15:56:30 -05:00
int enumIndex ;
2024-03-12 21:43:59 -06:00
while ( std :: getline ( file , line )) {
if ( ! inEnum && std :: regex_search ( line , match , enumRegex ) && match . size () > 1 ) {
enumName = match . str ( 1 );
inEnum = true ;
2024-03-13 15:56:30 -05:00
enumIndex = - 1 ;
2024-03-12 21:43:59 -06:00
continue ;
}
if ( ! inEnum ) {
continue ;
}
2024-05-15 19:16:26 -06:00
if ( line . find ( '}' ) != std :: string :: npos ) {
2024-03-12 21:43:59 -06:00
inEnum = false ;
continue ;
}
// Remove any comments and non-alphanumeric characters
2024-03-13 15:56:30 -05:00
line = std :: regex_replace ( line , std :: regex ( R "((/\*.*?\*/)|(//.*$)|([^a-zA-Z0-9=_\-\.]))" ), "" );
2024-03-12 21:43:59 -06:00
2024-05-15 19:16:26 -06:00
if ( line . find ( '=' ) != std :: string :: npos ) {
auto value = line . substr ( line . find ( '=' ) + 1 );
auto name = line . substr ( 0 , line . find ( '=' ));
enumIndex = static_cast < int32_t > ( std :: stoll ( value , nullptr , 0 ));
2024-03-13 15:56:30 -05:00
this -> gEnums [ enumName ][ enumIndex ] = name ;
2024-03-12 21:43:59 -06:00
} else {
2024-03-13 15:56:30 -05:00
enumIndex ++ ;
this -> gEnums [ enumName ][ enumIndex ] = line ;
2024-03-12 21:43:59 -06:00
}
2024-03-20 21:17:11 -06:00
2024-03-12 21:43:59 -06:00
}
}
2024-03-31 10:58:38 -06:00
std :: optional < ParseResultData > Companion :: ParseNode ( YAML :: Node & node , std :: string & name ) {
2025-07-09 00:22:59 +02:00
auto type = GetTypeNode ( node );
2023-11-15 15:11:15 -06:00
2024-01-31 11:17:29 -06:00
spdlog :: set_pattern ( regular );
2023-11-21 00:27:23 -06:00
if ( node [ "offset" ]) {
auto offset = node [ "offset" ]. as < uint32_t > ();
2024-01-31 11:17:29 -06:00
SPDLOG_INFO ( "- [{}] Processing {} at 0x{:X}" , type , name , offset );
2023-11-21 00:27:23 -06:00
} else {
2024-01-31 11:17:29 -06:00
SPDLOG_INFO ( "- [{}] Processing {}" , type , name );
2023-11-21 00:27:23 -06:00
}
2024-01-31 11:17:29 -06:00
spdlog :: set_pattern ( line );
2024-05-15 19:16:26 -06:00
node [ "vpath" ] = name ;
2023-11-21 00:27:23 -06:00
2023-11-15 15:11:15 -06:00
auto factory = this -> GetFactory ( type );
if ( ! factory . has_value ()){
2024-03-24 21:27:07 -06:00
throw std :: runtime_error ( "No factory by the name '" + type + "' found for '" + name + "'" );
2023-11-15 15:11:15 -06:00
}
2024-02-21 12:51:47 -06:00
auto impl = factory -> get ();
2024-03-15 09:16:22 -06:00
auto exporter = impl -> GetExporter ( this -> gConfig . exporterType );
2024-05-15 19:16:26 -06:00
if ( ! exporter . has_value () && ! impl -> HasModdedDependencies ()){
2024-03-15 09:16:22 -06:00
SPDLOG_WARN ( "No exporter found for {}" , name );
2024-03-31 10:58:38 -06:00
return std :: nullopt ;
2024-03-15 09:16:22 -06:00
}
2024-05-15 19:16:26 -06:00
bool executeDef = true ;
2024-02-21 12:51:47 -06:00
std :: optional < std :: shared_ptr < IParsedData >> result ;
2024-03-29 03:02:20 -06:00
if ( this -> gConfig . modding && impl -> SupportModdedAssets () && this -> gModdedAssetPaths . contains ( name )) {
auto path = fs :: path ( this -> gConfig . moddingPath ) / this -> gModdedAssetPaths [ name ];
2024-05-15 19:16:26 -06:00
if ( ! exists ( path )) {
2024-03-29 03:02:20 -06:00
SPDLOG_ERROR ( "Modded asset {} not found" , this -> gModdedAssetPaths [ name ]);
2024-05-15 19:16:26 -06:00
} else {
std :: ifstream input ( path , std :: ios :: binary );
std :: vector < uint8_t > data = std :: vector < uint8_t > ( std :: istreambuf_iterator ( input ), {});
input . close ();
result = impl -> parse_modding ( data , node );
executeDef = ! result . has_value ();
2024-03-29 03:02:20 -06:00
}
2024-05-15 19:16:26 -06:00
}
2024-02-21 12:51:47 -06:00
2024-05-15 19:16:26 -06:00
if ( executeDef && this -> gConfig . parseMode == ParseMode :: Default ) {
2024-03-29 03:02:20 -06:00
result = impl -> parse ( this -> gRomData , node );
2024-05-15 19:16:26 -06:00
}
if ( executeDef && this -> gConfig . parseMode == ParseMode :: Directory ) {
2024-04-29 12:58:04 -06:00
auto path = GetSafeNode < std :: string > ( node , "path" );
std :: ifstream input ( path , std :: ios :: binary );
auto data = std :: vector < uint8_t > ( std :: istreambuf_iterator ( input ), {} );
result = impl -> parse ( data , node );
input . close ();
2024-02-21 12:51:47 -06:00
}
2024-03-29 03:02:20 -06:00
2023-11-15 15:11:15 -06:00
if ( ! result . has_value ()){
SPDLOG_ERROR ( "Failed to process {}" , name );
2024-03-31 10:58:38 -06:00
return std :: nullopt ;
2023-11-15 15:11:15 -06:00
}
SPDLOG_INFO ( "Processed {}" , name );
2024-03-31 10:58:38 -06:00
return ParseResultData {
name , type , node , result
};
2023-11-15 15:11:15 -06:00
}
2024-02-21 12:51:47 -06:00
void Companion :: ParseModdingConfig () {
auto path = fs :: path ( this -> gConfig . moddingPath ) / "modding.yml" ;
if ( ! fs :: exists ( path )) {
throw std :: runtime_error ( "No modding config found, please run in export mode first" );
}
2024-02-21 21:05:28 -06:00
auto modding = YAML :: LoadFile ( path . string ());
2024-02-21 12:51:47 -06:00
for ( auto assets = modding [ "assets" ]. begin (); assets != modding [ "assets" ]. end (); ++ assets ) {
auto name = assets -> first . as < std :: string > ();
auto asset = assets -> second . as < std :: string > ();
this -> gModdedAssetPaths [ name ] = asset ;
}
}
2024-01-30 01:25:46 -06:00
void Companion :: ParseCurrentFileConfig ( YAML :: Node node ) {
2024-04-23 11:49:56 +08:00
if ( node [ "external_files" ]) {
auto externalFiles = node [ "external_files" ];
if ( externalFiles . IsSequence () && externalFiles . size ()) {
for ( size_t i = 0 ; i < externalFiles . size (); i ++ ) {
auto externalFile = externalFiles [ i ];
if ( externalFile . size () == 0 ) {
this -> gCurrentExternalFiles . push_back ( externalFile . as < std :: string > ());
} else {
SPDLOG_INFO ( "External File size {}" , externalFile . size ());
throw std :: runtime_error ( "Incorrect yaml syntax for external files. \n\n The yaml expects: \n :config: \n external_files: \n - <external_files> \n\n e.g.: \n external_files: \n - actors/actor1.yaml" );
}
auto externalFileName = externalFile . as < std :: string > ();
if ( std :: filesystem :: relative ( externalFileName , this -> gAssetPath ). string (). starts_with ( "../" )) {
throw std :: runtime_error ( "External File " + externalFileName + " Not In Asset Directory " + this -> gAssetPath );
} else if ( std :: filesystem :: relative ( externalFileName , this -> gAssetPath ). string () == "" ) {
throw std :: runtime_error ( "External File " + externalFileName + " Not In Asset Directory " + this -> gAssetPath );
}
if ( ! this -> gAddrMap . contains ( externalFileName )) {
SPDLOG_INFO ( "Dependency on external file {}. Now processing {}" , externalFileName , externalFileName );
auto currentFile = this -> gCurrentFile ;
auto currentDirectory = this -> gCurrentDirectory ;
auto currentExternalFiles = this -> gCurrentExternalFiles ;
this -> gCurrentFile = externalFileName ;
this -> gCurrentDirectory = std :: filesystem :: relative ( externalFileName , this -> gAssetPath ). replace_extension ( "" );
YAML :: Node root = YAML :: LoadFile ( externalFileName );
2024-05-25 09:32:22 -06:00
if ( ! this -> gProcessedFiles . contains ( this -> gCurrentFile )) {
ProcessFile ( root );
this -> gProcessedFiles . insert ( this -> gCurrentFile );
}
2024-04-23 11:49:56 +08:00
SPDLOG_INFO ( "Finishing processing of file: {}" , currentFile );
this -> gCurrentFile = currentFile ;
this -> gCurrentDirectory = currentDirectory ;
this -> gCurrentExternalFiles = currentExternalFiles ;
this -> gFileHeader . clear ();
}
}
}
}
2024-01-30 01:25:46 -06:00
if ( node [ "segments" ]) {
2024-03-08 13:24:58 -07:00
auto segments = node [ "segments" ];
// Set global variables for segmented data
if ( segments . IsSequence () && segments . size ()) {
if ( segments [ 0 ]. IsSequence () && segments [ 0 ]. size () == 2 ) {
gCurrentSegmentNumber = segments [ 0 ][ 0 ]. as < uint32_t > ();
gCurrentFileOffset = segments [ 0 ][ 1 ]. as < uint32_t > ();
2024-10-11 13:54:38 -06:00
gCurrentCompressionType = Decompressor :: GetCompressionType ( this -> gRomData , gCurrentFileOffset );
2024-05-03 13:56:40 -06:00
if ( node [ "no_compression" ]) {
gCurrentCompressionType = CompressionType :: None ;
}
2024-03-08 13:24:58 -07:00
} else {
2024-03-09 22:42:25 -07:00
throw std :: runtime_error ( "Incorrect yaml syntax for segments. \n\n The yaml expects: \n :config: \n segments: \n - [<segment>, <file_offset>] \n\n Like so: \n segments: \n - [0x06, 0x821D10]" );
2024-03-08 13:24:58 -07:00
}
}
// Set file offset for later use.
for ( size_t i = 0 ; i < segments . size (); i ++ ) {
auto segment = segments [ i ];
if ( segment . IsSequence () && segment . size () == 2 ) {
const auto id = segment [ 0 ]. as < uint32_t > ();
const auto replacement = segment [ 1 ]. as < uint32_t > ();
this -> gConfig . segment . local [ id ] = replacement ;
SPDLOG_DEBUG ( "Segment {} replaced with 0x{:X}" , id , replacement );
} else {
2024-03-09 22:42:25 -07:00
throw std :: runtime_error ( "Incorrect yaml syntax for segments. \n\n The yaml expects: \n :config: \n segments: \n - [<segment>, <file_offset>] \n\n Like so: \n segments: \n - [0x06, 0x821D10]" );
2024-03-08 13:24:58 -07:00
}
2024-01-30 01:25:46 -06:00
}
}
2024-03-10 23:41:36 -06:00
2025-06-19 12:21:07 +01:00
if ( node [ "virtual" ]) {
auto virtualAddrMap = node [ "virtual" ];
gVirtualAddrMap [ gCurrentFile ] = std :: make_tuple < uint32_t , uint32_t > ( virtualAddrMap [ 0 ]. as < uint32_t > (), virtualAddrMap [ 1 ]. as < uint32_t > ());
}
2024-02-16 22:52:11 -06:00
if ( node [ "header" ]) {
2024-02-18 16:54:21 -06:00
auto header = node [ "header" ];
switch ( this -> gConfig . exporterType ) {
case ExportType :: Header : {
if ( header [ "header" ]. IsSequence ()) {
for ( auto line = header [ "header" ]. begin (); line != header [ "header" ]. end (); ++ line ) {
this -> gFileHeader += line -> as < std :: string > () + " \n " ;
}
}
break ;
}
case ExportType :: Code : {
if ( header [ "code" ]. IsSequence ()) {
for ( auto line = header [ "code" ]. begin (); line != header [ "code" ]. end (); ++ line ) {
this -> gFileHeader += line -> as < std :: string > () + " \n " ;
}
}
break ;
}
default : break ;
2024-02-16 22:52:11 -06:00
}
}
2024-03-10 23:41:36 -06:00
if ( node [ "tables" ]){
for ( auto table = node [ "tables" ]. begin (); table != node [ "tables" ]. end (); ++ table ){
auto name = table -> first . as < std :: string > ();
auto range = table -> second [ "range" ]. as < std :: vector < uint32_t >> ();
auto start = gCurrentSegmentNumber ? gCurrentSegmentNumber << 24 | range [ 0 ] : range [ 0 ];
auto end = gCurrentSegmentNumber ? gCurrentSegmentNumber << 24 | range [ 1 ] : range [ 1 ];
auto mode = GetSafeNode < std :: string > ( table -> second , "mode" , "APPEND" );
TableMode tMode = mode == "REFERENCE" ? TableMode :: Reference : TableMode :: Append ;
2024-04-20 18:19:55 +08:00
auto index_size = GetSafeNode < int32_t > ( table -> second , "index_size" , - 1 );
this -> gTables . push_back ({ name , start , end , tMode , index_size });
2024-03-10 23:41:36 -06:00
}
}
2024-03-12 19:09:40 -06:00
if ( node [ "vram" ]){
auto vram = node [ "vram" ];
const auto addr = GetSafeNode < uint32_t > ( vram , "addr" );
const auto offset = GetSafeNode < uint32_t > ( vram , "offset" );
this -> gCurrentVram = { addr , offset };
}
2024-03-20 21:17:11 -06:00
this -> gEnablePadGen = GetSafeNode < bool > ( node , "autopads" , true );
2024-03-29 01:07:00 -06:00
this -> gNodeForceProcessing = GetSafeNode < bool > ( node , "force" , false );
2024-04-19 19:26:39 +08:00
this -> gIndividualIncludes = GetSafeNode < bool > ( node , "individual_data_incs" , false );
2024-05-15 19:16:26 -06:00
this -> gCurrentVirtualPath = GetSafeNode < std :: string > ( node , "path" , "" );
2024-03-29 03:02:20 -06:00
}
2024-03-29 01:07:00 -06:00
void Companion :: ParseHash () {
const std :: string out = "torch.hash.yml" ;
2024-03-29 03:02:56 -06:00
if ( fs :: exists ( out )) {
2024-03-29 01:07:00 -06:00
this -> gHashNode = YAML :: LoadFile ( out );
2024-03-29 03:02:56 -06:00
} else {
2024-03-29 01:07:00 -06:00
this -> gHashNode = YAML :: Node ();
2024-03-29 03:02:56 -06:00
}
}
2024-03-29 01:07:00 -06:00
std :: string ExportTypeToString ( ExportType type ) {
switch ( type ) {
case ExportType :: Binary : return "Binary" ;
case ExportType :: Header : return "Header" ;
case ExportType :: Code : return "Code" ;
case ExportType :: Modding : return "Modding" ;
2024-11-24 03:00:38 -06:00
case ExportType :: XML : return "XML" ;
2024-03-29 01:07:00 -06:00
default :
throw std :: runtime_error ( "Invalid ExportType" );
}
}
bool Companion :: NodeHasChanges ( const std :: string & path ) {
2024-03-29 01:28:24 -06:00
if ( this -> gConfig . modding ) {
2024-03-29 01:07:00 -06:00
return true ;
}
2024-03-29 03:02:20 -06:00
std :: ifstream yaml ( path );
const std :: vector < uint8_t > data = std :: vector < uint8_t > ( std :: istreambuf_iterator ( yaml ), {});
2024-03-29 01:07:00 -06:00
this -> gCurrentHash = CalculateHash ( data );
2024-03-29 01:28:24 -06:00
bool needsInit = true ;
2024-03-29 03:02:20 -06:00
2024-03-29 01:07:00 -06:00
if ( this -> gHashNode [ path ]) {
auto entry = GetSafeNode < YAML :: Node > ( this -> gHashNode , path );
2024-05-15 20:45:55 -06:00
const auto hash = GetSafeNode < std :: string > ( entry , "hash" , "no-hash" );
2024-03-29 01:07:00 -06:00
auto modes = GetSafeNode < YAML :: Node > ( entry , "extracted" );
auto extracted = GetSafeNode < bool > ( modes , ExportTypeToString ( this -> gConfig . exporterType ));
2024-03-28 23:38:32 -06:00
2024-03-29 01:28:24 -06:00
if ( hash == this -> gCurrentHash ) {
needsInit = false ;
if ( extracted ) {
SPDLOG_INFO ( "Skipping {} as it has not changed" , path );
return false ;
}
2024-03-29 03:02:20 -06:00
}
}
2024-03-29 01:28:24 -06:00
if ( needsInit ) {
this -> gHashNode [ path ] = YAML :: Node ();
this -> gHashNode [ path ][ "hash" ] = this -> gCurrentHash ;
this -> gHashNode [ path ][ "extracted" ] = YAML :: Node ();
for ( size_t m = 0 ; m <= static_cast < size_t > ( ExportType :: Modding ); m ++ ) {
this -> gHashNode [ path ][ "extracted" ][ ExportTypeToString ( static_cast < ExportType > ( m ))] = false ;
}
2024-03-28 23:38:32 -06:00
}
2024-03-29 01:07:00 -06:00
return true ;
2024-01-30 01:25:46 -06:00
}
2024-04-06 15:36:03 -06:00
void Companion :: LoadYAMLRecursively ( const std :: string & dirPath , std :: vector < YAML :: Node > & result , bool skipRoot ) {
for ( const auto & entry : std :: filesystem :: directory_iterator ( dirPath )) {
if ( entry . is_directory ()) {
// Skip the root directory if specified
if ( skipRoot && entry . path () == dirPath ) {
continue ;
}
// Recursive call for subdirectories
2024-07-28 18:51:16 -06:00
LoadYAMLRecursively ( entry . path (). generic_string (), result , false );
2024-04-06 15:36:03 -06:00
} else if ( entry . path (). extension () == ".yaml" || entry . path (). extension () == ".yml" ) {
// Load YAML file and add it to the result vector
2024-07-28 18:51:16 -06:00
result . push_back ( YAML :: LoadFile ( entry . path (). generic_string ()));
2024-04-06 15:36:03 -06:00
}
}
}
/**
* Config yaml requires tables: [assets/courses]
* Activate the factory using a normal asset yaml with type, input_directory, and output_directory nodes.
*/
void Companion :: ProcessTables ( YAML :: Node & rom ) {
auto dirs = rom [ "metadata" ]. as < std :: vector < std :: string >> ();
for ( const auto & dir : dirs ) {
std :: vector < YAML :: Node > configNodes ;
LoadYAMLRecursively ( dir , configNodes , true );
gCourseMetadata [ dir ] = configNodes ;
}
// Write yaml data to console
if ( this -> IsDebug ()) {
SPDLOG_INFO ( "------ Metadata ouptut ------" );
for ( auto & node : gCourseMetadata [ dirs [ 0 ]]) {
std :: cout << node << std :: endl ;
SPDLOG_INFO ( "------------" );
}
SPDLOG_INFO ( "------ Metadata end ------" );
}
}
2024-04-20 19:20:12 +01:00
void Companion :: ProcessFile ( YAML :: Node root ) {
// Set compressed file offsets and compression type
if ( auto segments = root [ ":config" ][ "segments" ]) {
if ( segments . IsSequence () && segments . size () > 0 ) {
if ( segments [ 0 ]. IsSequence () && segments [ 0 ]. size () == 2 ) {
gCurrentSegmentNumber = segments [ 0 ][ 0 ]. as < uint32_t > ();
gCurrentFileOffset = segments [ 0 ][ 1 ]. as < uint32_t > ();
2024-10-11 13:54:38 -06:00
gCurrentCompressionType = Decompressor :: GetCompressionType ( this -> gRomData , gCurrentFileOffset );
2024-05-03 13:56:40 -06:00
if ( root [ ":config" ][ "no_compression" ]) {
gCurrentCompressionType = CompressionType :: None ;
}
2024-04-20 19:20:12 +01:00
} else {
throw std :: runtime_error ( "Incorrect yaml syntax for segments. \n\n The yaml expects: \n :config: \n segments: \n - [<segment>, <file_offset>] \n\n Like so: \n segments: \n - [0x06, 0x821D10]" );
}
}
}
for ( auto asset = root . begin (); asset != root . end (); ++ asset ){
auto node = asset -> second ;
auto entryName = asset -> first . as < std :: string > ();
2024-11-24 03:00:38 -06:00
auto output = ( this -> gCurrentDirectory / entryName ). string ();
std :: replace ( output . begin (), output . end (), '\\' , '/' );
2024-04-20 19:20:12 +01:00
2024-11-24 03:00:38 -06:00
if ( node [ "type" ]){
2025-07-09 00:22:59 +02:00
const auto type = GetTypeNode ( node );
2024-12-25 00:43:20 -06:00
if ( type == "NAUDIO:V0:SAMPLE" ){
2024-11-24 03:00:38 -06:00
AudioManager :: Instance -> bind_sample ( node , output );
2024-04-20 19:20:12 +01:00
}
}
2024-11-24 03:00:38 -06:00
if ( ! node [ "offset" ]) {
continue ;
}
if ( gCurrentSegmentNumber ) {
if ( IS_SEGMENTED ( node [ "offset" ]. as < uint32_t > ()) == false ) {
node [ "offset" ] = ( gCurrentSegmentNumber << 24 ) | node [ "offset" ]. as < uint32_t > ();
}
}
if ( ! gCurrentVirtualPath . empty ()) {
node [ "path" ] = gCurrentVirtualPath ;
}
this -> gAddrMap [ this -> gCurrentFile ][ node [ "offset" ]. as < uint32_t > ()] = std :: make_tuple ( output , node );
2024-04-20 19:20:12 +01:00
}
// Stupid hack because the iteration broke the assets
root = YAML :: LoadFile ( this -> gCurrentFile );
this -> gConfig . segment . local . clear ();
this -> gFileHeader . clear ();
this -> gCurrentPad = 0 ;
this -> gCurrentVram = std :: nullopt ;
2024-05-15 19:16:26 -06:00
this -> gCurrentVirtualPath = "" ;
2024-04-20 19:20:12 +01:00
this -> gCurrentSegmentNumber = 0 ;
this -> gCurrentCompressionType = CompressionType :: None ;
this -> gCurrentFileOffset = 0 ;
this -> gTables . clear ();
this -> gCurrentExternalFiles . clear ();
GFXDOverride :: ClearVtx ();
if ( root [ ":config" ]) {
this -> ParseCurrentFileConfig ( root [ ":config" ]);
}
if ( ! this -> NodeHasChanges ( this -> gCurrentFile ) && ! this -> gNodeForceProcessing ) {
return ;
}
spdlog :: set_pattern ( regular );
SPDLOG_INFO ( "------------------------------------------------" );
spdlog :: set_pattern ( line );
for ( auto asset = root . begin (); asset != root . end (); ++ asset ){
auto entryName = asset -> first . as < std :: string > ();
auto assetNode = asset -> second ;
if ( entryName . find ( ":config" ) != std :: string :: npos ) {
continue ;
}
2024-11-24 03:00:38 -06:00
if ( gCurrentFileOffset && assetNode [ "offset" ]) {
const auto offset = assetNode [ "offset" ]. as < uint32_t > ();
if ( ! IS_SEGMENTED ( offset )) {
assetNode [ "offset" ] = ( gCurrentSegmentNumber << 24 ) | offset ;
2024-04-20 19:20:12 +01:00
}
2024-11-24 03:00:38 -06:00
}
2024-05-15 19:16:26 -06:00
2024-11-24 03:00:38 -06:00
if ( ! gCurrentVirtualPath . empty ()) {
assetNode [ "path" ] = gCurrentVirtualPath ;
}
2024-05-15 19:16:26 -06:00
2024-11-24 03:00:38 -06:00
std :: string output = ( this -> gCurrentDirectory / entryName ). string ();
std :: replace ( output . begin (), output . end (), '\\' , '/' );
this -> gConfig . segment . temporal . clear ();
auto result = this -> ParseNode ( assetNode , output );
if ( result . has_value ()) {
this -> gParseResults [ this -> gCurrentFile ]. push_back ( result . value ());
2024-04-20 19:20:12 +01:00
}
spdlog :: set_pattern ( regular );
SPDLOG_INFO ( "------------------------------------------------" );
spdlog :: set_pattern ( line );
}
for ( auto & result : this -> gParseResults [ this -> gCurrentFile ]){
std :: ostringstream stream ;
ExportResult endptr = std :: nullopt ;
WriteEntry wEntry ;
auto data = result . data . value ();
const auto impl = this -> GetFactory ( result . type ) -> get ();
const auto exporter = impl -> GetExporter ( this -> gConfig . exporterType );
2024-05-15 19:16:26 -06:00
if ( ! exporter . has_value ()) {
2024-04-20 19:20:12 +01:00
continue ;
}
switch ( this -> gConfig . exporterType ) {
case ExportType :: Binary : {
stream . str ( "" );
stream . clear ();
exporter -> get () -> Export ( stream , data , result . name , result . node , & result . name );
2024-05-07 22:00:25 -06:00
auto data = stream . str ();
2025-01-15 19:29:27 -06:00
this -> gCurrentWrapper -> AddFile ( result . name , std :: vector ( data . begin (), data . end ()));
2024-11-24 03:00:38 -06:00
for ( auto & entry : this -> gCompanionFiles ){
auto output = ( this -> gCurrentDirectory / entry . first ). string ();
std :: replace ( output . begin (), output . end (), '\\' , '/' );
2025-01-15 19:29:27 -06:00
this -> gCurrentWrapper -> AddFile ( output , entry . second );
2024-11-24 03:00:38 -06:00
}
2024-04-20 19:20:12 +01:00
break ;
}
2024-11-24 03:00:38 -06:00
case ExportType :: XML :
2024-04-20 19:20:12 +01:00
case ExportType :: Modding : {
stream . str ( "" );
stream . clear ();
std :: string ogname = result . name ;
exporter -> get () -> Export ( stream , data , result . name , result . node , & result . name );
auto data = stream . str ();
if ( data . empty ()) {
break ;
}
std :: string dpath = Instance -> GetOutputPath () + "/" + result . name ;
if ( ! exists ( fs :: path ( dpath ). parent_path ())){
create_directories ( fs :: path ( dpath ). parent_path ());
}
this -> gModdedAssetPaths [ ogname ] = result . name ;
std :: ofstream file ( dpath , std :: ios :: binary );
file . write ( data . c_str (), data . size ());
file . close ();
2024-11-24 03:00:38 -06:00
for ( auto & entry : this -> gCompanionFiles ){
auto cpath = ( Instance -> GetOutputPath () / this -> gCurrentDirectory / entry . first ). string ();
std :: replace ( cpath . begin (), cpath . end (), '\\' , '/' );
if ( ! exists ( fs :: path ( cpath ). parent_path ())){
create_directories ( fs :: path ( cpath ). parent_path ());
}
std :: ofstream cfile ( cpath , std :: ios :: binary );
cfile . write ( entry . second . data (), entry . second . size ());
cfile . close ();
}
2024-04-20 19:20:12 +01:00
break ;
}
default : {
endptr = exporter -> get () -> Export ( stream , data , result . name , result . node , & result . name );
break ;
}
}
2024-11-24 03:00:38 -06:00
this -> gCompanionFiles . clear ();
2024-04-20 19:20:12 +01:00
if ( result . node [ "offset" ]) {
auto alignment = GetSafeNode < uint32_t > ( result . node , "alignment" , impl -> GetAlignment ());
if ( ! endptr . has_value ()) {
wEntry = {
result . name ,
result . node [ "offset" ]. as < uint32_t > (),
alignment ,
stream . str (),
2025-03-09 14:10:32 -06:00
GetNode < std :: string > ( result . node , "comment" ),
2024-04-20 19:20:12 +01:00
std :: nullopt
};
} else {
switch ( endptr -> index ()) {
case 0 :
wEntry = {
result . name ,
result . node [ "offset" ]. as < uint32_t > (),
alignment ,
stream . str (),
2025-03-09 14:10:32 -06:00
GetNode < std :: string > ( result . node , "comment" ),
2024-04-20 19:20:12 +01:00
std :: get < size_t > ( endptr . value ())
};
break ;
case 1 : {
const auto oentry = std :: get < OffsetEntry > ( endptr . value ());
wEntry = {
result . name ,
oentry . start ,
alignment ,
stream . str (),
2025-03-09 14:10:32 -06:00
GetNode < std :: string > ( result . node , "comment" ),
2024-04-20 19:20:12 +01:00
oentry . end
};
break ;
}
default :
SPDLOG_ERROR ( "Invalid endptr index {}" , endptr -> index ());
SPDLOG_ERROR ( "Type of endptr: {}" , typeid ( endptr ). name ());
throw std :: runtime_error ( "We should never reach this point" );
}
}
}
this -> gWriteMap [ this -> gCurrentFile ][ result . type ]. push_back ( wEntry );
}
auto fsout = fs :: path ( this -> gConfig . outputPath );
2024-11-24 03:00:38 -06:00
if ( this -> gConfig . exporterType == ExportType :: Modding || this -> gConfig . exporterType == ExportType :: XML ) {
2024-04-20 19:20:12 +01:00
fsout /= "modding.yml" ;
YAML :: Node modding ;
for ( const auto & [ key , value ] : this -> gModdedAssetPaths ) {
modding [ "assets" ][ key ] = value ;
}
std :: ofstream file ( fsout . string (), std :: ios :: binary );
file << modding ;
file . close ();
} else if ( this -> gConfig . exporterType != ExportType :: Binary ){
std :: string filename = this -> gCurrentDirectory . filename (). string ();
switch ( this -> gConfig . exporterType ) {
case ExportType :: Header : {
2024-04-28 19:24:12 +08:00
fsout /= this -> gCurrentDirectory . parent_path () / ( filename + ".h" );
2024-04-20 19:20:12 +01:00
break ;
}
case ExportType :: Code : {
fsout /= this -> gCurrentDirectory / ( filename + ".c" );
break ;
}
default : break ;
}
std :: ostringstream stream ;
std :: vector < WriteEntry > entries ;
if ( std :: holds_alternative < std :: string > ( this -> gWriteOrder )) {
auto sort = std :: get < std :: string > ( this -> gWriteOrder );
for ( const auto & [ type , raw ] : this -> gWriteMap [ this -> gCurrentFile ]) {
entries . insert ( entries . end (), raw . begin (), raw . end ());
}
if ( sort == "OFFSET" ) {
std :: sort ( entries . begin (), entries . end (), []( const auto & a , const auto & b ) {
return a . addr < b . addr ;
});
} else if ( sort == "ROFFSET" ) {
std :: sort ( entries . begin (), entries . end (), []( const auto & a , const auto & b ) {
return a . addr > b . addr ;
});
} else if ( sort != "LINEAR" ) {
throw std :: runtime_error ( "Invalid write order" );
}
} else {
for ( const auto & type : std :: get < std :: vector < std :: string >> ( this -> gWriteOrder )) {
entries = this -> gWriteMap [ this -> gCurrentFile ][ type ];
std :: sort ( entries . begin (), entries . end (), []( const auto & a , const auto & b ) {
return a . addr > b . addr ;
});
}
}
for ( size_t i = 0 ; i < entries . size (); i ++ ) {
const auto result = entries [ i ];
const auto hasSize = result . endptr . has_value ();
2025-03-09 14:10:32 -06:00
if ( result . comment . has_value ()){
stream << "// " << result . comment . value () << " \n " ;
}
2024-04-20 19:20:12 +01:00
if ( hasSize && this -> IsDebug ()) {
stream << "// 0x" << std :: hex << std :: uppercase << ASSET_PTR ( result . addr ) << " \n " ;
}
stream << result . buffer ;
if ( hasSize && this -> IsDebug ()) {
stream << "// 0x" << std :: hex << std :: uppercase << ASSET_PTR ( result . endptr . value ()) << " \n\n " ;
}
if ( hasSize && i < entries . size () - 1 && this -> gConfig . exporterType == ExportType :: Code && ! this -> gIndividualIncludes ){
int32_t startptr = ASSET_PTR ( result . endptr . value ());
int32_t end = ASSET_PTR ( entries [ i + 1 ]. addr );
uint32_t alignment = entries [ i + 1 ]. alignment ;
int32_t gap = end - startptr ;
if ( gap < 0 ) {
stream << "// WARNING: Overlap detected between 0x" << std :: hex << startptr << " and 0x" << end << " with size 0x" << std :: abs ( gap ) << " \n " ;
SPDLOG_WARN ( "Overlap detected between 0x{:X} and 0x{:X} with size 0x{:X} on file {}" , startptr , end , gap , this -> gCurrentFile );
2024-11-27 21:37:31 +00:00
} else if ( gap < 0x10 && gap >= alignment && end % alignment == 0 && this -> gEnablePadGen ) {
2024-04-20 19:20:12 +01:00
SPDLOG_WARN ( "Gap detected between 0x{:X} and 0x{:X} with size 0x{:X} on file {}" , startptr , end , gap , this -> gCurrentFile );
SPDLOG_WARN ( "Creating pad of 0x{:X} bytes" , gap );
const auto padfile = this -> gCurrentDirectory . filename (). string ();
if ( this -> IsDebug ()){
stream << "// 0x" << std :: hex << std :: uppercase << startptr << " \n " ;
}
2024-05-26 02:06:36 -06:00
stream << "char pad_" << padfile << "_" << std :: to_string ( gCurrentPad ++ ) << "[] = { \n " << tab_t ;
2024-04-20 19:20:12 +01:00
auto gapSize = gap & ~ 3 ;
for ( size_t j = 0 ; j < gapSize ; j ++ ){
stream << "0x00, " ;
}
stream << " \n }; \n " ;
if ( this -> IsDebug ()){
stream << "// 0x" << std :: hex << std :: uppercase << end << " \n\n " ;
} else {
stream << " \n " ;
}
2024-11-27 20:53:14 +00:00
} else if ( gap >= 0x10 ) {
2024-04-20 19:20:12 +01:00
stream << "// WARNING: Gap detected between 0x" << std :: hex << startptr << " and 0x" << end << " with size 0x" << gap << " \n " ;
}
}
if ( this -> gConfig . exporterType == ExportType :: Code && this -> gIndividualIncludes ) {
2024-05-12 15:55:27 +08:00
fs :: path outinc = fs :: path ( this -> gConfig . outputPath ) / this -> gCurrentDirectory . parent_path () /
fs :: relative ( fs :: path ( result . name + ".inc.c" ), this -> gCurrentDirectory . parent_path ());
2024-04-20 19:20:12 +01:00
if ( ! exists ( outinc . parent_path ())){
create_directories ( outinc . parent_path ());
}
std :: ofstream file ( outinc , std :: ios :: binary );
if ( ! this -> gFileHeader . empty ()) {
file << this -> gFileHeader << std :: endl ;
}
file << stream . str ();
stream . str ( "" );
stream . seekp ( 0 );
file . close ();
}
}
this -> gWriteMap . clear ();
if ( this -> gConfig . exporterType != ExportType :: Code || ! this -> gIndividualIncludes ) {
std :: string buffer = stream . str ();
if ( buffer . empty ()) {
return ;
}
std :: string output = fsout . string ();
std :: replace ( output . begin (), output . end (), '\\' , '/' );
if ( ! exists ( fs :: path ( output ). parent_path ())){
create_directories ( fs :: path ( output ). parent_path ());
}
std :: ofstream file ( output , std :: ios :: binary );
if ( this -> gConfig . exporterType == ExportType :: Header ) {
fs :: path entryPath = this -> gCurrentFile ;
std :: string symbol = entryPath . stem (). string ();
std :: transform ( symbol . begin (), symbol . end (), symbol . begin (), toupper );
2024-06-09 00:50:45 -06:00
if ( this -> IsOTRMode ()){
file << "#pragma once \n\n " ;
} else {
file << "#ifndef " << symbol << "_H" << std :: endl ;
file << "#define " << symbol << "_H" << std :: endl << std :: endl ;
}
2024-04-20 19:20:12 +01:00
if ( ! this -> gFileHeader . empty ()) {
file << this -> gFileHeader << std :: endl ;
}
file << buffer ;
2024-06-09 00:50:45 -06:00
if ( ! this -> IsOTRMode ()){
file << std :: endl << "#endif" << std :: endl ;
}
2024-04-20 19:20:12 +01:00
} else {
if ( ! this -> gFileHeader . empty ()) {
file << this -> gFileHeader << std :: endl ;
}
file << buffer ;
}
file . close ();
}
}
if ( this -> gConfig . exporterType != ExportType :: Binary ) {
this -> gHashNode [ this -> gCurrentFile ][ "extracted" ][ ExportTypeToString ( this -> gConfig . exporterType )] = true ;
}
}
2023-09-17 23:55:59 -06:00
void Companion :: Process () {
2023-08-17 00:40:00 -06:00
2023-11-15 15:11:15 -06:00
if ( ! fs :: exists ( "config.yml" )) {
SPDLOG_ERROR ( "No config file found" );
return ;
}
2023-09-24 14:26:01 -06:00
2023-09-24 14:06:18 -06:00
auto start = duration_cast < milliseconds > ( system_clock :: now (). time_since_epoch ());
2023-09-17 23:55:59 -06:00
YAML :: Node config = YAML :: LoadFile ( "config.yml" );
2023-08-17 00:40:00 -06:00
2024-04-29 12:58:04 -06:00
bool isDirectoryMode = config [ "mode" ] && config [ "mode" ]. as < std :: string > () == "directory" ;
if ( ! isDirectoryMode ) {
2024-05-25 16:31:32 -06:00
if ( this -> gRomPath . has_value ()){
std :: ifstream input ( this -> gRomPath . value (), std :: ios :: binary );
this -> gRomData = std :: vector < uint8_t > ( std :: istreambuf_iterator ( input ), {} );
input . close ();
}
2024-04-29 12:58:04 -06:00
this -> gCartridge = std :: make_shared < N64 :: Cartridge > ( this -> gRomData );
this -> gCartridge -> Initialize ();
if ( ! config [ this -> gCartridge -> GetHash ()]){
SPDLOG_ERROR ( "No config found for {}" , this -> gCartridge -> GetHash ());
return ;
}
2025-01-15 19:29:27 -06:00
2024-04-29 12:58:04 -06:00
this -> gConfig . parseMode = ParseMode :: Default ;
} else {
this -> gConfig . parseMode = ParseMode :: Directory ;
2023-09-17 23:55:59 -06:00
}
2023-09-01 00:51:51 -06:00
2024-04-29 12:58:04 -06:00
auto rom = ! isDirectoryMode ? config [ this -> gCartridge -> GetHash ()] : config ;
2023-11-15 15:11:15 -06:00
2024-12-06 14:44:36 -06:00
if ( rom [ "preprocess" ]) {
auto preprocess = rom [ "preprocess" ];
for ( auto job = preprocess . begin (); job != preprocess . end (); job ++ ) {
auto name = job -> first . as < std :: string > ();
auto item = job -> second ;
auto method = GetSafeNode < std :: string > ( item , "method" );
if ( method == "mio0-comptool" ) {
2025-07-09 00:22:59 +02:00
auto type = GetTypeNode ( item );
2024-12-06 14:44:36 -06:00
auto target = GetSafeNode < std :: string > ( item , "target" );
auto restart = GetSafeNode < bool > ( item , "restart" );
2025-07-09 00:22:59 +02:00
if ( type == "DECOMPRESS" ) {
2024-12-06 14:44:36 -06:00
this -> gRomData = CompTool :: Decompress ( this -> gRomData );
this -> gCartridge = std :: make_shared < N64 :: Cartridge > ( this -> gRomData );
this -> gCartridge -> Initialize ();
auto hash = this -> gCartridge -> GetHash ();
SPDLOG_INFO ( "ROM decompressed to {}" , hash );
if ( hash != target ) {
throw std :: runtime_error ( "Hash mismatch" );
}
if ( restart ){
rom = config [ this -> gCartridge -> GetHash ()];
}
} else {
throw std :: runtime_error ( "Only decompression is supported" );
}
} else {
throw std :: runtime_error ( "Invalid preprocess method" );
}
}
}
auto cfg = rom [ "config" ];
2023-11-15 15:11:15 -06:00
if ( ! cfg ) {
2024-04-29 12:58:04 -06:00
SPDLOG_ERROR ( "No config found for {}" , ! isDirectoryMode ? this -> gCartridge -> GetHash () : GetSafeNode < std :: string > ( config , "folder" ));
2023-11-15 15:11:15 -06:00
return ;
}
2024-04-06 15:36:03 -06:00
if ( rom [ "metadata" ]) {
ProcessTables ( rom );
}
2023-11-15 15:11:15 -06:00
if ( rom [ "segments" ]) {
2024-01-30 01:25:46 -06:00
auto segments = rom [ "segments" ]. as < std :: vector < uint32_t >> ();
for ( int i = 0 ; i < segments . size (); i ++ ) {
this -> gConfig . segment . global [ i + 1 ] = segments [ i ];
}
2023-11-15 15:11:15 -06:00
}
2024-04-19 00:34:56 +01:00
this -> gAssetPath = rom [ "path" ]. as < std :: string > ();
2023-11-15 15:11:15 -06:00
auto opath = cfg [ "output" ];
auto gbi = cfg [ "gbi" ];
2025-03-01 23:34:48 -06:00
auto gbi_floats = cfg [ "gbi_floats" ];
2024-02-21 12:51:47 -06:00
auto modding_path = opath && opath [ "modding" ] ? opath [ "modding" ]. as < std :: string > () : "modding" ;
2023-11-15 15:11:15 -06:00
2024-02-21 12:51:47 -06:00
this -> gConfig . moddingPath = modding_path ;
2024-01-30 01:25:46 -06:00
switch ( this -> gConfig . exporterType ) {
2023-11-15 15:11:15 -06:00
case ExportType :: Binary : {
2025-01-09 00:15:16 +00:00
std :: string extension = "" ;
switch ( this -> gConfig . otrMode ) {
case ArchiveType :: OTR :
extension = ".otr" ;
break ;
case ArchiveType :: O2R :
extension = ".o2r" ;
break ;
default :
throw std :: runtime_error ( "Invalid archive type for export type Binary" );
}
this -> gConfig . outputPath = opath && opath [ "binary" ] ? opath [ "binary" ]. as < std :: string > () : ( "generic" + extension );
2023-11-15 15:11:15 -06:00
break ;
}
case ExportType :: Header : {
2024-01-30 01:25:46 -06:00
this -> gConfig . outputPath = opath && opath [ "headers" ] ? opath [ "headers" ]. as < std :: string > () : "headers" ;
2023-11-15 15:11:15 -06:00
break ;
}
case ExportType :: Code : {
2024-01-30 01:25:46 -06:00
this -> gConfig . outputPath = opath && opath [ "code" ] ? opath [ "code" ]. as < std :: string > () : "code" ;
2023-11-15 15:11:15 -06:00
break ;
}
2024-11-24 03:00:38 -06:00
case ExportType :: XML :
2024-02-21 12:51:47 -06:00
case ExportType :: Modding : {
this -> gConfig . outputPath = modding_path ;
break ;
}
2023-11-15 15:11:15 -06:00
}
if ( gbi ) {
auto key = gbi . as < std :: string > ();
if ( key == "F3D" ) {
2024-01-30 01:25:46 -06:00
this -> gConfig . gbi . version = GBIVersion :: f3d ;
2023-11-15 15:11:15 -06:00
} else if ( key == "F3DEX" ) {
2024-01-30 01:25:46 -06:00
this -> gConfig . gbi . version = GBIVersion :: f3dex ;
2023-11-15 15:11:15 -06:00
} else if ( key == "F3DB" ) {
2024-01-30 01:25:46 -06:00
this -> gConfig . gbi . version = GBIVersion :: f3db ;
2023-11-15 15:11:15 -06:00
} else if ( key == "F3DEX2" ) {
2024-01-30 01:25:46 -06:00
this -> gConfig . gbi . version = GBIVersion :: f3dex2 ;
2023-11-15 15:11:15 -06:00
} else if ( key == "F3DEXB" ) {
2024-01-30 01:25:46 -06:00
this -> gConfig . gbi . version = GBIVersion :: f3dexb ;
2023-11-15 15:11:15 -06:00
} else if ( key == "F3DEX_MK64" ) {
2024-01-30 01:25:46 -06:00
this -> gConfig . gbi . version = GBIVersion :: f3dex ;
this -> gConfig . gbi . subversion = GBIMinorVersion :: Mk64 ;
2023-11-15 15:11:15 -06:00
} else {
SPDLOG_ERROR ( "Invalid GBI version" );
return ;
}
2025-03-01 23:34:48 -06:00
if ( gbi_floats ) {
this -> gConfig . gbi . useFloats = gbi_floats . as < bool > ();
}
2023-11-15 15:11:15 -06:00
}
if ( auto sort = cfg [ "sort" ]) {
if ( sort . IsSequence ()) {
this -> gWriteOrder = sort . as < std :: vector < std :: string >> ();
} else {
this -> gWriteOrder = sort . as < std :: string > ();
}
} else {
this -> gWriteOrder = std :: vector < std :: string > {
"LIGHTS" , "TEXTURE" , "VTX" , "GFX"
};
}
2025-01-17 23:32:09 +00:00
if (( this -> gConfig . exporterType == ExportType :: Code || this -> gConfig . exporterType == ExportType :: Binary ) && this -> gConfig . modding ) {
2024-02-21 12:51:47 -06:00
this -> ParseModdingConfig ();
}
2023-11-15 15:11:15 -06:00
if ( std :: holds_alternative < std :: vector < std :: string >> ( this -> gWriteOrder )) {
for ( auto & [ key , _ ] : this -> gFactories ) {
auto entries = std :: get < std :: vector < std :: string >> ( this -> gWriteOrder );
if ( std :: find ( entries . begin (), entries . end (), key ) != entries . end ()) {
continue ;
}
entries . push_back ( key );
}
}
2025-01-15 19:29:27 -06:00
#ifdef STANDALONE
2024-03-12 21:43:59 -06:00
if ( cfg [ "enums" ]) {
auto enums = GetSafeNode < std :: vector < std :: string >> ( cfg , "enums" );
for ( auto & file : enums ) {
this -> ParseEnums ( file );
}
}
2025-01-15 19:29:27 -06:00
#endif
2024-03-12 21:43:59 -06:00
2024-03-12 22:42:00 -06:00
if ( cfg [ "logging" ]){
auto level = cfg [ "logging" ]. as < std :: string > ();
if ( level == "TRACE" ) {
spdlog :: set_level ( spdlog :: level :: trace );
} else if ( level == "DEBUG" ) {
spdlog :: set_level ( spdlog :: level :: debug );
} else if ( level == "INFO" ) {
spdlog :: set_level ( spdlog :: level :: info );
} else if ( level == "WARN" ) {
spdlog :: set_level ( spdlog :: level :: warn );
} else if ( level == "ERROR" ) {
spdlog :: set_level ( spdlog :: level :: err );
} else if ( level == "CRITICAL" ) {
spdlog :: set_level ( spdlog :: level :: critical );
} else if ( level == "OFF" ) {
spdlog :: set_level ( spdlog :: level :: off );
} else {
throw std :: runtime_error ( "Invalid logging level, please use TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL or OFF" );
}
}
2025-03-04 12:22:26 +00:00
this -> gConfig . textureDefines = cfg [ "textures" ] && ( cfg [ "textures" ]. as < std :: string > () == "ADDITIONAL_DEFINES" );
2024-03-29 01:07:00 -06:00
this -> ParseHash ();
2024-03-29 03:02:56 -06:00
2023-09-24 14:06:18 -06:00
SPDLOG_INFO ( "------------------------------------------------" );
2023-09-24 14:26:01 -06:00
spdlog :: set_pattern ( line );
2023-09-24 13:39:02 -06:00
2023-11-15 15:11:15 -06:00
SPDLOG_INFO ( "Starting Torch..." );
2024-04-29 12:58:04 -06:00
if ( this -> gConfig . parseMode == ParseMode :: Default ) {
SPDLOG_INFO ( "Game: {}" , this -> gCartridge -> GetGameTitle ());
SPDLOG_INFO ( "CRC: {}" , this -> gCartridge -> GetCRC ());
SPDLOG_INFO ( "Version: {}" , this -> gCartridge -> GetVersion ());
SPDLOG_INFO ( "Country: [{}]" , this -> gCartridge -> GetCountryCode ());
SPDLOG_INFO ( "Hash: {}" , this -> gCartridge -> GetHash ());
SPDLOG_INFO ( "Assets: {}" , this -> gAssetPath );
} else {
SPDLOG_INFO ( "Mode: Directory" );
SPDLOG_INFO ( "Directory: {}" , rom [ "folder" ]. as < std :: string > ());
}
2023-09-24 13:39:02 -06:00
2024-02-01 17:37:13 -06:00
AudioManager :: Instance = new AudioManager ();
2025-01-09 00:15:16 +00:00
BinaryWrapper * wrapper = nullptr ;
if ( this -> gConfig . exporterType == ExportType :: Binary ) {
switch ( this -> gConfig . otrMode ) {
case ArchiveType :: OTR :
wrapper = new SWrapper ( this -> gConfig . outputPath );
break ;
case ArchiveType :: O2R :
wrapper = new ZWrapper ( this -> gConfig . outputPath );
break ;
default :
throw std :: runtime_error ( "Invalid archive type for export type Binary" );
}
}
if ( wrapper ) {
wrapper -> CreateArchive ();
}
2024-03-31 10:58:38 -06:00
this -> gCurrentWrapper = wrapper ;
2023-08-17 00:40:00 -06:00
2023-09-18 03:39:03 -06:00
auto vWriter = LUS :: BinaryWriter ();
2024-05-25 16:31:32 -06:00
vWriter . SetEndianness ( Torch :: Endianness :: Big );
vWriter . Write ( static_cast < uint8_t > ( Torch :: Endianness :: Big ));
2024-04-29 12:58:04 -06:00
if ( this -> gConfig . parseMode == ParseMode :: Default ) {
vWriter . Write ( this -> gCartridge -> GetCRC ());
} else {
vWriter . Write (( uint32_t ) 0 );
}
2023-09-18 03:39:03 -06:00
2025-01-29 21:02:51 -06:00
for ( const auto & entry : Torch :: getRecursiveEntries ( this -> gAssetPath )){
2024-01-30 01:25:46 -06:00
if ( entry . is_directory ()) {
continue ;
}
2024-07-28 18:51:16 -06:00
const auto yamlPath = entry . path (). generic_string ();
2024-01-30 01:25:46 -06:00
if ( yamlPath . find ( ".yaml" ) == std :: string :: npos && yamlPath . find ( ".yml" ) == std :: string :: npos ) {
continue ;
2024-04-29 12:58:04 -06:00
}
if ( yamlPath . find ( "config.yml" ) != std :: string :: npos ) {
continue ;
2024-01-30 01:25:46 -06:00
}
YAML :: Node root = YAML :: LoadFile ( yamlPath );
2024-04-19 00:34:56 +01:00
this -> gCurrentDirectory = relative ( entry . path (), this -> gAssetPath ). replace_extension ( "" );
2024-01-30 01:25:46 -06:00
this -> gCurrentFile = yamlPath ;
2023-08-17 00:40:00 -06:00
2024-04-20 19:20:12 +01:00
if ( ! this -> gProcessedFiles . contains ( this -> gCurrentFile )) {
ProcessFile ( root );
this -> gProcessedFiles . insert ( this -> gCurrentFile );
2024-04-08 18:09:12 -06:00
}
2023-08-17 00:40:00 -06:00
}
2023-11-15 15:11:15 -06:00
if ( wrapper != nullptr ) {
SPDLOG_INFO ( "Writing version file" );
2025-01-15 19:29:27 -06:00
wrapper -> AddFile ( "version" , vWriter . ToVector ());
2023-11-15 15:11:15 -06:00
vWriter . Close ();
wrapper -> Close ();
}
2024-03-29 01:07:00 -06:00
// Write entries hash
std :: ofstream file ( "torch.hash.yml" , std :: ios :: binary );
file << this -> gHashNode ;
file . close ();
2023-09-24 14:06:18 -06:00
auto end = duration_cast < milliseconds > ( system_clock :: now (). time_since_epoch ());
2024-03-12 22:42:00 -06:00
auto level = spdlog :: get_level ();
spdlog :: set_level ( spdlog :: level :: info );
2023-09-24 14:06:18 -06:00
SPDLOG_INFO ( "Done! Took {}ms" , end . count () - start . count ());
2024-03-12 22:42:00 -06:00
spdlog :: set_level ( level );
2023-09-24 14:26:01 -06:00
spdlog :: set_pattern ( regular );
2023-09-24 13:39:02 -06:00
SPDLOG_INFO ( "------------------------------------------------" );
2023-09-18 03:39:03 -06:00
2024-01-30 23:03:23 -06:00
Decompressor :: ClearCache ();
2023-11-15 15:11:15 -06:00
this -> gCartridge = nullptr ;
Instance = nullptr ;
2023-09-17 23:55:59 -06:00
}
2023-10-07 21:07:04 -06:00
2025-01-09 00:15:16 +00:00
void Companion :: Pack ( const std :: string & folder , const std :: string & output , const ArchiveType otrMode ) {
2023-10-07 21:07:04 -06:00
2023-11-15 15:11:15 -06:00
spdlog :: set_level ( spdlog :: level :: debug );
spdlog :: set_pattern ( "[%Y-%m-%d %H:%M:%S.%e] [%l] %v" );
2023-10-07 21:07:04 -06:00
2023-11-15 15:11:15 -06:00
SPDLOG_INFO ( "------------------------------------------------" );
2023-10-07 21:07:04 -06:00
2023-11-15 15:11:15 -06:00
SPDLOG_INFO ( "Starting Torch..." );
SPDLOG_INFO ( "Scanning {}" , folder );
2023-10-07 21:07:04 -06:00
2023-11-15 15:11:15 -06:00
auto start = duration_cast < milliseconds > ( system_clock :: now (). time_since_epoch ());
std :: unordered_map < std :: string , std :: vector < char >> files ;
2023-10-07 21:07:04 -06:00
2025-01-29 21:02:51 -06:00
for ( const auto & entry : Torch :: getRecursiveEntries ( folder )){
2024-01-30 01:25:46 -06:00
if ( entry . is_directory ()) {
continue ;
}
2023-11-15 15:11:15 -06:00
std :: ifstream input ( entry . path (), std :: ios :: binary );
auto data = std :: vector ( std :: istreambuf_iterator ( input ), {} );
input . close ();
2024-07-28 18:51:16 -06:00
files [ entry . path (). generic_string ()] = data ;
2023-11-15 15:11:15 -06:00
}
2023-10-07 21:07:04 -06:00
2025-01-09 00:15:16 +00:00
std :: unique_ptr < BinaryWrapper > wrapper ;
switch ( otrMode ) {
case ArchiveType :: OTR :
wrapper . reset ( new SWrapper ( output ));
break ;
case ArchiveType :: O2R :
wrapper . reset ( new ZWrapper ( output ));
break ;
default :
throw std :: runtime_error ( "Invalid archive type for export type Binary" );
}
wrapper -> CreateArchive ();
2023-10-07 21:07:04 -06:00
2023-11-15 15:11:15 -06:00
for ( auto & [ path , data ] : files ){
std :: string normalized = path ;
std :: replace ( normalized . begin (), normalized . end (), '\\' , '/' );
// Remove parent folder
normalized = normalized . substr ( folder . length () + 1 );
2025-01-15 19:29:27 -06:00
wrapper -> AddFile ( normalized , data );
2023-11-15 15:11:15 -06:00
SPDLOG_INFO ( "> Added {}" , normalized );
}
2023-10-07 21:07:04 -06:00
2023-11-15 15:11:15 -06:00
auto end = duration_cast < milliseconds > ( system_clock :: now (). time_since_epoch ());
SPDLOG_INFO ( "Done! Took {}ms" , end . count () - start . count ());
SPDLOG_INFO ( "Exported to {}" , output );
spdlog :: set_pattern ( "[%Y-%m-%d %H:%M:%S.%e] [%l] %v" );
SPDLOG_INFO ( "------------------------------------------------" );
2023-10-07 21:07:04 -06:00
2025-01-09 00:15:16 +00:00
wrapper -> Close ();
2023-11-15 15:11:15 -06:00
}
2023-11-21 00:27:23 -06:00
std :: optional < std :: tuple < std :: string , YAML :: Node >> Companion :: RegisterAsset ( const std :: string & name , YAML :: Node & node ) {
2024-03-12 19:09:40 -06:00
if ( ! node [ "offset" ]) {
return std :: nullopt ;
}
2023-11-21 00:27:23 -06:00
2023-11-15 15:11:15 -06:00
auto output = ( this -> gCurrentDirectory / name ). string ();
std :: replace ( output . begin (), output . end (), '\\' , '/' );
2023-11-21 00:27:23 -06:00
auto entry = std :: make_tuple ( output , node );
this -> gAddrMap [ this -> gCurrentFile ][ node [ "offset" ]. as < uint32_t > ()] = entry ;
2025-01-03 01:04:32 -06:00
auto dResult = this -> ParseNode ( node , output );
if ( dResult . has_value ()) {
this -> gParseResults [ this -> gCurrentFile ]. push_back ( dResult . value ());
}
spdlog :: set_pattern ( regular );
SPDLOG_INFO ( "------------------------------------------------" );
spdlog :: set_pattern ( line );
2024-11-27 21:25:49 -06:00
2023-11-21 00:27:23 -06:00
return entry ;
2023-11-15 15:11:15 -06:00
}
void Companion :: RegisterFactory ( const std :: string & type , const std :: shared_ptr < BaseFactory >& factory ) {
this -> gFactories [ type ] = factory ;
SPDLOG_DEBUG ( "Registered factory for {}" , type );
}
std :: optional < std :: shared_ptr < BaseFactory >> Companion :: GetFactory ( const std :: string & type ) {
if ( ! this -> gFactories . contains ( type )){
return std :: nullopt ;
}
return this -> gFactories [ type ];
}
2024-03-10 23:41:36 -06:00
std :: optional < Table > Companion :: SearchTable ( uint32_t addr ){
for ( auto & table : this -> gTables ){
2024-04-06 15:36:03 -06:00
if ( addr >= table . start && addr <= table . end ){
2024-03-10 23:41:36 -06:00
return table ;
}
}
return std :: nullopt ;
}
2024-03-12 21:43:59 -06:00
std :: optional < std :: string > Companion :: GetEnumFromValue ( const std :: string & key , int32_t id ) {
if ( ! this -> gEnums . contains ( key )){
return std :: nullopt ;
}
if ( ! this -> gEnums [ key ]. contains ( id )){
return std :: nullopt ;
}
return this -> gEnums [ key ][ id ];
}
2024-03-08 13:24:58 -07:00
std :: optional < std :: uint32_t > Companion :: GetFileOffsetFromSegmentedAddr ( const uint8_t segment ) const {
2023-11-22 10:49:30 -06:00
2024-01-30 01:25:46 -06:00
auto segments = this -> gConfig . segment ;
if ( segments . temporal . contains ( segment )) {
return segments . temporal [ segment ];
2023-11-22 10:49:30 -06:00
}
2024-01-30 01:25:46 -06:00
if ( segments . local . contains ( segment )) {
return segments . local [ segment ];
2023-11-15 15:11:15 -06:00
}
2024-01-30 01:25:46 -06:00
if ( segments . global . contains ( segment )) {
return segments . global [ segment ];
}
return std :: nullopt ;
2023-11-15 15:11:15 -06:00
}
2025-06-19 14:36:00 +01:00
uint32_t Companion :: PatchVirtualAddr ( uint32_t addr ) {
2025-06-19 12:21:07 +01:00
if ( addr & 0x80000000 ) {
if ( gVirtualAddrMap . contains ( gCurrentFile )) {
addr -= std :: get < 0 > ( gVirtualAddrMap [ gCurrentFile ]);
addr += std :: get < 1 > ( gVirtualAddrMap [ gCurrentFile ]);
}
}
2025-06-19 14:36:00 +01:00
return addr ;
}
std :: optional < std :: tuple < std :: string , YAML :: Node >> Companion :: GetNodeByAddr ( uint32_t addr ){
if ( ! this -> gAddrMap . contains ( this -> gCurrentFile )){
return std :: nullopt ;
}
// HACK: Adjust address to rom address if virtual address
addr = PatchVirtualAddr ( addr );
2023-11-15 15:11:15 -06:00
if ( ! this -> gAddrMap [ this -> gCurrentFile ]. contains ( addr )){
2024-04-19 00:34:56 +01:00
for ( auto & file : this -> gCurrentExternalFiles ) {
if ( ! this -> gAddrMap . contains ( file )) {
SPDLOG_WARN ( "GetNodeByAddr: External File {} Not Found." , file );
continue ;
}
if ( ! this -> gAddrMap [ file ]. contains ( addr )) {
continue ;
}
return this -> gAddrMap [ file ][ addr ];
}
2023-11-15 15:11:15 -06:00
return std :: nullopt ;
}
return this -> gAddrMap [ this -> gCurrentFile ][ addr ];
}
2025-01-29 22:44:12 -06:00
std :: optional < std :: tuple < std :: string , YAML :: Node >> Companion :: GetSafeNodeByAddr ( const uint32_t addr , std :: string type ) {
auto node = this -> GetNodeByAddr ( addr );
if ( ! node . has_value ()) {
return std :: nullopt ;
}
auto [ name , n ] = node . value ();
2025-07-09 00:22:59 +02:00
auto n_type = GetTypeNode ( n );
2025-01-29 22:44:12 -06:00
2025-07-09 00:22:59 +02:00
if ( n_type != type ) {
2025-07-09 02:55:09 +02:00
throw std :: runtime_error ( "Requested node type does not match with the target node type at " + Torch :: to_hex ( addr , false ) + " Found: " + n_type + " Expected: " + type );
2025-01-29 22:44:12 -06:00
}
return node ;
}
2025-05-06 07:17:13 -05:00
std :: string Companion :: GetSymbolFromAddr ( uint32_t address , bool validZero ) {
auto dec = Companion :: Instance -> GetNodeByAddr ( address );
std :: ostringstream outSymbol ;
if ( address == 0 && ! validZero ) {
outSymbol << "NULL" ;
} else if ( dec . has_value ()) {
auto node = std :: get < 1 > ( dec . value ());
auto symbol = GetSafeNode < std :: string > ( node , "symbol" );
outSymbol << "&" << symbol ;
} else {
outSymbol << "0x" << std :: uppercase << std :: hex << address ;
}
return outSymbol . str ();
}
2024-04-13 00:26:45 -06:00
std :: optional < ParseResultData > Companion :: GetParseDataByAddr ( uint32_t addr ) {
if ( ! this -> gParseResults . contains ( this -> gCurrentFile )){
2024-04-19 00:34:56 +01:00
for ( auto & file : this -> gCurrentExternalFiles ) {
if ( ! this -> gParseResults . contains ( file )) {
SPDLOG_INFO ( "GetParseDataByAddr: External File {} Not Found." , file );
continue ;
}
for ( auto & result : this -> gParseResults [ file ]){
if ( result . data . has_value () && result . GetOffset () == addr ){
return result ;
}
}
}
2024-04-13 00:26:45 -06:00
return std :: nullopt ;
}
for ( auto & result : this -> gParseResults [ this -> gCurrentFile ]){
if ( result . data . has_value () && result . GetOffset () == addr ){
return result ;
}
}
return std :: nullopt ;
}
std :: optional < ParseResultData > Companion :: GetParseDataBySymbol ( const std :: string & symbol ) {
if ( ! this -> gParseResults . contains ( this -> gCurrentFile )){
return std :: nullopt ;
}
for ( auto & result : this -> gParseResults [ this -> gCurrentFile ]){
auto sym = GetNode < std :: string > ( result . node , "symbol" );
if ( result . data . has_value () && sym . has_value () && sym . value () == symbol ){
return result ;
}
}
return std :: nullopt ;
}
2024-03-09 23:12:39 -06:00
std :: optional < std :: vector < std :: tuple < std :: string , YAML :: Node >>> Companion :: GetNodesByType ( const std :: string & type ){
std :: vector < std :: tuple < std :: string , YAML :: Node >> nodes ;
if ( ! this -> gAddrMap . contains ( this -> gCurrentFile )){
return nodes ;
}
for ( auto & [ addr , tpl ] : this -> gAddrMap [ this -> gCurrentFile ]){
auto [ name , node ] = tpl ;
2025-07-09 00:22:59 +02:00
const auto n_type = GetTypeNode ( node );
2024-03-10 15:51:51 -06:00
if ( node [ "autogen" ]){
SPDLOG_DEBUG ( "Skipping autogenerated asset {}" , name );
continue ;
}
2024-03-09 23:12:39 -06:00
if ( n_type == type ){
nodes . push_back ( tpl );
}
}
return nodes ;
}
2024-11-24 03:00:38 -06:00
void Companion :: RegisterCompanionFile ( const std :: string path , std :: vector < char > data ) {
this -> gCompanionFiles [ path ] = data ;
2024-12-25 13:12:23 -06:00
SPDLOG_TRACE ( "Registered companion file {}" , path );
2024-11-24 03:00:38 -06:00
}
2023-11-15 15:11:15 -06:00
std :: string Companion :: NormalizeAsset ( const std :: string & name ) const {
auto path = fs :: path ( this -> gCurrentFile ). stem (). string () + "_" + name ;
return path ;
2024-02-21 12:51:47 -06:00
}
2024-04-06 12:28:51 -06:00
std :: string Companion :: RelativePath ( const std :: string & path ) const {
std :: string doutput = ( this -> gCurrentDirectory / path ). string ();
std :: replace ( doutput . begin (), doutput . end (), '\\' , '/' );
return doutput ;
}
2024-02-21 12:51:47 -06:00
std :: string Companion :: CalculateHash ( const std :: vector < uint8_t >& data ) {
return Chocobo1 :: SHA1 (). addData ( data ). finalize (). toString ();
2024-03-16 15:37:37 -05:00
}
std :: optional < YAML :: Node > Companion :: AddAsset ( YAML :: Node asset ) {
if ( ! asset [ "offset" ] || ! asset [ "type" ]) {
return std :: nullopt ;
}
2025-07-09 00:22:59 +02:00
const auto type = GetTypeNode ( asset );
2024-03-16 15:37:37 -05:00
const auto offset = GetSafeNode < uint32_t > ( asset , "offset" );
const auto symbol = GetSafeNode < std :: string > ( asset , "symbol" , "" );
const auto decl = this -> GetNodeByAddr ( offset );
if ( decl . has_value ()) {
2025-01-04 21:03:11 -06:00
auto found = std :: get < 1 > ( decl . value ());
2025-07-09 00:22:59 +02:00
if ( GetTypeNode ( found ) != type ) {
SPDLOG_ERROR ( "Asset clash detected {} vs {} at 0x{:X}" , type , GetTypeNode ( found ), offset );
2025-01-16 01:17:34 -06:00
} else {
return found ;
2025-01-04 21:03:11 -06:00
}
2024-03-16 15:37:37 -05:00
}
auto rom = this -> GetRomData ();
2024-03-26 00:11:46 -06:00
auto factory = this -> GetFactory ( type );
2024-03-29 03:02:20 -06:00
2024-03-26 00:11:46 -06:00
if ( ! factory . has_value ()) {
return std :: nullopt ;
}
2024-03-16 15:37:37 -05:00
std :: string output ;
std :: string typeId = ConvertType ( type );
2024-03-20 21:17:11 -06:00
2024-05-15 19:16:26 -06:00
if ( ! symbol . empty ()) {
2024-03-16 15:37:37 -05:00
output = symbol ;
} else if ( Decompressor :: IsSegmented ( offset )){
output = this -> NormalizeAsset ( "seg" + std :: to_string ( SEGMENT_NUMBER ( offset )) + "_" + typeId + "_" + Torch :: to_hex ( SEGMENT_OFFSET ( offset ), false ));
} else {
output = this -> NormalizeAsset ( typeId + "_" + Torch :: to_hex ( offset , false ));
}
2024-11-19 16:40:24 -06:00
std :: replace ( output . begin (), output . end (), ':' , '_' );
2024-03-16 15:37:37 -05:00
asset [ "autogen" ] = true ;
asset [ "symbol" ] = output ;
2024-03-26 00:11:46 -06:00
auto result = this -> RegisterAsset ( output , asset );
2024-03-16 15:37:37 -05:00
2024-07-29 03:55:45 -03:00
if ( ! gCurrentVirtualPath . empty ()) {
asset [ "path" ] = gCurrentVirtualPath ;
}
2024-03-16 15:37:37 -05:00
if ( result . has_value ()){
2024-05-15 19:16:26 -06:00
asset [ "vpath" ] = std :: get < 0 > ( result . value ());
2024-03-26 18:02:12 -05:00
2024-03-26 00:11:46 -06:00
return std :: get < 1 > ( result . value ());
2024-03-16 15:37:37 -05:00
}
return std :: nullopt ;
2024-04-25 18:28:44 +01:00
}