#include "OoTAnimationFactory.h" #include "spdlog/spdlog.h" #include "Companion.h" #include "utils/Decompressor.h" namespace OoT { std::optional> OoTAnimationFactory::parse(std::vector& buffer, YAML::Node& node) { // Check for legacy animation type (data stays in ROM segment, not extracted) auto animType = GetSafeNode(node, "anim_type", "normal"); if (animType == "legacy") { auto anim = std::make_shared(); anim->frameCount = 0; anim->limit = 0; anim->isLegacy = true; return anim; } auto [_, segment] = Decompressor::AutoDecode(node, buffer, 0x10); LUS::BinaryReader reader(segment.data, segment.size); reader.SetEndianness(Torch::Endianness::Big); // ROM layout: AnimationHeader (16 bytes) // +0x00: int16 frameCount // +0x02: int16 padding // +0x04: segptr rotationValues // +0x08: segptr rotationIndices // +0x0C: int16 limit // +0x0E: int16 padding int16_t frameCount = reader.ReadInt16(); reader.ReadInt16(); // padding uint32_t rawRotValues = reader.ReadUInt32(); uint32_t rawRotIndices = reader.ReadUInt32(); int16_t limit = reader.ReadInt16(); uint32_t rotValuesAddr = Companion::Instance->PatchVirtualAddr(rawRotValues); uint32_t rotIndicesAddr = Companion::Instance->PatchVirtualAddr(rawRotIndices); auto anim = std::make_shared(); anim->frameCount = frameCount; anim->limit = limit; // Translate segmented addresses to file offsets uint32_t rotValuesOffset = Decompressor::TranslateAddr(rotValuesAddr); uint32_t rotIndicesOffset = Decompressor::TranslateAddr(rotIndicesAddr); uint32_t animHeaderOffset = Decompressor::TranslateAddr(Companion::Instance->PatchVirtualAddr(GetSafeNode(node, "offset"))); // Read rotation values: array of uint16 from rotValues to rotIndices uint32_t rotValuesCount = (rotIndicesOffset - rotValuesOffset) / 2; if (rotValuesCount > 0 && rotValuesOffset < rotIndicesOffset) { YAML::Node rvNode; rvNode["offset"] = rotValuesAddr; auto rvRaw = Decompressor::AutoDecode(rvNode, buffer, rotValuesCount * 2); LUS::BinaryReader rvReader(rvRaw.segment.data, rvRaw.segment.size); rvReader.SetEndianness(Torch::Endianness::Big); for (uint32_t i = 0; i < rotValuesCount; i++) { anim->rotationValues.push_back(rvReader.ReadUInt16()); } } // Read rotation indices: array of {x,y,z} uint16 from rotIndices to animHeader uint32_t rotIndicesCount = (animHeaderOffset - rotIndicesOffset) / 6; if (rotIndicesCount > 0 && rotIndicesOffset < animHeaderOffset) { YAML::Node riNode; riNode["offset"] = rotIndicesAddr; auto riRaw = Decompressor::AutoDecode(riNode, buffer, rotIndicesCount * 6); LUS::BinaryReader riReader(riRaw.segment.data, riRaw.segment.size); riReader.SetEndianness(Torch::Endianness::Big); for (uint32_t i = 0; i < rotIndicesCount; i++) { RotationIndex ri; ri.x = riReader.ReadUInt16(); ri.y = riReader.ReadUInt16(); ri.z = riReader.ReadUInt16(); anim->rotationIndices.push_back(ri); } } return anim; } ExportResult OoTAnimationBinaryExporter::Export(std::ostream& write, std::shared_ptr raw, std::string& entryName, YAML::Node& node, std::string* replacement) { auto writer = LUS::BinaryWriter(); auto anim = std::static_pointer_cast(raw); WriteHeader(writer, Torch::ResourceType::OoTAnimation, 0); if (anim->isLegacy) { writer.Write(static_cast(OoTAnimationType::Legacy)); writer.Finish(write); return std::nullopt; } writer.Write(static_cast(OoTAnimationType::Normal)); writer.Write(anim->frameCount); writer.Write(static_cast(anim->rotationValues.size())); for (auto& val : anim->rotationValues) { writer.Write(val); } writer.Write(static_cast(anim->rotationIndices.size())); for (auto& ri : anim->rotationIndices) { writer.Write(ri.x); writer.Write(ri.y); writer.Write(ri.z); } writer.Write(anim->limit); writer.Finish(write); return std::nullopt; } } // namespace OoT