2019-02-22 04:41:20 +01:00
|
|
|
From 49d9979c5c63c62f676834076ab49026eecd4b68 Mon Sep 17 00:00:00 2001
|
2018-08-11 06:55:42 +02:00
|
|
|
From: Aikar <aikar@aikar.co>
|
|
|
|
Date: Sat, 11 Aug 2018 00:49:20 -0400
|
|
|
|
Subject: [PATCH] Detect and repair corrupt Region Files
|
|
|
|
|
|
|
|
If the file has partial data written but not the full 8192 bytes,
|
|
|
|
then the server will be unable to load that region file...
|
|
|
|
|
|
|
|
I don't know why mojang only checks for 4096, when anything less than 8192 is a crash.
|
|
|
|
|
|
|
|
But to be safe, it will attempt to back up the file.
|
|
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
|
2019-02-22 04:41:20 +01:00
|
|
|
index e2d4450e90..c20511588d 100644
|
2018-08-11 06:55:42 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/RegionFile.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/RegionFile.java
|
2019-02-22 04:41:20 +01:00
|
|
|
@@ -25,10 +25,10 @@ public class RegionFile {
|
|
|
|
private static final boolean ENABLE_EXTENDED_SAVE = Boolean.parseBoolean(System.getProperty("net.minecraft.server.RegionFile.enableExtendedSave", "true"));
|
|
|
|
// Spigot end
|
2018-10-11 03:28:12 +02:00
|
|
|
private static final byte[] a = new byte[4096];
|
|
|
|
- private final File b;
|
|
|
|
- private RandomAccessFile c;
|
|
|
|
- private final int[] d = new int[1024];
|
|
|
|
- private final int[] e = new int[1024];
|
|
|
|
+ private final File b;private File getFile() { return b; } // Paper - OBFHELPER
|
|
|
|
+ private RandomAccessFile c;private RandomAccessFile getDataFile() { return c; } // Paper - OBFHELPER
|
|
|
|
+ private final int[] d = new int[1024];private int[] offsets = d; // Paper - OBFHELPER
|
|
|
|
+ private final int[] e = new int[1024];private int[] timestamps = e; // Paper - OBFHELPER
|
|
|
|
private List<Boolean> f;
|
|
|
|
private int g;
|
|
|
|
private long h;
|
2019-02-22 04:41:20 +01:00
|
|
|
@@ -43,7 +43,7 @@ public class RegionFile {
|
2018-08-11 06:55:42 +02:00
|
|
|
}
|
|
|
|
|
2019-01-01 04:15:55 +01:00
|
|
|
this.c = new RandomAccessFile(file, "rw");
|
2018-08-11 06:55:42 +02:00
|
|
|
- if (this.c.length() < 4096L) {
|
2018-10-11 03:28:12 +02:00
|
|
|
+ if (this.c.length() < 8192L) { // Paper - headers should be 8192
|
2019-01-01 04:15:55 +01:00
|
|
|
this.c.write(RegionFile.a);
|
|
|
|
this.c.write(RegionFile.a);
|
2018-08-11 06:55:42 +02:00
|
|
|
this.g += 8192;
|
2019-02-22 04:41:20 +01:00
|
|
|
@@ -93,22 +93,23 @@ public class RegionFile {
|
|
|
|
this.c.seek(j * 4 + 4); // Go back to where we were
|
|
|
|
}
|
|
|
|
}
|
|
|
|
- if (k != 0 && (k >> 8) + (length) <= this.f.size()) {
|
2018-10-11 03:28:12 +02:00
|
|
|
+ if (k > 0 && (k >> 8) > 1 && (k >> 8) + (k & 255) <= this.f.size()) { // Paper >= 1 as 0/1 are the headers, and negative isnt valid
|
2019-02-22 04:41:20 +01:00
|
|
|
for (int l = 0; l < (length); ++l) {
|
|
|
|
// Spigot end
|
2018-10-11 03:28:12 +02:00
|
|
|
this.f.set((k >> 8) + l, false);
|
|
|
|
}
|
2019-02-22 04:41:20 +01:00
|
|
|
}
|
|
|
|
// Spigot start
|
|
|
|
- else if (length > 0) {
|
|
|
|
- org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Invalid chunk: ({0}, {1}) Offset: {2} Length: {3} runs off end file. {4}", new Object[]{j % 32, (int) (j / 32), k >> 8, length, file});
|
|
|
|
+ else if (k != 0) { // Paper
|
|
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Invalid chunk: ({0}, {1}) Offset: {2} Length: {3} runs off end file. {4}", new Object[]{j % 32, (int) (j / 32), k >> 8, length, file}); // Paper
|
|
|
|
+ deleteChunk(j); // Paper
|
|
|
|
}
|
|
|
|
// Spigot end
|
2018-10-11 03:28:12 +02:00
|
|
|
}
|
|
|
|
|
2019-01-01 04:15:55 +01:00
|
|
|
for (j = 0; j < 1024; ++j) {
|
2019-02-22 04:41:20 +01:00
|
|
|
k = headerAsInts.get(); // Paper
|
2019-01-01 04:15:55 +01:00
|
|
|
- this.e[j] = k;
|
|
|
|
+ if (offsets[j] != 0) this.timestamps[j] = k; // Paper - don't set timestamp if it got 0'd above due to corruption
|
2018-10-11 03:28:12 +02:00
|
|
|
}
|
|
|
|
} catch (IOException ioexception) {
|
|
|
|
ioexception.printStackTrace();
|
2019-02-22 04:41:20 +01:00
|
|
|
@@ -144,10 +145,10 @@ public class RegionFile {
|
|
|
|
int j1 = this.c.readInt();
|
|
|
|
|
|
|
|
if (j1 > 4096 * i1) {
|
|
|
|
- org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Invalid chunk: ({0}, {1}) Offset: {2} Invalid Size: {3}>{4} {5}", new Object[]{i, j, l, j1, i1 * 4096, this.b}); // Spigot
|
|
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Invalid chunk: ({0}, {1}) Offset: {2} Invalid Size: {3}>{4} {5}", new Object[]{i, j, l, j1, i1 * 4096, this.b}); // Spigot
|
|
|
|
return null;
|
|
|
|
} else if (j1 <= 0) {
|
|
|
|
- org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Invalid chunk: ({0}, {1}) Offset: {2} Invalid Size: {3} {4}", new Object[]{i, j, l, j1, this.b}); // Spigot
|
|
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Invalid chunk: ({0}, {1}) Offset: {2} Invalid Size: {3} {4}", new Object[]{i, j, l, j1, this.b}); // Spigot
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
byte b0 = this.c.readByte();
|
|
|
|
@@ -327,6 +328,54 @@ public class RegionFile {
|
2018-10-11 03:28:12 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
+ // Paper start
|
|
|
|
+ public synchronized void deleteChunk(int j1) {
|
|
|
|
+ backup();
|
|
|
|
+ int k = offsets[j1];
|
|
|
|
+ int x = j1 & 1024;
|
|
|
|
+ int z = j1 >> 2;
|
|
|
|
+ int offset = (k >> 8);
|
|
|
|
+ int len = (k & 255);
|
|
|
|
+ String debug = "idx:" + + j1 + " - " + x + "," + z + " - offset: " + offset + " - len: " + len;
|
|
|
|
+ try {
|
2019-02-22 04:41:20 +01:00
|
|
|
+ timestamps[j1] = 0;
|
|
|
|
+ offsets[j1] = 0;
|
2018-10-11 03:28:12 +02:00
|
|
|
+ RandomAccessFile file = getDataFile();
|
|
|
|
+ file.seek(j1 * 4);
|
|
|
|
+ file.writeInt(0);
|
|
|
|
+ // clear the timestamp
|
|
|
|
+ file.seek(4096 + j1 * 4);
|
|
|
|
+ file.writeInt(0);
|
2019-02-22 04:41:20 +01:00
|
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Deleted corrupt chunk (" + debug + ") " + getFile().getAbsolutePath(), e);
|
2018-10-11 03:28:12 +02:00
|
|
|
+ } catch (IOException e) {
|
|
|
|
+
|
2019-02-22 04:41:20 +01:00
|
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Error deleting corrupt chunk (" + debug + ") " + getFile().getAbsolutePath(), e);
|
2018-10-11 03:28:12 +02:00
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ private boolean backedUp = false;
|
|
|
|
+ private synchronized void backup() {
|
|
|
|
+ if (backedUp) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ backedUp = true;
|
|
|
|
+ File file = this.getFile();
|
|
|
|
+ java.text.DateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
+ java.util.Date today = new java.util.Date();
|
|
|
|
+ File corrupt = new File(file.getParentFile(), file.getName() + "." + formatter.format(today) + ".corrupt");
|
|
|
|
+ if (corrupt.exists()) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger();
|
|
|
|
+ logger.error("Region file " + file.getAbsolutePath() + " was corrupt. Backing up to " + corrupt.getAbsolutePath() + " and repairing");
|
|
|
|
+ try {
|
|
|
|
+ java.nio.file.Files.copy(file.toPath(), corrupt.toPath());
|
|
|
|
+
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ logger.error("Error backing up corrupt file" + file.getAbsolutePath(), e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // Paper end
|
|
|
|
+
|
|
|
|
class ChunkBuffer extends ByteArrayOutputStream {
|
2019-01-01 04:15:55 +01:00
|
|
|
|
2018-10-11 03:28:12 +02:00
|
|
|
private final int b;
|
2018-08-11 06:55:42 +02:00
|
|
|
--
|
2019-01-01 04:15:55 +01:00
|
|
|
2.20.1
|
2018-08-11 06:55:42 +02:00
|
|
|
|