Exemple #1
0
        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);
        }
Exemple #2
0
        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);
            }
        }
Exemple #3
0
        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);
            }
        }
Exemple #4
0
 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));
     }
 }
Exemple #5
0
        /// 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);
        }
Exemple #6
0
        /// <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);
            }
        }
Exemple #7
0
        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);
        }
Exemple #8
0
        /// <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);
            }
        }
Exemple #9
0
        /// <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);
            }
        }
Exemple #10
0
        /// <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);
                }
            }
        }
Exemple #11
0
        /// <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);
            }
        }
Exemple #12
0
        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);
                }
            }
        }
Exemple #13
0
 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)));
 }
Exemple #14
0
 /// <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));
 }
Exemple #15
0
        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);
            }
        }
Exemple #16
0
        /// <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)));
                }
            }
        }
Exemple #17
0
        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);
                }
            }
        }