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

MeltyPlayer / MeltyTool / 21234683291

22 Jan 2026 03:15AM UTC coverage: 42.253% (-0.02%) from 42.276%
21234683291

push

github

MeltyPlayer
Treated long visibility data as a bit mask, which is probably right?

6865 of 18396 branches covered (37.32%)

Branch coverage included in aggregate %.

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

1 existing line in 1 file now uncovered.

29415 of 67468 relevant lines covered (43.6%)

64437.66 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

15

16
namespace level5.schema;
17

18
public sealed class Mtn2 {
19
  public GenericAnimation Anim { get; } = new GenericAnimation();
×
20

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

23
  public sealed class AnimTrack {
24
    public int Type { get; set; }
×
25
    public int DataType { get; set; }
×
26

27
    [Unknown]
28
    public int Unk { get; set; }
×
29

30
    public int DataCount { get; set; }
×
31
    public int Start { get; set; }
×
32
    public int End { get; set; }
×
33
  }
34

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

46
    // This corresponds to PRMs that may be toggled on/off in the animation.
47
    var toggledPrmCount = r.ReadInt32();
×
48
    // This corresponds to bones that will be moved in the animation.
49
    var boneCount = r.ReadInt32();
×
50

51
    r.Position = 0x54;
×
52
    this.Anim.FrameCount = r.ReadInt32();
×
53

54
    r.Position = nameOffset;
×
55
    this.Anim.NameHash = r.ReadUInt32();
×
56
    this.Anim.Name = r.ReadStringNT();
×
57

58
    var data = new Level5Decompressor().Decompress(
×
NEW
59
        r.SubreadAt(compDataOffset,
×
NEW
60
                    () => r.ReadBytes(
×
NEW
61
                        (int) (r.Length - compDataOffset))));
×
62

63
    using (var d =
×
64
           new SchemaBinaryReader(new MemoryStream(data), endianness)) {
×
65
      // Header
66
      var hashTableOffset = d.ReadUInt32();
×
67
      var trackInfoOffset = d.ReadUInt32();
×
68
      var dataOffset = d.ReadUInt32();
×
69

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

78
      // Track Information
79
      List<AnimTrack> tracks = [];
×
80
      for (int i = 0; i < 4; i++) {
×
NEW
81
        d.Position = ((uint) (trackInfoOffset + 2 * i));
×
82
        d.Position = (d.ReadUInt16());
×
83

84
        tracks.Add(new AnimTrack() {
×
85
            Type = d.ReadByte(),
×
86
            DataType = d.ReadByte(),
×
87
            Unk = d.ReadByte(),
×
88
            DataCount = d.ReadByte(),
×
89
            Start = d.ReadUInt16(),
×
90
            End = d.ReadUInt16()
×
91
        });
×
92
      }
×
93

94
      // Data
95

96
      foreach (var v in hashes) {
×
97
        var node = new GenericAnimationTransform();
×
98
        node.Hash = v;
×
99
        node.HashType = AnimNodeHashType.CRC32C;
×
100
        this.Anim.TransformNodes.Add(node);
×
101
      }
×
102

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

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

152
      br.Position = (flagOffset);
×
153
      var boneIndex = br.ReadInt16();
×
154
      var keyFrameCount = br.ReadByte();
×
155
      var flag = br.ReadByte();
×
156

157
      var nodeIndex = boneIndex + (flag == 0 ? boneCount : 0);
×
158

159
      var node = this.Anim.TransformNodes[nodeIndex];
×
160

161
      br.Position = (keyDataOffset);
×
162
      for (int k = 0; k < keyFrameCount; k++) {
×
163
        var temp = br.Position;
×
NEW
164
        br.Position = ((uint) (keyFrameOffset + k * 2));
×
165
        var frame = br.ReadInt16();
×
166
        br.Position = (temp);
×
167

168
        if (br.Eof) {
×
169
          break;
×
170
        }
171

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

203
        switch (track.Type) {
×
204
          case 1:
NEW
205
            node.AddKey(frame,
×
NEW
206
                        animdata[0],
×
207
                        AnimationTrackFormat.TranslateX);
×
NEW
208
            node.AddKey(frame,
×
NEW
209
                        animdata[1],
×
210
                        AnimationTrackFormat.TranslateY);
×
NEW
211
            node.AddKey(frame,
×
NEW
212
                        animdata[2],
×
213
                        AnimationTrackFormat.TranslateZ);
×
214
            break;
×
215
          case 2:
216
            // TODO: Invert?
217
            var e = QuaternionUtil.ToEulerRadians(
×
NEW
218
                new Quaternion(animdata[0],
×
NEW
219
                               animdata[1],
×
NEW
220
                               animdata[2],
×
221
                               animdata[3]));
×
222
            node.AddKey(frame, e.X, AnimationTrackFormat.RotateX);
×
223
            node.AddKey(frame, e.Y, AnimationTrackFormat.RotateY);
×
224
            node.AddKey(frame, e.Z, AnimationTrackFormat.RotateZ);
×
225
            break;
×
226
          case 3:
227
            node.AddKey(frame, animdata[0], AnimationTrackFormat.ScaleX);
×
228
            node.AddKey(frame, animdata[1], AnimationTrackFormat.ScaleY);
×
229
            node.AddKey(frame, animdata[2], AnimationTrackFormat.ScaleZ);
×
230
            break;
×
231
          case 9: {
×
NEW
232
            switch (flag) {
×
NEW
233
              case 0x00: {
×
NEW
234
                this.Somethings.Add(node.Hash,
×
NEW
235
                                    (frame, (short) Math.Round(animdata[0])));
×
NEW
236
                break;
×
237
              }
NEW
238
              case 0x20: {
×
NEW
239
                for (var b = 0; b < hashes.Count; ++b) {
×
NEW
240
                  var isActive = (boneVisibilityData >> b) & 1;
×
NEW
241
                  this.Somethings.Add(hashes[boneIndex + b],
×
NEW
242
                                      (frame, (short) isActive));
×
NEW
243
                }
×
NEW
244
                break;
×
245
              }
246
            }
UNCOV
247
            break;
×
248
          }
249
          default:
250
            throw new NotImplementedException(
×
251
                "Track Type " + track.Type + " not implemented");
×
252
        }
253
      }
×
254
    }
×
255
  }
×
256
}
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