-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a copy of AnvilConverter and AnvilLevelStorageSource, but with ad…
…aptations for the PMAnvil format.
- Loading branch information
Showing
2 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package se.icus.mag.pmanvil; | ||
|
||
/** | ||
* Copyright Mojang AB. | ||
* | ||
* Don't do evil. | ||
* | ||
* Adaptations for PMAnvil conversions by magicus 2018. | ||
*/ | ||
|
||
|
||
import net.minecraft.world.level.storage.ProgressListener; | ||
|
||
import java.io.File; | ||
|
||
public class PMAnvilConverter { | ||
|
||
public static void main(String[] args) { | ||
|
||
if (args.length != 2) { | ||
printUsageAndExit(); | ||
} | ||
|
||
File baseFolder; | ||
try { | ||
baseFolder = new File(args[0]); | ||
if (!baseFolder.exists()) { | ||
throw new RuntimeException(args[0] + " doesn't exist"); | ||
} else if (!baseFolder.isDirectory()) { | ||
throw new RuntimeException(args[0] + " is not a folder"); | ||
} | ||
} catch (Exception e) { | ||
System.err.println("Base folder problem: " + e.getMessage()); | ||
System.out.println(""); | ||
printUsageAndExit(); | ||
return; | ||
} | ||
|
||
PMAnvilLevelStorageSource storage = new PMAnvilLevelStorageSource(baseFolder); | ||
if (!storage.isConvertible(args[1])) { | ||
System.err.println("World called " + args[1] + " is not convertible to the Anvil format"); | ||
System.out.println(""); | ||
printUsageAndExit(); | ||
return; | ||
} | ||
|
||
System.out.println("Converting map!"); | ||
storage.convertLevel(args[1], new ProgressListener() { | ||
private long timeStamp = System.currentTimeMillis(); | ||
|
||
public void progressStartNoAbort(String string) { | ||
} | ||
|
||
public void progressStart(String string) { | ||
} | ||
|
||
public void progressStagePercentage(int i) { | ||
if ((System.currentTimeMillis() - timeStamp) >= 1000L) { | ||
timeStamp = System.currentTimeMillis(); | ||
System.out.println("Converting... " + i + "%"); | ||
} | ||
} | ||
|
||
public void progressStage(String string) { | ||
} | ||
}); | ||
System.out.println("Done!"); | ||
System.out.println("You can now delete all *.mcapm files in the converted world."); | ||
} | ||
|
||
private static void printUsageAndExit() { | ||
System.out.println("Map converter for Minecraft, from format \"PMAnvil\" to \"Anvil\". (c) Mojang AB 2012, magicus 2018"); | ||
System.out.println(""); | ||
System.out.println("Usage:"); | ||
System.out.println("\tjava -jar pmanvil-converter.jar <base folder> <world name>"); | ||
System.out.println("Where:"); | ||
System.out.println("\t<base folder>\tThe full path to the folder containing Minecraft world folders"); | ||
System.out.println("\t<world name>\tThe folder name of the Minecraft world to be converted"); | ||
System.out.println("Example:"); | ||
System.out.println("\tjava -jar pmanvil-converter.jar /home/jeb_/minecraft world"); | ||
System.exit(1); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
package se.icus.mag.pmanvil; | ||
|
||
/** | ||
* Copyright Mojang AB. | ||
* | ||
* Don't do evil. | ||
* | ||
* Adaptations for PMAnvil conversions by magicus 2018. | ||
*/ | ||
|
||
import java.io.*; | ||
import java.util.ArrayList; | ||
|
||
import net.minecraft.world.level.chunk.storage.*; | ||
|
||
import com.mojang.nbt.*; | ||
import net.minecraft.world.level.storage.LevelStorage; | ||
import net.minecraft.world.level.storage.ProgressListener; | ||
|
||
public class PMAnvilLevelStorageSource { | ||
|
||
public static final String PM_ANVIL_EXTENSION = ".mcapm"; | ||
|
||
private File baseDir; | ||
|
||
public PMAnvilLevelStorageSource(File dir) { | ||
baseDir = dir; | ||
} | ||
|
||
public boolean isConvertible(String levelId) { | ||
|
||
// Pocketmine does not set level data version properly, so ignore it | ||
// Instead, check if there is mcapm files present | ||
File baseFolder = new File(baseDir, levelId); | ||
File regionFolder = new File(baseFolder, "region"); | ||
File[] list = regionFolder.listFiles(new FilenameFilter() { | ||
public boolean accept(File dir, String name) { | ||
return name.endsWith(PM_ANVIL_EXTENSION); | ||
} | ||
}); | ||
|
||
if (list == null) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private CompoundTag getDataTagFor(String levelId) { | ||
File dir = new File(baseDir, levelId); | ||
if (!dir.exists()) return null; | ||
|
||
File dataFile = new File(dir, "level.dat"); | ||
if (dataFile.exists()) { | ||
try { | ||
CompoundTag root = NbtIo.readCompressed(new FileInputStream(dataFile)); | ||
CompoundTag tag = root.getCompound("Data"); | ||
return tag; | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
dataFile = new File(dir, "level.dat_old"); | ||
if (dataFile.exists()) { | ||
try { | ||
CompoundTag root = NbtIo.readCompressed(new FileInputStream(dataFile)); | ||
CompoundTag tag = root.getCompound("Data"); | ||
return tag; | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
public boolean convertLevel(String levelId, ProgressListener progress) { | ||
|
||
progress.progressStagePercentage(0); | ||
|
||
ArrayList<File> normalRegions = new ArrayList<File>(); | ||
ArrayList<File> netherRegions = new ArrayList<File>(); | ||
ArrayList<File> enderRegions = new ArrayList<File>(); | ||
// | ||
File baseFolder = new File(baseDir, levelId); | ||
File netherFolder = new File(baseFolder, LevelStorage.NETHER_FOLDER); | ||
File enderFolder = new File(baseFolder, LevelStorage.ENDER_FOLDER); | ||
|
||
System.out.println("Scanning folders..."); | ||
|
||
// find normal world | ||
addRegionFiles(baseFolder, normalRegions); | ||
|
||
// find hell world | ||
if (netherFolder.exists()) { | ||
addRegionFiles(netherFolder, netherRegions); | ||
} | ||
if (enderFolder.exists()) { | ||
addRegionFiles(enderFolder, enderRegions); | ||
} | ||
|
||
int totalCount = normalRegions.size() + netherRegions.size() + enderRegions.size(); | ||
System.out.println("Total conversion count is " + totalCount); | ||
|
||
// convert normal world | ||
convertRegions(new File(baseFolder, "region"), normalRegions, 0, totalCount, progress); | ||
// convert hell world | ||
convertRegions(new File(netherFolder, "region"), netherRegions, normalRegions.size(), totalCount, progress); | ||
// convert end world | ||
convertRegions(new File(enderFolder, "region"), enderRegions, normalRegions.size() + netherRegions.size(), totalCount, progress); | ||
|
||
|
||
return true; | ||
} | ||
|
||
private void convertRegions(File baseFolder, ArrayList<File> regionFiles, int currentCount, int totalCount, ProgressListener progress) { | ||
|
||
for (File regionFile : regionFiles) { | ||
convertRegion(baseFolder, regionFile, currentCount, totalCount, progress); | ||
|
||
currentCount++; | ||
int percent = (int) Math.round(100.0d * (double) currentCount / (double) totalCount); | ||
progress.progressStagePercentage(percent); | ||
} | ||
|
||
} | ||
|
||
private void convertRegion(File baseFolder, File regionFile, int currentCount, int totalCount, ProgressListener progress) { | ||
|
||
try { | ||
String name = regionFile.getName(); | ||
|
||
RegionFile regionSource = new RegionFile(regionFile); | ||
RegionFile regionDest = new RegionFile(new File(baseFolder, name.substring(0, name.length() - PM_ANVIL_EXTENSION.length()) + RegionFile.ANVIL_EXTENSION)); | ||
|
||
for (int x = 0; x < 32; x++) { | ||
for (int z = 0; z < 32; z++) { | ||
if (regionSource.hasChunk(x, z) && !regionDest.hasChunk(x, z)) { | ||
DataInputStream regionChunkInputStream = regionSource.getChunkDataInputStream(x, z); | ||
if (regionChunkInputStream == null) { | ||
System.out.println("Failed to fetch input stream"); | ||
continue; | ||
} | ||
CompoundTag chunkData = NbtIo.read(regionChunkInputStream); | ||
regionChunkInputStream.close(); | ||
|
||
CompoundTag compound = chunkData.getCompound("Level"); | ||
{ | ||
CompoundTag tag = new CompoundTag(); | ||
CompoundTag levelData = new CompoundTag(); | ||
tag.put("Level", levelData); | ||
convertFromPmAnvilFormat(compound, levelData); | ||
|
||
DataOutputStream chunkDataOutputStream = regionDest.getChunkDataOutputStream(x, z); | ||
NbtIo.write(tag, chunkDataOutputStream); | ||
chunkDataOutputStream.close(); | ||
} | ||
} | ||
} | ||
int basePercent = (int) Math.round(100.0d * (double) (currentCount * 1024) / (double) (totalCount * 1024)); | ||
int newPercent = (int) Math.round(100.0d * (double) ((x + 1) * 32 + currentCount * 1024) / (double) (totalCount * 1024)); | ||
if (newPercent > basePercent) { | ||
progress.progressStagePercentage(newPercent); | ||
} | ||
} | ||
|
||
regionSource.close(); | ||
regionDest.close(); | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
private void addRegionFiles(File baseFolder, ArrayList<File> regionFiles) { | ||
|
||
File regionFolder = new File(baseFolder, "region"); | ||
File[] list = regionFolder.listFiles(new FilenameFilter() { | ||
public boolean accept(File dir, String name) { | ||
return name.endsWith(PM_ANVIL_EXTENSION); | ||
} | ||
}); | ||
|
||
if (list != null) { | ||
for (File file : list) { | ||
regionFiles.add(file); | ||
} | ||
} | ||
} | ||
|
||
private void convertFromPmAnvilFormat(CompoundTag inTag, CompoundTag outTag) { | ||
|
||
outTag.putInt("xPos", inTag.getInt("xPos")); | ||
outTag.putInt("zPos", inTag.getInt("zPos")); | ||
outTag.putLong("LastUpdate", inTag.getLong("LastUpdate")); | ||
outTag.putIntArray("HeightMap", inTag.getIntArray("HeightMap")); | ||
outTag.putByte("TerrainPopulated", inTag.getByte("TerrainPopulated")); | ||
|
||
ListTag<CompoundTag> outSectionTags = new ListTag<CompoundTag>("Sections"); | ||
ListTag inSectionTags = inTag.getList("Sections"); | ||
|
||
for (int i = 0; i < inSectionTags.size(); i++) { | ||
CompoundTag inSectionTag = (CompoundTag) inSectionTags.get(i); | ||
byte[] blocks = inSectionTag.getByteArray("Blocks"); | ||
byte[] blockLight = inSectionTag.getByteArray("BlockLight"); | ||
byte[] skyLight = inSectionTag.getByteArray("SkyLight"); | ||
byte[] data = inSectionTag.getByteArray("Data"); | ||
|
||
CompoundTag outSectionTag = new CompoundTag(); | ||
outSectionTag.putByte("Y", inSectionTag.getByte("Y")); | ||
outSectionTag.putByteArray("Blocks", reorderByteArray(blocks)); | ||
outSectionTag.putByteArray("Data", reorderNibbleArray(data)); | ||
outSectionTag.putByteArray("SkyLight", reorderNibbleArray(skyLight)); | ||
outSectionTag.putByteArray("BlockLight", reorderNibbleArray(blockLight)); | ||
|
||
outSectionTags.add(outSectionTag); | ||
} | ||
outTag.put("Sections", outSectionTags); | ||
|
||
if (inTag.contains("Biomes")) { | ||
outTag.putByteArray("Biomes", inTag.getByteArray("Biomes")); | ||
} | ||
|
||
outTag.put("Entities", inTag.getList("Entities")); | ||
|
||
outTag.put("TileEntities", inTag.getList("TileEntities")); | ||
|
||
if (inTag.contains("TileTicks")) { | ||
outTag.put("TileTicks", inTag.getList("TileTicks")); | ||
} | ||
} | ||
|
||
// Code for reorderByteArray and reorderNibbleArray, and processing of the | ||
// PMAnvil format is based on https://github.com/Awzaw/AnvilConverter | ||
private byte[] reorderByteArray(byte[] myarray) { | ||
byte[] result = new byte[16 * 16 * 16]; | ||
int i = 0; | ||
|
||
for (int x = 0; x < 16; x++) { | ||
int zMax = x + 256; | ||
for (int z = x; z < zMax; z += 16) { | ||
int yMax = z + 4096; | ||
for (int y = z; y < yMax; y += 256) { | ||
result[i] = myarray[y]; | ||
i++; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
private byte[] reorderNibbleArray(byte[] myarray) { | ||
byte commonValue = 0x00; | ||
byte[] result = new byte[16 * 16 * 8]; | ||
int i = 0; | ||
|
||
for (int x = 0; x < 8; x++) { | ||
for (int z = 0; z < 16; z++) { | ||
int zx = ((z << 3) | x); | ||
for (int y = 0; y < 8; y++) { | ||
int j = ((y << 8) | zx); | ||
int j80 = (j | 0x80); | ||
if (myarray[j] == commonValue && myarray[j80] == commonValue) { | ||
// values are already filled | ||
} else { | ||
byte i1 = myarray[j]; | ||
byte i2 = myarray[j80]; | ||
result[i] = (byte) ((i2 << 4) | (i1 & 0x0f)); | ||
result[i | 0x80] = (byte) (((i1 & 0xff) >>> 4) | (i2 & 0xf0)); | ||
} | ||
i++; | ||
} | ||
} | ||
i += 128; | ||
} | ||
|
||
return result; | ||
} | ||
} |