public static CachedChunk LoadChunk(WorldServer world, Vector3i chunkPos)
        {
            var region         = ChunkToRegion(chunkPos);
            var regionFilename = Path.Combine(WorldFolder, RegionsFolder, GetRegionFilename(region));
            var indexFile      = new FileInfo(regionFilename + RegionIndexExt);
            var dataFile       = new FileInfo(regionFilename + RegionDataExt);

            if (!indexFile.Exists || !dataFile.Exists)
            {
                return(null);
            }

            //Get chunk data position and length
            int chunkDataPosition, chunkDataLength;
            var chunkIndexPosition = GetChunkIndexPosition(chunkPos);

            lock (IndexLockObject)
            {
                var chunkIndexData = GetIndexData(region, indexFile);
                chunkDataPosition = BitConverter.ToInt32(chunkIndexData, chunkIndexPosition);
                chunkDataLength   = BitConverter.ToInt32(chunkIndexData, chunkIndexPosition + sizeof(int));
            }

            if (chunkDataPosition == IndexFileNull || chunkDataLength == IndexFileNull)
            {
                return(null);
            }
            //Read chunk data
            byte[] chunkData;

            lock (DataLockObject)
            {
                using (var reader = new BinaryReader(dataFile.OpenRead()))
                {
                    reader.BaseStream.Seek(chunkDataPosition, SeekOrigin.Begin);
                    chunkData = CompressionHelper.DecompressBytes(reader.ReadBytes(chunkDataLength));
                }
            }

            using (var reader = new BinaryReader(new MemoryStream(chunkData)))
            {
                return(new CachedChunk(world, chunkPos, reader));
            }
        }
        private static byte[] GetIndexData(Vector3i region, FileInfo indexFile)
        {
            foreach (var cachedIndexData in CachedIndexDatas)
            {
                if (cachedIndexData.Item1 == region)
                {
                    return(cachedIndexData.Item2);
                }
            }

            var data = CompressionHelper.DecompressBytes(File.ReadAllBytes(indexFile.FullName));

            CachedIndexDatas.Add(new Tuple <Vector3i, byte[]>(region, data));

            if (CachedIndexDatas.Count > MaxCachedIndexDatas)
            {
                CachedIndexDatas.RemoveAt(0);
            }

            return(data);
        }
        public static void SaveChunk(Chunk chunk)
        {
            if (!chunk.NeedsSaving)
            {
                return;
            }

            var region         = ChunkToRegion(chunk.Position);
            var regionFilename = Path.Combine(WorldFolder, RegionsFolder, GetRegionFilename(region));
            var indexFile      = new FileInfo(regionFilename + RegionIndexExt);
            var dataFile       = new FileInfo(regionFilename + RegionDataExt);

            // ReSharper disable once PossibleNullReferenceException
            indexFile.Directory.Create();

            lock (IndexLockObject)
            {
                if (!indexFile.Exists)
                {
                    using (var stream = new GZipStream(indexFile.Create(), CompressionMode.Compress))
                    {
                        var buffer = new byte[1024];
                        for (var i = 0; i < buffer.Length; i += sizeof(int))
                        {
                            BitConverter.GetBytes(IndexFileNull).CopyTo(buffer, i);
                        }

                        for (var i = 0; i < IndexFileLength / buffer.Length; i++)
                        {
                            stream.Write(buffer, 0, buffer.Length);
                        }
                    }
                }
            }

            //Compress chunk data
            byte[] compressedChunkData;
            using (var memoryStream = new MemoryStream())
            {
                using (var writer = new BinaryWriter(memoryStream))
                {
                    chunk.Write(writer);
                }

                compressedChunkData = CompressionHelper.CompressBytes(memoryStream.ToArray());
            }

            var chunkDataPosition = dataFile.Exists ? (int)dataFile.Length : 0;
            var chunkDataLength   = compressedChunkData.Length;

            //Append chunk to data file
            lock (DataLockObject)
            {
                using (var stream = dataFile.Open(FileMode.Append, FileAccess.Write))
                {
                    stream.Write(compressedChunkData, 0, compressedChunkData.Length);
                }
            }

            //Update chunk index
            var chunkIndexPosition = GetChunkIndexPosition(chunk.Position);

            lock (IndexLockObject)
            {
                var chunkIndexData = GetIndexData(region, indexFile);
                Array.Copy(BitConverter.GetBytes(chunkDataPosition), 0, chunkIndexData, chunkIndexPosition, sizeof(int));
                Array.Copy(BitConverter.GetBytes(chunkDataLength), 0, chunkIndexData, chunkIndexPosition + sizeof(int),
                           sizeof(int));
                File.WriteAllBytes(indexFile.FullName, CompressionHelper.CompressBytes(chunkIndexData));
            }

            chunk.NeedsSaving = false;
        }