Java tutorial
/* * This file is part of Vanilla. * * Copyright (c) 2011 Spout LLC <http://www.spout.org/> * Vanilla is licensed under the Spout License Version 1. * * Vanilla is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * In addition, 180 days after any changes are published, you can use the * software, incorporating those changes, under the terms of the MIT license, * as described in the Spout License Version 1. * * Vanilla is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License, * the MIT license and the Spout License Version 1 along with this program. * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public * License and see <http://spout.in/licensev1> for the full license, including * the MIT license. */ package org.spout.vanilla.protocol.codec.world.chunk; import java.io.IOException; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.spout.api.geo.cuboid.Chunk; import org.spout.api.protocol.MessageCodec; import org.spout.api.protocol.Session; import org.spout.api.protocol.reposition.NullRepositionManager; import org.spout.vanilla.protocol.VanillaProtocol; import org.spout.vanilla.protocol.msg.world.chunk.ChunkDataMessage; public final class ChunkDataCodec extends MessageCodec<ChunkDataMessage> { private static final int COMPRESSION_LEVEL = Deflater.BEST_SPEED; private static final int MAX_SECTIONS = 16; private final byte[] UNLOAD_COMPRESSED = { 0x78, (byte) 0x9C, 0x63, 0x64, 0x1C, (byte) 0xD9, 0x00, 0x00, (byte) 0x81, (byte) 0x80, 0x01, 0x01 }; //Fake compressed data, client expects this when unloading public ChunkDataCodec() { super(ChunkDataMessage.class, 0x33); } @Override public ChunkDataMessage decode(ByteBuf buffer) throws IOException { int x = buffer.readInt(); int z = buffer.readInt(); boolean contiguous = buffer.readByte() == 1; short primaryBitMap = buffer.readShort(); short addBitMap = buffer.readShort(); int compressedSize = buffer.readInt(); byte[] compressedData = new byte[compressedSize]; buffer.readBytes(compressedData); boolean[] hasAdditionalData = new boolean[MAX_SECTIONS]; byte[][] data = new byte[MAX_SECTIONS][]; int size = 0; for (int i = 0; i < MAX_SECTIONS; ++i) { if ((primaryBitMap & 1 << i) != 0) { // This chunk exists! Let's initialize the data for it. int sectionSize = Chunk.BLOCKS.HALF_VOLUME * 5; if ((addBitMap & 1 << i) != 0) { hasAdditionalData[i] = true; sectionSize += Chunk.BLOCKS.HALF_VOLUME; } data[i] = new byte[sectionSize]; size += sectionSize; } } if (contiguous) { size += Chunk.BLOCKS.AREA; } byte[] uncompressedData = new byte[size]; Inflater inflater = new Inflater(); inflater.setInput(compressedData); inflater.getRemaining(); int index = 0; try { while (index < uncompressedData.length) { int uncompressed = inflater.inflate(uncompressedData, index, uncompressedData.length - index); index += uncompressed; if (uncompressed == 0) { break; } } if (index != uncompressedData.length) { throw new IOException("Not all bytes uncompressed."); } } catch (DataFormatException e) { e.printStackTrace(); throw new IOException("Bad compressed data."); } finally { inflater.end(); } size = 0; // TODO - fix this total hack size = readSectionData(uncompressedData, size, data, 0, 4096); size = readSectionData(uncompressedData, size, data, 2 * 2048, 2048); size = readSectionData(uncompressedData, size, data, 3 * 2048, 2048); size = readSectionData(uncompressedData, size, data, 4 * 2048, 2048); /*size = 0; for (byte[] sectionData : data) { if (sectionData != null && sectionData.length + size < uncompressedData.length) { System.arraycopy(uncompressedData, size, sectionData, 0, sectionData.length); size += sectionData.length; } }*/ byte[] biomeData = new byte[Chunk.BLOCKS.AREA]; if (contiguous) { System.arraycopy(uncompressedData, size, biomeData, 0, biomeData.length); size += biomeData.length; } return new ChunkDataMessage(x, z, contiguous, hasAdditionalData, data, biomeData, null, NullRepositionManager.getInstance()); } @Override public ByteBuf encode(ChunkDataMessage message) throws IOException { ByteBuf buffer = Unpooled.buffer(); buffer.writeInt(message.getX()); buffer.writeInt(message.getZ()); buffer.writeByte(message.isContiguous() ? 1 : 0); if (message.shouldUnload()) { buffer.writeShort(0); buffer.writeShort(0); buffer.writeInt(UNLOAD_COMPRESSED.length); buffer.writeBytes(UNLOAD_COMPRESSED); return buffer; } short sectionsSentBitmap = 0; short additionalDataBitMap = 0; byte[][] data = message.getData(); int uncompressedSize = 0; for (int i = 0; i < MAX_SECTIONS; ++i) { if (data[i] != null) { // This chunk exists! Let's initialize the data for it. sectionsSentBitmap |= 1 << i; if (message.hasAdditionalData()[i]) { additionalDataBitMap |= 1 << i; } uncompressedSize += data[i].length; } } if (message.isContiguous()) { uncompressedSize += message.getBiomeData().length; } buffer.writeShort(sectionsSentBitmap); buffer.writeShort(additionalDataBitMap); byte[] uncompressedData = new byte[uncompressedSize]; int index = 0; // TODO - fix this total hack index = writeSectionData(data, 0, uncompressedData, index, 4096); index = writeSectionData(data, 2 * 2048, uncompressedData, index, 2048); index = writeSectionData(data, 3 * 2048, uncompressedData, index, 2048); index = writeSectionData(data, 4 * 2048, uncompressedData, index, 2048); if (message.isContiguous()) { System.arraycopy(message.getBiomeData(), 0, uncompressedData, index, message.getBiomeData().length); index += message.getBiomeData().length; } Session session = message.getSession(); if (session != null) { uncompressedData = message.getSession().getDataMap().get(VanillaProtocol.CHUNK_NET_CACHE) .handle(uncompressedData); } byte[] compressedData = new byte[uncompressedSize >> 2]; Deflater deflater = new Deflater(COMPRESSION_LEVEL); deflater.setInput(uncompressedData); deflater.finish(); int compressed; try { compressed = deflater.deflate(compressedData); if (compressed == 0) { throw new IOException("No compressed data found"); } else if (compressed == compressedData.length) { boolean done = false; while (!done) { byte[] newCompressedData = new byte[1 + compressedData.length + (compressedData.length >> 1)]; System.arraycopy(compressedData, 0, newCompressedData, 0, compressedData.length); compressedData = newCompressedData; compressed += deflater.deflate(compressedData, compressed, compressedData.length - compressed); done = compressed < compressedData.length; } } } finally { deflater.end(); } buffer.writeInt(compressed); buffer.writeBytes(compressedData, 0, compressed); return buffer; } private int readSectionData(byte[] data, int off, byte[][] target, int targetOff, int len) { for (byte[] sectionTarget : target) { if (sectionTarget != null) { for (int i = targetOff; i < targetOff + len && i < sectionTarget.length; ++i) { sectionTarget[i] = data[off++]; } } } return off; } private int writeSectionData(byte[][] data, int off, byte[] target, int targetOff, int len) { for (byte[] sectionData : data) { if (sectionData != null) { int j = off; for (int i = 0; i < len; i++) { target[targetOff++] = sectionData[j++]; } } } return targetOff; } }