diff --git a/src/Companion.cpp b/src/Companion.cpp index 61cd1289..dc205d53 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -344,10 +344,30 @@ void Companion::ParseCurrentFileConfig(YAML::Node node) { } else if (std::filesystem::relative(externalFileName, this->gAssetPath).string() == "") { throw std::runtime_error("External File " + externalFileName + " Not In Asset Directory " + this->gAssetPath); } - auto localCurrentDirectory = std::filesystem::relative(externalFileName, this->gAssetPath).replace_extension(""); if (!this->gAddrMap.contains(externalFileName)) { - SPDLOG_WARN("External File: {} Not Found in Global Address Map", externalFileName); + SPDLOG_INFO("Dependency on external file {}. Now processing {}", externalFileName, externalFileName); + auto currentFile = this->gCurrentFile; + auto currentDirectory = this->gCurrentDirectory; + auto currentSegmentNumber = gCurrentSegmentNumber; + auto currentFileOffset = gCurrentFileOffset; + auto currentCompressionType = gCurrentCompressionType; + + this->gCurrentFile = externalFileName; + this->gCurrentDirectory = std::filesystem::relative(externalFileName, this->gAssetPath).replace_extension(""); + + YAML::Node root = YAML::LoadFile(externalFileName); + + ProcessFile(root); + + SPDLOG_INFO("Finishing processing of file: {}", currentFile); + + this->gCurrentFile = currentFile; + this->gCurrentDirectory = currentDirectory; + + gCurrentSegmentNumber = currentSegmentNumber; + gCurrentFileOffset = currentFileOffset; + gCurrentCompressionType = currentCompressionType; } } } @@ -458,6 +478,418 @@ void Companion::ProcessTables(YAML::Node& rom) { } } +void Companion::ProcessFile(YAML::Node root) { + // 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(); + gCurrentFileOffset = segments[0][1].as(); + gCurrentCompressionType = GetCompressionType(this->gRomData, gCurrentFileOffset); + } else { + throw std::runtime_error("Incorrect yaml syntax for segments.\n\nThe yaml expects:\n:config:\n segments:\n - [, ]\n\nLike so:\nsegments:\n - [0x06, 0x821D10]"); + } + } + } + + for(auto asset = root.begin(); asset != root.end(); ++asset){ + auto node = asset->second; + auto entryName = asset->first.as(); + + // Parse horizontal assets + if(node["files"]){ + auto segment = node["segment"] ? node["segment"].as() : -1; + const auto files = node["files"]; + for (const auto& file : files) { + auto assetNode = file.as(); + auto childName = assetNode["name"].as(); + auto output = (this->gCurrentDirectory / entryName / childName).string(); + std::replace(output.begin(), output.end(), '\\', '/'); + + if(assetNode["type"]){ + const auto type = GetSafeNode(assetNode, "type"); + if(type == "SAMPLE"){ + AudioManager::Instance->bind_sample(assetNode, output); + } + } + + if(!assetNode["offset"]) { + continue; + } + + if(segment != -1 || gCurrentSegmentNumber) { + assetNode["offset"] = (segment << 24) | assetNode["offset"].as(); + } + + this->gAddrMap[this->gCurrentFile][assetNode["offset"].as()] = std::make_tuple(output, assetNode); + } + } else { + + auto output = (this->gCurrentDirectory / entryName).string(); + std::replace(output.begin(), output.end(), '\\', '/'); + + + if(node["type"]){ + const auto type = GetSafeNode(node, "type"); + if(type == "SAMPLE"){ + AudioManager::Instance->bind_sample(node, output); + } + } + + if(!node["offset"]) { + continue; + } + + if(gCurrentSegmentNumber) { + if (IS_SEGMENTED(node["offset"].as()) == false) { + node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as(); + } + } + + this->gAddrMap[this->gCurrentFile][node["offset"].as()] = std::make_tuple(output, node); + } + } + + // Stupid hack because the iteration broke the assets + root = YAML::LoadFile(this->gCurrentFile); + this->gConfig.segment.local.clear(); + this->gFileHeader.clear(); + this->gCurrentPad = 0; + this->gCurrentVram = std::nullopt; + this->gCurrentSegmentNumber = 0; + this->gCurrentCompressionType = CompressionType::None; + this->gCurrentFileOffset = 0; + this->gTables.clear(); + this->gCurrentExternalFiles.clear(); + GFXDOverride::ClearVtx(); + + if(root[":config"]) { + this->ParseCurrentFileConfig(root[":config"]); + } + + if(!this->NodeHasChanges(this->gCurrentFile) && !this->gNodeForceProcessing) { + return; + } + + spdlog::set_pattern(regular); + SPDLOG_INFO("------------------------------------------------"); + spdlog::set_pattern(line); + + for(auto asset = root.begin(); asset != root.end(); ++asset){ + + auto entryName = asset->first.as(); + auto assetNode = asset->second; + + if(entryName.find(":config") != std::string::npos) { + continue; + } + + // Parse horizontal assets + if(assetNode["files"]){ + auto segment = assetNode["segment"] ? assetNode["segment"].as() : -1; + auto files = assetNode["files"]; + for (const auto& file : files) { + auto node = file.as(); + auto childName = node["name"].as(); + + if(!node["offset"]) { + continue; + } + + if(segment != -1 || gCurrentFileOffset) { + node["offset"] = (segment << 24) | node["offset"].as(); + } + + auto output = (this->gCurrentDirectory / entryName / childName).string(); + std::replace(output.begin(), output.end(), '\\', '/'); + this->gConfig.segment.temporal.clear(); + auto result = this->ParseNode(node, output); + if(result.has_value()) { + this->gParseResults[this->gCurrentFile].push_back(result.value()); + } + } + } else { + if(gCurrentFileOffset && assetNode["offset"]) { + const auto offset = assetNode["offset"].as(); + if (!IS_SEGMENTED(offset)) { + assetNode["offset"] = (gCurrentSegmentNumber << 24) | offset; + } + } + std::string output = (this->gCurrentDirectory / entryName).string(); + std::replace(output.begin(), output.end(), '\\', '/'); + this->gConfig.segment.temporal.clear(); + auto result = this->ParseNode(assetNode, output); + if(result.has_value()) { + this->gParseResults[this->gCurrentFile].push_back(result.value()); + } + } + + spdlog::set_pattern(regular); + SPDLOG_INFO("------------------------------------------------"); + spdlog::set_pattern(line); + } + + 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(); + const auto exporter = impl->GetExporter(this->gConfig.exporterType); + + if(exporter == nullptr) { + continue; + } + + switch (this->gConfig.exporterType) { + case ExportType::Binary: { + stream.str(""); + stream.clear(); + exporter->get()->Export(stream, data, result.name, result.node, &result.name); + auto data = stream.str();; + break; + } + case ExportType::Modding: { + stream.str(""); + stream.clear(); + std::string ogname = result.name; + exporter->get()->Export(stream, data, result.name, result.node, &result.name); + + 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: { + endptr = exporter->get()->Export(stream, data, result.name, result.node, &result.name); + break; + } + } + + if(result.node["offset"]) { + auto alignment = GetSafeNode(result.node, "alignment", impl->GetAlignment()); + if(!endptr.has_value()) { + wEntry = { + result.name, + result.node["offset"].as(), + alignment, + stream.str(), + std::nullopt + }; + } else { + switch (endptr->index()) { + case 0: + wEntry = { + result.name, + result.node["offset"].as(), + alignment, + stream.str(), + std::get(endptr.value()) + }; + break; + case 1: { + const auto oentry = std::get(endptr.value()); + wEntry = { + result.name, + 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); + } + + 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){ + std::string filename = this->gCurrentDirectory.filename().string(); + + switch (this->gConfig.exporterType) { + case ExportType::Header: { + fsout /= filename + ".h"; + break; + } + case ExportType::Code: { + fsout /= this->gCurrentDirectory / (filename + ".c"); + break; + } + default: break; + } + + std::ostringstream stream; + + std::vector entries; + + if(std::holds_alternative(this->gWriteOrder)) { + auto sort = std::get(this->gWriteOrder); + for (const auto& [type, raw] : this->gWriteMap[this->gCurrentFile]) { + entries.insert(entries.end(), raw.begin(), raw.end()); + } + + if(sort == "OFFSET") { + std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { + return a.addr < b.addr; + }); + } else if(sort == "ROFFSET") { + std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { + return a.addr > b.addr; + }); + } else if(sort != "LINEAR") { + throw std::runtime_error("Invalid write order"); + } + } else { + for (const auto& type : std::get>(this->gWriteOrder)) { + entries = this->gWriteMap[this->gCurrentFile][type]; + + std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { + return a.addr > b.addr; + }); + } + } + + for (size_t i = 0; i < entries.size(); i++) { + const auto result = entries[i]; + const auto hasSize = result.endptr.has_value(); + if (hasSize && this->IsDebug()) { + stream << "// 0x" << std::hex << std::uppercase << ASSET_PTR(result.addr) << "\n"; + } + + stream << result.buffer; + + if (hasSize && this->IsDebug()) { + stream << "// 0x" << std::hex << std::uppercase << ASSET_PTR(result.endptr.value()) << "\n\n"; + } + + if(hasSize && i < entries.size() - 1 && this->gConfig.exporterType == ExportType::Code && !this->gIndividualIncludes){ + int32_t startptr = ASSET_PTR(result.endptr.value()); + int32_t end = ASSET_PTR(entries[i + 1].addr); + + uint32_t alignment = entries[i + 1].alignment; + int32_t gap = end - startptr; + + 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) { + 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()){ + stream << "// 0x" << std::hex << std::uppercase << startptr << "\n"; + } + 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()){ + stream << "// 0x" << std::hex << std::uppercase << end << "\n\n"; + } else { + stream << "\n"; + } + } else if(gap > 0x10) { + stream << "// WARNING: Gap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << gap << "\n"; + } + } + + if (this->gConfig.exporterType == ExportType::Code && this->gIndividualIncludes) { + fs::path outinc = fs::path(this->gConfig.outputPath) / this->gCurrentDirectory.parent_path() / (result.name + ".inc.c"); + + if(!exists(outinc.parent_path())){ + create_directories(outinc.parent_path()); + } + + std::ofstream file(outinc, std::ios::binary); + + if(!this->gFileHeader.empty()) { + file << this->gFileHeader << std::endl; + } + file << stream.str(); + stream.str(""); + stream.seekp(0); + file.close(); + } + } + + this->gWriteMap.clear(); + + if (this->gConfig.exporterType != ExportType::Code || !this->gIndividualIncludes) { + std::string buffer = stream.str(); + + if(buffer.empty()) { + return; + } + + std::string output = fsout.string(); + std::replace(output.begin(), output.end(), '\\', '/'); + if(!exists(fs::path(output).parent_path())){ + create_directories(fs::path(output).parent_path()); + } + + std::ofstream file(output, std::ios::binary); + + if(this->gConfig.exporterType == ExportType::Header) { + fs::path entryPath = this->gCurrentFile; + std::string symbol = entryPath.stem().string(); + std::transform(symbol.begin(), symbol.end(), symbol.begin(), toupper); + file << "#ifndef " << symbol << "_H" << std::endl; + file << "#define " << symbol << "_H" << std::endl << std::endl; + if(!this->gFileHeader.empty()) { + file << this->gFileHeader << std::endl; + } + file << buffer; + file << std::endl << "#endif" << std::endl; + } else { + if(!this->gFileHeader.empty()) { + file << this->gFileHeader << std::endl; + } + file << buffer; + } + + file.close(); + } + } + + if(this->gConfig.exporterType != ExportType::Binary) { + this->gHashNode[this->gCurrentFile]["extracted"][ExportTypeToString(this->gConfig.exporterType)] = true; + } +} + void Companion::Process() { if(!fs::exists("config.yml")) { @@ -639,413 +1071,9 @@ void Companion::Process() { this->gCurrentDirectory = relative(entry.path(), this->gAssetPath).replace_extension(""); this->gCurrentFile = yamlPath; - // 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(); - gCurrentFileOffset = segments[0][1].as(); - gCurrentCompressionType = GetCompressionType(this->gRomData, gCurrentFileOffset); - } else { - throw std::runtime_error("Incorrect yaml syntax for segments.\n\nThe yaml expects:\n:config:\n segments:\n - [, ]\n\nLike so:\nsegments:\n - [0x06, 0x821D10]"); - } - } - } - - for(auto asset = root.begin(); asset != root.end(); ++asset){ - auto node = asset->second; - auto entryName = asset->first.as(); - - // Parse horizontal assets - if(node["files"]){ - auto segment = node["segment"] ? node["segment"].as() : -1; - const auto files = node["files"]; - for (const auto& file : files) { - auto assetNode = file.as(); - auto childName = assetNode["name"].as(); - auto output = (this->gCurrentDirectory / entryName / childName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - - if(assetNode["type"]){ - const auto type = GetSafeNode(assetNode, "type"); - if(type == "SAMPLE"){ - AudioManager::Instance->bind_sample(assetNode, output); - } - } - - if(!assetNode["offset"]) { - continue; - } - - if(segment != -1 || gCurrentSegmentNumber) { - assetNode["offset"] = (segment << 24) | assetNode["offset"].as(); - } - - this->gAddrMap[this->gCurrentFile][assetNode["offset"].as()] = std::make_tuple(output, assetNode); - } - } else { - - auto output = (this->gCurrentDirectory / entryName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - - - if(node["type"]){ - const auto type = GetSafeNode(node, "type"); - if(type == "SAMPLE"){ - AudioManager::Instance->bind_sample(node, output); - } - } - - if(!node["offset"]) { - continue; - } - - if(gCurrentSegmentNumber) { - if (IS_SEGMENTED(node["offset"].as()) == false) { - node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as(); - } - } - - this->gAddrMap[this->gCurrentFile][node["offset"].as()] = std::make_tuple(output, node); - } - } - - // Stupid hack because the iteration broke the assets - root = YAML::LoadFile(yamlPath); - this->gConfig.segment.local.clear(); - this->gFileHeader.clear(); - this->gCurrentPad = 0; - this->gCurrentVram = std::nullopt; - this->gCurrentSegmentNumber = 0; - this->gCurrentCompressionType = CompressionType::None; - this->gCurrentFileOffset = 0; - this->gTables.clear(); - this->gCurrentExternalFiles.clear(); - GFXDOverride::ClearVtx(); - - if(root[":config"]) { - this->ParseCurrentFileConfig(root[":config"]); - } - - if(!this->NodeHasChanges(yamlPath) && !this->gNodeForceProcessing) { - continue; - } - - spdlog::set_pattern(regular); - SPDLOG_INFO("------------------------------------------------"); - spdlog::set_pattern(line); - - for(auto asset = root.begin(); asset != root.end(); ++asset){ - - auto entryName = asset->first.as(); - auto assetNode = asset->second; - - if(entryName.find(":config") != std::string::npos) { - continue; - } - - // Parse horizontal assets - if(assetNode["files"]){ - auto segment = assetNode["segment"] ? assetNode["segment"].as() : -1; - auto files = assetNode["files"]; - for (const auto& file : files) { - auto node = file.as(); - auto childName = node["name"].as(); - - if(!node["offset"]) { - continue; - } - - if(segment != -1 || gCurrentFileOffset) { - node["offset"] = (segment << 24) | node["offset"].as(); - } - - auto output = (this->gCurrentDirectory / entryName / childName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - this->gConfig.segment.temporal.clear(); - auto result = this->ParseNode(node, output); - if(result.has_value()) { - this->gParseResults[this->gCurrentFile].push_back(result.value()); - } - } - } else { - if(gCurrentFileOffset && assetNode["offset"]) { - const auto offset = assetNode["offset"].as(); - if (!IS_SEGMENTED(offset)) { - assetNode["offset"] = (gCurrentSegmentNumber << 24) | offset; - } - } - std::string output = (this->gCurrentDirectory / entryName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - this->gConfig.segment.temporal.clear(); - auto result = this->ParseNode(assetNode, output); - if(result.has_value()) { - this->gParseResults[this->gCurrentFile].push_back(result.value()); - } - } - - spdlog::set_pattern(regular); - SPDLOG_INFO("------------------------------------------------"); - spdlog::set_pattern(line); - } - - 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(); - const auto exporter = impl->GetExporter(this->gConfig.exporterType); - - if(exporter == nullptr) { - continue; - } - - switch (this->gConfig.exporterType) { - case ExportType::Binary: { - stream.str(""); - stream.clear(); - exporter->get()->Export(stream, data, result.name, result.node, &result.name); - auto data = stream.str();; - break; - } - case ExportType::Modding: { - stream.str(""); - stream.clear(); - std::string ogname = result.name; - exporter->get()->Export(stream, data, result.name, result.node, &result.name); - - 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: { - endptr = exporter->get()->Export(stream, data, result.name, result.node, &result.name); - break; - } - } - - if(result.node["offset"]) { - auto alignment = GetSafeNode(result.node, "alignment", impl->GetAlignment()); - if(!endptr.has_value()) { - wEntry = { - result.name, - result.node["offset"].as(), - alignment, - stream.str(), - std::nullopt - }; - } else { - switch (endptr->index()) { - case 0: - wEntry = { - result.name, - result.node["offset"].as(), - alignment, - stream.str(), - std::get(endptr.value()) - }; - break; - case 1: { - const auto oentry = std::get(endptr.value()); - wEntry = { - result.name, - 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); - } - - 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){ - std::string filename = this->gCurrentDirectory.filename().string(); - - switch (this->gConfig.exporterType) { - case ExportType::Header: { - fsout /= filename + ".h"; - break; - } - case ExportType::Code: { - fsout /= this->gCurrentDirectory / (filename + ".c"); - break; - } - default: break; - } - - std::ostringstream stream; - - std::vector entries; - - if(std::holds_alternative(this->gWriteOrder)) { - auto sort = std::get(this->gWriteOrder); - for (const auto& [type, raw] : this->gWriteMap[this->gCurrentFile]) { - entries.insert(entries.end(), raw.begin(), raw.end()); - } - - if(sort == "OFFSET") { - std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { - return a.addr < b.addr; - }); - } else if(sort == "ROFFSET") { - std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { - return a.addr > b.addr; - }); - } else if(sort != "LINEAR") { - throw std::runtime_error("Invalid write order"); - } - } else { - for (const auto& type : std::get>(this->gWriteOrder)) { - entries = this->gWriteMap[this->gCurrentFile][type]; - - std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { - return a.addr > b.addr; - }); - } - } - - for (size_t i = 0; i < entries.size(); i++) { - const auto result = entries[i]; - const auto hasSize = result.endptr.has_value(); - if (hasSize && this->IsDebug()) { - stream << "// 0x" << std::hex << std::uppercase << ASSET_PTR(result.addr) << "\n"; - } - - stream << result.buffer; - - if (hasSize && this->IsDebug()) { - stream << "// 0x" << std::hex << std::uppercase << ASSET_PTR(result.endptr.value()) << "\n\n"; - } - - if(hasSize && i < entries.size() - 1 && this->gConfig.exporterType == ExportType::Code && !this->gIndividualIncludes){ - int32_t startptr = ASSET_PTR(result.endptr.value()); - int32_t end = ASSET_PTR(entries[i + 1].addr); - - uint32_t alignment = entries[i + 1].alignment; - int32_t gap = end - startptr; - - 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) { - 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()){ - stream << "// 0x" << std::hex << std::uppercase << startptr << "\n"; - } - 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()){ - stream << "// 0x" << std::hex << std::uppercase << end << "\n\n"; - } else { - stream << "\n"; - } - } else if(gap > 0x10) { - stream << "// WARNING: Gap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << gap << "\n"; - } - } - - if (this->gConfig.exporterType == ExportType::Code && this->gIndividualIncludes) { - fs::path outinc = fs::path(this->gConfig.outputPath) / this->gCurrentDirectory.parent_path() / (result.name + ".inc.c"); - - if(!exists(outinc.parent_path())){ - create_directories(outinc.parent_path()); - } - - std::ofstream file(outinc, std::ios::binary); - - if(!this->gFileHeader.empty()) { - file << this->gFileHeader << std::endl; - } - file << stream.str(); - stream.str(""); - stream.seekp(0); - file.close(); - } - } - - this->gWriteMap.clear(); - - if (this->gConfig.exporterType != ExportType::Code || !this->gIndividualIncludes) { - std::string buffer = stream.str(); - - if(buffer.empty()) { - continue; - } - - std::string output = fsout.string(); - std::replace(output.begin(), output.end(), '\\', '/'); - if(!exists(fs::path(output).parent_path())){ - create_directories(fs::path(output).parent_path()); - } - - std::ofstream file(output, std::ios::binary); - - if(this->gConfig.exporterType == ExportType::Header) { - std::string symbol = entry.path().stem().string(); - std::transform(symbol.begin(), symbol.end(), symbol.begin(), toupper); - file << "#ifndef " << symbol << "_H" << std::endl; - file << "#define " << symbol << "_H" << std::endl << std::endl; - if(!this->gFileHeader.empty()) { - file << this->gFileHeader << std::endl; - } - file << buffer; - file << std::endl << "#endif" << std::endl; - } else { - if(!this->gFileHeader.empty()) { - file << this->gFileHeader << std::endl; - } - file << buffer; - } - - file.close(); - } - } - - if(this->gConfig.exporterType != ExportType::Binary) { - this->gHashNode[this->gCurrentFile]["extracted"][ExportTypeToString(this->gConfig.exporterType)] = true; + if (!this->gProcessedFiles.contains(this->gCurrentFile)) { + ProcessFile(root); + this->gProcessedFiles.insert(this->gCurrentFile); } } diff --git a/src/Companion.h b/src/Companion.h index ad48e333..26c92537 100644 --- a/src/Companion.h +++ b/src/Companion.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "factories/BaseFactory.h" #include "n64/Cartridge.h" @@ -173,6 +174,7 @@ class Companion { CompressionType gCurrentCompressionType = CompressionType::None; std::vector gTables; std::vector gCurrentExternalFiles; + std::unordered_set gProcessedFiles; std::unordered_map> gParseResults; @@ -183,6 +185,7 @@ class Companion { std::unordered_map>> gAssetDependencies; std::unordered_map>> gAddrMap; + void ProcessFile(YAML::Node root); void ParseEnums(std::string& file); void ParseHash(); void ParseModdingConfig();