2024-04-03 19:16:44 -04:00
# include "builder.h"
# include <functional>
# include <string>
# include <unordered_map>
2025-05-16 17:51:40 -04:00
# include <memory>
2024-04-03 19:16:44 -04:00
2025-05-16 17:51:40 -04:00
# include "libs/bytes_view.hpp"
# include "prebuild/compileAssets/compile.h"
# include "helpers/assetsHelper.h"
2024-04-03 19:16:44 -04:00
# include "helpers/stringHelper.h"
# include "helpers/jsonHelper.h"
# include "helpers/fileHelper.h"
# include "helpers/debugHelper.h"
2025-05-16 17:51:40 -04:00
# include "helpers/dataHelper.h"
# include "helpers/c/cContext.h"
# include "helpers/c/cHeader.h"
# include "misc/args.h"
# include "misc/globalSettings.h"
# include "fileTypes/types.hpp"
2024-04-03 19:16:44 -04:00
# include "builder/buildInfo.h"
2025-05-16 17:51:40 -04:00
# include "builder/buildInfoCollection.h"
2025-05-22 10:59:51 -04:00
# include "builder/buildInfoContext.h"
2025-05-16 17:51:40 -04:00
# include "builder/buildAssetTable.h"
2025-05-22 10:59:51 -04:00
# include "builder/textureCache.h"
2024-04-03 19:16:44 -04:00
# include "buildTypes/buildAudio.h"
# include "buildTypes/buildBinary.h"
# include "buildTypes/buildTexture.h"
# include "buildTypes/buildFonts.h"
2025-05-16 17:51:40 -04:00
# include "buildTypes/buildJpFonts.h"
2024-04-03 19:16:44 -04:00
# include "buildTypes/buildGameText.h"
# include "buildTypes/buildLevelModel.h"
# include "buildTypes/buildLevelHeader.h"
# include "buildTypes/buildLevelObjectTranslationTable.h"
# include "buildTypes/buildMenuText.h"
# include "buildTypes/buildObjectAnimation.h"
# include "buildTypes/buildObjectHeader.h"
# include "buildTypes/buildObjectMap.h"
# include "buildTypes/buildObjectModel.h"
# include "buildTypes/buildParticle.h"
# include "buildTypes/buildParticleBehavior.h"
# include "buildTypes/buildSprite.h"
# include "buildTypes/buildTTGhost.h"
# include "buildTypes/buildMisc.h"
2025-05-16 17:51:40 -04:00
using namespace DkrAssetsTool ;
/*************************************************************************************************/
# define BUILDER_ARGS BuildInfo &info
# define BUILDER_LAMDA(classConstructorCode) [](BUILDER_ARGS) { classConstructorCode(info); }
2024-04-03 19:16:44 -04:00
typedef std : : function < void ( BUILDER_ARGS ) > BuilderFunction ;
std : : unordered_map < std : : string , BuilderFunction > builderMap = {
2025-05-16 17:51:40 -04:00
{ " Binary " , BUILDER_LAMDA ( BuildBinary : : build ) } ,
{ " Fonts " , BUILDER_LAMDA ( BuildFonts : : build ) } ,
{ " JPFonts " , BUILDER_LAMDA ( BuildJPFonts : : build ) } ,
{ " LevelObjectTranslationTable " , BUILDER_LAMDA ( BuildLOTT : : build ) } ,
{ " Texture " , BUILDER_LAMDA ( BuildTexture : : build ) } ,
{ " LevelObjectMap " , BUILDER_LAMDA ( BuildObjectMap : : build ) } ,
{ " LevelModel " , BUILDER_LAMDA ( BuildLevelModel : : build ) } ,
{ " LevelHeader " , BUILDER_LAMDA ( BuildLevelHeader : : build ) } ,
{ " ObjectHeader " , BUILDER_LAMDA ( BuildObjectHeader : : build ) } ,
{ " ObjectModel " , BUILDER_LAMDA ( BuildObjectModel : : build ) } ,
{ " ObjectAnimation " , BUILDER_LAMDA ( BuildObjectAnimation : : build ) } ,
{ " TTGhost " , BUILDER_LAMDA ( BuildTTGhost : : build ) } ,
{ " GameText " , BUILDER_LAMDA ( BuildGameText : : build ) } ,
{ " MenuText " , BUILDER_LAMDA ( BuildMenuText : : build ) } ,
{ " Miscellaneous " , BUILDER_LAMDA ( BuildMisc : : build ) } ,
{ " Sprite " , BUILDER_LAMDA ( BuildSprite : : build ) } ,
{ " Audio " , BUILDER_LAMDA ( BuildAudio : : build ) } ,
{ " Particle " , BUILDER_LAMDA ( BuildParticle : : build ) } ,
{ " ParticleBehavior " , BUILDER_LAMDA ( BuildParticleBehavior : : build ) } ,
2024-04-03 19:16:44 -04:00
} ;
2025-05-16 17:51:40 -04:00
/*************************************************************************************************/
// Generate the asset_enums.h file in the include folder.
void generate_asset_enums_header_file ( BuildInfoCollection & collection , std : : vector < std : : string > & sectionIds ) {
WritableCHeader assetEnumsHeader ;
assetEnumsHeader . write_comment ( " // This file is automatically generated. Any changes you make to this file will get overwritten when building assets. " ) ;
assetEnumsHeader . write_newline ( ) ;
assetEnumsHeader . write_raw_text_line ( " #ifndef _ASSET_ENUMS_H " ) ;
assetEnumsHeader . write_raw_text_line ( " #define _ASSET_ENUMS_H " ) ;
assetEnumsHeader . write_newline ( ) ;
assetEnumsHeader . write_header_comment ( " Asset Sections " ) ;
WriteableCEnum assetSectionsEnum ( " AssetSectionsEnum " ) ;
for ( const std : : string & sectionBuildId : sectionIds ) {
assetSectionsEnum . add_symbol ( sectionBuildId ) ;
}
assetSectionsEnum . add_symbol ( " ASSET_SECTIONS_COUNT " ) ;
assetEnumsHeader . write_enum ( assetSectionsEnum ) ;
for ( const std : : string & sectionBuildId : sectionIds ) {
if ( collection . get_file_count_for_section ( sectionBuildId ) = = 1 ) {
if ( sectionBuildId = = " ASSET_FONTS " | | sectionBuildId = = " ASSET_LEVEL_OBJECT_TRANSLATION_TABLE " ) {
assetEnumsHeader . write_header_comment ( sectionBuildId . c_str ( ) ) ;
std : : string enumName = StringHelper : : upper_snake_case_to_pascal_case ( sectionBuildId ) + " Enum " ;
WriteableCEnum assetEnum ( enumName ) ;
if ( sectionBuildId = = " ASSET_FONTS " ) {
// Need to list out the individual fonts for ASSET_FONTS
JsonFile & fontJson = AssetsHelper : : get_asset_json ( sectionBuildId ) ;
std : : vector < std : : string > fontBuildIds ;
fontJson . get_array < std : : string > ( " /fonts-order " , fontBuildIds ) ;
for ( const std : : string & fontBuildId : fontBuildIds ) {
assetEnum . add_symbol ( fontBuildId ) ;
}
} else if ( sectionBuildId = = " ASSET_LEVEL_OBJECT_TRANSLATION_TABLE " ) {
// Need to list out the object ids for this table.
JsonFile & lottJson = AssetsHelper : : get_asset_json ( sectionBuildId ) ;
size_t tableLen = lottJson . length_of_array ( " /table " ) ;
for ( size_t i = 0 ; i < tableLen ; i + + ) {
std : : string ptr = " /table/ " + std : : to_string ( i ) ;
if ( lottJson . is_value_null ( ptr ) ) {
assetEnum . add_symbol ( " NULL_OBJECT_ " + std : : to_string ( i ) + " _ID " ) ;
continue ;
}
std : : string objectId = lottJson . get_string ( ptr ) ;
if ( StringHelper : : starts_with ( objectId , " ASSET_OBJECT_ " ) ) {
objectId . insert ( 13 , " ID_ " ) ;
} else {
objectId + = " _ID " ;
}
assetEnum . add_symbol ( objectId ) ;
}
}
assetEnum . add_symbol ( sectionBuildId + " _COUNT " ) ;
assetEnumsHeader . write_enum ( assetEnum ) ;
}
// Ignore sections with only one entry, since the build id is already covered in AssetSectionsEnum.
continue ;
}
assetEnumsHeader . write_header_comment ( sectionBuildId . c_str ( ) ) ;
std : : string enumName = StringHelper : : upper_snake_case_to_pascal_case ( sectionBuildId ) + " Enum " ;
WriteableCEnum assetEnum ( enumName ) ;
collection . get_infos_for_section ( sectionBuildId , [ & assetEnum ] ( BuildInfo & info ) {
assetEnum . add_symbol ( info . get_build_id ( ) ) ;
} ) ;
assetEnum . add_symbol ( sectionBuildId + " _COUNT " ) ;
assetEnumsHeader . write_enum ( assetEnum ) ;
if ( sectionBuildId = = " ASSET_MENU_TEXT " ) {
std : : string enumName = StringHelper : : upper_snake_case_to_pascal_case ( sectionBuildId ) + " IdsEnum " ;
WriteableCEnum menuTextIdsEnum ( enumName ) ;
JsonFile & menuTextSectionJson = AssetsHelper : : get_asset_section_json ( sectionBuildId ) ;
std : : vector < std : : string > menuTextIds ;
menuTextSectionJson . get_array < std : : string > ( " /menu-text-build-ids " , menuTextIds ) ;
for ( const std : : string & menuTextId : menuTextIds ) {
menuTextIdsEnum . add_symbol ( menuTextId ) ;
}
assetEnumsHeader . write_enum ( menuTextIdsEnum ) ;
}
}
assetEnumsHeader . write_raw_text_line ( " #endif " ) ;
assetEnumsHeader . write_newline ( ) ;
fs : : path assetEnumsHeaderPath = GlobalSettings : : get_decomp_path ( " /include-subpath " , " include/ " ) / " asset_enums.h " ;
assetEnumsHeader . save ( assetEnumsHeaderPath ) ;
DebugHelper : : info ( " Generated asset_enums.h in the include folder. " ) ;
}
/*************************************************************************************************/
// Build all assets.
void AssetBuilder : : build_all ( const fs : : path & dstPath ) {
if ( Args : : get < bool > ( " -m " , false ) ) {
// Attempt to compile assets if mods/order.json has any.
CompileAssets : : compile ( ) ;
}
JsonFile & mainJson = AssetsHelper : : get_main_json ( ) ;
std : : vector < std : : string > sectionIds ;
mainJson . get_array < std : : string > ( " /assets/order " , sectionIds ) ;
size_t numberOfAssetSections = sectionIds . size ( ) ;
CContext cContext ;
2025-05-22 10:59:51 -04:00
BuildTextureCache textureCache ;
2025-05-16 17:51:40 -04:00
BuildInfoCollection collection ;
// BuildInfoContext holds info that is shared between all assets.
2025-05-22 10:59:51 -04:00
BuildInfoContext infoContext ( cContext , textureCache , collection ) ;
2025-05-16 17:51:40 -04:00
fs : : path includeFolder = GlobalSettings : : get_decomp_path ( " include_subpath " , " include/ " ) ;
2025-06-12 10:29:08 -04:00
fs : : path srcFolder = GlobalSettings : : get_decomp_path ( " src_subpath " , " src/ " ) ;
2025-05-16 17:51:40 -04:00
CEnumsHelper : : load_enums_from_file ( cContext , includeFolder / " enums.h " ) ;
2025-06-12 10:29:08 -04:00
CEnumsHelper : : load_enums_from_file ( cContext , srcFolder / " textures_sprites.h " ) ; // For RenderFlags enum.
2025-05-16 17:51:40 -04:00
// Need to do this here for Object Maps to get built correctly.
CEnumsHelper : : load_enums_from_file ( cContext , includeFolder / " object_behaviors.h " ) ;
CStructHelper : : load_structs_from_file ( cContext , includeFolder / " level_object_entries.h " ) ;
infoContext . init_obj_beh_to_entry_map ( ) ;
// First loop to get all the information about assets to be extracted, and put them into the BuildInfoCollection.
for ( size_t sectionIndex = 0 ; sectionIndex < numberOfAssetSections ; sectionIndex + + ) {
std : : string sectionBuildId = sectionIds [ sectionIndex ] ;
std : : string sectionPtr = " /assets/sections/ " + sectionBuildId ;
std : : string sectionType = mainJson . get_string ( sectionPtr + " /type " ) ;
bool isDeferred = mainJson . get_bool ( sectionPtr + " /deferred " ) ;
if ( isDeferred | | sectionType = = " Table " | | sectionType = = " Empty " | | sectionType = = " NoExtract " ) {
continue ;
}
JsonFile & sectionJson = AssetsHelper : : get_asset_section_json ( sectionBuildId ) ;
std : : string sectionFolder = sectionJson . get_string ( " /folder " ) ;
if ( sectionJson . has ( " /filename " ) ) {
JsonFile & assetJson = AssetsHelper : : get_asset_json ( sectionBuildId ) ;
collection . add_build_info ( sectionBuildId , sectionBuildId , assetJson , sectionFolder , infoContext ) ;
continue ;
}
std : : vector < std : : string > sectionFileBuildIds ;
sectionJson . get_array < std : : string > ( " /files/order " , sectionFileBuildIds ) ;
size_t numberOfFiles = sectionFileBuildIds . size ( ) ;
for ( size_t fileIndex = 0 ; fileIndex < numberOfFiles ; fileIndex + + ) {
std : : string fileBuildId = sectionFileBuildIds [ fileIndex ] ;
JsonFile & assetJson = AssetsHelper : : get_asset_json ( sectionBuildId , fileBuildId ) ;
collection . add_build_info ( sectionBuildId , fileBuildId , assetJson , assetJson . get_filepath ( ) . parent_path ( ) , infoContext ) ;
}
}
2025-06-12 10:29:08 -04:00
2025-05-16 17:51:40 -04:00
// Build all the assets
collection . run_builds ( [ ] ( BuildInfo & info ) {
2025-06-12 10:29:08 -04:00
if ( DebugHelper : : get ( ) . has_error_occured ( ) ) {
return ; // Do NOT process any more if an error occured.
}
builderMap [ info . get_type ( ) ] ( info ) ;
2025-05-22 10:59:51 -04:00
} , sectionIds ) ;
2025-05-16 17:51:40 -04:00
2025-06-12 10:29:08 -04:00
if ( DebugHelper : : get ( ) . has_error_occured ( ) ) {
throw 1 ; // End the program if one of the threads had an error.
}
2025-05-16 17:51:40 -04:00
// Second loop to create the tables for the asset sections.
for ( size_t sectionIndex = 0 ; sectionIndex < numberOfAssetSections ; sectionIndex + + ) {
std : : string sectionBuildId = sectionIds [ sectionIndex ] ;
std : : string sectionPtr = " /assets/sections/ " + sectionBuildId ;
std : : string sectionType = mainJson . get_string ( sectionPtr + " /type " ) ;
if ( sectionType ! = " Table " & & sectionType ! = " ObjectAnimationIdsTable " ) {
continue ;
}
bool isNormalTable = sectionType = = " Table " ;
std : : string forSection = mainJson . get_string ( sectionPtr + " /for " ) ;
DebugHelper : : assert_ ( ! forSection . empty ( ) ,
" (AssetBuilder::build_all) Table section \" " , sectionBuildId , " \" needs a `for` parameter. " ) ;
std : : string forSectionPtr = " /assets/sections/ " + forSection ;
std : : string forType = mainJson . get_string ( forSectionPtr + " /type " , " Binary " ) ;
BuildAssetTable table ( isNormalTable ? forType : " ObjectAnimationIdsTable " ) ;
// Add entries into the table.
collection . get_infos_for_section ( forSection , [ & table , & isNormalTable ] ( BuildInfo & info ) {
bool highestBitSet = false ;
std : : string type = info . get_type ( ) ;
if ( type = = " GameText " ) {
const JsonFile & infoJson = info . get_src_json_file ( ) ;
highestBitSet = infoJson . get_string ( " /text-type " , " Textbox " ) = = " Dialog " ;
}
if ( isNormalTable ) {
table . add_entry ( info . out . size ( ) , highestBitSet ) ;
} else {
// ObjectAnimationIdsTable
2025-06-12 10:29:08 -04:00
if ( info . is_deferred ( ) ) {
std : : string deferredSectionId = info . get_deferred_from_section_build_id ( ) ;
if ( deferredSectionId ! = " ASSET_OBJECT_MODELS " ) {
return ; // Skip if not an object model. (Should never be the case?)
}
std : : string modelBuildId = info . get_deferred_from_build_id ( ) ;
int modelIndex = AssetsHelper : : get_asset_index ( " ASSET_OBJECT_MODELS " , modelBuildId ) ;
DebugHelper : : assert_ ( modelIndex > = 0 ,
" (AssetBuilder::build_all) Could not find object model \" " , modelBuildId , " \" in ASSETS_OBJECT_MODELS section. " ) ;
table . add_object_animation_ids_entry ( modelIndex ) ;
} else {
const JsonFile & infoJson = info . get_src_json_file ( ) ;
std : : string modelBuildId = infoJson . get_string ( " /for-model " ) ;
int modelIndex = AssetsHelper : : get_asset_index ( " ASSET_OBJECT_MODELS " , modelBuildId ) ;
DebugHelper : : assert_ ( modelIndex > = 0 ,
" (AssetBuilder::build_all) Could not find object model \" " , modelBuildId , " \" in ASSETS_OBJECT_MODELS section. " ) ;
table . add_object_animation_ids_entry ( modelIndex ) ;
}
2025-05-16 17:51:40 -04:00
}
} ) ;
BytesView tableView = table . get_view ( cContext ) ;
const std : : vector < uint8_t > & tableData = tableView . data_vector ( ) ;
2025-06-12 10:29:08 -04:00
collection . add_deferred_build_info ( sectionBuildId , sectionBuildId , " " , " " , tableData , " " , infoContext ) ;
2025-05-16 17:51:40 -04:00
}
//collection.print_section_counts();
BuildAssetTable mainTable ( DkrAssetTableType : : FixedTable ) ;
// Third loop to fill in the entries for the main assets table
for ( const std : : string & sectionId : sectionIds ) {
mainTable . add_entry ( collection . get_size_of_section ( sectionId ) ) ;
}
BytesView mainTableView = mainTable . get_view ( cContext ) ;
size_t assetsSize = mainTableView . size ( ) + mainTable . get_size_of_entries ( ) ;
std : : vector < uint8_t > outAssets ( assetsSize ) ;
BytesView outAssetsView ( outAssets ) ;
outAssetsView . copy_from ( mainTableView , 0 ) ; // Put the mainTable at the start of outAssets.
size_t currentAssetsOffset = mainTableView . size ( ) ;
// For the map
std : : map < size_t , std : : string > offsetToId ;
// Fourth loop to fill copy all the built data to outAssets
for ( size_t sectionIndex = 0 ; sectionIndex < numberOfAssetSections ; sectionIndex + + ) {
std : : string sectionBuildId = sectionIds [ sectionIndex ] ;
collection . get_infos_for_section ( sectionBuildId , [ & outAssetsView , & currentAssetsOffset , & offsetToId ] ( BuildInfo & info ) {
outAssetsView . copy_from ( info . out , currentAssetsOffset ) ;
offsetToId [ currentAssetsOffset ] = info . get_build_id ( ) ;
currentAssetsOffset + = info . out . size ( ) ;
} ) ;
// Make sure the alignment for each section is at a 16-byte boundary.
currentAssetsOffset = DataHelper : : align16 ( currentAssetsOffset ) ;
}
// Write the output file to the destination.
FileHelper : : write_binary_file ( outAssets , dstPath , true ) ;
DebugHelper : : info ( " Wrote assets to " , dstPath ) ;
// Generate the include/asset_enums.h file
generate_asset_enums_header_file ( collection , sectionIds ) ;
if ( GlobalSettings : : get_value < bool > ( " /debug/write-assets-map " , false ) ) {
std : : stringstream ss ;
for ( auto & entry : offsetToId ) {
ss < < std : : uppercase < < std : : hex < < std : : setw ( 8 ) < < std : : setfill ( ' 0 ' ) ;
ss < < entry . first < < " : " < < entry . second < < std : : endl ;
}
fs : : path mapDstPath = dstPath ;
mapDstPath . replace_extension ( " .map.txt " ) ;
FileHelper : : write_text_file ( ss . str ( ) , mapDstPath , true ) ;
}
}
void AssetBuilder : : build ( const fs : : path & srcPath , const fs : : path & dstPath ) {
DebugHelper : : error ( " (AssetBuilder::build) Not implemented. Yell at the dev to get off his lazy butt. " ) ;
/*
//settings.pathToAssets /= GlobalSettings::get_value<std::string>("dkr_version", "us_1.0") + "/";
2024-04-03 19:16:44 -04:00
// Make sure the input file is a JSON file.
DebugHelper : : assert_ ( StringHelper : : ends_with ( srcPath , " .json " ) , " (DkrAssetBuilder::DkrAssetBuilder) Input file is not a JSON file! Input file = \" " , srcPath , " \" " ) ;
2025-05-16 17:51:40 -04:00
auto tryGetJsonFile = JsonHelper : : get_file ( srcPath ) ;
2024-04-03 19:16:44 -04:00
// Get the file, and throw an error if it doesn't exist.
2025-05-16 17:51:40 -04:00
DebugHelper : : assert_ ( tryGetJsonFile . has_value ( ) , " (DkrAssetBuilder::DkrAssetBuilder) Could not find input file \" " , srcPath , " \" " ) ;
JsonFile & jsonFile = tryGetJsonFile . value ( ) ;
2024-04-03 19:16:44 -04:00
fs : : path localDirectory = FileHelper : : get_directory ( srcPath ) ;
2025-05-16 17:51:40 -04:00
BuildInfo info ( & jsonFile , dstPath , localDirectory ) ;
2024-04-03 19:16:44 -04:00
// Make sure the json file has the "type" key.
2025-05-16 17:51:40 -04:00
DebugHelper : : assert_ ( jsonFile . has ( " /type " ) , " (DkrAssetBuilder::DkrAssetBuilder) Input JSON file does not have a \" type \" ! Input file = \" " , srcPath , " \" " ) ;
2024-04-03 19:16:44 -04:00
2025-05-16 17:51:40 -04:00
std : : string type = jsonFile . get_string ( " /type " ) ;
2024-04-03 19:16:44 -04:00
// Check to make sure the builderMap has the given type.
if ( builderMap . find ( type ) = = builderMap . end ( ) ) {
// For now this is a warning, but maybe make it an error instead?
DebugHelper : : warn ( " The input type \" " , type , " \" is currently not implemented. " ) ;
return ;
}
// Call the class constructor of a type.
2025-05-16 17:51:40 -04:00
builderMap [ type ] ( info ) ;
*/
2024-04-03 19:16:44 -04:00
}