#include "builder.h" #include #include #include #include #include "libs/bytes_view.hpp" #include "prebuild/compileAssets/compile.h" #include "helpers/assetsHelper.h" #include "helpers/stringHelper.h" #include "helpers/jsonHelper.h" #include "helpers/fileHelper.h" #include "helpers/debugHelper.h" #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" #include "builder/buildInfo.h" #include "builder/buildInfoCollection.h" #include "builder/buildInfoContext.h" #include "builder/buildAssetTable.h" #include "builder/textureCache.h" #include "buildTypes/buildAudio.h" #include "buildTypes/buildBinary.h" #include "buildTypes/buildTexture.h" #include "buildTypes/buildFonts.h" #include "buildTypes/buildJpFonts.h" #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" using namespace DkrAssetsTool; /*************************************************************************************************/ #define BUILDER_ARGS BuildInfo &info #define BUILDER_LAMDA(classConstructorCode) [](BUILDER_ARGS) { classConstructorCode(info); } typedef std::function BuilderFunction; std::unordered_map builderMap = { { "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) }, }; /*************************************************************************************************/ // Generate the asset_enums.h file in the include folder. void generate_asset_enums_header_file(BuildInfoCollection &collection, std::vector §ionIds) { 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 §ionBuildId : sectionIds) { assetSectionsEnum.add_symbol(sectionBuildId); } assetSectionsEnum.add_symbol("ASSET_SECTIONS_COUNT"); assetEnumsHeader.write_enum(assetSectionsEnum); for(const std::string §ionBuildId : 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 fontBuildIds; fontJson.get_array("/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 menuTextIds; menuTextSectionJson.get_array("/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("-m", false)) { // Attempt to compile assets if mods/order.json has any. CompileAssets::compile(); } JsonFile &mainJson = AssetsHelper::get_main_json(); std::vector sectionIds; mainJson.get_array("/assets/order", sectionIds); size_t numberOfAssetSections = sectionIds.size(); CContext cContext; BuildTextureCache textureCache; BuildInfoCollection collection; // BuildInfoContext holds info that is shared between all assets. BuildInfoContext infoContext(cContext, textureCache, collection); fs::path includeFolder = GlobalSettings::get_decomp_path("include_subpath", "include/"); fs::path srcFolder = GlobalSettings::get_decomp_path("src_subpath", "src/"); CEnumsHelper::load_enums_from_file(cContext, includeFolder / "enums.h"); CEnumsHelper::load_enums_from_file(cContext, srcFolder / "textures_sprites.h"); // For RenderFlags enum. // 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 §ionJson = 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 sectionFileBuildIds; sectionJson.get_array("/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); } } // Build all the assets collection.run_builds([](BuildInfo &info) { if(DebugHelper::get().has_error_occured()) { return; // Do NOT process any more if an error occured. } builderMap[info.get_type()](info); }, sectionIds); if(DebugHelper::get().has_error_occured()) { throw 1; // End the program if one of the threads had an error. } // 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 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); } } }); BytesView tableView = table.get_view(cContext); const std::vector &tableData = tableView.data_vector(); collection.add_deferred_build_info(sectionBuildId, sectionBuildId, "", "", tableData, "", infoContext); } //collection.print_section_counts(); BuildAssetTable mainTable(DkrAssetTableType::FixedTable); // Third loop to fill in the entries for the main assets table for(const std::string §ionId : 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 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 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, ¤tAssetsOffset, &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("/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("dkr_version", "us_1.0") + "/"; // 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, "\""); auto tryGetJsonFile = JsonHelper::get_file(srcPath); // Get the file, and throw an error if it doesn't exist. DebugHelper::assert_(tryGetJsonFile.has_value(), "(DkrAssetBuilder::DkrAssetBuilder) Could not find input file \"", srcPath, "\""); JsonFile &jsonFile = tryGetJsonFile.value(); fs::path localDirectory = FileHelper::get_directory(srcPath); BuildInfo info(&jsonFile, dstPath, localDirectory); // Make sure the json file has the "type" key. DebugHelper::assert_(jsonFile.has("/type"), "(DkrAssetBuilder::DkrAssetBuilder) Input JSON file does not have a \"type\"! Input file = \"", srcPath, "\""); std::string type = jsonFile.get_string("/type"); // 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. builderMap[type](info); */ }