mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2025-07-03 03:46:42 +00:00
(Breaking) Moved some packages around.
Most of the changes should not break *most* WorldEdit-using plugins, but implementations of WorldEdit are broken by this change.
This commit is contained in:
37
src/main/java/com/sk89q/worldedit/world/DataException.java
Normal file
37
src/main/java/com/sk89q/worldedit/world/DataException.java
Normal file
@ -0,0 +1,37 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
271
src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk.java
Normal file
271
src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk.java
Normal file
@ -0,0 +1,271 @@
|
||||
package com.sk89q.worldedit.world.chunk;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sk89q.jnbt.ByteArrayTag;
|
||||
import com.sk89q.jnbt.ByteTag;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntTag;
|
||||
import com.sk89q.jnbt.ListTag;
|
||||
import com.sk89q.jnbt.NBTUtils;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldedit.LocalWorld;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.blocks.TileEntityBlock;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||
|
||||
public class AnvilChunk implements Chunk {
|
||||
|
||||
private CompoundTag rootTag;
|
||||
private byte[][] blocks;
|
||||
private byte[][] blocksAdd;
|
||||
private byte[][] data;
|
||||
private int rootX;
|
||||
private int rootZ;
|
||||
|
||||
private Map<BlockVector, Map<String,Tag>> tileEntities;
|
||||
@SuppressWarnings("unused")
|
||||
private LocalWorld world; // TODO: remove if stays unused.
|
||||
|
||||
/**
|
||||
* Construct the chunk with a compound tag.
|
||||
*
|
||||
* @param world the world to construct the chunk for
|
||||
* @param tag the tag to read
|
||||
* @throws DataException on a data error
|
||||
*/
|
||||
public AnvilChunk(LocalWorld world, CompoundTag tag) throws DataException {
|
||||
rootTag = tag;
|
||||
this.world = world;
|
||||
|
||||
rootX = NBTUtils.getChildTag(rootTag.getValue(), "xPos", IntTag.class).getValue();
|
||||
rootZ = NBTUtils.getChildTag(rootTag.getValue(), "zPos", IntTag.class).getValue();
|
||||
|
||||
blocks = new byte[16][16 * 16 * 16];
|
||||
blocksAdd = new byte[16][16 * 16 * 8];
|
||||
data = new byte[16][16 * 16 * 8];
|
||||
|
||||
List<Tag> sections = NBTUtils.getChildTag(rootTag.getValue(), "Sections", ListTag.class).getValue();
|
||||
|
||||
for (Tag rawSectionTag : sections) {
|
||||
if (!(rawSectionTag instanceof CompoundTag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CompoundTag sectionTag = (CompoundTag) rawSectionTag;
|
||||
if (!sectionTag.getValue().containsKey("Y")) {
|
||||
continue; // Empty section.
|
||||
}
|
||||
|
||||
int y = NBTUtils.getChildTag(sectionTag.getValue(), "Y", ByteTag.class).getValue();
|
||||
if (y < 0 || y >= 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
blocks[y] = NBTUtils.getChildTag(sectionTag.getValue(),
|
||||
"Blocks", ByteArrayTag.class).getValue();
|
||||
data[y] = NBTUtils.getChildTag(sectionTag.getValue(), "Data",
|
||||
ByteArrayTag.class).getValue();
|
||||
|
||||
// 4096 ID block support
|
||||
if (sectionTag.getValue().containsKey("Add")) {
|
||||
blocksAdd[y] = NBTUtils.getChildTag(sectionTag.getValue(),
|
||||
"Add", ByteArrayTag.class).getValue();
|
||||
}
|
||||
}
|
||||
|
||||
int sectionsize = 16 * 16 * 16;
|
||||
for (int i = 0; i < blocks.length; i++) {
|
||||
if (blocks[i].length != sectionsize) {
|
||||
throw new InvalidFormatException(
|
||||
"Chunk blocks byte array expected " + "to be "
|
||||
+ sectionsize + " bytes; found "
|
||||
+ blocks[i].length);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i].length != (sectionsize / 2)) {
|
||||
throw new InvalidFormatException("Chunk block data byte array "
|
||||
+ "expected to be " + sectionsize + " bytes; found "
|
||||
+ data[i].length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockID(Vector pos) throws DataException {
|
||||
int x = pos.getBlockX() - rootX * 16;
|
||||
int y = pos.getBlockY();
|
||||
int z = pos.getBlockZ() - rootZ * 16;
|
||||
|
||||
int section = y >> 4;
|
||||
if (section < 0 || section >= blocks.length) {
|
||||
throw new DataException("Chunk does not contain position " + pos);
|
||||
}
|
||||
|
||||
int yindex = y & 0x0F;
|
||||
if (yindex < 0 || yindex >= 16) {
|
||||
throw new DataException("Chunk does not contain position " + pos);
|
||||
}
|
||||
|
||||
int index = x + (z * 16 + (yindex * 16 * 16));
|
||||
|
||||
try {
|
||||
int addId = 0;
|
||||
|
||||
// The block ID is the combination of the Blocks byte array with the
|
||||
// Add byte array. 'Blocks' stores the lowest 8 bits of a block's ID, and
|
||||
// 'Add' stores the highest 4 bits of the ID. The first block is stored
|
||||
// in the lowest nibble in the Add byte array.
|
||||
if (index % 2 == 0) {
|
||||
addId = (blocksAdd[section][index >> 1] & 0x0F) << 8;
|
||||
} else {
|
||||
addId = (blocksAdd[section][index >> 1] & 0xF0) << 4;
|
||||
}
|
||||
|
||||
return (blocks[section][index] & 0xFF) + addId;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new DataException("Chunk does not contain position " + pos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockData(Vector pos) throws DataException {
|
||||
int x = pos.getBlockX() - rootX * 16;
|
||||
int y = pos.getBlockY();
|
||||
int z = pos.getBlockZ() - rootZ * 16;
|
||||
|
||||
int section = y >> 4;
|
||||
int yIndex = y & 0x0F;
|
||||
|
||||
if (section < 0 || section >= blocks.length) {
|
||||
throw new DataException("Chunk does not contain position " + pos);
|
||||
}
|
||||
|
||||
if (yIndex < 0 || yIndex >= 16) {
|
||||
throw new DataException("Chunk does not contain position " + pos);
|
||||
}
|
||||
|
||||
int index = x + (z * 16 + (yIndex * 16 * 16));
|
||||
boolean shift = index % 2 == 0;
|
||||
index /= 2;
|
||||
|
||||
try {
|
||||
if (!shift) {
|
||||
return (data[section][index] & 0xF0) >> 4;
|
||||
} else {
|
||||
return data[section][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 = NBTUtils.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 CompoundTag getBlockTileEntity(Vector pos) throws DataException {
|
||||
if (tileEntities == null) {
|
||||
populateTileEntities();
|
||||
}
|
||||
|
||||
Map<String, Tag> values = tileEntities.get(new BlockVector(pos));
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
return new CompoundTag("", values);
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
CompoundTag tileEntity = getBlockTileEntity(pos);
|
||||
if (tileEntity != null) {
|
||||
((TileEntityBlock) block).setNbtData(tileEntity);
|
||||
}
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
}
|
36
src/main/java/com/sk89q/worldedit/world/chunk/Chunk.java
Normal file
36
src/main/java/com/sk89q/worldedit/world/chunk/Chunk.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.sk89q.worldedit.world.chunk;
|
||||
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
public interface Chunk {
|
||||
|
||||
/**
|
||||
* Get the block ID of a block.
|
||||
*
|
||||
* @param pos
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
public int getBlockID(Vector pos) throws DataException;
|
||||
|
||||
/**
|
||||
* Get the block data of a block.
|
||||
*
|
||||
* @param pos
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
public int getBlockData(Vector pos) throws DataException;
|
||||
|
||||
|
||||
/**
|
||||
* Get a block;
|
||||
*
|
||||
* @param pos
|
||||
* @return block
|
||||
* @throws DataException
|
||||
*/
|
||||
public BaseBlock getBlock(Vector pos) throws DataException;
|
||||
}
|
217
src/main/java/com/sk89q/worldedit/world/chunk/OldChunk.java
Normal file
217
src/main/java/com/sk89q/worldedit/world/chunk/OldChunk.java
Normal file
@ -0,0 +1,217 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.chunk;
|
||||
|
||||
import com.sk89q.jnbt.*;
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldedit.LocalWorld;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.blocks.TileEntityBlock;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a chunk.
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class OldChunk implements Chunk {
|
||||
private CompoundTag rootTag;
|
||||
private byte[] blocks;
|
||||
private byte[] data;
|
||||
private int rootX;
|
||||
private int rootZ;
|
||||
|
||||
private Map<BlockVector, Map<String,Tag>> tileEntities;
|
||||
@SuppressWarnings("unused")
|
||||
private LocalWorld world; // TODO: remove if stays unused.
|
||||
|
||||
/**
|
||||
* Construct the chunk with a compound tag.
|
||||
*
|
||||
* @param tag
|
||||
* @throws DataException
|
||||
*/
|
||||
public OldChunk(LocalWorld world, CompoundTag tag) throws DataException {
|
||||
rootTag = tag;
|
||||
this.world = world;
|
||||
|
||||
|
||||
blocks = NBTUtils.getChildTag(rootTag.getValue(), "Blocks", ByteArrayTag.class).getValue();
|
||||
data = NBTUtils.getChildTag( rootTag.getValue(), "Data", ByteArrayTag.class).getValue();
|
||||
rootX = NBTUtils.getChildTag( rootTag.getValue(), "xPos", IntTag.class).getValue();
|
||||
rootZ = NBTUtils.getChildTag( rootTag.getValue(), "zPos", IntTag.class).getValue();
|
||||
|
||||
int size = 16 * 16 * 128;
|
||||
if (blocks.length != size) {
|
||||
throw new InvalidFormatException("Chunk blocks byte array expected "
|
||||
+ "to be " + size + " bytes; found " + blocks.length);
|
||||
}
|
||||
|
||||
if (data.length != (size/2)) {
|
||||
throw new InvalidFormatException("Chunk block data byte array "
|
||||
+ "expected to be " + size + " bytes; found " + data.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockID(Vector pos) throws DataException {
|
||||
if(pos.getBlockY() >= 128) return 0;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockData(Vector pos) throws DataException {
|
||||
if(pos.getBlockY() >= 128) return 0;
|
||||
|
||||
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 = NBTUtils.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 CompoundTag getBlockTileEntity(Vector pos) throws DataException {
|
||||
if (tileEntities == null) {
|
||||
populateTileEntities();
|
||||
}
|
||||
|
||||
Map<String, Tag> values = tileEntities.get(new BlockVector(pos));
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
return new CompoundTag("", values);
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
CompoundTag tileEntity = getBlockTileEntity(pos);
|
||||
if (tileEntity != null) {
|
||||
((TileEntityBlock) block).setNbtData(tileEntity);
|
||||
}
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class InvalidSnapshotException extends Exception {
|
||||
private static final long serialVersionUID = 7307139106494852893L;
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public class ModificationTimerParser implements SnapshotDateParser {
|
||||
|
||||
public Calendar detectDate(File file) {
|
||||
Calendar cal = new GregorianCalendar();
|
||||
cal.setTimeInMillis(file.lastModified());
|
||||
return cal;
|
||||
}
|
||||
|
||||
}
|
215
src/main/java/com/sk89q/worldedit/world/snapshot/Snapshot.java
Normal file
215
src/main/java/com/sk89q/worldedit/world/snapshot/Snapshot.java
Normal file
@ -0,0 +1,215 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.storage.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class Snapshot implements Comparable<Snapshot> {
|
||||
|
||||
protected static Logger logger = Logger.getLogger("Minecraft.WorldEdit");
|
||||
/**
|
||||
* Stores snapshot file.
|
||||
*/
|
||||
protected File file;
|
||||
/**
|
||||
* Name of the snapshot;
|
||||
*/
|
||||
protected String name;
|
||||
/**
|
||||
* Stores the date associated with the snapshot.
|
||||
*/
|
||||
protected Calendar date;
|
||||
|
||||
/**
|
||||
* Construct a snapshot restoration operation.
|
||||
*
|
||||
* @param repo
|
||||
* @param snapshot
|
||||
*/
|
||||
public Snapshot(SnapshotRepository repo, String snapshot) {
|
||||
file = new File(repo.getDirectory(), snapshot);
|
||||
name = snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chunk store.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws DataException
|
||||
*/
|
||||
public ChunkStore getChunkStore() throws IOException, DataException {
|
||||
ChunkStore chunkStore = _getChunkStore();
|
||||
|
||||
logger.info("WorldEdit: Using " + chunkStore.getClass().getCanonicalName()
|
||||
+ " for loading snapshot '" + file.getAbsolutePath() + "'");
|
||||
|
||||
return chunkStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chunk store.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws DataException
|
||||
*/
|
||||
public ChunkStore _getChunkStore() throws IOException, DataException {
|
||||
if (file.getName().toLowerCase().endsWith(".zip")) {
|
||||
try {
|
||||
ChunkStore chunkStore = new TrueZipMcRegionChunkStore(file);
|
||||
|
||||
if (!chunkStore.isValid()) {
|
||||
return new TrueZipLegacyChunkStore(file);
|
||||
}
|
||||
|
||||
return chunkStore;
|
||||
} catch (NoClassDefFoundError e) {
|
||||
ChunkStore chunkStore = new ZippedMcRegionChunkStore(file);
|
||||
|
||||
if (!chunkStore.isValid()) {
|
||||
return new ZippedLegacyChunkStore(file);
|
||||
}
|
||||
|
||||
return chunkStore;
|
||||
}
|
||||
} else if (file.getName().toLowerCase().endsWith(".tar.bz2")
|
||||
|| file.getName().toLowerCase().endsWith(".tar.gz")
|
||||
|| file.getName().toLowerCase().endsWith(".tar")) {
|
||||
try {
|
||||
ChunkStore chunkStore = new TrueZipMcRegionChunkStore(file);
|
||||
|
||||
if (!chunkStore.isValid()) {
|
||||
return new TrueZipLegacyChunkStore(file);
|
||||
}
|
||||
|
||||
return chunkStore;
|
||||
} catch (NoClassDefFoundError e) {
|
||||
throw new DataException("TrueZIP is required for .tar support");
|
||||
}
|
||||
} else {
|
||||
ChunkStore chunkStore = new FileMcRegionChunkStore(file);
|
||||
|
||||
if (!chunkStore.isValid()) {
|
||||
return new FileLegacyChunkStore(file);
|
||||
}
|
||||
|
||||
return chunkStore;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the zip/tar file it contains the given world.
|
||||
*
|
||||
* @return true if the zip/tar file contains the given world
|
||||
*/
|
||||
public boolean containsWorld(String worldname) {
|
||||
try {
|
||||
if (file.getName().toLowerCase().endsWith(".zip")) {
|
||||
ZipFile entry = new ZipFile(file);
|
||||
return (entry.getEntry(worldname) != null
|
||||
|| entry.getEntry(worldname + "/level.dat") != null);
|
||||
} else if (file.getName().toLowerCase().endsWith(".tar.bz2")
|
||||
|| file.getName().toLowerCase().endsWith(".tar.gz")
|
||||
|| file.getName().toLowerCase().endsWith(".tar")) {
|
||||
try {
|
||||
de.schlichtherle.util.zip.ZipFile entry = new de.schlichtherle.util.zip.ZipFile(file);
|
||||
|
||||
return entry.getEntry(worldname) != null;
|
||||
} catch (NoClassDefFoundError e) {
|
||||
throw new DataException("TrueZIP is required for .tar support");
|
||||
}
|
||||
} else {
|
||||
return (file.getName().equalsIgnoreCase(worldname));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// Skip the file, but print an error
|
||||
logger.info("Could not load snapshot: "
|
||||
+ file.getPath());
|
||||
} catch (DataException ex) {
|
||||
// No truezip, so tar file not supported.
|
||||
// Dont print, just skip the file.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the snapshot's name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file for the snapshot.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date associated with this snapshot.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Calendar getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date of the snapshot.
|
||||
*
|
||||
* @param date
|
||||
*/
|
||||
public void setDate(Calendar date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public int compareTo(Snapshot o) {
|
||||
if (o.date == null || date == null) {
|
||||
// Remove the folder from the name
|
||||
int i = name.indexOf("/"), j = o.name.indexOf("/");
|
||||
return name.substring((i > 0 ? 0 : i)).compareTo(o.name.substring((j > 0 ? 0 : j)));
|
||||
} else {
|
||||
return date.compareTo(o.date);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Snapshot) {
|
||||
return file.equals(((Snapshot) o).file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* A name parser attempts to make sense of a filename for a snapshot.
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public interface SnapshotDateParser {
|
||||
/**
|
||||
* Attempt to detect a date from a file.
|
||||
*
|
||||
* @param file
|
||||
* @return date or null
|
||||
*/
|
||||
public Calendar detectDate(File file);
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.sk89q.worldedit.world.storage.MissingWorldException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class SnapshotRepository {
|
||||
|
||||
/**
|
||||
* Stores the directory the snapshots come from.
|
||||
*/
|
||||
protected File dir;
|
||||
/**
|
||||
* List of date parsers.
|
||||
*/
|
||||
protected List<SnapshotDateParser> dateParsers = new ArrayList<SnapshotDateParser>();
|
||||
|
||||
/**
|
||||
* Create a new instance of a repository.
|
||||
*
|
||||
* @param dir
|
||||
*/
|
||||
public SnapshotRepository(File dir) {
|
||||
this.dir = dir;
|
||||
// If folder dont exist, make it.
|
||||
dir.mkdirs();
|
||||
|
||||
dateParsers.add(new YYMMDDHHIISSParser());
|
||||
dateParsers.add(new ModificationTimerParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a repository.
|
||||
*
|
||||
* @param dir
|
||||
*/
|
||||
public SnapshotRepository(String dir) {
|
||||
this(new File(dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of snapshots in a directory. The newest snapshot is
|
||||
* near the top of the array.
|
||||
*
|
||||
* @param newestFirst
|
||||
* @return
|
||||
*/
|
||||
public List<Snapshot> getSnapshots(boolean newestFirst, String worldname) throws MissingWorldException {
|
||||
FilenameFilter filter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
File f = new File(dir, name);
|
||||
return isValidSnapshot(f);
|
||||
}
|
||||
};
|
||||
|
||||
File[] snapshotFiles = dir.listFiles();
|
||||
if (snapshotFiles == null) {
|
||||
throw new MissingWorldException(worldname);
|
||||
}
|
||||
List<Snapshot> list = new ArrayList<Snapshot>(snapshotFiles.length);
|
||||
|
||||
for (File file : snapshotFiles) {
|
||||
if (isValidSnapshot(file)) {
|
||||
Snapshot snapshot = new Snapshot(this, file.getName());
|
||||
if (snapshot.containsWorld(worldname)) {
|
||||
detectDate(snapshot);
|
||||
list.add(snapshot);
|
||||
}
|
||||
} else if (file.isDirectory() && file.getName().equalsIgnoreCase(worldname)) {
|
||||
for (String name : file.list(filter)) {
|
||||
Snapshot snapshot = new Snapshot(this, file.getName() + "/" + name);
|
||||
detectDate(snapshot);
|
||||
list.add(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newestFirst) {
|
||||
Collections.sort(list, Collections.reverseOrder());
|
||||
} else {
|
||||
Collections.sort(list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first snapshot after a date.
|
||||
*
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
public Snapshot getSnapshotAfter(Calendar date, String world) throws MissingWorldException {
|
||||
List<Snapshot> snapshots = getSnapshots(true, world);
|
||||
Snapshot last = null;
|
||||
|
||||
for (Snapshot snapshot : snapshots) {
|
||||
if (snapshot.getDate() != null
|
||||
&& snapshot.getDate().before(date)) {
|
||||
return last;
|
||||
}
|
||||
|
||||
last = snapshot;
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first snapshot before a date.
|
||||
*
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
public Snapshot getSnapshotBefore(Calendar date, String world) throws MissingWorldException {
|
||||
List<Snapshot> snapshots = getSnapshots(false, world);
|
||||
Snapshot last = null;
|
||||
|
||||
for (Snapshot snapshot : snapshots) {
|
||||
if (snapshot.getDate().after(date)) {
|
||||
return last;
|
||||
}
|
||||
|
||||
last = snapshot;
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to detect a snapshot's date and assign it.
|
||||
*
|
||||
* @param snapshot
|
||||
*/
|
||||
protected void detectDate(Snapshot snapshot) {
|
||||
for (SnapshotDateParser parser : dateParsers) {
|
||||
Calendar date = parser.detectDate(snapshot.getFile());
|
||||
if (date != null) {
|
||||
snapshot.setDate(date);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snapshot.setDate(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default snapshot.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Snapshot getDefaultSnapshot(String world) throws MissingWorldException {
|
||||
List<Snapshot> snapshots = getSnapshots(true, world);
|
||||
|
||||
if (snapshots.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return snapshots.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a snapshot is valid.
|
||||
*
|
||||
* @param snapshot
|
||||
* @return whether it is a valid snapshot
|
||||
*/
|
||||
public boolean isValidSnapshotName(String snapshot) {
|
||||
return isValidSnapshot(new File(dir, snapshot));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a snapshot is valid.
|
||||
*
|
||||
* @param f
|
||||
* @return whether it is a valid snapshot
|
||||
*/
|
||||
protected boolean isValidSnapshot(File f) {
|
||||
if (!f.getName().matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (f.isDirectory() && (new File(f, "level.dat")).exists())
|
||||
|| (f.isFile() && (f.getName().toLowerCase().endsWith(".zip")
|
||||
|| f.getName().toLowerCase().endsWith(".tar.bz2")
|
||||
|| f.getName().toLowerCase().endsWith(".tar.gz")
|
||||
|| f.getName().toLowerCase().endsWith(".tar")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a snapshot.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @throws InvalidSnapshotException
|
||||
*/
|
||||
public Snapshot getSnapshot(String name) throws InvalidSnapshotException {
|
||||
if (!isValidSnapshotName(name)) {
|
||||
throw new InvalidSnapshotException();
|
||||
}
|
||||
|
||||
return new Snapshot(this, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the snapshot directory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public File getDirectory() {
|
||||
return dir;
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sk89q.worldedit.BlockVector2D;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.Vector2D;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.world.chunk.Chunk;
|
||||
import com.sk89q.worldedit.world.storage.ChunkStore;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.storage.MissingChunkException;
|
||||
import com.sk89q.worldedit.world.storage.MissingWorldException;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class SnapshotRestore {
|
||||
/**
|
||||
* Store a list of chunks that are needed and the points in them.
|
||||
*/
|
||||
private final Map<BlockVector2D, ArrayList<Vector>> neededChunks =
|
||||
new LinkedHashMap<BlockVector2D, ArrayList<Vector>>();
|
||||
/**
|
||||
* Chunk store.
|
||||
*/
|
||||
private final ChunkStore chunkStore;
|
||||
/**
|
||||
* Edit session.
|
||||
*/
|
||||
private final EditSession editSession;
|
||||
/**
|
||||
* Count of the number of missing chunks.
|
||||
*/
|
||||
private ArrayList<Vector2D> missingChunks;
|
||||
/**
|
||||
* Count of the number of chunks that could be loaded for other reasons.
|
||||
*/
|
||||
private ArrayList<Vector2D> errorChunks;
|
||||
/**
|
||||
* Last error message.
|
||||
*/
|
||||
private String lastErrorMessage;
|
||||
|
||||
/**
|
||||
* Construct the snapshot restore operation.
|
||||
*
|
||||
* @param chunkStore The {@link ChunkStore} to restore from
|
||||
* @param editSession The {@link EditSession} to restore to
|
||||
* @param region The {@link Region} to restore to
|
||||
*/
|
||||
public SnapshotRestore(ChunkStore chunkStore, EditSession editSession, Region region) {
|
||||
this.chunkStore = chunkStore;
|
||||
this.editSession = editSession;
|
||||
|
||||
if (region instanceof CuboidRegion) {
|
||||
findNeededCuboidChunks(region);
|
||||
} else {
|
||||
findNeededChunks(region);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find needed chunks in the axis-aligned bounding box of the region.
|
||||
*
|
||||
* @param region The {@link Region} to iterate
|
||||
*/
|
||||
private void findNeededCuboidChunks(Region region) {
|
||||
Vector min = region.getMinimumPoint();
|
||||
Vector max = region.getMaximumPoint();
|
||||
|
||||
// First, we need to group points by chunk so that we only need
|
||||
// to keep one chunk in memory at any given moment
|
||||
for (int x = min.getBlockX(); x <= max.getBlockX(); ++x) {
|
||||
for (int y = min.getBlockY(); y <= max.getBlockY(); ++y) {
|
||||
for (int z = min.getBlockZ(); z <= max.getBlockZ(); ++z) {
|
||||
Vector pos = new Vector(x, y, z);
|
||||
checkAndAddBlock(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find needed chunks in the region.
|
||||
*
|
||||
* @param region The {@link Region} to iterate
|
||||
*/
|
||||
private void findNeededChunks(Region region) {
|
||||
// First, we need to group points by chunk so that we only need
|
||||
// to keep one chunk in memory at any given moment
|
||||
for (Vector pos : region) {
|
||||
checkAndAddBlock(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndAddBlock(Vector pos) {
|
||||
if (editSession.getMask() != null && !editSession.getMask().matches(editSession, pos))
|
||||
return;
|
||||
|
||||
BlockVector2D chunkPos = ChunkStore.toChunk(pos);
|
||||
|
||||
// Unidentified chunk
|
||||
if (!neededChunks.containsKey(chunkPos)) {
|
||||
neededChunks.put(chunkPos, new ArrayList<Vector>());
|
||||
}
|
||||
|
||||
neededChunks.get(chunkPos).add(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of chunks that are needed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getChunksAffected() {
|
||||
return neededChunks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores to world.
|
||||
*
|
||||
* @throws MaxChangedBlocksException
|
||||
*/
|
||||
public void restore() throws MaxChangedBlocksException {
|
||||
|
||||
missingChunks = new ArrayList<Vector2D>();
|
||||
errorChunks = new ArrayList<Vector2D>();
|
||||
|
||||
// Now let's start restoring!
|
||||
for (Map.Entry<BlockVector2D, ArrayList<Vector>> entry : neededChunks.entrySet()) {
|
||||
BlockVector2D chunkPos = entry.getKey();
|
||||
Chunk chunk;
|
||||
|
||||
try {
|
||||
chunk = chunkStore.getChunk(chunkPos, editSession.getWorld());
|
||||
// Good, the chunk could be at least loaded
|
||||
|
||||
// Now just copy blocks!
|
||||
for (Vector pos : entry.getValue()) {
|
||||
try {
|
||||
BaseBlock block = chunk.getBlock(pos);
|
||||
editSession.rawSetBlock(pos, block);
|
||||
} catch (DataException e) {
|
||||
// this is a workaround: just ignore for now
|
||||
}
|
||||
}
|
||||
} catch (MissingChunkException me) {
|
||||
missingChunks.add(chunkPos);
|
||||
} catch (MissingWorldException me) {
|
||||
errorChunks.add(chunkPos);
|
||||
lastErrorMessage = me.getMessage();
|
||||
} catch (DataException de) {
|
||||
errorChunks.add(chunkPos);
|
||||
lastErrorMessage = de.getMessage();
|
||||
} catch (IOException ioe) {
|
||||
errorChunks.add(chunkPos);
|
||||
lastErrorMessage = ioe.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the missing chunks. restore() must have been called
|
||||
* already.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Vector2D> getMissingChunks() {
|
||||
return missingChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the chunks that could not have been loaded for other
|
||||
* reasons. restore() must have been called already.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Vector2D> getErrorChunks() {
|
||||
return errorChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see where the backup succeeded in any capacity. False will
|
||||
* be returned if no chunk could be successfully loaded.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hadTotalFailure() {
|
||||
return missingChunks.size() + errorChunks.size() == getChunksAffected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastErrorMessage
|
||||
*/
|
||||
public String getLastErrorMessage() {
|
||||
return lastErrorMessage;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.snapshot;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class YYMMDDHHIISSParser implements SnapshotDateParser {
|
||||
|
||||
protected Pattern patt =
|
||||
Pattern.compile("([0-9]+)[^0-9]?([0-9]+)[^0-9]?([0-9]+)[^0-9]?"
|
||||
+ "([0-9]+)[^0-9]?([0-9]+)[^0-9]?([0-9]+)");
|
||||
|
||||
public Calendar detectDate(File file) {
|
||||
Matcher matcher = patt.matcher(file.getName());
|
||||
if (matcher.matches()) {
|
||||
int year = Integer.parseInt(matcher.group(1));
|
||||
int month = Integer.parseInt(matcher.group(2));
|
||||
int day = Integer.parseInt(matcher.group(3));
|
||||
int hrs = Integer.parseInt(matcher.group(4));
|
||||
int min = Integer.parseInt(matcher.group(5));
|
||||
int sec = Integer.parseInt(matcher.group(6));
|
||||
Calendar calender = new GregorianCalendar();
|
||||
calender.set(year, month, day, hrs, min, sec);
|
||||
return calender;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
124
src/main/java/com/sk89q/worldedit/world/storage/BlockData.java
Normal file
124
src/main/java/com/sk89q/worldedit/world/storage/BlockData.java
Normal file
@ -0,0 +1,124 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.CuboidClipboard.FlipDirection;
|
||||
|
||||
/**
|
||||
* Block data related classes.
|
||||
*
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData}
|
||||
* @author sk89q
|
||||
*/
|
||||
@Deprecated
|
||||
public final class BlockData {
|
||||
/**
|
||||
* Rotate a block's data value 90 degrees (north->east->south->west->north);
|
||||
*
|
||||
* @param type
|
||||
* @param data
|
||||
* @return
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData#rotate90(int, int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int rotate90(int type, int data) {
|
||||
return com.sk89q.worldedit.blocks.BlockData.rotate90(type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate a block's data value -90 degrees (north<-east<-south<-west<-north);
|
||||
*
|
||||
* @param type
|
||||
* @param data
|
||||
* @return
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData#rotate90Reverse(int, int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int rotate90Reverse(int type, int data) {
|
||||
return com.sk89q.worldedit.blocks.BlockData.rotate90Reverse(type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip a block's data value.
|
||||
*
|
||||
* @param type
|
||||
* @param data
|
||||
* @return
|
||||
* @deprecated use return {@link com.sk89q.worldedit.blocks.BlockData#flip(int, int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int flip(int type, int data) {
|
||||
return rotate90(type, rotate90(type, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip a block's data value.
|
||||
*
|
||||
* @param type
|
||||
* @param data
|
||||
* @param direction
|
||||
* @return
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData#flip(int, int, FlipDirection)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int flip(int type, int data, FlipDirection direction) {
|
||||
return com.sk89q.worldedit.blocks.BlockData.flip(type, data, direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle a block's data value. This usually goes through some rotational pattern
|
||||
* depending on the block. If it returns -1, it means the id and data specified
|
||||
* do not have anything to cycle to.
|
||||
*
|
||||
* @param type block id to be cycled
|
||||
* @param data block data value that it starts at
|
||||
* @param increment whether to go forward (1) or backward (-1) in the cycle
|
||||
* @return the new data value for the block
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData#cycle(int, int, int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int cycle(int type, int data, int increment) {
|
||||
return com.sk89q.worldedit.blocks.BlockData.cycle(type, data, increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data value for the next color of cloth in the rainbow. This
|
||||
* should not be used if you want to just increment the data value.
|
||||
* @param data
|
||||
* @return
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData#nextClothColor(int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int nextClothColor(int data) {
|
||||
return com.sk89q.worldedit.blocks.BlockData.nextClothColor(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data value for the previous ext color of cloth in the rainbow.
|
||||
* This should not be used if you want to just increment the data value.
|
||||
* @param data
|
||||
* @return
|
||||
* @deprecated use {@link com.sk89q.worldedit.blocks.BlockData#prevClothColor(int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static int prevClothColor(int data) {
|
||||
return com.sk89q.worldedit.blocks.BlockData.prevClothColor(data);
|
||||
}
|
||||
}
|
102
src/main/java/com/sk89q/worldedit/world/storage/ChunkStore.java
Normal file
102
src/main/java/com/sk89q/worldedit/world/storage/ChunkStore.java
Normal file
@ -0,0 +1,102 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sk89q.jnbt.*;
|
||||
import com.sk89q.worldedit.*;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.chunk.AnvilChunk;
|
||||
import com.sk89q.worldedit.world.chunk.Chunk;
|
||||
import com.sk89q.worldedit.world.chunk.OldChunk;
|
||||
|
||||
/**
|
||||
* Represents chunk storage mechanisms.
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public abstract class ChunkStore {
|
||||
/**
|
||||
* >> to chunk
|
||||
* << from chunk
|
||||
*/
|
||||
public static final int CHUNK_SHIFTS = 4;
|
||||
/**
|
||||
* 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, LocalWorld world)
|
||||
throws DataException, IOException;
|
||||
|
||||
/**
|
||||
* Get a chunk at a location.
|
||||
*
|
||||
* @param pos
|
||||
* @return
|
||||
* @throws ChunkStoreException
|
||||
* @throws IOException
|
||||
* @throws DataException
|
||||
*/
|
||||
public Chunk getChunk(Vector2D pos, LocalWorld world)
|
||||
throws DataException, IOException {
|
||||
|
||||
CompoundTag tag = getChunkTag(pos, world);
|
||||
Map<String, Tag> tags = tag.getValue();
|
||||
if(tags.containsKey("Sections")) {
|
||||
return new AnvilChunk(world, tag);
|
||||
}
|
||||
return new OldChunk(world, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close resources.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the chunk store is of this type.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract boolean isValid();
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class ChunkStoreException extends DataException {
|
||||
private static final long serialVersionUID = 1483900743779953289L;
|
||||
|
||||
public ChunkStoreException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ChunkStoreException() {
|
||||
super();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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, String world) throws IOException,
|
||||
DataException {
|
||||
Pattern ext = Pattern.compile(".*\\.mc[ra]$"); // allow either file extension, both work the same
|
||||
File file = null;
|
||||
for (File f : new File(path, "region" + File.separator).listFiles()) {
|
||||
String tempName = f.getName().replaceFirst("mcr$", "mca"); // matcher only does one at a time
|
||||
if (ext.matcher(f.getName()).matches() && name.equalsIgnoreCase(tempName)) {
|
||||
// get full original path now
|
||||
file = new File(path + File.separator + "region" + File.separator + f.getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (file == null) throw new FileNotFoundException();
|
||||
return new FileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new MissingChunkException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return new File(path, "region").isDirectory() ||
|
||||
new File(path, "DIM-1" + File.separator + "region").isDirectory();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class InvalidFormatException extends DataException {
|
||||
private static final long serialVersionUID = -3401820540902726145L;
|
||||
|
||||
public InvalidFormatException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.*;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
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, LocalWorld world)
|
||||
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 {
|
||||
nbt.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;
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
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.LocalWorld;
|
||||
import com.sk89q.worldedit.Vector2D;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
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) + ".mca";
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
protected McRegionReader getReader(Vector2D pos, String worldname) 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, worldname);
|
||||
cachedReader = new McRegionReader(stream);
|
||||
//curFilename = filename;
|
||||
return cachedReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getChunkTag(Vector2D pos, LocalWorld world) throws DataException,
|
||||
IOException {
|
||||
|
||||
McRegionReader reader = getReader(pos, world.getName());
|
||||
|
||||
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 {
|
||||
nbt.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input stream for a chunk file.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
protected abstract InputStream getInputStream(String name, String worldname)
|
||||
throws IOException, DataException;
|
||||
|
||||
/**
|
||||
* Close resources.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (cachedReader != null) {
|
||||
cachedReader.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import com.sk89q.worldedit.Vector2D;
|
||||
import com.sk89q.worldedit.util.io.ForwardSeekableInputStream;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
throw new DataException("The chunk at " + x + "," + z + " is not generated");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sk89q
|
||||
*/
|
||||
public class MissingWorldException extends ChunkStoreException {
|
||||
|
||||
private static final long serialVersionUID = 6487395784195658467L;
|
||||
|
||||
private String worldname;
|
||||
|
||||
public MissingWorldException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MissingWorldException(String worldname) {
|
||||
super();
|
||||
this.worldname = worldname;
|
||||
}
|
||||
|
||||
public MissingWorldException(String msg, String worldname) {
|
||||
super(msg);
|
||||
this.worldname = worldname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of the world in question. May be null if unknown.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getWorldname() {
|
||||
return worldname;
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import de.schlichtherle.util.zip.ZipEntry;
|
||||
import de.schlichtherle.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import de.schlichtherle.util.zip.ZipEntry;
|
||||
import de.schlichtherle.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* 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, String worldname)
|
||||
throws IOException, DataException {
|
||||
|
||||
// Detect subfolder for the world's files
|
||||
if (folder != null) {
|
||||
if (!folder.equals("")) {
|
||||
name = folder + "/" + name;
|
||||
}
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile(".*\\.mc[ra]$");
|
||||
// World pattern
|
||||
Pattern worldPattern = Pattern.compile(worldname + "\\$");
|
||||
for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) {
|
||||
ZipEntry testEntry = (ZipEntry) e.nextElement();
|
||||
// Check for world
|
||||
if (worldPattern.matcher(worldname).matches()) {
|
||||
// Check for file
|
||||
if (pattern.matcher(testEntry.getName()).matches()) {
|
||||
folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/"));
|
||||
name = folder + "/" + name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if world is found
|
||||
if (folder == null) {
|
||||
throw new MissingWorldException("Target world is not present in ZIP.", worldname);
|
||||
}
|
||||
}
|
||||
|
||||
ZipEntry entry = getEntry(name);
|
||||
if (entry == null) {
|
||||
throw new MissingChunkException();
|
||||
}
|
||||
try {
|
||||
return zip.getInputStream(entry);
|
||||
} catch (ZipException e) {
|
||||
throw new IOException("Failed to read " + name + " 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$") || testEntry.getName().matches(".*\\.mca$")) { // TODO: does this need a separate class?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.zip.ZipFile;
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
// $Id$
|
||||
/*
|
||||
* WorldEdit
|
||||
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
|
||||
*
|
||||
* 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.world.storage;
|
||||
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.zip.ZipFile;
|
||||
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, String worldname)
|
||||
throws IOException, DataException {
|
||||
|
||||
// Detect subfolder for the world's files
|
||||
if (folder != null) {
|
||||
if (!folder.equals("")) {
|
||||
name = folder + "/" + name;
|
||||
}
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile(".*\\.mc[ra]$");
|
||||
for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) {
|
||||
ZipEntry testEntry = (ZipEntry) e.nextElement();
|
||||
// Check for world
|
||||
if (testEntry.getName().startsWith(worldname + "/")) {
|
||||
if (pattern.matcher(testEntry.getName()).matches()) { // does entry end in .mca
|
||||
folder = testEntry.getName().substring(0, testEntry.getName().lastIndexOf("/"));
|
||||
name = folder + "/" + name;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Check if world is found
|
||||
if (folder == null) {
|
||||
throw new MissingWorldException("Target world is not present in ZIP.", worldname);
|
||||
}
|
||||
}
|
||||
|
||||
ZipEntry entry = getEntry(name);
|
||||
if (entry == null) {
|
||||
throw new MissingChunkException();
|
||||
}
|
||||
try {
|
||||
return zip.getInputStream(entry);
|
||||
} catch (ZipException e) {
|
||||
throw new IOException("Failed to read " + name + " 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$") || testEntry.getName().matches(".*\\.mca$")) { // TODO: does this need a separate class?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user