2023-08-17 00:40:00 -06:00
|
|
|
#include "Companion.h"
|
|
|
|
|
|
2024-01-30 23:03:23 -06:00
|
|
|
#include "utils/Decompressor.h"
|
2024-03-24 21:27:07 -06:00
|
|
|
#include "utils/TorchUtils.h"
|
|
|
|
|
#include "storm/SWrapper.h"
|
|
|
|
|
#include "spdlog/spdlog.h"
|
|
|
|
|
#include "hj/sha1.h"
|
|
|
|
|
|
|
|
|
|
#include <regex>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <filesystem>
|
2024-03-12 11:25:51 -05:00
|
|
|
|
2023-09-17 23:55:59 -06:00
|
|
|
#include "factories/BankFactory.h"
|
2023-11-15 15:11:15 -06:00
|
|
|
#include "factories/AudioHeaderFactory.h"
|
|
|
|
|
#include "factories/SampleFactory.h"
|
|
|
|
|
#include "factories/SequenceFactory.h"
|
2024-04-01 02:37:33 +01:00
|
|
|
#include "factories/GenericArrayFactory.h"
|
2023-11-15 15:11:15 -06:00
|
|
|
#include "factories/VtxFactory.h"
|
2024-03-23 01:57:53 -06:00
|
|
|
#include "factories/MtxFactory.h"
|
|
|
|
|
#include "factories/FloatFactory.h"
|
2024-03-24 21:27:07 -06:00
|
|
|
#include "factories/IncludeFactory.h"
|
2023-08-17 00:40:00 -06:00
|
|
|
#include "factories/TextureFactory.h"
|
2023-11-15 15:11:15 -06:00
|
|
|
#include "factories/DisplayListFactory.h"
|
2024-03-09 23:12:39 -06:00
|
|
|
#include "factories/DisplayListOverrides.h"
|
2023-11-15 15:11:15 -06:00
|
|
|
#include "factories/BlobFactory.h"
|
|
|
|
|
#include "factories/LightsFactory.h"
|
2024-03-16 15:37:37 -05:00
|
|
|
#include "factories/Vec3fFactory.h"
|
2024-03-26 18:02:12 -05:00
|
|
|
#include "factories/Vec3sFactory.h"
|
2024-03-24 21:27:07 -06:00
|
|
|
|
|
|
|
|
#include "factories/sm64/AnimationFactory.h"
|
2024-04-02 14:55:56 +01:00
|
|
|
#include "factories/sm64/CollisionFactory.h"
|
2024-03-24 21:27:07 -06:00
|
|
|
#include "factories/sm64/DialogFactory.h"
|
|
|
|
|
#include "factories/sm64/DictionaryFactory.h"
|
|
|
|
|
#include "factories/sm64/TextFactory.h"
|
|
|
|
|
#include "factories/sm64/GeoLayoutFactory.h"
|
2024-04-08 11:47:11 +01:00
|
|
|
#include "factories/sm64/MovtexFactory.h"
|
|
|
|
|
#include "factories/sm64/MovtexQuadFactory.h"
|
2024-03-24 21:27:07 -06:00
|
|
|
|
2024-03-06 11:46:19 -07:00
|
|
|
#include "factories/mk64/CourseVtx.h"
|
|
|
|
|
#include "factories/mk64/Waypoints.h"
|
|
|
|
|
#include "factories/mk64/TrackSections.h"
|
|
|
|
|
#include "factories/mk64/SpawnData.h"
|
2024-03-23 01:57:53 -06:00
|
|
|
#include "factories/mk64/DrivingBehaviour.h"
|
2024-04-06 15:36:03 -06:00
|
|
|
#include "factories/mk64/ItemCurve.h"
|
|
|
|
|
#include "factories/mk64/CourseMetadata.h"
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2024-03-13 15:56:30 -05:00
|
|
|
#include "factories/sf64/ColPolyFactory.h"
|
2024-03-10 20:15:27 -06:00
|
|
|
#include "factories/sf64/MessageFactory.h"
|
2024-03-10 21:22:26 -06:00
|
|
|
#include "factories/sf64/MessageLookupFactory.h"
|
2024-03-12 11:25:51 -05:00
|
|
|
#include "factories/sf64/SkeletonFactory.h"
|
|
|
|
|
#include "factories/sf64/AnimFactory.h"
|
|
|
|
|
#include "factories/sf64/ScriptFactory.h"
|
2024-03-12 17:16:29 -05:00
|
|
|
#include "factories/sf64/HitboxFactory.h"
|
2024-03-13 10:09:54 -05:00
|
|
|
#include "factories/sf64/EnvSettingsFactory.h"
|
2024-03-12 21:43:59 -06:00
|
|
|
#include "factories/sf64/ObjInitFactory.h"
|
2024-03-13 17:49:25 -05:00
|
|
|
#include "factories/sf64/TriangleFactory.h"
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2023-09-24 14:06:18 -06:00
|
|
|
using namespace std::chrono;
|
2023-11-15 15:11:15 -06:00
|
|
|
namespace fs = std::filesystem;
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
static const std::string regular = "[%Y-%m-%d %H:%M:%S.%e] [%l] %v";
|
|
|
|
|
static const std::string line = "[%Y-%m-%d %H:%M:%S.%e] [%l] > %v";
|
|
|
|
|
|
2024-03-20 21:17:11 -06:00
|
|
|
#define ABS(x) ((x) < 0 ? -(x) : (x))
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
void Companion::Init(const ExportType type) {
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2023-09-24 13:39:02 -06:00
|
|
|
spdlog::set_level(spdlog::level::debug);
|
|
|
|
|
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
|
|
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.exporterType = type;
|
2023-11-15 15:11:15 -06:00
|
|
|
this->RegisterFactory("BLOB", std::make_shared<BlobFactory>());
|
|
|
|
|
this->RegisterFactory("TEXTURE", std::make_shared<TextureFactory>());
|
|
|
|
|
this->RegisterFactory("VTX", std::make_shared<VtxFactory>());
|
2024-03-23 01:57:53 -06:00
|
|
|
this->RegisterFactory("MTX", std::make_shared<MtxFactory>());
|
|
|
|
|
this->RegisterFactory("F32", std::make_shared<FloatFactory>());
|
2024-03-24 21:27:07 -06:00
|
|
|
this->RegisterFactory("INC", std::make_shared<IncludeFactory>());
|
2023-11-15 15:11:15 -06:00
|
|
|
this->RegisterFactory("LIGHTS", std::make_shared<LightsFactory>());
|
|
|
|
|
this->RegisterFactory("GFX", std::make_shared<DListFactory>());
|
|
|
|
|
this->RegisterFactory("AUDIO:HEADER", std::make_shared<AudioHeaderFactory>());
|
|
|
|
|
this->RegisterFactory("SEQUENCE", std::make_shared<SequenceFactory>());
|
|
|
|
|
this->RegisterFactory("SAMPLE", std::make_shared<SampleFactory>());
|
|
|
|
|
this->RegisterFactory("BANK", std::make_shared<BankFactory>());
|
2024-03-16 15:37:37 -05:00
|
|
|
this->RegisterFactory("VEC3F", std::make_shared<Vec3fFactory>());
|
2024-03-26 18:02:12 -05:00
|
|
|
this->RegisterFactory("VEC3S", std::make_shared<Vec3sFactory>());
|
2024-04-01 02:37:33 +01:00
|
|
|
this->RegisterFactory("ARRAY", std::make_shared<GenericArrayFactory>());
|
2023-09-17 23:55:59 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
// SM64 specific
|
|
|
|
|
this->RegisterFactory("SM64:DIALOG", std::make_shared<SM64::DialogFactory>());
|
|
|
|
|
this->RegisterFactory("SM64:TEXT", std::make_shared<SM64::TextFactory>());
|
|
|
|
|
this->RegisterFactory("SM64:DICTIONARY", std::make_shared<SM64::DictionaryFactory>());
|
|
|
|
|
this->RegisterFactory("SM64:ANIM", std::make_shared<SM64::AnimationFactory>());
|
2024-04-02 14:55:56 +01:00
|
|
|
this->RegisterFactory("SM64:COLLISION", std::make_shared<SM64::CollisionFactory>());
|
2023-11-21 00:27:23 -06:00
|
|
|
this->RegisterFactory("SM64:GEO_LAYOUT", std::make_shared<SM64::GeoLayoutFactory>());
|
2024-04-08 11:47:11 +01:00
|
|
|
this->RegisterFactory("SM64:MOVTEX_QUAD", std::make_shared<SM64::MovtexQuadFactory>());
|
|
|
|
|
this->RegisterFactory("SM64:MOVTEX", std::make_shared<SM64::MovtexFactory>());
|
2023-09-17 23:55:59 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
// MK64 specific
|
2024-03-13 10:21:41 -06:00
|
|
|
this->RegisterFactory("MK64:COURSE_VTX", std::make_shared<MK64::CourseVtxFactory>());
|
|
|
|
|
this->RegisterFactory("MK64:TRACK_WAYPOINTS", std::make_shared<MK64::WaypointsFactory>());
|
|
|
|
|
this->RegisterFactory("MK64:TRACK_SECTIONS", std::make_shared<MK64::TrackSectionsFactory>());
|
|
|
|
|
this->RegisterFactory("MK64:SPAWN_DATA", std::make_shared<MK64::SpawnDataFactory>());
|
2024-03-23 01:57:53 -06:00
|
|
|
this->RegisterFactory("MK64:DRIVING_BEHAVIOUR", std::make_shared<MK64::DrivingBehaviourFactory>());
|
2024-04-06 15:36:03 -06:00
|
|
|
this->RegisterFactory("MK64:ITEM_CURVE", std::make_shared<MK64::ItemCurveFactory>()); // Item curve for decomp only
|
|
|
|
|
this->RegisterFactory("MK64:METADATA", std::make_shared<MK64::CourseMetadataFactory>());
|
2023-09-21 09:52:11 -06:00
|
|
|
|
2024-03-10 19:26:21 -05:00
|
|
|
// SF64 specific
|
2024-03-10 20:43:44 -05:00
|
|
|
this->RegisterFactory("SF64:ANIM", std::make_shared<SF64::AnimFactory>());
|
2024-03-11 10:58:08 -05:00
|
|
|
this->RegisterFactory("SF64:SKELETON", std::make_shared<SF64::SkeletonFactory>());
|
2024-03-10 20:15:27 -06:00
|
|
|
this->RegisterFactory("SF64:MESSAGE", std::make_shared<SF64::MessageFactory>());
|
2024-03-10 21:22:26 -06:00
|
|
|
this->RegisterFactory("SF64:MSG_TABLE", std::make_shared<SF64::MessageLookupFactory>());
|
2024-03-12 11:25:51 -05:00
|
|
|
this->RegisterFactory("SF64:SCRIPT", std::make_shared<SF64::ScriptFactory>());
|
2024-03-12 17:16:29 -05:00
|
|
|
this->RegisterFactory("SF64:HITBOX", std::make_shared<SF64::HitboxFactory>());
|
2024-03-13 10:11:54 -06:00
|
|
|
this->RegisterFactory("SF64:ENV_SETTINGS", std::make_shared<SF64::EnvSettingsFactory>());
|
2024-03-12 21:43:59 -06:00
|
|
|
this->RegisterFactory("SF64:OBJECT_INIT", std::make_shared<SF64::ObjInitFactory>());
|
2024-03-13 15:56:30 -05:00
|
|
|
this->RegisterFactory("SF64:COLPOLY", std::make_shared<SF64::ColPolyFactory>());
|
2024-03-13 17:49:25 -05:00
|
|
|
this->RegisterFactory("SF64:TRIANGLE", std::make_shared<SF64::TriangleFactory>());
|
2024-03-12 11:25:51 -05:00
|
|
|
|
2023-09-17 23:55:59 -06:00
|
|
|
this->Process();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 21:43:59 -06:00
|
|
|
void Companion::ParseEnums(std::string& header) {
|
|
|
|
|
std::ifstream file(header);
|
|
|
|
|
|
|
|
|
|
if (!file.is_open()) {
|
2024-04-06 15:36:03 -06:00
|
|
|
throw std::runtime_error("Failed to open header files for enums node in config");
|
2024-03-12 21:43:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::regex enumRegex(R"(enum\s+(\w+)\s*(?:\s*:\s*(\w+))?[\s\n\r]*\{)");
|
|
|
|
|
|
|
|
|
|
std::string line;
|
|
|
|
|
std::smatch match;
|
|
|
|
|
std::string enumName;
|
|
|
|
|
|
|
|
|
|
bool inEnum = false;
|
2024-03-13 15:56:30 -05:00
|
|
|
int enumIndex;
|
2024-03-12 21:43:59 -06:00
|
|
|
while (std::getline(file, line)) {
|
|
|
|
|
if (!inEnum && std::regex_search(line, match, enumRegex) && match.size() > 1) {
|
|
|
|
|
enumName = match.str(1);
|
|
|
|
|
inEnum = true;
|
2024-03-13 15:56:30 -05:00
|
|
|
enumIndex = -1;
|
2024-03-12 21:43:59 -06:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!inEnum) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(line.find("}") != std::string::npos) {
|
|
|
|
|
inEnum = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove any comments and non-alphanumeric characters
|
2024-03-13 15:56:30 -05:00
|
|
|
line = std::regex_replace(line, std::regex(R"((/\*.*?\*/)|(//.*$)|([^a-zA-Z0-9=_\-\.]))"), "");
|
2024-03-12 21:43:59 -06:00
|
|
|
|
|
|
|
|
if(line.find("=") != std::string::npos) {
|
|
|
|
|
auto value = line.substr(line.find("=") + 1);
|
2024-03-13 15:56:30 -05:00
|
|
|
auto name = line.substr(0, line.find("="));
|
|
|
|
|
enumIndex = std::stoi(value);
|
|
|
|
|
this->gEnums[enumName][enumIndex] = name;
|
2024-03-12 21:43:59 -06:00
|
|
|
} else {
|
2024-03-13 15:56:30 -05:00
|
|
|
enumIndex++;
|
|
|
|
|
this->gEnums[enumName][enumIndex] = line;
|
2024-03-12 21:43:59 -06:00
|
|
|
}
|
2024-03-20 21:17:11 -06:00
|
|
|
|
2024-03-12 21:43:59 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-31 10:58:38 -06:00
|
|
|
std::optional<ParseResultData> Companion::ParseNode(YAML::Node& node, std::string& name) {
|
2024-01-30 23:03:23 -06:00
|
|
|
auto type = GetSafeNode<std::string>(node, "type");
|
2023-11-15 15:11:15 -06:00
|
|
|
std::transform(type.begin(), type.end(), type.begin(), ::toupper);
|
|
|
|
|
|
2024-01-31 11:17:29 -06:00
|
|
|
spdlog::set_pattern(regular);
|
2023-11-21 00:27:23 -06:00
|
|
|
if(node["offset"]) {
|
|
|
|
|
auto offset = node["offset"].as<uint32_t>();
|
2024-01-31 11:17:29 -06:00
|
|
|
SPDLOG_INFO("- [{}] Processing {} at 0x{:X}", type, name, offset);
|
2023-11-21 00:27:23 -06:00
|
|
|
} else {
|
2024-01-31 11:17:29 -06:00
|
|
|
SPDLOG_INFO("- [{}] Processing {}", type, name);
|
2023-11-21 00:27:23 -06:00
|
|
|
}
|
2024-01-31 11:17:29 -06:00
|
|
|
spdlog::set_pattern(line);
|
2023-11-21 00:27:23 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
auto factory = this->GetFactory(type);
|
|
|
|
|
if(!factory.has_value()){
|
2024-03-24 21:27:07 -06:00
|
|
|
throw std::runtime_error("No factory by the name '"+type+"' found for '"+name+"'");
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
auto impl = factory->get();
|
|
|
|
|
|
2024-03-15 09:16:22 -06:00
|
|
|
auto exporter = impl->GetExporter(this->gConfig.exporterType);
|
|
|
|
|
if(!exporter.has_value()){
|
|
|
|
|
SPDLOG_WARN("No exporter found for {}", name);
|
2024-03-31 10:58:38 -06:00
|
|
|
return std::nullopt;
|
2024-03-15 09:16:22 -06:00
|
|
|
}
|
|
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
std::optional<std::shared_ptr<IParsedData>> result;
|
2024-03-29 03:02:20 -06:00
|
|
|
if(this->gConfig.modding && impl->SupportModdedAssets() && this->gModdedAssetPaths.contains(name)) {
|
|
|
|
|
auto path = fs::path(this->gConfig.moddingPath) / this->gModdedAssetPaths[name];
|
|
|
|
|
if(!fs::exists(path)) {
|
|
|
|
|
SPDLOG_ERROR("Modded asset {} not found", this->gModdedAssetPaths[name]);
|
2024-03-31 10:58:38 -06:00
|
|
|
return std::nullopt;
|
2024-03-29 03:02:20 -06:00
|
|
|
}
|
2024-02-21 12:51:47 -06:00
|
|
|
|
2024-03-29 03:02:20 -06:00
|
|
|
std::ifstream input(path, std::ios::binary);
|
|
|
|
|
std::vector<uint8_t> data = std::vector<uint8_t>( std::istreambuf_iterator( input ), {});
|
|
|
|
|
input.close();
|
2024-02-21 12:51:47 -06:00
|
|
|
|
2024-03-29 03:02:20 -06:00
|
|
|
result = impl->parse_modding(data, node);
|
2024-02-21 12:51:47 -06:00
|
|
|
} else {
|
2024-03-29 03:02:20 -06:00
|
|
|
result = impl->parse(this->gRomData, node);
|
2024-02-21 12:51:47 -06:00
|
|
|
}
|
2024-03-29 03:02:20 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
if(!result.has_value()){
|
|
|
|
|
SPDLOG_ERROR("Failed to process {}", name);
|
2024-03-31 10:58:38 -06:00
|
|
|
return std::nullopt;
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto [fst, snd] : this->gAssetDependencies[this->gCurrentFile]) {
|
2024-01-30 01:25:46 -06:00
|
|
|
if(snd.second) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
std::string doutput = (this->gCurrentDirectory / fst).string();
|
|
|
|
|
std::replace(doutput.begin(), doutput.end(), '\\', '/');
|
|
|
|
|
this->gAssetDependencies[this->gCurrentFile][fst].second = true;
|
2024-04-12 23:52:30 -06:00
|
|
|
auto result = this->ParseNode(snd.first, doutput);
|
|
|
|
|
if(result.has_value()) {
|
|
|
|
|
this->gParseResults[this->gCurrentFile].push_back(result.value());
|
|
|
|
|
}
|
2024-01-31 11:17:29 -06:00
|
|
|
spdlog::set_pattern(regular);
|
|
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
|
|
|
|
spdlog::set_pattern(line);
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SPDLOG_INFO("Processed {}", name);
|
2024-03-31 10:58:38 -06:00
|
|
|
|
|
|
|
|
return ParseResultData {
|
|
|
|
|
name, type, node, result
|
|
|
|
|
};
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
void Companion::ParseModdingConfig() {
|
|
|
|
|
auto path = fs::path(this->gConfig.moddingPath) / "modding.yml";
|
|
|
|
|
if(!fs::exists(path)) {
|
|
|
|
|
throw std::runtime_error("No modding config found, please run in export mode first");
|
|
|
|
|
}
|
2024-02-21 21:05:28 -06:00
|
|
|
auto modding = YAML::LoadFile(path.string());
|
2024-02-21 12:51:47 -06:00
|
|
|
for(auto assets = modding["assets"].begin(); assets != modding["assets"].end(); ++assets) {
|
|
|
|
|
auto name = assets->first.as<std::string>();
|
|
|
|
|
auto asset = assets->second.as<std::string>();
|
|
|
|
|
this->gModdedAssetPaths[name] = asset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
void Companion::ParseCurrentFileConfig(YAML::Node node) {
|
|
|
|
|
if(node["segments"]) {
|
2024-03-08 13:24:58 -07:00
|
|
|
auto segments = node["segments"];
|
|
|
|
|
|
|
|
|
|
// Set global variables for segmented data
|
|
|
|
|
if (segments.IsSequence() && segments.size()) {
|
|
|
|
|
if (segments[0].IsSequence() && segments[0].size() == 2) {
|
|
|
|
|
gCurrentSegmentNumber = segments[0][0].as<uint32_t>();
|
|
|
|
|
gCurrentFileOffset = segments[0][1].as<uint32_t>();
|
|
|
|
|
gCurrentCompressionType = GetCompressionType(this->gRomData, gCurrentFileOffset);
|
|
|
|
|
} else {
|
2024-03-09 22:42:25 -07:00
|
|
|
throw std::runtime_error("Incorrect yaml syntax for segments.\n\nThe yaml expects:\n:config:\n segments:\n - [<segment>, <file_offset>]\n\nLike so:\nsegments:\n - [0x06, 0x821D10]");
|
2024-03-08 13:24:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set file offset for later use.
|
|
|
|
|
for(size_t i = 0; i < segments.size(); i++) {
|
|
|
|
|
auto segment = segments[i];
|
|
|
|
|
if (segment.IsSequence() && segment.size() == 2) {
|
|
|
|
|
const auto id = segment[0].as<uint32_t>();
|
|
|
|
|
const auto replacement = segment[1].as<uint32_t>();
|
|
|
|
|
this->gConfig.segment.local[id] = replacement;
|
|
|
|
|
SPDLOG_DEBUG("Segment {} replaced with 0x{:X}", id, replacement);
|
|
|
|
|
} else {
|
2024-03-09 22:42:25 -07:00
|
|
|
throw std::runtime_error("Incorrect yaml syntax for segments.\n\nThe yaml expects:\n:config:\n segments:\n - [<segment>, <file_offset>]\n\nLike so:\nsegments:\n - [0x06, 0x821D10]");
|
2024-03-08 13:24:58 -07:00
|
|
|
}
|
2024-01-30 01:25:46 -06:00
|
|
|
}
|
|
|
|
|
}
|
2024-03-10 23:41:36 -06:00
|
|
|
|
2024-02-16 22:52:11 -06:00
|
|
|
if(node["header"]) {
|
2024-02-18 16:54:21 -06:00
|
|
|
auto header = node["header"];
|
|
|
|
|
switch (this->gConfig.exporterType) {
|
|
|
|
|
case ExportType::Header: {
|
|
|
|
|
if(header["header"].IsSequence()) {
|
|
|
|
|
for(auto line = header["header"].begin(); line != header["header"].end(); ++line) {
|
|
|
|
|
this->gFileHeader += line->as<std::string>() + "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ExportType::Code: {
|
|
|
|
|
if(header["code"].IsSequence()) {
|
|
|
|
|
for(auto line = header["code"].begin(); line != header["code"].end(); ++line) {
|
|
|
|
|
this->gFileHeader += line->as<std::string>() + "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: break;
|
2024-02-16 22:52:11 -06:00
|
|
|
}
|
|
|
|
|
}
|
2024-03-10 23:41:36 -06:00
|
|
|
|
|
|
|
|
if(node["tables"]){
|
|
|
|
|
for(auto table = node["tables"].begin(); table != node["tables"].end(); ++table){
|
|
|
|
|
auto name = table->first.as<std::string>();
|
|
|
|
|
auto range = table->second["range"].as<std::vector<uint32_t>>();
|
|
|
|
|
auto start = gCurrentSegmentNumber ? gCurrentSegmentNumber << 24 | range[0] : range[0];
|
|
|
|
|
auto end = gCurrentSegmentNumber ? gCurrentSegmentNumber << 24 | range[1] : range[1];
|
|
|
|
|
auto mode = GetSafeNode<std::string>(table->second, "mode", "APPEND");
|
|
|
|
|
TableMode tMode = mode == "REFERENCE" ? TableMode::Reference : TableMode::Append;
|
|
|
|
|
this->gTables.push_back({name, start, end, tMode});
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-12 19:09:40 -06:00
|
|
|
|
|
|
|
|
if(node["vram"]){
|
|
|
|
|
auto vram = node["vram"];
|
|
|
|
|
const auto addr = GetSafeNode<uint32_t>(vram, "addr");
|
|
|
|
|
const auto offset = GetSafeNode<uint32_t>(vram, "offset");
|
|
|
|
|
this->gCurrentVram = { addr, offset };
|
|
|
|
|
}
|
2024-03-20 21:17:11 -06:00
|
|
|
|
|
|
|
|
this->gEnablePadGen = GetSafeNode<bool>(node, "autopads", true);
|
2024-03-29 01:07:00 -06:00
|
|
|
this->gNodeForceProcessing = GetSafeNode<bool>(node, "force", false);
|
2024-03-29 03:02:20 -06:00
|
|
|
}
|
|
|
|
|
|
2024-03-29 01:07:00 -06:00
|
|
|
void Companion::ParseHash() {
|
|
|
|
|
const std::string out = "torch.hash.yml";
|
2024-03-29 03:02:56 -06:00
|
|
|
|
|
|
|
|
if(fs::exists(out)) {
|
2024-03-29 01:07:00 -06:00
|
|
|
this->gHashNode = YAML::LoadFile(out);
|
2024-03-29 03:02:56 -06:00
|
|
|
} else {
|
2024-03-29 01:07:00 -06:00
|
|
|
this->gHashNode = YAML::Node();
|
2024-03-29 03:02:56 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 01:07:00 -06:00
|
|
|
std::string ExportTypeToString(ExportType type) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case ExportType::Binary: return "Binary";
|
|
|
|
|
case ExportType::Header: return "Header";
|
|
|
|
|
case ExportType::Code: return "Code";
|
|
|
|
|
case ExportType::Modding: return "Modding";
|
|
|
|
|
default:
|
|
|
|
|
throw std::runtime_error("Invalid ExportType");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Companion::NodeHasChanges(const std::string& path) {
|
|
|
|
|
|
2024-03-29 01:28:24 -06:00
|
|
|
if(this->gConfig.modding) {
|
2024-03-29 01:07:00 -06:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 03:02:20 -06:00
|
|
|
std::ifstream yaml(path);
|
|
|
|
|
const std::vector<uint8_t> data = std::vector<uint8_t>(std::istreambuf_iterator( yaml ), {});
|
2024-03-29 01:07:00 -06:00
|
|
|
this->gCurrentHash = CalculateHash(data);
|
2024-03-29 01:28:24 -06:00
|
|
|
bool needsInit = true;
|
2024-03-29 03:02:20 -06:00
|
|
|
|
2024-03-29 01:07:00 -06:00
|
|
|
if(this->gHashNode[path]) {
|
|
|
|
|
auto entry = GetSafeNode<YAML::Node>(this->gHashNode, path);
|
|
|
|
|
const auto hash = GetSafeNode<std::string>(entry, "hash");
|
|
|
|
|
auto modes = GetSafeNode<YAML::Node>(entry, "extracted");
|
|
|
|
|
auto extracted = GetSafeNode<bool>(modes, ExportTypeToString(this->gConfig.exporterType));
|
2024-03-28 23:38:32 -06:00
|
|
|
|
2024-03-29 01:28:24 -06:00
|
|
|
if(hash == this->gCurrentHash) {
|
|
|
|
|
needsInit = false;
|
|
|
|
|
if(extracted) {
|
|
|
|
|
SPDLOG_INFO("Skipping {} as it has not changed", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-03-29 03:02:20 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 01:28:24 -06:00
|
|
|
if(needsInit) {
|
|
|
|
|
this->gHashNode[path] = YAML::Node();
|
|
|
|
|
this->gHashNode[path]["hash"] = this->gCurrentHash;
|
|
|
|
|
this->gHashNode[path]["extracted"] = YAML::Node();
|
|
|
|
|
for(size_t m = 0; m <= static_cast<size_t>(ExportType::Modding); m++) {
|
|
|
|
|
this->gHashNode[path]["extracted"][ExportTypeToString(static_cast<ExportType>(m))] = false;
|
|
|
|
|
}
|
2024-03-28 23:38:32 -06:00
|
|
|
}
|
|
|
|
|
|
2024-03-29 01:07:00 -06:00
|
|
|
return true;
|
2024-01-30 01:25:46 -06:00
|
|
|
}
|
|
|
|
|
|
2024-04-06 15:36:03 -06:00
|
|
|
void Companion::LoadYAMLRecursively(const std::string &dirPath, std::vector<YAML::Node> &result, bool skipRoot) {
|
|
|
|
|
for (const auto &entry : std::filesystem::directory_iterator(dirPath)) {
|
|
|
|
|
if (entry.is_directory()) {
|
|
|
|
|
// Skip the root directory if specified
|
|
|
|
|
if (skipRoot && entry.path() == dirPath) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recursive call for subdirectories
|
2024-04-08 18:17:09 -06:00
|
|
|
LoadYAMLRecursively(entry.path().string(), result, false);
|
2024-04-06 15:36:03 -06:00
|
|
|
} else if (entry.path().extension() == ".yaml" || entry.path().extension() == ".yml") {
|
|
|
|
|
// Load YAML file and add it to the result vector
|
|
|
|
|
result.push_back(YAML::LoadFile(entry.path().string()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Config yaml requires tables: [assets/courses]
|
|
|
|
|
* Activate the factory using a normal asset yaml with type, input_directory, and output_directory nodes.
|
|
|
|
|
*/
|
|
|
|
|
void Companion::ProcessTables(YAML::Node& rom) {
|
|
|
|
|
auto dirs = rom["metadata"].as<std::vector<std::string>>();
|
|
|
|
|
|
|
|
|
|
for (const auto &dir : dirs) {
|
|
|
|
|
std::vector<YAML::Node> configNodes;
|
|
|
|
|
LoadYAMLRecursively(dir, configNodes, true);
|
|
|
|
|
gCourseMetadata[dir] = configNodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write yaml data to console
|
|
|
|
|
if (this->IsDebug()) {
|
|
|
|
|
SPDLOG_INFO("------ Metadata ouptut ------");
|
|
|
|
|
for (auto &node : gCourseMetadata[dirs[0]]) {
|
|
|
|
|
std::cout << node << std::endl;
|
|
|
|
|
SPDLOG_INFO("------------");
|
|
|
|
|
}
|
|
|
|
|
SPDLOG_INFO("------ Metadata end ------");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-17 23:55:59 -06:00
|
|
|
void Companion::Process() {
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
if(!fs::exists("config.yml")) {
|
|
|
|
|
SPDLOG_ERROR("No config file found");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-09-24 14:26:01 -06:00
|
|
|
|
2023-09-24 14:06:18 -06:00
|
|
|
auto start = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
|
|
|
|
|
2023-08-17 00:40:00 -06:00
|
|
|
std::ifstream input( this->gRomPath, std::ios::binary );
|
2023-11-15 15:11:15 -06:00
|
|
|
this->gRomData = std::vector<uint8_t>( std::istreambuf_iterator( input ), {} );
|
2023-08-17 00:40:00 -06:00
|
|
|
input.close();
|
|
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gCartridge = std::make_shared<N64::Cartridge>(this->gRomData);
|
|
|
|
|
this->gCartridge->Initialize();
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2023-09-17 23:55:59 -06:00
|
|
|
YAML::Node config = YAML::LoadFile("config.yml");
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
if(!config[this->gCartridge->GetHash()]){
|
|
|
|
|
SPDLOG_ERROR("No config found for {}", this->gCartridge->GetHash());
|
2023-09-17 23:55:59 -06:00
|
|
|
return;
|
|
|
|
|
}
|
2023-09-01 00:51:51 -06:00
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
auto rom = config[this->gCartridge->GetHash()];
|
2023-11-15 15:11:15 -06:00
|
|
|
auto cfg = rom["config"];
|
|
|
|
|
|
|
|
|
|
if(!cfg) {
|
2024-01-30 01:25:46 -06:00
|
|
|
SPDLOG_ERROR("No config found for {}", this->gCartridge->GetHash());
|
2023-11-15 15:11:15 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-06 15:36:03 -06:00
|
|
|
if (rom["metadata"]) {
|
|
|
|
|
ProcessTables(rom);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
if(rom["segments"]) {
|
2024-01-30 01:25:46 -06:00
|
|
|
auto segments = rom["segments"].as<std::vector<uint32_t>>();
|
|
|
|
|
for (int i = 0; i < segments.size(); i++) {
|
|
|
|
|
this->gConfig.segment.global[i + 1] = segments[i];
|
|
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
auto path = rom["path"].as<std::string>();
|
|
|
|
|
auto opath = cfg["output"];
|
|
|
|
|
auto gbi = cfg["gbi"];
|
2024-02-21 12:51:47 -06:00
|
|
|
auto modding_path = opath && opath["modding"] ? opath["modding"].as<std::string>() : "modding";
|
2023-11-15 15:11:15 -06:00
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
this->gConfig.moddingPath = modding_path;
|
2024-01-30 01:25:46 -06:00
|
|
|
switch (this->gConfig.exporterType) {
|
2023-11-15 15:11:15 -06:00
|
|
|
case ExportType::Binary: {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.outputPath = opath && opath["binary"] ? opath["binary"].as<std::string>() : "generic.otr";
|
2023-11-15 15:11:15 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ExportType::Header: {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.outputPath = opath && opath["headers"] ? opath["headers"].as<std::string>() : "headers";
|
2023-11-15 15:11:15 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ExportType::Code: {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.outputPath = opath && opath["code"] ? opath["code"].as<std::string>() : "code";
|
2023-11-15 15:11:15 -06:00
|
|
|
break;
|
|
|
|
|
}
|
2024-02-21 12:51:47 -06:00
|
|
|
case ExportType::Modding: {
|
|
|
|
|
this->gConfig.outputPath = modding_path;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(gbi) {
|
|
|
|
|
auto key = gbi.as<std::string>();
|
|
|
|
|
|
|
|
|
|
if(key == "F3D") {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.gbi.version = GBIVersion::f3d;
|
2023-11-15 15:11:15 -06:00
|
|
|
} else if(key == "F3DEX") {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.gbi.version = GBIVersion::f3dex;
|
2023-11-15 15:11:15 -06:00
|
|
|
} else if(key == "F3DB") {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.gbi.version = GBIVersion::f3db;
|
2023-11-15 15:11:15 -06:00
|
|
|
} else if(key == "F3DEX2") {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.gbi.version = GBIVersion::f3dex2;
|
2023-11-15 15:11:15 -06:00
|
|
|
} else if(key == "F3DEXB") {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.gbi.version = GBIVersion::f3dexb;
|
2023-11-15 15:11:15 -06:00
|
|
|
} else if (key == "F3DEX_MK64") {
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gConfig.gbi.version = GBIVersion::f3dex;
|
|
|
|
|
this->gConfig.gbi.subversion = GBIMinorVersion::Mk64;
|
2023-11-15 15:11:15 -06:00
|
|
|
} else {
|
|
|
|
|
SPDLOG_ERROR("Invalid GBI version");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(auto sort = cfg["sort"]) {
|
|
|
|
|
if(sort.IsSequence()) {
|
|
|
|
|
this->gWriteOrder = sort.as<std::vector<std::string>>();
|
|
|
|
|
} else {
|
|
|
|
|
this->gWriteOrder = sort.as<std::string>();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this->gWriteOrder = std::vector<std::string> {
|
|
|
|
|
"LIGHTS", "TEXTURE", "VTX", "GFX"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
if(this->gConfig.exporterType == ExportType::Code && this->gConfig.modding) {
|
|
|
|
|
this->ParseModdingConfig();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
if(std::holds_alternative<std::vector<std::string>>(this->gWriteOrder)) {
|
|
|
|
|
for (auto& [key, _] : this->gFactories) {
|
|
|
|
|
auto entries = std::get<std::vector<std::string>>(this->gWriteOrder);
|
|
|
|
|
|
|
|
|
|
if(std::find(entries.begin(), entries.end(), key) != entries.end()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entries.push_back(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2024-03-12 21:43:59 -06:00
|
|
|
if(cfg["enums"]) {
|
|
|
|
|
auto enums = GetSafeNode<std::vector<std::string>>(cfg, "enums");
|
|
|
|
|
for (auto& file : enums) {
|
|
|
|
|
this->ParseEnums(file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 22:42:00 -06:00
|
|
|
if(cfg["logging"]){
|
|
|
|
|
auto level = cfg["logging"].as<std::string>();
|
|
|
|
|
if(level == "TRACE") {
|
|
|
|
|
spdlog::set_level(spdlog::level::trace);
|
|
|
|
|
} else if(level == "DEBUG") {
|
|
|
|
|
spdlog::set_level(spdlog::level::debug);
|
|
|
|
|
} else if(level == "INFO") {
|
|
|
|
|
spdlog::set_level(spdlog::level::info);
|
|
|
|
|
} else if(level == "WARN") {
|
|
|
|
|
spdlog::set_level(spdlog::level::warn);
|
|
|
|
|
} else if(level == "ERROR") {
|
|
|
|
|
spdlog::set_level(spdlog::level::err);
|
|
|
|
|
} else if(level == "CRITICAL") {
|
|
|
|
|
spdlog::set_level(spdlog::level::critical);
|
|
|
|
|
} else if(level == "OFF") {
|
|
|
|
|
spdlog::set_level(spdlog::level::off);
|
|
|
|
|
} else {
|
|
|
|
|
throw std::runtime_error("Invalid logging level, please use TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL or OFF");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 01:07:00 -06:00
|
|
|
this->ParseHash();
|
2024-03-29 03:02:56 -06:00
|
|
|
|
2023-09-24 14:06:18 -06:00
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
2023-09-24 14:26:01 -06:00
|
|
|
spdlog::set_pattern(line);
|
2023-09-24 13:39:02 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
SPDLOG_INFO("Starting Torch...");
|
2024-01-30 01:25:46 -06:00
|
|
|
SPDLOG_INFO("Game: {}", this->gCartridge->GetGameTitle());
|
|
|
|
|
SPDLOG_INFO("CRC: {}", this->gCartridge->GetCRC());
|
|
|
|
|
SPDLOG_INFO("Version: {}", this->gCartridge->GetVersion());
|
|
|
|
|
SPDLOG_INFO("Country: [{}]", this->gCartridge->GetCountryCode());
|
|
|
|
|
SPDLOG_INFO("Hash: {}", this->gCartridge->GetHash());
|
2023-09-24 13:39:02 -06:00
|
|
|
SPDLOG_INFO("Assets: {}", path);
|
|
|
|
|
|
2024-02-01 17:37:13 -06:00
|
|
|
AudioManager::Instance = new AudioManager();
|
2024-01-30 01:25:46 -06:00
|
|
|
auto wrapper = this->gConfig.exporterType == ExportType::Binary ? new SWrapper(this->gConfig.outputPath) : nullptr;
|
2024-03-31 10:58:38 -06:00
|
|
|
this->gCurrentWrapper = wrapper;
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2023-09-18 03:39:03 -06:00
|
|
|
auto vWriter = LUS::BinaryWriter();
|
|
|
|
|
vWriter.SetEndianness(LUS::Endianness::Big);
|
2023-11-15 15:11:15 -06:00
|
|
|
vWriter.Write(static_cast<uint8_t>(LUS::Endianness::Big));
|
2024-01-30 01:25:46 -06:00
|
|
|
vWriter.Write(this->gCartridge->GetCRC());
|
2023-09-18 03:39:03 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
for (const auto & entry : fs::recursive_directory_iterator(path)){
|
2024-01-30 01:25:46 -06:00
|
|
|
if(entry.is_directory()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto yamlPath = entry.path().string();
|
|
|
|
|
|
|
|
|
|
if(yamlPath.find(".yaml") == std::string::npos && yamlPath.find(".yml") == std::string::npos) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
YAML::Node root = YAML::LoadFile(yamlPath);
|
2023-11-15 15:11:15 -06:00
|
|
|
this->gCurrentDirectory = relative(entry.path(), path).replace_extension("");
|
2024-01-30 01:25:46 -06:00
|
|
|
this->gCurrentFile = yamlPath;
|
2023-08-17 00:40:00 -06:00
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
// Set compressed file offsets and compression type
|
|
|
|
|
if (auto segments = root[":config"]["segments"]) {
|
|
|
|
|
if (segments.IsSequence() && segments.size() > 0) {
|
|
|
|
|
if (segments[0].IsSequence() && segments[0].size() == 2) {
|
|
|
|
|
gCurrentSegmentNumber = segments[0][0].as<uint32_t>();
|
|
|
|
|
gCurrentFileOffset = segments[0][1].as<uint32_t>();
|
|
|
|
|
gCurrentCompressionType = GetCompressionType(this->gRomData, gCurrentFileOffset);
|
|
|
|
|
} else {
|
2024-03-09 22:42:25 -07:00
|
|
|
throw std::runtime_error("Incorrect yaml syntax for segments.\n\nThe yaml expects:\n:config:\n segments:\n - [<segment>, <file_offset>]\n\nLike so:\nsegments:\n - [0x06, 0x821D10]");
|
2024-03-08 13:24:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
for(auto asset = root.begin(); asset != root.end(); ++asset){
|
|
|
|
|
auto node = asset->second;
|
2024-02-01 17:37:13 -06:00
|
|
|
auto entryName = asset->first.as<std::string>();
|
2024-01-30 01:25:46 -06:00
|
|
|
|
2024-02-01 17:37:13 -06:00
|
|
|
// Parse horizontal assets
|
|
|
|
|
if(node["files"]){
|
|
|
|
|
auto segment = node["segment"] ? node["segment"].as<uint8_t>() : -1;
|
|
|
|
|
const auto files = node["files"];
|
|
|
|
|
for (const auto& file : files) {
|
|
|
|
|
auto assetNode = file.as<YAML::Node>();
|
|
|
|
|
auto childName = assetNode["name"].as<std::string>();
|
|
|
|
|
auto output = (this->gCurrentDirectory / entryName / childName).string();
|
|
|
|
|
std::replace(output.begin(), output.end(), '\\', '/');
|
|
|
|
|
|
|
|
|
|
if(assetNode["type"]){
|
|
|
|
|
const auto type = GetSafeNode<std::string>(assetNode, "type");
|
|
|
|
|
if(type == "SAMPLE"){
|
|
|
|
|
AudioManager::Instance->bind_sample(assetNode, output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!assetNode["offset"]) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
if(segment != -1 || gCurrentSegmentNumber) {
|
2024-02-01 17:37:13 -06:00
|
|
|
assetNode["offset"] = (segment << 24) | assetNode["offset"].as<uint32_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->gAddrMap[this->gCurrentFile][assetNode["offset"].as<uint32_t>()] = std::make_tuple(output, assetNode);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-03-08 13:24:58 -07:00
|
|
|
|
2024-02-01 17:37:13 -06:00
|
|
|
auto output = (this->gCurrentDirectory / entryName).string();
|
|
|
|
|
std::replace(output.begin(), output.end(), '\\', '/');
|
|
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
|
2024-02-01 17:37:13 -06:00
|
|
|
if(node["type"]){
|
|
|
|
|
const auto type = GetSafeNode<std::string>(node, "type");
|
|
|
|
|
if(type == "SAMPLE"){
|
|
|
|
|
AudioManager::Instance->bind_sample(node, output);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!node["offset"]) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
if(gCurrentSegmentNumber) {
|
|
|
|
|
if (IS_SEGMENTED(node["offset"].as<uint32_t>()) == false) {
|
|
|
|
|
node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as<uint32_t>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-01 17:37:13 -06:00
|
|
|
this->gAddrMap[this->gCurrentFile][node["offset"].as<uint32_t>()] = std::make_tuple(output, node);
|
2024-01-30 01:25:46 -06:00
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stupid hack because the iteration broke the assets
|
2024-01-30 01:25:46 -06:00
|
|
|
root = YAML::LoadFile(yamlPath);
|
|
|
|
|
this->gConfig.segment.local.clear();
|
2024-02-16 22:52:11 -06:00
|
|
|
this->gFileHeader.clear();
|
2024-03-05 00:09:30 -06:00
|
|
|
this->gCurrentPad = 0;
|
2024-03-12 19:09:40 -06:00
|
|
|
this->gCurrentVram = std::nullopt;
|
|
|
|
|
this->gCurrentSegmentNumber = 0;
|
2024-03-22 10:53:17 -06:00
|
|
|
this->gCurrentCompressionType = CompressionType::None;
|
2024-03-10 23:41:36 -06:00
|
|
|
this->gTables.clear();
|
2024-03-09 23:12:39 -06:00
|
|
|
GFXDOverride::ClearVtx();
|
2024-01-30 01:25:46 -06:00
|
|
|
|
|
|
|
|
if(root[":config"]) {
|
|
|
|
|
this->ParseCurrentFileConfig(root[":config"]);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 01:07:00 -06:00
|
|
|
if(!this->NodeHasChanges(yamlPath) && !this->gNodeForceProcessing) {
|
|
|
|
|
continue;
|
2024-03-29 03:02:20 -06:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
spdlog::set_pattern(regular);
|
|
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
|
|
|
|
spdlog::set_pattern(line);
|
2023-11-15 15:11:15 -06:00
|
|
|
|
|
|
|
|
for(auto asset = root.begin(); asset != root.end(); ++asset){
|
2023-08-19 23:55:59 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
auto entryName = asset->first.as<std::string>();
|
2024-01-31 10:26:10 -06:00
|
|
|
auto assetNode = asset->second;
|
2023-11-15 15:11:15 -06:00
|
|
|
|
2024-01-31 10:26:10 -06:00
|
|
|
if(entryName.find(":config") != std::string::npos) {
|
2024-01-30 01:25:46 -06:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 10:26:10 -06:00
|
|
|
// Parse horizontal assets
|
|
|
|
|
if(assetNode["files"]){
|
|
|
|
|
auto segment = assetNode["segment"] ? assetNode["segment"].as<uint8_t>() : -1;
|
|
|
|
|
auto files = assetNode["files"];
|
|
|
|
|
for (const auto& file : files) {
|
|
|
|
|
auto node = file.as<YAML::Node>();
|
|
|
|
|
auto childName = node["name"].as<std::string>();
|
2024-01-30 01:25:46 -06:00
|
|
|
|
2024-01-31 10:26:10 -06:00
|
|
|
if(!node["offset"]) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
if(segment != -1 || gCurrentFileOffset) {
|
2024-01-31 10:26:10 -06:00
|
|
|
node["offset"] = (segment << 24) | node["offset"].as<uint32_t>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto output = (this->gCurrentDirectory / entryName / childName).string();
|
|
|
|
|
std::replace(output.begin(), output.end(), '\\', '/');
|
|
|
|
|
this->gConfig.segment.temporal.clear();
|
2024-04-12 23:52:30 -06:00
|
|
|
auto result = this->ParseNode(node, output);
|
|
|
|
|
if(result.has_value()) {
|
|
|
|
|
this->gParseResults[this->gCurrentFile].push_back(result.value());
|
|
|
|
|
}
|
2024-01-31 10:26:10 -06:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-03-22 12:36:59 -06:00
|
|
|
if(gCurrentFileOffset && assetNode["offset"]) {
|
2024-03-22 11:30:11 -06:00
|
|
|
const auto offset = assetNode["offset"].as<uint32_t>();
|
2024-03-22 09:59:49 -06:00
|
|
|
if (!IS_SEGMENTED(offset)) {
|
2024-03-12 19:09:40 -06:00
|
|
|
assetNode["offset"] = (gCurrentSegmentNumber << 24) | offset;
|
2024-03-08 13:24:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-31 10:26:10 -06:00
|
|
|
std::string output = (this->gCurrentDirectory / entryName).string();
|
|
|
|
|
std::replace(output.begin(), output.end(), '\\', '/');
|
|
|
|
|
this->gConfig.segment.temporal.clear();
|
2024-04-12 23:52:30 -06:00
|
|
|
auto result = this->ParseNode(assetNode, output);
|
|
|
|
|
if(result.has_value()) {
|
|
|
|
|
this->gParseResults[this->gCurrentFile].push_back(result.value());
|
|
|
|
|
}
|
2024-01-31 10:26:10 -06:00
|
|
|
}
|
2024-01-30 01:25:46 -06:00
|
|
|
|
|
|
|
|
spdlog::set_pattern(regular);
|
|
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
|
|
|
|
spdlog::set_pattern(line);
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
2024-03-31 10:58:38 -06:00
|
|
|
for(auto& result : this->gParseResults[this->gCurrentFile]){
|
|
|
|
|
std::ostringstream stream;
|
|
|
|
|
ExportResult endptr = std::nullopt;
|
|
|
|
|
WriteEntry wEntry;
|
|
|
|
|
|
|
|
|
|
auto data = result.data.value();
|
|
|
|
|
const auto impl = this->GetFactory(result.type)->get();
|
2024-04-12 23:52:30 -06:00
|
|
|
const auto exporter = impl->GetExporter(this->gConfig.exporterType);
|
|
|
|
|
|
|
|
|
|
if(exporter == nullptr) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-03-31 10:58:38 -06:00
|
|
|
|
|
|
|
|
switch (this->gConfig.exporterType) {
|
|
|
|
|
case ExportType::Binary: {
|
|
|
|
|
stream.str("");
|
|
|
|
|
stream.clear();
|
2024-04-12 23:52:30 -06:00
|
|
|
exporter->get()->Export(stream, data, result.name, result.node, &result.name);
|
2024-03-31 10:58:38 -06:00
|
|
|
auto data = stream.str();;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ExportType::Modding: {
|
|
|
|
|
stream.str("");
|
|
|
|
|
stream.clear();
|
|
|
|
|
std::string ogname = result.name;
|
2024-04-12 23:52:30 -06:00
|
|
|
exporter->get()->Export(stream, data, result.name, result.node, &result.name);
|
2024-03-31 10:58:38 -06:00
|
|
|
|
|
|
|
|
auto data = stream.str();
|
|
|
|
|
if(data.empty()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string dpath = Instance->GetOutputPath() + "/" + result.name;
|
|
|
|
|
if(!exists(fs::path(dpath).parent_path())){
|
|
|
|
|
create_directories(fs::path(dpath).parent_path());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->gModdedAssetPaths[ogname] = result.name;
|
|
|
|
|
|
|
|
|
|
std::ofstream file(dpath, std::ios::binary);
|
|
|
|
|
file.write(data.c_str(), data.size());
|
|
|
|
|
file.close();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
2024-04-12 23:52:30 -06:00
|
|
|
endptr = exporter->get()->Export(stream, data, result.name, result.node, &result.name);
|
2024-03-31 10:58:38 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(result.node["offset"]) {
|
|
|
|
|
auto alignment = GetSafeNode<uint32_t>(result.node, "alignment", impl->GetAlignment());
|
|
|
|
|
if(!endptr.has_value()) {
|
|
|
|
|
wEntry = {
|
|
|
|
|
result.node["offset"].as<uint32_t>(),
|
|
|
|
|
alignment,
|
|
|
|
|
stream.str(),
|
|
|
|
|
std::nullopt
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
switch (endptr->index()) {
|
|
|
|
|
case 0:
|
|
|
|
|
wEntry = {
|
|
|
|
|
result.node["offset"].as<uint32_t>(),
|
|
|
|
|
alignment,
|
|
|
|
|
stream.str(),
|
|
|
|
|
std::get<size_t>(endptr.value())
|
|
|
|
|
};
|
|
|
|
|
break;
|
|
|
|
|
case 1: {
|
|
|
|
|
const auto oentry = std::get<OffsetEntry>(endptr.value());
|
|
|
|
|
wEntry = {
|
|
|
|
|
oentry.start,
|
|
|
|
|
alignment,
|
|
|
|
|
stream.str(),
|
|
|
|
|
oentry.end
|
|
|
|
|
};
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
SPDLOG_ERROR("Invalid endptr index {}", endptr->index());
|
|
|
|
|
SPDLOG_ERROR("Type of endptr: {}", typeid(endptr).name());
|
|
|
|
|
throw std::runtime_error("We should never reach this point");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->gWriteMap[this->gCurrentFile][result.type].push_back(wEntry);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
auto fsout = fs::path(this->gConfig.outputPath);
|
|
|
|
|
|
|
|
|
|
if(this->gConfig.exporterType == ExportType::Modding) {
|
|
|
|
|
fsout /= "modding.yml";
|
|
|
|
|
YAML::Node modding;
|
|
|
|
|
|
|
|
|
|
for (const auto& [key, value] : this->gModdedAssetPaths) {
|
|
|
|
|
modding["assets"][key] = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::ofstream file(fsout.string(), std::ios::binary);
|
|
|
|
|
file << modding;
|
|
|
|
|
file.close();
|
|
|
|
|
} else if(this->gConfig.exporterType != ExportType::Binary){
|
2024-02-18 16:54:21 -06:00
|
|
|
std::string filename = this->gCurrentDirectory.filename().string();
|
2023-11-15 15:11:15 -06:00
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
switch (this->gConfig.exporterType) {
|
2023-11-15 15:11:15 -06:00
|
|
|
case ExportType::Header: {
|
2024-02-18 16:54:21 -06:00
|
|
|
fsout /= filename + ".h";
|
2023-11-15 15:11:15 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ExportType::Code: {
|
2024-02-18 19:03:58 -06:00
|
|
|
fsout /= this->gCurrentDirectory / (filename + ".c");
|
2023-11-15 15:11:15 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::ostringstream stream;
|
|
|
|
|
|
2024-03-20 21:17:11 -06:00
|
|
|
std::vector<WriteEntry> entries;
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
if(std::holds_alternative<std::string>(this->gWriteOrder)) {
|
|
|
|
|
auto sort = std::get<std::string>(this->gWriteOrder);
|
2024-03-20 21:17:11 -06:00
|
|
|
for (const auto& [type, raw] : this->gWriteMap[this->gCurrentFile]) {
|
|
|
|
|
entries.insert(entries.end(), raw.begin(), raw.end());
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(sort == "OFFSET") {
|
2024-03-20 21:17:11 -06:00
|
|
|
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
|
|
|
|
|
return a.addr < b.addr;
|
2023-11-15 15:11:15 -06:00
|
|
|
});
|
|
|
|
|
} else if(sort == "ROFFSET") {
|
2024-03-20 21:17:11 -06:00
|
|
|
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
|
|
|
|
|
return a.addr > b.addr;
|
2023-11-15 15:11:15 -06:00
|
|
|
});
|
|
|
|
|
} else if(sort != "LINEAR") {
|
|
|
|
|
throw std::runtime_error("Invalid write order");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (const auto& type : std::get<std::vector<std::string>>(this->gWriteOrder)) {
|
2024-03-20 21:17:11 -06:00
|
|
|
entries = this->gWriteMap[this->gCurrentFile][type];
|
2023-11-15 15:11:15 -06:00
|
|
|
|
2024-03-20 21:17:11 -06:00
|
|
|
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
|
|
|
|
|
return a.addr > b.addr;
|
2023-11-15 15:11:15 -06:00
|
|
|
});
|
2024-03-20 21:17:11 -06:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
|
2024-03-20 21:17:11 -06:00
|
|
|
for (size_t i = 0; i < entries.size(); i++) {
|
|
|
|
|
const auto result = entries[i];
|
2024-03-22 09:59:49 -06:00
|
|
|
const auto hasSize = result.endptr.has_value();
|
|
|
|
|
if (hasSize && this->IsDebug()) {
|
|
|
|
|
stream << "// 0x" << std::hex << std::uppercase << ASSET_PTR(result.addr) << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-20 21:17:11 -06:00
|
|
|
stream << result.buffer;
|
|
|
|
|
|
2024-03-22 09:59:49 -06:00
|
|
|
if (hasSize && this->IsDebug()) {
|
|
|
|
|
stream << "// 0x" << std::hex << std::uppercase << ASSET_PTR(result.endptr.value()) << "\n\n";
|
|
|
|
|
}
|
2024-03-20 21:17:11 -06:00
|
|
|
|
2024-03-22 09:59:49 -06:00
|
|
|
if(hasSize && i < entries.size() - 1 && this->gConfig.exporterType == ExportType::Code){
|
2024-03-27 09:35:45 -06:00
|
|
|
int32_t startptr = ASSET_PTR(result.endptr.value());
|
|
|
|
|
int32_t end = ASSET_PTR(entries[i + 1].addr);
|
2024-03-20 21:17:11 -06:00
|
|
|
|
|
|
|
|
uint32_t alignment = entries[i + 1].alignment;
|
|
|
|
|
int32_t gap = end - startptr;
|
|
|
|
|
|
2024-03-27 09:35:45 -06:00
|
|
|
if(gap < 0) {
|
|
|
|
|
stream << "// WARNING: Overlap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << std::abs(gap) << "\n";
|
|
|
|
|
SPDLOG_WARN("Overlap detected between 0x{:X} and 0x{:X} with size 0x{:X} on file {}", startptr, end, gap, this->gCurrentFile);
|
|
|
|
|
} else if(gap < 0x10 && gap >= alignment && end % 0x10 == 0 && this->gEnablePadGen) {
|
2024-03-20 21:17:11 -06:00
|
|
|
SPDLOG_WARN("Gap detected between 0x{:X} and 0x{:X} with size 0x{:X} on file {}", startptr, end, gap, this->gCurrentFile);
|
|
|
|
|
SPDLOG_WARN("Creating pad of 0x{:X} bytes", gap);
|
|
|
|
|
const auto padfile = this->gCurrentDirectory.filename().string();
|
|
|
|
|
if(this->IsDebug()){
|
2024-03-21 13:31:25 -05:00
|
|
|
stream << "// 0x" << std::hex << std::uppercase << startptr << "\n";
|
2024-03-20 21:17:11 -06:00
|
|
|
}
|
|
|
|
|
stream << "char pad_" << padfile << "_" << std::to_string(gCurrentPad++) << "[] = {\n" << tab;
|
|
|
|
|
auto gapSize = gap & ~3;
|
|
|
|
|
for(size_t j = 0; j < gapSize; j++){
|
|
|
|
|
stream << "0x00, ";
|
|
|
|
|
}
|
|
|
|
|
stream << "\n};\n";
|
|
|
|
|
if(this->IsDebug()){
|
2024-03-21 13:31:25 -05:00
|
|
|
stream << "// 0x" << std::hex << std::uppercase << end << "\n\n";
|
2024-03-20 21:17:11 -06:00
|
|
|
} else {
|
|
|
|
|
stream << "\n";
|
|
|
|
|
}
|
|
|
|
|
} else if(gap > 0x10) {
|
2024-03-27 09:35:45 -06:00
|
|
|
stream << "// WARNING: Gap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << gap << "\n";
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-20 21:17:11 -06:00
|
|
|
this->gWriteMap.clear();
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
std::string buffer = stream.str();
|
|
|
|
|
|
|
|
|
|
if(buffer.empty()) {
|
2023-09-17 23:55:59 -06:00
|
|
|
continue;
|
2023-08-30 21:26:38 -06:00
|
|
|
}
|
2023-09-17 23:55:59 -06:00
|
|
|
|
2024-02-18 16:54:21 -06:00
|
|
|
std::string output = fsout.string();
|
2023-09-24 03:28:51 -06:00
|
|
|
std::replace(output.begin(), output.end(), '\\', '/');
|
2023-11-15 15:11:15 -06:00
|
|
|
if(!exists(fs::path(output).parent_path())){
|
|
|
|
|
create_directories(fs::path(output).parent_path());
|
2023-09-17 23:55:59 -06:00
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
std::ofstream file(output, std::ios::binary);
|
2023-09-17 23:55:59 -06:00
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
if(this->gConfig.exporterType == ExportType::Header) {
|
2023-11-17 09:39:13 -05:00
|
|
|
std::string symbol = entry.path().stem().string();
|
2023-11-15 15:11:15 -06:00
|
|
|
std::transform(symbol.begin(), symbol.end(), symbol.begin(), toupper);
|
|
|
|
|
file << "#ifndef " << symbol << "_H" << std::endl;
|
|
|
|
|
file << "#define " << symbol << "_H" << std::endl << std::endl;
|
2024-02-18 16:54:21 -06:00
|
|
|
if(!this->gFileHeader.empty()) {
|
|
|
|
|
file << this->gFileHeader << std::endl;
|
|
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
file << buffer;
|
|
|
|
|
file << std::endl << "#endif" << std::endl;
|
|
|
|
|
} else {
|
2024-02-16 22:52:11 -06:00
|
|
|
if(!this->gFileHeader.empty()) {
|
|
|
|
|
file << this->gFileHeader << std::endl;
|
|
|
|
|
}
|
2023-11-15 15:11:15 -06:00
|
|
|
file << buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.close();
|
2023-08-19 23:55:59 -06:00
|
|
|
}
|
2024-03-29 03:10:01 -06:00
|
|
|
|
2024-04-08 18:09:12 -06:00
|
|
|
if(this->gConfig.exporterType != ExportType::Binary) {
|
|
|
|
|
this->gHashNode[this->gCurrentFile]["extracted"][ExportTypeToString(this->gConfig.exporterType)] = true;
|
|
|
|
|
}
|
2023-08-17 00:40:00 -06:00
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
if(wrapper != nullptr) {
|
|
|
|
|
SPDLOG_INFO("Writing version file");
|
|
|
|
|
wrapper->CreateFile("version", vWriter.ToVector());
|
|
|
|
|
vWriter.Close();
|
|
|
|
|
wrapper->Close();
|
|
|
|
|
}
|
2024-03-29 01:07:00 -06:00
|
|
|
|
|
|
|
|
// Write entries hash
|
|
|
|
|
std::ofstream file("torch.hash.yml", std::ios::binary);
|
|
|
|
|
file << this->gHashNode;
|
|
|
|
|
file.close();
|
|
|
|
|
|
2023-09-24 14:06:18 -06:00
|
|
|
auto end = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
2024-03-12 22:42:00 -06:00
|
|
|
auto level = spdlog::get_level();
|
|
|
|
|
spdlog::set_level(spdlog::level::info);
|
2023-09-24 14:06:18 -06:00
|
|
|
SPDLOG_INFO("Done! Took {}ms", end.count() - start.count());
|
2024-03-12 22:42:00 -06:00
|
|
|
spdlog::set_level(level);
|
2023-09-24 14:26:01 -06:00
|
|
|
spdlog::set_pattern(regular);
|
2023-09-24 13:39:02 -06:00
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
2023-09-18 03:39:03 -06:00
|
|
|
|
2024-01-30 23:03:23 -06:00
|
|
|
Decompressor::ClearCache();
|
2023-11-15 15:11:15 -06:00
|
|
|
this->gCartridge = nullptr;
|
|
|
|
|
Instance = nullptr;
|
2023-09-17 23:55:59 -06:00
|
|
|
}
|
2023-10-07 21:07:04 -06:00
|
|
|
|
|
|
|
|
void Companion::Pack(const std::string& folder, const std::string& output) {
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
spdlog::set_level(spdlog::level::debug);
|
|
|
|
|
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
SPDLOG_INFO("Starting Torch...");
|
|
|
|
|
SPDLOG_INFO("Scanning {}", folder);
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
auto start = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
|
|
|
|
std::unordered_map<std::string, std::vector<char>> files;
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
for (const auto & entry : fs::recursive_directory_iterator(folder)){
|
2024-01-30 01:25:46 -06:00
|
|
|
if(entry.is_directory()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
std::ifstream input( entry.path(), std::ios::binary );
|
|
|
|
|
auto data = std::vector( std::istreambuf_iterator( input ), {} );
|
|
|
|
|
input.close();
|
|
|
|
|
files[entry.path().string()] = data;
|
|
|
|
|
}
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
auto wrapper = SWrapper(output);
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
for(auto& [path, data] : files){
|
|
|
|
|
std::string normalized = path;
|
|
|
|
|
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
|
|
|
|
// Remove parent folder
|
|
|
|
|
normalized = normalized.substr(folder.length() + 1);
|
|
|
|
|
wrapper.CreateFile(normalized, data);
|
|
|
|
|
SPDLOG_INFO("> Added {}", normalized);
|
|
|
|
|
}
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
auto end = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
|
|
|
|
SPDLOG_INFO("Done! Took {}ms", end.count() - start.count());
|
|
|
|
|
SPDLOG_INFO("Exported to {}", output);
|
|
|
|
|
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
|
|
|
|
|
SPDLOG_INFO("------------------------------------------------");
|
2023-10-07 21:07:04 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
wrapper.Close();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 00:27:23 -06:00
|
|
|
std::optional<std::tuple<std::string, YAML::Node>> Companion::RegisterAsset(const std::string& name, YAML::Node& node) {
|
2024-03-12 19:09:40 -06:00
|
|
|
if(!node["offset"]) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
2023-11-21 00:27:23 -06:00
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
this->gAssetDependencies[this->gCurrentFile][name] = std::make_pair(node, false);
|
|
|
|
|
|
|
|
|
|
auto output = (this->gCurrentDirectory / name).string();
|
|
|
|
|
std::replace(output.begin(), output.end(), '\\', '/');
|
2023-11-21 00:27:23 -06:00
|
|
|
|
|
|
|
|
auto entry = std::make_tuple(output, node);
|
|
|
|
|
this->gAddrMap[this->gCurrentFile][node["offset"].as<uint32_t>()] = entry;
|
|
|
|
|
|
2024-03-28 23:38:32 -06:00
|
|
|
|
|
|
|
|
|
2023-11-21 00:27:23 -06:00
|
|
|
return entry;
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Companion::RegisterFactory(const std::string& type, const std::shared_ptr<BaseFactory>& factory) {
|
|
|
|
|
this->gFactories[type] = factory;
|
|
|
|
|
SPDLOG_DEBUG("Registered factory for {}", type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<std::shared_ptr<BaseFactory>> Companion::GetFactory(const std::string &type) {
|
|
|
|
|
if(!this->gFactories.contains(type)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this->gFactories[type];
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
/**
|
|
|
|
|
* @param offset Rom offset of compressed mio0 file.
|
|
|
|
|
* @returns CompressionType
|
|
|
|
|
*/
|
|
|
|
|
CompressionType Companion::GetCompressionType(std::vector<uint8_t>& buffer, const uint32_t offset) {
|
|
|
|
|
if (offset) {
|
|
|
|
|
LUS::BinaryReader reader((char*) buffer.data() + offset, sizeof(uint32_t));
|
|
|
|
|
reader.SetEndianness(LUS::Endianness::Big);
|
|
|
|
|
|
|
|
|
|
const std::string header = reader.ReadCString();
|
|
|
|
|
|
|
|
|
|
// Check if a compressed header exists
|
|
|
|
|
if (header == "MIO0") {
|
|
|
|
|
return CompressionType::MIO0;
|
|
|
|
|
} else if (header == "YAY0") {
|
|
|
|
|
return CompressionType::YAY0;
|
|
|
|
|
} else if (header == "YAZ0") {
|
|
|
|
|
return CompressionType::YAZ0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return CompressionType::None;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-10 23:41:36 -06:00
|
|
|
std::optional<Table> Companion::SearchTable(uint32_t addr){
|
|
|
|
|
for(auto& table : this->gTables){
|
2024-04-06 15:36:03 -06:00
|
|
|
if(addr >= table.start && addr <= table.end){
|
2024-03-10 23:41:36 -06:00
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 21:43:59 -06:00
|
|
|
std::optional<std::string> Companion::GetEnumFromValue(const std::string& key, int32_t id) {
|
|
|
|
|
if(!this->gEnums.contains(key)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!this->gEnums[key].contains(id)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this->gEnums[key][id];
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-08 13:24:58 -07:00
|
|
|
std::optional<std::uint32_t> Companion::GetFileOffsetFromSegmentedAddr(const uint8_t segment) const {
|
2023-11-22 10:49:30 -06:00
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
auto segments = this->gConfig.segment;
|
|
|
|
|
|
|
|
|
|
if(segments.temporal.contains(segment)) {
|
|
|
|
|
return segments.temporal[segment];
|
2023-11-22 10:49:30 -06:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
if(segments.local.contains(segment)) {
|
|
|
|
|
return segments.local[segment];
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 01:25:46 -06:00
|
|
|
if(segments.global.contains(segment)) {
|
|
|
|
|
return segments.global[segment];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
2023-11-15 15:11:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<std::tuple<std::string, YAML::Node>> Companion::GetNodeByAddr(const uint32_t addr){
|
|
|
|
|
if(!this->gAddrMap.contains(this->gCurrentFile)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!this->gAddrMap[this->gCurrentFile].contains(addr)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this->gAddrMap[this->gCurrentFile][addr];
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 00:26:45 -06:00
|
|
|
std::optional<ParseResultData> Companion::GetParseDataByAddr(uint32_t addr) {
|
|
|
|
|
if(!this->gParseResults.contains(this->gCurrentFile)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto& result : this->gParseResults[this->gCurrentFile]){
|
|
|
|
|
if(result.data.has_value() && result.GetOffset() == addr){
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<ParseResultData> Companion::GetParseDataBySymbol(const std::string& symbol) {
|
|
|
|
|
if(!this->gParseResults.contains(this->gCurrentFile)){
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto& result : this->gParseResults[this->gCurrentFile]){
|
|
|
|
|
auto sym = GetNode<std::string>(result.node, "symbol");
|
|
|
|
|
|
|
|
|
|
if(result.data.has_value() && sym.has_value() && sym.value() == symbol){
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-09 23:12:39 -06:00
|
|
|
std::optional<std::vector<std::tuple<std::string, YAML::Node>>> Companion::GetNodesByType(const std::string& type){
|
|
|
|
|
std::vector<std::tuple<std::string, YAML::Node>> nodes;
|
|
|
|
|
|
|
|
|
|
if(!this->gAddrMap.contains(this->gCurrentFile)){
|
|
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto& [addr, tpl] : this->gAddrMap[this->gCurrentFile]){
|
|
|
|
|
auto [name, node] = tpl;
|
|
|
|
|
const auto n_type = GetSafeNode<std::string>(node, "type");
|
2024-03-10 15:51:51 -06:00
|
|
|
if(node["autogen"]){
|
|
|
|
|
SPDLOG_DEBUG("Skipping autogenerated asset {}", name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-03-09 23:12:39 -06:00
|
|
|
if(n_type == type){
|
|
|
|
|
nodes.push_back(tpl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nodes;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 15:11:15 -06:00
|
|
|
std::string Companion::NormalizeAsset(const std::string& name) const {
|
|
|
|
|
auto path = fs::path(this->gCurrentFile).stem().string() + "_" + name;
|
|
|
|
|
return path;
|
2024-02-21 12:51:47 -06:00
|
|
|
}
|
|
|
|
|
|
2024-04-06 12:28:51 -06:00
|
|
|
std::string Companion::RelativePath(const std::string& path) const {
|
|
|
|
|
std::string doutput = (this->gCurrentDirectory / path).string();
|
|
|
|
|
std::replace(doutput.begin(), doutput.end(), '\\', '/');
|
|
|
|
|
return doutput;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 12:51:47 -06:00
|
|
|
std::string Companion::CalculateHash(const std::vector<uint8_t>& data) {
|
|
|
|
|
return Chocobo1::SHA1().addData(data).finalize().toString();
|
2024-03-16 15:37:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string ConvertType(std::string type) {
|
|
|
|
|
int index;
|
|
|
|
|
|
|
|
|
|
if((index = type.find(':')) != std::string::npos) {
|
|
|
|
|
type = type.substr(index + 1);
|
|
|
|
|
}
|
|
|
|
|
type = std::regex_replace(type, std::regex(R"([^_A-Za-z0-9]*)"), "");
|
|
|
|
|
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
|
|
|
|
|
return type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<YAML::Node> Companion::AddAsset(YAML::Node asset) {
|
|
|
|
|
if(!asset["offset"] || !asset["type"]) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
const auto type = GetSafeNode<std::string>(asset, "type");
|
|
|
|
|
const auto offset = GetSafeNode<uint32_t>(asset, "offset");
|
|
|
|
|
const auto symbol = GetSafeNode<std::string>(asset, "symbol", "");
|
|
|
|
|
const auto decl = this->GetNodeByAddr(offset);
|
|
|
|
|
|
2024-03-26 18:02:12 -05:00
|
|
|
|
2024-03-16 15:37:37 -05:00
|
|
|
if(decl.has_value()) {
|
|
|
|
|
return std::get<1>(decl.value());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto rom = this->GetRomData();
|
2024-03-26 00:11:46 -06:00
|
|
|
auto factory = this->GetFactory(type);
|
2024-03-29 03:02:20 -06:00
|
|
|
|
2024-03-26 00:11:46 -06:00
|
|
|
if(!factory.has_value()) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
2024-03-16 15:37:37 -05:00
|
|
|
|
|
|
|
|
std::string output;
|
|
|
|
|
std::string typeId = ConvertType(type);
|
2024-03-20 21:17:11 -06:00
|
|
|
|
2024-03-16 15:37:37 -05:00
|
|
|
if(symbol != "") {
|
|
|
|
|
output = symbol;
|
|
|
|
|
} else if(Decompressor::IsSegmented(offset)){
|
|
|
|
|
output = this->NormalizeAsset("seg" + std::to_string(SEGMENT_NUMBER(offset)) +"_" + typeId + "_" + Torch::to_hex(SEGMENT_OFFSET(offset), false));
|
|
|
|
|
} else {
|
|
|
|
|
output = this->NormalizeAsset(typeId + "_" + Torch::to_hex(offset, false));
|
|
|
|
|
}
|
|
|
|
|
asset["autogen"] = true;
|
|
|
|
|
asset["symbol"] = output;
|
|
|
|
|
|
2024-03-26 00:11:46 -06:00
|
|
|
auto result = this->RegisterAsset(output, asset);
|
2024-03-16 15:37:37 -05:00
|
|
|
|
|
|
|
|
if(result.has_value()){
|
2024-03-26 00:11:46 -06:00
|
|
|
asset["path"] = std::get<0>(result.value());
|
2024-03-26 18:02:12 -05:00
|
|
|
|
2024-03-26 00:11:46 -06:00
|
|
|
return std::get<1>(result.value());
|
2024-03-16 15:37:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::nullopt;
|
2024-04-06 15:36:03 -06:00
|
|
|
}
|