public static void Pack(Stream inputStream, Stream outputStream, GxGame game) { if (inputStream == null) { throw new ArgumentNullException("inputStream"); } if (outputStream == null) { throw new ArgumentNullException("outputStream"); } if (!Enum.IsDefined(typeof(GxGame), game)) { throw new ArgumentOutOfRangeException("game"); } // Read the input data and compress with LZSS byte[] uncompressedData = StreamUtil.ReadFully(inputStream); LzssEncoder encoder = new LzssEncoder(); byte[] compressedData = encoder.Encode(uncompressedData); // Write file header and data int headerSizeField = compressedData.Length; if (game == GxGame.SuperMonkeyBall || game == GxGame.SuperMonkeyBallDX) { headerSizeField += 8; // SMB counts the 8 bytes of header in the compressed size field } EndianBinaryWriter outputBinaryWriter = new EndianBinaryWriter(EndianBitConverter.Little, outputStream); outputBinaryWriter.Write(headerSizeField); outputBinaryWriter.Write(uncompressedData.Length); outputBinaryWriter.Write(compressedData); }
private void Load(EndianBinaryReader input, GxGame game) { if (game == GxGame.SuperMonkeyBallDX) { input.ReadInt32(); } int numTextures = input.ReadInt32(); // Load texture definition headers TextureHeader[] texHdr = new TextureHeader[numTextures]; for (int i = 0; i < numTextures; i++) { texHdr[i].FormatRaw = input.ReadInt32(); texHdr[i].Offset = input.ReadInt32(); texHdr[i].Width = Convert.ToInt32(input.ReadUInt16()); texHdr[i].Height = Convert.ToInt32(input.ReadUInt16()); texHdr[i].LevelCount = Convert.ToInt32(input.ReadUInt16()); UInt16 check = input.ReadUInt16(); if ((game != GxGame.SuperMonkeyBallDX && check != 0x1234) || (game == GxGame.SuperMonkeyBallDX && check != 0x3412)) { throw new InvalidTplFileException("Invalid texture header (Field @0x0E)."); } } // Load textures data for (int i = 0; i < numTextures; i++) { TplTexture tex = new TplTexture(); if (texHdr[i].Offset != 0 && texHdr[i].Width != 0 && texHdr[i].Height != 0 && texHdr[i].LevelCount != 0) // Texture with defined levels { if (!Enum.IsDefined(typeof(GxTextureFormat), texHdr[i].FormatRaw)) { throw new InvalidTplFileException("Invalid texture header (invalid format."); } if (game == GxGame.SuperMonkeyBallDX) { input.BaseStream.Position = texHdr[i].Offset + 0x20; } else { input.BaseStream.Position = texHdr[i].Offset; } tex.LoadTextureData(input, game, (GxTextureFormat)texHdr[i].FormatRaw, texHdr[i].Width, texHdr[i].Height, texHdr[i].LevelCount); } else if (texHdr[i].Offset == 0 && texHdr[i].Width == 0 && texHdr[i].Height == 0 && texHdr[i].LevelCount == 0) // Texture with no defined levels { tex.DefineEmptyTexture(texHdr[i].FormatRaw); } else { throw new InvalidTplFileException("Invalid texture header (invalid combination of fields)."); } Add(tex); } }
private void Save(EndianBinaryWriter output, GxGame game, bool noHeader = false) { if (game == GxGame.SuperMonkeyBallDX) { output.Write('X'); output.Write('T'); output.Write('P'); output.Write('L'); } if (!noHeader) { output.Write(Count); // Write texture definition headers int beginDataOffset = SizeOfHeaderEntries(game); int currentDataOffset = beginDataOffset; foreach (TplTexture tex in Items) { if (tex.LevelCount != 0) { output.Write((int)tex.Format); output.Write(currentDataOffset); output.Write(Convert.ToUInt16(tex.WidthOfLevel(0))); output.Write(Convert.ToUInt16(tex.HeightOfLevel(0))); output.Write(Convert.ToUInt16(tex.LevelCount)); } else { output.Write(tex.FormatRaw); output.Write((int)0); output.Write((ushort)0); output.Write((ushort)0); output.Write((ushort)0); } if (game == GxGame.SuperMonkeyBallDX) { output.Write((ushort)0x3412); } else { output.Write((ushort)0x1234); } currentDataOffset += tex.SizeOfTextureData(game); } int paddingAmount = beginDataOffset - Convert.ToInt32(output.BaseStream.Position); for (int i = 0; i < paddingAmount; i++) { output.Write((byte)i); // Curious padding pattern of 0x00, 0x01, 0x02, 0x03, ... } } // Write texture data foreach (TplTexture tex in Items) { // No need to worry about textures with no levels, they have size zero tex.SaveTextureData(output, game); } }
private int SizeOfHeaderEntries(GxGame game) { if (game == GxGame.SuperMonkeyBallDX) { return(PaddingUtils.Align((8 + (4 + 4 + 2 + 2 + 2 + 2) * Count), 0x20)); } else { return(PaddingUtils.Align(4 + (4 + 4 + 2 + 2 + 2 + 2) * Count, 0x20)); } }
/// Gets the size of the texture when written to a binary stream. internal int SizeOfTextureData(GxGame game) { int size = 0; if (game == GxGame.SuperMonkeyBallDX) { size += 0x20; } for (int level = 0; level < LevelCount; level++) { size += CalculateSizeOfLevel(level, (game == GxGame.FZeroGX)); } return(size); }
/// <summary> /// Save a Gma model to the given .GMA stream. /// </summary> private void Save(EndianBinaryWriter output, GxGame game) { int modelBasePosition = SizeOfHeader(); // Write header entries output.Write(Items.Count); output.Write(modelBasePosition); int nameCurrentOffset = 0, modelCurrentOffset = 0; foreach (GmaEntry entry in Items) { if (entry != null) { output.Write(modelCurrentOffset); output.Write(nameCurrentOffset); nameCurrentOffset += entry.Name.Length + 1; modelCurrentOffset += entry.ModelObject.SizeOf(); } else { output.Write(-1); output.Write(0); } } // Write name table foreach (GmaEntry entry in Items.Where(e => e != null)) { output.WriteAsciiString(entry.Name); } // The alignment of the name table can be easily checked to be 0x20 // However, there's a bug on the official files (such as init/sel.gma), // in which the files that fall exactly on the 0x20 alignment boundary // have an extra 0x20 bytes of padding. // To work around this weirdness and generate the same output as the originals, // we add an extra byte after the name table, which will make the // files that fall exactly on the 0x20 boundary to get the desired extra 0x20 bytes. output.Write((byte)0); output.Align(0x20); // Write models foreach (GmaEntry entry in Items.Where(e => e != null)) { entry.ModelObject.Save(output, game); } }
public static void Unpack(Stream inputStream, Stream outputStream, GxGame game) { if (inputStream == null) { throw new ArgumentNullException("inputStream"); } if (outputStream == null) { throw new ArgumentNullException("outputStream"); } if (!Enum.IsDefined(typeof(GxGame), game)) { throw new ArgumentOutOfRangeException("game"); } EndianBinaryReader inputBinaryStream = new EndianBinaryReader(EndianBitConverter.Little, inputStream); // Read file header int headerSizeField = inputBinaryStream.ReadInt32(); int uncompressedSize = inputBinaryStream.ReadInt32(); int compressedSize = headerSizeField; if (game == GxGame.SuperMonkeyBall || game == GxGame.SuperMonkeyBallDX) { compressedSize -= 8; // SMB counts the 8 bytes of header in the compressed size field } // Check that the size of the input matches the expected value if (compressedSize + 8 != inputStream.Length) { throw new InvalidLzFileException("Invalid .lz file, inputSize does not match actual input size."); } // Read and uncompress LZSS data byte[] compressedData = inputBinaryStream.ReadBytesOrThrow(compressedSize); LzssDecoder decoder = new LzssDecoder(); byte[] uncompressedData = decoder.Decode(compressedData); if (uncompressedData.Length != uncompressedSize) { throw new InvalidLzFileException("Invalid .lz file, outputSize does not match actual output size."); } // Write uncompressed data to output stream outputStream.Write(uncompressedData, 0, uncompressedData.Length); }
/// <summary> /// Create a Tpl texture container from a .TPL file. /// </summary> /// <param name="inputStream">The input stream that contains the .TPL file.</param> /// <param name="game">The game from which the .TPL file is.</param> public Tpl(Stream inputStream, GxGame game, GeneratedTextureHeader?newTextureHeader = null) { if (inputStream == null) { throw new ArgumentNullException("inputStream"); } if (!Enum.IsDefined(typeof(GxGame), game)) { throw new ArgumentOutOfRangeException("game"); } if (game == GxGame.SuperMonkeyBallDX) { Load(new EndianBinaryReader(EndianBitConverter.Little, inputStream), game, newTextureHeader); } else { Load(new EndianBinaryReader(EndianBitConverter.Big, inputStream), game, newTextureHeader); } }
/// <summary> /// Save a Tpl texture container to a .TPL file. /// </summary> /// <param name="outputStream">The input stream to which to write the .TPL file.</param> /// <param name="game">The game from which the .TPL file is.</param> public void Save(Stream outputStream, GxGame game, bool noHeader = false) { if (outputStream == null) { throw new ArgumentNullException("outputStream"); } if (!Enum.IsDefined(typeof(GxGame), game)) { throw new ArgumentOutOfRangeException("game"); } if (game == GxGame.SuperMonkeyBallDX) { Save(new EndianBinaryWriter(EndianBitConverter.Little, outputStream), game, noHeader); } else { Save(new EndianBinaryWriter(EndianBitConverter.Big, outputStream), game, noHeader); } }
/// <summary> /// Load a Gma model from the given .GMA stream. /// </summary> private void Load(EndianBinaryReader input, GxGame game) { int numObjs = input.ReadInt32(); int modelBasePosition = input.ReadInt32(); List <GmaEntryOffsets> entryOffs = new List <GmaEntryOffsets>(); for (int i = 0; i < numObjs; i++) { entryOffs.Add(new GmaEntryOffsets { ModelOffset = input.ReadInt32(), NameOffset = input.ReadInt32() }); } int nameBasePosition = Convert.ToInt32(input.BaseStream.Position); foreach (GmaEntryOffsets entryOff in entryOffs) { // There are some "empty" entries, without any data or a name. // We insert null into the container in this case. if (entryOff.ModelOffset != -1 || entryOff.NameOffset != 0) { input.BaseStream.Position = nameBasePosition + entryOff.NameOffset; string name = input.ReadAsciiString(); input.BaseStream.Position = modelBasePosition + entryOff.ModelOffset; Gcmf model = new Gcmf(); model.Load(input, game); Add(new GmaEntry(name, model)); } else { Add(null); } } }
/// <summary> /// Reads a texture with the specified characteristics from a binary stream. /// </summary> internal void LoadTextureData(EndianBinaryReader input, GxGame game, GxTextureFormat format, int width, int height, int levelCount) { if (!SupportedTextureFormats.Contains(format)) { throw new InvalidTplFileException("Unsupported texture format."); } this.format = format; this.width = width; this.height = height; for (int level = 0; level < levelCount; level++) { byte[] levelData = new byte[CalculateSizeOfLevel(level)]; input.Read(levelData, 0, CalculateSizeOfLevel(level, (game == GxGame.FZeroGX))); if (game == GxGame.SuperMonkeyBallDX) { if (format == GxTextureFormat.CMPR) { // Swap pallete byte order for (int i = 0; i < levelData.Length; i += 8) { byte temp = levelData[i]; levelData[i] = levelData[i + 1]; levelData[i + 1] = temp; temp = levelData[i + 2]; levelData[i + 2] = levelData[i + 3]; levelData[i + 3] = temp; } } } encodedLevelData.Add(levelData); } }
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); } } }
private int SizeOfTextureData(GxGame game) { // No need to worry about textures with no levels, they have size zero return(Items.Sum(t => t.SizeOfTextureData(game))); }
/// <summary> /// Calculate the size of the TPL when written to a file. /// </summary> /// <param name="game">The game from which the .TPL file is.</param> /// <returns>The size of the TPL when written to a file.</returns> public int SizeOf(GxGame game) { return(SizeOfHeaderEntries(game) + SizeOfTextureData(game)); }
private void Load(EndianBinaryReader input, GxGame game, GeneratedTextureHeader?newHeader) { if (game == GxGame.SuperMonkeyBallDX) { input.ReadInt32(); } // If there is a header, use the value from that header, otherwise use the generated header value int numTextures = (newHeader == null) ? (numTextures = input.ReadInt32()) : (numTextures = newHeader.Value.textureCount); // Load texture definition headers TextureHeader[] texHdr = new TextureHeader[numTextures]; if (newHeader == null) { for (int i = 0; i < numTextures; i++) { texHdr[i].FormatRaw = input.ReadInt32(); texHdr[i].Offset = input.ReadInt32(); texHdr[i].Width = Convert.ToInt32(input.ReadUInt16()); texHdr[i].Height = Convert.ToInt32(input.ReadUInt16()); texHdr[i].LevelCount = Convert.ToInt32(input.ReadUInt16()); UInt16 check = input.ReadUInt16(); if ((game != GxGame.SuperMonkeyBallDX && check != 0x1234) || (game == GxGame.SuperMonkeyBallDX && check != 0x3412)) { throw new InvalidTplFileException("Invalid texture header (Field @0x0E)."); } } } // Creates a new texture header from provided texture header characteristics else { // The length of the header after this operation's completion int initialOffset = newHeader.Value.textureCount * 16; // Size of a texture based on how many bytes per pixel int texSize = newHeader.Value.textureHeight * newHeader.Value.textureWidth * GxTextureFormatCodec.GetCodec(newHeader.Value.textureFormat).BitsPerPixel / 8; // Maximum possible offset for the TPL int maxOffset = initialOffset + (texSize) * numTextures; // Iteration variable for referencing the index of the texture header int iteration; for (int iterateOffset = initialOffset; iterateOffset < maxOffset; iterateOffset += texSize) { iteration = (iterateOffset - initialOffset) / texSize; texHdr[iteration].FormatRaw = (int)newHeader.Value.textureFormat; texHdr[iteration].Width = newHeader.Value.textureWidth; texHdr[iteration].Height = newHeader.Value.textureHeight; texHdr[iteration].Offset = iterateOffset; texHdr[iteration].LevelCount = newHeader.Value.textureMipmapCount; } } // Load textures data for (int i = 0; i < numTextures; i++) { TplTexture tex = new TplTexture(); if (texHdr[i].Offset != 0 && texHdr[i].Width != 0 && texHdr[i].Height != 0 && texHdr[i].LevelCount != 0) // Texture with defined levels { if (game != GxGame.SuperMonkeyBallDX && !Enum.IsDefined(typeof(GxTextureFormat), texHdr[i].FormatRaw)) { throw new InvalidTplFileException("Invalid texture header (invalid format."); } if (game == GxGame.SuperMonkeyBallDX) { input.BaseStream.Position = texHdr[i].Offset; int formatRaw = input.ReadInt32(); switch (formatRaw) { case 0x0C: texHdr[i].FormatRaw = 0x0E; break; case 0x1A: texHdr[i].FormatRaw = 0x01; break; case 0x0E: texHdr[i].FormatRaw = 0x05; break; default: int x = 5; break; } texHdr[i].Width = input.ReadInt32(); texHdr[i].Height = input.ReadInt32(); texHdr[i].LevelCount = input.ReadInt32(); // Compressed? input.ReadInt32(); // If uncompressed, data length? input.ReadInt32(); // Some other length (for compressed)? input.ReadInt32(); // Zero? input.ReadInt32(); if (!Enum.IsDefined(typeof(GxTextureFormat), texHdr[i].FormatRaw)) { throw new InvalidTplFileException("Invalid texture header (invalid format."); } } else { if (newHeader == null) { input.BaseStream.Position = texHdr[i].Offset; } else { input.BaseStream.Position = texHdr[i].Offset - (newHeader.Value.textureCount * 16); } } tex.LoadTextureData(input, game, (GxTextureFormat)texHdr[i].FormatRaw, texHdr[i].Width, texHdr[i].Height, texHdr[i].LevelCount); } else if (texHdr[i].Offset == 0 && texHdr[i].Width == 0 && texHdr[i].Height == 0 && texHdr[i].LevelCount == 0) // Texture with no defined levels { tex.DefineEmptyTexture(texHdr[i].FormatRaw); } else { throw new InvalidTplFileException("Invalid texture header (invalid combination of fields)."); } Add(tex); } }
/// <summary> /// Writes this texture to a binary stream. /// </summary> internal void SaveTextureData(EndianBinaryWriter output, GxGame game) { if (LevelCount != 0 && game == GxGame.SuperMonkeyBallDX) { List <byte> texHeader = new List <byte>(); switch (format) { case GxTextureFormat.CMPR: texHeader.Add(0x0C); texHeader.Add(0x00); texHeader.Add(0x00); texHeader.Add(0x00); break; case GxTextureFormat.I8: texHeader.Add(0x1A); texHeader.Add(0x00); texHeader.Add(0x00); texHeader.Add(0x00); break; default: texHeader.Add(0x0C); texHeader.Add(0x00); texHeader.Add(0x00); texHeader.Add(0x00); break; } // Width texHeader.Add((byte)WidthOfLevel(0)); texHeader.Add((byte)(WidthOfLevel(0) >> 8)); // Padding texHeader.Add(0); texHeader.Add(0); // Height texHeader.Add((byte)HeightOfLevel(0)); texHeader.Add((byte)(HeightOfLevel(0) >> 8)); texHeader.Add(0); texHeader.Add(0); // Five texHeader.Add(5); texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); // 0 for uncompressed texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); // Data Length int levelSize = 0; for (int level = 0; level < LevelCount; level++) { levelSize += CalculateSizeOfLevel(level, (game == GxGame.FZeroGX)); } texHeader.Add((byte)levelSize); texHeader.Add((byte)(levelSize >> 8)); texHeader.Add((byte)(levelSize >> 16)); texHeader.Add((byte)(levelSize >> 24)); // Data length + a little (0 if uncompressed) texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); // Zero/Padding? texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); texHeader.Add(0); output.Write(texHeader.ToArray(), 0, texHeader.Count); } for (int level = 0; level < LevelCount; level++) { if (game == GxGame.SuperMonkeyBallDX) { if (Format == GxTextureFormat.CMPR) { // Swap pallete byte order byte[] swappedOrder = new byte[encodedLevelData[level].Length]; for (int i = 0; i < CalculateSizeOfLevel(level, (game == GxGame.FZeroGX)); i += 8) { swappedOrder[i] = encodedLevelData[level][i + 1]; swappedOrder[i + 1] = encodedLevelData[level][i]; swappedOrder[i + 2] = encodedLevelData[level][i + 3]; swappedOrder[i + 3] = encodedLevelData[level][i + 2]; // Pallete index values swappedOrder[i + 4] = encodedLevelData[level][i + 4]; swappedOrder[i + 5] = encodedLevelData[level][i + 5]; swappedOrder[i + 6] = encodedLevelData[level][i + 6]; swappedOrder[i + 7] = encodedLevelData[level][i + 7]; } output.Write(swappedOrder, 0, CalculateSizeOfLevel(level, (game == GxGame.FZeroGX))); } else { output.Write(encodedLevelData[level], 0, CalculateSizeOfLevel(level, (game == GxGame.FZeroGX))); } } else { output.Write(encodedLevelData[level], 0, CalculateSizeOfLevel(level, (game == GxGame.FZeroGX))); } } }
internal void Save(EndianBinaryWriter output, GxGame 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(); int offsetPartType8Unknown2 = offsetPartType8Unknown1 + (((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) ? SizeOf2Type8Unknown1() : 0); int offsetPartMeshData = offsetPartType8Unknown2 + (((SectionFlags & (uint)GcmfSectionFlags.SkinModel) != 0) ? SizeOf2Type8Unknown2() : 0); if ((game == GxGame.SuperMonkeyBall || game == GxGame.SuperMonkeyBallDX) && (SectionFlags & (uint)GcmfSectionFlags.EffectiveModel) != 0) { // Those are zero on SMB 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 foreach (GcmfVertex vtx in VertexPool) { vtx.SaveIndexed(output); } 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, 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); } } }