Added McRegion reader classes (incomplete), moved some com.sk89q.worldedit.data classes around and moved org.jnbt com.sk89q.jnbt to coincide with some modifications to make the JNBT library a bit more generic by accepting uncompressed input streams.

This commit is contained in:
sk89q 2011-03-09 23:10:59 -08:00
parent 488f841d69
commit 5e7d9c7f7d
38 changed files with 759 additions and 367 deletions

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,4 @@
package org.jnbt; package com.sk89q.jnbt;
/* /*
* JNBT License * JNBT License
@ -35,6 +35,7 @@ package org.jnbt;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import com.sk89q.jnbt.Tag;
/** /**
* The <code>TAG_Compound</code> tag. * The <code>TAG_Compound</code> tag.

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,4 @@
package org.jnbt; package com.sk89q.jnbt;
/* /*
* JNBT License * JNBT License
@ -35,6 +35,8 @@ package org.jnbt;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.Tag;
/** /**
* The <code>TAG_List</code> tag. * The <code>TAG_List</code> tag.

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,4 @@
package org.jnbt; package com.sk89q.jnbt;
import java.nio.charset.Charset; import java.nio.charset.Charset;

View File

@ -1,4 +1,4 @@
package org.jnbt; package com.sk89q.jnbt;
/* /*
* JNBT License * JNBT License
@ -41,7 +41,20 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.zip.GZIPInputStream; import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.EndTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
/** /**
* <p>This class reads <strong>NBT</strong>, or * <p>This class reads <strong>NBT</strong>, or
@ -68,7 +81,7 @@ public final class NBTInputStream implements Closeable {
* @throws IOException if an I/O error occurs. * @throws IOException if an I/O error occurs.
*/ */
public NBTInputStream(InputStream is) throws IOException { public NBTInputStream(InputStream is) throws IOException {
this.is = new DataInputStream(new GZIPInputStream(is)); this.is = new DataInputStream(is);
} }
/** /**

View File

@ -1,4 +1,4 @@
package org.jnbt; package com.sk89q.jnbt;
import java.io.Closeable; import java.io.Closeable;
import java.io.DataOutputStream; import java.io.DataOutputStream;
@ -6,6 +6,20 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List; import java.util.List;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.EndTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,18 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.EndTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,6 @@
package org.jnbt; package com.sk89q.jnbt;
import com.sk89q.jnbt.Tag;
/* /*
* JNBT License * JNBT License

View File

@ -1,4 +1,4 @@
package org.jnbt; package com.sk89q.jnbt;
/* /*
* JNBT License * JNBT License

View File

@ -19,14 +19,15 @@
package com.sk89q.worldedit; package com.sk89q.worldedit;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.blocks.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import org.jnbt.*;
import java.io.*; import java.io.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.zip.GZIPInputStream;
/** /**
* The clipboard remembers the state of a cuboid region. * The clipboard remembers the state of a cuboid region.
@ -381,7 +382,8 @@ public class CuboidClipboard {
public static CuboidClipboard loadSchematic(File path) public static CuboidClipboard loadSchematic(File path)
throws DataException, IOException { throws DataException, IOException {
FileInputStream stream = new FileInputStream(path); FileInputStream stream = new FileInputStream(path);
NBTInputStream nbtStream = new NBTInputStream(stream); NBTInputStream nbtStream = new NBTInputStream(
new GZIPInputStream(stream));
Vector origin = new Vector(); Vector origin = new Vector();
Vector offset = new Vector(); Vector offset = new Vector();

View File

@ -2109,7 +2109,7 @@ public class EditSession {
/** /**
* Returns the highest solid 'terrain' block which can occur naturally. * Returns the highest solid 'terrain' block which can occur naturally.
* Looks at: 1, 2, 3, 7, 12, 13, 14, 15, 16, 56, 73, 74, 87, 88, 89 * Looks at: 1, 2, 3, 7, 12, 13, 14, 15, 16, 56, 73, 74, 87, 88, 89, 82
* *
* @param x * @param x
* @param z * @param z
@ -2131,6 +2131,7 @@ public class EditSession {
|| id == 7 // bedrock || id == 7 // bedrock
|| id == 12 // sand || id == 12 // sand
|| id == 13 // gravel || id == 13 // gravel
|| id == 82 // clay
// hell // hell
|| id == 87 // netherstone || id == 87 // netherstone
|| id == 88 // slowsand || id == 88 // slowsand

View File

@ -19,12 +19,12 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import org.jnbt.*;
/** /**
* Represents chests. * Represents chests.

View File

@ -19,12 +19,12 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import org.jnbt.*;
/** /**
* Represents dispensers. * Represents dispensers.

View File

@ -19,12 +19,12 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import org.jnbt.*;
/** /**
* Represents furnaces. * Represents furnaces.

View File

@ -19,10 +19,10 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import org.jnbt.*;
/** /**
* Represents chests. * Represents chests.

View File

@ -19,10 +19,10 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import org.jnbt.*;
/** /**
* *

View File

@ -19,10 +19,10 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import org.jnbt.*;
/** /**
* *

View File

@ -19,9 +19,9 @@
package com.sk89q.worldedit.blocks; package com.sk89q.worldedit.blocks;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.util.Map; import java.util.Map;
import org.jnbt.Tag;
/** /**
* A class implementing this interface has extra TileEntityBlock data to store. * A class implementing this interface has extra TileEntityBlock data to store.

View File

@ -25,7 +25,7 @@ import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import com.sk89q.worldedit.data.NestedFileChunkStore; import com.sk89q.worldedit.data.LegacyChunkStore;
/** /**
* Chunk tools. * Chunk tools.
@ -73,7 +73,7 @@ public class ChunkCommands {
Set<Vector2D> chunks = session.getSelection(player.getWorld()).getChunks(); Set<Vector2D> chunks = session.getSelection(player.getWorld()).getChunks();
for (Vector2D chunk : chunks) { for (Vector2D chunk : chunks) {
player.print(NestedFileChunkStore.getFilename(chunk)); player.print(LegacyChunkStore.getFilename(chunk));
} }
} }
@ -109,7 +109,7 @@ public class ChunkCommands {
writer.write("PAUSE\r\n"); writer.write("PAUSE\r\n");
for (Vector2D chunk : chunks) { for (Vector2D chunk : chunks) {
String filename = NestedFileChunkStore.getFilename(chunk); String filename = LegacyChunkStore.getFilename(chunk);
writer.write("ECHO " + filename + "\r\n"); writer.write("ECHO " + filename + "\r\n");
writer.write("DEL \"world/" + filename + "\"\r\n"); writer.write("DEL \"world/" + filename + "\"\r\n");
} }
@ -138,7 +138,7 @@ public class ChunkCommands {
writer.write("read -p \"Press any key to continue...\"\n"); writer.write("read -p \"Press any key to continue...\"\n");
for (Vector2D chunk : chunks) { for (Vector2D chunk : chunks) {
String filename = NestedFileChunkStore.getFilename(chunk); String filename = LegacyChunkStore.getFilename(chunk);
writer.write("echo " + filename + "\n"); writer.write("echo " + filename + "\n");
writer.write("rm \"world/" + filename + "\"\n"); writer.write("rm \"world/" + filename + "\"\n");
} }

View File

@ -23,9 +23,9 @@ package com.sk89q.worldedit.data;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.*; import com.sk89q.worldedit.blocks.*;
import org.jnbt.*;
/** /**
* Represents a chunk. * Represents a chunk.

View File

@ -20,8 +20,8 @@
package com.sk89q.worldedit.data; package com.sk89q.worldedit.data;
import java.io.*; import java.io.*;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import org.jnbt.*;
/** /**
* Represents chunk storage mechanisms. * Represents chunk storage mechanisms.

View File

@ -26,7 +26,7 @@ import java.io.*;
* *
* @author sk89q * @author sk89q
*/ */
public class AlphaChunkStore extends NestedFileChunkStore { public class FileLegacyChunkStore extends LegacyChunkStore {
/** /**
* Folder to read from. * Folder to read from.
*/ */
@ -38,7 +38,7 @@ public class AlphaChunkStore extends NestedFileChunkStore {
* *
* @param path * @param path
*/ */
public AlphaChunkStore(File path) { public FileLegacyChunkStore(File path) {
this.path = path; this.path = path;
} }

View File

@ -0,0 +1,57 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.data;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class FileMcRegionChunkStore extends McRegionChunkStore {
/**
* Folder to read from.
*/
private File path;
/**
* Create an instance. The passed path is the folder to read the
* chunk files from.
*
* @param path
*/
public FileMcRegionChunkStore(File path) {
this.path = path;
}
@Override
protected InputStream getInputStream(String name) throws IOException,
DataException {
String file = "region" + File.separator + name;
try {
return new FileInputStream(new File(path, file));
} catch (FileNotFoundException e) {
throw new MissingChunkException();
}
}
}

View File

@ -0,0 +1,100 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.data;
import java.io.IOException;
import java.io.InputStream;
public class ForwardSeekableInputStream extends InputStream {
protected InputStream parent;
protected long position = 0;
public ForwardSeekableInputStream(InputStream parent) {
this.parent = parent;
}
@Override
public int read() throws IOException {
int ret = parent.read();
position++;
return ret;
}
@Override
public int available() throws IOException {
return parent.available();
}
@Override
public void close() throws IOException {
parent.close();
}
@Override
public synchronized void mark(int readlimit) {
parent.mark(readlimit);
}
@Override
public boolean markSupported() {
return parent.markSupported();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
throw new IOException("Offset not supported for read()");
}
@Override
public int read(byte[] b) throws IOException {
int read = parent.read(b);
position += read;
return read;
}
@Override
public synchronized void reset() throws IOException {
parent.reset();
}
@Override
public long skip(long n) throws IOException {
long skipped = parent.skip(n);
position += skipped;
return skipped;
}
public void seek(long n) throws IOException {
long diff = n - position;
if (diff < 0) {
throw new IOException("Can't seek backwards");
}
if (diff == 0) {
return;
}
if (skip(diff) < diff) {
throw new IOException("Failed to seek " + diff + " bytes");
}
}
}

View File

@ -19,10 +19,13 @@
package com.sk89q.worldedit.data; package com.sk89q.worldedit.data;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import java.io.*; import java.io.*;
import java.util.Map; import java.util.Map;
import org.jnbt.*; import java.util.zip.GZIPInputStream;
/** /**
* Represents chunk stores that use Alpha's file format for storing chunks. * Represents chunk stores that use Alpha's file format for storing chunks.
@ -31,7 +34,7 @@ import org.jnbt.*;
* *
* @author sk89q * @author sk89q
*/ */
public abstract class NestedFileChunkStore extends ChunkStore { public abstract class LegacyChunkStore extends ChunkStore {
/** /**
* Get the filename of a chunk. * Get the filename of a chunk.
* *
@ -82,7 +85,8 @@ public abstract class NestedFileChunkStore extends ChunkStore {
+ "." + Integer.toString(z, 36) + ".dat"; + "." + Integer.toString(z, 36) + ".dat";
InputStream stream = getInputStream(folder1, folder2, filename); InputStream stream = getInputStream(folder1, folder2, filename);
NBTInputStream nbt = new NBTInputStream(stream); NBTInputStream nbt = new NBTInputStream(
new GZIPInputStream(stream));
Tag tag; Tag tag;
try { try {

View File

@ -0,0 +1,123 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.data;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.Vector2D;
public abstract class McRegionChunkStore extends ChunkStore {
protected String curFilename = null;
protected McRegionReader cachedReader = null;
/**
* Get the filename of a region file.
*
* @param pos
* @return
*/
public static String getFilename(Vector2D pos) {
int x = pos.getBlockX();
int z = pos.getBlockZ();
String filename = "r." + (x >> 5) + "." + (z >> 5) + ".mcr";
return filename;
}
protected McRegionReader getReader(Vector2D pos) throws DataException, IOException {
String filename = getFilename(pos);
if (curFilename.equals(filename)) {
return cachedReader;
}
InputStream stream = getInputStream(filename);
cachedReader = new McRegionReader(stream);
return cachedReader;
}
@Override
public CompoundTag getChunkTag(Vector2D pos) throws DataException,
IOException {
McRegionReader reader = getReader(pos);
InputStream stream = reader.getChunkInputStream(pos);
NBTInputStream nbt = new NBTInputStream(stream);
Tag tag;
try {
tag = nbt.readTag();
if (!(tag instanceof CompoundTag)) {
throw new ChunkStoreException("CompoundTag expected for chunk; got "
+ tag.getClass().getName());
}
Map<String,Tag> children = (Map<String,Tag>)((CompoundTag)tag).getValue();
CompoundTag rootTag = null;
// Find Level tag
for (Map.Entry<String,Tag> entry : children.entrySet()) {
if (entry.getKey().equals("Level")) {
if (entry.getValue() instanceof CompoundTag) {
rootTag = (CompoundTag)entry.getValue();
break;
} else {
throw new ChunkStoreException("CompoundTag expected for 'Level'; got "
+ entry.getValue().getClass().getName());
}
}
}
if (rootTag == null) {
throw new ChunkStoreException("Missing root 'Level' tag");
}
return rootTag;
} finally {
stream.close();
}
}
/**
* Get the input stream for a chunk file.
*
* @param name
* @return
* @throws IOException
*/
protected abstract InputStream getInputStream(String name)
throws IOException, DataException;
/**
* Close resources.
*
* @throws IOException
*/
@Override
public void close() throws IOException {
if (cachedReader != null) {
cachedReader.close();
}
}
}

View File

@ -0,0 +1,197 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Region File Format
Concept: The minimum unit of storage on hard drives is 4KB. 90% of Minecraft
chunks are smaller than 4KB. 99% are smaller than 8KB. Write a simple
container to store chunks in single files in runs of 4KB sectors.
Each region file represents a 32x32 group of chunks. The conversion from
chunk number to region number is floor(coord / 32): a chunk at (30, -3)
would be in region (0, -1), and one at (70, -30) would be at (3, -1).
Region files are named "r.x.z.data", where x and z are the region coordinates.
A region file begins with a 4KB header that describes where chunks are stored
in the file. A 4-byte big-endian integer represents sector offsets and sector
counts. The chunk offset for a chunk (x, z) begins at byte 4*(x+z*32) in the
file. The bottom byte of the chunk offset indicates the number of sectors the
chunk takes up, and the top 3 bytes represent the sector number of the chunk.
Given a chunk offset o, the chunk data begins at byte 4096*(o/256) and takes up
at most 4096*(o%256) bytes. A chunk cannot exceed 1MB in size. If a chunk
offset is 0, the corresponding chunk is not stored in the region file.
Chunk data begins with a 4-byte big-endian integer representing the chunk data
length in bytes, not counting the length field. The length must be smaller than
4096 times the number of sectors. The next byte is a version field, to allow
backwards-compatible updates to how chunks are encoded.
A version of 1 represents a gzipped NBT file. The gzipped data is the chunk
length - 1.
A version of 2 represents a deflated (zlib compressed) NBT file. The deflated
data is the chunk length - 1.
*/
package com.sk89q.worldedit.data;
import java.io.*;
import java.util.zip.*;
import com.sk89q.worldedit.Vector2D;
/**
* Reader for a MCRegion file. This reader works on input streams, meaning
* that it can be used to read files from non-file based sources.
*
* @author sk89q
*/
public class McRegionReader {
protected static final int VERSION_GZIP = 1;
protected static final int VERSION_DEFLATE = 2;
protected static final int SECTOR_BYTES = 4096;
protected static final int SECTOR_INTS = SECTOR_BYTES / 4;
public static final int CHUNK_HEADER_SIZE = 5;
protected ForwardSeekableInputStream stream;
protected DataInputStream dataStream;
protected int offsets[];
/**
* Construct the reader.
*
* @param stream
* @throws DataException
* @throws IOException
*/
public McRegionReader(InputStream stream) throws DataException, IOException {
this.stream = new ForwardSeekableInputStream(stream);
this.dataStream = new DataInputStream(this.stream);
readHeader();
}
/**
* Read the header.
*
* @throws DataException
* @throws IOException
*/
private void readHeader() throws DataException, IOException {
offsets = new int[SECTOR_INTS];
for (int i = 0; i < SECTOR_INTS; ++i) {
int offset = dataStream.readInt();
offsets[i] = offset;
}
}
/**
* Gets the uncompressed data input stream for a chunk.
*
* @param pos
* @return
* @throws IOException
* @throws DataException
*/
public synchronized InputStream getChunkInputStream(Vector2D pos)
throws IOException, DataException {
int x = pos.getBlockX();
int z = pos.getBlockZ();
if (x < 0 || x >= 32 || z < 0 || z >= 32) {
throw new DataException("MCRegion file does not contain " + x + "," + z);
}
int offset = getOffset(x, z);
// The chunk hasn't been generated
if (offset == 0) {
return null;
}
int sectorNumber = offset >> 8;
int numSectors = offset & 0xFF;
stream.seek(sectorNumber * SECTOR_BYTES);
int length = dataStream.readInt();
if (length > SECTOR_BYTES * numSectors) {
throw new DataException("MCRegion chunk at "
+ x + "," + z + " has an invalid length of " + length);
}
byte version = dataStream.readByte();
if (version == VERSION_GZIP) {
byte[] data = new byte[length - 1];
if (dataStream.read(data) < length - 1) {
throw new DataException("MCRegion file does not contain "
+ x + "," + z + " in full");
}
return new GZIPInputStream(new ByteArrayInputStream(data));
} else if (version == VERSION_DEFLATE) {
byte[] data = new byte[length - 1];
if (dataStream.read(data) < length - 1) {
throw new DataException("MCRegion file does not contain "
+ x + "," + z + " in full");
}
return new InflaterInputStream(new ByteArrayInputStream(data));
} else {
throw new DataException("MCRegion chunk at "
+ x + "," + z + " has an unsupported version of " + version);
}
}
/**
* Get the offset for a chunk. May return 0 if it doesn't exist.
*
* @param x
* @param z
* @return
*/
private int getOffset(int x, int z) {
return offsets[x + z * 32];
}
/**
* Returns whether the file contains a chunk.
*
* @param x
* @param z
* @return
*/
public boolean hasChunk(int x, int z) {
return getOffset(x, z) != 0;
}
/**
* Close the stream.
*
* @throws IOException
*/
public void close() throws IOException {
stream.close();
}
}

View File

@ -1,157 +0,0 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.data;
import de.schlichtherle.io.*;
import de.schlichtherle.io.File;
import de.schlichtherle.io.FileInputStream;
import java.io.*;
import java.util.regex.Pattern;
/**
* Represents the chunk store used by Minecraft alpha. This driver uses
* TrueZip to read zip files, althoguh it currently does not work well.
*
* @author sk89q
*/
public class TrueZipAlphaChunkStore extends NestedFileChunkStore {
/**
* Folder to read from.
*/
private File path;
/**
* Create an instance. The passed path is the folder to read the
* chunk files from.
*
* @param path
*/
public TrueZipAlphaChunkStore(java.io.File path) {
this.path = new File(path);
File root = findWorldPath(this.path);
if (root != null) {
this.path = root;
}
}
/**
* Create an instance. The passed path is the folder to read the
* chunk files from.
*
* @param path
*/
public TrueZipAlphaChunkStore(File path) {
this.path = path;
File root = findWorldPath(path);
if (root != null) {
this.path = root;
}
}
/**
* Create an instance. The passed path is the folder to read the
* chunk files from.
*
* @param path
*/
public TrueZipAlphaChunkStore(String path) {
this.path = new File(path);
File root = findWorldPath(this.path);
if (root != null) {
this.path = root;
}
}
/**
* Get the input stream for a chunk file.
*
* @param f1
* @param f2
* @param name
* @return
* @throws DataException
* @throws IOException
*/
@Override
protected InputStream getInputStream(String f1, String f2, String name)
throws DataException, IOException {
String file = f1 + File.separator + f2 + File.separator + name;
File f = new File(path, file);
try {
return new FileInputStream(f.getAbsolutePath());
} catch (FileNotFoundException e) {
throw new MissingChunkException();
}
}
/**
* Find the root directory for the chunk files.
*
* @param path
* @return
*/
private File findWorldPath(File path) {
File f = new File(path, "world");
if (path.contains(f)) {
return f;
}
return searchForPath(path);
}
/**
* Find the root directory for the chunk files.
*
* @param path
* @return
*/
private File searchForPath(File path) {
String[] children = path.list();
Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$");
if (children == null) {
return null;
} else {
for (String f : children) {
if (pattern.matcher(f).matches()) {
return (File)(new File(path, f)).getParentFile();
}
}
}
return null;
}
/**
* Close the archive.
*
* @throws IOException
*/
@Override
public void close() throws IOException {
try {
File.umount(new File(path));
} catch (ArchiveException e) {
throw new IOException(e);
}
}
}

View File

@ -1,167 +1,167 @@
// $Id$ // $Id$
/* /*
* WorldEdit * WorldEdit
* Copyright (C) 2010 sk89q <http://www.sk89q.com> * Copyright (C) 2010 sk89q <http://www.sk89q.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.data; package com.sk89q.worldedit.data;
import java.io.*; import java.io.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipException; import java.util.zip.ZipException;
import java.util.Enumeration; import java.util.Enumeration;
import de.schlichtherle.util.zip.*; import de.schlichtherle.util.zip.*;
/** /**
* Represents the chunk store used by Minecraft alpha but zipped. Uses * Represents the chunk store used by Minecraft alpha but zipped. Uses
* the replacement classes for java.util.zip.* from TrueZip. * the replacement classes for java.util.zip.* from TrueZip.
* *
* @author sk89q * @author sk89q
*/ */
public class TrueZipLegacyAlphaChunkStore extends NestedFileChunkStore { public class TrueZipLegacyChunkStore extends LegacyChunkStore {
/** /**
* ZIP file. * ZIP file.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
private File zipFile; private File zipFile;
/** /**
* Actual ZIP. * Actual ZIP.
*/ */
private ZipFile zip; private ZipFile zip;
/** /**
* Folder inside the ZIP file to read from, if any. * Folder inside the ZIP file to read from, if any.
*/ */
private String folder; private String folder;
/** /**
* Create an instance. The folder argument lets you choose a folder or * Create an instance. The folder argument lets you choose a folder or
* path to look into in the ZIP for the files. Use a blank string for * path to look into in the ZIP for the files. Use a blank string for
* the folder to not look into a subdirectory. * the folder to not look into a subdirectory.
* *
* @param zipFile * @param zipFile
* @param folder * @param folder
* @throws IOException * @throws IOException
* @throws ZipException * @throws ZipException
*/ */
public TrueZipLegacyAlphaChunkStore(File zipFile, String folder) public TrueZipLegacyChunkStore(File zipFile, String folder)
throws IOException, ZipException { throws IOException, ZipException {
this.zipFile = zipFile; this.zipFile = zipFile;
this.folder = folder; this.folder = folder;
zip = new ZipFile(zipFile); zip = new ZipFile(zipFile);
} }
/** /**
* Create an instance. The subfolder containing the chunk data will * Create an instance. The subfolder containing the chunk data will
* be detected. * be detected.
* *
* @param zipFile * @param zipFile
* @throws IOException * @throws IOException
* @throws ZipException * @throws ZipException
*/ */
public TrueZipLegacyAlphaChunkStore(File zipFile) public TrueZipLegacyChunkStore(File zipFile)
throws IOException, ZipException { throws IOException, ZipException {
this.zipFile = zipFile; this.zipFile = zipFile;
zip = new ZipFile(zipFile); zip = new ZipFile(zipFile);
} }
/** /**
* Get the input stream for a chunk file. * Get the input stream for a chunk file.
* *
* @param f1 * @param f1
* @param f2 * @param f2
* @param name * @param name
* @return * @return
* @throws IOException * @throws IOException
* @throws DataException * @throws DataException
*/ */
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected InputStream getInputStream(String f1, String f2, String name) protected InputStream getInputStream(String f1, String f2, String name)
throws IOException, DataException { throws IOException, DataException {
String file = f1 + "/" + f2 + "/" + name; String file = f1 + "/" + f2 + "/" + name;
// Detect subfolder for the world's files // Detect subfolder for the world's files
if (folder != null) { if (folder != null) {
if (!folder.equals("")) { if (!folder.equals("")) {
file = folder + "/" + file; file = folder + "/" + file;
} }
} else { } else {
ZipEntry testEntry = zip.getEntry("level.dat"); ZipEntry testEntry = zip.getEntry("level.dat");
// So, the data is not in the root directory // So, the data is not in the root directory
if (testEntry == null) { if (testEntry == null) {
// Let's try a world/ sub-directory // Let's try a world/ sub-directory
testEntry = getEntry("world/level.dat"); testEntry = getEntry("world/level.dat");
Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$"); Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$");
// So not there either... // So not there either...
if (testEntry == null) { if (testEntry == null) {
for (Enumeration<? extends ZipEntry> e = zip.entries(); for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) { e.hasMoreElements(); ) {
testEntry = (ZipEntry)e.nextElement(); testEntry = (ZipEntry)e.nextElement();
// Whoo, found level.dat! // Whoo, found level.dat!
if (pattern.matcher(testEntry.getName()).matches()) { if (pattern.matcher(testEntry.getName()).matches()) {
file = testEntry.getName().replaceAll("level\\.dat$", "") file = testEntry.getName().replaceAll("level\\.dat$", "")
+ file; + file;
break; break;
} }
} }
} else { } else {
file = "world/" + file; file = "world/" + file;
} }
} }
} }
ZipEntry entry = getEntry(file); ZipEntry entry = getEntry(file);
if (entry == null) { if (entry == null) {
throw new MissingChunkException(); throw new MissingChunkException();
} }
try { try {
return zip.getInputStream(entry); return zip.getInputStream(entry);
} catch (ZipException e) { } catch (ZipException e) {
throw new IOException("Failed to read " + file + " in ZIP"); throw new IOException("Failed to read " + file + " in ZIP");
} }
} }
/** /**
* Get an entry from the ZIP, trying both types of slashes. * Get an entry from the ZIP, trying both types of slashes.
* *
* @param file * @param file
* @return * @return
*/ */
private ZipEntry getEntry(String file) { private ZipEntry getEntry(String file) {
ZipEntry entry = zip.getEntry(file); ZipEntry entry = zip.getEntry(file);
if (entry != null) { if (entry != null) {
return entry; return entry;
} }
return zip.getEntry(file.replace("/", "\\")); return zip.getEntry(file.replace("/", "\\"));
} }
/** /**
* Close resources. * Close resources.
* *
* @throws IOException * @throws IOException
*/ */
@Override @Override
public void close() throws IOException { public void close() throws IOException {
zip.close(); zip.close();
} }
} }

View File

@ -29,7 +29,7 @@ import java.util.Enumeration;
* *
* @author sk89q * @author sk89q
*/ */
public class ZippedAlphaChunkStore extends NestedFileChunkStore { public class ZippedAlphaChunkStore extends LegacyChunkStore {
/** /**
* ZIP file. * ZIP file.
*/ */

View File

@ -21,12 +21,15 @@ package com.sk89q.worldedit.snapshots;
import com.sk89q.worldedit.data.*; import com.sk89q.worldedit.data.*;
import java.io.*; import java.io.*;
import java.util.logging.Logger;
/** /**
* *
* @author sk89q * @author sk89q
*/ */
public class Snapshot { public class Snapshot {
protected static Logger logger = Logger.getLogger("Minecraft.WorldEdit");
/** /**
* Stores snapshot file. * Stores snapshot file.
*/ */
@ -57,7 +60,7 @@ public class Snapshot {
public ChunkStore getChunkStore() throws IOException, DataException { public ChunkStore getChunkStore() throws IOException, DataException {
if (file.getName().toLowerCase().endsWith(".zip")) { if (file.getName().toLowerCase().endsWith(".zip")) {
try { try {
return new TrueZipLegacyAlphaChunkStore(file); return new TrueZipLegacyChunkStore(file);
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError e) {
return new ZippedAlphaChunkStore(file); return new ZippedAlphaChunkStore(file);
} }
@ -65,12 +68,12 @@ public class Snapshot {
|| file.getName().toLowerCase().endsWith(".tar.gz") || file.getName().toLowerCase().endsWith(".tar.gz")
|| file.getName().toLowerCase().endsWith(".tar")) { || file.getName().toLowerCase().endsWith(".tar")) {
try { try {
return new TrueZipAlphaChunkStore(file); return new TrueZipLegacyChunkStore(file);
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError e) {
throw new DataException("TrueZIP is required for .tar support"); throw new DataException("TrueZIP is required for .tar support");
} }
} else { } else {
return new AlphaChunkStore(file); return new FileLegacyChunkStore(file);
} }
} }