internal void Load(EndianBinaryReader input, GcGame game) { int baseOffset = Convert.ToInt32(input.BaseStream.Position); // Load GCMF header if (input.ReadUInt32() != GcmfMagic) { throw new InvalidGmaFileException("Expected Gcmf[0x00] == GcmfMagic."); } SectionFlags = input.ReadUInt32(); BoundingSphereCenter = new Vector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); BoundingSphereRadius = input.ReadSingle(); int numMaterials = (int)input.ReadUInt16(); int numLayer1Meshes = (int)input.ReadUInt16(); int numLayer2Meshes = (int)input.ReadUInt16(); int numTransformMatrices = (int)input.ReadByte(); if (input.ReadByte() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x1F] == 0"); } int headerSize = input.ReadInt32(); if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x24] == 0"); } input.Read(TransformMatrixDefaultIdxs, 0, 8); if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x30] == 0"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x34] == 0"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x38] == 0"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x3C] == 0"); } // Check that the combination of flags is correct if ((SectionFlags & (uint)~(GcmfSectionFlags._16Bit | GcmfSectionFlags.StitchingModel | GcmfSectionFlags.SkinModel | GcmfSectionFlags.EffectiveModel)) != 0) { throw new InvalidGmaFileException("Unknown GCMF section flags."); } if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0 && !GcmfVersionDetails.IsGcmfSkinModelSupported(game)) { throw new InvalidGmaFileException("GCMF with skin model flag is not supported in this game."); } // Load materials for (int i = 0; i < numMaterials; i++) { GcmfMaterial mat = new GcmfMaterial(); mat.Load(input, i); Materials.Add(mat); } // Load transform matrices if ((SectionFlags & (uint)GcmfSectionFlags.StitchingModel) != 0 || (SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { for (int i = 0; i < numTransformMatrices; i++) { GcmfTransformMatrix tmtx = new GcmfTransformMatrix(); tmtx.Load(input); TransformMatrices.Add(tmtx); } } else { if (numTransformMatrices != 0) { throw new InvalidGmaFileException("GcmfSection: No transform matrices expected, but transformMatrixCount != 0?"); } } if (PaddingUtils.Align(Convert.ToInt32(input.BaseStream.Position), 0x20) != baseOffset + headerSize) { throw new InvalidGmaFileException("Gcmf [End Header Offset] mismatch."); } input.BaseStream.Position = baseOffset + headerSize; if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0 || (SectionFlags & (uint)GcmfSectionFlags.EffectiveModel) != 0) { int sectionBaseOffset = Convert.ToInt32(input.BaseStream.Position); int numVertices = input.ReadInt32(); int offsetPartType8Unknown1 = input.ReadInt32(); int offsetPartVertexPool = input.ReadInt32(); int offsetPartMeshData = input.ReadInt32(); int offsetPartType8Unknown2 = input.ReadInt32(); if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Gcmf[PreSectionHdr-0x14]"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Gcmf[PreSectionHdr-0x18]"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Gcmf[PreSectionHdr-0x1C]"); } // Load the mesh headers List <GcmfMesh.HeaderSectionInfo> meshHeaderSectionInfos = new List <GcmfMesh.HeaderSectionInfo>(); for (int i = 0; i < numLayer1Meshes + numLayer2Meshes; i++) { GcmfMesh mesh = new GcmfMesh(); meshHeaderSectionInfos.Add(mesh.LoadHeader(input, (i < numLayer1Meshes) ? GcmfMesh.MeshLayer.Layer1 : GcmfMesh.MeshLayer.Layer2)); Meshes.Add(mesh); } if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartVertexPool) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartVertexPool doesn't match expected value."); } // Load the vertex pool, i.e. the vertices referenced from the indexed triangle strips // On SMB1, the 16 bit flag is ignored in effective models bool isVtx16Bit = (SectionFlags & (uint)GcmfSectionFlags._16Bit) != 0 && !GcmfVersionDetails.Is16BitEffectiveModelIgnored(game); for (int i = 0; i < numVertices; i++) { GcmfVertex vtx = new GcmfVertex(); vtx.LoadIndexed(input, isVtx16Bit); VertexPool.Add(vtx); } // Load type 8 unknown 1 / unknown 2 if (GcmfVersionDetails.IsGcmfSkinModelSupported(game)) { if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartType8Unknown1) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown1 doesn't match expected value."); } if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { for (int i = 0; i < (offsetPartType8Unknown2 - offsetPartType8Unknown1) / 0x20; i++) { GcmfType8Unknown1 unk1 = new GcmfType8Unknown1(); unk1.Load(input); Type8Unknown1.Add(unk1); } } if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartType8Unknown2) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown2 doesn't match expected value."); } if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { for (int i = 0; i < numTransformMatrices; i++) { Type8Unknown2.Add(input.ReadUInt16()); } if (PaddingUtils.Align(Convert.ToInt32(input.BaseStream.Position), 0x20) != sectionBaseOffset + offsetPartMeshData) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartMeshData doesn't match expected value."); } input.BaseStream.Position = sectionBaseOffset + offsetPartMeshData; } } else { // We already made sure that the section flags are not skin model if it's not supported, // so if we're here, the flag is set to effective model. // In this case, the offsets of those sections must be zero // SMB doesn't have have any 0x08 section flags, so those offsets are zero if (offsetPartType8Unknown1 != 0) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown1 is not zero on SMB."); } if (offsetPartType8Unknown2 != 0) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown2 is not zero on SMB."); } } // Load the mesh data itself (the indexed triangle strips) if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartMeshData) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartMeshData doesn't match expected value."); } for (int i = 0; i < numLayer1Meshes + numLayer2Meshes; i++) { Meshes[i].LoadIndexedData(input, isVtx16Bit, VertexPool, meshHeaderSectionInfos[i]); } // Make sure that all the vertices in the vertex pool got their // vertex flags set when we read the indexed triangle strips, // that is, that they were referenced at least once. // Otherwise, we would have semi-initialized vertices in the vertex pool! if (VertexPool.Any(vtx => !vtx.IsIndexedVertexInitialized())) { throw new InvalidGmaFileException("Not all vertex flags of all vertices in the vertex pool got initialized!"); } } else { for (int i = 0; i < numLayer1Meshes + numLayer2Meshes; i++) { GcmfMesh mesh = new GcmfMesh(); GcmfMesh.HeaderSectionInfo headerSectionInfo = mesh.LoadHeader(input, (i < numLayer1Meshes) ? GcmfMesh.MeshLayer.Layer1 : GcmfMesh.MeshLayer.Layer2); mesh.LoadNonIndexedData(input, headerSectionInfo, (SectionFlags & (uint)GcmfSectionFlags._16Bit) != 0); Meshes.Add(mesh); } } }
internal void Load(EndianBinaryReader input, GxGame game) { int baseOffset = Convert.ToInt32(input.BaseStream.Position); // Load GCMF header if (input.ReadUInt32() != GcmfMagic) { throw new InvalidGmaFileException("Expected Gcmf[0x00] == GcmfMagic."); } SectionFlags = input.ReadUInt32(); BoundingSphereCenter = new Vector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); BoundingSphereRadius = input.ReadSingle(); int numMaterials = (int)input.ReadUInt16(); int numLayer1Meshes = (int)input.ReadUInt16(); int numLayer2Meshes = (int)input.ReadUInt16(); int transformMatrixCount = (int)input.ReadByte(); if (input.ReadByte() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x1F] == 0"); } int headerSize = input.ReadInt32(); if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x24] == 0"); } input.Read(TransformMatrixDefaultIdxs, 0, 8); if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x30] == 0"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x34] == 0"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x38] == 0"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Expected Gcmf[0x3C] == 0"); } // Load materials for (int i = 0; i < numMaterials; i++) { GcmfMaterial mat = new GcmfMaterial(); mat.Load(input, i); Materials.Add(mat); } if ((SectionFlags & (uint)~(GcmfSectionFlags._16Bit | GcmfSectionFlags.StitchingModel | GcmfSectionFlags.SkinModel | GcmfSectionFlags.EffectiveModel)) != 0) { throw new InvalidGmaFileException("Unknown GCMF section flags."); } if ((SectionFlags & (uint)GcmfSectionFlags.StitchingModel) != 0 || (SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { for (int i = 0; i < transformMatrixCount; i++) { GcmfTransformMatrix tmtx = new GcmfTransformMatrix(); tmtx.Load(input); TransformMatrices.Add(tmtx); } } else { if (transformMatrixCount != 0) { throw new InvalidGmaFileException("GcmfSection: No transform matrices expected, but transformMatrixCount != 0?"); } } if (PaddingUtils.Align(Convert.ToInt32(input.BaseStream.Position), 0x20) != baseOffset + headerSize) { throw new InvalidGmaFileException("Gcmf [End Header Offset] mismatch."); } input.BaseStream.Position = baseOffset + headerSize; if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0 || (SectionFlags & (uint)GcmfSectionFlags.EffectiveModel) != 0) { int sectionBaseOffset = Convert.ToInt32(input.BaseStream.Position); int numVertices = input.ReadInt32(); int offsetPartType8Unknown1 = input.ReadInt32(); int offsetPartVertexPool = input.ReadInt32(); int offsetPartMeshData = input.ReadInt32(); int offsetPartType8Unknown2 = input.ReadInt32(); if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Gcmf[PreSectionHdr-0x14]"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Gcmf[PreSectionHdr-0x18]"); } if (input.ReadUInt32() != 0) { throw new InvalidGmaFileException("Gcmf[PreSectionHdr-0x1C]"); } // Load the mesh headers List <GcmfMesh.HeaderSectionInfo> meshHeaderSectionInfos = new List <GcmfMesh.HeaderSectionInfo>(); for (int i = 0; i < numLayer1Meshes + numLayer2Meshes; i++) { GcmfMesh mesh = new GcmfMesh(); meshHeaderSectionInfos.Add(mesh.LoadHeader(input, (i < numLayer1Meshes) ? GcmfMesh.MeshLayer.Layer1 : GcmfMesh.MeshLayer.Layer2)); Meshes.Add(mesh); } if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartVertexPool) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartVertexPool doesn't match expected value."); } // Load the vertex pool, i.e. the vertices referenced from the indexed triangle strips for (int i = 0; i < numVertices; i++) { GcmfVertex vtx = new GcmfVertex(); vtx.LoadIndexed(input); VertexPool.Add(vtx); } // Just because it probably won't be there doesn't mean we shouldn't allow it // and some stages have the things that are not allowed /*if ((game == GxGame.SuperMonkeyBall || game == GxGame.SuperMonkeyBallDX) && (SectionFlags & (uint)GcmfSectionFlags.EffectiveModel) != 0) * { * // SMB doesn't have have any 0x08 section flags, so it's unknown how this field may work in that case * if (offsetPartType8Unknown1 != 0) * throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown1 is not zero on SMB."); * } * else * {*/ if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartType8Unknown1) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown1 doesn't match expected value."); } //} if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { for (int i = 0; i < (offsetPartType8Unknown2 - offsetPartType8Unknown1) / 0x20; i++) { GcmfType8Unknown1 unk1 = new GcmfType8Unknown1(); unk1.Load(input); Type8Unknown1.Add(unk1); } } // Just because it probably won't be there doesn't mean we shouldn't allow it // and some stages have the things that are not allowed /*if ((game == GxGame.SuperMonkeyBall || game == GxGame.SuperMonkeyBallDX) && (SectionFlags & (uint)GcmfSectionFlags.EffectiveModel) != 0) * { * // SMB doesn't have have any 0x08 section flags, so it's unknown how this field may work in that case * if (offsetPartType8Unknown2 != 0) * throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown2 is not zero on SMB."); * } * else * {*/ if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartType8Unknown2) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartType8Unknown2 doesn't match expected value."); } //} if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { // TODO figure out a better way to calculate this int numEntriesPart3 = Type8Unknown1.Max(u1 => u1.unk18) + 1; for (int i = 0; i < numEntriesPart3; i++) { Type8Unknown2.Add(input.ReadUInt16()); } if (PaddingUtils.Align(Convert.ToInt32(input.BaseStream.Position), 0x20) != sectionBaseOffset + offsetPartMeshData) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPart4 doesn't match expected value."); } input.BaseStream.Position = sectionBaseOffset + offsetPartMeshData; } if (Convert.ToInt32(input.BaseStream.Position) != sectionBaseOffset + offsetPartMeshData) { throw new InvalidGmaFileException("Gcmf [PreSectionHdr] offsetPartMeshData doesn't match expected value."); } // Load the mesh data itself (the indexed triangle strips) for (int i = 0; i < numLayer1Meshes + numLayer2Meshes; i++) { Meshes[i].LoadIndexedData(input, VertexPool, meshHeaderSectionInfos[i]); } // Make sure that all the vertices in the vertex pool got their // vertex flags set when we read the indexed triangle strips, // that is, that they were referenced at least once. // Otherwise, we would have semi-initialized vertices in the vertex pool! if (VertexPool.Any(vtx => !vtx.IsIndexedVertexInitialized())) { throw new InvalidGmaFileException("Not all vertex flags of all vertices in the vertex pool got initialized!"); } } else { for (int i = 0; i < numLayer1Meshes + numLayer2Meshes; i++) { GcmfMesh mesh = new GcmfMesh(); GcmfMesh.HeaderSectionInfo headerSectionInfo = mesh.LoadHeader(input, (i < numLayer1Meshes) ? GcmfMesh.MeshLayer.Layer1 : GcmfMesh.MeshLayer.Layer2); mesh.LoadNonIndexedData(input, headerSectionInfo, (SectionFlags & (uint)GcmfSectionFlags._16Bit) != 0); Meshes.Add(mesh); } } }