mirror of
https://github.com/PaperMC/Paper.git
synced 2024-06-26 14:54:50 +02:00
Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 6680169e [#660/Enum] Merge remote-tracking branch 'origin/pr/660' into experimental 8b97f215 Add missing AbstractTestingBase extension 9f21f42b [#660/Enum] Merge remote-tracking branch 'origin/pr/660' into experimental fb59a4a0 Create experimental version a7c1393b Merge branch 'master' into enums-to-registers 1af01165 Merge branch 'master' into enums-to-registers 4ee82e4e Implement feedback af8ffd60 Merge branch 'master' into enums-to-registers 6a8ea63f Updated to 1.20 22ae9ebc Merge branch 'master' into enums-to-registers b1d669be Some clean up 685d812e Merge branch 'master' into enums-to-registers 00d778c7 Convert MusicInstrument bba2eb5f Convert GameEvent ffbf67a1 Convert PotionType eacaa45d Convert Particle d08d21d1 Convert PatternType c6b51f7c Convert Cat type af6c2987 Make missing Frog variant abstract a67a5f5c Add missing Annotation 6ab21c50 Change how converting from / to BlockType and ItemType work e3e84e69 Add Objects.requireNonNull so that there are not marked as nullable fceddab5 Add missing deprecation e91906f5 Don't convert legacy in register instead, only in required method 2200b334 Use static constructors for ItemStack b5f483b0 Deprecated Material a995df2a Fix typo getItemTyp 9cedb664 import ItemType 27e282b2 getSteerItemType -> getSteerItem d8d0e43b Better Piglin method names 3a2ab399 BLOCK_TYPE -> BLOCK, ITEM_TYPE -> ITEM a0eb63ac Interface it is 4bb0b646 Split Material into BlockType and ItemType b6bfcff5 Merge branch 'master' into enums-to-registers 1f86c847 Updated to Mockito 5.3.1 280ee1f7 Fix merge, updated to 1.19.4 9e0c7ad5 Merge branch 'master' into enums-to-registers fdbed698 Updated to 1.19.3 85c3e2d3 Merge branch 'master' into enums-to-registers b2c390af Fix merge 4e405647 Merge branch 'master' into enums-to-registers d01b4c90 Fix copy/paste ba2c8cb1 Update to 1.19 7e4f2db2 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers a1a974f0 Fix merge 7d3a91d3 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers 499e22d9 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers a0cf419f Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers d5bd36a2 Fix / Implement merge changes fe643952 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers cf1d2005 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers f18dce93 Make Statistic abstract bbe3f791 Fix 1.18 merge Handle comment out test cases 0988647e Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers 6e4f2c50 Populated BlockType and ItemType 7a58144d Convert Material enum, midpoint push, it compiles and runs 4771132c Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers e6b179ff Convert Material enum, midpoint push to merge BlockState changes f33b85a0 Change other enums / classes. 8b0d5418 Create seperat OldEnum class, which holds common enum methods. 45544426 Change Enums to classes to easier handle none standart minecraft values CraftBukkit Changes: 8969b32d0 [#931/Enum] Merge remote-tracking branch 'origin/pr/931' into experimental 222257a67 Add missing AbstractTestingBase extension 0480af399 [#931/Enum] Merge remote-tracking branch 'origin/pr/931' into experimental 1afa1ddc2 Create experimental version 00780ea51 Ignore FactoryItemMaterialTest test 676969d01 SPIGOT-7389: Handle setting null items in ChiseledBookshelf Inventory 84f10cc36 Fix merge 9e114e13e Merge branch 'master' into enums-to-registers 941787e24 Add missing Commodore for 1.20 updated 6dac9a12d Updated tests 737426398 Only allow reference holder aaaa5fa88 Merge branch 'master' into enums-to-registers 74957eb99 Merge branch 'master' into enums-to-registers a1ca4e870 Merge branch 'master' into enums-to-registers f293f4a61 Updated to 1.20 b434b3d15 Merge branch 'master' into enums-to-registers e99dcbda7 Some clean up fcead8aed Use correct primitive class e955d9c50 Fix some Commodore errors af5526ebb Allow Material to support older plugins b83afd643 Add rewrite for Google enum set methods 067323765 Add missing method replacement in dynamic invocation 63e17e631 Merge branch 'master' into enums-to-registers 26dfcacf8 Bad copy and past d50c9bd6a Convert MusicInstrument c0c5312db Convert GameEvent 39daffe2c Convert PotionType 9b974f832 Convert Particle f528fca63 Convert PatternType 525c65006 Convert Cat type 6832b8fbb More consistent to / from bukkit / minecraft methods d31e38e16 Make missing Frog variant abstract e4f0e7d8e This shouldn't be committed 6fee81baa Add Commodore for EnumSet 82a668683 Fix hasItemType / getItemType f70162d66 Change how converting from / to BlockType and ItemType work c3f7c7886 Don't convert legacy in register instead, only in required method 2039e05fa Use static constructors for ItemStack fe221578b More Commodore 2b70bd171 More Commodore 70f4a89f5 Fix some Commodore 06544ed4b Fix typo getItemTyp 6269d2e42 getSteerItemType -> getSteerItem a19ac46c0 Better Piglin method names eef5f52c6 BLOCK_TYPE -> BLOCK, ITEM_TYPE -> ITEM bbaff1348 Interface it is c39e1316c Finish Commodore action for Material split dd8552105 Work on Commodore 1d4ef8bf2 Split Material into BlockType and ItemType 6c5a98220 Merge branch 'master' into enums-to-registers 869658a96 Handle Material calls in lambda expression 523ac4ac0 Add reroute for Class#getEnumConstants 0a4463279 Use extra method for getting registry 602d9b404 Updated to Mockito 5.3.1 8ff87b77d Fix merge, updated to 1.19.4 9d739d313 Merge branch 'master' into enums-to-registers eb6f702ff Reduce usage of BuiltInRegistries b6f667cac Some more asm compatibility changes, add config option 87c931d38 Handle enum maps a2c6699db Updated to 1.19.3 f7c27584f Merge branch 'master' into enums-to-registers 2f95b9951 Fix merge 184b05740 Merge branch 'master' into enums-to-registers 12bd8de26 Updated to 1.19 9c57831b7 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers 4ed8eb402 Fix merge a9faac8e4 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers 0d2988603 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers 3f8f9557d Fix merge, updated to 1.18.2 1560490c6 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers a0e4eb12c Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers 8b59f682d Move getType method to CraftEntity class b849c0147 Add missing patches 4644ba79f Fix / Implement merge changes cf9ee732e Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers 0c9125b67 Use Tag where possible cc05153d9 Cache interactable call ab5cc36de Use getHandle 83ebf4114 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers bc20aea0c Make Statistic abstract 3faa7e135 Add Tests for Material BlockType and ItemType e10f74365 Fix 1.18 merge Handle comment out test cases f72f70ec4 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers dbf4f5b7e Populated BlockType and ItemType 015afc1bc Convert Material enum, midpoint push, it compiles and runs cc0112866 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers e26742c59 Convert Material enum, midpoint push to merge BlockState changes 796ad9295 Fix bug in legacy naming converting. 199c8278c Change other enums / classes. fd513652a Seperated custom biome value handling. Fix compareTo call. 60c71ce07 Change Enums to classes to easier handle none standart minecraft values Spigot Changes: addcf45f [Enum] Rebuild patches
778 lines
42 KiB
Diff
778 lines
42 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
Date: Sun, 2 Feb 2020 02:25:10 -0800
|
|
Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt
|
|
|
|
Instead of trying to relocate the chunk, which is seems to never
|
|
be the correct choice, so we end up duplicating or swapping chunks,
|
|
we instead drop the current regionfile header and recalculate -
|
|
hoping that at least then we don't swap chunks, and maybe recover
|
|
them all.
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
index fce679e2b72d2ce1c8e75710041937c2c0566044..9c6a2884c34a9f6e775103da42480cd6b8c693b3 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
@@ -70,6 +70,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks;
|
|
import org.slf4j.Logger;
|
|
|
|
public class ChunkSerializer {
|
|
+ // Paper start
|
|
+ // TODO: Check on update
|
|
+ public static long getLastWorldSaveTime(CompoundTag chunkData) {
|
|
+ final int dataVersion = ChunkStorage.getVersion(chunkData);
|
|
+ if (dataVersion < 2842) { // Level tag is removed after this version
|
|
+ final CompoundTag levelData = chunkData.getCompound("Level");
|
|
+ return levelData.getLong("LastUpdate");
|
|
+ } else {
|
|
+ return chunkData.getLong("LastUpdate");
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
@@ -450,7 +462,7 @@ public class ChunkSerializer {
|
|
nbttagcompound.putInt("xPos", chunkcoordintpair.x);
|
|
nbttagcompound.putInt("yPos", chunk.getMinSection());
|
|
nbttagcompound.putInt("zPos", chunkcoordintpair.z);
|
|
- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
|
|
+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change
|
|
nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
|
|
nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
|
BlendingData blendingdata = chunk.getBlendingData();
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
|
index e4b3a70ff9f906a10f2ba3c07642193ca3269db7..a5da3333e87bcc9def785a8e742a30d38c0ecc27 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
|
@@ -41,7 +41,7 @@ public class ChunkStorage implements AutoCloseable {
|
|
this.fixerUpper = dataFixer;
|
|
// Paper start - async chunk io
|
|
// remove IO worker
|
|
- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker
|
|
+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper
|
|
// Paper end - async chunk io
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
|
index c8298a597818227de33a4afce4698ec0666cf758..6baceb6ce9021c489be6e79d338a9704285afa26 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
|
@@ -9,6 +9,27 @@ import java.util.BitSet;
|
|
public class RegionBitmap {
|
|
private final BitSet used = new BitSet();
|
|
|
|
+ // Paper start
|
|
+ public final void copyFrom(RegionBitmap other) {
|
|
+ BitSet thisBitset = this.used;
|
|
+ BitSet otherBitset = other.used;
|
|
+
|
|
+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) {
|
|
+ thisBitset.set(i, otherBitset.get(i));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final boolean tryAllocate(int from, int length) {
|
|
+ BitSet bitset = this.used;
|
|
+ int firstSet = bitset.nextSetBit(from);
|
|
+ if (firstSet > 0 && firstSet < (from + length)) {
|
|
+ return false;
|
|
+ }
|
|
+ bitset.set(from, from + length);
|
|
+ return true;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public void force(int start, int size) {
|
|
this.used.set(start, start + size);
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
|
index cda87a66fe80bf910f629c64e36c1fecbad81d77..9bae47f99336c377beb72c4b50b7f01cb4db15da 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
|
@@ -51,6 +51,355 @@ public class RegionFile implements AutoCloseable {
|
|
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
|
|
public final Path regionFile; // Paper
|
|
|
|
+ // Paper start - try to recover from RegionFile header corruption
|
|
+ private static long roundToSectors(long bytes) {
|
|
+ long sectors = bytes >>> 12; // 4096 = 2^12
|
|
+ long remainingBytes = bytes & 4095;
|
|
+ long sign = -remainingBytes; // sign is 1 if nonzero
|
|
+ return sectors + (sign >>> 63);
|
|
+ }
|
|
+
|
|
+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag();
|
|
+
|
|
+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
|
|
+ try {
|
|
+ if (chunkDataLength < 0) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ long offset = sector * 4096L + 4L; // offset for chunk data
|
|
+
|
|
+ if ((offset + chunkDataLength) > fileLength) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength);
|
|
+ if (chunkDataLength != this.file.read(chunkData, offset)) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ ((java.nio.Buffer)chunkData).flip();
|
|
+
|
|
+ byte compressionType = chunkData.get();
|
|
+ if (compressionType < 0) { // compressionType & 128 != 0
|
|
+ // oversized chunk
|
|
+ return OVERSIZED_COMPOUND;
|
|
+ }
|
|
+
|
|
+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType);
|
|
+ if (compression == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
|
|
+
|
|
+ return NbtIo.read(new DataInputStream(input));
|
|
+ } catch (Exception ex) {
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private int getLength(long sector) throws IOException {
|
|
+ ByteBuffer length = ByteBuffer.allocate(4);
|
|
+ if (4 != this.file.read(length, sector * 4096L)) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return length.getInt(0);
|
|
+ }
|
|
+
|
|
+ private void backupRegionFile() {
|
|
+ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup");
|
|
+ this.backupRegionFile(backup);
|
|
+ }
|
|
+
|
|
+ private void backupRegionFile(Path to) {
|
|
+ try {
|
|
+ this.file.force(true);
|
|
+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath());
|
|
+ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
|
|
+ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath());
|
|
+ } catch (IOException ex) {
|
|
+ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) {
|
|
+ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31);
|
|
+ }
|
|
+
|
|
+ // note: only call for CHUNK regionfiles
|
|
+ boolean recalculateHeader() throws IOException {
|
|
+ if (!this.canRecalcHeader) {
|
|
+ return false;
|
|
+ }
|
|
+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile);
|
|
+ if (ourLowerLeftPosition == null) {
|
|
+ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header");
|
|
+ return false;
|
|
+ }
|
|
+ synchronized (this) {
|
|
+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable());
|
|
+
|
|
+ // try to backup file so maybe it could be sent to us for further investigation
|
|
+
|
|
+ this.backupRegionFile();
|
|
+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
|
|
+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
|
|
+ int[] sectorOffsets = new int[32 * 32]; // in sectors
|
|
+ boolean[] hasAikarOversized = new boolean[32 * 32];
|
|
+
|
|
+ long fileLength = this.file.size();
|
|
+ long totalSectors = roundToSectors(fileLength);
|
|
+
|
|
+ // search the regionfile from start to finish for the most up-to-date chunk data
|
|
+
|
|
+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
|
|
+ int chunkDataLength = this.getLength(i);
|
|
+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
|
|
+ if (compound == null || compound == OVERSIZED_COMPOUND) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound);
|
|
+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) {
|
|
+ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")");
|
|
+ continue;
|
|
+ }
|
|
+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5);
|
|
+
|
|
+ CompoundTag otherCompound = compounds[location];
|
|
+
|
|
+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) {
|
|
+ continue; // don't overwrite newer data.
|
|
+ }
|
|
+
|
|
+ // aikar oversized?
|
|
+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z);
|
|
+ boolean isAikarOversized = false;
|
|
+ if (Files.exists(aikarOversizedFile)) {
|
|
+ try {
|
|
+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z);
|
|
+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) {
|
|
+ // best we got for an id. hope it's good enough
|
|
+ isAikarOversized = true;
|
|
+ }
|
|
+ } catch (Exception ex) {
|
|
+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex);
|
|
+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data
|
|
+ }
|
|
+ }
|
|
+
|
|
+ hasAikarOversized[location] = isAikarOversized;
|
|
+ compounds[location] = compound;
|
|
+ rawLengths[location] = chunkDataLength + 4;
|
|
+ sectorOffsets[location] = (int)i;
|
|
+
|
|
+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]);
|
|
+ i += chunkSectorLength;
|
|
+ --i; // gets incremented next iteration
|
|
+ }
|
|
+
|
|
+ // forge style oversized data is already handled by the local search, and aikar data we just hope
|
|
+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding
|
|
+ // local data compound
|
|
+
|
|
+ java.nio.file.Path containingFolder = this.externalFileDir;
|
|
+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new);
|
|
+ boolean[] oversized = new boolean[32 * 32];
|
|
+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32];
|
|
+
|
|
+ if (regionFiles != null) {
|
|
+ int lowerXBound = ourLowerLeftPosition.x; // inclusive
|
|
+ int lowerZBound = ourLowerLeftPosition.z; // inclusive
|
|
+ int upperXBound = lowerXBound + 32 - 1; // inclusive
|
|
+ int upperZBound = lowerZBound + 32 - 1; // inclusive
|
|
+
|
|
+ // read mojang oversized data
|
|
+ for (Path regionFile : regionFiles) {
|
|
+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile);
|
|
+ if (oversizedCoords == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) {
|
|
+ continue; // not in our regionfile
|
|
+ }
|
|
+
|
|
+ // ensure oversized data is valid & is newer than data in the regionfile
|
|
+
|
|
+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5);
|
|
+
|
|
+ byte[] chunkData;
|
|
+ try {
|
|
+ chunkData = Files.readAllBytes(regionFile);
|
|
+ } catch (Exception ex) {
|
|
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ CompoundTag compound = null;
|
|
+
|
|
+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
|
|
+ RegionFileVersion compression = null;
|
|
+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
|
|
+ try {
|
|
+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java
|
|
+ compound = NbtIo.read((java.io.DataInput)in);
|
|
+ compression = compressionType;
|
|
+ break; // reaches here iff readNBT does not throw
|
|
+ } catch (Exception ex) {
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (compound == null) {
|
|
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost");
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) {
|
|
+ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) {
|
|
+ oversized[location] = true;
|
|
+ oversizedCompressionTypes[location] = compression;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // now we need to calculate a new offset header
|
|
+
|
|
+ int[] calculatedOffsets = new int[32 * 32];
|
|
+ RegionBitmap newSectorAllocations = new RegionBitmap();
|
|
+ newSectorAllocations.force(0, 2); // make space for header
|
|
+
|
|
+ // allocate sectors for normal chunks
|
|
+
|
|
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
|
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
|
+ int location = chunkX | (chunkZ << 5);
|
|
+
|
|
+ if (oversized[location]) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ int rawLength = rawLengths[location]; // bytes
|
|
+ int sectorOffset = sectorOffsets[location]; // sectors
|
|
+ int sectorLength = (int)roundToSectors(rawLength);
|
|
+
|
|
+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) {
|
|
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
|
|
+ } else {
|
|
+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // allocate sectors for oversized chunks
|
|
+
|
|
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
|
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
|
+ int location = chunkX | (chunkZ << 5);
|
|
+
|
|
+ if (!oversized[location]) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ int sectorOffset = newSectorAllocations.allocate(1);
|
|
+ int sectorLength = 1;
|
|
+
|
|
+ try {
|
|
+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096);
|
|
+ // only allocate in the new offsets if the write succeeds
|
|
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
|
|
+ } catch (IOException ex) {
|
|
+ newSectorAllocations.free(sectorOffset, sectorLength);
|
|
+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // rewrite aikar oversized data
|
|
+
|
|
+ this.oversizedCount = 0;
|
|
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
|
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
|
+ int location = chunkX | (chunkZ << 5);
|
|
+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0;
|
|
+
|
|
+ this.oversizedCount += isAikarOversized;
|
|
+ this.oversized[location] = (byte)isAikarOversized;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (this.oversizedCount > 0) {
|
|
+ try {
|
|
+ this.writeOversizedMeta();
|
|
+ } catch (Exception ex) {
|
|
+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex);
|
|
+ Files.deleteIfExists(this.getOversizedMetaFile());
|
|
+ }
|
|
+ } else {
|
|
+ Files.deleteIfExists(this.getOversizedMetaFile());
|
|
+ }
|
|
+
|
|
+ this.usedSectors.copyFrom(newSectorAllocations);
|
|
+
|
|
+ // before we overwrite the old sectors, print a summary of the chunks that got changed.
|
|
+
|
|
+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath());
|
|
+
|
|
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
|
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
|
+ int location = chunkX | (chunkZ << 5);
|
|
+
|
|
+ int oldOffset = this.offsets.get(location);
|
|
+ int newOffset = calculatedOffsets[location];
|
|
+
|
|
+ if (oldOffset == newOffset) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ this.offsets.put(location, newOffset); // overwrite incorrect offset
|
|
+
|
|
+ if (oldOffset == 0) {
|
|
+ // found lost data
|
|
+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath());
|
|
+ } else if (newOffset == 0) {
|
|
+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated");
|
|
+ } else {
|
|
+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath());
|
|
+
|
|
+ // simply destroy the timestamp header, it's not used
|
|
+
|
|
+ for (int i = 0; i < 32 * 32; ++i) {
|
|
+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this
|
|
+ }
|
|
+
|
|
+ // write new header
|
|
+ try {
|
|
+ this.flush();
|
|
+ this.file.force(true); // try to ensure it goes through...
|
|
+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath());
|
|
+ } catch (IOException ex) {
|
|
+ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ final boolean canRecalcHeader; // final forces compile fail on new constructor
|
|
+ // Paper end
|
|
+
|
|
// Paper start - Cache chunk status
|
|
private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
|
|
|
|
@@ -78,8 +427,19 @@ public class RegionFile implements AutoCloseable {
|
|
public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
|
|
this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
|
|
}
|
|
+ // Paper start - add can recalc flag
|
|
+ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
+ this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
|
|
+ }
|
|
+ // Paper end - add can recalc flag
|
|
|
|
public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
|
|
+ // Paper start - add can recalc flag
|
|
+ this(file, directory, outputChunkStreamVersion, dsync, false);
|
|
+ }
|
|
+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
+ this.canRecalcHeader = canRecalcHeader;
|
|
+ // Paper end - add can recalc flag
|
|
this.header = ByteBuffer.allocateDirect(8192);
|
|
this.regionFile = file; // Paper
|
|
initOversizedState(); // Paper
|
|
@@ -108,14 +468,16 @@ public class RegionFile implements AutoCloseable {
|
|
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i);
|
|
}
|
|
|
|
- long j = Files.size(file);
|
|
+ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption
|
|
|
|
- for (int k = 0; k < 1024; ++k) {
|
|
- int l = this.offsets.get(k);
|
|
+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
|
|
+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption
|
|
+ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location
|
|
+ final int l = this.offsets.get(k);
|
|
|
|
if (l != 0) {
|
|
- int i1 = RegionFile.getSectorNumber(l);
|
|
- int j1 = RegionFile.getNumSectors(l);
|
|
+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors
|
|
+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
|
|
// Spigot start
|
|
if (j1 == 255) {
|
|
// We're maxed out, so we need to read the proper length from the section
|
|
@@ -124,32 +486,102 @@ public class RegionFile implements AutoCloseable {
|
|
j1 = (realLen.getInt(0) + 4) / 4096 + 1;
|
|
}
|
|
// Spigot end
|
|
+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region
|
|
|
|
if (i1 < 2) {
|
|
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1});
|
|
- this.offsets.put(k, 0);
|
|
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
|
|
} else if (j1 == 0) {
|
|
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k);
|
|
- this.offsets.put(k, 0);
|
|
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
|
|
} else if ((long) i1 * 4096L > j) {
|
|
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1});
|
|
- this.offsets.put(k, 0);
|
|
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
|
|
} else {
|
|
- this.usedSectors.force(i1, j1);
|
|
+ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate
|
|
+ }
|
|
+ // Paper start - recalculate header on header corruption
|
|
+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) {
|
|
+ if (canRecalcHeader) {
|
|
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header...");
|
|
+ needsHeaderRecalc = true;
|
|
+ break;
|
|
+ } else {
|
|
+ // location = chunkX | (chunkZ << 5);
|
|
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() +
|
|
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
|
|
+ if (!hasBackedUp) {
|
|
+ hasBackedUp = true;
|
|
+ this.backupRegionFile();
|
|
+ }
|
|
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
|
|
+ this.offsets.put(headerLocation, 0); // delete the entry from header
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength);
|
|
+ if (failedToAllocate) {
|
|
+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath());
|
|
}
|
|
+ if (failedToAllocate & !canRecalcHeader) {
|
|
+ // location = chunkX | (chunkZ << 5);
|
|
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() +
|
|
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
|
|
+ if (!hasBackedUp) {
|
|
+ hasBackedUp = true;
|
|
+ this.backupRegionFile();
|
|
+ }
|
|
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
|
|
+ this.offsets.put(headerLocation, 0); // delete the entry from header
|
|
+ continue;
|
|
+ }
|
|
+ needsHeaderRecalc |= failedToAllocate;
|
|
+ // Paper end - recalculate header on header corruption
|
|
}
|
|
}
|
|
+ // Paper start - recalculate header on header corruption
|
|
+ // we move the recalc here so comparison to old header is correct when logging to console
|
|
+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues
|
|
+ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations");
|
|
+ this.recalculateHeader();
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private Path getExternalChunkPath(ChunkPos chunkPos) {
|
|
- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc";
|
|
+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change
|
|
|
|
return this.externalFileDir.resolve(s);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ private static ChunkPos getOversizedChunkPair(Path file) {
|
|
+ String fileName = file.getFileName().toString();
|
|
+
|
|
+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ String[] split = fileName.split("\\.");
|
|
+
|
|
+ if (split.length != 4) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ int x = Integer.parseInt(split[1]);
|
|
+ int z = Integer.parseInt(split[2]);
|
|
+
|
|
+ return new ChunkPos(x, z);
|
|
+ } catch (NumberFormatException ex) {
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Nullable
|
|
public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException {
|
|
int i = this.getOffset(pos);
|
|
@@ -173,6 +605,11 @@ public class RegionFile implements AutoCloseable {
|
|
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
|
|
if (bytebuffer.remaining() < 5) {
|
|
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()});
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
return null;
|
|
} else {
|
|
int i1 = bytebuffer.getInt();
|
|
@@ -180,6 +617,11 @@ public class RegionFile implements AutoCloseable {
|
|
|
|
if (i1 == 0) {
|
|
RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
return null;
|
|
} else {
|
|
int j1 = i1 - 1;
|
|
@@ -187,17 +629,44 @@ public class RegionFile implements AutoCloseable {
|
|
if (RegionFile.isExternalStreamChunk(b0)) {
|
|
if (j1 != 0) {
|
|
RegionFile.LOGGER.warn("Chunk has both internal and external streams");
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
}
|
|
|
|
- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
|
|
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ return ret;
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
} else if (j1 > bytebuffer.remaining()) {
|
|
RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()});
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
return null;
|
|
} else if (j1 < 0) {
|
|
RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos);
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
return null;
|
|
} else {
|
|
- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
|
|
+ // Paper start - recalculate header on regionfile corruption
|
|
+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
|
|
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
|
|
+ return this.getChunkDataInputStream(pos);
|
|
+ }
|
|
+ return ret;
|
|
+ // Paper end - recalculate header on regionfile corruption
|
|
}
|
|
}
|
|
}
|
|
@@ -372,10 +841,15 @@ public class RegionFile implements AutoCloseable {
|
|
}
|
|
|
|
private ByteBuffer createExternalStub() {
|
|
+ // Paper start - add version param
|
|
+ return this.createExternalStub(this.version);
|
|
+ }
|
|
+ private ByteBuffer createExternalStub(RegionFileVersion version) {
|
|
+ // Paper end - add version param
|
|
ByteBuffer bytebuffer = ByteBuffer.allocate(5);
|
|
|
|
bytebuffer.putInt(1);
|
|
- bytebuffer.put((byte) (this.version.getId() | 128));
|
|
+ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param
|
|
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
|
|
return bytebuffer;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
|
index d3d4d10a77af51cff4da201201bac325427fc20c..c2643f61fe5efb62d15f0798db691c76c7674a6e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
|
@@ -25,6 +25,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap();
|
|
private final Path folder;
|
|
private final boolean sync;
|
|
+ private final boolean isChunkData; // Paper
|
|
|
|
// Paper start - cache regionfile does not exist state
|
|
static final int MAX_NON_EXISTING_CACHE = 1024 * 64;
|
|
@@ -56,6 +57,12 @@ public class RegionFileStorage implements AutoCloseable {
|
|
// Paper end - cache regionfile does not exist state
|
|
|
|
protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor
|
|
+ // Paper start - add isChunkData param
|
|
+ this(directory, dsync, false);
|
|
+ }
|
|
+ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) {
|
|
+ this.isChunkData = isChunkData;
|
|
+ // Paper end - add isChunkData param
|
|
this.folder = directory;
|
|
this.sync = dsync;
|
|
}
|
|
@@ -125,7 +132,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
// Paper - only create directory if not existing only - moved down
|
|
Path path = this.folder;
|
|
int j = chunkcoordintpair.getRegionX();
|
|
- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
|
|
+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
|
|
if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state
|
|
this.markNonExisting(regionPos);
|
|
return null; // CraftBukkit
|
|
@@ -134,7 +141,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
}
|
|
// Paper end - cache regionfile does not exist state
|
|
FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above
|
|
- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
|
|
+ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
|
|
|
|
this.regionCache.putAndMoveToFirst(i, regionfile1);
|
|
// Paper start
|
|
@@ -219,6 +226,13 @@ public class RegionFileStorage implements AutoCloseable {
|
|
if (regionfile == null) {
|
|
return null;
|
|
}
|
|
+ // Paper start - Add regionfile parameter
|
|
+ return this.read(pos, regionfile);
|
|
+ }
|
|
+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException {
|
|
+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile
|
|
+ // if we decide to re-read
|
|
+ // Paper end
|
|
// CraftBukkit end
|
|
try { // Paper
|
|
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
|
|
@@ -235,6 +249,20 @@ public class RegionFileStorage implements AutoCloseable {
|
|
try {
|
|
if (datainputstream != null) {
|
|
nbttagcompound = NbtIo.read((DataInput) datainputstream);
|
|
+ // Paper start - recover from corrupt regionfile header
|
|
+ if (this.isChunkData) {
|
|
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound);
|
|
+ if (!chunkPos.equals(pos)) {
|
|
+ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath());
|
|
+ if (regionfile.recalculateHeader()) {
|
|
+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once.
|
|
+ return this.read(pos, regionfile);
|
|
+ }
|
|
+ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath());
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+ // Paper end - recover from corrupt regionfile header
|
|
break label43;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
|
index 5fa7a842431dd64c7a0dc5d8e940563a2aeef463..4411e427d3b6b592f8a18e61b6c59309cf699d3f 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
|
@@ -14,7 +14,7 @@ import javax.annotation.Nullable;
|
|
import net.minecraft.util.FastBufferedInputStream;
|
|
|
|
public class RegionFileVersion {
|
|
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
|
|
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
|
|
public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, (stream) -> {
|
|
return new FastBufferedInputStream(new GZIPInputStream(stream));
|
|
}, (stream) -> {
|