|
| 1 | +package com.ljyloo.PE2PC; |
| 2 | + |
| 3 | +import org.iq80.leveldb.*; |
| 4 | + |
| 5 | +import com.mojang.nbt.CompoundTag; |
| 6 | +import com.mojang.nbt.ListTag; |
| 7 | +import com.mojang.nbt.NbtIo; |
| 8 | + |
| 9 | +import net.minecraft.world.level.chunk.DataLayer; |
| 10 | +import net.minecraft.world.level.chunk.OldDataLayer; |
| 11 | +import net.minecraft.world.level.chunk.storage.*; |
| 12 | +import static org.iq80.leveldb.impl.Iq80DBFactory.*; |
| 13 | + |
| 14 | +import java.io.*; |
| 15 | +import java.util.HashMap; |
| 16 | +import java.util.Iterator; |
| 17 | +import java.util.Map.Entry; |
| 18 | + |
| 19 | +public class PE2PC { |
| 20 | + private final static int DATALAYER_BITS = 7; |
| 21 | + private final static int BLOCKDATA_BYTES = 32768; |
| 22 | + private final static int METADATA_BYTES = 16384; |
| 23 | + private final static int SKYLIGHTDATA_BYTES = 16384; |
| 24 | + private final static int BLOCKLIGHTDATA_BYTES = 16384; |
| 25 | + |
| 26 | + public static void main(String[] args) throws IOException { |
| 27 | + //System.out.println((-1 % 32 + 32) % 32); |
| 28 | + if (args.length != 2) { |
| 29 | + printUsageAndExit(); |
| 30 | + } |
| 31 | + |
| 32 | + File srcFolder; |
| 33 | + try { |
| 34 | + srcFolder = new File(args[0]); |
| 35 | + if (!srcFolder.exists()) { |
| 36 | + throw new RuntimeException(args[0] + " doesn't exist"); |
| 37 | + } else if (!srcFolder.isDirectory()) { |
| 38 | + throw new RuntimeException(args[0] + " is not a folder"); |
| 39 | + } |
| 40 | + } catch (Exception e) { |
| 41 | + System.err.println("import folder problem: " + e.getMessage()); |
| 42 | + System.out.println(""); |
| 43 | + printUsageAndExit(); |
| 44 | + return; |
| 45 | + } |
| 46 | + |
| 47 | + File desFolder; |
| 48 | + try { |
| 49 | + desFolder = new File(args[0]+"/db"); |
| 50 | + if (!desFolder.exists()) { |
| 51 | + throw new RuntimeException(args[0] + " doesn't exist"); |
| 52 | + } else if (!desFolder.isDirectory()) { |
| 53 | + throw new RuntimeException(args[0] + " is not a folder"); |
| 54 | + } |
| 55 | + } catch (Exception e) { |
| 56 | + System.err.println("export folder problem: " + e.getMessage()); |
| 57 | + System.out.println(""); |
| 58 | + printUsageAndExit(); |
| 59 | + return; |
| 60 | + } |
| 61 | + |
| 62 | + convert(srcFolder, desFolder); |
| 63 | + } |
| 64 | + |
| 65 | + private static void printUsageAndExit() { |
| 66 | + System.out.println("Map converter for Minecraft:Pocket Edition, from format \"LevelDB\" to \"Anvil\". (c) ljyloo 2015"); |
| 67 | + System.out.println(""); |
| 68 | + System.out.println("Usage:"); |
| 69 | + System.out.println("\tjava -jar Converter.jar <import folder> <export folder>"); |
| 70 | + System.out.println("Where:"); |
| 71 | + System.out.println("\t<import folder>\tThe full path to the folder containing Minecraft:Pocket Edition world"); |
| 72 | + System.out.println("\t<export folder>\tThe full path to the folder which you want to export"); |
| 73 | + System.out.println("Example:"); |
| 74 | + System.out.println("\tjava -jar Converter.jar /home/ljyloo/import /home/ljyloo/export"); |
| 75 | + System.exit(1); |
| 76 | + } |
| 77 | + |
| 78 | + public static void convert(File src, File des) throws IOException{ |
| 79 | + DB db = null; |
| 80 | + try{ |
| 81 | + Options options = new Options(); |
| 82 | + options.createIfMissing(true); |
| 83 | + db = factory.open(src, options); |
| 84 | + |
| 85 | + DBIterator iterator = db.iterator(); |
| 86 | + //ArrayList<byte[]> keys = new ArrayList<byte[]>(); |
| 87 | + HashMap<String, RegionFile> regions = new HashMap<String, RegionFile>(); |
| 88 | + try{ |
| 89 | + for(iterator.seekToFirst(); iterator.hasNext(); iterator.next()){ |
| 90 | + byte[] key = iterator.peekNext().getKey(); |
| 91 | + if(key.length == 9 && key[8] == 0x30){ |
| 92 | + byte[] value = iterator.peekNext().getValue(); |
| 93 | + int chunkX = byteArrayToInt(new byte[]{key[3], key[2], key[1], key[0]}); |
| 94 | + int chunkZ = byteArrayToInt(new byte[]{key[7], key[6], key[5], key[4]}); |
| 95 | + System.out.println("Converting chunk X:"+chunkX+" Z:"+chunkZ); |
| 96 | + CompoundTag tag = new CompoundTag(); |
| 97 | + CompoundTag levelData = new CompoundTag(); |
| 98 | + tag.put("Level", levelData); |
| 99 | + |
| 100 | + levelData.putByte("LightPopulated", (byte)1); |
| 101 | + levelData.putByte("TerrainPopulated", (byte)1); |
| 102 | + levelData.putByte("V", (byte)1); |
| 103 | + levelData.putInt("xPos", chunkX); |
| 104 | + levelData.putInt("zPos", chunkZ); |
| 105 | + levelData.putLong("InhabitedTime", 0); |
| 106 | + levelData.putLong("LastUpdate", 0); |
| 107 | + byte[] biomes = new byte[16 * 16]; |
| 108 | + for(int i = 0; i <256; i++) |
| 109 | + biomes[i] = -1; |
| 110 | + levelData.putByteArray("Biomes", biomes); |
| 111 | + levelData.put("Entities", new ListTag<CompoundTag>("Entities")); |
| 112 | + |
| 113 | + ListTag<CompoundTag> sectionTags = new ListTag<CompoundTag>("Sections"); |
| 114 | + |
| 115 | + LevelDBChunk data = new LevelDBChunk(chunkX, chunkZ); |
| 116 | + |
| 117 | + data.blocks = new byte[BLOCKDATA_BYTES]; |
| 118 | + System.arraycopy(value, 0, data.blocks, 0, BLOCKDATA_BYTES); |
| 119 | + |
| 120 | + byte[] metadata = new byte[METADATA_BYTES]; |
| 121 | + System.arraycopy(value, BLOCKDATA_BYTES, metadata, 0, METADATA_BYTES); |
| 122 | + data.data = new OldDataLayer(metadata, DATALAYER_BITS); |
| 123 | + |
| 124 | + byte[] skyLightData = new byte[SKYLIGHTDATA_BYTES]; |
| 125 | + System.arraycopy(value, BLOCKDATA_BYTES + METADATA_BYTES, skyLightData, 0, SKYLIGHTDATA_BYTES); |
| 126 | + data.skyLight = new OldDataLayer(skyLightData, DATALAYER_BITS); |
| 127 | + |
| 128 | + byte[] blockLightData = new byte[BLOCKLIGHTDATA_BYTES]; |
| 129 | + System.arraycopy(value, BLOCKDATA_BYTES + METADATA_BYTES + SKYLIGHTDATA_BYTES, blockLightData, 0, BLOCKLIGHTDATA_BYTES); |
| 130 | + data.blockLight = new OldDataLayer(blockLightData, DATALAYER_BITS); |
| 131 | + |
| 132 | + for (int yBase = 0; yBase < (128 / 16); yBase++) { |
| 133 | + |
| 134 | + // find non-air |
| 135 | + boolean allAir = true; |
| 136 | + for (int x = 0; x < 16 && allAir; x++) { |
| 137 | + for (int y = 0; y < 16 && allAir; y++) { |
| 138 | + for (int z = 0; z < 16; z++) { |
| 139 | + int pos = (x << 11) | (z << 7) | (y + (yBase << 4)); |
| 140 | + int block = data.blocks[pos]; |
| 141 | + if (block != 0) { |
| 142 | + allAir = false; |
| 143 | + break; |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + if (allAir) { |
| 150 | + continue; |
| 151 | + } |
| 152 | + |
| 153 | + // build section |
| 154 | + byte[] blocks = new byte[16 * 16 * 16]; |
| 155 | + DataLayer dataValues = new DataLayer(blocks.length, 4); |
| 156 | + DataLayer skyLight = new DataLayer(blocks.length, 4); |
| 157 | + DataLayer blockLight = new DataLayer(blocks.length, 4); |
| 158 | + |
| 159 | + for (int x = 0; x < 16; x++) { |
| 160 | + for (int y = 0; y < 16; y++) { |
| 161 | + for (int z = 0; z < 16; z++) { |
| 162 | + int pos = (x << 11) | (z << 7) | (y + (yBase << 4)); |
| 163 | + int block = data.blocks[pos]; |
| 164 | + |
| 165 | + blocks[(y << 8) | (z << 4) | x] = (byte) (block & 0xff); |
| 166 | + dataValues.set(x, y, z, data.data.get(x, y + (yBase << 4), z)); |
| 167 | + skyLight.set(x, y, z, data.skyLight.get(x, y + (yBase << 4), z)); |
| 168 | + blockLight.set(x, y, z, data.blockLight.get(x, y + (yBase << 4), z)); |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + CompoundTag sectionTag = new CompoundTag(); |
| 174 | + |
| 175 | + sectionTag.putByte("Y", (byte) (yBase & 0xff)); |
| 176 | + sectionTag.putByteArray("Blocks", blocks); |
| 177 | + sectionTag.putByteArray("Data", dataValues.data); |
| 178 | + sectionTag.putByteArray("SkyLight", skyLight.data); |
| 179 | + sectionTag.putByteArray("BlockLight", blockLight.data); |
| 180 | + |
| 181 | + sectionTags.add(sectionTag); |
| 182 | + } |
| 183 | + levelData.put("Sections", sectionTags); |
| 184 | + |
| 185 | + levelData.put("TileEntities", new ListTag<CompoundTag>("TileEntities")); |
| 186 | + int[] heightMap = new int[256]; |
| 187 | + for(int x = 0; x < 16; x++){ |
| 188 | + for(int z = 0; z < 16; z++){ |
| 189 | + for(int y = 127; y >= 0; y--){ |
| 190 | + int pos = (x << 11) | (z << 7) | y; |
| 191 | + int block = data.blocks[pos]; |
| 192 | + if(block != 0){ |
| 193 | + heightMap[(x << 4) | z] = y; |
| 194 | + break; |
| 195 | + } |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + levelData.putIntArray("HeightMap", heightMap); |
| 200 | + |
| 201 | + String k = (chunkX >> 5) + "." + (chunkZ >> 5); |
| 202 | + if(!regions.containsKey(k)){ |
| 203 | + regions.put(k, new RegionFile(new File(des, "r." + (chunkX >> 5) + "." + (chunkZ >> 5) + ".mca"))); |
| 204 | + } |
| 205 | + RegionFile regionDest = regions.get(k); |
| 206 | + int regionX = (chunkX % 32 + 32) % 32; |
| 207 | + int regionZ = (chunkZ % 32 + 32) % 32; |
| 208 | + /*if(chunkX < 0 || chunkZ < 0){ |
| 209 | + @SuppressWarnings("unused") |
| 210 | + int i = 1+1; |
| 211 | + }*/ |
| 212 | + DataOutputStream chunkDataOutputStream = regionDest.getChunkDataOutputStream(regionX, regionZ); |
| 213 | + if(chunkDataOutputStream == null){ |
| 214 | + System.out.println(chunkX % 32); |
| 215 | + System.out.println(chunkZ % 32); |
| 216 | + } |
| 217 | + NbtIo.write(tag, chunkDataOutputStream); |
| 218 | + chunkDataOutputStream.close(); |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + finally{ |
| 223 | + Iterator<Entry<String, RegionFile>> iter = regions.entrySet().iterator(); |
| 224 | + while (iter.hasNext()){ |
| 225 | + Entry<String, RegionFile> entry = iter.next(); |
| 226 | + RegionFile region = entry.getValue(); |
| 227 | + region.close(); |
| 228 | + } |
| 229 | + iterator.close(); |
| 230 | + } |
| 231 | + } |
| 232 | + finally{ |
| 233 | + db.close(); |
| 234 | + } |
| 235 | + System.out.println("Done!"); |
| 236 | + } |
| 237 | + |
| 238 | + public static String byte2s(byte[] b, boolean ignoreTooLong){ |
| 239 | + String s = "0x"; |
| 240 | + int length = b.length; |
| 241 | + boolean tooLong = false; |
| 242 | + if(length > 100){ |
| 243 | + length = 100; |
| 244 | + tooLong = true; |
| 245 | + } |
| 246 | + for(int i = 0; i < length; i++){ |
| 247 | + s = s + b[i] + " "; |
| 248 | + } |
| 249 | + if(tooLong && ignoreTooLong) |
| 250 | + s = s + "..."; |
| 251 | + return s; |
| 252 | + } |
| 253 | + |
| 254 | + public static byte[] intToByteArray(int i){ |
| 255 | + byte[] result = new byte[4]; |
| 256 | + result[0] = (byte)((i >> 24) & 0xFF); |
| 257 | + result[1] = (byte)((i >> 16) & 0xFF); |
| 258 | + result[2] = (byte)((i >> 8) & 0xFF); |
| 259 | + result[3] = (byte)(i & 0xFF); |
| 260 | + return result; |
| 261 | + } |
| 262 | + |
| 263 | + public static int byteArrayToInt(byte[] bytes){ |
| 264 | + int value= 0; |
| 265 | + for (int i = 0; i < 4; i++){ |
| 266 | + int shift = (4 - 1 - i) * 8; |
| 267 | + value += (bytes[i] & 0x000000FF) << shift; |
| 268 | + } |
| 269 | + return value; |
| 270 | + } |
| 271 | + |
| 272 | + public static class LevelDBChunk{ |
| 273 | + public OldDataLayer blockLight; |
| 274 | + public OldDataLayer skyLight; |
| 275 | + public OldDataLayer data; |
| 276 | + public byte[] blocks; |
| 277 | + |
| 278 | + public final int x; |
| 279 | + public final int z; |
| 280 | + |
| 281 | + public LevelDBChunk(int x, int z){ |
| 282 | + this.x = x; |
| 283 | + this.z = z; |
| 284 | + } |
| 285 | + } |
| 286 | +} |
0 commit comments