• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MeltyPlayer / MeltyTool / 21328384721

25 Jan 2026 06:39AM UTC coverage: 42.223% (-0.007%) from 42.23%
21328384721

push

github

MeltyPlayer
Set up a super basic implementation for texture swaps, so it should just work when I set up an importer for it.

6863 of 18404 branches covered (37.29%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 1 file covered. (0.0%)

55 existing lines in 2 files now uncovered.

29404 of 67490 relevant lines covered (43.57%)

64416.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/FinModelUtility/Libraries/Level5/Level5/src/schema/Mtn2.cs
1
using System.Numerics;
2

3
using CommunityToolkit.Diagnostics;
4

5
using fin.data.dictionaries;
6
using fin.io;
7
using fin.math.rotations;
8
using fin.schema;
9
using fin.util.asserts;
10

11
using level5.decompression;
12

13
using schema.binary;
14
using schema.binary.attributes;
15

16

17
namespace level5.schema;
18

19
/// <summary>
20
///   Skeletal animation
21
/// </summary>
22
public sealed class Mtn2 {
UNCOV
23
  public GenericAnimation Anim { get; } = new GenericAnimation();
×
24

25
  public ListDictionary<uint, (short, short)> Somethings { get; } = new();
×
26

27
  public sealed class AnimTrack {
UNCOV
28
    public int Type { get; set; }
×
29
    public int DataType { get; set; }
×
30

31
    [Unknown]
32
    public int Unk { get; set; }
×
33

UNCOV
34
    public int DataCount { get; set; }
×
UNCOV
35
    public int Start { get; set; }
×
36
    public int End { get; set; }
×
37
  }
38

39
  public void Open(IReadOnlyGenericFile mtn2File) {
×
40
    var endianness = Endianness.LittleEndian;
×
41
    using var r = mtn2File.OpenReadAsBinary(endianness);
×
42
    r.Position = 0x08;
×
43
    var decomSize = r.ReadInt32();
×
44
    var nameOffset = r.ReadUInt32();
×
45
    var compDataOffset = r.ReadUInt32();
×
UNCOV
46
    var positionCount = r.ReadInt32();
×
UNCOV
47
    var rotationCount = r.ReadInt32();
×
48
    var scaleCount = r.ReadInt32();
×
49

50
    // This corresponds to PRMs that may be toggled on/off in the animation.
UNCOV
51
    var toggledPrmCount = r.ReadInt32();
×
52
    // This corresponds to bones that will be moved in the animation.
53
    var boneCount = r.ReadInt32();
×
54

55
    r.Position = 0x54;
×
56
    this.Anim.FrameCount = r.ReadInt32();
×
57

UNCOV
58
    r.Position = nameOffset;
×
59
    this.Anim.NameHash = r.ReadUInt32();
×
60
    this.Anim.Name = r.ReadStringNT(StringEncodingType.SHIFT_JIS);
×
61

62
    var data = new Level5Decompressor().Decompress(
×
UNCOV
63
        r.SubreadAt(compDataOffset,
×
64
                    () => r.ReadBytes(
×
65
                        (int) (r.Length - compDataOffset))));
×
66

67
    using (var d =
×
68
           new SchemaBinaryReader(new MemoryStream(data), endianness)) {
×
69
      // Header
UNCOV
70
      var hashTableOffset = d.ReadUInt32();
×
UNCOV
71
      var trackInfoOffset = d.ReadUInt32();
×
72
      var dataOffset = d.ReadUInt32();
×
73

74
      // Bone Hashes
75
      List<uint> hashes = [];
×
76
      d.Position = (hashTableOffset);
×
77
      while (d.Position < trackInfoOffset) {
×
UNCOV
78
        hashes.Add(d.ReadUInt32());
×
UNCOV
79
      }
×
80
      Asserts.Equal(toggledPrmCount + boneCount, hashes.Count);
×
81

82
      // Track Information
83
      List<AnimTrack> tracks = [];
×
UNCOV
84
      for (int i = 0; i < 4; i++) {
×
85
        d.Position = ((uint) (trackInfoOffset + 2 * i));
×
86
        d.Position = (d.ReadUInt16());
×
87

88
        tracks.Add(new AnimTrack() {
×
89
            Type = d.ReadByte(),
×
90
            DataType = d.ReadByte(),
×
91
            Unk = d.ReadByte(),
×
92
            DataCount = d.ReadByte(),
×
93
            Start = d.ReadUInt16(),
×
UNCOV
94
            End = d.ReadUInt16()
×
UNCOV
95
        });
×
UNCOV
96
      }
×
97

98
      // Data
99

100
      foreach (var v in hashes) {
×
101
        var node = new GenericAnimationTransform();
×
102
        node.Hash = v;
×
UNCOV
103
        node.HashType = AnimNodeHashType.CRC32C;
×
104
        this.Anim.TransformNodes.Add(node);
×
105
      }
×
106

107
      var offset = 0;
×
108
      this.ReadFrameData_(d,
×
109
                          offset,
×
110
                          positionCount,
×
111
                          dataOffset,
×
112
                          boneCount,
×
113
                          tracks[0],
×
114
                          hashes);
×
115
      offset += positionCount;
×
116
      this.ReadFrameData_(d,
×
117
                          offset,
×
118
                          rotationCount,
×
119
                          dataOffset,
×
120
                          boneCount,
×
121
                          tracks[1],
×
122
                          hashes);
×
123
      offset += rotationCount;
×
124
      this.ReadFrameData_(d,
×
125
                          offset,
×
126
                          scaleCount,
×
127
                          dataOffset,
×
128
                          boneCount,
×
129
                          tracks[2],
×
130
                          hashes);
×
131
      offset += scaleCount;
×
132
      this.ReadFrameData_(d,
×
133
                          offset,
×
134
                          toggledPrmCount,
×
135
                          dataOffset,
×
136
                          boneCount,
×
137
                          tracks[3],
×
UNCOV
138
                          hashes);
×
UNCOV
139
    }
×
UNCOV
140
  }
×
141

142
  private void ReadFrameData_(IBinaryReader br,
143
                              int offset,
144
                              int count,
145
                              uint dataOffset,
146
                              int boneCount,
147
                              AnimTrack track,
148
                              IReadOnlyList<uint> hashes) {
×
149
    for (int i = offset; i < offset + count; i++) {
×
150
      br.Position = ((uint) (dataOffset + 4 * 4 * i));
×
151
      var flagOffset = br.ReadUInt32();
×
UNCOV
152
      var keyFrameOffset = br.ReadUInt32();
×
153
      var keyDataOffset = br.ReadUInt32();
×
154
      br.AssertUInt32(0);
×
155

156
      br.Position = (flagOffset);
×
UNCOV
157
      var boneIndex = br.ReadInt16();
×
158
      var keyFrameCount = br.ReadByte();
×
UNCOV
159
      var flag = br.ReadByte();
×
160

UNCOV
161
      var nodeIndex = boneIndex + (flag == 0 ? boneCount : 0);
×
162

163
      var node = this.Anim.TransformNodes[nodeIndex];
×
164

165
      br.Position = (keyDataOffset);
×
166
      for (int k = 0; k < keyFrameCount; k++) {
×
167
        var temp = br.Position;
×
UNCOV
168
        br.Position = ((uint) (keyFrameOffset + k * 2));
×
169
        var frame = br.ReadInt16();
×
170
        br.Position = (temp);
×
171

UNCOV
172
        if (br.Eof) {
×
173
          break;
×
174
        }
175

176
        float[] animdata = new float[track.DataCount];
×
UNCOV
177
        ulong boneVisibilityData = 0;
×
178
        for (int j = 0; j < track.DataCount; j++)
×
179
          switch (track.DataType) {
×
180
            case 1:
181
              animdata[j] = br.ReadInt16() / (float) short.MaxValue;
×
182
              break;
×
183
            case 2:
184
              animdata[j] = br.ReadSingle();
×
185
              break;
×
186
            case 4:
187
              switch (flag) {
×
UNCOV
188
                case 0: {
×
189
                  animdata[j] = br.ReadByte();
×
190
                  break;
×
191
                }
UNCOV
192
                case 0x20: {
×
193
                  boneVisibilityData = br.ReadUInt64();
×
194
                  break;
×
195
                }
UNCOV
196
                default: {
×
UNCOV
197
                  throw new NotImplementedException(
×
198
                      $"Flag with value: {flag.ToHexString()}");
×
199
                }
200
              }
201
              break;
×
202
            default:
UNCOV
203
              throw new NotImplementedException(
×
204
                  "Data Type " + track.DataType + " not implemented");
×
205
          }
206

207
        switch (track.Type) {
×
208
          case 1:
209
            node.AddKey(frame,
×
210
                        animdata[0],
×
211
                        AnimationTrackFormat.TranslateX);
×
212
            node.AddKey(frame,
×
213
                        animdata[1],
×
214
                        AnimationTrackFormat.TranslateY);
×
215
            node.AddKey(frame,
×
UNCOV
216
                        animdata[2],
×
UNCOV
217
                        AnimationTrackFormat.TranslateZ);
×
218
            break;
×
219
          case 2:
220
            // TODO: Invert?
221
            var e = QuaternionUtil.ToEulerRadians(
×
222
                new Quaternion(animdata[0],
×
223
                               animdata[1],
×
224
                               animdata[2],
×
225
                               animdata[3]));
×
226
            node.AddKey(frame, e.X, AnimationTrackFormat.RotateX);
×
UNCOV
227
            node.AddKey(frame, e.Y, AnimationTrackFormat.RotateY);
×
228
            node.AddKey(frame, e.Z, AnimationTrackFormat.RotateZ);
×
229
            break;
×
230
          case 3:
231
            node.AddKey(frame, animdata[0], AnimationTrackFormat.ScaleX);
×
232
            node.AddKey(frame, animdata[1], AnimationTrackFormat.ScaleY);
×
233
            node.AddKey(frame, animdata[2], AnimationTrackFormat.ScaleZ);
×
234
            break;
×
235
          case 9: {
×
236
            switch (flag) {
×
237
              case 0x00: {
×
UNCOV
238
                this.Somethings.Add(node.Hash,
×
239
                                    (frame, (short) Math.Round(animdata[0])));
×
240
                break;
×
241
              }
242
              case 0x20: {
×
243
                for (var b = 0; b < hashes.Count; ++b) {
×
244
                  var isActive = (boneVisibilityData >> b) & 1;
×
245
                  this.Somethings.Add(hashes[boneIndex + b],
×
UNCOV
246
                                      (frame, (short) isActive));
×
UNCOV
247
                }
×
248
                break;
×
249
              }
250
            }
251
            break;
×
252
          }
253
          default:
254
            throw new NotImplementedException(
×
255
                "Track Type " + track.Type + " not implemented");
×
256
        }
UNCOV
257
      }
×
UNCOV
258
    }
×
UNCOV
259
  }
×
260
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc