public void ApplyChanges()
        {
            // Models are always the first entry per CGFX standard
            var models = cgfx.Data.Entries[0]?.Entries;

            // Only one model per file for now
            var model = (DICTObjModel)models.First().EntryObject;

            // Textures are the second
            var textures = cgfx.Data.Entries[1]?.Entries.Select(e => e.EntryObject).Cast <DICTObjTexture>().ToList();

            if (textures != null && textures.Count > 0)
            {
                for (var t = 0; t < textures.Count; t++)
                {
                    if (Textures[t].TextureBitmap != null)
                    {
                        var textureBitmap = Textures[t].TextureBitmap;
                        var textureData   = textures[t];

                        if (textureData != null)
                        {
                            if (textureBitmap.Width != textureData.Width || textureBitmap.Height != textureData.Height)
                            {
                                throw new InvalidOperationException($"{textureData.Name} import: Bitmap mismatched expected dimensions -- texture data expected {textureData.Width}x{textureData.Height}, importing image is {textureBitmap.Width}x{textureBitmap.Height}");
                            }

                            var imgData     = textureBitmap.LockBits(new Rectangle(0, 0, (int)textureData.Width, (int)textureData.Height), ImageLockMode.ReadOnly, textureBitmap.PixelFormat);
                            var rawRGBAData = new byte[(imgData.Height * imgData.Stride)];
                            Marshal.Copy(imgData.Scan0, rawRGBAData, 0, rawRGBAData.Length);
                            textureBitmap.UnlockBits(imgData);

                            if (textureBitmap.PixelFormat == PixelFormat.Format24bppRgb)
                            {
                                // Need to upconvert the data to RGBA before going in!
                                var newData = new byte[textureBitmap.Width * textureBitmap.Height * 4];

                                var newDataOffset = 0;
                                for (var oldDataOffset = 0; oldDataOffset < rawRGBAData.Length; oldDataOffset += 3)
                                {
                                    newData[newDataOffset + 0] = rawRGBAData[oldDataOffset + 0];
                                    newData[newDataOffset + 1] = rawRGBAData[oldDataOffset + 1];
                                    newData[newDataOffset + 2] = rawRGBAData[oldDataOffset + 2];
                                    newData[newDataOffset + 3] = 0xFF;

                                    newDataOffset += 4;
                                }

                                rawRGBAData = newData;
                            }
                            else if (textureBitmap.PixelFormat != PixelFormat.Format32bppArgb)
                            {
                                throw new InvalidOperationException($"{textureData.Name} import: Unsupported image format {textureBitmap.PixelFormat}");
                            }

                            var utility = new Utility(null, null, Endianness.Little);
                            textureData.SetTexture(utility, rawRGBAData);       // TODO -- try disabling the safety checks...
                        }
                    }
                }
            }

            for (var m = 0; m < model.Meshes.Length; m++)
            {
                var mesh  = model.Meshes[m];
                var shape = model.Shapes[mesh.ShapeIndex];

                var SMMesh = Meshes[m];

                // NOTE: These are all making the same assumptions as noted in the constructor
                var vertexBuffer = shape.VertexBuffers.Where(vb => vb is VertexBufferInterleaved).Cast <VertexBufferInterleaved>().Single();

                var subMesh        = shape.SubMeshes[0];
                var faceHeader     = subMesh.Faces[0];
                var faceDescriptor = faceHeader.FaceDescriptors[0];

                // Vertices are stored (in GPU-compatible form)
                var vertexBufferIndex = shape.VertexBuffers.IndexOf(vertexBuffer);
                var attributes        = vertexBuffer.Attributes.Select(a => VertexBufferCodec.PICAAttribute.GetPICAAttribute(a)).ToList();

                // Convert to the simplified vertices
                var boneIndexCount  = GetElementsOfAttribute(attributes, VertexBuffer.PICAAttributeName.BoneIndex);  // How many bone indices are actually used
                var boneWeightCount = GetElementsOfAttribute(attributes, VertexBuffer.PICAAttributeName.BoneWeight); // How many bone weights are actually used

                var vertices = SMMesh.Vertices.Select(v => new VertexBufferCodec.PICAVertex
                {
                    Position  = new Vector4(v.Position.X, v.Position.Y, v.Position.Z, 0),
                    Normal    = new Vector4(v.Normal.X, v.Normal.Y, v.Normal.Z, 0),
                    TexCoord0 = new Vector4(v.TexCoord.X, v.TexCoord.Y, v.TexCoord.Z, 0),
                    Indices   = GetBoneIndices(v.BoneIndices),
                    Weights   = GetBoneWeights(v.Weights),
                    Color     = v.Color
                }).ToArray();

                // Same as when we loaded it in the first place, the bone indices need to be changed into a list
                var boneReferences =
                    // Get the absolute indices
                    SMMesh.Vertices.SelectMany(v => v.BoneIndices).Distinct().ToList()

                    // Remap to the bones
                    .SelectMany(smshape => model.Skeleton.Bones.Entries.Select(b => b.EntryObject).Cast <DICTObjBone>().Where(b => b.Index == smshape))

                    .ToList();

                var boneReferenceIndexes = boneReferences.Select(b => b.Index).ToList();

                // Reassign the indices so they're relative to the bone references pool
                for (var v = 0; v < vertices.Length; v++)
                {
                    for (var bi = 0; bi < boneIndexCount; bi++)
                    {
                        var absoluteIndex = vertices[v].Indices[bi];

                        vertices[v].Indices[bi] = boneReferenceIndexes.IndexOf(absoluteIndex);
                    }
                }

                // Convert the vertices back to native interleaved format
                vertexBuffer.RawBuffer = VertexBufferCodec.GetBuffer(vertices, vertexBuffer.Attributes.Select(a => VertexBufferCodec.PICAAttribute.GetPICAAttribute(a)));

                // Align if ending on odd byte
                if ((vertexBuffer.RawBuffer.Length & 1) == 1)
                {
                    vertexBuffer.RawBuffer = vertexBuffer.RawBuffer.Concat(new byte[] { 0 }).ToArray();
                }

                // Replace the bone references in the SubMesh with the generated list
                // NOTE: Don't do this if boneIndexCount = 0, because apparently there
                // is always one defined even if not explicitly used...
                //
                // NOTE 2: I handle this during the Load case but not here but just
                // assuming if the submesh has bone refs then the object probably does
                // want to use them even if this is zero; seems MAYBE that means that
                // vertices don't specify a bone reference if it applies to the entire
                // mesh. Could use some more research to verify this...
                //
                // The big issue is if the editor tries to assign vertices in this mesh
                // to other bones and the assignment will BE IGNORED.
                if (boneIndexCount > 0)
                {
                    subMesh.BoneReferences.Clear();
                    subMesh.BoneReferences.AddRange(boneReferences);
                }

                //// Get indicies of triangle faces
                var indices = SMMesh.Triangles.SelectMany(t => new[] { t.v1, t.v2, t.v3 }).ToList();

                faceDescriptor.Indices.Clear();
                faceDescriptor.Indices.AddRange(indices);
            }
        }