Esempio n. 1
0
        private static byte[] CompressData(byte[] data)
        {
            byte[] prefix         = { 0x00, 0x00, 0x06, 0x00 };     // These bytes are important, otherwise the game won't launch
            byte[] compressedData = ZLib.CompressData(data);

            return(prefix.Concat(compressedData).ToArray());
        }
Esempio n. 2
0
        /// <summary>
        /// Builds a compressed payload from the current content's data.
        /// </summary>
        /// <param name="opcode">The packet's opcode.</param>
        /// <returns>A new compressed payload.</returns>
        public byte[] GetCompressedPayload(short opcode)
        {
            byte[] compressedData = ZLib.CompressData(_contentData);

            byte[] packetId         = BigEndian.GetBytes(opcode);
            byte[] size             = BigEndian.GetBytes(compressedData.Length + sizeof(int));
            byte[] compressionFlag  = { 1 }; // true
            byte[] decompressedSize = LittleEndian.GetBytes(_contentData.Length);
            byte[] padding          = { 0, 0, 0, 0 };

            return(Sequence.Concat(packetId, size, compressionFlag, decompressedSize, compressedData, padding));
        }
Esempio n. 3
0
        /// <summary>
        /// Builds a compressed payload from the current content's data.
        /// </summary>
        /// <param name="id">The packet's id.</param>
        /// <returns>A new compressed payload.</returns>
        public byte[] GetCompressedPayload(CenterOpcodes oId)
        {
            short id = (short)oId;

            byte[] compressedData = ZLib.CompressData(Data);

            byte[] packetId         = BigEndian.GetBytes(id);
            byte[] size             = BigEndian.GetBytes(compressedData.Length + 4);
            byte[] compressionFlag  = { 1 }; // true
            byte[] decompressedSize = LittleEndian.GetBytes(Data.Length);
            byte[] padding          = { 0, 0, 0 };

            return(Sequence.Concat(packetId, size, compressionFlag, decompressedSize, compressedData, padding));
        }
Esempio n. 4
0
        public void Dispose()
        {
            if (_baseStream == null)
            {
                return;
            }

            try
            {
                switch (_compression)
                {
                case Compression.Zlib:
                    byte[] compressedData = ZLib.CompressData(_writer.BaseStream, _compressionLevel);
                    new BinaryWriter(_baseStream).Write(compressedData.Length);
                    _baseStream.Write(compressedData, 0, compressedData.Length);
                    break;
                }
            }
            finally
            {
                _writer.Dispose();
            }
        }
Esempio n. 5
0
        private void WriteLevelTr5Main()
        {
            // Now begin to compile the geometry block in a MemoryStream
            byte[] geometryDataBuffer;
            using (var geometryDataStream = new MemoryStream())
            {
                var writer = new BinaryWriterEx(geometryDataStream); // Don't dispose
                ReportProgress(80, "Writing geometry data to memory buffer");

                const int filler = 0;
                writer.Write(filler);

                var numRooms = (uint)_level.Rooms.Count(r => r != null);
                writer.Write(numRooms);

                foreach (var r in _level.Rooms.Where(r => r != null))
                {
                    _tempRooms[r].WriteTr5(writer);
                }

                // Write floordata
                var numFloorData = (uint)_floorData.Count;
                writer.Write(numFloorData);
                writer.WriteBlockArray(_floorData);

                // Write meshes
                var offset = writer.BaseStream.Position;

                const int numMeshData = 0;
                writer.Write(numMeshData);
                var totalMeshSize = 0;

                for (var i = 0; i < _meshes.Count; i++)
                {
                    var meshSize = _meshes[i].WriteTr4AndTr5(writer);
                    totalMeshSize += (int)meshSize;
                }

                var offset2 = writer.BaseStream.Position;
                // ReSharper disable once SuggestVarOrType_BuiltInTypes
                uint meshDataSize = (uint)((offset2 - offset - 4) / 2);

                // Save the size of the meshes
                writer.BaseStream.Seek(offset, SeekOrigin.Begin);
                writer.Write(meshDataSize);
                writer.BaseStream.Seek(offset2, SeekOrigin.Begin);

                // Write mesh pointers
                writer.Write((uint)_meshPointers.Count);
                writer.WriteBlockArray(_meshPointers);

                // Write animations' data
                writer.Write((uint)_animations.Count);
                foreach (var anim in _animations)
                {
                    anim.Write(writer, _level);
                }

                writer.Write((uint)_stateChanges.Count);
                writer.WriteBlockArray(_stateChanges);

                writer.Write((uint)_animDispatches.Count);
                writer.WriteBlockArray(_animDispatches);

                writer.Write((uint)_animCommands.Count);
                writer.WriteBlockArray(_animCommands);

                writer.Write((uint)_meshTrees.Count);
                writer.WriteBlockArray(_meshTrees);

                writer.Write((uint)_frames.Count);
                writer.WriteBlockArray(_frames);

                writer.Write((uint)_moveables.Count);
                for (var k = 0; k < _moveables.Count; k++)
                {
                    writer.WriteBlock(_moveables[k]);
                    writer.Write((ushort)0xfeff);
                }

                writer.Write((uint)_staticMeshes.Count);
                writer.WriteBlockArray(_staticMeshes);

                // SPR block
                writer.WriteBlockArray(new byte[] { 0x53, 0x50, 0x52, 0x00 });

                writer.Write((uint)_spriteTextures.Count);
                writer.WriteBlockArray(_spriteTextures);

                writer.Write((uint)_spriteSequences.Count);
                writer.WriteBlockArray(_spriteSequences);

                // Write camera, flyby and sound sources
                writer.Write((uint)_cameras.Count);
                writer.WriteBlockArray(_cameras);

                writer.Write((uint)_flyByCameras.Count);
                writer.WriteBlockArray(_flyByCameras);

                writer.Write((uint)_soundSources.Count);
                writer.WriteBlockArray(_soundSources);

                // Write pathfinding data
                writer.Write((uint)_boxes.Length);
                writer.WriteBlockArray(_boxes);

                writer.Write((uint)_overlaps.Length);
                writer.WriteBlockArray(_overlaps);

                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone1_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone2_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone3_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone4_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].FlyZone_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone1_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone2_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone3_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone4_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].FlyZone_Alternate);
                }

                // Write animated textures
                _textureInfoManager.WriteAnimatedTextures(writer);

                // Write object textures
                writer.Write(checked ((byte)_textureInfoManager.UvRotateCount));
                writer.Write(new byte[] { 0x54, 0x45, 0x58, 0x00 });

                _textureInfoManager.WriteTextureInfos(writer, _level);

                // Write items and AI objects
                writer.Write((uint)_items.Count);
                writer.WriteBlockArray(_items);

                writer.Write((uint)_aiItems.Count);
                writer.WriteBlockArray(_aiItems);

                // Write sound meta data
                PrepareSoundsData();
                WriteSoundMetadata(writer);

                // Finish it
                writer.Write((ushort)0xcdcd);
                writer.Write((ushort)0xcdcd);
                writer.Write((ushort)0xcdcd);

                geometryDataBuffer = geometryDataStream.ToArray();
            }

            using (var fs = new FileStream(_dest, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var writer = new BinaryWriterEx(fs))
                {
                    ReportProgress(90, "Writing final level");
                    writer.WriteBlockArray(new byte[] { 0x54, 0x52, 0x34, 0x00 });

                    ReportProgress(91, "Writing textures");

                    // The room texture tile count currently also currently contains the wad textures
                    // But lets not bother with those fields too much since they only matter when bump maps are used and we don't use them.
                    writer.Write((ushort)_textureInfoManager.NumRoomPages);
                    writer.Write((ushort)_textureInfoManager.NumObjectsPages);
                    // Bump map pages must be multiplied by 2 or tile index will be wrong
                    writer.Write((ushort)(_textureInfoManager.NumBumpPages * 2));

                    // Compress data
                    ReportProgress(95, "Compressing data");

                    byte[] texture32 = null;
                    int    texture32UncompressedSize = -1;
                    byte[] texture16 = null;
                    int    texture16UncompressedSize = -1;
                    byte[] textureMisc = null;
                    int    textureMiscUncompressedSize = -1;
                    byte[] geometryData = geometryDataBuffer;
                    int    geometryDataUncompressedSize = geometryData.Length;

                    using (Task Texture32task = Task.Factory.StartNew(() =>
                    {
                        texture32 = ZLib.CompressData(_texture32Data);
                        texture32UncompressedSize = _texture32Data.Length;
                    }))
                        using (Task Texture16task = Task.Factory.StartNew(() =>
                        {
                            byte[] texture16Data = PackTextureMap32To16Bit(_texture32Data, _level.Settings);
                            texture16 = ZLib.CompressData(texture16Data);
                            texture16UncompressedSize = texture16Data.Length;
                        }))
                            using (Task textureMiscTask = Task.Factory.StartNew(() =>
                            {
                                Stream textureMiscData = PrepareFontAndSkyTexture();
                                textureMisc = ZLib.CompressData(textureMiscData);
                                textureMiscUncompressedSize = (int)textureMiscData.Length;
                            }))

                                Task.WaitAll(Texture32task, Texture16task, textureMiscTask);

                    // Write data
                    ReportProgress(97, "Writing compressed data to file.");

                    writer.Write(texture32UncompressedSize);
                    writer.Write(texture32.Length);
                    writer.Write(texture32);

                    writer.Write(texture16UncompressedSize);
                    writer.Write(texture16.Length);
                    writer.Write(texture16);

                    writer.Write(textureMiscUncompressedSize);
                    writer.Write(textureMisc.Length);
                    writer.Write(textureMisc);

                    writer.Write((ushort)_level.Settings.Tr5LaraType);
                    writer.Write((ushort)_level.Settings.Tr5WeatherType);

                    for (var i = 0; i < 28; i++)
                    {
                        writer.Write((byte)0);
                    }

                    // In TR5 geometry data is not compressed
                    writer.Write(geometryDataUncompressedSize);
                    writer.Write(geometryDataUncompressedSize);
                    writer.Write(geometryData);

                    ReportProgress(98, "Writing WAVE sounds");
                    WriteSoundData(writer);

                    // Write extra data
                    _volumeScripts = new List <VolumeScriptInstance>();

                    using (var ms = new MemoryStream())
                    {
                        var chunkIO = new ChunkWriter(new byte[] { 0x54, 0x52, 0x35, 0x4D }, new BinaryWriterFast(ms));

                        int currRoom = 0;
                        foreach (var r in _level.Rooms.Where(r => r != null))
                        {
                            // Add further extra data conditions here, otherwise compiler will skip this room altogether
                            if (r.Volumes.Count() > 0)
                            {
                                currRoom++;
                            }
                            else
                            {
                                currRoom++;
                                continue;
                            }

                            using (var extraRoomDataChunk = chunkIO.WriteChunk(Tr5MainExtraRoomData))
                            {
                                // First and only param after signature is internal room number
                                chunkIO.Raw.Write(currRoom);

                                using (var volumeListChunk = chunkIO.WriteChunk(Tr5MainChunkVolumeList))
                                {
                                    var trRoom = _tempRooms[r];

                                    foreach (var vol in r.Volumes)
                                    {
                                        using (var volumeChunk = chunkIO.WriteChunk(Tr5MainChunkVolume))
                                        {
                                            int scriptIndex = 0;
                                            if (_volumeScripts.Contains(vol.Scripts))
                                            {
                                                scriptIndex = _volumeScripts.IndexOf(vol.Scripts);
                                            }
                                            else
                                            {
                                                _volumeScripts.Add(vol.Scripts);
                                                scriptIndex = _volumeScripts.Count - 1;
                                            }

                                            // FIXME is it needed?
                                            int add = 0;
                                            if (vol is BoxVolumeInstance)
                                            {
                                                add = (int)((vol as BoxVolumeInstance).Size.Y / 2.0f);
                                            }

                                            var X = (int)Math.Round(trRoom.Info.X + vol.Position.X);
                                            var Y = (int)-Math.Round(r.WorldPos.Y + vol.Position.Y + add);
                                            var Z = (int)Math.Round(trRoom.Info.Z + vol.Position.Z);

                                            if (vol is BoxVolumeInstance)
                                            {
                                                chunkIO.Raw.Write(0);
                                            }
                                            else if (vol is SphereVolumeInstance)
                                            {
                                                chunkIO.Raw.Write(1);
                                            }
                                            else if (vol is PrismVolumeInstance)
                                            {
                                                chunkIO.Raw.Write(2);
                                            }

                                            chunkIO.Raw.Write(X);
                                            chunkIO.Raw.Write(Y);
                                            chunkIO.Raw.Write(Z);
                                            chunkIO.Raw.Write((short)vol.Activators);
                                            chunkIO.Raw.Write(scriptIndex);

                                            if (vol is BoxVolumeInstance)
                                            {
                                                var bv   = (BoxVolumeInstance)vol;
                                                var min  = vol.Position - (bv.Size / 2.0f);
                                                var max  = vol.Position + (bv.Size / 2.0f);
                                                var rotY = (ushort)Math.Max(0, Math.Min(ushort.MaxValue,
                                                                                        Math.Round(bv.RotationY * (65536.0 / 360.0))));
                                                var rotX = (ushort)Math.Max(0, Math.Min(ushort.MaxValue,
                                                                                        Math.Round(bv.RotationX * (65536.0 / 360.0))));

                                                chunkIO.Raw.Write(rotY);
                                                chunkIO.Raw.Write(rotX);
                                                chunkIO.Raw.Write((short)min.X);
                                                chunkIO.Raw.Write((short)min.Y);
                                                chunkIO.Raw.Write((short)min.Z);
                                                chunkIO.Raw.Write((short)max.X);
                                                chunkIO.Raw.Write((short)max.Y);
                                                chunkIO.Raw.Write((short)max.Z);
                                            }
                                            else if (vol is SphereVolumeInstance)
                                            {
                                                chunkIO.Raw.Write((vol as SphereVolumeInstance).Size);
                                            }
                                            else if (vol is PrismVolumeInstance)
                                            {
                                                var pv = (PrismVolumeInstance)vol;
                                                chunkIO.Raw.Write(pv.RotationY);
                                                chunkIO.Raw.Write(pv.Size);
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        /*
                         * using (var extraDataChunk = chunkIO.WriteChunk(Tr5MainExtraData))
                         * {
                         *  using (var volScriptListChunk = chunkIO.WriteChunk(Tr5MainChunkVolumeScriptList))
                         *  {
                         *      for (int i = 0; i < _volumeScripts.Count; i++)
                         *      {
                         *          var script = _volumeScripts[i];
                         *          using (var volScriptChunk = chunkIO.WriteChunk(Tr5MainChunkVolumeScript))
                         *          {
                         *              chunkIO.Raw.WriteStringUTF8(script.Name);
                         *
                         *              string onEnter = string.Empty;
                         *              string onInside = string.Empty;
                         *              string onLeave = string.Empty;
                         *
                         *              if (script.OnEnter.Trim().Length > 0)
                         *                  onEnter = "volscripts[" + i + "].OnEnter  = function(activator) \n" +
                         *                              _indent + script.OnEnter.Replace("\n", "\n" + _indent) + "\n" + "end;";
                         *
                         *              if (script.OnInside.Trim().Length > 0)
                         *                  onInside = "volscripts[" + i + "].OnInside = function(activator) \n" +
                         *                              _indent + script.OnInside.Replace("\n", "\n" + _indent) + "\n" + "end;";
                         *
                         *              if (script.OnLeave.Trim().Length > 0)
                         *                  onLeave  = "volscripts[" + i + "].OnLeave = function(activator) \n" +
                         *                              _indent + script.OnLeave.Replace("\n", "\n" + _indent) + "\n" + "end;";
                         *
                         *              string functionCode =
                         *                  onEnter  + (string.IsNullOrEmpty(onEnter)  ? string.Empty : "\n\n") +
                         *                  onInside + (string.IsNullOrEmpty(onInside) ? string.Empty : "\n\n") +
                         *                  onLeave  + (string.IsNullOrEmpty(onLeave)  ? string.Empty : "\n\n") ;
                         *
                         *              chunkIO.Raw.WriteStringUTF8(functionCode);
                         *          }
                         *      }
                         *  }
                         *
                         *  using (var chunkLuaIds = chunkIO.WriteChunk(Tr5MainChunkLuaIds))
                         *  {
                         *      for (int i = 0; i < _luaIdToItems.Count; i++)
                         *      {
                         *          chunkIO.WriteChunk(Tr5MainChunkLuaId, () =>
                         *          {
                         *              chunkIO.Raw.Write(_luaIdToItems.ElementAt(i).Key);
                         *              chunkIO.Raw.Write(_luaIdToItems.ElementAt(i).Value);
                         *          });
                         *      }
                         *  }
                         * }
                         */

                        chunkIO.Raw.Flush();

                        writer.Write((int)(ms.Length + 4));
                        writer.Write((int)(ms.Length + 4));
                        writer.Write(ms.ToArray(), 0, (int)ms.Length);
                        writer.Write((int)0);
                    }

                    ReportProgress(99, "Done");
                }
            }
        }
Esempio n. 6
0
        private void WriteLevelTr4(string ngVersion = null)
        {
            // Now begin to compile the geometry block in a MemoryStream
            byte[] geometryDataBuffer;
            using (var geometryDataStream = new MemoryStream())
            {
                var writer = new BinaryWriterEx(geometryDataStream); // Don't dispose
                ReportProgress(80, "Writing geometry data to memory buffer");

                const int filler = 0;
                writer.Write(filler);

                var numRooms = (ushort)_level.Rooms.Count(r => r != null);
                writer.Write(numRooms);

                foreach (var r in _level.Rooms.Where(r => r != null))
                {
                    _tempRooms[r].WriteTr4(writer);
                }

                // Write floordata
                var numFloorData = (uint)_floorData.Count;
                writer.Write(numFloorData);
                writer.WriteBlockArray(_floorData);

                // Write meshes
                var offset = writer.BaseStream.Position;

                const int numMeshData = 0;
                writer.Write(numMeshData);
                var totalMeshSize = 0;

                for (var i = 0; i < _meshes.Count; i++)
                {
                    var meshSize = _meshes[i].WriteTr4AndTr5(writer);
                    totalMeshSize += (int)meshSize;
                }

                var offset2 = writer.BaseStream.Position;
                // ReSharper disable once SuggestVarOrType_BuiltInTypes
                uint meshDataSize = (uint)((offset2 - offset - 4) / 2);

                // Save the size of the meshes
                writer.BaseStream.Seek(offset, SeekOrigin.Begin);
                writer.Write(meshDataSize);
                writer.BaseStream.Seek(offset2, SeekOrigin.Begin);

                // Write mesh pointers
                writer.Write((uint)_meshPointers.Count);
                writer.WriteBlockArray(_meshPointers);

                // Write animations' data
                writer.Write((uint)_animations.Count);
                foreach (var anim in _animations)
                {
                    anim.Write(writer, _level);
                }

                writer.Write((uint)_stateChanges.Count);
                writer.WriteBlockArray(_stateChanges);

                writer.Write((uint)_animDispatches.Count);
                writer.WriteBlockArray(_animDispatches);

                writer.Write((uint)_animCommands.Count);
                writer.WriteBlockArray(_animCommands);

                writer.Write((uint)_meshTrees.Count);
                writer.WriteBlockArray(_meshTrees);

                writer.Write((uint)_frames.Count);
                writer.WriteBlockArray(_frames);

                writer.Write((uint)_moveables.Count);
                writer.WriteBlockArray(_moveables);

                writer.Write((uint)_staticMeshes.Count);
                writer.WriteBlockArray(_staticMeshes);

                // SPR block
                writer.WriteBlockArray(new byte[] { 0x53, 0x50, 0x52 });

                writer.Write((uint)_spriteTextures.Count);
                writer.WriteBlockArray(_spriteTextures);

                writer.Write((uint)_spriteSequences.Count);
                writer.WriteBlockArray(_spriteSequences);

                // Write camera, flyby and sound sources
                writer.Write((uint)_cameras.Count);
                writer.WriteBlockArray(_cameras);

                writer.Write((uint)_flyByCameras.Count);
                writer.WriteBlockArray(_flyByCameras);

                writer.Write((uint)_soundSources.Count);
                writer.WriteBlockArray(_soundSources);

                // Write pathfinding data
                writer.Write((uint)_boxes.Length);
                writer.WriteBlockArray(_boxes);

                writer.Write((uint)_overlaps.Length);
                writer.WriteBlockArray(_overlaps);

                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone1_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone2_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone3_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone4_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].FlyZone_Normal);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone1_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone2_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone3_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].GroundZone4_Alternate);
                }
                for (var i = 0; i < _boxes.Length; i++)
                {
                    writer.Write(_zones[i].FlyZone_Alternate);
                }

                // Write animated textures
                _textureInfoManager.WriteAnimatedTextures(writer);

                // Write object textures
                writer.Write(checked ((byte)_textureInfoManager.UvRotateCount));
                writer.Write(new byte[] { 0x54, 0x45, 0x58 });

                _textureInfoManager.WriteTextureInfos(writer, _level);

                // Write items and AI objects
                writer.Write((uint)_items.Count);
                writer.WriteBlockArray(_items);

                writer.Write((uint)_aiItems.Count);
                writer.WriteBlockArray(_aiItems);

                // Write sound meta data
                PrepareSoundsData();
                WriteSoundMetadata(writer);

                // Finish it
                writer.Write((short)0);
                writer.Write((short)0);
                writer.Write((short)0);

                geometryDataBuffer = geometryDataStream.ToArray();
            }

            using (var writer = new BinaryWriterEx(new FileStream(_dest, FileMode.Create, FileAccess.Write, FileShare.None)))
            {
                ReportProgress(90, "Writing final level");
                writer.WriteBlockArray(new byte[] { 0x54, 0x52, 0x34, 0x00 });

                ReportProgress(91, "Writing textures");

                writer.Write((ushort)_textureInfoManager.NumRoomPages);
                writer.Write((ushort)_textureInfoManager.NumObjectsPages);
                // Bump map pages must be multiplied by 2 or tile index will be wrong
                writer.Write((ushort)(_textureInfoManager.NumBumpPages * 2));

                // Compress data
                ReportProgress(95, "Compressing data");

                byte[] texture32 = null;
                int    texture32UncompressedSize = -1;
                byte[] texture16 = null;
                int    texture16UncompressedSize = -1;
                byte[] textureMisc = null;
                int    textureMiscUncompressedSize = -1;
                byte[] geometryData = null;
                int    geometryDataUncompressedSize = -1;

                using (Task Texture32task = Task.Factory.StartNew(() =>
                {
                    texture32 = ZLib.CompressData(_texture32Data);
                    texture32UncompressedSize = _texture32Data.Length;
                }))
                    using (Task Texture16task = Task.Factory.StartNew(() =>
                    {
                        byte[] texture16Data = PackTextureMap32To16Bit(_texture32Data, _level.Settings);
                        texture16 = ZLib.CompressData(texture16Data);
                        texture16UncompressedSize = texture16Data.Length;
                    }))
                        using (Task textureMiscTask = Task.Factory.StartNew(() =>
                        {
                            Stream textureMiscData = PrepareFontAndSkyTexture();
                            textureMisc = ZLib.CompressData(textureMiscData);
                            textureMiscUncompressedSize = (int)textureMiscData.Length;
                        }))
                            using (Task GeometryDataTask = Task.Factory.StartNew(() =>
                            {
                                geometryData = ZLib.CompressData(geometryDataBuffer);
                                geometryDataUncompressedSize = geometryDataBuffer.Length;
                            }))
                                Task.WaitAll(Texture32task, Texture16task, textureMiscTask, GeometryDataTask);

                // Write data
                ReportProgress(96, "Writing compressed data to file.");

                writer.Write(texture32UncompressedSize);
                writer.Write(texture32.Length);
                writer.Write(texture32);

                writer.Write(texture16UncompressedSize);
                writer.Write(texture16.Length);
                writer.Write(texture16);

                writer.Write(textureMiscUncompressedSize);
                writer.Write(textureMisc.Length);
                writer.Write(textureMisc);

                writer.Write(geometryDataUncompressedSize);
                writer.Write(geometryData.Length);
                writer.Write(geometryData);

                ReportProgress(97, "Writing WAVE sounds");
                WriteSoundData(writer);

                // Write NG header
                if (!string.IsNullOrEmpty(ngVersion))
                {
                    ReportProgress(98, "Writing NG header");
                    WriteNgHeader(writer, ngVersion);
                }

                ReportProgress(99, "Done");
            }
        }