summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKiritoDv <kiritodev01@gmail.com>2024-12-25 00:43:20 -0600
committerKiritoDv <kiritodev01@gmail.com>2024-12-25 00:43:20 -0600
commit64266cbac2f51e50380ea0d7191b97e63e64ddfa (patch)
treef774cdbecb283295fe283244b6c370b33d9a2b69 /src
parent0efd51b717fe7ffef8a8c93a4c25efcb8ef3f919 (diff)
parentabb74ef940afe35ddf7bea5ea2e03c45484a95a3 (diff)
Merge branch 'main' into audio_extraction
Diffstat (limited to 'src')
-rw-r--r--src/Companion.cpp49
-rw-r--r--src/factories/BaseFactory.h6
-rw-r--r--src/factories/CompressedTextureFactory.cpp538
-rw-r--r--src/factories/CompressedTextureFactory.h47
-rw-r--r--src/factories/TextureFactory.cpp74
-rw-r--r--src/factories/TextureFactory.h21
-rw-r--r--src/factories/naudio/v0/AudioHeaderFactory.cpp37
-rw-r--r--src/factories/naudio/v0/AudioHeaderFactory.h11
-rw-r--r--src/factories/naudio/v0/AudioManager.cpp184
-rw-r--r--src/factories/naudio/v0/AudioManager.h19
-rw-r--r--src/factories/naudio/v0/SampleFactory.cpp20
-rw-r--r--src/factories/naudio/v0/SampleFactory.h8
-rw-r--r--src/factories/naudio/v1/AudioConverter.cpp88
-rw-r--r--src/factories/naudio/v1/AudioConverter.h4
-rw-r--r--src/factories/naudio/v1/SampleFactory.cpp2
-rw-r--r--src/factories/sf64/SkeletonFactory.cpp3
-rw-r--r--src/preprocess/CompTool.cpp87
-rw-r--r--src/preprocess/CompTool.h18
-rw-r--r--src/utils/Decompressor.cpp6
-rw-r--r--src/utils/Decompressor.h2
-rw-r--r--src/utils/TextureUtils.cpp57
-rw-r--r--src/utils/TextureUtils.h31
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);
+};