package ua.com.minersstudios.whomine.util.misc;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import lombok.RequiredArgsConstructor;
/**
* Interface to communicate with region file in one way only.
*/
final class WorldRegion extends RandomAccessFile
{
WorldRegion(File file, String mode) throws FileNotFoundException
{
super(file, mode);
}
WorldRegion(String name, String mode) throws FileNotFoundException
{
super(name, mode);
}
private final int fourKiBSector = 4096;
// all in bytes.
private final int offsetSize = 3;
private final int timestampSize = 4;
private final int lengthSize = 4;
private final int sectorSizeStartsAfter = 3;
private final int compressionTypeStartsAfter = 4;
private final int dataStartsAfter = 5;
/// Header
// took from Minecraft wiki.
private int locateChunkInsideHeader(final int x, final int z) { return 4 * ((x & 31) + (z & 31) * 32); }
/**
* Read given chunk's offset inside the region file from the header of the file itself.
* @param x chunk longitude
* @param z chunk latitude
* @return chunk offset in 4 KiB sectors
*/
int getChunkOffset(final int x, final int z) throws IOException
{
byte[] rawChunkOffset = new byte[offsetSize];
readFully(rawChunkOffset, rawChunkOffset.length, locateChunkInsideHeader(x, z));
return ByteBuffer.allocate(rawChunkOffset.length).order(ByteOrder.BIG_ENDIAN).put(rawChunkOffset).getInt();
}
/**
* Read given chunk's disk space size from the region file.
* @param x chunk longitude
* @param z chunk latitude
* @return chunk disk space size in 4 KiB sectors
*/
byte getChunkSectorCount(final int x, final int z) throws IOException
{
final long previousFilePointer = getFilePointer();
seek(locateChunkInsideHeader(x, z) + sectorSizeStartsAfter);
byte chunkSectorCount = readByte();
seek(previousFilePointer);
return chunkSectorCount;
}
/**
* Read given chunk's modification date from the region file.
* @param x chunk longitude
* @param z chunk latitude
* @return chunk modification date in epoch seconds
*/
int getChunkModificationDate(final int x, final int z) throws IOException
{
byte[] rawChunkModificationDate = new byte[timestampSize];
readFully(rawChunkModificationDate, rawChunkModificationDate.length, fourKiBSector | locateChunkInsideHeader(x, z));
return ByteBuffer.allocate(rawChunkModificationDate.length).order(ByteOrder.BIG_ENDIAN).put(rawChunkModificationDate).getInt();
}
/// Data
private int getChunkRemainingLength(final int x, final int z) throws IOException
{
byte[] rawChunkRemainingSize = new byte[lengthSize];
readFully(rawChunkRemainingSize, rawChunkRemainingSize.length, getChunkOffset(x, z) * fourKiBSector);
return ByteBuffer.allocate(rawChunkRemainingSize.length).order(ByteOrder.BIG_ENDIAN).put(rawChunkRemainingSize).getInt();
}
/**
* Read given chunk's compression type from the region file.
* @param x chunk longitude
* @param z chunk latitude
* @return chunk compression scheme
*/
CompressionType getChunkCompressionType(final int x, final int z) throws IOException
{
final long previousFilePointer = getFilePointer();
seek(getChunkOffset(x, z) * fourKiBSector + compressionTypeStartsAfter);
byte rawChunkCompressionType = readByte();
seek(previousFilePointer);
return CompressionType.valueOf(rawChunkCompressionType);
}
/**
* Read given chunk's raw data from the region file.
* @param x chunk longitude
* @param z chunk latitude
* @return chunk data as is
*/
byte[] getChunkDataPadded(final int x, final int z) throws IOException
{
byte[] rawChunkData = new byte[getChunkSectorCount(x, z) * fourKiBSector];
readFully(rawChunkData, rawChunkData.length, getChunkOffset(x, z) * fourKiBSector);
return rawChunkData;
}
/**
* Read given chunk's compressed data from the region file.
* @param x chunk longitude
* @param z chunk latitude
* @return chunk data in NBT data
*/
byte[] getChunkData(final int x, final int z) throws IOException
{
assert getChunkCompressionType(x, z) != CompressionType.External;
byte[] rawChunkData = new byte[getChunkRemainingLength(x, z) - 1];
readFully(rawChunkData, rawChunkData.length, getChunkOffset(x, z) * fourKiBSector + dataStartsAfter);
return rawChunkData;
}
/**
* Characterises compression type being used in the region file.
* Where External means that chunk's data is moved into a separate file.
*/
@RequiredArgsConstructor
enum CompressionType
{
RFC1952(1),
RFC1950(2),
None(3),
External(129);
private final int value;
public static CompressionType valueOf(final int value)
{
return value < External.ordinal() ? values()[value] : External;
}
}
}