Files
David Benepe e30f1c535a DKR Assets Tool v0.5.2 (Basic glTF support) (#610)
* Removed old unused files

* Added gltf support

* Reverted dkr_assets_tool_settings.json to previous

* Added animated model example

* document audio_vehicle.c (#588)

* wip

* wip

* wip

* wip

* document audio_vehicle.c

* update score once again

* unify function prefix

* small tweaks

* match func_80060910 and func_80060AC8 (#589)

* match func_80060AC8

* reworked func_80060C58

* match func_80060910

* non equivalent func_8006017C

* format

* match calc_env_mapping_for_object

* Homogenate batch flags and render flags and begin the painful task of documenting waves. (#590)

* part one of wave pain

* 2

* littel bit more

* m

* h

* l

* aagghh

* yeah

* :)

* bruh

* b

* m

* faith

* z

* b

* rename batch flags

* Update waves.c

* fix conflicts

* match func_8000B38C (#592)

* match func_8000B38C

* format code

* update score

* match calc_dynamic_lighting_for_object_1 (#594)

* match calc_dynamic_lighting_for_object_1

* format and score

* update score

* update score again

* Document some Boost data (#593)

* Run formatter

* Document some boost info

* decompile and document math_util.c (#595)

* Match func_8002F440 (#596)

* Add a WIP attempt of func_8004447C, and add some scratch links

* match func_8002F440 and run formatter

* Cleanup some comments

* Update score, fix warnings, and run formatter again

* Remove some bad comments

* match: func_8007FFEC (#599)

* feat: allow score script to generate treemap file for decomp progress (#598)

* Various touchups here and there, some renames in camera.c (#597)

* the beginning of the void

* update asset tool

* void :)

* there

* cam

* model

* more

* fix warns

* ok

* whoops missed a rename

* Add a few NON_EQUIVALENT functions from scratches to keep them up to date (#602)

* WIP func_800101AC

* WIP func_800452A0

* cleanup

* Formatter and func_8004CC20 near match

* func_8004CC20 better score and documentation

* func_80049794 WIP and compilable

* Major cleanup of func_80049794

* Formatter

* Update scratch

* Add func_80019808 from scratch

* Cleanup func_80019808

* func_80019808 score improvment

* Improve func_80049794 score (Thanks Dominik!)

* func_8001C6C4 WIP

* func_8001E93C WIP

* small documenting

* Eliminate a lot of Obj64 casts.

* fix: do not pass default to treemap arg (#604)

* Quick fix for score script (#606)

* WIP func_80060EA8

* Quick fix for score script

* name and document lights, CC modes and textures (#607)

* improve match for func_800BDC80 & match func_80060EA8, func_8002F2AC (#609)

* improve match for func_800BDC80 & match func_80060EA8

* match: func_8002F2AC

* match: func_800452A0 & func_80042D20 (#611)

* partial match: func_800452A0, improve from non equiv to non matching

* match: func_800452A0 & func_80042D20

* document: remaining funcs, add decomp.me links (#612)

* match func_8004447C (#615)

* Document camera and sprites (#614)

* Cleanup, and use constants where possible (#617)

* Update func_80060EA8 WIP

* formatter

* More func_80060EA8

* More func_80060EA8

* WIP func_80060EA8

* Minor cleanups and documenting

* minot cleanup

* Define some constant flags properly

* Misc documenting and cleanup

* formatter

* Fix the scratch link for vsprintf (It was DP's)

* Add the WIP version of vsprintf that's 95.82% matching.

* Fix score script (#619)

* Added gltf support

* Allow either commas or vertical pipes for splitting render flags

---------

Co-authored-by: Unnunu <serge_zorkin@mail.ru>
Co-authored-by: Fazana <52551480+FazanaJ@users.noreply.github.com>
Co-authored-by: Ryan Myers <ryan.p.myers@gmail.com>
Co-authored-by: Dominik Peters <github@d-peters.com>
2025-06-12 10:29:08 -04:00

427 lines
19 KiB
C++

#include "builder.h"
#include <functional>
#include <string>
#include <unordered_map>
#include <memory>
#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<void(BUILDER_ARGS)> BuilderFunction;
std::unordered_map<std::string, BuilderFunction> 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<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;
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 &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);
}
}
// 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<uint8_t> &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 &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") + "/";
// 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);
*/
}