Skip to content
This repository has been archived by the owner on Dec 28, 2020. It is now read-only.

Commit

Permalink
Some progress on #20
Browse files Browse the repository at this point in the history
  • Loading branch information
LB-- committed Sep 9, 2014
1 parent 9761670 commit 0201278
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 197 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.lb_stuff.mcmodify.location;

import com.lb_stuff.mcmodify.minecraft.Region;

/**
* Location of a Chunk in a Dimension.
*/
public class LocChunkInDimension
{
public final int x;
public final int z;
public LocChunkInDimension(int cx, int cz)
{
x = cx;
z = cz;
}

/**
* Returns the location of this chunk in a region.
* @return The location of this chunk in a region.
*/
public LocChunkInRegion getLocInRegion()
{
return new LocChunkInRegion(x - x/Region.CHUNK_X_SIZE*Region.CHUNK_X_SIZE, z - z/Region.CHUNK_Z_SIZE*Region.CHUNK_Z_SIZE);
}
/**
* Returns the region this chunk is in.
* @return The region this chunk is in.
*/
public LocRegionInDimension getRegionLoc()
{
return new LocRegionInDimension(x/Region.CHUNK_X_SIZE, z/Region.CHUNK_Z_SIZE);
}
}
31 changes: 31 additions & 0 deletions src/main/java/com/lb_stuff/mcmodify/location/LocChunkInRegion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.lb_stuff.mcmodify.location;

import com.lb_stuff.mcmodify.minecraft.Region;

/**
* Location of a Chunk in a Region.
*/
public class LocChunkInRegion
{
public final int x;
public final int z;
public LocChunkInRegion(int cx, int cz)
{
if(cx < 0 || cx >= 32 || cz < 0 || cz >= 32)
{
throw new IllegalArgumentException("Invalid chunk location ("+cx+","+cz+") in region");
}
x = cx;
z = cz;
}

/**
* Returns the location of this chunk in a dimension.
* @param loc The region this chunk is in.
* @return The location of this chunk in a dimension.
*/
public LocChunkInDimension getLocInDimension(LocRegionInDimension loc)
{
return new LocChunkInDimension(loc.x*Region.CHUNK_X_SIZE + x, loc.z*Region.CHUNK_Z_SIZE + z);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.lb_stuff.mcmodify.location;

import com.lb_stuff.mcmodify.minecraft.Region;

/**
* Location of a Region in a Dimension.
*/
public class LocRegionInDimension
{
public final int x;
public final int z;
public LocRegionInDimension(int cx, int cz)
{
x = cx;
z = cz;
}

/**
* Returns whether this region contains the given chunk.
* @param loc The chunk being tested.
* @return Whether this region contains the given chunk.
*/
public boolean containsChunk(LocChunkInDimension loc)
{
return
(x+0)*Region.CHUNK_X_SIZE <= loc.x &&
(x+1)*Region.CHUNK_X_SIZE > loc.x &&
(z+0)*Region.CHUNK_Z_SIZE <= loc.z &&
(z+1)*Region.CHUNK_Z_SIZE > loc.z;
}
}
178 changes: 42 additions & 136 deletions src/main/java/com/lb_stuff/mcmodify/minecraft/FileRegion.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
package com.lb_stuff.mcmodify.minecraft;

import com.lb_stuff.mcmodify.nbt.FormatException;
import com.lb_stuff.mcmodify.location.LocChunkInRegion;
import com.lb_stuff.mcmodify.nbt.Tag;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;

/**
* Region file reader/writer
* @see <a href="http://minecraft.gamepedia.com/Region_file_format">Region file format</a> on the Minecraft Wiki
*/
public class FileRegion extends Region
{
@Deprecated
private static final int NUMBER_OF_HEADER_SECTORS = 2;

/**
* The Region File.
*/
Expand All @@ -45,179 +36,94 @@ public FileRegion(File mca) throws IOException
rf.createNewFile();
try(FileOutputStream region = new FileOutputStream(rf))
{
region.write(new byte[8192]);
}
}
}

/**
* Returns the offset and sector count for a chunk.
* @param region The RandomAccessFile to read the data from.
* @param index The chunk index, pre-calculated.
* @return The offset and sector count for a chunk. The offset is the key and the sector count is the value.
* @throws IOException if the input operation throws an exception.
*/
@Deprecated
private static Entry<Integer, Integer> sectorOffset(RandomAccessFile region, int index) throws IOException
{
region.getChannel().position(4*index);
byte[] buf = new byte[4];
region.readFully(buf);
int offset;
try(DataInputStream dis = new DataInputStream(new ByteArrayInputStream(new byte[]{0, buf[0], buf[1], buf[2]})))
{
offset = dis.readInt()-NUMBER_OF_HEADER_SECTORS;
}
int sectors = buf[3];
return new SimpleEntry<>(offset, sectors);
}
/**
* Writes the offset and sector count for a chunk.
* @param region The RandomAccessFile to write the data to.
* @param index The chunk index, pre-computed.
* @param offset The offset of the chunk.
* @param sectors The sector count of the chunk.
* @throws IOException if the output operation throws an exception.
*/
@Deprecated
private static void sectorOffset(RandomAccessFile region, int index, int offset, int sectors) throws IOException
{
try(ByteArrayOutputStream baos = new ByteArrayOutputStream(4))
{
try(DataOutputStream dos = new DataOutputStream(baos))
{
dos.writeInt(offset+NUMBER_OF_HEADER_SECTORS);
region.write(new byte[(int)CHUNK_SECTORS_START]);
}
byte[] temp = baos.toByteArray();
ByteBuffer buf = ByteBuffer.allocate(4);
buf.put(new byte[]{temp[1], temp[2], temp[3], (byte)sectors});
region.getChannel().position(4*index);
region.write(buf.array());
}
}

/**
* Reads a chunk from the region file.
* @param x The X chunk coordinate of the chunk.
* @param z The Z chunk coordinate of the chunk.
* @return The read chunk, or null if the chunk does not exist.
* @throws FormatException if the read chunk is invalid.
* @throws IOException if an input operation throws an exception.
*/
@Override
public Chunk getChunk(int x, int z) throws FormatException, IOException
public Chunk getChunk(LocChunkInRegion pos) throws IOException
{
try(RandomAccessFile region = new RandomAccessFile(rf, "r"))
{
Entry<Integer, Integer> pair = sectorOffset(region, ((x%32) + (z%32)*32));
int offset = pair.getKey();
int sectors = pair.getValue();
if(offset != -NUMBER_OF_HEADER_SECTORS && sectors != 0)
region.seek(LOCATIONS_SECTOR_START + chunkIndex(pos)*4);
LocationPair loc = new LocationPair(region);
if(loc.offset > 0 && loc.size > 0)
{
region.seek(CHUNK_SECTORS_START+offset*SECTOR_BYTES);
int length = region.readInt()-1;
CompressionScheme compression = CompressionScheme.fromId(region.readByte());
byte[] chunk = new byte[length];
region.seek(loc.offset);
int length = region.readInt();
CompressionScheme compressed = CompressionScheme.fromId(region.readByte());
byte[] chunk = new byte[length-1];
region.readFully(chunk);
try(InputStream is = compression.getInputStream(new ByteArrayInputStream(chunk)))
try(InputStream is = compressed.getInputStream(new ByteArrayInputStream(chunk)))
{
return new Chunk((Tag.Compound)Tag.deserialize(is));
}
}
}
return null;
}
/**
* Reads a chunk timestamp from the region file.
* @param x The X chunk coordinate of the chunk.
* @param z The Z chunk coordinate of the chunk.
* @return The read chunk timestamp.
* @throws IOException if an input operation throws an exception.
*/
@Override
public int getTimestamp(int x, int z) throws IOException
public int getTimestamp(LocChunkInRegion pos) throws IOException
{
try(FileInputStream region = new FileInputStream(rf))
try(RandomAccessFile region = new RandomAccessFile(rf, "r"))
{
region.getChannel().position(TIMESTAMPS_SECTOR_START+4*((x%32) + (z%32)*32));
try(DataInputStream dis = new DataInputStream(region))
{
return dis.readInt();
}
region.seek(TIMESTAMPS_SECTOR_START + chunkIndex(pos)*4);
return region.readInt();
}
}

/**
* Writes the given chunk to the region file in a lazy fashion. If the chunk exists in the region file and the given chunk can fit, it will be placed there and the sector size will be updated. Otherwise the chunk will be placed at the end of the region file without removing the old chunk, and the offset and sector size will be updated.
* @param x The X chunk coordinate of the chunk.
* @param z The Z chunk coordinate of the chunk.
* @param c The chunk to write.
* @throws IOException if an input or output operation throws an exception.
*/
@Override
public void setChunk(int x, int z, Chunk c) throws IOException
public void setChunk(LocChunkInRegion pos, Chunk c) throws IOException
{
try(RandomAccessFile region = new RandomAccessFile(rf, "rw"))
{
final int index = ((x%32) + (z%32)*32);
final int index = chunkIndex(pos);
if(c == null)
{
sectorOffset(region, index, -NUMBER_OF_HEADER_SECTORS, 0);
region.seek(LOCATIONS_SECTOR_START + index*4);
new LocationPair(0, 0).serialize(region);
return;
}
int chunksize, newsectors;
ByteBuffer chunkbytes;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream(0))
final byte[] chunkdata;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
baos.write(CompressionScheme.GZip.getId());
try(OutputStream os = CompressionScheme.GZip.getOutputStream(baos))
{
c.ToNBT("").serialize(os);
}
chunksize = baos.size();
newsectors = (int)((chunksize+5)/SECTOR_BYTES+1); //TODO: remove cast
chunkbytes = ByteBuffer.allocate((int)(newsectors*SECTOR_BYTES)); //TODO: remove cast
chunkbytes.putInt(chunksize+1);
chunkbytes.put(CompressionScheme.GZip.getId());
chunkbytes.put(baos.toByteArray());
chunkdata = baos.toByteArray();
}
final long newsize = 4+chunkdata.length;

Entry<Integer, Integer> pair = sectorOffset(region, index);
int offset = pair.getKey();
int sectors = pair.getValue();

if((offset == -NUMBER_OF_HEADER_SECTORS && sectors == 0) || sectors < newsectors)
region.seek(LOCATIONS_SECTOR_START + index*4);
LocationPair loc = new LocationPair(region);
if((loc.offset == 0 && loc.size == 0) || loc.size < newsize)
{
int newoffset = (int)((region.length()-CHUNK_SECTORS_START)/SECTOR_BYTES)+1;
newoffset = newoffset < 0 ? 0 : newoffset;
region.seek(CHUNK_SECTORS_START+SECTOR_BYTES*newoffset);
region.write(chunkbytes.array());
sectorOffset(region, index, newoffset, newsectors);
final long offset = nextSector(region.length());
region.seek(offset);
region.writeInt(chunkdata.length);
region.write(chunkdata);
loc = new LocationPair(offset, region.getFilePointer()-offset);
region.seek(LOCATIONS_SECTOR_START + index*4);
loc.serialize(region);
}
else if(sectors >= newsectors)
else
{
region.seek(CHUNK_SECTORS_START+SECTOR_BYTES*offset);
region.write(chunkbytes.array());
sectorOffset(region, index, offset, newsectors);
region.seek(loc.offset);
region.writeInt(chunkdata.length);
region.write(chunkdata);
}
}
}
/**
* Writes a chunk timestamp to the region file.
* @param x The X chunk coordinate of the chunk.
* @param z The Z chunk coordinate of the chunk.
* @param timestamp The new timestamp.
* @throws IOException if the output operation throws an exception.
*/
@Override
public void setTimestamp(int x, int z, int timestamp) throws IOException
public void setTimestamp(LocChunkInRegion pos, int timestamp) throws IOException
{
try(FileOutputStream region = new FileOutputStream(rf))
try(RandomAccessFile region = new RandomAccessFile(rf, "rw"))
{
region.getChannel().position(TIMESTAMPS_SECTOR_START+4*((x%32) + (z%32)*32));
try(DataOutputStream dos = new DataOutputStream(region))
{
dos.writeInt(timestamp);
}
region.seek(TIMESTAMPS_SECTOR_START + chunkIndex(pos)*4);
region.writeInt(timestamp);
}
}
}
Loading

0 comments on commit 0201278

Please sign in to comment.