package net.minecraft.world.chunk.storage; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.util.datafix.DataFixer; import net.minecraft.util.datafix.FixTypes; import net.minecraft.util.datafix.IDataFixer; import net.minecraft.util.datafix.IDataWalker; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.MinecraftException; import net.minecraft.world.NextTickListEntry; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.NibbleArray; import net.minecraft.world.storage.IThreadedFileIO; import net.minecraft.world.storage.ThreadedFileIOBase; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class AnvilChunkLoader implements IChunkLoader, IThreadedFileIO { private static final Logger LOGGER = LogManager.getLogger(); private final Map chunksToRemove = new ConcurrentHashMap(); // MC-22147 FIX // private final Set pendingAnvilChunksCoordinates = Collections.newSetFromMap(new ConcurrentHashMap()); /** Save directory for chunks using the Anvil format */ private final File chunkSaveLocation; private final DataFixer dataFixer; private boolean savingExtraData; public AnvilChunkLoader(File chunkSaveLocationIn, DataFixer dataFixerIn) { this.chunkSaveLocation = chunkSaveLocationIn; this.dataFixer = dataFixerIn; } @Nullable /** * Loads the specified(XZ) chunk into the specified world. */ public Chunk loadChunk(World worldIn, int x, int z) throws IOException { ChunkPos chunkpos = new ChunkPos(x, z); NBTTagCompound nbttagcompound = (NBTTagCompound)this.chunksToRemove.get(chunkpos); if (nbttagcompound == null) { DataInputStream datainputstream = RegionFileCache.getChunkInputStream(this.chunkSaveLocation, x, z); if (datainputstream == null) { return null; } nbttagcompound = this.dataFixer.process(FixTypes.CHUNK, CompressedStreamTools.read(datainputstream)); } return this.checkedReadChunkFromNBT(worldIn, x, z, nbttagcompound); } public boolean func_191063_a(int p_191063_1_, int p_191063_2_) { ChunkPos chunkpos = new ChunkPos(p_191063_1_, p_191063_2_); NBTTagCompound nbttagcompound = (NBTTagCompound)this.chunksToRemove.get(chunkpos); return nbttagcompound != null ? true : RegionFileCache.func_191064_f(this.chunkSaveLocation, p_191063_1_, p_191063_2_); } @Nullable /** * Wraps readChunkFromNBT. Checks the coordinates and several NBT tags. */ protected Chunk checkedReadChunkFromNBT(World worldIn, int x, int z, NBTTagCompound compound) { if (!compound.hasKey("Level", 10)) { LOGGER.error("Chunk file at {},{} is missing level data, skipping", new Object[] {Integer.valueOf(x), Integer.valueOf(z)}); return null; } else { NBTTagCompound nbttagcompound = compound.getCompoundTag("Level"); if (!nbttagcompound.hasKey("Sections", 9)) { LOGGER.error("Chunk file at {},{} is missing block data, skipping", new Object[] {Integer.valueOf(x), Integer.valueOf(z)}); return null; } else { Chunk chunk = this.readChunkFromNBT(worldIn, nbttagcompound); if (!chunk.isAtLocation(x, z)) { LOGGER.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", new Object[] {Integer.valueOf(x), Integer.valueOf(z), Integer.valueOf(x), Integer.valueOf(z), Integer.valueOf(chunk.xPosition), Integer.valueOf(chunk.zPosition)}); nbttagcompound.setInteger("xPos", x); nbttagcompound.setInteger("zPos", z); chunk = this.readChunkFromNBT(worldIn, nbttagcompound); } return chunk; } } } public void saveChunk(World worldIn, Chunk chunkIn) throws MinecraftException, IOException { worldIn.checkSessionLock(); try { // MC-22147 FIX START // Get a time reference for the chunk being saved int new_tick = worldIn.getMinecraftServer().getTickCounter(); // Coordinates of chunk to save ChunkPos pos = chunkIn.getChunkCoordIntPair(); // See if the chunk is already queued and reject this save if the // tick number is the same NBTTagCompound old = chunksToRemove.get(this); if (old != null) { if (old.hasKey("SaveTimeTickCounter")) { int old_tick = old.getInteger("SaveTimeTickCounter"); // If the tick numbers are the same, there's no way the chunk could have been modified // in between when the chunk was saved normally and when saveChunk was // called a second time for unloading, so we can skip all this work. if (old_tick == new_tick) return; } } // MC-22147 FIX END NBTTagCompound nbttagcompound = new NBTTagCompound(); NBTTagCompound nbttagcompound1 = new NBTTagCompound(); nbttagcompound.setTag("Level", nbttagcompound1); nbttagcompound.setInteger("DataVersion", 922); // This new NBT tag is temporary. We will remove it before saving // to disk. The tick number is stored in the chunk itself // so that everything is totally atomic. Having a separate // structure like pendingAnvilChunksCoordinates is a recipe // for race conditions. Doing it this way, we can be sure // that chunk data and its timestamp are updated atomically. // MC-22147 FIX nbttagcompound.setInteger("SaveTimeTickCounter", new_tick); this.writeChunkToNBT(chunkIn, worldIn, nbttagcompound1); this.addChunkToPending(pos, nbttagcompound); } catch (Exception exception) { LOGGER.error((String)"Failed to save chunk", (Throwable)exception); } } protected void addChunkToPending(ChunkPos pos, NBTTagCompound compound) { // Only saveChunk called addChunkToPending, and the check to // cancel redundant saves has been moved into saveChunk. // If addChunkToPending DOES ever in the future get other callers, // and they don't perform the check properly, we'll get redundant // writes, but that will affect performance, not correctness. // MC-22147 FIX // if (!this.pendingAnvilChunksCoordinates.contains(pos)) // { // this.chunksToRemove.put(pos, compound); // } this.chunksToRemove.put(pos, compound); // MC-22147 FIX ThreadedFileIOBase.getThreadedIOInstance().queueIO(this); } /** * Returns a boolean stating if the write was unsuccessful. */ public boolean writeNextIO() { if (this.chunksToRemove.isEmpty()) { if (this.savingExtraData) { LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", new Object[] {this.chunkSaveLocation.getName()}); } return false; } else { // Get some chunk to save. It doesn't matter how we get one. // Getting a regular iterator from chunksToRemove would work too. ChunkPos chunkpos = (ChunkPos)this.chunksToRemove.keySet().iterator().next(); boolean lvt_3_1_; try { // MC-22147 FIX // this.pendingAnvilChunksCoordinates.add(chunkpos); // Remove the chunk. This is atomic, so either: // (a) We get the same one at chunkpos, or // (b) We get a newer one. // Either way, we're going to get and remove from the container // the latest one we have in the container, and any newer one // will get inserted afterward and then get saved later. NBTTagCompound nbttagcompound = (NBTTagCompound)this.chunksToRemove.remove(chunkpos); if (nbttagcompound != null) { try { // Remove the tick number from the chunk // MC-22147 FIX nbttagcompound.removeTag("SaveTimeTickCounter"); this.writeChunkData(chunkpos, nbttagcompound); } catch (Exception exception) { LOGGER.error((String)"Failed to save chunk", (Throwable)exception); } } lvt_3_1_ = true; } finally { // MC-22147 FIX // this.pendingAnvilChunksCoordinates.remove(chunkpos); } return lvt_3_1_; } } private void writeChunkData(ChunkPos pos, NBTTagCompound compound) throws IOException { DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, pos.chunkXPos, pos.chunkZPos); CompressedStreamTools.write(compound, dataoutputstream); dataoutputstream.close(); } /** * Save extra data associated with this Chunk not normally saved during autosave, only during chunk unload. * Currently unused. */ public void saveExtraChunkData(World worldIn, Chunk chunkIn) throws IOException { } /** * Called every World.tick() */ public void chunkTick() { } /** * Save extra data not associated with any Chunk. Not saved during autosave, only during world unload. Currently * unused. */ public void saveExtraData() { try { this.savingExtraData = true; while (this.writeNextIO()); } finally { this.savingExtraData = false; } } public static void registerFixes(DataFixer fixer) { fixer.registerWalker(FixTypes.CHUNK, new IDataWalker() { public NBTTagCompound process(IDataFixer fixer, NBTTagCompound compound, int versionIn) { if (compound.hasKey("Level", 10)) { NBTTagCompound nbttagcompound = compound.getCompoundTag("Level"); if (nbttagcompound.hasKey("Entities", 9)) { NBTTagList nbttaglist = nbttagcompound.getTagList("Entities", 10); for (int i = 0; i < nbttaglist.tagCount(); ++i) { nbttaglist.set(i, fixer.process(FixTypes.ENTITY, (NBTTagCompound)nbttaglist.get(i), versionIn)); } } if (nbttagcompound.hasKey("TileEntities", 9)) { NBTTagList nbttaglist1 = nbttagcompound.getTagList("TileEntities", 10); for (int j = 0; j < nbttaglist1.tagCount(); ++j) { nbttaglist1.set(j, fixer.process(FixTypes.BLOCK_ENTITY, (NBTTagCompound)nbttaglist1.get(j), versionIn)); } } } return compound; } }); } /** * Writes the Chunk passed as an argument to the NBTTagCompound also passed, using the World argument to retrieve * the Chunk's last update time. */ private void writeChunkToNBT(Chunk chunkIn, World worldIn, NBTTagCompound compound) { compound.setInteger("xPos", chunkIn.xPosition); compound.setInteger("zPos", chunkIn.zPosition); compound.setLong("LastUpdate", worldIn.getTotalWorldTime()); compound.setIntArray("HeightMap", chunkIn.getHeightMap()); compound.setBoolean("TerrainPopulated", chunkIn.isTerrainPopulated()); compound.setBoolean("LightPopulated", chunkIn.isLightPopulated()); compound.setLong("InhabitedTime", chunkIn.getInhabitedTime()); ExtendedBlockStorage[] aextendedblockstorage = chunkIn.getBlockStorageArray(); NBTTagList nbttaglist = new NBTTagList(); boolean flag = worldIn.provider.func_191066_m(); for (ExtendedBlockStorage extendedblockstorage : aextendedblockstorage) { if (extendedblockstorage != Chunk.NULL_BLOCK_STORAGE) { NBTTagCompound nbttagcompound = new NBTTagCompound(); nbttagcompound.setByte("Y", (byte)(extendedblockstorage.getYLocation() >> 4 & 255)); byte[] abyte = new byte[4096]; NibbleArray nibblearray = new NibbleArray(); NibbleArray nibblearray1 = extendedblockstorage.getData().getDataForNBT(abyte, nibblearray); nbttagcompound.setByteArray("Blocks", abyte); nbttagcompound.setByteArray("Data", nibblearray.getData()); if (nibblearray1 != null) { nbttagcompound.setByteArray("Add", nibblearray1.getData()); } nbttagcompound.setByteArray("BlockLight", extendedblockstorage.getBlocklightArray().getData()); if (flag) { nbttagcompound.setByteArray("SkyLight", extendedblockstorage.getSkylightArray().getData()); } else { nbttagcompound.setByteArray("SkyLight", new byte[extendedblockstorage.getBlocklightArray().getData().length]); } nbttaglist.appendTag(nbttagcompound); } } compound.setTag("Sections", nbttaglist); compound.setByteArray("Biomes", chunkIn.getBiomeArray()); chunkIn.setHasEntities(false); NBTTagList nbttaglist1 = new NBTTagList(); for (int i = 0; i < chunkIn.getEntityLists().length; ++i) { for (Entity entity : chunkIn.getEntityLists()[i]) { NBTTagCompound nbttagcompound2 = new NBTTagCompound(); if (entity.writeToNBTOptional(nbttagcompound2)) { chunkIn.setHasEntities(true); nbttaglist1.appendTag(nbttagcompound2); } } } compound.setTag("Entities", nbttaglist1); NBTTagList nbttaglist2 = new NBTTagList(); for (TileEntity tileentity : chunkIn.getTileEntityMap().values()) { NBTTagCompound nbttagcompound3 = tileentity.writeToNBT(new NBTTagCompound()); nbttaglist2.appendTag(nbttagcompound3); } compound.setTag("TileEntities", nbttaglist2); List list = worldIn.getPendingBlockUpdates(chunkIn, false); if (list != null) { long j = worldIn.getTotalWorldTime(); NBTTagList nbttaglist3 = new NBTTagList(); for (NextTickListEntry nextticklistentry : list) { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ResourceLocation resourcelocation = (ResourceLocation)Block.REGISTRY.getNameForObject(nextticklistentry.getBlock()); nbttagcompound1.setString("i", resourcelocation == null ? "" : resourcelocation.toString()); nbttagcompound1.setInteger("x", nextticklistentry.position.getX()); nbttagcompound1.setInteger("y", nextticklistentry.position.getY()); nbttagcompound1.setInteger("z", nextticklistentry.position.getZ()); nbttagcompound1.setInteger("t", (int)(nextticklistentry.scheduledTime - j)); nbttagcompound1.setInteger("p", nextticklistentry.priority); nbttaglist3.appendTag(nbttagcompound1); } compound.setTag("TileTicks", nbttaglist3); } } /** * Reads the data stored in the passed NBTTagCompound and creates a Chunk with that data in the passed World. * Returns the created Chunk. */ private Chunk readChunkFromNBT(World worldIn, NBTTagCompound compound) { int i = compound.getInteger("xPos"); int j = compound.getInteger("zPos"); Chunk chunk = new Chunk(worldIn, i, j); chunk.setHeightMap(compound.getIntArray("HeightMap")); chunk.setTerrainPopulated(compound.getBoolean("TerrainPopulated")); chunk.setLightPopulated(compound.getBoolean("LightPopulated")); chunk.setInhabitedTime(compound.getLong("InhabitedTime")); NBTTagList nbttaglist = compound.getTagList("Sections", 10); int k = 16; ExtendedBlockStorage[] aextendedblockstorage = new ExtendedBlockStorage[16]; boolean flag = worldIn.provider.func_191066_m(); for (int l = 0; l < nbttaglist.tagCount(); ++l) { NBTTagCompound nbttagcompound = nbttaglist.getCompoundTagAt(l); int i1 = nbttagcompound.getByte("Y"); ExtendedBlockStorage extendedblockstorage = new ExtendedBlockStorage(i1 << 4, flag); byte[] abyte = nbttagcompound.getByteArray("Blocks"); NibbleArray nibblearray = new NibbleArray(nbttagcompound.getByteArray("Data")); NibbleArray nibblearray1 = nbttagcompound.hasKey("Add", 7) ? new NibbleArray(nbttagcompound.getByteArray("Add")) : null; extendedblockstorage.getData().setDataFromNBT(abyte, nibblearray, nibblearray1); extendedblockstorage.setBlocklightArray(new NibbleArray(nbttagcompound.getByteArray("BlockLight"))); if (flag) { extendedblockstorage.setSkylightArray(new NibbleArray(nbttagcompound.getByteArray("SkyLight"))); } extendedblockstorage.removeInvalidBlocks(); aextendedblockstorage[i1] = extendedblockstorage; } chunk.setStorageArrays(aextendedblockstorage); if (compound.hasKey("Biomes", 7)) { chunk.setBiomeArray(compound.getByteArray("Biomes")); } NBTTagList nbttaglist1 = compound.getTagList("Entities", 10); for (int j1 = 0; j1 < nbttaglist1.tagCount(); ++j1) { NBTTagCompound nbttagcompound1 = nbttaglist1.getCompoundTagAt(j1); readChunkEntity(nbttagcompound1, worldIn, chunk); chunk.setHasEntities(true); } NBTTagList nbttaglist2 = compound.getTagList("TileEntities", 10); for (int k1 = 0; k1 < nbttaglist2.tagCount(); ++k1) { NBTTagCompound nbttagcompound2 = nbttaglist2.getCompoundTagAt(k1); TileEntity tileentity = TileEntity.create(worldIn, nbttagcompound2); if (tileentity != null) { chunk.addTileEntity(tileentity); } } if (compound.hasKey("TileTicks", 9)) { NBTTagList nbttaglist3 = compound.getTagList("TileTicks", 10); for (int l1 = 0; l1 < nbttaglist3.tagCount(); ++l1) { NBTTagCompound nbttagcompound3 = nbttaglist3.getCompoundTagAt(l1); Block block; if (nbttagcompound3.hasKey("i", 8)) { block = Block.getBlockFromName(nbttagcompound3.getString("i")); } else { block = Block.getBlockById(nbttagcompound3.getInteger("i")); } worldIn.scheduleBlockUpdate(new BlockPos(nbttagcompound3.getInteger("x"), nbttagcompound3.getInteger("y"), nbttagcompound3.getInteger("z")), block, nbttagcompound3.getInteger("t"), nbttagcompound3.getInteger("p")); } } return chunk; } @Nullable public static Entity readChunkEntity(NBTTagCompound compound, World worldIn, Chunk chunkIn) { Entity entity = createEntityFromNBT(compound, worldIn); if (entity == null) { return null; } else { chunkIn.addEntity(entity); if (compound.hasKey("Passengers", 9)) { NBTTagList nbttaglist = compound.getTagList("Passengers", 10); for (int i = 0; i < nbttaglist.tagCount(); ++i) { Entity entity1 = readChunkEntity(nbttaglist.getCompoundTagAt(i), worldIn, chunkIn); if (entity1 != null) { entity1.startRiding(entity, true); } } } return entity; } } @Nullable public static Entity readWorldEntityPos(NBTTagCompound compound, World worldIn, double x, double y, double z, boolean attemptSpawn) { Entity entity = createEntityFromNBT(compound, worldIn); if (entity == null) { return null; } else { entity.setLocationAndAngles(x, y, z, entity.rotationYaw, entity.rotationPitch); if (attemptSpawn && !worldIn.spawnEntityInWorld(entity)) { return null; } else { if (compound.hasKey("Passengers", 9)) { NBTTagList nbttaglist = compound.getTagList("Passengers", 10); for (int i = 0; i < nbttaglist.tagCount(); ++i) { Entity entity1 = readWorldEntityPos(nbttaglist.getCompoundTagAt(i), worldIn, x, y, z, attemptSpawn); if (entity1 != null) { entity1.startRiding(entity, true); } } } return entity; } } } @Nullable protected static Entity createEntityFromNBT(NBTTagCompound compound, World worldIn) { try { return EntityList.createEntityFromNBT(compound, worldIn); } catch (RuntimeException var3) { return null; } } public static void spawnEntity(Entity entityIn, World worldIn) { if (worldIn.spawnEntityInWorld(entityIn) && entityIn.isBeingRidden()) { for (Entity entity : entityIn.getPassengers()) { spawnEntity(entity, worldIn); } } } @Nullable public static Entity readWorldEntity(NBTTagCompound compound, World worldIn, boolean p_186051_2_) { Entity entity = createEntityFromNBT(compound, worldIn); if (entity == null) { return null; } else if (p_186051_2_ && !worldIn.spawnEntityInWorld(entity)) { return null; } else { if (compound.hasKey("Passengers", 9)) { NBTTagList nbttaglist = compound.getTagList("Passengers", 10); for (int i = 0; i < nbttaglist.tagCount(); ++i) { Entity entity1 = readWorldEntity(nbttaglist.getCompoundTagAt(i), worldIn, p_186051_2_); if (entity1 != null) { entity1.startRiding(entity, true); } } } return entity; } } }