Skip to content

Commit

Permalink
Add a copy of AnvilConverter and AnvilLevelStorageSource, but with ad…
Browse files Browse the repository at this point in the history
…aptations for the PMAnvil format.
  • Loading branch information
magicus committed Jan 31, 2018
1 parent 76d07f1 commit b1ebddb
Show file tree
Hide file tree
Showing 2 changed files with 362 additions and 0 deletions.
84 changes: 84 additions & 0 deletions src/se/icus/mag/pmanvil/PMAnvilConverter.java
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);
}

}
278 changes: 278 additions & 0 deletions src/se/icus/mag/pmanvil/PMAnvilLevelStorageSource.java
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;
}
}

0 comments on commit b1ebddb

Please sign in to comment.