public static void FindAnimations(Z64Object obj, byte[] data, int segmentId) { // Search for Animation Headers // Structure: FF FF 00 00 SS OO OO OO SS OO OO OO II II 00 00 for (int i = 0; i < data.Length - Z64Object.AnimationHolder.HEADER_SIZE; i += 4) { var frameCount = ArrayUtil.ReadInt16BE(data, i); // check positive nonzero frame count, check struct padding zeroes if (frameCount > 0 && data[i + 2] == 0 && data[i + 3] == 0 && data[i + 14] == 0 && data[i + 15] == 0) { if (!obj.IsOffsetFree(i)) { continue; } var frameDataSeg = new SegmentedAddress(ArrayUtil.ReadUint32BE(data, i + 4)); var jointIndicesSeg = new SegmentedAddress(ArrayUtil.ReadUint32BE(data, i + 8)); // check for segmentId match, check for valid segment offsets if (frameDataSeg.SegmentId == segmentId && jointIndicesSeg.SegmentId == segmentId && frameDataSeg.SegmentOff < data.Length && jointIndicesSeg.SegmentOff < data.Length) { // Assumes these are all in order and end at the start of the next, which seems to be the case so far int frameDataSize = (int)(jointIndicesSeg.SegmentOff - frameDataSeg.SegmentOff); int jointIndicesSize = (int)(i - jointIndicesSeg.SegmentOff); // if not a multiple of 2, check for struct padding if ((frameDataSize % 2) != 0) { byte[] possiblePadding = new byte[frameDataSize % 2]; Buffer.BlockCopy(data, (int)(frameDataSeg.SegmentOff + frameDataSize - (frameDataSize % 2)), possiblePadding, 0, frameDataSize % 2); // if assumed struct padding is nonzero, consider invalid if (possiblePadding.Any(b => b != 0)) { continue; } frameDataSize -= (frameDataSize % 2); } // if not a multiple of 6, check for struct padding if ((jointIndicesSize % 6) != 0) { byte[] possiblePadding = new byte[jointIndicesSize % 6]; Buffer.BlockCopy(data, (int)(jointIndicesSeg.SegmentOff + jointIndicesSize - (jointIndicesSize % 6)), possiblePadding, 0, jointIndicesSize % 6); // if assumed struct padding is nonzero, consider invalid if (possiblePadding.Any(b => b != 0)) { continue; } jointIndicesSize -= (jointIndicesSize % 6); } obj.AddAnimation(off: i); obj.AddFrameData(frameDataSize / 2, off: (int)frameDataSeg.SegmentOff); obj.AddJointIndices(jointIndicesSize / Z64Object.AnimationJointIndicesHolder.ENTRY_SIZE, off: (int)jointIndicesSeg.SegmentOff); } } } }
public static void FindDlists(Z64Object obj, byte[] data, int segmentId, Config cfg) { obj.Entries.Clear(); obj.AddUnknow(data.Length); var ret = FindRegions(data, segmentId, cfg); List <int> entries = FindDlistEntries(ret.Item2, ret.Item1, data, cfg); foreach (var start in entries) { for (int off = start; off < data.Length; off += 8) { if (data[off] == (byte)CmdID.G_ENDDL) { obj.AddDList(off + 8 - start, off: start); break; } } } obj.GroupUnkEntries(); obj.FixNames(); obj.SetData(data); }
public static List <string> AnalyzeDlists(Z64Object obj, byte[] data, int segmentId) { List <string> errors = new List <string>(); List <int> dlists = new List <int>(); for (int i = 0; i < obj.Entries.Count; i++) { var entry = obj.Entries[i]; if (entry.GetEntryType() == Z64Object.EntryType.DList) { dlists.Add(obj.OffsetOf(entry)); } else { obj.Entries[i] = new Z64Object.UnknowHolder($"unk_{obj.OffsetOf(entry):X8}", entry.GetData()); } } foreach (var dlist in dlists) { uint lastTexAddr = 0xFFFFFFFF; G_IM_FMT lastFmt = (G_IM_FMT)(-1); G_IM_SIZ lastSiz = (G_IM_SIZ)(-1); Z64Object.TextureHolder lastTlut = null; Z64Object.TextureHolder lastCiTex = null; bool exit = false; for (int i = dlist; i < data.Length && !exit; i += 8) { CmdID op = (CmdID)data[i]; switch (op) { case CmdID.G_QUAD: case CmdID.G_TRI2: case CmdID.G_TRI1: case CmdID.G_TEXRECTFLIP: case CmdID.G_TEXRECT: { if (lastCiTex != null && lastTlut != null) { lastCiTex.Tlut = lastTlut; lastCiTex = null; } break; } case CmdID.G_ENDDL: { exit = true; break; } case CmdID.G_MTX: { var gmtx = CmdInfo.DecodeCommand <GMtx>(data, i); var addr = new SegmentedAddress(gmtx.mtxaddr); if (addr.Segmented && addr.SegmentId == segmentId) { obj.AddMtx(1, off: (int)addr.SegmentOff); } break; } case CmdID.G_VTX: { var gvtx = CmdInfo.DecodeCommand <GVtx>(data, i); var addr = new SegmentedAddress(gvtx.vaddr); if (addr.Segmented && addr.SegmentId == segmentId) { try { obj.AddVertices(gvtx.numv, off: (int)addr.SegmentOff); } catch (Exception ex) { errors.Add($"Error in Dlist 0x{new SegmentedAddress(segmentId, dlist).VAddr:X8} : {ex.Message}"); } } break; } case CmdID.G_SETTIMG: { var settimg = CmdInfo.DecodeCommand <GSetTImg>(data, i); lastTexAddr = settimg.imgaddr; break; } case CmdID.G_SETTILE: { var settile = CmdInfo.DecodeCommand <GSetTile>(data, i); if (settile.tile != G_TX_TILE.G_TX_LOADTILE) { lastFmt = settile.fmt; lastSiz = settile.siz; } break; } case CmdID.G_SETTILESIZE: { var settilesize = CmdInfo.DecodeCommand <GLoadTile>(data, i); var addr = new SegmentedAddress(lastTexAddr); if ((int)lastFmt == -1 || (int)lastSiz == -1 || lastTexAddr == 0xFFFFFFFF) { /* can't really thow an exception here since in some object files, there are two gsDPSetTileSize next to each other (see object_en_warp_uzu) */ //throw new Z64ObjectAnalyzerException(); break; } if (addr.Segmented && addr.SegmentId == segmentId) { try { var tex = obj.AddTexture((int)(settilesize.lrs.Float() + 1), (int)(settilesize.lrt.Float() + 1), N64Texture.ConvertFormat(lastFmt, lastSiz), off: (int)addr.SegmentOff); if (lastFmt == G_IM_FMT.G_IM_FMT_CI) { lastCiTex = tex; } } catch (Exception ex) { errors.Add($"Error in Dlist 0x{new SegmentedAddress(segmentId, dlist).VAddr:X8} : {ex.Message}"); } } lastFmt = (G_IM_FMT)(-1); lastSiz = (G_IM_SIZ)(-1); lastTexAddr = 0xFFFFFFFF; break; } case CmdID.G_LOADTLUT: { var loadtlut = CmdInfo.DecodeCommand <GLoadTlut>(data, i); var addr = new SegmentedAddress(lastTexAddr); if (lastTexAddr == 0xFFFFFFFF) { throw new Z64ObjectAnalyzerException(); } int w = GetTlutWidth(loadtlut.count + 1); if (addr.Segmented && addr.SegmentId == segmentId) { try { lastTlut = obj.AddTexture(w, (loadtlut.count + 1) / w, N64Texture.ConvertFormat(G_IM_FMT.G_IM_FMT_RGBA, G_IM_SIZ.G_IM_SIZ_16b), off: (int)addr.SegmentOff); } catch (Exception ex) { errors.Add($"Error in Dlist 0x{new SegmentedAddress(segmentId, dlist).VAddr:X8} : {ex.Message}"); } } break; } } } } // These are carried out here as they are dependent on a lot of heuristics. // Having lots of the object already mapped out reduces possible mis-identifications. FindSkeletons(obj, data, segmentId); FindAnimations(obj, data, segmentId); obj.GroupUnkEntries(); obj.FixNames(); obj.SetData(data); return(errors); }
public static void FindSkeletons(Z64Object obj, byte[] data, int segmentId) { // Search for Skeleton Headers // Structure: SS OO OO OO XX 00 00 00 [XX 00 00 00] for (int i = 0; i < data.Length - Z64Object.SkeletonHolder.HEADER_SIZE; i += 4) { var segment = new SegmentedAddress(ArrayUtil.ReadUint32BE(data, i)); // check for segmentId match, check for valid segment offset, // check for Limbs 0x4 alignment, check for nonzero limb count, // check for zeroes in struct padding if (segment.SegmentId == segmentId && segment.SegmentOff < data.Length && (segment.SegmentOff % 4) == 0 && data[i + 4] != 0 && data[i + 5] == 0 && data[i + 6] == 0 && data[i + 7] == 0) { if (!obj.IsOffsetFree(i)) { continue; } int nLimbs = data[i + 4]; byte[] limbsData = new byte[nLimbs * 4]; Buffer.BlockCopy(data, (int)segment.SegmentOff, limbsData, 0, nLimbs * 4); // check for limbs array ending at the start of the skeleton header, // check for limbs array's segmented addresses being 0xC apart from one another if (segment.SegmentOff + nLimbs * 4 == i && ArrayUtil.ReadUint32BE(limbsData, 4) - ArrayUtil.ReadUint32BE(limbsData, 0) == Z64Object.SkeletonLimbHolder.ENTRY_SIZE) { int nNonNullDlists = 0; obj.AddSkeletonLimbs(nLimbs, off: (int)segment.SegmentOff); for (int j = 0; j < nLimbs * 4; j += 4) { SegmentedAddress limbSeg = new SegmentedAddress(ArrayUtil.ReadUint32BE(limbsData, j)); if (limbSeg.SegmentId != segmentId) { throw new Z64ObjectAnalyzerException( $"Limb segment {limbSeg.Segmented} is not the correct segment id, mis-detected SkeletonHeader?"); } obj.AddSkeletonLimb(off: (int)limbSeg.SegmentOff); // check if dlist is non-null (dlists may be null in general, this is only for FlexSkeletonHeader detection) if (ArrayUtil.ReadUint32BE(data, (int)(limbSeg.SegmentOff + 0x8)) != 0) { nNonNullDlists++; } } // try to detect flex headers over normal headers // check for the existence of extra bytes beyond standard header size, // check if nothing is already assumed to occupy that space, // check if the number of dlists is equal to the actual number of non-null dlists, // check struct padding if (i < data.Length - Z64Object.FlexSkeletonHolder.HEADER_SIZE && obj.IsOffsetFree(i + Z64Object.SkeletonHolder.HEADER_SIZE) && data[i + 8] == nNonNullDlists && data[i + 9] == 0 && data[i + 10] == 0 && data[i + 11] == 0) { obj.AddFlexSkeleton(off: i); } else { obj.AddSkeleton(off: i); } } } } }