internal void Save(EndianBinaryWriter output, GcGame game) { int headerSize = SizeOfHeader(); // In the GCMF file, the triangle meshes are classified in two layers: // The "opaque" layer and the "translucid" layer. // We store both kinds of layers in the same vector for easier manipulation // (setting the layer property on the triangle mesh itself), which the user // may order freely, but now that we're writting the GCMF again, we need to // reclassify the meshes in both layers. GcmfMesh[] layer1Meshes = Meshes.Where(mesh => mesh.Layer == GcmfMesh.MeshLayer.Layer1).ToArray(); GcmfMesh[] layer2Meshes = Meshes.Where(mesh => mesh.Layer == GcmfMesh.MeshLayer.Layer2).ToArray(); GcmfMesh[] meshesSortedByLayer = layer1Meshes.Union(layer2Meshes).ToArray(); // Write GCMF header output.Write(GcmfMagic); output.Write(SectionFlags); output.Write(BoundingSphereCenter.X); output.Write(BoundingSphereCenter.Y); output.Write(BoundingSphereCenter.Z); output.Write(BoundingSphereRadius); output.Write(Convert.ToUInt16(Materials.Count)); output.Write(Convert.ToUInt16(layer1Meshes.Length)); output.Write(Convert.ToUInt16(layer2Meshes.Length)); output.Write(Convert.ToByte(TransformMatrices.Count)); output.Write((byte)0); output.Write(headerSize); output.Write((uint)0); output.Write(TransformMatrixDefaultIdxs); output.Write((uint)0); output.Write((uint)0); output.Write((uint)0); output.Write((uint)0); for (int i = 0; i < Materials.Count; i++) { Materials[i].Save(output, i); } foreach (GcmfTransformMatrix tmtx in TransformMatrices) { tmtx.Save(output); } output.Align(0x20); if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0 || (SectionFlags & (uint)GcmfSectionFlags.EffectiveModel) != 0) { int offsetPartVertexPool = SizeOf2Header() + SizeOf2MeshHeaders(); int offsetPartType8Unknown1 = offsetPartVertexPool + SizeOf2VertexPool(game); int offsetPartType8Unknown2 = offsetPartType8Unknown1 + (((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) ? SizeOf2Type8Unknown1() : 0); int offsetPartMeshData = offsetPartType8Unknown2 + (((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) ? SizeOf2Type8Unknown2() : 0); if (!GcmfVersionDetails.IsGcmfSkinModelSupported(game)) { // Those are zero on SMB1 offsetPartType8Unknown1 = 0; offsetPartType8Unknown2 = 0; } output.Write(VertexPool.Count); output.Write(offsetPartType8Unknown1); output.Write(offsetPartVertexPool); output.Write(offsetPartMeshData); output.Write(offsetPartType8Unknown2); output.Write((uint)0); output.Write((uint)0); output.Write((uint)0); // Write the mesh headers foreach (GcmfMesh mesh in meshesSortedByLayer) { mesh.SaveHeader(output, true, false); } // Write the vertex pool bool isVtx16Bit = (SectionFlags & (uint)GcmfSectionFlags._16Bit) != 0 && !GcmfVersionDetails.Is16BitEffectiveModelIgnored(game); foreach (GcmfVertex vtx in VertexPool) { vtx.SaveIndexed(output, isVtx16Bit); } if ((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) { foreach (GcmfType8Unknown1 unk1 in Type8Unknown1) { unk1.Save(output); } foreach (ushort unk2 in Type8Unknown2) { output.Write(unk2); } output.Align(0x20); } // Write the section data itself (the indexed triangle strips) Dictionary <GcmfVertex, int> vertexPoolIndexes = new Dictionary <GcmfVertex, int>(); for (int i = 0; i < VertexPool.Count; i++) { vertexPoolIndexes.Add(VertexPool[i], i); } foreach (GcmfMesh mesh in meshesSortedByLayer) { mesh.SaveIndexedData(output, isVtx16Bit, vertexPoolIndexes); } output.Align(0x20); } else { bool is16Bit = ((SectionFlags & (uint)GcmfSectionFlags._16Bit) != 0); foreach (GcmfMesh mesh in meshesSortedByLayer) { mesh.SaveHeader(output, false, is16Bit); mesh.SaveNonIndexedData(output, is16Bit); } } }
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); } } }