public void Convert( uint Width, uint Height, uint LineSize, IList <byte> SourceImg, int SourceTexPos, ref byte[] DestImg, Color4UByte[] _) { var DestTexPos = 0; DestImg = new byte[(int)(Width * Height * 8L + 1)]; for (int i = 0, loopTo = (int)(Height - 1L); i <= loopTo; i++) { for (int j = 0, loopTo1 = (int)(Width / 2L - 1L); j <= loopTo1; j++) { BitMath.Split(SourceImg[SourceTexPos], out var upper4, out var lower4); byte IAAlpha; if (Conversions.ToBoolean(upper4 & 1)) { IAAlpha = 255; } else { IAAlpha = 0; } var upperIntensity = (byte)Math.Round(IoUtil.ShiftR(upper4, 1, 3) * FACTOR); DestImg[DestTexPos] = upperIntensity; DestImg[DestTexPos + 1] = upperIntensity; DestImg[DestTexPos + 2] = upperIntensity; DestImg[DestTexPos + 3] = IAAlpha; var lowerIntensity = (byte)Math.Round(IoUtil.ShiftR(lower4, 1, 3) * FACTOR); if (Conversions.ToBoolean(lower4 & 1)) { IAAlpha = 255; } else { IAAlpha = 0; } DestImg[DestTexPos + 4] = lowerIntensity; DestImg[DestTexPos + 5] = lowerIntensity; DestImg[DestTexPos + 6] = lowerIntensity; DestImg[DestTexPos + 7] = IAAlpha; ++SourceTexPos; DestTexPos += 8; } SourceTexPos = (int)(SourceTexPos + (LineSize * 8L - Width / 2L)); } }
private void DWordInterleaveWrap_( byte[] src, uint srcIdx, uint srcMask, uint numQWords) { uint p0; int idx0, idx1; while (numQWords-- > 0) { idx0 = (int)(srcIdx++ & srcMask); idx1 = (int)(srcIdx++ & srcMask); // TODO: Original logic was meant for u32, so we need to think in terms // of ints. var offset0 = 4 * idx0; var offset1 = 4 * idx1; p0 = IoUtil.ReadUInt32(src, (uint)offset0); var p1 = IoUtil.ReadUInt32(src, (uint)offset1); IoUtil.WriteInt32(src, p1, ref offset0); IoUtil.WriteInt32(src, p0, ref offset1); } }
public int GetIndexByAddress(uint address) { IoUtil.SplitAddress(address, out var bank, out var offset); for (var i = 0; i < this.impl_.Count; ++i) { var zSegment = this.impl_[i].StartPos; if (zSegment.Bank == bank && zSegment.Offset == offset) { return(i); } } return(-1); }
public void Update(uint low, uint high) { this.Low = low & 0x00ffffff; for (var i = 0; i < 4; ++i) { this.CMDParams[i] = (byte)IoUtil.ShiftR(low, (3 - i) * 8, 8); } this.High = high; for (var i = 0; i < 4; ++i) { this.CMDParams[4 + i] = (byte)IoUtil.ShiftR(high, (3 - i) * 8, 8); } this.Opcode = this.CMDParams[0]; }
/// <summary> /// Shamelessly copied from GLideN64's source. /// </summary> private void LoadTile32b_( ref TileDescriptor tileDescriptor, TimgArgs timgArgs) { var uls = tileDescriptor.ULS; var ult = tileDescriptor.ULT; var lrs = tileDescriptor.LRS; var lrt = tileDescriptor.LRT; var width = lrs - uls + 1; var height = lrt - ult + 1; var line = tileDescriptor.LineSize << 2; var tbase = tileDescriptor.TmemOffset << 2; IoUtil.SplitAddress(timgArgs.Address, out var bank, out var offset); var targetBank = Asserts.Assert(RamBanks.GetBankByIndex(bank)); var timgWidth = timgArgs.Width; for (var j = 0; j < height; ++j) { var tline = tbase + line * j; var s = ((j + ult) * timgWidth) + uls; var xorval = (j & 1) != 0 ? 3 : 1; for (var i = 0; i < width; ++i) { var addr = offset + s + i; var c = IoUtil.ReadUInt32(targetBank, (uint)(4 * addr)); var ptr = ((tline + i) ^ xorval) & 0x3ff; var offset1 = 2 * ptr; IoUtil.WriteInt16(targetBank, ref offset1, (ushort)(c >> 16)); var offset2 = 2 * (ptr | 0x400); IoUtil.WriteInt16(targetBank, ref offset2, (ushort)(c & 0xffff)); } } }
public void Convert( uint Width, uint Height, uint LineSize, IList <byte> SourceImg, int SourceTexPos, ref byte[] DestImg, Color4UByte[] _) { var DestTexPos = 0; ushort RGBA5551 = 0; DestImg = new byte[(int)(Width * Height * 8L + 1)]; for (var i = 0; i < Height; ++i) { for (int j = 0; j < Width; ++j) { RGBA5551 = IoUtil.ReadUInt16(SourceImg, (uint)SourceTexPos); DestImg[DestTexPos] = (byte)Math.Round(IoUtil.ShiftR(RGBA5551, 11, 5) * FACTOR); DestImg[DestTexPos + 1] = (byte)Math.Round(IoUtil.ShiftR(RGBA5551, 6, 5) * FACTOR); DestImg[DestTexPos + 2] = (byte)Math.Round(IoUtil.ShiftR(RGBA5551, 1, 5) * FACTOR); if (Conversions.ToBoolean(RGBA5551 & 1)) { DestImg[DestTexPos + 3] = 255; } else { DestImg[DestTexPos + 3] = 0; } SourceTexPos += 2; DestTexPos += 4; } SourceTexPos = (int)(SourceTexPos + (LineSize * 4L - Width)); } }
public static void WriteInt32(IList <byte> buffer, uint data, ref int offset) => IoUtil.WriteInt(buffer, data, ref offset, 4);
public static void WriteInt16(IList <byte> buffer, ref int offset, ushort data) => IoUtil.WriteInt(buffer, data, ref offset, 2);
public static uint ReadUInt32(IList <byte> buffer, uint offset) => IoUtil.ReadUInt(buffer, offset, 4);
public static ushort ReadUInt16(IList <byte> buffer, uint offset) => (ushort)IoUtil.ReadUInt(buffer, offset, 2);
// TODO: Remove combo box input. public static int ReadInDL( DlManager dlManager, uint address, ComboBox dListSelection) { IoUtil.SplitAddress(address, out var bank, out var offset); var data = Asserts.Assert(RamBanks.GetBankByIndex(bank)); try { if (offset < data.Count) { // TODO: This jumps into the lowest level DL, but the 0xDE command (DL) // actually allows returning back up and calling more DLs. So this seems // like it will sometimes overlook any DLs that follow. // This should just be deleted and replaced w/ emulating in // F3DEX2_Parser. if (data[(int)offset] == 0xDE) { while (data[(int)offset] == 0xDE) { offset = IoUtil.ReadUInt24(data, (uint)(offset + 5L)); } } int index = dlManager.Count; var displayList = new N64DisplayList(); dlManager.Add(displayList); uint EPLoc = offset; dListSelection.Items.Add((index + 1).ToString() + ". " + Conversion.Hex(offset)); displayList.StartPos = new ZSegment(); displayList.StartPos.Offset = offset; displayList.StartPos.Bank = data.Segment; displayList.Skip = false; PickerUtil.NextRgb(out var r, out var g, out var b); displayList.PickCol = new Color3UByte { r = r, g = g, b = b }; do { var commands = displayList.Commands; Array.Resize(ref commands, displayList.CommandCount + 1); displayList.Commands = commands; displayList.Commands[displayList.CommandCount] = new DLCommand(data, EPLoc); if (data[(int)EPLoc] == (int)F3DZEX.ENDDL | EPLoc >= data.Count) { EPLoc = (uint)(EPLoc + 8L); break; } EPLoc = (uint)(EPLoc + 8L); displayList.CommandCount += 1; } while (true); return((int)EPLoc); } } catch (Exception ex) { Interaction.MsgBox("Error reading in display list: " + ex.Message, MsgBoxStyle.Critical, "Exception"); return(default);
// TODO: The other method passes in "texels", not "lrs"??? /// <summary> /// Shamelessly copied from GLideN64's source. /// </summary> public void LoadBlock( ref TileDescriptor tileDescriptor, uint uls, uint ult, uint lrs, uint dxt, TimgArgs timgArgs) { tileDescriptor.ULS = (int)uls >> 2; tileDescriptor.ULT = (int)ult >> 2; // TODO: This feels like a bug? tileDescriptor.LRS = (int)lrs >> 2; tileDescriptor.LRT = (int)dxt >> 2; var tmem = tileDescriptor.TmemOffset; var colorFormat = tileDescriptor.ColorFormat; var bitSize = tileDescriptor.BitSize; /*if (gSP.DMAOffsets.tex_offset != 0) { * if (gSP.DMAOffsets.tex_shift % (((lrs >> 2) + 1) << 3)) { * gDP.textureImage.address -= gSP.DMAOffsets.tex_shift; * gSP.DMAOffsets.tex_offset = 0; * gSP.DMAOffsets.tex_shift = 0; * gSP.DMAOffsets.tex_count = 0; * } else ++gSP.DMAOffsets.tex_count; * }*/ var timgAddress = timgArgs.Address; var timgBitSize = timgArgs.BitSize; var timgWidth = timgArgs.Width; var timgBpl = timgWidth << (int)timgBitSize >> 1; IoUtil.SplitAddress(timgAddress, out var bank, out var offset); tileDescriptor.Address = (int)timgAddress; tileDescriptor.ImageBank = (int)bank; tileDescriptor.Offset = (int)offset; var texture = this.cache_[tileDescriptor]; if (texture != null) { return; } /*gDPLoadTileInfo & info = gDP.loadInfo[gDP.loadTile->tmem]; * info.texAddress = gDP.loadTile->imageAddress; * info.uls = static_cast<u16>(gDP.loadTile->uls); * info.ult = static_cast<u16>(gDP.loadTile->ult); * info.lrs = static_cast<u16>(gDP.loadTile->lrs); * info.lrt = static_cast<u16>(gDP.loadTile->lrt); * info.width = static_cast<u16>(gDP.loadTile->lrs); * info.dxt = dxt; * info.size = static_cast<u8>(gDP.textureImage.size); * info.loadType = LOADTYPE_BLOCK;*/ // TODO: This doesn't look right? uint jankWidth = (lrs - uls + 1) & 0x0FFF; uint bytes = jankWidth << (int)bitSize >> 1; if ((bytes & 7) != 0) { bytes = (bytes & (~7U)) + 8; } //info.bytes = bytes; uint address = (uint)(timgAddress + ult * timgBpl + (uls << (int)timgBitSize >> 1)); IoUtil.SplitAddress(address, out var specBank, out var specOffset); /*if (bytes == 0 || (address + bytes) > RDRAMSize) { * DebugMsg(DEBUG_NORMAL | DEBUG_ERROR, "// Attempting to load texture block out of range\n"); * DebugMsg(DEBUG_NORMAL, "gDPLoadBlock( %i, %i, %i, %i, %i );\n", tile, uls, ult, lrs, dxt); * return; * }*/ /* * gDP.loadTile->frameBufferAddress = 0; * CheckForFrameBufferTexture(address, info.width, bytes); // Load data to TMEM even if FB texture is found. See comment to texturedRectDepthBufferCopy */ /*var texLowerBound = tileDescriptor.TmemOffset; * var texUpperBound = texLowerBound + (bytes >> 3); * for (var i = 0; i < tile; ++i) { * if (gDP.tiles[i].tmem >= texLowerBound && gDP.tiles[i].tmem < texUpperBound) { * gDPLoadTileInfo & info = gDP.loadInfo[gDP.tiles[i].tmem]; * info.loadType = LOADTYPE_BLOCK; * } * }*/ var targetBuffer = RamBanks.GetBankByIndex(specBank); if (targetBuffer == null) { return; } uint tmemAddr = tmem; if (bitSize == BitSize.S_32B) { //gDPLoadBlock32(gDP.loadTile->uls, gDP.loadTile->lrs, dxt); } else if (colorFormat == ColorFormat.YUV) { //memcpy(TMEM, &RDRAM[address], bytes); // HACK! } else { for (var i = 0; i < bytes; ++i) { this.impl_[i] = targetBuffer[(int)(specOffset + i)]; } // TODO: Figure out what the heck this stuff below does. /*this.UnswapCopyWrap_(targetBuffer, * specOffset, * this.impl_, * tmemAddr << 3, * 0xFFF, * bytes);*/ /*if (dxt != 0) { * uint dxtCounter = 0; * uint qwords = (bytes >> 3); * uint line = 0; * while (true) { * do { ++tmemAddr; * --qwords; * if (qwords == 0) * goto end_dxt_test; * dxtCounter += dxt; * } while ((dxtCounter & 0x800) == 0); * do { ++line; * --qwords; * if (qwords == 0) * goto end_dxt_test; * dxtCounter += dxt; * } while ((dxtCounter & 0x800) != 0); * this.DWordInterleaveWrap_(this.impl_, tmemAddr << 1, 0x3FF, line); * tmemAddr += line; * line = 0; * } * end_dxt_test: * * this.DWordInterleaveWrap_(this.impl_, tmemAddr << 1, 0x3FF, line); * }*/ } }
/// <summary> /// Shamelessly copied from GLideN64's source. /// </summary> public void LoadTile( ref TileDescriptor tileDescriptor, ushort uls, ushort ult, ushort lrs, ushort lrt, TimgArgs timgArgs) { // TODO: Implement this method. // TODO: To verify behavior, load contents into a new texture and save to // file. tileDescriptor.ULS = uls; tileDescriptor.ULT = ult; tileDescriptor.LRS = lrs; tileDescriptor.LRT = lrt; var fUls = IoUtil.Fixed2Float(uls, 2); var fUlt = IoUtil.Fixed2Float(ult, 2); var fLrs = IoUtil.Fixed2Float(lrs, 2); var fLrt = IoUtil.Fixed2Float(lrt, 2); var timgImageAddress = timgArgs.Address; var timgBitSize = timgArgs.BitSize; var timgWidth = timgArgs.Width; var timgBpl = timgWidth << (int)timgBitSize >> 1; var tmem = tileDescriptor.TmemOffset; var line = tileDescriptor.LineSize; var maskS = tileDescriptor.MaskS; var maskT = tileDescriptor.MaskT; byte imageBank; uint offset; IoUtil.SplitAddress(timgImageAddress, out imageBank, out offset); tileDescriptor.ImageBank = imageBank; tileDescriptor.Offset = (int)offset; var texture = this.cache_[tileDescriptor]; if (texture != null) { return; } if (lrs < uls || lrt < ult) { return; } var width = (lrs - uls + 1) & 0x03FF; var height = (lrt - ult + 1) & 0x03FF; var bpl = line << 3; var alignedWidth = width; var wmask = 0; switch (timgBitSize) { case BitSize.S_8B: wmask = 7; break; case BitSize.S_16B: wmask = 3; break; case BitSize.S_32B: wmask = 1; break; default: throw new NotSupportedException(); } if ((width & wmask) != 0) { alignedWidth = (width & (~wmask)) + wmask + 1; } var bpr = alignedWidth << (int)timgBitSize >> 1; // Start doing the loading. var infoTexAddress = timgImageAddress; var infoWidth = (ushort)(maskS != 0 ? Math.Min(width, 1U << maskS) : width); var infoHeight = (ushort)(maskT != 0 ? Math.Min(height, 1U << maskT) : height); var infoTexWidth = timgWidth; var infoSize = timgBitSize; var infoBytes = bpl * height; if (timgBitSize == BitSize.S_32B) { // 32 bit texture loaded into lower and upper half of TMEM, thus actual bytes doubled. infoBytes *= 2; } if (line == 0) { return; } if (maskS == 0) { tileDescriptor.LoadWidth = Math.Max(tileDescriptor.LoadWidth, infoWidth); } if (maskT == 0) { if (Gdp.CycleMode != (int)RdpCycleMode.G_CYC_2CYCLE && tmem % line == 0) { var theight = (ushort)(infoHeight + tmem / line); tileDescriptor.LoadHeight = Math.Max(tileDescriptor.LoadHeight, theight); } else { tileDescriptor.LoadHeight = Math.Max(tileDescriptor.LoadHeight, infoHeight); } } var address = timgImageAddress + ult * timgBpl + (uls << (int)timgBitSize >> 1); /* * u32 bpl2 = bpl; * if (gDP.loadTile->lrs > timgWidth) * bpl2 = (gDP.textureImage.width - gDP.loadTile->uls); * var height2 = height; * * if (gDP.loadTile->lrt > gDP.scissor.lry) * height2 = static_cast<u32>(gDP.scissor.lry) - gDP.loadTile->ult; * * if (CheckForFrameBufferTexture(address, info.width, bpl2 * height2)) * return;*/ if (timgBitSize == BitSize.S_32B) { this.LoadTile32b_(ref tileDescriptor, timgArgs); } else { ; /*u32 tmemAddr = gDP.loadTile->tmem; * const u32 line = gDP.loadTile->line; * const u32 qwpr = bpr >> 3; * for (u32 y = 0; y < height; ++y) { * if (address + bpl > RDRAMSize) * UnswapCopyWrap(RDRAM, * address, * reinterpret_cast<u8*>(TMEM), * tmemAddr << 3, * 0xFFF, * RDRAMSize - address); * else * UnswapCopyWrap(RDRAM, * address, * reinterpret_cast<u8*>(TMEM), * tmemAddr << 3, * 0xFFF, * bpr); * if (y & 1) * DWordInterleaveWrap(reinterpret_cast<u32*>(TMEM), * tmemAddr << 1, * 0x3FF, * qwpr); * * address += gDP.textureImage.bpl; * if (address >= RDRAMSize) * break; * tmemAddr += line; * }*/ } }
/// <summary> /// Parses a set of animations according to the spec at: /// https://wiki.cloudmodding.com/oot/Animation_Format#C_code /// </summary> public IList <IAnimation>?GetLinkAnimations( IBank HeaderData, int LimbCount, IBank animationData, ListBox animationList) { animationList.Items.Clear(); var animations = new List <IAnimation>(); var trackCount = (uint)(LimbCount * 3); var frameSize = 2 * (3 + trackCount) + 2; for (uint i = 0x2310; i <= 0x34F8; i += 4) { // Verifies the frame count is positive. var frameCount = IoUtil.ReadUInt16(HeaderData, i); if (frameCount == 0) { continue; } var animationAddress = IoUtil.ReadUInt32(HeaderData, i + 4); IoUtil.SplitAddress(animationAddress, out var animationBank, out var animationOffset); // Should use link_animetion bank. var validAnimationBank = animationBank == 7; if (!validAnimationBank) { continue; } // Should have zeroes in the expected bytes of the header. var hasZeroes = IoUtil.ReadUInt16(HeaderData, i + 2) == 0; if (!hasZeroes) { continue; } // Should be within the bounds of the bank. var validOffset = animationOffset + frameSize * frameCount < animationData.Count; if (!validOffset) { continue; } // Everything looks good with this animation location! // Starts parsing animation from this spot. var tracks = new LinkAnimetionTrack[(int)(trackCount - 1L + 1)]; var positions = new Vec3s[frameCount]; var facialStates = new FacialState[frameCount]; for (int t = 0, loopTo = (int)(trackCount - 1L); t <= loopTo; t++) { tracks[t] = new LinkAnimetionTrack(1, new ushort[frameCount]); } for (int f = 0, loopTo1 = frameCount - 1; f <= loopTo1; f++) { var frameOffset = (uint)(animationOffset + f * frameSize); // TODO: This should be ReadInt16() instead. positions[f] = new Vec3s { X = (short)IoUtil.ReadUInt16(animationData, frameOffset), Y = (short)IoUtil.ReadUInt16(animationData, frameOffset + 2), Z = (short)IoUtil.ReadUInt16(animationData, frameOffset + 4), }; for (int t = 0, loopTo2 = (int)(trackCount - 1L); t <= loopTo2; t++) { var trackOffset = (uint)(frameOffset + 2 * (3 + t)); tracks[t].Frames[f] = IoUtil.ReadUInt16(animationData, trackOffset); } var facialStateOffset = (int)(frameOffset + 2 * (3 + trackCount)); var facialState = animationData[facialStateOffset + 1]; var mouthState = IoUtil.ShiftR(facialState, 4, 4); var eyeState = IoUtil.ShiftR(facialState, 0, 4); facialStates[f] = new FacialState((EyeState)eyeState, (MouthState)mouthState); } var animation = new LinkAnimetion(frameCount, tracks, positions, facialStates); animations.Add(animation); animationList.Items.Add("0x" + Conversion.Hex(i)); } return(animations.Count > 0 ? animations : null); }
/// <summary> /// Parses a set of animations according to the spec at: /// https://wiki.cloudmodding.com/oot/Animation_Format#Normal_Animations /// </summary> // TODO: Some jank still slips through, is there a proper list of these // addresses somewhere in the file? public IList <IAnimation>?GetCommonAnimations( IBank bank, int limbCount, ListBox animationList) { animationList.Items.Clear(); uint trackCount = (uint)(limbCount * 3); var animations = new List <IAnimation>(); // Guesstimating the index by looking for an spot where the header's angle // address and track address have the same bank as the param at the top. for (var i = 4; i < bank.Count - 12; i += 4) { var attemptOffset = (uint)(i - 4); // Verifies the frame count is positive. var frameCount = IoUtil.ReadUInt16(bank, attemptOffset); if (frameCount == 0) { continue; } var rotationValuesAddress = IoUtil.ReadUInt32( bank, attemptOffset + 4); IoUtil.SplitAddress(rotationValuesAddress, out var rotationValuesBank, out var rotationValuesOffset); // Verifies the rotation values address has a valid bank. if (!RamBanks.IsValidBank(rotationValuesBank)) { continue; } // Verifies the rotation indices address has a valid bank. var rotationIndicesAddress = IoUtil.ReadUInt32( bank, attemptOffset + 8); IoUtil.SplitAddress(rotationIndicesAddress, out var rotationIndicesBank, out var rotationIndicesOffset); if (!RamBanks.IsValidBank(rotationIndicesBank)) { continue; } // Obtains the specified banks. var rotationValuesBuffer = Asserts.Assert(RamBanks.GetBankByIndex(rotationValuesBank)); var rotationIndicesBuffer = Asserts.Assert(RamBanks.GetBankByIndex(rotationIndicesBank)); // Offsets should be within bounds of the bank. var validRotationValuesOffset = rotationValuesOffset < rotationValuesBuffer.Count; var validRotationIndicesOffset = rotationIndicesOffset < rotationIndicesBuffer.Count; if (!validRotationValuesOffset || !validRotationIndicesOffset) { continue; } // Angle count should be greater than 0. var angleCount = (uint)((rotationIndicesOffset - rotationValuesOffset) / 2L); var validAngleCount = rotationIndicesOffset > rotationValuesOffset && angleCount > 0L; if (!validAngleCount) { continue; } // Should have zeroes present in two spots of the animation header. var hasZeroes = IoUtil.ReadUInt16(bank, attemptOffset + 2) == 0 && IoUtil.ReadUInt16(bank, attemptOffset + 14) == 0; if (!hasZeroes) { continue; } // All values of "tTrack" should be within the bounds of .Angles. var validTTracks = true; var limit = IoUtil.ReadUInt16(bank, attemptOffset + 12); for (var i1 = 0; i1 < 3 + trackCount; i1++) { var tTrack = IoUtil.ReadUInt16( rotationIndicesBuffer, (uint)(rotationIndicesOffset + 2 * i1)); if (tTrack < limit) { if (tTrack >= angleCount) { validTTracks = false; goto badTTracks; } } else if ((uint)(tTrack + frameCount) > angleCount) { validTTracks = false; goto badTTracks; } } badTTracks: if (!validTTracks) { continue; } var animation = new NormalAnimation { FrameCount = frameCount, TrackOffset = rotationIndicesOffset, AngleCount = angleCount }; animation.Angles = new ushort[animation.AngleCount]; for (var i1 = 0; i1 < animation.AngleCount; ++i1) { animation.Angles[i1] = IoUtil.ReadUInt16(rotationValuesBuffer, rotationValuesOffset); rotationValuesOffset = (uint)(rotationValuesOffset + 2L); } // Translation is at the start. var xList = ReadFrames_( IoUtil.ReadUInt16(rotationIndicesBuffer, animation.TrackOffset + 0), limit, animation); var yList = ReadFrames_( IoUtil.ReadUInt16(rotationIndicesBuffer, animation.TrackOffset + 2), limit, animation); var zList = ReadFrames_( IoUtil.ReadUInt16(rotationIndicesBuffer, animation.TrackOffset + 4), limit, animation); animation.Positions = new Vec3s[animation.FrameCount]; for (var pi = 0; pi < animation.FrameCount; ++pi) { animation.Positions[pi] = new Vec3s { X = (short)xList[Math.Min(pi, xList.Length - 1)], Y = (short)yList[Math.Min(pi, yList.Length - 1)], Z = (short)zList[Math.Min(pi, zList.Length - 1)], }; } animation.Tracks = new NormalAnimationTrack[trackCount]; var tTrackOffset = (int)(animation.TrackOffset + 6L); for (var i1 = 0; i1 < trackCount; ++i1) { var track = animation.Tracks[i1] = new NormalAnimationTrack(); var tTrack = IoUtil.ReadUInt16(rotationIndicesBuffer, (uint)tTrackOffset); if (tTrack < limit) { // Constant (single value) track.Type = 0; track.Frames = new ushort[1]; track.Frames[0] = animation.Angles[tTrack]; } else { // Keyframes track.Type = 1; track.Frames = new ushort[animation.FrameCount]; for (var i2 = 0; i2 < animation.FrameCount; ++i2) { try { track.Frames[i2] = animation.Angles[tTrack + i2]; } catch { return(null); } } } tTrackOffset += 2; } animations.Add(animation); animationList.Items.Add("0x" + Conversion.Hex(i)); } return(animations.Count > 0 ? animations : null); }
public void Update(IList <byte> src, uint offset) => this.Update(IoUtil.ReadUInt32(src, offset), IoUtil.ReadUInt32(src, offset + 4));