diff options
| author | KiritoDv <kiritodev01@gmail.com> | 2024-12-25 00:43:20 -0600 |
|---|---|---|
| committer | KiritoDv <kiritodev01@gmail.com> | 2024-12-25 00:43:20 -0600 |
| commit | 64266cbac2f51e50380ea0d7191b97e63e64ddfa (patch) | |
| tree | f774cdbecb283295fe283244b6c370b33d9a2b69 /src | |
| parent | 0efd51b717fe7ffef8a8c93a4c25efcb8ef3f919 (diff) | |
| parent | abb74ef940afe35ddf7bea5ea2e03c45484a95a3 (diff) | |
Merge branch 'main' into audio_extraction
Diffstat (limited to 'src')
22 files changed, 1040 insertions, 272 deletions
diff --git a/src/Companion.cpp b/src/Companion.cpp index 07f0845..f56b2ed 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -25,6 +25,7 @@ #include "factories/Vec3sFactory.h" #include "factories/AssetArrayFactory.h" #include "factories/ViewportFactory.h" +#include "factories/CompressedTextureFactory.h" #include "factories/sm64/AnimationFactory.h" #include "factories/sm64/BehaviorScriptFactory.h" @@ -78,6 +79,8 @@ #include "factories/naudio/v1/BookFactory.h" #include "factories/naudio/v1/SequenceFactory.h" +#include "preprocess/CompTool.h" + using namespace std::chrono; namespace fs = std::filesystem; @@ -105,6 +108,7 @@ void Companion::Init(const ExportType type) { this->RegisterFactory("ARRAY", std::make_shared<GenericArrayFactory>()); this->RegisterFactory("ASSET_ARRAY", std::make_shared<AssetArrayFactory>()); this->RegisterFactory("VP", std::make_shared<ViewportFactory>()); + this->RegisterFactory("COMPRESSED_TEXTURE", std::make_shared<CompressedTextureFactory>()); // SM64 specific this->RegisterFactory("SM64:DIALOG", std::make_shared<SM64::DialogFactory>()); @@ -559,13 +563,12 @@ void Companion::ProcessFile(YAML::Node root) { for(auto asset = root.begin(); asset != root.end(); ++asset){ auto node = asset->second; auto entryName = asset->first.as<std::string>(); - auto output = (this->gCurrentDirectory / entryName).string(); std::replace(output.begin(), output.end(), '\\', '/'); if(node["type"]){ const auto type = GetSafeNode<std::string>(node, "type"); - if(type == "SAMPLE"){ + if(type == "NAUDIO:V0:SAMPLE"){ AudioManager::Instance->bind_sample(node, output); } } @@ -845,7 +848,7 @@ void Companion::ProcessFile(YAML::Node root) { 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) { + } else if(gap < 0x10 && gap >= alignment && end % alignment == 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(); @@ -863,7 +866,7 @@ void Companion::ProcessFile(YAML::Node root) { } else { stream << "\n"; } - } else if(gap > 0x10) { + } else if(gap >= 0x10) { stream << "// WARNING: Gap detected between 0x" << std::hex << startptr << " and 0x" << end << " with size 0x" << gap << "\n"; } } @@ -970,8 +973,44 @@ void Companion::Process() { } auto rom = !isDirectoryMode ? config[this->gCartridge->GetHash()] : config; - auto cfg = rom["config"]; + if(rom["preprocess"]) { + auto preprocess = rom["preprocess"]; + for(auto job = preprocess.begin(); job != preprocess.end(); job++) { + auto name = job->first.as<std::string>(); + auto item = job->second; + auto method = GetSafeNode<std::string>(item, "method"); + if (method == "mio0-comptool") { + auto type = GetSafeNode<std::string>(item, "type"); + auto target = GetSafeNode<std::string>(item, "target"); + auto restart = GetSafeNode<bool>(item, "restart"); + + if (type == "decompress") { + this->gRomData = CompTool::Decompress(this->gRomData); + this->gCartridge = std::make_shared<N64::Cartridge>(this->gRomData); + this->gCartridge->Initialize(); + + auto hash = this->gCartridge->GetHash(); + + SPDLOG_INFO("ROM decompressed to {}", hash); + + if (hash != target) { + throw std::runtime_error("Hash mismatch"); + } + + if(restart){ + rom = config[this->gCartridge->GetHash()]; + } + } else { + throw std::runtime_error("Only decompression is supported"); + } + } else { + throw std::runtime_error("Invalid preprocess method"); + } + } + } + + auto cfg = rom["config"]; if(!cfg) { SPDLOG_ERROR("No config found for {}", !isDirectoryMode ? this->gCartridge->GetHash() : GetSafeNode<std::string>(config, "folder")); return; diff --git a/src/factories/BaseFactory.h b/src/factories/BaseFactory.h index f3f30aa..3fdb1cf 100644 --- a/src/factories/BaseFactory.h +++ b/src/factories/BaseFactory.h @@ -56,10 +56,12 @@ std::optional<T> GetNode(YAML::Node& node, const std::string& key) { template<typename T> T GetSafeNode(YAML::Node& node, const std::string& key) { if(!node[key]) { + auto dump = YAML::Dump(node); + if (node["symbol"]) { - throw std::runtime_error("Yaml asset missing the '" + key + "' node for '" + node["symbol"].as<std::string>() + "'"); + throw std::runtime_error("Yaml asset missing the '" + key + "' node for '" + node["symbol"].as<std::string>() + "'\nProblematic YAML:\n" + dump); } else { - throw std::runtime_error("Yaml asset missing the '" + key + "' node"); + throw std::runtime_error("Yaml asset missing the '" + key + "' node\nProblematic YAML:\n" + dump); } } diff --git a/src/factories/CompressedTextureFactory.cpp b/src/factories/CompressedTextureFactory.cpp new file mode 100644 index 0000000..4d30bc2 --- /dev/null +++ b/src/factories/CompressedTextureFactory.cpp @@ -0,0 +1,538 @@ +#include "CompressedTextureFactory.h" +#include "utils/Decompressor.h" +#include "spdlog/spdlog.h" +#include "Companion.h" +#include <iomanip> +#include <regex> + +extern "C" { +#include "n64graphics/n64graphics.h" +#include "BaseFactory.h" +#include <libmio0/mio0.h> +} + +static bool isTable = false; +static std::vector<std::string> tableEntries; + +static const std::unordered_map <std::string, TextureFormat> sTextureFormats = { + { "RGBA16", { TextureType::RGBA16bpp, 16 } }, + { "RGBA32", { TextureType::RGBA32bpp, 32 } }, + { "CI4", { TextureType::Palette4bpp, 4 } }, + { "CI8", { TextureType::Palette8bpp, 8 } }, + { "I4", { TextureType::Grayscale4bpp, 4 } }, + { "I8", { TextureType::Grayscale8bpp, 8 } }, + { "IA1", { TextureType::GrayscaleAlpha1bpp, 1 } }, + { "IA4", { TextureType::GrayscaleAlpha4bpp, 4 } }, + { "IA8", { TextureType::GrayscaleAlpha8bpp, 8 } }, + { "IA16", { TextureType::GrayscaleAlpha16bpp, 16 } }, + { "TLUT", { TextureType::TLUT, 16 } }, +}; + +static const std::unordered_map <std::string, CompressionType> sCompressionTypes = { + { "MIO0", CompressionType::MIO0 }, + { "YAY0", CompressionType::YAY0 }, + { "YAZ0", CompressionType::YAZ0 }, +}; + +ExportResult CompressedTextureHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + const auto symbol = GetSafeNode(node, "symbol", entryName); + const auto offset = GetSafeNode<uint32_t>(node, "offset"); + auto format = GetSafeNode<std::string>(node, "format"); + auto texture = std::static_pointer_cast<TextureData>(raw); + auto data = texture->mBuffer; + auto isOTR = Companion::Instance->IsOTRMode(); + size_t byteSize = std::max(1, (int) (texture->mFormat.depth / 8)); + + const auto searchTable = Companion::Instance->SearchTable(offset); + + if(searchTable.has_value()){ + const auto [name, start, end, mode, index_size] = searchTable.value(); + unsigned int isize = index_size > -1 ? index_size : data.size() / byteSize; + + if(isOTR){ + write << "static const ALIGN_ASSET(2) char " << symbol << "[] = \"__OTR__" << (*replacement) << "\";\n\n"; + + tableEntries.push_back(symbol); + + if(end == offset){ + write << "static const char* " << name << "[] = {\n"; + for(auto& entry : tableEntries){ + write << tab_t << entry << ",\n"; + } + write << "};\n\n"; + tableEntries.clear(); + } + } else { + write << "extern " << "u8 " << name << "[][" << isize << "];\n"; + } + } else { + if(isOTR){ + write << "static const ALIGN_ASSET(2) char " << symbol << "[] = \"__OTR__" << (*replacement) << "\";\n\n"; + } else { + write << "extern " << "u8 " << symbol << "[];\n"; + } + } + + return std::nullopt; +} + +ExportResult CompressedTextureCodeExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + auto texture = std::static_pointer_cast<CompressedTextureData>(raw); + auto data = texture->mBuffer; + auto offset = GetSafeNode<uint32_t>(node, "offset"); + auto symbol = GetSafeNode(node, "symbol", entryName); + auto format = GetSafeNode<std::string>(node, "format"); + + std::transform(format.begin(), format.end(), format.begin(), tolower); + (*replacement) += "." + format; + + std::string dpath = Companion::Instance->GetOutputPath() + "/" + (*replacement); + if(!exists(fs::path(dpath).parent_path())){ + create_directories(fs::path(dpath).parent_path()); + } + + std::ostringstream imgstream; + + size_t byteSize = std::max(1, (int) (texture->mFormat.depth / 8)); + size_t isize = texture->mBuffer.size() / byteSize; + + for (int i = 0; i < data.size(); i+=byteSize) { + if (i % 16 == 0 && i != 0) { + imgstream << std::endl; + } + + imgstream << "0x"; + + for (int j = 0; j < byteSize; j++) { + imgstream << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i + j]); + } + + imgstream << ", "; + } + imgstream << std::endl; + + std::ofstream file(dpath + ".inc.c", std::ios::binary); + file << imgstream.str(); + file.close(); + + // Allocate worse case size + uint8_t* compressedData; + size_t compressedSize; + size_t worstSize; + + switch (texture->mCompressionType) { + case CompressionType::MIO0: + worstSize = MIO0_HEADER_LENGTH + ((data.size()+7)/8) + data.size(); + compressedData = static_cast<uint8_t*>(std::calloc(worstSize, sizeof(uint8_t))); + compressedSize = mio0_encode(data.data(), data.size(), compressedData); + break; + default: + // UNIMPLEMENTED + throw std::runtime_error("Unsupported Compressed Texture Type"); + break; + } + if (compressedData) { + std::ostringstream compressedStream; + + for (size_t i = 0; i < compressedSize; i++) { + if (i % 16 == 0 && i != 0) { + compressedStream << std::endl; + } + + compressedStream << "0x"; + compressedStream << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(compressedData[i]); + compressedStream << ", "; + } + compressedStream << std::endl; + + std::ofstream file(dpath + ".incbin.c", std::ios::binary); + file << compressedStream.str(); + file.close(); + free(compressedData); + } + + const auto searchTable = Companion::Instance->SearchTable(offset); + + if(searchTable.has_value()){ + const auto [name, start, end, mode, index_size] = searchTable.value(); + + if(mode != TableMode::Append){ + throw std::runtime_error("Reference mode is not supported for now"); + } + + if (index_size > -1) { + isize = index_size; + } + + if(start == offset){ + write << "u8 " << name << "[][" << isize << "] = {\n"; + } + + write << tab_t << "{\n"; + + write << tab_t << tab_t << "#include \"" << Companion::Instance->GetOutputPath() + "/" << *replacement << ".incbin.c\"\n"; + + write << tab_t << "},\n"; + + if(end == offset){ + write << "};\n"; + if (Companion::Instance->IsDebug()) { + write << "// size: 0x" << std::hex << std::uppercase << ASSET_PTR((end - start) + isize * byteSize) << "\n"; + } + } + } else { + write << "u8 " << symbol << "[] = {\n"; + + write << tab_t << "#include \"" << Companion::Instance->GetOutputPath() + "/" << *replacement << ".incbin.c\"\n"; + + write << "};\n"; + + const auto sz = data.size(); + if (Companion::Instance->IsDebug()) { + write << "// size: 0x" << std::hex << std::uppercase << sz; + } + + write << "\n"; + } + return offset + compressedSize; +} + +ExportResult CompressedTextureBinaryExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { + auto writer = LUS::BinaryWriter(); + auto texture = std::static_pointer_cast<CompressedTextureData>(raw); + auto data = texture->mBuffer; + + // TODO: Recompress? + + WriteHeader(writer, Torch::ResourceType::Texture, 0); + + if(texture->mFormat.type == TextureType::TLUT) { + texture->mFormat.type = TextureType::RGBA16bpp; + } + + writer.Write((uint32_t) texture->mFormat.type); + writer.Write(texture->mWidth); + writer.Write(texture->mHeight); + + writer.Write((uint32_t) data.size()); + writer.Write((char*) data.data(), data.size()); + writer.Finish(write); + return std::nullopt; +} + +ExportResult CompressedTextureModdingExporter::Export(std::ostream&write, std::shared_ptr<IParsedData> data, std::string&entryName, YAML::Node&node, std::string* replacement) { + auto texture = std::static_pointer_cast<CompressedTextureData>(data); + auto format = texture->mFormat; + uint8_t* raw = new uint8_t[TextureUtils::CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2]; + int size = 0; + + auto ext = GetSafeNode<std::string>(node, "format"); + + std::transform(ext.begin(), ext.end(), ext.begin(), tolower); + *replacement += "." + ext + ".png"; + + switch (format.type) { + case TextureType::TLUT: + case TextureType::RGBA16bpp: + case TextureType::RGBA32bpp: { + rgba* imgr = raw2rgba(texture->mBuffer.data(), texture->mWidth, texture->mHeight, format.depth); + if(rgba2png(&raw, &size, imgr, texture->mWidth, texture->mHeight)) { + throw std::runtime_error("Failed to convert texture to PNG"); + } + break; + } + case TextureType::GrayscaleAlpha16bpp: + case TextureType::GrayscaleAlpha8bpp: + case TextureType::GrayscaleAlpha4bpp: + case TextureType::GrayscaleAlpha1bpp: { + ia* imgia = raw2ia(texture->mBuffer.data(), texture->mWidth, texture->mHeight, format.depth); + if(ia2png(&raw, &size, imgia, texture->mWidth, texture->mHeight)) { + throw std::runtime_error("Failed to convert texture to PNG"); + } + break; + } + case TextureType::Palette8bpp: + case TextureType::Palette4bpp: { + if (node["tlut_symbol"]) { + auto tlut = GetSafeNode<std::string>(node,"tlut_symbol"); + auto palette = Companion::Instance->GetParseDataBySymbol(tlut); + + if (palette.has_value()) { + auto palTexture = std::static_pointer_cast<TextureData>(palette.value().data.value()); + convert_raw_to_ci8(&raw, &size, texture->mBuffer.data(), (uint8_t *)palTexture->mBuffer.data(), 0, texture->mWidth, texture->mHeight, texture->mFormat.depth, palTexture->mFormat.depth); + } else { + auto symbol = GetSafeNode<std::string>(node, "symbol"); + throw std::runtime_error("Could not convert ci8 '"+symbol+"' the tlut symbol name is probably wrong for tlut_symbol node"); + } + break; + } + + if (node["tlut"]) { + auto tlut = GetSafeNode<uint32_t>(node,"tlut"); + auto palette = Companion::Instance->GetParseDataByAddr(tlut); + + if (palette.has_value()) { + auto palTexture = std::static_pointer_cast<TextureData>(palette.value().data.value()); + convert_raw_to_ci8(&raw, &size, texture->mBuffer.data(), (uint8_t *)palTexture->mBuffer.data(), 0, texture->mWidth, texture->mHeight, texture->mFormat.depth, palTexture->mFormat.depth); + } else { + auto symbol = GetSafeNode<std::string>(node, "symbol"); + throw std::runtime_error("Could not convert ci8 '"+symbol+"' the address is probably wrong for tlut address node"); + } + break; + } + } + case TextureType::Grayscale8bpp: + case TextureType::Grayscale4bpp: { + ia* imgi = raw2i(texture->mBuffer.data(), texture->mWidth, texture->mHeight, format.depth); + if(ia2png(&raw, &size, imgi, texture->mWidth, texture->mHeight)) { + throw std::runtime_error("Failed to convert texture to PNG"); + } + break; + } + default: { + SPDLOG_ERROR("Unsupported texture format for modding: {}", ext); + } + } + + write.write(reinterpret_cast<char*>(raw), size); + return std::nullopt; +} + +std::string getcomptype(CompressionType type) { + switch (type) { + case CompressionType::MIO0: + return "MIO0"; + case CompressionType::YAY0: + return "YAY0"; + case CompressionType::YAZ0: + return "YAZ0"; + default: + break; + } + + return "None"; +} + +std::optional<std::shared_ptr<IParsedData>> CompressedTextureFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { + auto offset = GetSafeNode<uint32_t>(node, "offset"); + auto format = GetSafeNode<std::string>(node, "format"); + auto symbol = GetSafeNode<std::string>(node, "symbol"); + uint32_t width; + uint32_t height; + uint32_t size; + auto compression = GetSafeNode<std::string>(node, "compression"); + CompressionType compressionType; + if (!sCompressionTypes.contains(compression)) { + SPDLOG_ERROR("Compresed Texture entry at {:X} in yaml missing compression type\n\ + Please add one of the following compression types\n\ + MIO0, YAY0 (Unsupported), YAZ0 (Unsupported)", offset); + return std::nullopt; + } + compressionType = sCompressionTypes.at(compression); + + CompressionType realCompressionType = Decompressor::GetCompressionType(buffer, Decompressor::TranslateAddr(offset, false)); + + if (realCompressionType != compressionType) { + SPDLOG_ERROR("Compressed Texture entry at {:X} in yaml uses mismatching compression type\n\ + Passed In {}, expected {}", offset, getcomptype(compressionType), getcomptype(realCompressionType)); + return std::nullopt; + } + + DataChunk* uncompressedData = Decompressor::Decode(buffer, Decompressor::TranslateAddr(offset, false), compressionType); + + std::transform(format.begin(), format.end(), format.begin(), ::toupper); + + if (format.empty()) { + SPDLOG_ERROR("Texture entry at {:X} in yaml missing format node\n\ + Please add one of the following formats\n\ + rgba16, rgba32, ia16, ia8, ia4, i8, i4, ci8, ci4, 1bpp, tlut", offset); + return std::nullopt; + } + + if(!sTextureFormats.contains(format)) { + return std::nullopt; + } + + TextureFormat fmt = sTextureFormats.at(format); + + if(fmt.type == TextureType::TLUT){ + width = GetSafeNode<uint32_t>(node, "colors"); + height = 1; + } else { + width = GetSafeNode<uint32_t>(node, "width"); + height = GetSafeNode<uint32_t>(node, "height"); + } + + if((format == "CI4" || format == "CI8") && node["tlut"] && node["colors"]) { + YAML::Node tlutNode; + const auto tlutOffset = GetSafeNode<uint32_t>(node, "tlut"); + const auto tlutSymbol = GetSafeNode(node, "tlut_symbol", symbol + "_tlut"); + std::ostringstream offsetSeg; + offsetSeg << std::uppercase << std::hex << tlutOffset; + tlutNode["symbol"] = std::regex_replace(tlutSymbol, std::regex(R"(OFFSET)"), offsetSeg.str()); + tlutNode["type"] = "TEXTURE"; + tlutNode["format"] = "TLUT"; + tlutNode["offset"] = tlutOffset; + tlutNode["colors"] = GetSafeNode<uint32_t>(node, "colors"); + node["tlut"] = tlutOffset; + if(node["tlut_ctype"]) { + tlutNode["ctype"] = GetSafeNode<std::string>(node, "tlut_ctype"); + } + Companion::Instance->AddAsset(tlutNode); + } + size = GetSafeNode<uint32_t>(node, "size", TextureUtils::CalculateTextureSize(sTextureFormats.at(format).type, width, height)); + + std::vector<uint8_t> result; + + if(fmt.type == TextureType::GrayscaleAlpha1bpp){ + result = TextureUtils::alloc_ia8_text_from_i1(reinterpret_cast<uint16_t*>(uncompressedData->data), 8, 16); + } else { + result = std::vector(uncompressedData->data, uncompressedData->data + uncompressedData->size); + } + + SPDLOG_INFO("Texture: {}", format); + if(fmt.type == TextureType::TLUT){ + SPDLOG_INFO("Colors: {}", width); + } else { + SPDLOG_INFO("Width: {}", width); + SPDLOG_INFO("Height: {}", height); + } + SPDLOG_INFO("Size: {}", size); + SPDLOG_INFO("Offset: 0x{:X}", offset); + + if(result.size() == 0){ + return std::nullopt; + } + + if(result.size() == 0){ + return std::nullopt; + } + + return std::make_shared<CompressedTextureData>(fmt, width, height, result, compressionType); +} + +std::optional<std::shared_ptr<IParsedData>> CompressedTextureFactory::parse_modding(std::vector<uint8_t>& buffer, YAML::Node& node) { + auto format = GetSafeNode<std::string>(node, "format"); + int width; + int height; + uint32_t size; + auto offset = GetSafeNode<uint32_t>(node, "offset"); + auto compression = GetSafeNode<std::string>(node, "compression"); + CompressionType compressionType; + if (!sCompressionTypes.contains(compression)) { + SPDLOG_ERROR("Compresed Texture entry at {:X} in yaml missing compression type\n\ + Please add one of the following compression types\n\ + MIO0, YAY0 (Unsupported), YAZ0 (Unsupported)", offset); + return std::nullopt; + } + compressionType = sCompressionTypes.at(compression); + + if (format.empty()) { + SPDLOG_ERROR("Texture entry at {:X} in yaml missing format node\n\ + Please add one of the following formats\n\ + rgba16, rgba32, ia16, ia8, ia4, i8, i4, ci8, ci4, 1bpp, tlut", offset); + return std::nullopt; + } + + if(!sTextureFormats.contains(format)) { + return std::nullopt; + } + + TextureFormat fmt = sTextureFormats.at(format); + if(fmt.type == TextureType::TLUT){ + width = GetSafeNode<uint32_t>(node, "colors"); + height = 1; + } else { + width = GetSafeNode<uint32_t>(node, "width"); + height = GetSafeNode<uint32_t>(node, "height"); + } + + uint8_t* raw; + switch (fmt.type) { + case TextureType::TLUT: + case TextureType::RGBA16bpp: + case TextureType::RGBA32bpp: { + const auto imgr = png2rgba(buffer.data(), buffer.size(), &width, &height); + size = width * height * fmt.depth / 8; + raw = new uint8_t[size]; + if(rgba2raw(raw, imgr, width, height, fmt.depth) <= 0){ + throw std::runtime_error("Failed to convert PNG to texture"); + } + break; + } + case TextureType::GrayscaleAlpha16bpp: + case TextureType::GrayscaleAlpha8bpp: + case TextureType::GrayscaleAlpha4bpp: + case TextureType::GrayscaleAlpha1bpp: { + const auto imgia = png2ia(buffer.data(), buffer.size(), &width, &height); + size = width * height * fmt.depth / 8; + raw = new uint8_t[size]; + if(ia2raw(raw, imgia, width, height, fmt.depth) <= 0){ + throw std::runtime_error("Failed to convert PNG to texture"); + } + break; + } + case TextureType::Palette8bpp: + case TextureType::Palette4bpp: { + // This implementation is not correct. + // The process should be: + // png2rgba --> imgpal2rawci + + // todo: Add wheel palette input + // Implement so that it works. + + // auto tlut = GetSafeNode<std::string>(node,"tlut_symbol"); + // auto tlutTextureMap = Companion::Instance->GetTlutTextureMap(); + // auto palettePtr = tlutTextureMap[tlut]; + + // if (palettePtr) { + + // auto imgi = png2rgba(buffer.data(), buffer.size(), &width, &height); + // auto pal = png2rgba(palettePtr->mBuffer.data(), (palettePtr->mWidth * palettePtr->mWidth * palettePtr->mFormat.depth * 2), &width, &height); + + // size = width * height * fmt.depth / 8; + // raw = new uint8_t[size]; + + // if(imgpal2rawci(raw, imgi, pal, 0, 0, width, height, fmt.depth) <= 0){ + // throw std::runtime_error("Failed to convert PNG to texture"); + // } + // } else { + + // } + SPDLOG_ERROR("Unsupported texture format for modding: {}", format); + return std::nullopt; + } + case TextureType::Grayscale8bpp: + case TextureType::Grayscale4bpp: { + const auto imgi = png2ia(buffer.data(), buffer.size(), &width, &height); + size = width * height * fmt.depth / 8; + raw = new uint8_t[size]; + if(i2raw(raw, imgi, width, height, fmt.depth) <= 0){ + throw std::runtime_error("Failed to convert PNG to texture"); + } + break; + } + default: { + SPDLOG_ERROR("Unsupported texture format for modding: {}", format); + return std::nullopt; + } + } + + auto result = std::vector(raw, raw + size); + + SPDLOG_INFO("Texture: {}", format); + if(fmt.type == TextureType::TLUT){ + SPDLOG_INFO("Colors: {}", width); + } else { + SPDLOG_INFO("Width: {}", width); + SPDLOG_INFO("Height: {}", height); + } + SPDLOG_INFO("Size: {}", size); + SPDLOG_INFO("Offset: 0x{:X}", offset); + + if(result.size() == 0){ + return std::nullopt; + } + + return std::make_shared<CompressedTextureData>(fmt, width, height, result, compressionType); +} diff --git a/src/factories/CompressedTextureFactory.h b/src/factories/CompressedTextureFactory.h new file mode 100644 index 0000000..521f499 --- /dev/null +++ b/src/factories/CompressedTextureFactory.h @@ -0,0 +1,47 @@ +#pragma once + +#include "BaseFactory.h" +#include "utils/Decompressor.h" +#include "utils/TextureUtils.h" + +class CompressedTextureData : public IParsedData { +public: + TextureFormat mFormat; + uint32_t mWidth; + uint32_t mHeight; + std::vector<uint8_t> mBuffer; + CompressionType mCompressionType; + + CompressedTextureData(TextureFormat format, uint32_t width, uint32_t height, std::vector<uint8_t>& buffer, CompressionType compressionType) : mFormat(format), mWidth(width), mHeight(height), mBuffer(std::move(buffer)), mCompressionType(compressionType) {} +}; + +class CompressedTextureHeaderExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureCodeExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureModdingExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class CompressedTextureFactory : public BaseFactory { +public: + std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; + std::optional<std::shared_ptr<IParsedData>> parse_modding(std::vector<uint8_t>& buffer, YAML::Node& data) override; + inline std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override { + return { + REGISTER(Header, CompressedTextureHeaderExporter) + REGISTER(Binary, CompressedTextureBinaryExporter) + REGISTER(Code, CompressedTextureCodeExporter) + REGISTER(Modding, CompressedTextureModdingExporter) + }; + } + bool SupportModdedAssets() override { return true; } +}; diff --git a/src/factories/TextureFactory.cpp b/src/factories/TextureFactory.cpp index 34a6362..1165b62 100644 --- a/src/factories/TextureFactory.cpp +++ b/src/factories/TextureFactory.cpp @@ -10,10 +10,10 @@ extern "C" { #include "BaseFactory.h" } -bool isTable = false; -std::vector<std::string> tableEntries; +static bool isTable = false; +static std::vector<std::string> tableEntries; -static const std::unordered_map <std::string, TextureFormat> gTextureFormats = { +static const std::unordered_map <std::string, TextureFormat> sTextureFormats = { { "RGBA16", { TextureType::RGBA16bpp, 16 } }, { "RGBA32", { TextureType::RGBA32bpp, 32 } }, { "CI4", { TextureType::Palette4bpp, 4 } }, @@ -27,60 +27,6 @@ static const std::unordered_map <std::string, TextureFormat> gTextureFormats = { { "TLUT", { TextureType::TLUT, 16 } }, }; -size_t CalculateTextureSize(TextureType type, uint32_t width, uint32_t height) { - switch (type) { - // 4 bytes per pixel - case TextureType::RGBA32bpp: - return width * height * 4; - // 2 bytes per pixel - case TextureType::TLUT: - case TextureType::RGBA16bpp: - case TextureType::GrayscaleAlpha16bpp: - return width * height * 2; - // 1 byte per pixel - case TextureType::Grayscale8bpp: - case TextureType::Palette8bpp: - case TextureType::GrayscaleAlpha8bpp: - // TODO: We need to validate this MegaMech - case TextureType::GrayscaleAlpha1bpp: - return width * height; - // 1/2 byte per pixel - case TextureType::Palette4bpp: - case TextureType::Grayscale4bpp: - case TextureType::GrayscaleAlpha4bpp: - return (width * height) / 2; - default: - return 0; - } -} - -std::vector<uint8_t> alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height) { - int32_t inPos; - uint16_t bitMask; - int16_t outPos = 0; - const auto out = new uint8_t[width * height]; - - for (int32_t inPos = 0; inPos < (width * height) / 16; inPos++) { - uint16_t bitMask = 0x8000; - - while (bitMask != 0) { - if (BSWAP16(in[inPos]) & bitMask) { - out[outPos] = 0xFF; - } else { - out[outPos] = 0x00; - } - - bitMask /= 2; - outPos++; - } - } - - auto result = std::vector(out, out + width * height); - delete[] out; - - return result; -} - ExportResult TextureHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { const auto symbol = GetSafeNode(node, "symbol", entryName); const auto offset = GetSafeNode<uint32_t>(node, "offset"); @@ -239,7 +185,7 @@ ExportResult TextureBinaryExporter::Export(std::ostream &write, std::shared_ptr< ExportResult TextureModdingExporter::Export(std::ostream&write, std::shared_ptr<IParsedData> data, std::string&entryName, YAML::Node&node, std::string* replacement) { auto texture = std::static_pointer_cast<TextureData>(data); auto format = texture->mFormat; - uint8_t* raw = new uint8_t[CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2]; + uint8_t* raw = new uint8_t[TextureUtils::CalculateTextureSize(format.type, texture->mWidth, texture->mHeight) * 2]; int size = 0; auto ext = GetSafeNode<std::string>(node, "format"); @@ -332,11 +278,11 @@ std::optional<std::shared_ptr<IParsedData>> TextureFactory::parse(std::vector<ui return std::nullopt; } - if(!gTextureFormats.contains(format)) { + if(!sTextureFormats.contains(format)) { return std::nullopt; } - TextureFormat fmt = gTextureFormats.at(format); + TextureFormat fmt = sTextureFormats.at(format); if(fmt.type == TextureType::TLUT){ width = GetSafeNode<uint32_t>(node, "colors"); @@ -363,12 +309,12 @@ std::optional<std::shared_ptr<IParsedData>> TextureFactory::parse(std::vector<ui } Companion::Instance->AddAsset(tlutNode); } - size = GetSafeNode<uint32_t>(node, "size", CalculateTextureSize(gTextureFormats.at(format).type, width, height)); + size = GetSafeNode<uint32_t>(node, "size", TextureUtils::CalculateTextureSize(sTextureFormats.at(format).type, width, height)); auto [_, segment] = Decompressor::AutoDecode(node, buffer, size); std::vector<uint8_t> result; if(fmt.type == TextureType::GrayscaleAlpha1bpp){ - result = alloc_ia8_text_from_i1(reinterpret_cast<uint16_t*>(segment.data), 8, 16); + result = TextureUtils::alloc_ia8_text_from_i1(reinterpret_cast<uint16_t*>(segment.data), 8, 16); } else { result = std::vector(segment.data, segment.data + segment.size); } @@ -408,11 +354,11 @@ std::optional<std::shared_ptr<IParsedData>> TextureFactory::parse_modding(std::v return std::nullopt; } - if(!gTextureFormats.contains(format)) { + if(!sTextureFormats.contains(format)) { return std::nullopt; } - TextureFormat fmt = gTextureFormats.at(format); + TextureFormat fmt = sTextureFormats.at(format); if(fmt.type == TextureType::TLUT){ width = GetSafeNode<uint32_t>(node, "colors"); height = 1; diff --git a/src/factories/TextureFactory.h b/src/factories/TextureFactory.h index 102ee54..9fa0006 100644 --- a/src/factories/TextureFactory.h +++ b/src/factories/TextureFactory.h @@ -1,26 +1,7 @@ #pragma once #include "BaseFactory.h" - -enum class TextureType { - Error, - RGBA32bpp, - RGBA16bpp, - Palette4bpp, - Palette8bpp, - Grayscale4bpp, - Grayscale8bpp, - GrayscaleAlpha4bpp, - GrayscaleAlpha8bpp, - GrayscaleAlpha16bpp, - GrayscaleAlpha1bpp, - TLUT -}; - -struct TextureFormat { - TextureType type; - uint32_t depth; -}; +#include "utils/TextureUtils.h" class TextureData : public IParsedData { public: diff --git a/src/factories/naudio/v0/AudioHeaderFactory.cpp b/src/factories/naudio/v0/AudioHeaderFactory.cpp index dc4d547..e4ef99f 100644 --- a/src/factories/naudio/v0/AudioHeaderFactory.cpp +++ b/src/factories/naudio/v0/AudioHeaderFactory.cpp @@ -1,7 +1,42 @@ #include "AudioHeaderFactory.h" #include <vector> -#include "AudioManager.h" +#include "Companion.h" +#include "AIFCDecode.h" +#include "spdlog/spdlog.h" +#include <factories/naudio/v1/AudioConverter.h> + +/* +ExportResult AudioAIFCExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) { + + auto samples = AudioManager::Instance->get_samples(); + + int temp = 0; + for(auto& sample : samples){ + std::string dpath = Companion::Instance->GetOutputPath() + "/" + (*replacement); + if(!exists(fs::path(dpath).parent_path())){ + create_directories(fs::path(dpath).parent_path()); + } + std::ofstream file(dpath + "_bank_" + std::to_string(++temp) + ".aiff", std::ios::binary); + + LUS::BinaryWriter aifc = LUS::BinaryWriter(); + AudioConverter::SampleV0ToAIFC(sample, aifc); + + LUS::BinaryWriter aiff = LUS::BinaryWriter(); + write_aiff(aifc.ToVector(), aiff); + aifc.Close(); + aiff.Finish(file); + file.close(); + // SPDLOG_INFO("Exported {}", dpath + "_bank_" + std::to_string(temp) + ".aiff"); + + SPDLOG_INFO("sample_{}:", temp); + SPDLOG_INFO(" type: NAUDIO:V0:SAMPLE"); + SPDLOG_INFO(" id: {}\n", temp); + } + + return std::nullopt; +} +*/ std::optional<std::shared_ptr<IParsedData>> AudioHeaderFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& data) { AudioManager::Instance->initialize(buffer, data); diff --git a/src/factories/naudio/v0/AudioHeaderFactory.h b/src/factories/naudio/v0/AudioHeaderFactory.h index 1c5c0b1..7e4ef5d 100644 --- a/src/factories/naudio/v0/AudioHeaderFactory.h +++ b/src/factories/naudio/v0/AudioHeaderFactory.h @@ -2,6 +2,13 @@ #include <factories/BaseFactory.h> +/* +class AudioAIFCExporter : public BaseExporter { +public: + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement); +}; +*/ + class AudioDummyExporter : public BaseExporter { public: ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override { @@ -17,11 +24,11 @@ public: } std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override { return { - REGISTER(Modding, AudioDummyExporter) + // REGISTER(Modding, AudioGenericAIFCExporter) REGISTER(Header, AudioDummyExporter) REGISTER(Binary, AudioDummyExporter) REGISTER(Code, AudioDummyExporter) }; } - bool SupportModdedAssets() override { return true; } + bool HasModdedDependencies() override { return true; } };
\ No newline at end of file diff --git a/src/factories/naudio/v0/AudioManager.cpp b/src/factories/naudio/v0/AudioManager.cpp index c168a39..4c836a9 100644 --- a/src/factories/naudio/v0/AudioManager.cpp +++ b/src/factories/naudio/v0/AudioManager.cpp @@ -128,9 +128,11 @@ Bank AudioManager::parse_ctl(CTLHeader header, std::vector<uint8_t> data, Sample for (size_t i = 0; i < numDrums; ++i) { uint32_t drumOffset; memcpy(&drumOffset, rawData + drumBaseAddr + i * 4, 4); - drumOffset = BSWAP32(drumOffset); - assert(drumOffset != 0); - drumOffsets.push_back(drumOffset); + if(drumOffset == 0){ + continue; + } + + drumOffsets.push_back(BSWAP32(drumOffset)); } } else { assert(drumBaseAddr == 0); @@ -382,7 +384,14 @@ AudioBankSample* AudioManager::parse_sample(std::vector<uint8_t>& data, std::vec uint32_t loop = reader.ReadUInt32(); uint32_t book = reader.ReadUInt32(); uint32_t sampleSize = reader.ReadUInt32(); - assert(zero == 0); + + SPDLOG_INFO("Zero: 0x{:X}", zero); + SPDLOG_INFO("Addr: 0x{:X}", addr); + SPDLOG_INFO("Loop: 0x{:X}", loop); + SPDLOG_INFO("Book: 0x{:X}", book); + SPDLOG_INFO("Sample Size: {}", sampleSize); + + // assert(zero == 0); assert(loop != 0); assert(book != 0); @@ -479,154 +488,6 @@ void AudioManager::initialize(std::vector<uint8_t>& buffer, YAML::Node& data) { } } -void serialize_f80(double num, LUS::BinaryWriter &writer) { - // Convert the input double to an uint64_t - std::uint64_t f64; - std::memcpy(&f64, &num, sizeof(double)); - - std::uint64_t f64_sign_bit = f64 & (std::uint64_t) pow(2, 63); - if (num == 0.0) { - if (f64_sign_bit) { - writer.Write(0x80000000); - } else { - writer.Write(0x00000000); - } - } - - std::uint64_t exponent = ((f64 ^ f64_sign_bit) >> 52); - - assert(exponent != 0); - assert(exponent != 0x7FF); - - exponent -= 1023; - uint64_t f64_mantissa_bits = f64 & (uint64_t) pow(2, 52) - 1; - uint64_t f80_sign_bit = f64_sign_bit << (80 - 64); - uint64_t f80_exponent = (exponent + 0x3FFF) << 64; - uint64_t f80_mantissa_bits = (uint64_t) pow(2, 63) | (f64_mantissa_bits << (63 - 52)); - uint64_t f80 = f80_sign_bit | f80_exponent | f80_mantissa_bits; - - // Split the f80 representation into two parts (high and low) - uint16_t high = BSWAP16((uint16_t) f80 >> 64); - writer.Write((char*) &high, 2); - uint64_t low = BSWAP64(f80 & ((uint64_t) pow(2, 64) - 1)); - writer.Write((char*) &low, 8); -} - -#define START_SECTION(section) \ - { \ - out.Write((uint32_t) BSWAP32(section)); \ - LUS::BinaryWriter tmp = LUS::BinaryWriter(); \ - tmp.SetEndianness(Torch::Endianness::Big); \ - -#define START_CUSTOM_SECTION(section) \ - { \ - LUS::BinaryWriter tmp = LUS::BinaryWriter(); \ - tmp.SetEndianness(Torch::Endianness::Big); \ - out.Write((uint32_t) BSWAP32(AIFC::MagicValues::AAPL)); \ - tmp.Write(AIFC::MagicValues::stoc); \ - tmp.Write(section, false); \ - -#define END_SECTION() \ - auto odata = tmp.ToVector(); \ - size_t size = odata.size(); \ - len += ALIGN(size, 2) + 8; \ - out.Write((uint32_t) BSWAP32((uint32_t) size)); \ - out.Write(odata.data(), odata.size()); \ - if(size % 2){ \ - out.WriteByte(0); \ - } \ - } \ - -void AudioManager::write_aifc(AudioBankSample* entry, LUS::BinaryWriter &out) { - int16_t num_channels = 1; - auto data = entry->data; - size_t len = 0; - assert(data.size() % 9 == 0); - if(data.size() % 2 == 1){ - data.push_back('\0'); - } - uint32_t num_frames = data.size() * 16 / 9; - int16_t sample_size = 16; - - uint32_t sample_rate = -1; - if(entry->tunings.size() == 1){ - sample_rate = 32000 * entry->tunings[0]; - } else { - float tmin = PyUtils::min(entry->tunings); - float tmax = PyUtils::max(entry->tunings); - - if(tmin <= 0.5f <= tmax){ - sample_rate = 16000; - } else if(tmin <= 1.0f <= tmax){ - sample_rate = 32000; - } else if(tmin <= 1.5f <= tmax){ - sample_rate = 48000; - } else if(tmin <= 2.5f <= tmax){ - sample_rate = 80000; - } else { - sample_rate = 16000 * (tmin + tmax); - } - } - - out.Write((uint32_t) BSWAP32(AIFC::MagicValues::FORM)); - // This should be where the size is, but we need to write it later - out.Write((uint32_t) 0); - out.Write((uint32_t) BSWAP32(AIFC::MagicValues::AIFC)); - - START_SECTION(AIFC::MagicValues::COMM); - - tmp.Write((uint16_t) num_channels); - tmp.Write((uint32_t) num_frames); - - tmp.Write((uint16_t) sample_size); - serialize_f80(sample_rate, tmp); - tmp.Write(AIFC::MagicValues::VAPC); - tmp.Write("\x0bVADPCM ~4-1", false); - - END_SECTION(); - - START_SECTION(AIFC::MagicValues::INST) - tmp.Write(std::string(20, '\0'), false); - END_SECTION(); - - START_CUSTOM_SECTION("\x0bVADPCMCODES") - tmp.Write((uint16_t) 1); - tmp.Write((uint16_t) entry->book.order); - tmp.Write((uint16_t) entry->book.npredictors); - - for(auto x : entry->book.table){ - tmp.Write((int16_t) x); - } - END_SECTION(); - - START_SECTION(AIFC::MagicValues::SSND) - uint32_t zero = 0; - tmp.Write((char*) &zero, 4); - tmp.Write((char*) &zero, 4); - tmp.Write((char*) data.data(), data.size()); - END_SECTION(); - - if(entry->loop.count != 0){ - START_CUSTOM_SECTION("\x0bVADPCMLOOPS") - uint16_t one = BSWAP16(1); - tmp.Write(reinterpret_cast<char*>(&one), 2); - tmp.Write(reinterpret_cast<char*>(&one), 2); - tmp.Write(entry->loop.start); - tmp.Write(entry->loop.end); - tmp.Write(entry->loop.count); - for(size_t i = 0; i < 16; i++){ - int16_t loop = BSWAP16(entry->loop.state.value()[i]); - tmp.Write(reinterpret_cast<char*>(&loop), 2); - } - END_SECTION(); - } - - len += 4; - out.Seek(4, LUS::SeekOffsetType::Start); - out.Write((uint32_t) BSWAP32(len)); - -} - void AudioManager::bind_sample(YAML::Node& node, const std::string& path){ auto id = GetSafeNode<uint32_t>(node, "id"); sample_table[id] = path; @@ -639,6 +500,7 @@ std::string& AudioManager::get_sample(uint32_t id) { return sample_table[id]; } +/* void AudioManager::create_aifc(int32_t index, LUS::BinaryWriter &out) { int32_t idx = -1; for(auto &sample_bank : this->loaded_tbl.banks){ @@ -653,6 +515,7 @@ void AudioManager::create_aifc(int32_t index, LUS::BinaryWriter &out) { } } } +*/ AudioBankSample AudioManager::get_aifc(int32_t index) { int32_t idx = 0; @@ -680,4 +543,21 @@ uint32_t AudioManager::get_index(AudioBankSample* entry) { std::map<uint32_t, Bank> AudioManager::get_banks() { return this->banks; +} + +std::vector<SampleBank*> AudioManager::get_loaded_banks() { + return this->loaded_tbl.banks; +} + +std::vector<AudioBankSample*> AudioManager::get_samples() { + std::vector<AudioBankSample*> samples; + for(auto &bank : this->loaded_tbl.banks){ + for(auto &entry : bank->entries){ + // Avoid duplicates + if(std::find(samples.begin(), samples.end(), entry.second) == samples.end()){ + samples.push_back(entry.second); + } + } + } + return samples; }
\ No newline at end of file diff --git a/src/factories/naudio/v0/AudioManager.h b/src/factories/naudio/v0/AudioManager.h index b5a9dcc..644ce15 100644 --- a/src/factories/naudio/v0/AudioManager.h +++ b/src/factories/naudio/v0/AudioManager.h @@ -12,19 +12,6 @@ #define NONE 0xFFFF #define ALIGN(val, al) (size_t) ((val + (al - 1)) & -al) -namespace AIFC { - enum MagicValues { - FORM = 0x464f524d, - AIFC = 0x41494643, - COMM = 0x434f4d4d, - INST = 0x494e5354, - VAPC = 0x56415043, - SSND = 0x53534e44, - AAPL = 0x4150504c, - stoc = 0x73746f63, - }; -} - struct Entry { uint32_t offset; uint32_t length; @@ -139,12 +126,12 @@ public: static AudioManager* Instance; void initialize(std::vector<uint8_t>& buffer, YAML::Node& data); void bind_sample(YAML::Node& node, const std::string& path); - void create_aifc(int32_t index, LUS::BinaryWriter& writer); std::string& get_sample(uint32_t id); AudioBankSample get_aifc(int32_t index); std::map<uint32_t, Bank> get_banks(); + std::vector<SampleBank*> get_loaded_banks(); + std::vector<AudioBankSample*> get_samples(); uint32_t get_index(AudioBankSample* bank); - private: std::map<uint32_t, Bank> banks; std::map<AudioBankSample*, uint32_t> sampleMap; @@ -161,6 +148,4 @@ private: static std::vector<AdsrEnvelope> parse_envelope(uint32_t addr, std::vector<uint8_t>& dataBank); static Bank parse_ctl(CTLHeader header, std::vector<uint8_t> data, SampleBank* bank, uint32_t index); static TBLFile parse_tbl(std::vector<uint8_t>& data, std::vector<Entry>& entries); - - static void write_aifc(AudioBankSample* entry, LUS::BinaryWriter& writer); };
\ No newline at end of file diff --git a/src/factories/naudio/v0/SampleFactory.cpp b/src/factories/naudio/v0/SampleFactory.cpp index b8bc7fa..8d25096 100644 --- a/src/factories/naudio/v0/SampleFactory.cpp +++ b/src/factories/naudio/v0/SampleFactory.cpp @@ -1,5 +1,25 @@ #include "SampleFactory.h" +#include <vector> +#include "Companion.h" +#include "AIFCDecode.h" +#include "spdlog/spdlog.h" +#include <factories/naudio/v1/AudioConverter.h> + +ExportResult SampleModdingExporter::Export(std::ostream& writer, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node& node, std::string* replacement) { + auto sample = std::static_pointer_cast<SampleData>(raw); + *replacement += ".aiff"; + + LUS::BinaryWriter aifc = LUS::BinaryWriter(); + AudioConverter::SampleV0ToAIFC(&sample->mSample, aifc); + + LUS::BinaryWriter aiff = LUS::BinaryWriter(); + write_aiff(aifc.ToVector(), aiff); + aifc.Close(); + aiff.Finish(writer); + return std::nullopt; +} + ExportResult SampleBinaryExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { auto writer = LUS::BinaryWriter(); auto sample = std::static_pointer_cast<SampleData>(raw)->mSample; diff --git a/src/factories/naudio/v0/SampleFactory.h b/src/factories/naudio/v0/SampleFactory.h index ae940e2..ed292fe 100644 --- a/src/factories/naudio/v0/SampleFactory.h +++ b/src/factories/naudio/v0/SampleFactory.h @@ -5,6 +5,11 @@ #include <factories/BaseFactory.h> #include "AudioManager.h" +class SampleModdingExporter : public BaseExporter { +public: + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement); +}; + class SampleData : public IParsedData { public: AudioBankSample mSample; @@ -21,7 +26,10 @@ public: std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; std::unordered_map<ExportType, std::shared_ptr<BaseExporter>> GetExporters() override { return { + REGISTER(Modding, SampleModdingExporter) REGISTER(Binary, SampleBinaryExporter) }; } + + bool SupportModdedAssets() override { return true; } };
\ No newline at end of file diff --git a/src/factories/naudio/v1/AudioConverter.cpp b/src/factories/naudio/v1/AudioConverter.cpp index f476422..62d1470 100644 --- a/src/factories/naudio/v1/AudioConverter.cpp +++ b/src/factories/naudio/v1/AudioConverter.cpp @@ -8,6 +8,7 @@ #include <Companion.h> #include <cassert> #include <cstring> +#include "hj/pyutils.h" void AIFCWriter::End(std::string chunk, LUS::BinaryWriter& writer) { auto buffer = writer.ToVector(); @@ -79,7 +80,92 @@ void SerializeF80(double num, LUS::BinaryWriter &writer) { writer.Write(low); } -void AudioConverter::SampleToAIFC(NSampleData* sample, LUS::BinaryWriter &out) { +void AudioConverter::SampleV0ToAIFC(AudioBankSample* sample, LUS::BinaryWriter &out) { + auto aifc = AIFCWriter(); + auto data = sample->data; + + uint32_t num_frames = data.size() * 16 / 9; + uint32_t sample_rate = -1; + + if(sample->tunings.size() == 1){ + sample_rate = 32000 * sample->tunings[0]; + } else { + float tmin = PyUtils::min(sample->tunings); + float tmax = PyUtils::max(sample->tunings); + + if(tmin <= 0.5f <= tmax){ + sample_rate = 16000; + } else if(tmin <= 1.0f <= tmax){ + sample_rate = 32000; + } else if(tmin <= 1.5f <= tmax){ + sample_rate = 48000; + } else if(tmin <= 2.5f <= tmax){ + sample_rate = 80000; + } else { + sample_rate = 16000 * (tmin + tmax); + } + } + + int16_t num_channels = 1; + int16_t sample_size = 16; + + // COMM Chunk + auto comm = aifc.Start(); + comm.Write(num_channels); + comm.Write(num_frames); + comm.Write(sample_size); + SerializeF80(sample_rate, comm); + comm.Write(AIFCMagicValues::VAPC); + comm.Write((char*) "\x0bVADPCM ~4-1", 12); + aifc.End("COMM", comm); + + // INST Chunk + auto inst = aifc.Start(); + for(size_t i = 0; i < 5; i++){ + inst.Write((int32_t) 0); + } + aifc.End("INST", inst); + + // VADPCMCODES Chunk + auto vcodes = aifc.Start(); + vcodes.Write((char*) "stoc\x0bVADPCMCODES", 16); + vcodes.Write((int16_t) 1); + vcodes.Write((int16_t) sample->book.order); + vcodes.Write((int16_t) sample->book.npredictors); + + for(auto page : sample->book.table){ + vcodes.Write(page); + } + aifc.End("APPL", vcodes); + + // SSND Chunk + auto ssnd = aifc.Start(); + ssnd.Write((uint64_t) 0); + ssnd.Write((char*) data.data(), data.size()); + aifc.End("SSND", ssnd); + + // VADPCMLOOPS + if(sample->loop.count != 0){ + auto vloops = aifc.Start(); + vloops.Write((char*) "stoc\x0bVADPCMLOOPS", 16); + vloops.Write((uint16_t) 1); + vloops.Write((uint16_t) 1); + vloops.Write(sample->loop.start); + vloops.Write(sample->loop.end); + vloops.Write(sample->loop.count); + + if(sample->loop.state.has_value()){ + for(auto state : sample->loop.state.value()){ + vcodes.Write(state); + } + } + aifc.End("APPL", vloops); + } + + aifc.Close(out); +} + +void AudioConverter::SampleV1ToAIFC(NSampleData* sample, LUS::BinaryWriter &out) { auto loop = std::static_pointer_cast<ADPCMLoopData>(Companion::Instance->GetParseDataByAddr(sample->loop)->data.value()); auto book = std::static_pointer_cast<ADPCMBookData>(Companion::Instance->GetParseDataByAddr(sample->book)->data.value()); auto entry = AudioContext::tableData[AudioTableType::SAMPLE_TABLE]->entries[sample->sampleBankId]; diff --git a/src/factories/naudio/v1/AudioConverter.h b/src/factories/naudio/v1/AudioConverter.h index ef87a0d..1840ab0 100644 --- a/src/factories/naudio/v1/AudioConverter.h +++ b/src/factories/naudio/v1/AudioConverter.h @@ -2,6 +2,7 @@ #include <factories/BaseFactory.h> #include <factories/naudio/v1/SampleFactory.h> +#include <factories/naudio/v0/AudioManager.h> enum AIFCMagicValues { FORM = (uint32_t) 0x464f524d, @@ -15,7 +16,8 @@ enum AIFCMagicValues { class AudioConverter { public: - static void SampleToAIFC(NSampleData* tSample, LUS::BinaryWriter &out); + static void SampleV0ToAIFC(AudioBankSample* entry, LUS::BinaryWriter &out); + static void SampleV1ToAIFC(NSampleData* tSample, LUS::BinaryWriter &out); }; struct AIFCChunk { diff --git a/src/factories/naudio/v1/SampleFactory.cpp b/src/factories/naudio/v1/SampleFactory.cpp index 18d539a..a89888f 100644 --- a/src/factories/naudio/v1/SampleFactory.cpp +++ b/src/factories/naudio/v1/SampleFactory.cpp @@ -51,7 +51,7 @@ ExportResult NSampleModdingExporter::Export(std::ostream &write, std::shared_ptr *replacement += ".aiff"; auto aifc = LUS::BinaryWriter(); - AudioConverter::SampleToAIFC(data.get(), aifc); + AudioConverter::SampleV1ToAIFC(data.get(), aifc); auto cnv = aifc.ToVector(); if(!cnv.empty()){ diff --git a/src/factories/sf64/SkeletonFactory.cpp b/src/factories/sf64/SkeletonFactory.cpp index af10694..ff1a762 100644 --- a/src/factories/sf64/SkeletonFactory.cpp +++ b/src/factories/sf64/SkeletonFactory.cpp @@ -111,9 +111,8 @@ ExportResult SF64::SkeletonBinaryExporter::Export(std::ostream &write, std::shar auto limbWriter = LUS::BinaryWriter(); WriteHeader(limbWriter, Torch::ResourceType::Limb, 0); - bool hasDList = limb.mDList != 0 && (SEGMENT_NUMBER(limb.mDList) == SEGMENT_NUMBER(limb.mAddr)); - if(hasDList){ + if(limb.mDList != 0){ auto dec = Companion::Instance->GetNodeByAddr(limb.mDList); if (dec.has_value()){ std::string path = std::get<0>(dec.value()); diff --git a/src/preprocess/CompTool.cpp b/src/preprocess/CompTool.cpp new file mode 100644 index 0000000..f453050 --- /dev/null +++ b/src/preprocess/CompTool.cpp @@ -0,0 +1,87 @@ +#include "CompTool.h" +#include "utils/Decompressor.h" +#include "lib/binarytools/BinaryWriter.h" +#include "lib/binarytools/BinaryReader.h" +#include <fstream> +#include <cstring> + +uint32_t CompTool::FindFileTable(std::vector<uint8_t>& rom) { + uint8_t query_one[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x50, 0x00, 0x00, 0x00, 0x00 }; + uint8_t query_two[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x60, 0x00, 0x00, 0x00, 0x00 }; + + for(size_t i = 0; i < rom.size() - sizeof(query_one); i++) { + if(memcmp(rom.data() + i, query_one, sizeof(query_one)) == 0){ + return i; + } + + if(memcmp(rom.data() + i, query_two, sizeof(query_two)) == 0){ + return i; + } + } + + throw std::runtime_error("Failed to find file table"); +} + +std::vector<uint8_t> CompTool::Decompress(std::vector<uint8_t> rom){ + LUS::BinaryReader basefile((char*) rom.data(), rom.size()); + basefile.SetEndianness(Torch::Endianness::Big); + + LUS::BinaryWriter decompfile; + decompfile.SetEndianness(Torch::Endianness::Big); + decompfile.Write((uint8_t)0x80); + + uint32_t table = CompTool::FindFileTable(rom); + uint32_t count = 0; + while (true){ + auto entry = table + 0x10 * count; + basefile.Seek(entry, LUS::SeekOffsetType::Start); + + auto v_begin = basefile.ReadInt32(); + auto p_begin = basefile.ReadInt32(); + auto p_end = basefile.ReadInt32(); + auto comp_flag = basefile.ReadInt32(); + + auto p_size = p_end - p_begin; + auto v_size = (int32_t) 0; + DataChunk* decoded = nullptr; + + if(v_begin == 0 && p_end == 0){ + break; + } + + basefile.Seek(p_begin, LUS::SeekOffsetType::Start); + + auto bytes = new uint8_t[p_size]; + basefile.Read((char*) bytes, p_size); + + switch ((CompType) comp_flag) { + case CompType::UNCOMPRESSED: + v_size = p_size; + break; + case CompType::COMPRESSED: + decoded = Decompressor::Decode(std::vector(bytes, bytes + p_size), 0, CompressionType::MIO0, true); + bytes = decoded->data; + v_size = decoded->size; + break; + default: + throw std::runtime_error("Invalid compression flag. There may be a problem with your ROM."); + } + + decompfile.Seek(v_begin, LUS::SeekOffsetType::Start); + decompfile.Write((char*) bytes, v_size); + auto v_end = v_begin + v_size; + + decompfile.Seek(entry + 4, LUS::SeekOffsetType::Start); + decompfile.Write(v_begin); + decompfile.Write(v_end); + decompfile.Write((uint32_t) CompType::UNCOMPRESSED); + count++; + } + + decompfile.Seek(0x10, LUS::SeekOffsetType::Start); + decompfile.Write(0xA7D5F194); // CRC1 + decompfile.Write(0xFE3DF761); // CRC2 + + auto result = decompfile.ToVector(); + return { (uint8_t*) result.data(), (uint8_t*) result.data() + result.size() }; +}
\ No newline at end of file diff --git a/src/preprocess/CompTool.h b/src/preprocess/CompTool.h new file mode 100644 index 0000000..15b7db5 --- /dev/null +++ b/src/preprocess/CompTool.h @@ -0,0 +1,18 @@ +#pragma once + +#include <cstdint> +#include <string> +#include <vector> + +enum class CompType { + UNCOMPRESSED, + COMPRESSED, + UNKNOWN +}; + +class CompTool { +public: + static std::vector<uint8_t> Decompress(std::vector<uint8_t> rom); +private: + static uint32_t FindFileTable(std::vector<uint8_t>& rom); +};
\ No newline at end of file diff --git a/src/utils/Decompressor.cpp b/src/utils/Decompressor.cpp index 95af7b1..1fee382 100644 --- a/src/utils/Decompressor.cpp +++ b/src/utils/Decompressor.cpp @@ -12,9 +12,9 @@ extern "C" { std::unordered_map<uint32_t, DataChunk*> gCachedChunks; -DataChunk* Decompressor::Decode(const std::vector<uint8_t>& buffer, const uint32_t offset, const CompressionType type) { +DataChunk* Decompressor::Decode(const std::vector<uint8_t>& buffer, const uint32_t offset, const CompressionType type, bool ignoreCache) { - if(gCachedChunks.contains(offset)){ + if(!ignoreCache && gCachedChunks.contains(offset)){ return gCachedChunks[offset]; } @@ -208,4 +208,4 @@ void Decompressor::ClearCache() { delete value->data; } gCachedChunks.clear(); -} +}
\ No newline at end of file diff --git a/src/utils/Decompressor.h b/src/utils/Decompressor.h index 4d2056a..4aff942 100644 --- a/src/utils/Decompressor.h +++ b/src/utils/Decompressor.h @@ -32,7 +32,7 @@ struct DecompressedData { class Decompressor { public: - static DataChunk* Decode(const std::vector<uint8_t>& buffer, uint32_t offset, CompressionType type); + static DataChunk* Decode(const std::vector<uint8_t>& buffer, uint32_t offset, CompressionType type, bool ignoreCache = false); static DataChunk* DecodeTKMK00(const std::vector<uint8_t>& buffer, const uint32_t offset, const uint32_t size, const uint32_t alpha); static DecompressedData AutoDecode(YAML::Node& node, std::vector<uint8_t>& buffer, std::optional<size_t> size = std::nullopt); static DecompressedData AutoDecode(uint32_t offset, std::optional<size_t> size, std::vector<uint8_t>& buffer); diff --git a/src/utils/TextureUtils.cpp b/src/utils/TextureUtils.cpp new file mode 100644 index 0000000..7b70f19 --- /dev/null +++ b/src/utils/TextureUtils.cpp @@ -0,0 +1,57 @@ +#include "TextureUtils.h" +#include <vector> +#include <binarytools/endianness.h> + +size_t TextureUtils::CalculateTextureSize(TextureType type, uint32_t width, uint32_t height) { + switch (type) { + // 4 bytes per pixel + case TextureType::RGBA32bpp: + return width * height * 4; + // 2 bytes per pixel + case TextureType::TLUT: + case TextureType::RGBA16bpp: + case TextureType::GrayscaleAlpha16bpp: + return width * height * 2; + // 1 byte per pixel + case TextureType::Grayscale8bpp: + case TextureType::Palette8bpp: + case TextureType::GrayscaleAlpha8bpp: + // TODO: We need to validate this MegaMech + case TextureType::GrayscaleAlpha1bpp: + return width * height; + // 1/2 byte per pixel + case TextureType::Palette4bpp: + case TextureType::Grayscale4bpp: + case TextureType::GrayscaleAlpha4bpp: + return (width * height) / 2; + default: + return 0; + } +} + +std::vector<uint8_t> TextureUtils::alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height) { + int32_t inPos; + uint16_t bitMask; + int16_t outPos = 0; + const auto out = new uint8_t[width * height]; + + for (int32_t inPos = 0; inPos < (width * height) / 16; inPos++) { + uint16_t bitMask = 0x8000; + + while (bitMask != 0) { + if (BSWAP16(in[inPos]) & bitMask) { + out[outPos] = 0xFF; + } else { + out[outPos] = 0x00; + } + + bitMask /= 2; + outPos++; + } + } + + auto result = std::vector(out, out + width * height); + delete[] out; + + return result; +}
\ No newline at end of file diff --git a/src/utils/TextureUtils.h b/src/utils/TextureUtils.h new file mode 100644 index 0000000..90501c6 --- /dev/null +++ b/src/utils/TextureUtils.h @@ -0,0 +1,31 @@ +#pragma once + +#include <cstdint> +#include <vector> +#include <cstddef> + +enum class TextureType { + Error, + RGBA32bpp, + RGBA16bpp, + Palette4bpp, + Palette8bpp, + Grayscale4bpp, + Grayscale8bpp, + GrayscaleAlpha4bpp, + GrayscaleAlpha8bpp, + GrayscaleAlpha16bpp, + GrayscaleAlpha1bpp, + TLUT +}; + +struct TextureFormat { + TextureType type; + uint32_t depth; +}; + +class TextureUtils { + public: + static size_t CalculateTextureSize(TextureType type, uint32_t width, uint32_t height); + static std::vector<uint8_t> alloc_ia8_text_from_i1(uint16_t *in, int16_t width, int16_t height); +}; |
