mirror of
https://github.com/izzy2lost/Diddy-Kong-Racing.git
synced 2026-06-19 01:16:26 -07:00
DKR Assets Tool v0.5 (#577)
* Saving files before attempting to integrate custom crash screen * Put code under DkrAssetsTool namespace * More progress * Lots of work done. * Forgot to revert back to v77 in the makefile * Included cstdint in bytes_view.hpp * Hopefully fixed issue with CEnum::tostring(), removed asset_enums from tracking, and modified gitignore to allow obj files from the mods folder * .mtl file and the textures should now check if the path is absolute or relative. * Fixed compile_all.sh * Doing a thing that is technically not valid. * Removed 'make assets', now assets are built in the normal 'make' setting.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"default_dkr_version": "us_1.0",
|
||||
"baseroms_subpath" : "baseroms/",
|
||||
"assets_subpath" : "assets/",
|
||||
"configs_subpath" : "extract-ver/",
|
||||
"include_subpath" : "include/",
|
||||
"asm_subpath" : "asm/",
|
||||
"build_subpath" : "build/",
|
||||
"data_subpath" : "data/",
|
||||
"model_scale": 0.001,
|
||||
"extract-config" : "tools/dkr_assets_tool_extract.json",
|
||||
"baseroms-subpath" : "baseroms/",
|
||||
"assets-subpath" : "assets/",
|
||||
"configs-subpath" : "extract-ver/",
|
||||
"include-subpath" : "include/",
|
||||
"asm-subpath" : "asm/",
|
||||
"build-subpath" : "build/",
|
||||
"data-subpath" : "data/",
|
||||
"model-scale": 0.001,
|
||||
"threads" : 0,
|
||||
"debug": {
|
||||
"keep-uncompressed": true
|
||||
"keep-uncompressed": true,
|
||||
"write-assets-map": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
#include "buildAssetTable.h"
|
||||
|
||||
#include "helpers/assetsHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
#include "helpers/dataHelper.h"
|
||||
#include "helpers/debugHelper.h"
|
||||
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
BuildAssetTable::BuildAssetTable(std::string assetType) {
|
||||
_type = AssetTable::table_type_from_section_type(assetType);
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
BuildAssetTable::BuildAssetTable(DkrAssetTableType tableType) : _type(tableType) {
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
BuildAssetTable::~BuildAssetTable() {
|
||||
}
|
||||
|
||||
void BuildAssetTable::add_entry(int32_t size, bool highestBitSet) {
|
||||
DebugHelper::assert_(_type != DkrAssetTableType::ObjectAnimationIdsTable,
|
||||
"(BuildAssetTable::add_entry) Table is the incorrect type! Should NOT be a ObjectAnimationIdsTable.");
|
||||
|
||||
if(_type == DkrAssetTableType::MiscTable) {
|
||||
size /= 4; // Misc table entries have to be divided by 4 (for some reason)
|
||||
}
|
||||
|
||||
// GameText uses the highest bit to determine if the entry is a textbox or dialog.
|
||||
int32_t offset = _currentOffset | (highestBitSet ? (0x80000000) : 0);
|
||||
_entries.emplace_back(offset, size);
|
||||
_currentOffset += size;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
int BuildAssetTable::_find_index_of_object_model_id(int32_t objectModelId) {
|
||||
int foundIndex = -1;
|
||||
for(size_t i = 0; i < _entries.size(); i++) {
|
||||
if(_entries[i].objectModelId == objectModelId) {
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return foundIndex;
|
||||
}
|
||||
|
||||
void BuildAssetTable::add_object_animation_ids_entry(int32_t objectModelId) {
|
||||
DebugHelper::assert_(_type == DkrAssetTableType::ObjectAnimationIdsTable,
|
||||
"(BuildAssetTable::add_object_animation_ids_entry) Table is the incorrect type! Should be a ObjectAnimationIdsTable.");
|
||||
|
||||
int foundIndex = _find_index_of_object_model_id(objectModelId);
|
||||
|
||||
if(foundIndex < 0) {
|
||||
foundIndex = _entries.size();
|
||||
_entries.emplace_back(objectModelId, 0);
|
||||
}
|
||||
|
||||
_entries[foundIndex].count++;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
BytesView BuildAssetTable::get_view(CContext &cContext) {
|
||||
if(_isDirty) {
|
||||
_compile_entries_to_data(cContext);
|
||||
}
|
||||
return BytesView(_data);
|
||||
}
|
||||
|
||||
size_t BuildAssetTable::get_size_of_entries() const {
|
||||
return _currentOffset;
|
||||
}
|
||||
|
||||
BytesView BuildAssetTable::_reset_data(size_t newSize) {
|
||||
_data.clear();
|
||||
_data.resize(DataHelper::align16(newSize));
|
||||
return BytesView(_data);
|
||||
}
|
||||
|
||||
void BuildAssetTable::_compile_entries_to_data(CContext &cContext) {
|
||||
int32_t totalSizeOfEntries = _currentOffset;
|
||||
|
||||
switch(_type) {
|
||||
case DkrAssetTableType::FixedTable:
|
||||
{
|
||||
int32_t numberOfEntries = (int32_t)_entries.size();
|
||||
BytesView view = _reset_data((numberOfEntries + 2) * 4);
|
||||
|
||||
view.set_s32_be(0, numberOfEntries); // First entry is the number of following entries.
|
||||
|
||||
for(int32_t i = 0; i < numberOfEntries; i++) {
|
||||
view.set_s32_be((1 + i) * 4, _entries[i].offset);
|
||||
}
|
||||
|
||||
view.set_s32_be((1 + numberOfEntries) * 4, totalSizeOfEntries); // Last entry is the total size of the assets.
|
||||
}
|
||||
break;
|
||||
case DkrAssetTableType::VariableTable:
|
||||
case DkrAssetTableType::GameTextTable:
|
||||
case DkrAssetTableType::MiscTable:
|
||||
case DkrAssetTableType::AudioTable:
|
||||
{
|
||||
int32_t numberOfEntries = (int32_t)_entries.size();
|
||||
BytesView view = _reset_data((numberOfEntries + 2) * 4);
|
||||
|
||||
int startOffset = 0;
|
||||
|
||||
// The audio table skips the first entry for whatever reason.
|
||||
if(_type == DkrAssetTableType::AudioTable) {
|
||||
numberOfEntries--;
|
||||
startOffset = 1;
|
||||
}
|
||||
|
||||
for(int i = 0; i < numberOfEntries; i++) {
|
||||
view.set_s32_be(i * 4, _entries[startOffset + i].offset);
|
||||
}
|
||||
|
||||
view.set_s32_be(numberOfEntries * 4, totalSizeOfEntries); // 2nd to last entry is the total size of the assets.
|
||||
view.set_s32_be((numberOfEntries + 1) * 4, -1); // Last entry should be -1
|
||||
}
|
||||
break;
|
||||
case DkrAssetTableType::MenuTextTable:
|
||||
{
|
||||
int32_t numberOfEntries = (int32_t)_entries.size();
|
||||
JsonFile &menuTextSectionJson = AssetsHelper::get_asset_section_json("ASSET_MENU_TEXT");
|
||||
size_t numberOfMenuTextEntries = menuTextSectionJson.length_of_array("/menu-text-build-ids");
|
||||
|
||||
BytesView view = _reset_data((numberOfEntries + 3) * 4);
|
||||
|
||||
view.set_s32_be(0, numberOfMenuTextEntries); // First entry is the number of menu text entries.
|
||||
|
||||
for(int32_t i = 0; i < numberOfEntries; i++) {
|
||||
view.set_s32_be((1 + i) * 4, _entries[i].offset);
|
||||
}
|
||||
|
||||
view.set_s32_be((1 + numberOfEntries) * 4, totalSizeOfEntries); // 2nd to last entry is the total size of the assets.
|
||||
view.set_s32_be((1 + numberOfEntries + 1) * 4, -1); // Last entry should be -1
|
||||
}
|
||||
break;
|
||||
case DkrAssetTableType::TTGhostTable:
|
||||
{
|
||||
int32_t numberOfEntries = (int32_t)_entries.size();
|
||||
BytesView view = _reset_data((numberOfEntries + 2) * 8);
|
||||
|
||||
for(int32_t i = 0; i < numberOfEntries; i++) {
|
||||
std::string ttGhostBuildId = AssetsHelper::get_build_id_of_index("ASSET_TTGHOSTS", i);
|
||||
JsonFile &ttGhostJson = AssetsHelper::get_asset_json("ASSET_TTGHOSTS", ttGhostBuildId);
|
||||
std::string levelBuildId = ttGhostJson.get_string("/header/level");
|
||||
std::string vehicleEnumStr = ttGhostJson.get_string("/header/vehicle");
|
||||
|
||||
int levelIndex = AssetsHelper::get_asset_index("ASSET_LEVEL_HEADERS", levelBuildId) & 0xFF;
|
||||
int vehicleEnumValue = cContext.get_int_value_of_symbol(vehicleEnumStr) & 0xFF;
|
||||
|
||||
view.set_s32_be((i * 8) + 0, (levelIndex << 24) | (vehicleEnumValue << 16));
|
||||
view.set_s32_be((i * 8) + 4, _entries[i].offset);
|
||||
}
|
||||
|
||||
view.set_s32_be((numberOfEntries * 8) + 0, 0xFFFF0000);
|
||||
view.set_s32_be((numberOfEntries * 8) + 4, totalSizeOfEntries); // 2nd to last entry is the total size of the assets.
|
||||
view.set_s32_be(((numberOfEntries + 1) * 8) + 0, 0xFFFF0000);
|
||||
view.set_s32_be(((numberOfEntries + 1) * 8) + 4, -1); // Last entry should be -1
|
||||
}
|
||||
break;
|
||||
case DkrAssetTableType::ObjectAnimationIdsTable:
|
||||
{
|
||||
int32_t numberOfModels = AssetsHelper::get_asset_section_count("ASSET_OBJECT_MODELS");
|
||||
BytesView view = _reset_data((numberOfModels + 1) * sizeof(int16_t));
|
||||
|
||||
int16_t currAnimOffset = 0;
|
||||
for(int32_t modelIndex = 0; modelIndex < numberOfModels; modelIndex++) {
|
||||
view.set_s16_be(modelIndex * sizeof(int16_t), currAnimOffset);
|
||||
|
||||
int foundIndex = _find_index_of_object_model_id(modelIndex);
|
||||
|
||||
if(foundIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
currAnimOffset += _entries[foundIndex].count;
|
||||
}
|
||||
|
||||
// Last entry should be the total number of animations.
|
||||
view.set_s16_be(numberOfModels * sizeof(int16_t), currAnimOffset);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugHelper::error("Unknown table type: ", _type);
|
||||
break;
|
||||
}
|
||||
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
// Refactor TODO: Combine this with extract/assetTable.h?
|
||||
#include "extract/assetTable.h"
|
||||
|
||||
#include "helpers/c/cContext.h"
|
||||
|
||||
#include "libs/bytes_view.hpp"
|
||||
|
||||
namespace DkrAssetsTool {
|
||||
|
||||
class BuildAssetTable {
|
||||
public:
|
||||
BuildAssetTable(std::string assetType="Binary");
|
||||
BuildAssetTable(DkrAssetTableType tableType);
|
||||
~BuildAssetTable();
|
||||
|
||||
// GameText uses the highest bit to determine if the entry is a textbox or dialog.
|
||||
void add_entry(int32_t size, bool highestBitSet=false);
|
||||
|
||||
void add_object_animation_ids_entry(int32_t objectModelId);
|
||||
|
||||
BytesView get_view(CContext &cContext);
|
||||
|
||||
size_t get_size_of_entries() const;
|
||||
|
||||
private:
|
||||
std::vector<DkrTableEntryInfo> _entries;
|
||||
DkrAssetTableType _type;
|
||||
std::vector<uint8_t> _data;
|
||||
|
||||
bool _isDirty = false;
|
||||
size_t _currentOffset = 0;
|
||||
|
||||
BytesView _reset_data(size_t newSize);
|
||||
void _compile_entries_to_data(CContext &cContext);
|
||||
int _find_index_of_object_model_id(int32_t objectModelId);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,8 +1,221 @@
|
||||
#include "buildInfo.h"
|
||||
|
||||
#include "misc/globalSettings.h"
|
||||
#include "helpers/c/cContext.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
BuildInfoContext::BuildInfoContext(CContext &cContext, BuildStats &stats, BuildInfoCollection &collection)
|
||||
: _cContext(cContext), _stats(stats), _collection(collection) {}
|
||||
|
||||
CContext &BuildInfoContext::get_c_context() const {
|
||||
return _cContext;
|
||||
}
|
||||
|
||||
BuildStats &BuildInfoContext::get_stats() const {
|
||||
return _stats;
|
||||
}
|
||||
|
||||
BuildInfoCollection &BuildInfoContext::get_collection() const {
|
||||
return _collection;
|
||||
}
|
||||
|
||||
void BuildInfoContext::init_obj_beh_to_entry_map() {
|
||||
if(!_objBehaviorToEntry.empty()) {
|
||||
return; // Don't redo this if the map is already filled!
|
||||
}
|
||||
|
||||
// TODO: This was copy-pasted from extract/config.cpp; need to refactor to not require the config file.
|
||||
|
||||
fs::path pathToConfig = GlobalSettings::get_decomp_path("tools", "tools/") / "dkr_assets_tool_extract.json";
|
||||
auto tryGetConfigFile = JsonHelper::get_file(pathToConfig);
|
||||
DebugHelper::assert_(tryGetConfigFile.has_value(),
|
||||
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not find the config file at: ", pathToConfig);
|
||||
JsonFile &configJson = tryGetConfigFile.value();
|
||||
|
||||
CContext &cContext = get_c_context();
|
||||
|
||||
CEnum *objBehaviors = cContext.get_enum("ObjectBehaviours");
|
||||
|
||||
DebugHelper::assert_(objBehaviors != nullptr,
|
||||
"(BuildInfoContext::init_obj_beh_to_entry_map) The enum ObjectBehaviours could not be loaded!");
|
||||
|
||||
std::vector<std::string> defaultObjEntriesOrder;
|
||||
configJson.get_array<std::string>("/misc/default-object-entries-order", defaultObjEntriesOrder);
|
||||
|
||||
for(int i = 0; i < 128; i++) {
|
||||
std::string symbol;
|
||||
DebugHelper::assert_(objBehaviors->get_symbol_of_value(i, symbol),
|
||||
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not get a symbol for the value ", i, " in the ObjectBehaviors enum.");
|
||||
|
||||
CStruct *entryStruct = cContext.get_struct(defaultObjEntriesOrder[i]);
|
||||
DebugHelper::assert_(entryStruct != nullptr,
|
||||
"(BuildInfoContext::init_obj_beh_to_entry_map) Could not find struct \"", defaultObjEntriesOrder[i], "\"");
|
||||
|
||||
_objBehaviorToEntry[symbol] = defaultObjEntriesOrder[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildInfoContext::get_object_entry_from_behavior(std::string objBehavior) const {
|
||||
DebugHelper::assert_(_objBehaviorToEntry.find(objBehavior) != _objBehaviorToEntry.end(),
|
||||
"(AssetExtractConfig::get_object_entry_from_behavior) ", objBehavior,
|
||||
" was not in the _objBehaviorToEntry map!");
|
||||
|
||||
return _objBehaviorToEntry.at(objBehavior);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
BuildInfo::BuildInfo() {
|
||||
DebugHelper::error("Must not initalize empty build info.");
|
||||
}
|
||||
/*
|
||||
BuildInfo::BuildInfo(JsonFile *src, const fs::path &dst, const fs::path &dir)
|
||||
: srcFile(src), dstPath(dst), localDirectory(dir) {
|
||||
_buildType = BUILD_TO_FILE;
|
||||
}*/
|
||||
|
||||
/*
|
||||
BuildInfo::BuildInfo(JsonFile *src, const fs::path &dir)
|
||||
: srcFile(src), dstPath(""), localDirectory(dir) {
|
||||
_buildType = BUILD_TO_BINARY;
|
||||
}
|
||||
*/
|
||||
|
||||
BuildInfo::BuildInfo(std::string buildId, const JsonFile &src, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext)
|
||||
: _dstPath(""), _localDirectory(dir), _jsonFile(src), _infoContext(infoContext), _buildId(buildId), _fileIndex(fileIndex) {
|
||||
_buildType = BUILD_TO_BINARY;
|
||||
const JsonFile &jsonFile = get_src_json_file();
|
||||
_type = jsonFile.get_string("/type", "NoType");
|
||||
}
|
||||
|
||||
BuildInfo::BuildInfo(std::string buildId, const std::vector<uint8_t> &outData, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext)
|
||||
: out(outData), _dstPath(""), _localDirectory(dir), _jsonFile(std::nullopt), _infoContext(infoContext), _buildId(buildId), _type("Binary"), _fileIndex(fileIndex) {
|
||||
_buildType = BUILD_TO_BINARY;
|
||||
}
|
||||
|
||||
BuildInfo::~BuildInfo() {
|
||||
}
|
||||
|
||||
BuildInfoType BuildInfo::get_build_type() const {
|
||||
return _buildType;
|
||||
}
|
||||
|
||||
bool BuildInfo::build_to_file() const {
|
||||
return _buildType == BUILD_TO_FILE;
|
||||
}
|
||||
|
||||
bool BuildInfo::build_to_binary() const {
|
||||
return _buildType == BUILD_TO_BINARY;
|
||||
}
|
||||
|
||||
void BuildInfo::write_out_to_dstPath() {
|
||||
FileHelper::write_binary_file(out, _dstPath, true);
|
||||
}
|
||||
|
||||
void BuildInfo::copy_to_dstPath(fs::path srcPath) {
|
||||
FileHelper::copy(srcPath, _dstPath);
|
||||
}
|
||||
|
||||
void BuildInfo::write_empty_file_to_dstPath() {
|
||||
FileHelper::write_empty_file(_dstPath, true);
|
||||
}
|
||||
|
||||
fs::path BuildInfo::get_dstPath() const {
|
||||
return _dstPath;
|
||||
}
|
||||
|
||||
fs::path BuildInfo::get_dst_folder() const {
|
||||
return _dstPath.parent_path();
|
||||
}
|
||||
|
||||
fs::path BuildInfo::get_dst_filename() const {
|
||||
return _dstPath.filename();
|
||||
}
|
||||
|
||||
fs::path BuildInfo::get_path_to_directory() const {
|
||||
return GlobalSettings::get_decomp_path_to_output_assets() / _localDirectory;
|
||||
}
|
||||
|
||||
CContext &BuildInfo::get_c_context() const {
|
||||
const BuildInfoContext &infoContext = _infoContext.value();
|
||||
return infoContext.get_c_context();
|
||||
}
|
||||
|
||||
BuildStats &BuildInfo::get_stats() const {
|
||||
const BuildInfoContext &infoContext = _infoContext.value();
|
||||
return infoContext.get_stats();
|
||||
}
|
||||
|
||||
BuildInfoCollection &BuildInfo::get_collection() const {
|
||||
const BuildInfoContext &infoContext = _infoContext.value();
|
||||
return infoContext.get_collection();
|
||||
}
|
||||
|
||||
const JsonFile &BuildInfo::get_src_json_file() const {
|
||||
DebugHelper::assert_(_jsonFile.has_value(),
|
||||
"(BuildInfo::get_src_json_file) _jsonFile is null! build id was \"", _buildId, "\"");
|
||||
return _jsonFile.value();
|
||||
}
|
||||
|
||||
const BuildInfoContext &BuildInfo::get_info_context() const {
|
||||
DebugHelper::assert_(_infoContext.has_value(),
|
||||
"(BuildInfo::get_info_context) _infoContext is null! build id was \"", _buildId, "\"");
|
||||
return _infoContext.value();
|
||||
}
|
||||
|
||||
std::string BuildInfo::get_type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
std::string BuildInfo::get_build_id() const {
|
||||
return _buildId;
|
||||
}
|
||||
|
||||
size_t BuildInfo::get_file_index() const {
|
||||
return _fileIndex;
|
||||
}
|
||||
|
||||
bool BuildInfo::is_complete() const {
|
||||
return _complete;
|
||||
}
|
||||
|
||||
void BuildInfo::done() {
|
||||
_complete = true;
|
||||
//pad_output_data();
|
||||
}
|
||||
|
||||
void BuildInfo::pad_output_data() {
|
||||
while((out.size() % 8) != 0) {
|
||||
out.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildInfo::load_structs_into_c_context(std::vector<fs::path> structFilesToLoad) {
|
||||
fs::path includeFolder = GlobalSettings::get_decomp_path("include_subpath", "include/");
|
||||
|
||||
DebugHelper::assert_(!includeFolder.empty(),
|
||||
"(BuildInfo::load_structs_into_c_context) Could not find the include folder!");
|
||||
|
||||
CContext &cContext = get_c_context();
|
||||
|
||||
for(fs::path &structPath : structFilesToLoad) {
|
||||
CStructHelper::load_structs_from_file(cContext, includeFolder / structPath);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildInfo::load_enums_into_c_context(std::vector<fs::path> enumFilesToLoad) {
|
||||
fs::path includeFolder = GlobalSettings::get_decomp_path("include_subpath", "include/");
|
||||
|
||||
DebugHelper::assert_(!includeFolder.empty(),
|
||||
"(BuildInfo::load_enums_into_c_context) Could not find the include folder!");
|
||||
|
||||
CContext &cContext = get_c_context();
|
||||
|
||||
for(fs::path &enumPath : enumFilesToLoad) {
|
||||
CEnumsHelper::load_enums_from_file(cContext, includeFolder / enumPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <mutex>
|
||||
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
#include "helpers/c/cContext.h"
|
||||
|
||||
namespace DkrAssetsTool {
|
||||
|
||||
typedef enum BuildInfoType {
|
||||
BUILD_TO_FILE, // When building a single asset.
|
||||
BUILD_TO_BINARY // When building all assets
|
||||
} BuildInfoType;
|
||||
|
||||
class BuildStats;
|
||||
class BuildInfoCollection;
|
||||
|
||||
class BuildInfoContext {
|
||||
public:
|
||||
BuildInfoContext(CContext &cContext, BuildStats &stats, BuildInfoCollection &collection);
|
||||
|
||||
CContext &get_c_context() const;
|
||||
BuildStats &get_stats() const;
|
||||
BuildInfoCollection &get_collection() const;
|
||||
|
||||
// Used in BuildObjectMap
|
||||
void init_obj_beh_to_entry_map();
|
||||
std::string get_object_entry_from_behavior(std::string objBehavior) const;
|
||||
private:
|
||||
std::reference_wrapper<CContext> _cContext;
|
||||
std::reference_wrapper<BuildStats> _stats;
|
||||
std::reference_wrapper<BuildInfoCollection> _collection;
|
||||
|
||||
std::unordered_map<std::string, std::string> _objBehaviorToEntry;
|
||||
};
|
||||
|
||||
class BuildInfo {
|
||||
public:
|
||||
BuildInfo(JsonFile *src, const fs::path &dst, const fs::path &dir);
|
||||
BuildInfo();
|
||||
//BuildInfo(std::string buildId, JsonFile *src, const fs::path &dst, const fs::path &dir); // BUILD_TO_FILE, TODO
|
||||
//BuildInfo(std::string buildId, JsonFile *src, const fs::path &dir); // BUILD_TO_BINARY
|
||||
BuildInfo(std::string buildId, const JsonFile &src, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext); // BUILD_TO_BINARY
|
||||
BuildInfo(std::string buildId, const std::vector<uint8_t> &outData, size_t fileIndex, const fs::path &dir, const BuildInfoContext &infoContext); // BUILD_TO_BINARY
|
||||
~BuildInfo();
|
||||
|
||||
JsonFile *srcFile;
|
||||
const fs::path &dstPath;
|
||||
const fs::path &localDirectory;
|
||||
//JsonFile *srcFile; // Deprecated!
|
||||
std::vector<uint8_t> out;
|
||||
|
||||
BuildInfoType get_build_type() const;
|
||||
bool build_to_file() const;
|
||||
bool build_to_binary() const;
|
||||
|
||||
void write_out_to_dstPath();
|
||||
void copy_to_dstPath(fs::path srcPath);
|
||||
void write_empty_file_to_dstPath();
|
||||
|
||||
fs::path get_dstPath() const;
|
||||
fs::path get_dst_folder() const;
|
||||
fs::path get_dst_filename() const;
|
||||
|
||||
//void set_c_context(CContext &cContext);
|
||||
//void set_stats(BuildStats &stats);
|
||||
//void set_collection(BuildInfoCollection &collection);
|
||||
|
||||
fs::path get_path_to_directory() const;
|
||||
|
||||
CContext &get_c_context() const;
|
||||
BuildStats &get_stats() const;
|
||||
BuildInfoCollection &get_collection() const;
|
||||
|
||||
const JsonFile &get_src_json_file() const;
|
||||
const BuildInfoContext &get_info_context() const;
|
||||
|
||||
std::string get_type() const;
|
||||
std::string get_build_id() const;
|
||||
size_t get_file_index() const;
|
||||
|
||||
bool is_complete() const;
|
||||
void done();
|
||||
|
||||
void pad_output_data();
|
||||
|
||||
void load_structs_into_c_context(std::vector<fs::path> structFilesToLoad);
|
||||
void load_enums_into_c_context(std::vector<fs::path> enumsFilesToLoad);
|
||||
|
||||
private:
|
||||
};
|
||||
fs::path _dstPath; // Only used for BUILD_TO_FILE
|
||||
fs::path _localDirectory;
|
||||
|
||||
std::optional<std::reference_wrapper<const JsonFile>> _jsonFile;
|
||||
std::optional<std::reference_wrapper<const BuildInfoContext>> _infoContext;
|
||||
|
||||
std::string _buildId;
|
||||
std::string _type;
|
||||
size_t _fileIndex;
|
||||
BuildInfoType _buildType;
|
||||
bool _complete = false;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
#include "buildInfoCollection.h"
|
||||
|
||||
#include "libs/ThreadPool.h"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/dataHelper.h"
|
||||
#include "misc/globalSettings.h"
|
||||
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
const size_t MAX_NUMBER_OF_BUILD_INFOS = 10000;
|
||||
|
||||
BuildInfoCollection::BuildInfoCollection() {
|
||||
fs::path assetsPath = GlobalSettings::get_decomp_path_to_output_assets();
|
||||
_buildInfos.reserve(MAX_NUMBER_OF_BUILD_INFOS); // More than likely overkill, but I REALLY don't want pointers changing mid-build.
|
||||
}
|
||||
|
||||
BuildInfoCollection::~BuildInfoCollection() {
|
||||
}
|
||||
|
||||
void BuildInfoCollection::add_build_info(std::string sectionBuildId, std::string buildId, const JsonFile &src, const fs::path &dir, const BuildInfoContext &infoContext) {
|
||||
_buildInfoMutex.lock();
|
||||
|
||||
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
|
||||
_buildInfoSections[sectionBuildId] = {};
|
||||
|
||||
// Textures have a lot of files.
|
||||
if(sectionBuildId == "ASSET_TEXTURES_2D") {
|
||||
_buildInfoSections[sectionBuildId].reserve(1000);
|
||||
} else if(sectionBuildId == "ASSET_TEXTURES_3D") {
|
||||
_buildInfoSections[sectionBuildId].reserve(1500);
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = _buildInfos.size();
|
||||
|
||||
DebugHelper::assert_(index < MAX_NUMBER_OF_BUILD_INFOS,
|
||||
"(BuildInfoCollection::add_build_info) Max number of assets reached! Limit was ", MAX_NUMBER_OF_BUILD_INFOS);
|
||||
|
||||
size_t fileIndex = _buildInfoSections[sectionBuildId].size();
|
||||
_buildInfos.emplace_back(buildId, src, fileIndex, dir, infoContext);
|
||||
|
||||
// Add the index to the section.
|
||||
_buildInfoSections[sectionBuildId].emplace_back(index);
|
||||
|
||||
_buildInfoMutex.unlock();
|
||||
}
|
||||
|
||||
size_t BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, std::string buildId, const std::vector<uint8_t> &out, const fs::path &dir, const BuildInfoContext &infoContext) {
|
||||
_buildInfoMutex.lock();
|
||||
|
||||
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
|
||||
_buildInfoSections[sectionBuildId] = {};
|
||||
}
|
||||
|
||||
int index = _buildInfos.size();
|
||||
|
||||
DebugHelper::assert_(index < MAX_NUMBER_OF_BUILD_INFOS,
|
||||
"(BuildInfoCollection::add_build_info) Max number of assets reached! Limit was ", MAX_NUMBER_OF_BUILD_INFOS);
|
||||
|
||||
size_t fileIndex = _buildInfoSections[sectionBuildId].size();
|
||||
_buildInfos.emplace_back(buildId, out, fileIndex, dir, infoContext);
|
||||
|
||||
// Do not process deferred assets.
|
||||
_buildInfos.back().done();
|
||||
|
||||
// Add the index to the section.
|
||||
_buildInfoSections[sectionBuildId].emplace_back(index);
|
||||
|
||||
_buildInfoMutex.unlock();
|
||||
|
||||
return fileIndex;
|
||||
}
|
||||
|
||||
void BuildInfoCollection::add_deferred_build_info(std::string sectionBuildId, std::string buildId, size_t fileIndex, const std::vector<uint8_t> &out,
|
||||
const fs::path &dir, const BuildInfoContext &infoContext) {
|
||||
_buildInfoMutex.lock();
|
||||
|
||||
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
|
||||
_buildInfoSections[sectionBuildId] = {};
|
||||
}
|
||||
|
||||
if(_buildInfoSections[sectionBuildId].size() <= fileIndex) {
|
||||
_buildInfoSections[sectionBuildId].resize(fileIndex + 1);
|
||||
}
|
||||
|
||||
size_t index = _buildInfos.size();
|
||||
|
||||
DebugHelper::assert_(index < MAX_NUMBER_OF_BUILD_INFOS,
|
||||
"(BuildInfoCollection::add_build_info) Max number of assets reached! Limit was ", MAX_NUMBER_OF_BUILD_INFOS);
|
||||
|
||||
_buildInfos.emplace_back(buildId, out, fileIndex, dir, infoContext);
|
||||
|
||||
// Do not process deferred assets.
|
||||
_buildInfos.back().done();
|
||||
|
||||
// Add the index to the section.
|
||||
_buildInfoSections[sectionBuildId][fileIndex] = index;
|
||||
|
||||
_buildInfoMutex.unlock();
|
||||
}
|
||||
|
||||
void BuildInfoCollection::run_builds(std::function<void(BuildInfo &)> callbackFunction) {
|
||||
size_t threadCount = GlobalSettings::get_max_thread_count();
|
||||
bool multithreaded = threadCount != 1;
|
||||
DebugHelper::info_verbose("Using ", threadCount, " thread", (multithreaded ? "s" : ""));
|
||||
|
||||
if(multithreaded) {
|
||||
// Multi-threaded (Better for performance)
|
||||
ThreadPool pool(threadCount);
|
||||
for(auto &pair : _buildInfoSections) {
|
||||
for(int buildInfoIndex : pair.second) {
|
||||
BuildInfo &info = _buildInfos[buildInfoIndex];
|
||||
pool.enqueue([&info, &callbackFunction] {
|
||||
if(!info.is_complete()) {
|
||||
callbackFunction(info);
|
||||
info.done();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single-threaded (Better for debugging)
|
||||
for(auto &pair : _buildInfoSections) {
|
||||
for(int buildInfoIndex : pair.second) {
|
||||
BuildInfo &info = _buildInfos[buildInfoIndex];
|
||||
if(!info.is_complete()) {
|
||||
callbackFunction(info);
|
||||
info.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void BuildInfoCollection::get_infos_for_section(std::string sectionBuildId, std::function<void(BuildInfo &info)> callbackFunction) {
|
||||
std::vector<int> §ionIndices = _buildInfoSections[sectionBuildId];
|
||||
for(int buildInfoIndex : sectionIndices) {
|
||||
callbackFunction(_buildInfos[buildInfoIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildInfoCollection::create_assets_file() {
|
||||
|
||||
}
|
||||
|
||||
void BuildInfoCollection::print_section_counts() {
|
||||
for(auto &pair : _buildInfoSections) {
|
||||
DebugHelper::info(pair.first, ": ", pair.second.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex &BuildInfoCollection::get_global_mutex() {
|
||||
return _globalMutex;
|
||||
}
|
||||
|
||||
size_t BuildInfoCollection::get_section_count() const {
|
||||
return _buildInfoSections.size();
|
||||
}
|
||||
|
||||
size_t BuildInfoCollection::get_file_count_for_section(std::string sectionBuildId) const {
|
||||
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
|
||||
return 0;
|
||||
}
|
||||
return _buildInfoSections.at(sectionBuildId).size();
|
||||
}
|
||||
|
||||
size_t BuildInfoCollection::get_size_of_section(std::string sectionBuildId) const {
|
||||
if(_buildInfoSections.find(sectionBuildId) == _buildInfoSections.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::vector<int> §ionIndices = _buildInfoSections.at(sectionBuildId);
|
||||
|
||||
size_t totalSize = 0;
|
||||
|
||||
for(int buildInfoIndex : sectionIndices) {
|
||||
totalSize += _buildInfos.at(buildInfoIndex).out.size();
|
||||
}
|
||||
|
||||
// Make sure the size is 16-byte aligned
|
||||
totalSize = DataHelper::align16(totalSize);
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
#include "helpers/fileHelper.h"
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
|
||||
namespace DkrAssetsTool {
|
||||
|
||||
class BuildInfoCollection {
|
||||
public:
|
||||
BuildInfoCollection();
|
||||
~BuildInfoCollection();
|
||||
|
||||
void add_build_info(std::string sectionBuildId, std::string buildId, const JsonFile &src, const fs::path &dir, const BuildInfoContext &infoContext);
|
||||
|
||||
// Adds deferred info to the end of the list. Returns the fileIndex of the added asset.
|
||||
size_t add_deferred_build_info(std::string sectionBuildId, std::string buildId, const std::vector<uint8_t> &out,
|
||||
const fs::path &dir, const BuildInfoContext &infoContext);
|
||||
|
||||
// Adds deferred info to a specific file index.
|
||||
void add_deferred_build_info(std::string sectionBuildId, std::string buildId, size_t fileIndex, const std::vector<uint8_t> &out,
|
||||
const fs::path &dir, const BuildInfoContext &infoContext);
|
||||
|
||||
void run_builds(std::function<void(BuildInfo &info)> callbackFunction);
|
||||
|
||||
void get_infos_for_section(std::string sectionBuildId, std::function<void(BuildInfo &info)> callbackFunction);
|
||||
|
||||
void create_assets_file();
|
||||
|
||||
void print_section_counts();
|
||||
|
||||
std::mutex &get_global_mutex();
|
||||
|
||||
size_t get_section_count() const;
|
||||
|
||||
// Returns number of files for section.
|
||||
size_t get_file_count_for_section(std::string sectionBuildId) const;
|
||||
|
||||
// Returns total bytes size for section.
|
||||
size_t get_size_of_section(std::string sectionBuildId) const;
|
||||
|
||||
private:
|
||||
// Place for all of the build infos to exist.
|
||||
// Note: Should only ever be added to! Do NOT erase any entries during the building process!
|
||||
std::vector<BuildInfo> _buildInfos;
|
||||
|
||||
std::vector<std::string> _sectionOrder;
|
||||
|
||||
// <key: Section build id, value: Array of indices into _buildInfos for files associated with the section>
|
||||
std::unordered_map<std::string, std::vector<int>> _buildInfoSections;
|
||||
|
||||
std::mutex _buildInfoMutex;
|
||||
std::mutex _globalMutex;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
#include "buildAudio.h"
|
||||
|
||||
BuildAudio::BuildAudio(DkrAssetsSettings &settings, BuildInfo &info) : _settings(settings), _info(info) {
|
||||
if(info.srcFile->is_value_null("/raw")) {
|
||||
FileHelper::write_empty_file(_info.dstPath, true);
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
void BuildAudio::build(BuildInfo &info) {
|
||||
const JsonFile &jsonFile = info.get_src_json_file();
|
||||
|
||||
if(jsonFile.is_value_null("/raw")) {
|
||||
if(info.build_to_file()) {
|
||||
info.write_empty_file_to_dstPath();
|
||||
}
|
||||
// Don't need to do anything for the BUILD_TO_BINARY case.
|
||||
return;
|
||||
}
|
||||
|
||||
std::string rawPath = info.srcFile->get_string("/raw");
|
||||
std::string rawPath = jsonFile.get_string("/raw");
|
||||
|
||||
DebugHelper::assert(!rawPath.empty(), "(BuildAudio::BuildAudio) \"raw\" not specified!");
|
||||
|
||||
// Copy file from rawPath to destination path.
|
||||
FileHelper::copy(_info.localDirectory / rawPath, info.dstPath);
|
||||
}
|
||||
|
||||
BuildAudio::~BuildAudio() {
|
||||
fs::path dir = info.get_path_to_directory();
|
||||
|
||||
if(info.build_to_file()) {
|
||||
// Copy file from rawPath to destination path.
|
||||
info.copy_to_dstPath(dir / rawPath);
|
||||
} else {
|
||||
// Load raw binary into info's out
|
||||
info.out = FileHelper::read_binary_file(dir / rawPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
#include "misc/settings.hpp"
|
||||
#include "helpers/c/cContext.h"
|
||||
|
||||
#include "fileTypes/fonts.hpp"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
|
||||
class BuildAudio {
|
||||
public:
|
||||
BuildAudio(DkrAssetsSettings &settings, BuildInfo &info);
|
||||
~BuildAudio();
|
||||
private:
|
||||
DkrAssetsSettings &_settings;
|
||||
BuildInfo &_info;
|
||||
};
|
||||
namespace DkrAssetsTool {
|
||||
namespace BuildAudio {
|
||||
void build(BuildInfo &info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
#include "buildBinary.h"
|
||||
|
||||
BuildBinary::BuildBinary(DkrAssetsSettings &settings, BuildInfo &info) : _settings(settings), _info(info) {
|
||||
std::string rawPath = info.srcFile->get_string("/raw");
|
||||
#include "misc/globalSettings.h"
|
||||
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
void BuildBinary::build(BuildInfo &info) {
|
||||
const JsonFile &jsonFile = info.get_src_json_file();
|
||||
std::string rawPath = jsonFile.get_string("/raw");
|
||||
|
||||
DebugHelper::assert(!rawPath.empty(), "(BuildBinary::BuildBinary) \"raw\" not specified!");
|
||||
DebugHelper::assert(!rawPath.empty(), "(BuildBinary::build) \"raw\" not specified!");
|
||||
|
||||
// Copy file from rawPath to destination path.
|
||||
FileHelper::copy(_info.localDirectory / rawPath, info.dstPath);
|
||||
fs::path dir = info.get_path_to_directory();
|
||||
|
||||
if(info.build_to_file()) {
|
||||
// Copy file from rawPath to destination path.
|
||||
info.copy_to_dstPath(dir / rawPath);
|
||||
} else {
|
||||
// Load raw binary into info's out
|
||||
info.out = FileHelper::read_binary_file(dir / rawPath);
|
||||
}
|
||||
}
|
||||
|
||||
BuildBinary::~BuildBinary() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
#include "misc/settings.hpp"
|
||||
#include "helpers/c/cContext.h"
|
||||
|
||||
#include "fileTypes/fonts.hpp"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
|
||||
class BuildBinary {
|
||||
public:
|
||||
BuildBinary(DkrAssetsSettings &settings, BuildInfo &info);
|
||||
~BuildBinary();
|
||||
private:
|
||||
DkrAssetsSettings &_settings;
|
||||
BuildInfo &_info;
|
||||
};
|
||||
namespace DkrAssetsTool {
|
||||
namespace BuildBinary {
|
||||
void build(BuildInfo &info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,103 +2,30 @@
|
||||
|
||||
#include <cstring> // for memset
|
||||
|
||||
#include "fileTypes/fonts.hpp"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/dataHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
#include "helpers/stringHelper.h"
|
||||
#include "helpers/assetsHelper.h"
|
||||
|
||||
BuildFonts::BuildFonts(DkrAssetsSettings &settings, BuildInfo &info) : _settings(settings), _info(info) {
|
||||
size_t numberOfFonts = info.srcFile->length_of_array("/fonts-order");
|
||||
|
||||
// "rawDataSize" is the size of the FontData structure with the correct number of fonts.
|
||||
// Have to do this, since the number of fonts is dynamic.
|
||||
size_t rawDataSize = FileHelper::align16(sizeof(be_uint32_t) + (numberOfFonts * sizeof(FontFile)));
|
||||
uint8_t *rawData = new uint8_t[rawDataSize];
|
||||
std::memset(rawData, 0, rawDataSize); // zero out rawData bytes.
|
||||
|
||||
// Convert the raw bytes into the FontData structure.
|
||||
FontData *fontData = reinterpret_cast<FontData *>(rawData);
|
||||
|
||||
fontData->numberOfFonts = numberOfFonts;
|
||||
|
||||
for(size_t fontIndex = 0; fontIndex < numberOfFonts; fontIndex++) {
|
||||
std::string fontBuildId = info.srcFile->get_string("/fonts-order/" + std::to_string(fontIndex));
|
||||
fs::path localFontFilepath = info.srcFile->get_string("/fonts/" + fontBuildId);
|
||||
|
||||
JsonFile *fontJson = _get_font_file(localFontFilepath);
|
||||
FontFile *outFontFile = &fontData->fonts[fontIndex];
|
||||
|
||||
fontJson->copy_string_to("/name", outFontFile->name, FONTS_NAME_LENGTH, "unnamedFont");
|
||||
fontJson->copy_string_to("/junk-text", outFontFile->junkText, FONTS_JUNKTEXT_LENGTH);
|
||||
|
||||
outFontFile->tabWidth = fontJson->get_int("/tab-width");
|
||||
outFontFile->fixedWidth = fontJson->get_int("/fixed-width");
|
||||
outFontFile->yOffset = fontJson->get_int("/y-offset");
|
||||
outFontFile->specialCharacterWidth = fontJson->get_int("/special-character-width");
|
||||
|
||||
size_t numTextures = fontJson->length_of_array("/textures");
|
||||
|
||||
if(numTextures > FONTS_NUMBER_OF_TEXTURE_INDICES) {
|
||||
std::string fontName = fontJson->get_string("/name");
|
||||
DebugHelper::warn("(BuildFonts::BuildFonts) Too many textures in font \"", fontName, "\". Have ", numTextures, " texture ids, but the limit is ", FONTS_NUMBER_OF_TEXTURE_INDICES);
|
||||
numTextures = FONTS_NUMBER_OF_TEXTURE_INDICES;
|
||||
}
|
||||
|
||||
for(size_t texIndex = 0; texIndex < numTextures; texIndex++) {
|
||||
std::string texIndexPtr = "/textures/" + std::to_string(texIndex);
|
||||
|
||||
if(fontJson->is_value_null(texIndexPtr)) {
|
||||
outFontFile->textureIndices[texIndex] = -1; // -1 = No Texture
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugHelper::assert(fontJson->is_value_a_string(texIndexPtr), "(BuildFonts::BuildFonts) [", texIndexPtr, "] is not a string!");
|
||||
|
||||
std::string texBuildId = fontJson->get_string(texIndexPtr);
|
||||
|
||||
int fontTextureIndex = AssetsHelper::get_asset_index(settings, "ASSET_TEXTURES_2D", texBuildId);
|
||||
|
||||
DebugHelper::info(texBuildId, " = ", fontTextureIndex);
|
||||
|
||||
outFontFile->textureIndices[texIndex] = fontTextureIndex;
|
||||
}
|
||||
|
||||
// Fills in the rest with nulls
|
||||
for(size_t texIndex = numTextures; texIndex < FONTS_NUMBER_OF_TEXTURE_INDICES; texIndex++) {
|
||||
outFontFile->textureIndices[texIndex] = -1;
|
||||
}
|
||||
|
||||
// Assume ASCII by default.
|
||||
std::string encodingType = fontJson->get_string("/encoding/type", "ASCII");
|
||||
|
||||
StringHelper::make_uppercase(encodingType); // Make sure string is uppercase.
|
||||
|
||||
if(encodingType == "ASCII") {
|
||||
_parse_ascii_encoding(fontJson, outFontFile);
|
||||
} else {
|
||||
// TODO: Support other encodings!
|
||||
DebugHelper::error("(BuildFonts::BuildFonts) Unsupported font encoding type: \"", encodingType, "\"");
|
||||
}
|
||||
}
|
||||
|
||||
FileHelper::write_binary_file(rawData, rawDataSize, _info.dstPath, true);
|
||||
|
||||
delete[] rawData;
|
||||
}
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
BuildFonts::~BuildFonts() {
|
||||
}
|
||||
|
||||
JsonFile *BuildFonts::_get_font_file(fs::path &localFontFilepath) {
|
||||
fs::path fontPath = _info.localDirectory / localFontFilepath;
|
||||
static std::reference_wrapper<JsonFile> get_font_file(BuildInfo &info, fs::path &localFontFilepath) {
|
||||
fs::path fontPath = info.get_path_to_directory() / localFontFilepath;
|
||||
|
||||
JsonFile *jsonFile;
|
||||
auto tryGetJsonFile = JsonHelper::get_file(fontPath);
|
||||
|
||||
// Get the font file, and throw an error if it doesn't exist.
|
||||
DebugHelper::assert(JsonHelper::get().get_file(fontPath, &jsonFile), "(BuildFonts::_get_font_file) Could not font file ", fontPath);
|
||||
DebugHelper::assert_(tryGetJsonFile.has_value(),
|
||||
"(BuildFonts::get_font_file) Could not font file ", fontPath);
|
||||
|
||||
return jsonFile;
|
||||
return tryGetJsonFile.value();
|
||||
}
|
||||
|
||||
void BuildFonts::_parse_ascii_encoding(JsonFile *fontJson, FontFile *fontFile) {
|
||||
static void parse_ascii_encoding(const JsonFile &fontJson, FontFile *fontFile) {
|
||||
for(int i = 0; i < FONTS_NUMBER_OF_CHARACTERS; i++) {
|
||||
FontCharacter *outFontCharacter = &fontFile->characters[i];
|
||||
char character = (char)(FONTS_START_CHAR + i);
|
||||
@@ -114,13 +41,91 @@ JsonFile *BuildFonts::_get_font_file(fs::path &localFontFilepath) {
|
||||
charPtr += character;
|
||||
}
|
||||
|
||||
outFontCharacter->texIndex = fontJson->get_int(charPtr + "/tex-index");
|
||||
outFontCharacter->charWidth = fontJson->get_int(charPtr + "/char-width", 0);
|
||||
outFontCharacter->offsetX = fontJson->get_int(charPtr + "/offset/x", 0);
|
||||
outFontCharacter->offsetY = fontJson->get_int(charPtr + "/offset/y", 0);
|
||||
outFontCharacter->texU = fontJson->get_int(charPtr + "/uv/u", 0);
|
||||
outFontCharacter->texV = fontJson->get_int(charPtr + "/uv/v", 0);
|
||||
outFontCharacter->texWidth = fontJson->get_int(charPtr + "/tex-size/width", 0);
|
||||
outFontCharacter->texHeight = fontJson->get_int(charPtr + "/tex-size/height", 0);
|
||||
outFontCharacter->texIndex = fontJson.get_int(charPtr + "/tex-index");
|
||||
outFontCharacter->charWidth = fontJson.get_int(charPtr + "/char-width", 0);
|
||||
outFontCharacter->offsetX = fontJson.get_int(charPtr + "/offset/x", 0);
|
||||
outFontCharacter->offsetY = fontJson.get_int(charPtr + "/offset/y", 0);
|
||||
outFontCharacter->texU = fontJson.get_int(charPtr + "/uv/u", 0);
|
||||
outFontCharacter->texV = fontJson.get_int(charPtr + "/uv/v", 0);
|
||||
outFontCharacter->texWidth = fontJson.get_int(charPtr + "/tex-size/width", 0);
|
||||
outFontCharacter->texHeight = fontJson.get_int(charPtr + "/tex-size/height", 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BuildFonts::build(BuildInfo &info) {
|
||||
const JsonFile &jsonFile = info.get_src_json_file();
|
||||
|
||||
size_t numberOfFonts = jsonFile.length_of_array("/fonts-order");
|
||||
|
||||
size_t outSize = DataHelper::align16(sizeof(be_uint32_t) + (numberOfFonts * sizeof(FontFile)));
|
||||
info.out.resize(outSize);
|
||||
|
||||
// Convert the raw bytes into the FontData structure.
|
||||
FontData *fontData = reinterpret_cast<FontData *>(&info.out[0]);
|
||||
|
||||
fontData->numberOfFonts = numberOfFonts;
|
||||
|
||||
for(size_t fontIndex = 0; fontIndex < numberOfFonts; fontIndex++) {
|
||||
std::string fontBuildId = jsonFile.get_string("/fonts-order/" + std::to_string(fontIndex));
|
||||
fs::path localFontFilepath = jsonFile.get_string("/fonts/" + fontBuildId);
|
||||
|
||||
JsonFile &fontJson = get_font_file(info, localFontFilepath);
|
||||
FontFile *outFontFile = &fontData->fonts[fontIndex];
|
||||
|
||||
fontJson.copy_string_to("/name", outFontFile->name, FONTS_NAME_LENGTH, "unnamedFont");
|
||||
fontJson.copy_string_to("/junk-text", outFontFile->junkText, FONTS_JUNKTEXT_LENGTH);
|
||||
|
||||
outFontFile->tabWidth = fontJson.get_int("/tab-width");
|
||||
outFontFile->fixedWidth = fontJson.get_int("/fixed-width");
|
||||
outFontFile->yOffset = fontJson.get_int("/y-offset");
|
||||
outFontFile->specialCharacterWidth = fontJson.get_int("/special-character-width");
|
||||
|
||||
size_t numTextures = fontJson.length_of_array("/textures");
|
||||
|
||||
if(numTextures > FONTS_NUMBER_OF_TEXTURE_INDICES) {
|
||||
std::string fontName = fontJson.get_string("/name");
|
||||
DebugHelper::warn("(BuildFonts::build) Too many textures in font \"", fontName, "\". Have ",
|
||||
numTextures, " texture ids, but the limit is ", FONTS_NUMBER_OF_TEXTURE_INDICES);
|
||||
numTextures = FONTS_NUMBER_OF_TEXTURE_INDICES;
|
||||
}
|
||||
|
||||
for(size_t texIndex = 0; texIndex < numTextures; texIndex++) {
|
||||
std::string texIndexPtr = "/textures/" + std::to_string(texIndex);
|
||||
|
||||
if(fontJson.is_value_null(texIndexPtr)) {
|
||||
outFontFile->textureIndices[texIndex] = -1; // -1 = No Texture
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugHelper::assert(fontJson.is_value_a_string(texIndexPtr), "(BuildFonts::build) [", texIndexPtr, "] is not a string!");
|
||||
|
||||
std::string texBuildId = fontJson.get_string(texIndexPtr);
|
||||
|
||||
int fontTextureIndex = AssetsHelper::get_asset_index("ASSET_TEXTURES_2D", texBuildId);
|
||||
|
||||
outFontFile->textureIndices[texIndex] = fontTextureIndex;
|
||||
}
|
||||
|
||||
// Fills in the rest with nulls
|
||||
for(size_t texIndex = numTextures; texIndex < FONTS_NUMBER_OF_TEXTURE_INDICES; texIndex++) {
|
||||
outFontFile->textureIndices[texIndex] = -1;
|
||||
}
|
||||
|
||||
// Assume ASCII by default.
|
||||
std::string encodingType = fontJson.get_string("/encoding/type", "ASCII");
|
||||
|
||||
StringHelper::make_uppercase(encodingType); // Make sure string is uppercase.
|
||||
|
||||
if(encodingType == "ASCII") {
|
||||
parse_ascii_encoding(fontJson, outFontFile);
|
||||
} else {
|
||||
// TODO: Support other encodings?
|
||||
DebugHelper::error("(BuildFonts::build) Unsupported font encoding type: \"", encodingType, "\"");
|
||||
}
|
||||
}
|
||||
|
||||
if(info.build_to_file()) {
|
||||
info.write_out_to_dstPath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
#include "misc/settings.hpp"
|
||||
|
||||
#include "fileTypes/fonts.hpp"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
|
||||
class BuildFonts {
|
||||
public:
|
||||
BuildFonts(DkrAssetsSettings &settings, BuildInfo &info);
|
||||
~BuildFonts();
|
||||
private:
|
||||
DkrAssetsSettings &_settings;
|
||||
BuildInfo &_info;
|
||||
|
||||
JsonFile *_get_font_file(fs::path &localFontPath);
|
||||
void _parse_ascii_encoding(JsonFile *fontJson, FontFile *fontFile);
|
||||
};
|
||||
namespace DkrAssetsTool {
|
||||
namespace BuildFonts {
|
||||
void build(BuildInfo &info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "buildGameText.h"
|
||||
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
@@ -8,168 +10,185 @@
|
||||
#include "helpers/assetsHelper.h"
|
||||
#include "helpers/stringHelper.h"
|
||||
#include "helpers/dataHelper.h"
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
|
||||
#include "fileTypes/gameText.hpp"
|
||||
|
||||
#include "text/dkrText.h"
|
||||
|
||||
#define TEXTBOX_CMD_ARGS std::vector<uint8_t> &out, const std::string &cmdPtr
|
||||
#define TEXTBOX_CMD_LAMDA(func) [this](TEXTBOX_CMD_ARGS) { func(out, cmdPtr); }
|
||||
#define TEXTBOX_CMD_ARGS BuildInfo &info_, const std::string &cmdPtr, const JsonFile &jsonFile
|
||||
#define TEXTBOX_CMD_LAMDA(func) [](TEXTBOX_CMD_ARGS) { func(info_, cmdPtr, jsonFile); }
|
||||
|
||||
//typedef void (*MiscFuncPtr)(MISC_ARGS);
|
||||
|
||||
BuildGameText::BuildGameText(DkrAssetsSettings &settings, BuildInfo &info) : _settings(settings), _info(info) {
|
||||
// Get text-type as lowercase to make sure it is case-insensitive
|
||||
std::string textType = _info.srcFile->get_string_lowercase("/text-type", "not_set");
|
||||
void build_dialog(BuildInfo &info, const JsonFile &jsonFile) {
|
||||
std::string rawPath = jsonFile.get_string("/raw");
|
||||
|
||||
if(textType == "dialog") {
|
||||
_build_dialog();
|
||||
} else if(textType == "textbox") {
|
||||
_build_textbox();
|
||||
DebugHelper::assert_(!rawPath.empty(),
|
||||
"(BuildGameText::BuildGameText) \"raw\" not specified!");
|
||||
|
||||
fs::path dir = info.get_path_to_directory();
|
||||
|
||||
if(info.build_to_file()) {
|
||||
// Copy file from rawPath to destination path.
|
||||
info.copy_to_dstPath(dir / rawPath);
|
||||
} else {
|
||||
DebugHelper::error("Invalid text type: ", textType);
|
||||
// Load raw binary into info's out
|
||||
info.out = FileHelper::read_binary_file(dir / rawPath);
|
||||
}
|
||||
}
|
||||
|
||||
BuildGameText::~BuildGameText() {
|
||||
void textbox_set_font(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
std::string fontId = jsonFile.get_string(cmdPtr + "/value");
|
||||
JsonFile &fontsFile = AssetsHelper::get_asset_json("ASSET_FONTS");
|
||||
DebugHelper::assert_(fontsFile.has("/fonts-order"),
|
||||
"(textbox_set_font) fonts.json is missing the `/fonts-order` property!");
|
||||
uint8_t font = fontsFile.get_index_of_elem_in_array<std::string>("/fonts-order", fontId);
|
||||
|
||||
}
|
||||
|
||||
void BuildGameText::_build_dialog() {
|
||||
std::string rawPath = _info.srcFile->get_string("/raw");
|
||||
|
||||
DebugHelper::assert(!rawPath.empty(), "(BuildGameText::BuildGameText) \"raw\" not specified!");
|
||||
|
||||
// Copy file from rawPath to destination path.
|
||||
FileHelper::copy(_info.localDirectory / rawPath, _info.dstPath);
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_set_font(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
std::string fontId = _info.srcFile->get_string(cmdPtr + "/value");
|
||||
JsonFile *fontsFile = AssetsHelper::get_asset_json(_settings, "ASSET_FONTS");
|
||||
uint8_t font = fontsFile->get_index_of_elem_in_array<std::string>("/fonts-order", fontId);
|
||||
DebugHelper::assert_(font != 0xFF, "(textbox_set_font) Could not find the font: ", fontId);
|
||||
|
||||
// Write font id
|
||||
out.push_back(font);
|
||||
info.out.push_back(font);
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_set_border(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
size_t offset = out.size();
|
||||
out.resize(offset + sizeof(SetBorderCommand));
|
||||
void textbox_set_border(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
size_t offset = info.out.size();
|
||||
info.out.resize(offset + sizeof(SetBorderCommand));
|
||||
|
||||
SetBorderCommand *borderCmd = reinterpret_cast<SetBorderCommand *>(&out[offset]);
|
||||
SetBorderCommand *borderCmd = reinterpret_cast<SetBorderCommand *>(&info.out[offset]);
|
||||
|
||||
borderCmd->left = _info.srcFile->get_int(cmdPtr + "/value/left");
|
||||
borderCmd->top = _info.srcFile->get_int(cmdPtr + "/value/top");
|
||||
borderCmd->right = _info.srcFile->get_int(cmdPtr + "/value/right");
|
||||
borderCmd->bottom = _info.srcFile->get_int(cmdPtr + "/value/bottom");
|
||||
borderCmd->left = jsonFile.get_int(cmdPtr + "/value/left");
|
||||
borderCmd->top = jsonFile.get_int(cmdPtr + "/value/top");
|
||||
borderCmd->right = jsonFile.get_int(cmdPtr + "/value/right");
|
||||
borderCmd->bottom = jsonFile.get_int(cmdPtr + "/value/bottom");
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_set_colour(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
size_t offset = out.size();
|
||||
out.resize(offset + sizeof(SetColourCommand));
|
||||
void textbox_set_colour(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
size_t offset = info.out.size();
|
||||
info.out.resize(offset + sizeof(SetColourCommand));
|
||||
|
||||
SetColourCommand *colourCmd = reinterpret_cast<SetColourCommand *>(&out[offset]);
|
||||
SetColourCommand *colourCmd = reinterpret_cast<SetColourCommand *>(&info.out[offset]);
|
||||
|
||||
colourCmd->red = _info.srcFile->get_int(cmdPtr + "/value/red");
|
||||
colourCmd->green = _info.srcFile->get_int(cmdPtr + "/value/green");
|
||||
colourCmd->blue = _info.srcFile->get_int(cmdPtr + "/value/blue");
|
||||
colourCmd->alpha = _info.srcFile->get_int(cmdPtr + "/value/alpha");
|
||||
colourCmd->red = jsonFile.get_int(cmdPtr + "/value/red");
|
||||
colourCmd->green = jsonFile.get_int(cmdPtr + "/value/green");
|
||||
colourCmd->blue = jsonFile.get_int(cmdPtr + "/value/blue");
|
||||
colourCmd->alpha = jsonFile.get_int(cmdPtr + "/value/alpha");
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_set_alignment(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
std::string alignment = _info.srcFile->get_string_lowercase(cmdPtr + "/value");
|
||||
void textbox_set_alignment(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
std::string alignment = jsonFile.get_string_lowercase(cmdPtr + "/value");
|
||||
|
||||
if(alignment == "center") {
|
||||
out.push_back(0);
|
||||
info.out.push_back(0);
|
||||
} else { // alignment == "left"
|
||||
out.push_back(1);
|
||||
info.out.push_back(1);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_unknown7(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
out.push_back(_info.srcFile->get_int(cmdPtr + "/value"));
|
||||
void textbox_unknown7(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
info.out.push_back(jsonFile.get_int(cmdPtr + "/value"));
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_add_vertical_spacing(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
out.push_back(_info.srcFile->get_int(cmdPtr + "/value"));
|
||||
void textbox_add_vertical_spacing(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
info.out.push_back(jsonFile.get_int(cmdPtr + "/value"));
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_set_line_height(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
out.push_back(_info.srcFile->get_int(cmdPtr + "/value"));
|
||||
void textbox_set_line_height(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
info.out.push_back(jsonFile.get_int(cmdPtr + "/value"));
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_set_timer(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
out.push_back(_info.srcFile->get_int(cmdPtr + "/value"));
|
||||
void textbox_set_timer(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
info.out.push_back(jsonFile.get_int(cmdPtr + "/value"));
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_allow_user_input(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
out.push_back(_info.srcFile->get_int(cmdPtr + "/value"));
|
||||
void textbox_allow_user_input(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
info.out.push_back(jsonFile.get_int(cmdPtr + "/value"));
|
||||
}
|
||||
|
||||
void BuildGameText::_textbox_text(std::vector<uint8_t> &out, const std::string &cmdPtr) {
|
||||
std::string valueStr = _info.srcFile->get_string(cmdPtr + "/value");
|
||||
size_t valueStrLen = valueStr.size();
|
||||
int offset = out.size();
|
||||
void textbox_text(BuildInfo &info, const std::string &cmdPtr, const JsonFile &jsonFile) {
|
||||
std::string valueStr = jsonFile.get_string(cmdPtr + "/value");
|
||||
|
||||
DKRText text(valueStr);
|
||||
|
||||
std::vector<uint8_t> textBytes = text.get_bytes();
|
||||
|
||||
size_t numBytesToCopy = textBytes.size();
|
||||
|
||||
int offset = info.out.size();
|
||||
|
||||
// Expand `out` to include the string.
|
||||
out.resize(out.size() + valueStrLen + 1); // +1 for the NULL terminator.
|
||||
info.out.resize(info.out.size() + numBytesToCopy + 1); // +1 for the NULL terminator.
|
||||
|
||||
char *outText = reinterpret_cast<char *>(&out[offset]);
|
||||
std::strncpy(outText, valueStr.c_str(), valueStrLen); // Copy text to out.
|
||||
outText[valueStrLen] = '\0'; // Add Null terminator to the end of the string.
|
||||
std::copy(textBytes.begin(), textBytes.begin() + numBytesToCopy, info.out.begin() + offset);
|
||||
}
|
||||
|
||||
void BuildGameText::_build_textbox() {
|
||||
if(!_info.srcFile->has("/pages")) {
|
||||
// No pages means it is an empty file!
|
||||
FileHelper::write_empty_file(_info.dstPath, true);
|
||||
void build_textbox(BuildInfo &info, const JsonFile &jsonFile) {
|
||||
if(!jsonFile.has("/pages")) {
|
||||
if(info.build_to_file()) {
|
||||
info.write_empty_file_to_dstPath();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> out;
|
||||
|
||||
size_t numberOfPages = _info.srcFile->length_of_array("/pages");
|
||||
size_t numberOfPages = jsonFile.length_of_array("/pages");
|
||||
|
||||
for(size_t page = 0; page < numberOfPages; page++) {
|
||||
std::string pagePtr = "/pages/" + std::to_string(page);
|
||||
size_t numberOfCmds = _info.srcFile->length_of_array(pagePtr);
|
||||
size_t numberOfCmds = jsonFile.length_of_array(pagePtr);
|
||||
for(size_t cmd = 0; cmd < numberOfCmds; cmd++) {
|
||||
std::string cmdPtr = pagePtr + "/" + std::to_string(cmd);
|
||||
std::string command = _info.srcFile->get_string(cmdPtr + "/command");
|
||||
std::string command = jsonFile.get_string(cmdPtr + "/command");
|
||||
|
||||
const std::unordered_map<std::string, std::function<void(TEXTBOX_CMD_ARGS)>> cmdFunctions = {
|
||||
{ "SetFont", TEXTBOX_CMD_LAMDA(_textbox_set_font) },
|
||||
{ "SetBorder", TEXTBOX_CMD_LAMDA(_textbox_set_border) },
|
||||
{ "SetColour", TEXTBOX_CMD_LAMDA(_textbox_set_colour) },
|
||||
{ "SetAlignment", TEXTBOX_CMD_LAMDA(_textbox_set_alignment) },
|
||||
{ "Unknown7", TEXTBOX_CMD_LAMDA(_textbox_unknown7) },
|
||||
{ "AddVerticalSpacing", TEXTBOX_CMD_LAMDA(_textbox_add_vertical_spacing) },
|
||||
{ "SetLineHeight", TEXTBOX_CMD_LAMDA(_textbox_set_line_height) },
|
||||
{ "SetTimer", TEXTBOX_CMD_LAMDA(_textbox_set_timer) },
|
||||
{ "AllowUserInput", TEXTBOX_CMD_LAMDA(_textbox_allow_user_input) },
|
||||
{ "Text", TEXTBOX_CMD_LAMDA(_textbox_text) },
|
||||
{ "SetFont", TEXTBOX_CMD_LAMDA(textbox_set_font) },
|
||||
{ "SetBorder", TEXTBOX_CMD_LAMDA(textbox_set_border) },
|
||||
{ "SetColour", TEXTBOX_CMD_LAMDA(textbox_set_colour) },
|
||||
{ "SetAlignment", TEXTBOX_CMD_LAMDA(textbox_set_alignment) },
|
||||
{ "Unknown7", TEXTBOX_CMD_LAMDA(textbox_unknown7) },
|
||||
{ "AddVerticalSpacing", TEXTBOX_CMD_LAMDA(textbox_add_vertical_spacing) },
|
||||
{ "SetLineHeight", TEXTBOX_CMD_LAMDA(textbox_set_line_height) },
|
||||
{ "SetTimer", TEXTBOX_CMD_LAMDA(textbox_set_timer) },
|
||||
{ "AllowUserInput", TEXTBOX_CMD_LAMDA(textbox_allow_user_input) },
|
||||
{ "Text", TEXTBOX_CMD_LAMDA(textbox_text) },
|
||||
};
|
||||
|
||||
// Make sure the misc type has a function to extract it.
|
||||
DebugHelper::assert(cmdFunctions.find(command) != cmdFunctions.end(),
|
||||
"(BuildGameText::_build_textbox) Invalid textbox command: ", command);
|
||||
"(build_textbox) Invalid textbox command: ", command);
|
||||
|
||||
if(command != "Text") {
|
||||
int commandIndex = std::find(TEXTBOX_COMMANDS.begin(), TEXTBOX_COMMANDS.end(), command) - TEXTBOX_COMMANDS.begin();
|
||||
out.push_back(commandIndex); // Add command byte to out
|
||||
info.out.push_back(commandIndex); // Add command byte to out
|
||||
}
|
||||
|
||||
cmdFunctions.at(command)(out, cmdPtr);
|
||||
cmdFunctions.at(command)(info, cmdPtr, jsonFile);
|
||||
}
|
||||
if(page < numberOfPages - 1) {
|
||||
out.push_back(1); // cmd 0x01 means next-page.
|
||||
info.out.push_back(1); // cmd 0x01 means next-page.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Probably should pre-allocate the correct size.
|
||||
if((out.size() & 0x7)) {
|
||||
out.resize(DataHelper::align8(out.size()), 0);
|
||||
if((info.out.size() & 0x7)) {
|
||||
info.out.resize(DataHelper::align8(info.out.size()), 0);
|
||||
}
|
||||
|
||||
FileHelper::write_binary_file(out, _info.dstPath, true);
|
||||
if(info.build_to_file()) {
|
||||
info.write_out_to_dstPath();
|
||||
}
|
||||
}
|
||||
|
||||
void BuildGameText::build(BuildInfo &info) {
|
||||
const JsonFile &jsonFile = info.get_src_json_file();
|
||||
|
||||
// Get text-type as lowercase to make sure it is case-insensitive
|
||||
std::string textType = jsonFile.get_string_lowercase("/text-type", "not_set");
|
||||
|
||||
if(textType == "dialog") {
|
||||
build_dialog(info, jsonFile);
|
||||
} else if(textType == "textbox") {
|
||||
build_textbox(info, jsonFile);
|
||||
} else {
|
||||
DebugHelper::error("Invalid text type: ", textType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
#include "misc/settings.hpp"
|
||||
#include "helpers/c/cContext.h"
|
||||
|
||||
#include "fileTypes/fonts.hpp"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
|
||||
class BuildGameText {
|
||||
public:
|
||||
BuildGameText(DkrAssetsSettings &settings, BuildInfo &info);
|
||||
~BuildGameText();
|
||||
private:
|
||||
DkrAssetsSettings &_settings;
|
||||
BuildInfo &_info;
|
||||
|
||||
void _build_dialog();
|
||||
|
||||
void _textbox_set_font(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_set_border(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_set_colour(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_set_alignment(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_unknown7(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_add_vertical_spacing(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_set_line_height(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_set_timer(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_allow_user_input(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
void _textbox_text(std::vector<uint8_t> &out, const std::string &cmdPtr);
|
||||
|
||||
void _build_textbox();
|
||||
};
|
||||
namespace DkrAssetsTool {
|
||||
namespace BuildGameText {
|
||||
void build(BuildInfo &info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
#include "buildJpFonts.h"
|
||||
|
||||
#include "fileTypes/jpFonts.hpp"
|
||||
#include "fileTypes/texture.hpp"
|
||||
|
||||
#include "helpers/dataHelper.h"
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/imageHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
#include "helpers/stringHelper.h"
|
||||
#include "helpers/assetsHelper.h"
|
||||
|
||||
#include "misc/globalSettings.h"
|
||||
|
||||
#include "libs/bytes_view.hpp"
|
||||
|
||||
#include "text/dkrText.h"
|
||||
|
||||
#include "builder/buildInfoCollection.h"
|
||||
|
||||
using namespace DkrAssetsTool;
|
||||
|
||||
void build_v77_beta_font(BuildInfo &info, const JsonFile &jsonFile) {
|
||||
DeferredAssetInfo deferInfo = AssetsHelper::get_deferred_asset_info("ASSET_JAPANESE_FONTS_TABLE");
|
||||
BuildInfoCollection &collection = info.get_collection();
|
||||
|
||||
size_t numberOfFonts = jsonFile.length_of_array("/fonts-order");
|
||||
|
||||
std::vector<uint8_t> outJpFontTable(numberOfFonts * sizeof(BetaJpFontHeader));
|
||||
|
||||
BytesView outJpFontTableView(outJpFontTable);
|
||||
BetaJpFontHeader *headers = outJpFontTableView.data_cast<BetaJpFontHeader>();
|
||||
|
||||
std::vector<N64Image> images;
|
||||
images.reserve(numberOfFonts);
|
||||
|
||||
size_t totalDataSize = 0;
|
||||
|
||||
for(size_t fontIndex = 0; fontIndex < numberOfFonts; fontIndex++) {
|
||||
std::string fontBuildId = jsonFile.get_string("/fonts-order/" + std::to_string(fontIndex));
|
||||
|
||||
std::string fontLocalFilepath = jsonFile.get_string("/fonts/" + fontBuildId);
|
||||
|
||||
DebugHelper::assert_(!fontLocalFilepath.empty(),
|
||||
"(BuildJpFonts::build_v77_beta_font) Could not find a filepath for jp font \"", fontBuildId, "\"");
|
||||
|
||||
fs::path fontFilepath = info.get_path_to_directory() / fontLocalFilepath;
|
||||
|
||||
auto tryGetFontJsonFile = JsonHelper::get_file(fontFilepath);
|
||||
|
||||
DebugHelper::assert_(tryGetFontJsonFile.has_value(),
|
||||
"(BuildJpFonts::build_v77_beta_font) Could not find the file ", fontFilepath);
|
||||
|
||||
JsonFile &fontJsonFile = tryGetFontJsonFile.value();
|
||||
|
||||
fs::path imgPath = fontJsonFile.get_filepath().parent_path() / fontJsonFile.get_string("/image/filename");
|
||||
std::string imgFormat = fontJsonFile.get_string("/image/format", "I4");
|
||||
|
||||
DebugHelper::assert_(imgFormat == "I4",
|
||||
"(BuildJpFonts::build_v77_beta_font) The image format \"", imgFormat,
|
||||
"\" is currently not supported for beta JP fonts. Only I4 images are supported at the moment. Sorry!");
|
||||
|
||||
//DebugHelper::info(fontJsonFile.get_filepath());
|
||||
|
||||
images.emplace_back(imgPath, imgFormat);
|
||||
|
||||
size_t imgWidth = images[fontIndex].get_width();
|
||||
size_t imgHeight = images[fontIndex].get_height();
|
||||
|
||||
//DebugHelper::info(fontJsonFile.get_int("/header/unk0"));
|
||||
|
||||
headers[fontIndex].unk0 = fontJsonFile.get_int("/header/unk0");
|
||||
headers[fontIndex].x = fontJsonFile.get_int("/header/x");
|
||||
headers[fontIndex].y = fontJsonFile.get_int("/header/y");
|
||||
headers[fontIndex].charWidth = fontJsonFile.get_int("/header/charWidth");
|
||||
headers[fontIndex].height = fontJsonFile.get_int("/header/height");
|
||||
headers[fontIndex].unk5 = fontJsonFile.get_int("/header/unk5");
|
||||
headers[fontIndex].imgWidth = imgWidth;
|
||||
headers[fontIndex].unk8 = fontJsonFile.get_int("/header/unk8");
|
||||
headers[fontIndex].unkA = fontJsonFile.get_int("/header/unkA");
|
||||
headers[fontIndex].unkC = totalDataSize; // Not sure about this! Not like it is getting used anyway.
|
||||
|
||||
totalDataSize += (imgWidth * imgHeight) / 2;
|
||||
}
|
||||
|
||||
info.out.resize(totalDataSize);
|
||||
|
||||
size_t curOutOffset = 0;
|
||||
for(size_t i = 0; i < numberOfFonts; i++) {
|
||||
BytesView outImgData(info.out, curOutOffset);
|
||||
N64Image &curImg = images[i];
|
||||
|
||||
curImg.flip_vertically(); // Was flipped vertically during extraction, now I need to flip it back.
|
||||
|
||||
uint8_t *imgData = curImg.get_img_as_ia16(); // Must be freed!
|
||||
|
||||
size_t imgWidth = images[i].get_width();
|
||||
size_t imgHeight = images[i].get_height();
|
||||
|
||||
size_t numberOfBytes = (imgWidth * imgHeight) / 2;
|
||||
|
||||
for(size_t j = 0; j < numberOfBytes; j++) {
|
||||
uint8_t a = imgData[j * 4]; // Should be either 0xFF or 0x00
|
||||
uint8_t b = imgData[j * 4 + 2]; // Should be either 0xFF or 0x00
|
||||
|
||||
if(a == 0xFF) {
|
||||
a = 0x10;
|
||||
}
|
||||
if(b == 0xFF) {
|
||||
b = 0x1;
|
||||
}
|
||||
outImgData[j] = a | b;
|
||||
}
|
||||
|
||||
curOutOffset += numberOfBytes;
|
||||
|
||||
free(imgData);
|
||||
}
|
||||
|
||||
//outJpFontTableView.print(0, outJpFontTable.size());
|
||||
|
||||
// Add jp font table to build collection.
|
||||
collection.add_deferred_build_info("ASSET_JAPANESE_FONTS_TABLE", "ASSET_JAPANESE_FONTS_TABLE", outJpFontTable,
|
||||
info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
|
||||
|
||||
}
|
||||
|
||||
void build_v79_jp_fonts(BuildInfo &info, const JsonFile &jsonFile) {
|
||||
DeferredAssetInfo deferInfo = AssetsHelper::get_deferred_asset_info("ASSET_JAPANESE_FONTS_TABLE");
|
||||
BuildInfoCollection &collection = info.get_collection();
|
||||
|
||||
size_t numberOfFonts = jsonFile.length_of_array("/fonts-order");
|
||||
|
||||
std::vector<uint8_t> outJpFontTable(numberOfFonts * sizeof(JpFontHeader));
|
||||
|
||||
BytesView outJpFontTableView(outJpFontTable);
|
||||
JpFontHeader *headers = outJpFontTableView.data_cast<JpFontHeader>();
|
||||
|
||||
size_t totalDataSize = 0;
|
||||
|
||||
std::vector<std::pair<be_int32_t, be_int32_t>> fontImageOffsets(numberOfFonts);
|
||||
std::vector<fs::path> fontJsonPaths(numberOfFonts);
|
||||
|
||||
// First loop to fill in the font headers and get offsets;
|
||||
for(size_t fontIndex = 0; fontIndex < numberOfFonts; fontIndex++) {
|
||||
std::string fontBuildId = jsonFile.get_string("/fonts-order/" + std::to_string(fontIndex));
|
||||
|
||||
std::string fontLocalFilepath = jsonFile.get_string("/fonts/" + fontBuildId);
|
||||
|
||||
DebugHelper::assert_(!fontLocalFilepath.empty(),
|
||||
"(BuildJpFonts::build_v79_jp_fonts) Could not find a filepath for jp font \"", fontBuildId, "\"");
|
||||
|
||||
fontJsonPaths[fontIndex] = info.get_path_to_directory() / fontLocalFilepath;
|
||||
|
||||
auto tryGetFontJsonFile = JsonHelper::get_file(fontJsonPaths[fontIndex]);
|
||||
|
||||
DebugHelper::assert_(tryGetFontJsonFile.has_value(),
|
||||
"(BuildJpFonts::build_v79_jp_fonts) Could not find the file ", fontJsonPaths[fontIndex]);
|
||||
|
||||
JsonFile &fontJsonFile = tryGetFontJsonFile.value();
|
||||
|
||||
headers[fontIndex].unk0 = fontJsonFile.get_int("/header/unk0");
|
||||
headers[fontIndex].cellWidth = fontJsonFile.get_int("/header/cellWidth");
|
||||
headers[fontIndex].cellHeight = fontJsonFile.get_int("/header/cellHeight");
|
||||
headers[fontIndex].charWidth = fontJsonFile.get_int("/header/charWidth");
|
||||
headers[fontIndex].height = fontJsonFile.get_int("/header/height");
|
||||
headers[fontIndex].unk5 = fontJsonFile.get_int("/header/unk5");
|
||||
headers[fontIndex].unk6 = fontJsonFile.get_int("/header/unk6");
|
||||
headers[fontIndex].unk7 = fontJsonFile.get_int("/header/unk7");
|
||||
|
||||
headers[fontIndex].offset = totalDataSize;
|
||||
|
||||
size_t bytesPerCharacter = sizeof(JpFontChar);
|
||||
fontImageOffsets[fontIndex].first = bytesPerCharacter;
|
||||
|
||||
fs::path colorImgFilename = fontJsonFile.get_string("/image/filename");
|
||||
fs::path colorImageFilepath = fontJsonFile.get_filepath().parent_path() / colorImgFilename;
|
||||
int imgWidth, imgHeight;
|
||||
ImageHelper::get_width_and_height(colorImageFilepath, imgWidth, imgHeight);
|
||||
|
||||
DebugHelper::assert_(imgWidth % 16 == 0,
|
||||
"(BuildJpFonts::build_v79_jp_fonts) ", colorImgFilename, " width of ", imgWidth, " is not divisible by 16!");
|
||||
DebugHelper::assert_(imgHeight % 16 == 0,
|
||||
"(BuildJpFonts::build_v79_jp_fonts) ", colorImgFilename, " height of ", imgHeight, " is not divisible by 16!");
|
||||
|
||||
int cellWidth = imgWidth / 16;
|
||||
int cellHeight = imgHeight / 16;
|
||||
|
||||
std::string colorImageFormat = fontJsonFile.get_string("/image/format", "RGBA16");
|
||||
|
||||
size_t colorImgSize = ImageHelper::image_size(cellWidth, cellHeight, colorImageFormat);
|
||||
bytesPerCharacter += colorImgSize;
|
||||
|
||||
bool hasAlphaImage = fontJsonFile.has("/image-alpha/filename");
|
||||
|
||||
if(hasAlphaImage) {
|
||||
fontImageOffsets[fontIndex].second = bytesPerCharacter;
|
||||
std::string alphaImageFormat = fontJsonFile.get_string("/image-alpha/format", "I4");
|
||||
bytesPerCharacter += ImageHelper::image_size(cellWidth, cellHeight, alphaImageFormat);
|
||||
} else {
|
||||
fontImageOffsets[fontIndex].second = -1; // JpFontChar.offsetToSecondImg is -1 if there is no alpha image.
|
||||
}
|
||||
|
||||
headers[fontIndex].bytesPerCharacter = bytesPerCharacter;
|
||||
totalDataSize += bytesPerCharacter * 256;
|
||||
}
|
||||
|
||||
info.out.resize(totalDataSize);
|
||||
BytesView outFontImagesBytesView(info.out);
|
||||
|
||||
const std::vector<std::string> &DKRJP_FONT_CHARACTERS = DKRText::get_dkrjp_characters();
|
||||
|
||||
std::string charactersPtr = "/characters/";
|
||||
|
||||
// Second loop to build the data for each font.
|
||||
for(size_t fontIndex = 0; fontIndex < numberOfFonts; fontIndex++) {
|
||||
size_t bytesPerCharacter = headers[fontIndex].bytesPerCharacter;
|
||||
size_t sizeOfFont = bytesPerCharacter * 256;
|
||||
BytesView outFontImgBytesView = outFontImagesBytesView.get_sub_view(headers[fontIndex].offset, sizeOfFont);
|
||||
|
||||
// Already checked the existance of the file in the previous loop, so it *should* be safe to just do value() here.
|
||||
JsonFile &fontJsonFile = JsonHelper::get_file(fontJsonPaths[fontIndex]).value();
|
||||
bool hasAlphaImage = fontJsonFile.has("/image-alpha/filename");
|
||||
|
||||
std::string colorImageFormat = fontJsonFile.get_string("/image/format", "RGBA16");
|
||||
|
||||
fs::path colorImageFilepath = fontJsonFile.get_filepath().parent_path() / fontJsonFile.get_string("/image/filename");
|
||||
N64Image colorImage = ImageHelper::load_image(colorImageFilepath, colorImageFormat);
|
||||
colorImage.interlace();
|
||||
|
||||
N64Image alphaImage;
|
||||
std::string alphaImageFormat = fontJsonFile.get_string("/image-alpha/format", "I4");
|
||||
if(hasAlphaImage) {
|
||||
fs::path alphaImageFilepath = fontJsonFile.get_filepath().parent_path() / fontJsonFile.get_string("/image-alpha/filename");
|
||||
alphaImage = ImageHelper::load_image(alphaImageFilepath, alphaImageFormat);
|
||||
alphaImage.interlace();
|
||||
}
|
||||
|
||||
uint8_t colorImageFormatInt = DataHelper::vector_index_of<std::string>(TEXTURE_FORMAT_INT_TO_STRING, colorImageFormat);
|
||||
uint8_t alphaImageFormatInt = DataHelper::vector_index_of<std::string>(TEXTURE_FORMAT_INT_TO_STRING, alphaImageFormat);
|
||||
|
||||
for(size_t charIndex = 0; charIndex < 256; charIndex++) {
|
||||
std::string jpChar = DKRJP_FONT_CHARACTERS[charIndex];
|
||||
|
||||
if(jpChar.empty()) {
|
||||
jpChar = "<NULL>";
|
||||
} else if(jpChar == "/") {
|
||||
jpChar = "~1"; // Frontslash
|
||||
}
|
||||
|
||||
std::string charPtr = charactersPtr + jpChar;
|
||||
|
||||
BytesView outCharImgBytesView = outFontImgBytesView.get_sub_view(charIndex * bytesPerCharacter, bytesPerCharacter);
|
||||
JpFontChar *outChar = outCharImgBytesView.data_cast<JpFontChar>();
|
||||
|
||||
outChar->offsetToFirstImg = fontImageOffsets[fontIndex].first;
|
||||
outChar->offsetToSecondImg = fontImageOffsets[fontIndex].second;
|
||||
outChar->firstImageFormat = colorImageFormatInt;
|
||||
outChar->secondImageFormat = alphaImageFormatInt;
|
||||
outChar->spacing = fontJsonFile.get_int(charPtr + "/spacing", 0);
|
||||
// TODO: Calculate these automatically.
|
||||
outChar->left = fontJsonFile.get_int(charPtr + "/left", 0);
|
||||
outChar->top = fontJsonFile.get_int(charPtr + "/top", 0);
|
||||
outChar->right = fontJsonFile.get_int(charPtr + "/right", 0);
|
||||
outChar->bottom = fontJsonFile.get_int(charPtr + "/bottom", 0);
|
||||
|
||||
int x = (charIndex % 16) * headers[fontIndex].cellWidth;
|
||||
int y = (charIndex / 16) * headers[fontIndex].cellHeight;
|
||||
|
||||
N64Image::Region region = { x, y, x + headers[fontIndex].cellWidth, y + headers[fontIndex].cellHeight };
|
||||
|
||||
uint8_t *outCharColorData = (uint8_t*)outCharImgBytesView.data() + outChar->offsetToFirstImg;
|
||||
colorImage.copy_data_from_region(outCharColorData, headers[fontIndex].cellWidth, region);
|
||||
|
||||
if(hasAlphaImage) {
|
||||
uint8_t *outCharAlphaData = (uint8_t*)outCharImgBytesView.data() + outChar->offsetToSecondImg;
|
||||
alphaImage.copy_data_from_region(outCharAlphaData, headers[fontIndex].cellWidth, region);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add jp font table to build collection.
|
||||
collection.add_deferred_build_info("ASSET_JAPANESE_FONTS_TABLE", "ASSET_JAPANESE_FONTS_TABLE", outJpFontTable,
|
||||
info.get_dst_folder() / deferInfo.outputPath, info.get_info_context());
|
||||
}
|
||||
|
||||
void BuildJPFonts::build(BuildInfo &info) {
|
||||
const JsonFile &jsonFile = info.get_src_json_file();
|
||||
|
||||
//size_t numberOfFonts = jsonFile.length_of_array("/fonts-order");
|
||||
|
||||
//size_t outSize = FileHelper::align16(sizeof(be_uint32_t) + (numberOfFonts * sizeof(FontFile)));
|
||||
//info.out.resize(outSize);
|
||||
|
||||
// Note: The format is different in v79/v80. So gotta check if this is the beta version of the JP font.
|
||||
bool isBetaJpFont = StringHelper::ends_with(GlobalSettings::get_dkr_version(), "v77");
|
||||
|
||||
if(isBetaJpFont) {
|
||||
build_v77_beta_font(info, jsonFile);
|
||||
} else {
|
||||
// v79/v80
|
||||
build_v79_jp_fonts(info, jsonFile);
|
||||
}
|
||||
|
||||
if(info.build_to_file()) {
|
||||
info.write_out_to_dstPath();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
|
||||
namespace DkrAssetsTool {
|
||||
namespace BuildJPFonts {
|
||||
void build(BuildInfo &info);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,34 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "builder/buildInfo.h"
|
||||
#include "misc/settings.hpp"
|
||||
|
||||
#include "fileTypes/fonts.hpp"
|
||||
|
||||
#include "helpers/debugHelper.h"
|
||||
#include "helpers/fileHelper.h"
|
||||
#include "helpers/jsonHelper.h"
|
||||
#include "helpers/c/cContext.h"
|
||||
#include "fileTypes/levelHeader.hpp"
|
||||
|
||||
class BuildLevelHeader {
|
||||
public:
|
||||
BuildLevelHeader(DkrAssetsSettings &settings, BuildInfo &info);
|
||||
~BuildLevelHeader();
|
||||
private:
|
||||
DkrAssetsSettings &_settings;
|
||||
BuildInfo &_info;
|
||||
|
||||
CContext _c_context; // C Code context. (For loading enums & structs)
|
||||
|
||||
void _build_weather(std::string ptr, LevelHeader_Weather &weather);
|
||||
void _build_ai_levels(std::string ptr, LevelHeader_AiLevels &aiLevels);
|
||||
void _build_fog(std::string ptr, LevelHeader_Fog &fogInfo);
|
||||
|
||||
template<typename T>
|
||||
T _build_enum_bitfield(std::string ptr);
|
||||
|
||||
void _build_level_name();
|
||||
|
||||
void _preload_c_context();
|
||||
};
|
||||
namespace DkrAssetsTool {
|
||||
namespace BuildLevelHeader {
|
||||
void build(BuildInfo &info);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user