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

MeltyPlayer / MeltyTool / 19622977055

24 Nov 2025 04:05AM UTC coverage: 41.989% (+2.1%) from 39.89%
19622977055

push

github

MeltyPlayer
Switched float precision to fix broken tests.

6747 of 18131 branches covered (37.21%)

Branch coverage included in aggregate %.

28639 of 66144 relevant lines covered (43.3%)

65384.8 hits per line

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

0.0
/FinModelUtility/Utility%20of%20Time%20CSharp/model/AnimationReader2.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4

5
using CommunityToolkit.HighPerformance;
6

7
using f3dzex2.io;
8

9
using fin.schema;
10

11
using UoT.memory;
12

13
#pragma warning disable CS8603
14

15

16
namespace UoT.model {
17
  public sealed class AnimationReader2 {
18
    /// <summary>
19
    ///   Parses a set of animations according to the spec at:
20
    ///   https://wiki.cloudmodding.com/oot/Animation_Format#Normal_Animations
21
    /// </summary>
22
    // TODO: Some jank still slips through, is there a proper list of these
23
    // addresses somewhere in the file?
24
    public IList<IAnimation>? GetCommonAnimations(
25
        IN64Memory n64Memory,
26
        (IEnumerable<IZFile>, int[]?) animationFilesAndExpectedOffsets,
27
        int limbCount) {
×
28
      uint trackCount = (uint) (limbCount * 3);
×
29
      var animations = new List<IAnimation>();
×
30

31
      var existingSegment6 = n64Memory.GetSegment(6);
×
32

33
      var (animationFiles, expectedOffsets) = animationFilesAndExpectedOffsets;
×
34
      foreach (var animationFile in animationFiles) {
×
35
        n64Memory.SetSegment(6, animationFile.Segment);
×
36
        using var entryEr = n64Memory.OpenSegment(animationFile.Segment);
×
37

38
        var offsets = expectedOffsets ??
×
39
                      Enumerable.Range(0, (int) (entryEr.Length - 16));
×
40

41
        // Guesstimating the index by looking for a spot where the header's
42
        // angle address and track address have the same bank as the param at
43
        // the top.
44
        foreach (var i in offsets) {
×
45
          entryEr.Position = i;
×
46

47
          var frameCount = entryEr.ReadUInt16();
×
48
          var pad0 = entryEr.ReadUInt16();
×
49
          var rotationValuesAddress = entryEr.ReadUInt32();
×
50
          var rotationIndicesAddress = entryEr.ReadUInt32();
×
51
          var limit = entryEr.ReadUInt16();
×
52
          var pad1 = entryEr.ReadUInt16();
×
53

54
          IoUtils.SplitSegmentedAddress(rotationValuesAddress, out var rotationValueSegment, out var rotationValueOffset);
×
55
          IoUtils.SplitSegmentedAddress(rotationIndicesAddress, out var rotationIndicesSegment, out var rotationIndicesOffset);
×
56

57
          if (pad0 != 0 || pad1 != 0) {
×
58
            continue;
×
59
          }
60
          
61
          // Verifies the frame count is positive.
62
          if (frameCount == 0) {
×
63
            continue;
×
64
          }
65

66
          if (!n64Memory.IsValidSegmentedAddress(rotationValuesAddress)) {
×
67
            continue;
×
68
          }
69

70
          // Verifies the rotation indices address has a valid bank.
71
          if (!n64Memory.IsValidSegmentedAddress(rotationIndicesAddress)) {
×
72
            continue;
×
73
          }
74

75
          // Obtains the specified banks.
76
          using var rotationValuesEr =
×
77
              n64Memory.OpenAtSegmentedAddress(rotationValuesAddress);
×
78
          using var rotationIndicesEr =
×
79
              n64Memory.OpenAtSegmentedAddress(rotationIndicesAddress);
×
80
          var originalRotationIndicesOffset = rotationIndicesEr.Position;
×
81

82
          // Angle count should be greater than 0.
83
          var angleCount =
×
84
              (rotationIndicesEr.Position - rotationValuesEr.Position) / 2L;
×
85
          if (angleCount <= 0) {
×
86
            continue;
×
87
          }
88

89
          // All values of "tTrack" should be within the bounds of .Angles.
90
          var validTTracks = true;
×
91
          for (var i1 = 0; i1 < 3 + (trackCount + 1); i1++) {
×
92
            var tTrack = rotationIndicesEr.ReadUInt16();
×
93
            if (tTrack < limit) {
×
94
              if (tTrack >= angleCount) {
×
95
                validTTracks = false;
×
96
                goto badTTracks;
×
97
              }
98
            } else if ((uint) (tTrack + frameCount) > angleCount) {
×
99
              validTTracks = false;
×
100
              goto badTTracks;
×
101
            }
102
          }
×
103

104
          badTTracks:
×
105
          if (!validTTracks) {
×
106
            continue;
×
107
          }
108

109
          var animation = new NormalAnimation {
×
110
              Offset = (uint) i,
×
111
              FrameCount = frameCount,
×
112
              TrackOffset = (uint) originalRotationIndicesOffset,
×
113
              AngleCount = (uint) angleCount
×
114
          };
×
115

116
          animation.Angles = rotationValuesEr.ReadUInt16s(animation.AngleCount);
×
117

118
          // Translation is at the start.
119
          rotationIndicesEr.Position = originalRotationIndicesOffset;
×
120
          var xList =
×
121
              ReadFrames_(
×
122
                  rotationIndicesEr.ReadUInt16(),
×
123
                  limit,
×
124
                  animation);
×
125
          var yList =
×
126
              ReadFrames_(
×
127
                  rotationIndicesEr.ReadUInt16(),
×
128
                  limit,
×
129
                  animation);
×
130
          var zList =
×
131
              ReadFrames_(
×
132
                  rotationIndicesEr.ReadUInt16(),
×
133
                  limit,
×
134
                  animation);
×
135

136
          animation.Positions = new Vec3s[animation.FrameCount];
×
137
          for (var pi = 0; pi < animation.FrameCount; ++pi) {
×
138
            animation.Positions[pi] = new Vec3s {
×
139
                X = this.ConvertUShortToShort_(xList[Math.Min(pi, xList.Length - 1)]),
×
140
                Y = this.ConvertUShortToShort_(yList[Math.Min(pi, yList.Length - 1)]),
×
141
                Z = this.ConvertUShortToShort_(zList[Math.Min(pi, zList.Length - 1)]),
×
142
            };
×
143
          }
×
144

145
          animation.Tracks = new NormalAnimationTrack[trackCount];
×
146

147
          for (var i1 = 0; i1 < trackCount; ++i1) {
×
148
            var track = animation.Tracks[i1] = new NormalAnimationTrack();
×
149
            track.Frames =
×
150
                ReadFrames_(rotationIndicesEr.ReadUInt16(), limit, animation);
×
151
          }
×
152

153
          animations.Add(animation);
×
154
        }
×
155
      }
×
156

157
      n64Memory.SetSegment(6, existingSegment6);
×
158

159
      return animations.Count > 0 ? animations : null;
×
160
    }
×
161

162
    private short ConvertUShortToShort_(ushort value) {
×
163
      Span<ushort> ptr = stackalloc ushort[1];
×
164
      ptr[0] = value;
×
165
      return ptr.Cast<ushort, short>()[0];
×
166
    }
×
167

168
    private static ushort[] ReadFrames_(
169
        ushort tTrack,
170
        ushort limit,
171
        NormalAnimation animation) {
×
172
      ushort[] frames;
173

174
      // Constant
175
      if (tTrack < limit) {
×
176
        frames = new ushort[1];
×
177
        frames[0] = animation.Angles[tTrack];
×
178
      } else {
×
179
        // Keyframes
180
        frames = new ushort[animation.FrameCount];
×
181
        for (var i2 = 0; i2 < animation.FrameCount; ++i2) {
×
182
          try {
×
183
            frames[i2] = animation.Angles[tTrack + i2];
×
184
          } catch {
×
185
            return null;
×
186
          }
187
        }
×
188
      }
×
189

190
      return frames;
×
191
    }
×
192

193
    /// <summary>
194
    ///   Parses a set of animations according to the spec at:
195
    ///   https://wiki.cloudmodding.com/oot/Animation_Format#C_code
196
    /// </summary>
197
    [Unknown]
198
    public IList<IAnimation>? GetLinkAnimations(
199
        IN64Memory n64Memory,
200
        IZFile headerFile,
201
        int limbCount) {
×
202
      var animations = new List<IAnimation>();
×
203

204
      using var headerEr = n64Memory.OpenSegment(headerFile.Segment);
×
205

206
      var trackCount = (uint) (limbCount * 3);
×
207
      var frameSize = 2 * (3 + trackCount) + 2;
×
208
      for (uint i = 0x2310; i <= 0x34F8; i += 4) {
×
209
        headerEr.Position = i;
×
210

211
        var frameCount = headerEr.ReadUInt16();
×
212
        var pad0 = headerEr.ReadUInt16();
×
213
        var animationAddress = headerEr.ReadUInt32();
×
214

215
        if (pad0 != 0) {
×
216
          continue;
×
217
        }
218

219
        // Verifies the frame count is positive.
220
        if (frameCount == 0) {
×
221
          continue;
×
222
        }
223

224
        if (!n64Memory.IsValidSegmentedAddress(animationAddress)) {
×
225
          continue;
×
226
        }
227

228
        // Everything looks good with this animation location!
229

230
        // Starts parsing animation from this spot.
231
        var tracks = new LinkAnimetionTrack[(int) (trackCount - 1L + 1)];
×
232
        var positions = new Vec3s[frameCount];
×
233
        var facialStates = new FacialState[frameCount];
×
234

235
        for (int t = 0, loopTo = (int) (trackCount - 1L);
×
236
             t <= loopTo;
×
237
             t++) {
×
238
          tracks[t] = new LinkAnimetionTrack(1, new ushort[frameCount]);
×
239
        }
×
240

241
        using var animationEr = n64Memory.OpenAtSegmentedAddress(animationAddress);
×
242
        var originalAnimationOffset = animationEr.Position;
×
243
        for (int f = 0; f < frameCount; f++) {
×
244
          var frameOffset = animationEr.Position = (uint) (originalAnimationOffset + f * frameSize);
×
245

246
          positions[f] = new Vec3s {
×
247
              X = animationEr.ReadInt16(),
×
248
              Y = animationEr.ReadInt16(),
×
249
              Z = animationEr.ReadInt16(),
×
250
          };
×
251
          for (int t = 0, loopTo2 = (int) (trackCount - 1L);
×
252
               t <= loopTo2;
×
253
               t++) {
×
254
            animationEr.Position = (uint) (frameOffset + 2 * (3 + t));
×
255
            tracks[t].Frames[f] = animationEr.ReadUInt16();
×
256
          }
×
257

258
          animationEr.Position =
×
259
              (int) (frameOffset + 2 * (3 + trackCount));
×
260
          var unk = animationEr.ReadByte();
×
261
          var facialState = animationEr.ReadByte();
×
262
          var mouthState = IoUtil.ShiftR(facialState, 4, 4);
×
263
          var eyeState = IoUtil.ShiftR(facialState, 0, 4);
×
264

265
          facialStates[f] = new FacialState((EyeState) eyeState,
×
266
                                            (MouthState) mouthState);
×
267
        }
×
268

269
        var animation =
×
270
            new LinkAnimetion(frameCount, tracks, positions, facialStates) {
×
271
                Offset = i,
×
272
            };
×
273
        animations.Add(animation);
×
274
      }
×
275

276
      return animations.Count > 0 ? animations : null;
×
277
    }
×
278
  }
279
}
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