More file moving.

This commit is contained in:
sk89q
2011-05-01 01:30:33 -07:00
parent 4bcbfa76ef
commit 582b98dad0
206 changed files with 133 additions and 2 deletions

View File

@ -0,0 +1,232 @@
// $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 com.sk89q.worldedit.blocks.BlockID;
/**
* Block data related classes.
*
* @author sk89q
*/
public final class BlockData {
/**
* Rotate a block's data value 90 degrees (north->east->south->west->north);
*
* @param type
* @param data
* @return
*/
public static int rotate90(int type, int data) {
if (type == BlockID.TORCH
|| type == BlockID.REDSTONE_TORCH_OFF
|| type == BlockID.REDSTONE_TORCH_ON) {
switch (data) {
case 1: return 3;
case 2: return 4;
case 3: return 2;
case 4: return 1;
}
} else if (type == BlockID.MINECART_TRACKS) {
switch (data) {
case 0: return 1;
case 1: return 0;
case 2: return 5;
case 3: return 4;
case 4: return 2;
case 5: return 3;
case 6: return 7;
case 7: return 8;
case 8: return 9;
case 9: return 6;
}
} else if (type == BlockID.WOODEN_STAIRS
|| type == BlockID.COBBLESTONE_STAIRS) {
switch (data) {
case 0: return 2;
case 1: return 3;
case 2: return 1;
case 3: return 0;
}
} else if (type == BlockID.LEVER) {
int thrown = data & 0x8;
int withoutThrown = data ^ 0x8;
switch (withoutThrown) {
case 1: return 3 | thrown;
case 2: return 4 | thrown;
case 3: return 2 | thrown;
case 4: return 1 | thrown;
}
} else if (type == BlockID.WOODEN_DOOR
|| type == BlockID.IRON_DOOR) {
int topHalf = data & 0x8;
int swung = data & 0x4;
int withoutFlags = data ^ (0x8 | 0x4);
switch (withoutFlags) {
case 0: return 1 | topHalf | swung;
case 1: return 2 | topHalf | swung;
case 2: return 3 | topHalf | swung;
case 3: return 0 | topHalf | swung;
}
} else if (type == BlockID.STONE_BUTTON) {
int thrown = data & 0x8;
int withoutThrown = data ^ 0x8;
switch (withoutThrown) {
case 1: return 3 | thrown;
case 2: return 4 | thrown;
case 3: return 2 | thrown;
case 4: return 1 | thrown;
}
} else if (type == BlockID.SIGN_POST) {
return (data + 4) % 16;
} else if (type == BlockID.LADDER
|| type == BlockID.WALL_SIGN
|| type == BlockID.FURNACE
|| type == BlockID.BURNING_FURNACE
|| type == BlockID.DISPENSER) {
switch (data) {
case 2: return 5;
case 3: return 4;
case 4: return 2;
case 5: return 3;
}
} else if (type == BlockID.PUMPKIN
|| type == BlockID.JACKOLANTERN) {
switch (data) {
case 0: return 1;
case 1: return 2;
case 2: return 3;
case 3: return 0;
}
}
return data;
}
/**
* Rotate a block's data value -90 degrees (north<-east<-south<-west<-north);
*
* @param type
* @param data
* @return
*/
public static int rotate90Reverse(int type, int data) {
// case ([0-9]+): return ([0-9]+) -> case \2: return \1
if (type == BlockID.TORCH
|| type == BlockID.REDSTONE_TORCH_OFF
|| type == BlockID.REDSTONE_TORCH_ON) {
switch (data) {
case 3: return 1;
case 4: return 2;
case 2: return 3;
case 1: return 4;
}
} else if (type == BlockID.MINECART_TRACKS) {
switch (data) {
case 1: return 0;
case 0: return 1;
case 5: return 2;
case 4: return 3;
case 2: return 4;
case 3: return 5;
case 7: return 6;
case 8: return 7;
case 9: return 8;
case 6: return 9;
}
} else if (type == BlockID.WOODEN_STAIRS
|| type == BlockID.COBBLESTONE_STAIRS) {
switch (data) {
case 2: return 0;
case 3: return 1;
case 1: return 2;
case 0: return 3;
}
} else if (type == BlockID.LEVER) {
int thrown = data & 0x8;
int withoutThrown = data ^ 0x8;
switch (withoutThrown) {
case 3: return 1 | thrown;
case 4: return 2 | thrown;
case 2: return 3 | thrown;
case 1: return 4 | thrown;
}
} else if (type == BlockID.WOODEN_DOOR
|| type == BlockID.IRON_DOOR) {
int topHalf = data & 0x8;
int swung = data & 0x4;
int withoutFlags = data ^ (0x8 | 0x4);
switch (withoutFlags) {
case 1: return 0 | topHalf | swung;
case 2: return 1 | topHalf | swung;
case 3: return 2 | topHalf | swung;
case 0: return 3 | topHalf | swung;
}
} else if (type == BlockID.STONE_BUTTON) {
int thrown = data & 0x8;
int withoutThrown = data ^ 0x8;
switch (withoutThrown) {
case 3: return 1 | thrown;
case 4: return 2 | thrown;
case 2: return 3 | thrown;
case 1: return 4 | thrown;
}
} else if (type == BlockID.SIGN_POST) {
int newData = (data - 4);
if (newData < 0) {
newData += 16;
}
return newData;
} else if (type == BlockID.LADDER
|| type == BlockID.WALL_SIGN
|| type == BlockID.FURNACE
|| type == BlockID.BURNING_FURNACE
|| type == BlockID.DISPENSER) {
switch (data) {
case 5: return 2;
case 4: return 3;
case 2: return 4;
case 3: return 5;
}
} else if (type == BlockID.PUMPKIN
|| type == BlockID.JACKOLANTERN) {
switch (data) {
case 1: return 0;
case 2: return 1;
case 3: return 2;
case 0: return 3;
}
}
return data;
}
/**
* Flip a block's data value.
*
* @param type
* @param data
* @return
*/
public static int flip(int type, int data) {
return rotate90(type, rotate90(type, data));
}
}

View File

@ -0,0 +1,240 @@
// $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 java.util.List;
import java.util.Map;
import java.util.HashMap;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.*;
/**
* Represents a chunk.
*
* @author sk89q
*/
public class Chunk {
private CompoundTag rootTag;
private byte[] blocks;
private byte[] data;
private int rootX;
private int rootZ;
private Map<BlockVector,Map<String,Tag>> tileEntities;
/**
* Construct the chunk with a compound tag.
*
* @param tag
* @throws DataException
*/
public Chunk(CompoundTag tag) throws DataException {
rootTag = tag;
blocks = ((ByteArrayTag)getChildTag(
rootTag.getValue(), "Blocks", ByteArrayTag.class)).getValue();
data = ((ByteArrayTag)getChildTag(
rootTag.getValue(), "Data", ByteArrayTag.class)).getValue();
rootX = ((IntTag)getChildTag(
rootTag.getValue(), "xPos", IntTag.class)).getValue();
rootZ = ((IntTag)getChildTag(
rootTag.getValue(), "zPos", IntTag.class)).getValue();
if (blocks.length != 32768) {
throw new InvalidFormatException("Chunk blocks byte array expected "
+ "to be 32,768 bytes; found " + blocks.length);
}
if (data.length != 16384) {
throw new InvalidFormatException("Chunk block data byte array "
+ "expected to be 16,384 bytes; found " + data.length);
}
}
/**
* Get the block ID of a block.
*
* @param pos
* @return
* @throws DataException
*/
public int getBlockID(Vector pos) throws DataException {
int x = pos.getBlockX() - rootX * 16;
int y = pos.getBlockY();
int z = pos.getBlockZ() - rootZ * 16;
int index = y + (z * 128 + (x * 128 * 16));
try {
return blocks[index];
} catch (IndexOutOfBoundsException e) {
throw new DataException("Chunk does not contain position " + pos);
}
}
/**
* Get the block data of a block.
*
* @param pos
* @return
* @throws DataException
*/
public int getBlockData(Vector pos) throws DataException {
int x = pos.getBlockX() - rootX * 16;
int y = pos.getBlockY();
int z = pos.getBlockZ() - rootZ * 16;
int index = y + (z * 128 + (x * 128 * 16));
boolean shift = index % 2 == 0;
index /= 2;
try {
if (!shift) {
return (data[index] & 0xF0) >> 4;
} else {
return data[index] & 0xF;
}
} catch (IndexOutOfBoundsException e) {
throw new DataException("Chunk does not contain position " + pos);
}
}
/**
* Used to load the tile entities.
*
* @throws DataException
*/
private void populateTileEntities() throws DataException {
List<Tag> tags = (List<Tag>)((ListTag)getChildTag(
rootTag.getValue(), "TileEntities", ListTag.class))
.getValue();
tileEntities = new HashMap<BlockVector,Map<String,Tag>>();
for (Tag tag : tags) {
if (!(tag instanceof CompoundTag)) {
throw new InvalidFormatException("CompoundTag expected in TileEntities");
}
CompoundTag t = (CompoundTag)tag;
int x = 0;
int y = 0;
int z = 0;
Map<String,Tag> values = new HashMap<String,Tag>();
for (Map.Entry<String,Tag> entry : t.getValue().entrySet()) {
if (entry.getKey().equals("x")) {
if (entry.getValue() instanceof IntTag) {
x = ((IntTag)entry.getValue()).getValue();
}
} else if (entry.getKey().equals("y")) {
if (entry.getValue() instanceof IntTag) {
y = ((IntTag)entry.getValue()).getValue();
}
} else if (entry.getKey().equals("z")) {
if (entry.getValue() instanceof IntTag) {
z = ((IntTag)entry.getValue()).getValue();
}
}
values.put(entry.getKey(), entry.getValue());
}
BlockVector vec = new BlockVector(x, y, z);
tileEntities.put(vec, values);
}
}
/**
* Get the map of tags keyed to strings for a block's tile entity data. May
* return null if there is no tile entity data. Not public yet because
* what this function returns isn't ideal for usage.
*
* @param pos
* @return
* @throws DataException
*/
private Map<String,Tag> getBlockTileEntity(Vector pos) throws DataException {
if (tileEntities == null)
populateTileEntities();
return tileEntities.get(new BlockVector(pos));
}
/**
* Get a block;
*
* @param pos
* @return block
* @throws DataException
*/
public BaseBlock getBlock(Vector pos) throws DataException {
int id = getBlockID(pos);
int data = getBlockData(pos);
BaseBlock block;
if (id == BlockID.WALL_SIGN || id == BlockID.SIGN_POST) {
block = new SignBlock(id, data);
} else if (id == BlockID.CHEST) {
block = new ChestBlock(data);
} else if (id == BlockID.FURNACE || id == BlockID.BURNING_FURNACE) {
block = new FurnaceBlock(id, data);
} else if (id == BlockID.DISPENSER) {
block = new DispenserBlock(data);
} else if (id == BlockID.MOB_SPAWNER) {
block = new MobSpawnerBlock(data);
} else if (id == BlockID.NOTE_BLOCK) {
block = new NoteBlock(data);
} else {
block = new BaseBlock(id, data);
}
if (block instanceof TileEntityBlock) {
Map<String,Tag> tileEntity = getBlockTileEntity(pos);
((TileEntityBlock)block).fromTileEntityNBT(tileEntity);
}
return block;
}
/**
* Get child tag of a NBT structure.
*
* @param items
* @param key
* @param expected
* @return child tag
* @throws InvalidFormatException
*/
public static Tag getChildTag(Map<String,Tag> items, String key,
Class<? extends Tag> expected)
throws InvalidFormatException {
if (!items.containsKey(key)) {
throw new InvalidFormatException("Missing a \"" + key + "\" tag");
}
Tag tag = items.get(key);
if (!expected.isInstance(tag)) {
throw new InvalidFormatException(
key + " tag is not of tag type " + expected.getName());
}
return tag;
}
}

View File

@ -0,0 +1,85 @@
// $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 java.io.*;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*;
/**
* Represents chunk storage mechanisms.
*
* @author sk89q
*/
public abstract class ChunkStore {
/**
* Convert a position to a chunk.
*
* @param pos
* @return
*/
public static BlockVector2D toChunk(Vector pos) {
int chunkX = (int)Math.floor(pos.getBlockX() / 16.0);
int chunkZ = (int)Math.floor(pos.getBlockZ() / 16.0);
return new BlockVector2D(chunkX, chunkZ);
}
/**
* Get the tag for a chunk.
*
* @param pos
* @return tag
* @throws DataException
* @throws IOException
*/
public abstract CompoundTag getChunkTag(Vector2D pos)
throws DataException, IOException;
/**
* Get a chunk at a location.
*
* @param pos
* @return
* @throws ChunkStoreException
* @throws IOException
* @throws DataException
*/
public Chunk getChunk(Vector2D pos)
throws DataException, IOException {
return new Chunk(getChunkTag(pos));
}
/**
* Close resources.
*
* @throws IOException
*/
public void close() throws IOException {
}
/**
* Returns whether the chunk store is of this type.
*
* @return
*/
public abstract boolean isValid();
}

View File

@ -0,0 +1,36 @@
// $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;
/**
*
* @author sk89q
*/
public class ChunkStoreException extends DataException {
private static final long serialVersionUID = 1483900743779953289L;
public ChunkStoreException(String msg) {
super(msg);
}
public ChunkStoreException() {
super();
}
}

View File

@ -0,0 +1,37 @@
// $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;
/**
* Thrown when there is an exception related to data handling.
*
* @author sk89q
*/
public class DataException extends Exception {
private static final long serialVersionUID = 5806521052111023788L;
public DataException(String msg) {
super(msg);
}
public DataException() {
super();
}
}

View File

@ -0,0 +1,70 @@
// $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 java.io.*;
/**
* Represents the chunk store used by Minecraft alpha.
*
* @author sk89q
*/
public class FileLegacyChunkStore extends LegacyChunkStore {
/**
* 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 FileLegacyChunkStore(File path) {
this.path = path;
}
/**
* 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;
try {
return new FileInputStream(new File(path, file));
} catch (FileNotFoundException e) {
throw new MissingChunkException();
}
}
@Override
public boolean isValid() {
return true; // Yeah, oh well
}
}

View File

@ -0,0 +1,62 @@
// $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();
}
}
@Override
public boolean isValid() {
return new File(path, "region").isDirectory();
}
}

View File

@ -0,0 +1,102 @@
// $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 {
int read = super.read(b, off, len);
position += read;
return 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

@ -0,0 +1,32 @@
// $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;
/**
*
* @author sk89q
*/
public class InvalidFormatException extends DataException {
private static final long serialVersionUID = -3401820540902726145L;
public InvalidFormatException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,147 @@
// $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 com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.*;
import java.io.*;
import java.util.Map;
import java.util.zip.GZIPInputStream;
/**
* Represents chunk stores that use Alpha's file format for storing chunks.
* The code to resolve the filename is already implemented in this class
* and an inheriting class merely needs to implement getInputStream().
*
* @author sk89q
*/
public abstract class LegacyChunkStore extends ChunkStore {
/**
* Get the filename of a chunk.
*
* @param pos
* @param separator
* @return
*/
public static String getFilename(Vector2D pos, String separator) {
int x = pos.getBlockX();
int z = pos.getBlockZ();
String folder1 = Integer.toString(divisorMod(x, 64), 36);
String folder2 = Integer.toString(divisorMod(z, 64), 36);
String filename = "c." + Integer.toString(x, 36)
+ "." + Integer.toString(z, 36) + ".dat";
return folder1 + separator + folder2 + separator + filename;
}
/**
* Get the filename of a chunk, using the system's default path
* separator.
*
* @param pos
* @return
*/
public static String getFilename(Vector2D pos) {
return getFilename(pos, File.separator);
}
/**
* Get the tag for a chunk.
*
* @param pos
* @return tag
* @throws DataException
* @throws IOException
*/
@Override
public CompoundTag getChunkTag(Vector2D pos)
throws DataException, IOException {
int x = pos.getBlockX();
int z = pos.getBlockZ();
String folder1 = Integer.toString(divisorMod(x, 64), 36);
String folder2 = Integer.toString(divisorMod(z, 64), 36);
String filename = "c." + Integer.toString(x, 36)
+ "." + Integer.toString(z, 36) + ".dat";
InputStream stream = getInputStream(folder1, folder2, filename);
NBTInputStream nbt = new NBTInputStream(
new GZIPInputStream(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();
}
}
/**
* Modulus, divisor-style.
*
* @param a
* @param n
* @return
*/
private static int divisorMod(int a, int n) {
return (int)(a - n * Math.floor(Math.floor(a) / (double)n));
}
/**
* Get the input stream for a chunk file.
*
* @param f1
* @param f2
* @param name
* @return
* @throws IOException
*/
protected abstract InputStream getInputStream(String f1, String f2, String name)
throws IOException, DataException;
}

View File

@ -0,0 +1,131 @@
// $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 != null) {
if (curFilename.equals(filename)) {
return cachedReader;
} else {
try {
cachedReader.close();
} catch (IOException e) {
}
}
}
InputStream stream = getInputStream(filename);
cachedReader = new McRegionReader(stream);
//curFilename = filename;
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() & 31;
int z = pos.getBlockZ() & 31;
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

@ -0,0 +1,50 @@
// $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 com.sk89q.worldedit.Vector2D;
/**
*
* @author sk89q
*/
public class MissingChunkException extends ChunkStoreException {
private static final long serialVersionUID = 8013715483709973489L;
private Vector2D pos;
public MissingChunkException() {
super();
}
public MissingChunkException(Vector2D pos) {
super();
this.pos = pos;
}
/**
* Get chunk position in question. May be null if unknown.
*
* @return
*/
public Vector2D getChunkPosition() {
return pos;
}
}

View File

@ -0,0 +1,173 @@
// $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 java.io.*;
import java.util.regex.Pattern;
import java.util.zip.ZipException;
import java.util.Enumeration;
import de.schlichtherle.util.zip.*;
/**
* Represents the chunk store used by Minecraft alpha but zipped. Uses
* the replacement classes for java.util.zip.* from TrueZip.
*
* @author sk89q
*/
public class TrueZipLegacyChunkStore extends LegacyChunkStore {
/**
* ZIP file.
*/
@SuppressWarnings("unused")
private File zipFile;
/**
* Actual ZIP.
*/
private ZipFile zip;
/**
* Folder inside the ZIP file to read from, if any.
*/
private String folder;
/**
* 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
* the folder to not look into a subdirectory.
*
* @param zipFile
* @param folder
* @throws IOException
* @throws ZipException
*/
public TrueZipLegacyChunkStore(File zipFile, String folder)
throws IOException, ZipException {
this.zipFile = zipFile;
this.folder = folder;
zip = new ZipFile(zipFile);
}
/**
* Create an instance. The subfolder containing the chunk data will
* be detected.
*
* @param zipFile
* @throws IOException
* @throws ZipException
*/
public TrueZipLegacyChunkStore(File zipFile)
throws IOException, ZipException {
this.zipFile = zipFile;
zip = new ZipFile(zipFile);
}
/**
* Get the input stream for a chunk file.
*
* @param f1
* @param f2
* @param name
* @return
* @throws IOException
* @throws DataException
*/
@Override
@SuppressWarnings("unchecked")
protected InputStream getInputStream(String f1, String f2, String name)
throws IOException, DataException {
String file = f1 + "/" + f2 + "/" + name;
// Detect subfolder for the world's files
if (folder != null) {
if (!folder.equals("")) {
file = folder + "/" + file;
}
} else {
ZipEntry testEntry = zip.getEntry("level.dat");
// So, the data is not in the root directory
if (testEntry == null) {
// Let's try a world/ sub-directory
testEntry = getEntry("world/level.dat");
Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$");
// So not there either...
if (testEntry == null) {
for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
testEntry = (ZipEntry)e.nextElement();
// Whoo, found level.dat!
if (pattern.matcher(testEntry.getName()).matches()) {
folder = testEntry.getName().replaceAll("level\\.dat$", "");
folder = folder.substring(0, folder.length() - 1);
file = folder + file;
break;
}
}
} else {
file = "world/" + file;
}
}
}
ZipEntry entry = getEntry(file);
if (entry == null) {
throw new MissingChunkException();
}
try {
return zip.getInputStream(entry);
} catch (ZipException e) {
throw new IOException("Failed to read " + file + " in ZIP");
}
}
/**
* Get an entry from the ZIP, trying both types of slashes.
*
* @param file
* @return
*/
private ZipEntry getEntry(String file) {
ZipEntry entry = zip.getEntry(file);
if (entry != null) {
return entry;
}
return zip.getEntry(file.replace("/", "\\"));
}
/**
* Close resources.
*
* @throws IOException
*/
@Override
public void close() throws IOException {
zip.close();
}
@Override
public boolean isValid() {
return true; // Yeah, oh well
}
}

View File

@ -0,0 +1,181 @@
// $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 java.io.*;
import java.util.regex.Pattern;
import java.util.zip.ZipException;
import java.util.Enumeration;
import de.schlichtherle.util.zip.*;
/**
* Represents the chunk store used by Minecraft but zipped. Uses
* the replacement classes for java.util.zip.* from TrueZip.
*
* @author sk89q
*/
public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
/**
* ZIP file.
*/
protected File zipFile;
/**
* Actual ZIP.
*/
protected ZipFile zip;
/**
* Folder inside the ZIP file to read from, if any.
*/
protected String folder;
/**
* 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
* the folder to not look into a subdirectory.
*
* @param zipFile
* @param folder
* @throws IOException
* @throws ZipException
*/
public TrueZipMcRegionChunkStore(File zipFile, String folder)
throws IOException, ZipException {
this.zipFile = zipFile;
this.folder = folder;
zip = new ZipFile(zipFile);
}
/**
* Create an instance. The subfolder containing the chunk data will
* be detected.
*
* @param zipFile
* @throws IOException
* @throws ZipException
*/
public TrueZipMcRegionChunkStore(File zipFile)
throws IOException, ZipException {
this.zipFile = zipFile;
zip = new ZipFile(zipFile);
}
/**
* Get the input stream for a chunk file.
*
* @param name
* @return
* @throws IOException
* @throws DataException
*/
@Override
@SuppressWarnings("unchecked")
protected InputStream getInputStream(String name)
throws IOException, DataException {
String file = "region/" + name;
// Detect subfolder for the world's files
if (folder != null) {
if (!folder.equals("")) {
file = folder + "/" + file;
}
} else {
ZipEntry testEntry = zip.getEntry("level.dat");
// So, the data is not in the root directory
if (testEntry == null) {
// Let's try a world/ sub-directory
testEntry = getEntry("world/level.dat");
Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$");
// So not there either...
if (testEntry == null) {
for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
testEntry = e.nextElement();
// Whoo, found level.dat!
if (pattern.matcher(testEntry.getName()).matches()) {
folder = testEntry.getName().replaceAll("level\\.dat$", "");
folder = folder.substring(0, folder.length() - 1);
file = folder + file;
break;
}
}
} else {
file = "world/" + file;
}
}
}
ZipEntry entry = getEntry(file);
if (entry == null) {
throw new MissingChunkException();
}
try {
return zip.getInputStream(entry);
} catch (ZipException e) {
throw new IOException("Failed to read " + file + " in ZIP");
}
}
/**
* Get an entry from the ZIP, trying both types of slashes.
*
* @param file
* @return
*/
private ZipEntry getEntry(String file) {
ZipEntry entry = zip.getEntry(file);
if (entry != null) {
return entry;
}
return zip.getEntry(file.replace("/", "\\"));
}
/**
* Close resources.
*
* @throws IOException
*/
@Override
public void close() throws IOException {
zip.close();
}
@Override
@SuppressWarnings("unchecked")
public boolean isValid() {
for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
ZipEntry testEntry = e.nextElement();
if (testEntry.getName().matches(".*\\.mcr$")) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,170 @@
// $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 java.io.*;
import java.util.regex.Pattern;
import java.util.zip.*;
import java.util.Enumeration;
/**
* Represents the chunk store used by Minecraft alpha but zipped.
*
* @author sk89q
*/
public class ZippedLegacyChunkStore extends LegacyChunkStore {
/**
* ZIP file.
*/
@SuppressWarnings("unused")
private File zipFile;
/**
* Actual ZIP.
*/
private ZipFile zip;
/**
* Folder inside the ZIP file to read from, if any.
*/
private String folder;
/**
* 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
* the folder to not look into a subdirectory.
*
* @param zipFile
* @param folder
* @throws IOException
* @throws ZipException
*/
public ZippedLegacyChunkStore(File zipFile, String folder)
throws IOException, ZipException {
this.zipFile = zipFile;
this.folder = folder;
zip = new ZipFile(zipFile);
}
/**
* Create an instance. The subfolder containing the chunk data will
* be detected.
*
* @param zipFile
* @throws IOException
* @throws ZipException
*/
public ZippedLegacyChunkStore(File zipFile)
throws IOException, ZipException {
this.zipFile = zipFile;
zip = new ZipFile(zipFile);
}
/**
* Get the input stream for a chunk file.
*
* @param f1
* @param f2
* @param name
* @return
* @throws IOException
* @throws DataException
*/
@Override
protected InputStream getInputStream(String f1, String f2, String name)
throws IOException, DataException {
String file = f1 + "/" + f2 + "/" + name;
// Detect subfolder for the world's files
if (folder != null) {
if (!folder.equals("")) {
file = folder + "/" + file;
}
} else {
ZipEntry testEntry = zip.getEntry("level.dat");
// So, the data is not in the root directory
if (testEntry == null) {
// Let's try a world/ sub-directory
testEntry = getEntry("world/level.dat");
Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$");
// So not there either...
if (testEntry == null) {
for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
testEntry = (ZipEntry)e.nextElement();
// Whoo, found level.dat!
if (pattern.matcher(testEntry.getName()).matches()) {
folder = testEntry.getName().replaceAll("level\\.dat$", "");
folder = folder.substring(0, folder.length() - 1);
file = folder + file;
break;
}
}
} else {
file = "world/" + file;
}
}
}
ZipEntry entry = getEntry(file);
if (entry == null) {
throw new MissingChunkException();
}
try {
return zip.getInputStream(entry);
} catch (ZipException e) {
throw new IOException("Failed to read " + file + " in ZIP");
}
}
/**
* Get an entry from the ZIP, trying both types of slashes.
*
* @param file
* @return
*/
private ZipEntry getEntry(String file) {
ZipEntry entry = zip.getEntry(file);
if (entry != null) {
return entry;
}
return zip.getEntry(file.replace("/", "\\"));
}
/**
* Close resources.
*
* @throws IOException
*/
@Override
public void close() throws IOException {
zip.close();
}
@Override
public boolean isValid() {
return true; // Yeah, oh well
}
}

View File

@ -0,0 +1,177 @@
// $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 java.io.*;
import java.util.regex.Pattern;
import java.util.zip.*;
import java.util.Enumeration;
/**
* Represents the chunk store used by Minecraft alpha but zipped.
*
* @author sk89q
*/
public class ZippedMcRegionChunkStore extends McRegionChunkStore {
/**
* ZIP file.
*/
protected File zipFile;
/**
* Actual ZIP.
*/
protected ZipFile zip;
/**
* Folder inside the ZIP file to read from, if any.
*/
protected String folder;
/**
* 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
* the folder to not look into a subdirectory.
*
* @param zipFile
* @param folder
* @throws IOException
* @throws ZipException
*/
public ZippedMcRegionChunkStore(File zipFile, String folder)
throws IOException, ZipException {
this.zipFile = zipFile;
this.folder = folder;
zip = new ZipFile(zipFile);
}
/**
* Create an instance. The subfolder containing the chunk data will
* be detected.
*
* @param zipFile
* @throws IOException
* @throws ZipException
*/
public ZippedMcRegionChunkStore(File zipFile)
throws IOException, ZipException {
this.zipFile = zipFile;
zip = new ZipFile(zipFile);
}
/**
* Get the input stream for a chunk file.
*
* @param name
* @return
* @throws IOException
* @throws DataException
*/
@Override
protected InputStream getInputStream(String name)
throws IOException, DataException {
String file = "region/" + name;
// Detect subfolder for the world's files
if (folder != null) {
if (!folder.equals("")) {
file = folder + "/" + file;
}
} else {
ZipEntry testEntry = zip.getEntry("level.dat");
// So, the data is not in the root directory
if (testEntry == null) {
// Let's try a world/ sub-directory
testEntry = getEntry("world/level.dat");
Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$");
// So not there either...
if (testEntry == null) {
for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
testEntry = (ZipEntry)e.nextElement();
// Whoo, found level.dat!
if (pattern.matcher(testEntry.getName()).matches()) {
folder = testEntry.getName().replaceAll("level\\.dat$", "");
folder = folder.substring(0, folder.length() - 1);
file = folder + file;
break;
}
}
} else {
file = "world/" + file;
}
}
}
ZipEntry entry = getEntry(file);
if (entry == null) {
throw new MissingChunkException();
}
try {
return zip.getInputStream(entry);
} catch (ZipException e) {
throw new IOException("Failed to read " + file + " in ZIP");
}
}
/**
* Get an entry from the ZIP, trying both types of slashes.
*
* @param file
* @return
*/
private ZipEntry getEntry(String file) {
ZipEntry entry = zip.getEntry(file);
if (entry != null) {
return entry;
}
return zip.getEntry(file.replace("/", "\\"));
}
/**
* Close resources.
*
* @throws IOException
*/
@Override
public void close() throws IOException {
zip.close();
}
@Override
public boolean isValid() {
for (Enumeration<? extends ZipEntry> e = zip.entries();
e.hasMoreElements(); ) {
ZipEntry testEntry = e.nextElement();
if (testEntry.getName().matches(".*\\.mcr$")) {
return true;
}
}
return false;
}
}