/** * File: ZTextureAnimation.cpp * ZResources defined: ZTextureAnimation, ZTextureAnimationParams (XML declaration not supported for * the latter) * Purpose: extracting texture animating structures from asset files * Note: data type is exclusive to Majora's Mask * * Structure of data: * A texture animation consists of a main array of data of the form * `SS 00 00 0T PPPPPPPP`, * where `S` is the segment that the functionality is sent to (and read by DLists), `T` is the * "type" (see below), and `P` is a pointer to the type's subsidiary information, which is * invariably just above this main array. Details of this subsidiary data are given below. * * Segment * === * The segment starts at 1 and is incremented in each entry; the final one is negated, which is used * as the indication to stop reading. The actual segment the game will use to draw is S + 7. * * Types * === * There are 7 values that `T` can take: * `0`: A single texture scroll (Implemented by Gfx_TexScroll): subsidiary params are given as * an array with one entry, a struct `XX YY WW HH` containing xStep, yStep, width, height * (all u8). * * `1`: A dual texture scroll (Implementated by Gfx_TwoTexScroll): same as type `0`, but with * two entries in the params array. * * `2`: Color changing: Changes the primColor (with a LOD factor) and envColor * `KKKK LLLL PPPPPPPP QQQQQQQQ RRRRRRRR` * - `K` (u16) is the total length of the animation (and in this case, the total number of * colors) * - `L` (u16) is seemingly always 0 for this type, and not used in the code * - `P` segmented pointer to array of augmented primColors * - `Q` segmented pointer to array of envColors, and can be NULL * - `R` segmented pointer to array of frameData (u8), which is also seemingly always NULL * for this type. * * envColors take the form `RR GG BB AA` as usual, while primColors have an extra LODFrac * element: RR GG BB AA LL * * `3`: Color changing (LERP): similar to type `2`, but uses linear interpolation. The structure * is similar to `2`, but * - `K` is now just the animation length, while * - `L` is the total number of colors, and * - the frameData is used to determine which colors to interpolate. * * `4`: Color changing (Lagrange interpolation): For extraction purposes identical to type 3. * Uses a nonlinear interpolation formula to change the colours more smoothly. * * `5`: Texture cycle: functions like a gif. Subsidiary params: * `FFFF 0000 PPPPPPPP QQQQQQQQ` * where * - `F` (u16) time between changes in frames * - `P` pointer to array of segmented pointers to textures to cycle through * - `Q` array of indices, indicating which texture to use next when a change frame is * reached. The maximum indicates the number of textures in the texture array. * * `6`: This is used to indicate an empty/unimplemented params set. It is ignored by the game's * function provided that the segment is 0. A generic empty one takes the form * `00 00 00 06 00000000`, * and has no extra data. * * Implementation * === * - ZTextureAnimation requires a declaration in the XML to extract anything. It handles the main * array. It uses a POD struct for each entry, and declares references to the params structs. * * - ZTextureAnimationParams is not (currently) declarable in an XML. It is a parent class for the * three classes that handle the various cases: * - TextureScrollingParams for types `0` and `1` * - TextureColorChangingParams for types `2`,`3`,`4` * - TextureCyclingParams for type `5` * Each of these will declare all its subsidiary arrays, using POD structs. */ #include "ZTextureAnimation.h" #include #include #include #include "Globals.h" #include "Utils/BitConverter.h" #include "WarningHandler.h" #include "ZFile.h" #include "ZResource.h" #include "tinyxml2.h" REGISTER_ZFILENODE(TextureAnimation, ZTextureAnimation); /* Constructors */ ZTextureAnimationParams::ZTextureAnimationParams(ZFile* parent) : ZResource::ZResource(parent) { } TextureScrollingParams::TextureScrollingParams(ZFile* parent) : ZTextureAnimationParams::ZTextureAnimationParams(parent) { } TextureColorChangingParams::TextureColorChangingParams(ZFile* parent) : ZTextureAnimationParams::ZTextureAnimationParams(parent) { } TextureCyclingParams::TextureCyclingParams(ZFile* parent) : ZTextureAnimationParams::ZTextureAnimationParams(parent) { } /* TextureAnimationParams */ /* This class only implements the functions common to all or most its inheritors */ void ZTextureAnimationParams::ExtractFromBinary(uint32_t nRawDataIndex) { rawDataIndex = nRawDataIndex; ParseRawData(); } // Implemented by TextureScrollingParams only void ZTextureAnimationParams::ExtractFromBinary([[maybe_unused]] uint32_t nRawDataIndex, [[maybe_unused]] int count) { } std::string ZTextureAnimationParams::GetDefaultName([[maybe_unused]] const std::string& prefix) const { return "ShouldNotBeVIsible"; } ZResourceType ZTextureAnimationParams::GetResourceType() const { return ZResourceType::TextureAnimationParams; } /* TextureScrollingParams */ void TextureScrollingParams::ParseRawData() { const auto& rawData = parent->GetRawData(); for (int i = 0; i < count; i++) { rows[i].xStep = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i); rows[i].yStep = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i + 1); rows[i].width = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i + 2); rows[i].height = BitConverter::ToUInt8BE(rawData, rawDataIndex + 4 * i + 3); } } void TextureScrollingParams::ExtractFromBinary(uint32_t nRawDataIndex, int nCount) { rawDataIndex = nRawDataIndex; count = nCount; ParseRawData(); } std::string TextureScrollingParams::GetSourceTypeName() const { return "AnimatedMatTexScrollParams"; // TODO: Better name } std::string TextureScrollingParams::GetDefaultName(const std::string& prefix) const { return StringHelper::Sprintf("%sTexScrollParams_%06X", prefix.c_str(), rawDataIndex); } size_t TextureScrollingParams::GetRawDataSize() const { return 4 * count; } /** * Overrides the parent version to declare an array of the params rather than just one entry. */ Declaration* TextureScrollingParams::DeclareVar(const std::string& prefix, const std::string& bodyStr) { std::string auxName = name; if (name == "") auxName = GetDefaultName(prefix); return parent->AddDeclarationArray(rawDataIndex, GetDeclarationAlignment(), GetRawDataSize(), GetSourceTypeName(), auxName, count, bodyStr); } std::string TextureScrollingParams::GetBodySourceCode() const { std::string bodyStr; for (int i = 0; i < count; i++) { bodyStr += StringHelper::Sprintf("\t{ %d, %d, 0x%02X, 0x%02X },\n", rows[i].xStep, rows[i].yStep, rows[i].width, rows[i].height); } bodyStr.pop_back(); return bodyStr; } /* TextureColorChangingParams */ /** * Also parses the color and frameData arrays */ void TextureColorChangingParams::ParseRawData() { const auto& rawData = parent->GetRawData(); animLength = BitConverter::ToUInt16BE(rawData, rawDataIndex); colorListCount = BitConverter::ToUInt16BE(rawData, rawDataIndex + 2); // Handle type 2 separately uint16_t listLength = ((type == TextureAnimationParamsType::ColorChange) ? animLength : colorListCount); if (listLength == 0) HANDLE_ERROR_RESOURCE(WarningType::Always, parent, this, rawDataIndex, "color list length cannot be 0", ""); primColorListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 4); envColorListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 8); frameDataListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 0xC); uint32_t primColorListOffset = Seg2Filespace(primColorListAddress, parent->baseAddress); uint32_t envColorListOffset = Seg2Filespace(envColorListAddress, parent->baseAddress); uint32_t frameDataListOffset = Seg2Filespace(frameDataListAddress, parent->baseAddress); uint32_t currentPtr; F3DPrimColor currentPrimColor; for (currentPtr = primColorListOffset; currentPtr < primColorListOffset + 5 * listLength; currentPtr += 5) { currentPrimColor = {BitConverter::ToUInt8BE(rawData, currentPtr), BitConverter::ToUInt8BE(rawData, currentPtr + 1), BitConverter::ToUInt8BE(rawData, currentPtr + 2), BitConverter::ToUInt8BE(rawData, currentPtr + 3), BitConverter::ToUInt8BE(rawData, currentPtr + 4)}; primColorList.push_back(currentPrimColor); } F3DEnvColor currentEnvColor; for (currentPtr = envColorListOffset; currentPtr < envColorListOffset + 4 * listLength; currentPtr += 4) { currentEnvColor = {BitConverter::ToUInt8BE(rawData, currentPtr), BitConverter::ToUInt8BE(rawData, currentPtr + 1), BitConverter::ToUInt8BE(rawData, currentPtr + 2), BitConverter::ToUInt8BE(rawData, currentPtr + 3)}; envColorList.push_back(currentEnvColor); } uint16_t currentFrameData; for (currentPtr = frameDataListOffset; currentPtr < frameDataListOffset + 2 * listLength; currentPtr += 2) { currentFrameData = BitConverter::ToUInt16BE(rawData, currentPtr); frameDataList.push_back(currentFrameData); } } std::string TextureColorChangingParams::GetSourceTypeName() const { return "AnimatedMatColorParams"; // TODO: Better name } std::string TextureColorChangingParams::GetDefaultName(const std::string& prefix) const { return StringHelper::Sprintf("%sColorParams_%06X", prefix.c_str(), rawDataIndex); } size_t TextureColorChangingParams::GetRawDataSize() const { return 0x10; } void TextureColorChangingParams::DeclareReferences([[maybe_unused]] const std::string& prefix) { if (primColorListAddress != 0) // NULL { std::string primColorBodyStr; for (const auto& color : primColorList) { primColorBodyStr += StringHelper::Sprintf("\t{ %d, %d, %d, %d, %d },\n", color.r, color.g, color.b, color.a, color.lodFrac); } primColorBodyStr.pop_back(); parent->AddDeclarationArray( Seg2Filespace(primColorListAddress, parent->baseAddress), DeclarationAlignment::Align4, primColorList.size() * 5, "F3DPrimColor", StringHelper::Sprintf("%sTexColorChangingPrimColors_%06X", parent->GetName().c_str(), Seg2Filespace(primColorListAddress, parent->baseAddress)), primColorList.size(), primColorBodyStr); } if (envColorListAddress != 0) // NULL { std::string envColorBodyStr; for (const auto& color : envColorList) { envColorBodyStr += StringHelper::Sprintf("\t{ %d, %d, %d, %d },\n", color.r, color.g, color.b, color.a); } envColorBodyStr.pop_back(); parent->AddDeclarationArray( Seg2Filespace(envColorListAddress, parent->baseAddress), DeclarationAlignment::Align4, envColorList.size() * 4, "F3DEnvColor", StringHelper::Sprintf("%sTexColorChangingEnvColors_%06X", parent->GetName().c_str(), Seg2Filespace(envColorListAddress, parent->baseAddress)), envColorList.size(), envColorBodyStr); } if (frameDataListAddress != 0) // NULL { std::string frameDataBodyStr = "\t"; for (const auto& frame : frameDataList) { frameDataBodyStr += StringHelper::Sprintf("%d, ", frame); } frameDataBodyStr.pop_back(); parent->AddDeclarationArray( Seg2Filespace(frameDataListAddress, parent->baseAddress), DeclarationAlignment::Align4, frameDataList.size() * 2, "u16", StringHelper::Sprintf("%sTexColorChangingFrameData_%06X", parent->GetName().c_str(), Seg2Filespace(frameDataListAddress, parent->baseAddress)), frameDataList.size(), frameDataBodyStr); } } std::string TextureColorChangingParams::GetBodySourceCode() const { std::string primColorListName; std::string envColorListName; std::string frameDataListName; Globals::Instance->GetSegmentedPtrName(primColorListAddress, parent, "", primColorListName, parent->workerID); Globals::Instance->GetSegmentedPtrName(envColorListAddress, parent, "", envColorListName, parent->workerID); Globals::Instance->GetSegmentedPtrName(frameDataListAddress, parent, "", frameDataListName, parent->workerID); std::string bodyStr = StringHelper::Sprintf( "\n %d, %d, %s, %s, %s,\n", animLength, colorListCount, primColorListName.c_str(), envColorListName.c_str(), frameDataListName.c_str()); return bodyStr; } /* TextureCyclingParams */ void TextureCyclingParams::ParseRawData() { const auto& rawData = parent->GetRawData(); cycleLength = BitConverter::ToUInt16BE(rawData, rawDataIndex); if (cycleLength == 0) HANDLE_ERROR_RESOURCE(WarningType::Always, parent, this, rawDataIndex, "cycle length cannot be 0", ""); textureListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 4); textureIndexListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 8); uint32_t textureListOffset = Seg2Filespace(textureListAddress, parent->baseAddress); uint32_t textureIndexListOffset = Seg2Filespace(textureIndexListAddress, parent->baseAddress); uint32_t currentPtr; uint8_t currentIndex; uint8_t maxIndex = 0; // To find the length of the texture list for (currentPtr = textureIndexListOffset; currentPtr < textureIndexListOffset + cycleLength; currentPtr++) { currentIndex = BitConverter::ToUInt8BE(rawData, currentPtr); textureIndexList.push_back(currentIndex); if (currentIndex > maxIndex) maxIndex = currentIndex; } for (currentPtr = textureListOffset; currentPtr <= textureListOffset + 4 * maxIndex; currentPtr += 4) { textureList.push_back(BitConverter::ToUInt32BE(rawData, currentPtr)); } } std::string TextureCyclingParams::GetSourceTypeName() const { return "AnimatedMatTexCycleParams"; // TODO: Better name } std::string TextureCyclingParams::GetDefaultName(const std::string& prefix) const { return StringHelper::Sprintf("%sTexCycleParams_%06X", prefix.c_str(), rawDataIndex); } size_t TextureCyclingParams::GetRawDataSize() const { return 0xC; } void TextureCyclingParams::DeclareReferences([[maybe_unused]] const std::string& prefix) { if (textureListAddress != 0) // NULL { std::string texturesBodyStr; std::string texName; std::string comment; for (const auto& tex : textureList) { bool texFound = Globals::Instance->GetSegmentedPtrName(tex, parent, "", texName, parent->workerID); // texName is a raw segmented pointer. This occurs if the texture is not declared // separately since we cannot read the format. In theory we could scan DLists for the // format on the appropriate segments. if (!texFound) { comment = " // Raw pointer, declare texture in XML to use proper symbol"; auto msgHeader = StringHelper::Sprintf( "TexCycle texture array declared here points to unknown texture at address %s", texName.c_str()); HANDLE_WARNING_RESOURCE( WarningType::HardcodedPointer, parent, this, rawDataIndex, msgHeader, "Please declare the texture in the XML to use the proper symbol."); } texturesBodyStr += StringHelper::Sprintf("\t%s,%s\n", texName.c_str(), comment.c_str()); } texturesBodyStr.pop_back(); parent->AddDeclarationArray( Seg2Filespace(textureListAddress, parent->baseAddress), DeclarationAlignment::Align4, textureList.size() * 4, "TexturePtr", StringHelper::Sprintf("%sTexCycleTexPtrs_%06X", parent->GetName().c_str(), Seg2Filespace(textureListAddress, parent->baseAddress)), textureList.size(), texturesBodyStr); } if (textureIndexListAddress != 0) // NULL { std::string indicesBodyStr = "\t"; for (uint8_t index : textureIndexList) { indicesBodyStr += StringHelper::Sprintf("%d, ", index); } indicesBodyStr.pop_back(); parent->AddDeclarationArray( Seg2Filespace(textureIndexListAddress, parent->baseAddress), DeclarationAlignment::Align4, textureIndexList.size(), "u8", StringHelper::Sprintf("%sTexCycleTexIndices_%06X", parent->GetName().c_str(), Seg2Filespace(textureIndexListAddress, parent->baseAddress)), textureIndexList.size(), indicesBodyStr); } } std::string TextureCyclingParams::GetBodySourceCode() const { std::string textureListName; std::string textureIndexListName; Globals::Instance->GetSegmentedPtrName(textureListAddress, parent, "", textureListName, parent->workerID); Globals::Instance->GetSegmentedPtrName(textureIndexListAddress, parent, "", textureIndexListName, parent->workerID); std::string bodyStr = StringHelper::Sprintf( "\n %d, %s, %s,\n", cycleLength, textureListName.c_str(), textureIndexListName.c_str()); return bodyStr; } /* ZTextureAnimation */ ZTextureAnimation::ZTextureAnimation(ZFile* nParent) : ZResource(nParent) { } /** * Builds the array of params */ void ZTextureAnimation::ParseRawData() { ZResource::ParseRawData(); TextureAnimationEntry currentEntry; auto rawData = parent->GetRawData(); int16_t type; for (uint32_t currentPtr = rawDataIndex;; currentPtr += 8) { type = BitConverter::ToInt16BE(rawData, currentPtr + 2); currentEntry.segment = BitConverter::ToInt8BE(rawData, currentPtr); currentEntry.type = (TextureAnimationParamsType)type; currentEntry.paramsPtr = BitConverter::ToUInt32BE(rawData, currentPtr + 4); entries.push_back(currentEntry); if ((type < 0) || (type > 6)) { HANDLE_ERROR_RESOURCE( WarningType::Always, parent, this, rawDataIndex, StringHelper::Sprintf( "unknown TextureAnimationParams type 0x%02X in TextureAnimation", type), StringHelper::Sprintf( "Entry reads { 0x%02X, 0x%02X, 0x%08X } , but type should be " "between 0x00 and 0x06 inclusive.", currentEntry.segment, type, currentEntry.paramsPtr)); } if (currentEntry.segment <= 0) break; } } #include /** * For each params entry, */ void ZTextureAnimation::DeclareReferences(const std::string& prefix) { std::string varPrefix = name; if (varPrefix == "") varPrefix = prefix; ZResource::DeclareReferences(varPrefix); for (const auto& entry : entries) { if (entry.paramsPtr != 0 && GETSEGNUM(entry.paramsPtr) == parent->segment) { uint32_t paramsOffset = Seg2Filespace(entry.paramsPtr, parent->baseAddress); if (!parent->HasDeclaration(paramsOffset)) { ZTextureAnimationParams* params; int count; switch (entry.type) { case TextureAnimationParamsType::SingleScroll: if (true) { count = 1; // The else now allows SingleScroll to fall through to params = ... without // touching the code in the else block } else { // The contents of this block can only be run by jumping into it with the // case label [[fallthrough]]; case TextureAnimationParamsType::DualScroll: count = 2; } params = new TextureScrollingParams(parent); params->ExtractFromBinary(paramsOffset, count); break; case TextureAnimationParamsType::ColorChange: case TextureAnimationParamsType::ColorChangeLERP: case TextureAnimationParamsType::ColorChangeLagrange: params = new TextureColorChangingParams(parent); params->type = entry.type; params->ExtractFromBinary(paramsOffset); break; case TextureAnimationParamsType::TextureCycle: params = new TextureCyclingParams(parent); params->ExtractFromBinary(paramsOffset); break; case TextureAnimationParamsType::Empty: HANDLE_WARNING_RESOURCE( WarningType::InvalidExtractedData, parent, this, rawDataIndex, "TextureAnimationParams entry has empty type (6), but params pointer is " "not NULL", StringHelper::Sprintf("Params read { 0x%02X, 0x%02X, 0x%08X } .", entry.segment, (int)entry.type, entry.paramsPtr)); return; default: // Because GCC is worried this could happen assert(entry.type < TextureAnimationParamsType::SingleScroll || entry.type > TextureAnimationParamsType::Empty); return; } params->SetName(params->GetDefaultName(varPrefix)); params->DeclareVar(varPrefix, ""); parent->AddResource(params); } } } } std::string ZTextureAnimation::GetSourceTypeName() const { return "AnimatedMaterial"; // TODO: Better name } ZResourceType ZTextureAnimation::GetResourceType() const { return ZResourceType::TextureAnimation; } /** * The size of the main array */ size_t ZTextureAnimation::GetRawDataSize() const { return entries.size() * 8; } std::string ZTextureAnimation::GetDefaultName(const std::string& prefix) const { return StringHelper::Sprintf("%sTexAnim_%06X", prefix.c_str(), rawDataIndex); } Declaration* ZTextureAnimation::DeclareVar(const std::string& prefix, const std::string& bodyStr) { std::string auxName = name; if (name == "") auxName = GetDefaultName(prefix); Declaration* decl = parent->AddDeclarationArray(rawDataIndex, GetDeclarationAlignment(), GetRawDataSize(), GetSourceTypeName(), auxName, entries.size(), bodyStr); decl->staticConf = staticConf; return decl; } std::string ZTextureAnimation::GetBodySourceCode() const { std::string bodyStr; for (const auto& entry : entries) { std::string paramName; Globals::Instance->GetSegmentedPtrName(entry.paramsPtr, parent, "", paramName, parent->workerID); bodyStr += StringHelper::Sprintf("\t{ %d, %d, %s },\n", entry.segment, entry.type, paramName.c_str()); } bodyStr.pop_back(); return bodyStr; }