summaryrefslogtreecommitdiff
path: root/src/factories/pm64/ImgFXAnimFactory.cpp
blob: f4daa66ad380056c1eb5e0816cfb7775709516a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include "ImgFXAnimFactory.h"
#include "Companion.h"
#include "spdlog/spdlog.h"

// ImgFX animation blob layout (produced by this factory):
//
// [0x00] u32 n64KeyframesOffset  (original segment-relative, for vertex fixup)
// [0x04] u32 n64GfxOffset        (original segment-relative, unused on port)
// [0x08] u16 vtxCount
// [0x0A] u16 gfxCount
// [0x0C] u16 keyframesCount
// [0x0E] u16 flags
// [0x10] Keyframe data (keyframesCount * vtxCount * 12 bytes, positions byte-swapped)
// [0x10 + keyframeDataSize] GFX data (gfxCount * 8 bytes, N64 Gfx commands word-swapped)

std::optional<std::shared_ptr<IParsedData>> PM64ImgFXAnimFactory::parse(std::vector<uint8_t>& buffer,
                                                                        YAML::Node& node) {
    auto segmentBase = GetSafeNode<uint32_t>(node, "offset");
    auto headerOffset = GetSafeNode<uint32_t>(node, "header_offset");

    uint32_t headerRomAddr = segmentBase + headerOffset;

    if (headerRomAddr + 16 > buffer.size()) {
        SPDLOG_ERROR("PM64:IMGFX_ANIM: Header at 0x{:X} exceeds buffer size 0x{:X}", headerRomAddr, buffer.size());
        return std::nullopt;
    }

    // Read 16-byte N64 header (big-endian)
    uint8_t* hdr = buffer.data() + headerRomAddr;
    uint32_t n64KeyframesOffset = BSWAP32(*(uint32_t*)(hdr + 0x00));
    uint32_t n64GfxOffset = BSWAP32(*(uint32_t*)(hdr + 0x04));
    uint16_t vtxCount = BSWAP16(*(uint16_t*)(hdr + 0x08));
    uint16_t gfxCount = BSWAP16(*(uint16_t*)(hdr + 0x0A));
    uint16_t keyframesCount = BSWAP16(*(uint16_t*)(hdr + 0x0C));
    uint16_t flags = BSWAP16(*(uint16_t*)(hdr + 0x0E));

    uint32_t keyframeDataSize = keyframesCount * vtxCount * 12; // sizeof(ImgFXVtx) = 0x0C
    uint32_t gfxDataSize = gfxCount * 8;                        // sizeof(N64 Gfx) = 8

    uint32_t keyframesRomAddr = segmentBase + n64KeyframesOffset;
    uint32_t gfxRomAddr = segmentBase + n64GfxOffset;

    if (keyframesRomAddr + keyframeDataSize > buffer.size()) {
        SPDLOG_ERROR("PM64:IMGFX_ANIM: Keyframe data at 0x{:X} (size 0x{:X}) exceeds buffer", keyframesRomAddr,
                     keyframeDataSize);
        return std::nullopt;
    }
    if (gfxRomAddr + gfxDataSize > buffer.size()) {
        SPDLOG_ERROR("PM64:IMGFX_ANIM: GFX data at 0x{:X} (size 0x{:X}) exceeds buffer", gfxRomAddr, gfxDataSize);
        return std::nullopt;
    }

    // Build output blob: header (16) + keyframes + gfx
    uint32_t blobSize = 16 + keyframeDataSize + gfxDataSize;
    std::vector<uint8_t> blob(blobSize);

    // Write header (already byte-swapped to native LE)
    *(uint32_t*)(blob.data() + 0x00) = n64KeyframesOffset;
    *(uint32_t*)(blob.data() + 0x04) = n64GfxOffset;
    *(uint16_t*)(blob.data() + 0x08) = vtxCount;
    *(uint16_t*)(blob.data() + 0x0A) = gfxCount;
    *(uint16_t*)(blob.data() + 0x0C) = keyframesCount;
    *(uint16_t*)(blob.data() + 0x0E) = flags;

    // Copy and byte-swap keyframe data
    // ImgFXVtx: s16 ob[3] (bytes 0-5, need swap), u8 tc[2] (bytes 6-7, no swap),
    //           s8 cn[3] (bytes 8-10, no swap), pad (byte 11, no swap)
    uint8_t* kfSrc = buffer.data() + keyframesRomAddr;
    uint8_t* kfDst = blob.data() + 16;
    memcpy(kfDst, kfSrc, keyframeDataSize);
    for (uint32_t i = 0; i + 12 <= keyframeDataSize; i += 12) {
        uint16_t* v = reinterpret_cast<uint16_t*>(kfDst + i);
        v[0] = BSWAP16(v[0]); // ob[0]
        v[1] = BSWAP16(v[1]); // ob[1]
        v[2] = BSWAP16(v[2]); // ob[2]
        // bytes 6-11: u8/s8 fields, no swap needed
    }

    // Copy and byte-swap GFX data (N64 Gfx: two u32 words each)
    uint8_t* gfxSrc = buffer.data() + gfxRomAddr;
    uint8_t* gfxDst = blob.data() + 16 + keyframeDataSize;
    memcpy(gfxDst, gfxSrc, gfxDataSize);
    for (uint32_t i = 0; i + 8 <= gfxDataSize; i += 8) {
        uint32_t* words = reinterpret_cast<uint32_t*>(gfxDst + i);
        words[0] = BSWAP32(words[0]);
        words[1] = BSWAP32(words[1]);
    }

    return std::make_shared<RawBuffer>(blob);
}

ExportResult PM64ImgFXAnimBinaryExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> raw,
                                                 std::string& entryName, YAML::Node& node, std::string* replacement) {
    auto writer = LUS::BinaryWriter();
    auto data = std::static_pointer_cast<RawBuffer>(raw)->mBuffer;

    WriteHeader(writer, Torch::ResourceType::Blob, 0);
    writer.Write(static_cast<uint32_t>(data.size()));
    writer.Write(reinterpret_cast<char*>(data.data()), data.size());
    writer.Finish(write);

    return std::nullopt;
}

ExportResult PM64ImgFXAnimHeaderExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> raw,
                                                 std::string& entryName, YAML::Node& node, std::string* replacement) {
    return std::nullopt;
}