mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-30 05:50:48 +00:00
f
This commit is contained in:
-130
@@ -1,130 +0,0 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.core.lifecycle.WorldReplacementSeed;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import io.papermc.paper.world.saveddata.PaperLevelOverrides;
|
||||
import io.papermc.paper.world.saveddata.PaperWorldMetadata;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import net.minecraft.world.level.storage.SavedDataStorage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
final class CurrentPaperWorldDataWriter {
|
||||
private static final long SNAPSHOT_TIMEOUT_SECONDS = 30L;
|
||||
|
||||
private CurrentPaperWorldDataWriter() {
|
||||
}
|
||||
|
||||
static void write(
|
||||
Path sourceWorldDirectory,
|
||||
Path targetWorldDirectory,
|
||||
long seed
|
||||
) throws IOException {
|
||||
CraftServer craftServer = (CraftServer) Bukkit.getServer();
|
||||
MinecraftServer server = craftServer.getHandle().getServer();
|
||||
PaperLevelOverrides levelOverrides = captureLevelOverrides(craftServer, server);
|
||||
|
||||
Path targetWorld = targetWorldDirectory.toAbsolutePath().normalize();
|
||||
UUID metadataUuid = UUID.randomUUID();
|
||||
WorldReplacementSeed.copyWithAuthoritativeSeed(sourceWorldDirectory, targetWorld, seed);
|
||||
try (SavedDataStorage savedDataStorage = new SavedDataStorage(
|
||||
targetWorld.resolve("data"),
|
||||
server.getFixerUpper(),
|
||||
server.registryAccess()
|
||||
)) {
|
||||
savedDataStorage.set(PaperWorldMetadata.TYPE, new PaperWorldMetadata(metadataUuid));
|
||||
savedDataStorage.set(PaperLevelOverrides.TYPE, levelOverrides);
|
||||
}
|
||||
|
||||
List<Path> requiredDataFiles = List.of(
|
||||
targetWorld.resolve("data/minecraft/world_gen_settings.dat"),
|
||||
targetWorld.resolve("data/paper/metadata.dat"),
|
||||
targetWorld.resolve("data/paper/level_overrides.dat")
|
||||
);
|
||||
for (Path requiredDataFile : requiredDataFiles) {
|
||||
if (!Files.isRegularFile(requiredDataFile, LinkOption.NOFOLLOW_LINKS)) {
|
||||
throw new IOException("Current Paper world data was not written: " + requiredDataFile);
|
||||
}
|
||||
}
|
||||
|
||||
long writtenSeed = WorldReplacementSeed.readAuthoritativeSeed(targetWorld);
|
||||
if (writtenSeed != seed) {
|
||||
throw new IOException("Current Paper world data did not retain the requested seed.");
|
||||
}
|
||||
|
||||
try (SavedDataStorage verificationStorage = new SavedDataStorage(
|
||||
targetWorld.resolve("data"),
|
||||
server.getFixerUpper(),
|
||||
server.registryAccess()
|
||||
)) {
|
||||
PaperWorldMetadata metadata = verificationStorage.get(PaperWorldMetadata.TYPE);
|
||||
if (metadata == null || !metadataUuid.equals(metadata.uuid())) {
|
||||
throw new IOException("Current Paper world metadata could not be verified.");
|
||||
}
|
||||
PaperLevelOverrides overrides = verificationStorage.get(PaperLevelOverrides.TYPE);
|
||||
if (overrides == null || overrides.isInitialized()) {
|
||||
throw new IOException("Current Paper level overrides could not be verified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PaperLevelOverrides captureLevelOverrides(
|
||||
CraftServer craftServer,
|
||||
MinecraftServer server
|
||||
) throws IOException {
|
||||
if (craftServer.isGlobalTickThread()) {
|
||||
return createLevelOverrides(craftServer, server);
|
||||
}
|
||||
if (J.isFolia() && J.isPrimaryThread()) {
|
||||
throw new IOException("Current Paper world data cannot be staged from a Folia region tick thread.");
|
||||
}
|
||||
|
||||
CompletableFuture<PaperLevelOverrides> captured = new CompletableFuture<>();
|
||||
boolean scheduled = J.runGlobal(() -> {
|
||||
try {
|
||||
captured.complete(createLevelOverrides(craftServer, server));
|
||||
} catch (Throwable failure) {
|
||||
captured.completeExceptionally(failure);
|
||||
}
|
||||
});
|
||||
if (!scheduled) {
|
||||
throw new IOException("Could not schedule the current Paper level-data snapshot on the global thread.");
|
||||
}
|
||||
|
||||
try {
|
||||
return captured.get(SNAPSHOT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException failure) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while capturing current Paper level data.", failure);
|
||||
} catch (ExecutionException failure) {
|
||||
throw new IOException("Could not capture current Paper level data.", failure.getCause());
|
||||
} catch (TimeoutException failure) {
|
||||
throw new IOException("Timed out while capturing current Paper level data.", failure);
|
||||
}
|
||||
}
|
||||
|
||||
private static PaperLevelOverrides createLevelOverrides(
|
||||
CraftServer craftServer,
|
||||
MinecraftServer server
|
||||
) throws IOException {
|
||||
if (!craftServer.isGlobalTickThread()) {
|
||||
throw new IOException("Current Paper level data must be captured on the global tick thread.");
|
||||
}
|
||||
if (!(server.getWorldData().overworldData() instanceof PrimaryLevelData primaryLevelData)) {
|
||||
throw new IOException("Paper primary level data is unavailable for current world data staging.");
|
||||
}
|
||||
return PaperLevelOverrides.createFromLiveLevelData(primaryLevelData);
|
||||
}
|
||||
}
|
||||
+2
-14
@@ -154,7 +154,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -1572,8 +1571,7 @@ public class NMSBinding implements INMSBinding {
|
||||
injected.set(true);
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
|
||||
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
|
||||
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
|
||||
serverLevelTransformer = null;
|
||||
@@ -1627,22 +1625,12 @@ public class NMSBinding implements INMSBinding {
|
||||
try {
|
||||
transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.error(C.RED + "Failed to remove Bukkit world lifecycle injection");
|
||||
e.printStackTrace();
|
||||
IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeCurrentPaperWorldData(
|
||||
Path sourceWorldDirectory,
|
||||
Path targetWorldDirectory,
|
||||
long seed
|
||||
) throws IOException {
|
||||
CurrentPaperWorldDataWriter.write(sourceWorldDirectory, targetWorldDirectory, seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) {
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
|
||||
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.core.nms.INMSBinding;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NMSBindingCurrentPaperWorldDataContractTest {
|
||||
@Test
|
||||
public void bindingDelegatesWithoutLinkingPaperSavedDataClasses() throws Exception {
|
||||
String bindingSource = Files.readString(bindingSourcePath()).replace("\r\n", "\n");
|
||||
String writer = section(
|
||||
bindingSource,
|
||||
"public void writeCurrentPaperWorldData(",
|
||||
"public boolean awaitServerShutdownBoundary("
|
||||
);
|
||||
|
||||
assertTrue(writer.contains("CurrentPaperWorldDataWriter.write("));
|
||||
assertFalse(bindingSource.contains("PaperWorldMetadata"));
|
||||
assertFalse(bindingSource.contains("PaperLevelOverrides"));
|
||||
assertFalse(bindingSource.contains("io.papermc.paper.world.saveddata"));
|
||||
|
||||
InputStream classResource = NMSBindingCurrentPaperWorldDataContractTest.class
|
||||
.getResourceAsStream("NMSBinding.class");
|
||||
assertNotNull(classResource);
|
||||
try (InputStream input = classResource) {
|
||||
String classFile = new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
|
||||
assertFalse(classFile.contains("PaperWorldMetadata"));
|
||||
assertFalse(classFile.contains("PaperLevelOverrides"));
|
||||
assertFalse(classFile.contains("io/papermc/paper/world/saveddata"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stagesAllCurrentPaperWorldDataFromLiveServerState() throws Exception {
|
||||
String writer = Files.readString(writerSourcePath()).replace("\r\n", "\n");
|
||||
|
||||
assertTrue(writer.contains("WorldReplacementSeed.copyWithAuthoritativeSeed("));
|
||||
assertTrue(writer.contains("UUID metadataUuid = UUID.randomUUID()"));
|
||||
assertTrue(writer.contains("new PaperWorldMetadata(metadataUuid)"));
|
||||
assertTrue(writer.contains("captureLevelOverrides(craftServer, server)"));
|
||||
assertTrue(writer.indexOf("captureLevelOverrides(craftServer, server)")
|
||||
< writer.indexOf("WorldReplacementSeed.copyWithAuthoritativeSeed("));
|
||||
assertTrue(writer.contains("new SavedDataStorage("));
|
||||
assertTrue(writer.contains("server.getFixerUpper()"));
|
||||
assertTrue(writer.contains("server.registryAccess()"));
|
||||
assertTrue(writer.contains("data/minecraft/world_gen_settings.dat"));
|
||||
assertTrue(writer.contains("data/paper/metadata.dat"));
|
||||
assertTrue(writer.contains("data/paper/level_overrides.dat"));
|
||||
assertTrue(writer.contains("WorldReplacementSeed.readAuthoritativeSeed(targetWorld)"));
|
||||
assertTrue(writer.contains("verificationStorage.get(PaperWorldMetadata.TYPE)"));
|
||||
assertTrue(writer.contains("metadataUuid.equals(metadata.uuid())"));
|
||||
assertTrue(writer.contains("verificationStorage.get(PaperLevelOverrides.TYPE)"));
|
||||
assertTrue(writer.contains("overrides == null || overrides.isInitialized()"));
|
||||
assertTrue(writer.contains("Files.isRegularFile(requiredDataFile, LinkOption.NOFOLLOW_LINKS)"));
|
||||
assertFalse(writer.toLowerCase().contains("migrat"));
|
||||
assertFalse(writer.toLowerCase().contains("fallback"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void capturesOnlyLiveLevelOverridesOnTheGlobalThread() throws Exception {
|
||||
String source = Files.readString(writerSourcePath()).replace("\r\n", "\n");
|
||||
String capture = section(
|
||||
source,
|
||||
"private static PaperLevelOverrides captureLevelOverrides(",
|
||||
"private static PaperLevelOverrides createLevelOverrides("
|
||||
);
|
||||
String create = section(
|
||||
source,
|
||||
"private static PaperLevelOverrides createLevelOverrides(",
|
||||
"\n }\n}"
|
||||
);
|
||||
|
||||
assertTrue(capture.contains("craftServer.isGlobalTickThread()"));
|
||||
assertTrue(capture.contains("J.isFolia() && J.isPrimaryThread()"));
|
||||
assertTrue(capture.contains("J.runGlobal("));
|
||||
assertTrue(capture.contains("createLevelOverrides(craftServer, server)"));
|
||||
assertTrue(capture.contains("captured.get(SNAPSHOT_TIMEOUT_SECONDS"));
|
||||
assertTrue(capture.contains("Thread.currentThread().interrupt()"));
|
||||
assertTrue(create.contains("if (!craftServer.isGlobalTickThread())"));
|
||||
assertTrue(create.indexOf("if (!craftServer.isGlobalTickThread())")
|
||||
< create.indexOf("server.getWorldData().overworldData()"));
|
||||
assertTrue(create.contains("PaperLevelOverrides.createFromLiveLevelData(primaryLevelData)"));
|
||||
assertFalse(capture.contains("WorldReplacementSeed"));
|
||||
assertFalse(capture.contains("SavedDataStorage"));
|
||||
assertFalse(capture.contains("Files."));
|
||||
assertFalse(create.contains("WorldReplacementSeed"));
|
||||
assertFalse(create.contains("SavedDataStorage"));
|
||||
assertFalse(create.contains("Files."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedBindingsRejectCurrentPaperWorldDataStaging() {
|
||||
INMSBinding binding = (INMSBinding) Proxy.newProxyInstance(
|
||||
INMSBinding.class.getClassLoader(),
|
||||
new Class<?>[]{INMSBinding.class},
|
||||
(proxy, method, arguments) -> InvocationHandler.invokeDefault(proxy, method, arguments)
|
||||
);
|
||||
|
||||
UnsupportedOperationException error = assertThrows(
|
||||
UnsupportedOperationException.class,
|
||||
() -> binding.writeCurrentPaperWorldData(Path.of("source"), Path.of("target"), 1L)
|
||||
);
|
||||
assertTrue(error.getMessage().contains("does not support current Paper world data staging"));
|
||||
}
|
||||
|
||||
private static Path bindingSourcePath() {
|
||||
return Path.of(System.getProperty("iris.nmsBindingSource"));
|
||||
}
|
||||
|
||||
private static Path writerSourcePath() {
|
||||
return bindingSourcePath().resolveSibling("CurrentPaperWorldDataWriter.java");
|
||||
}
|
||||
|
||||
private static String section(String source, String startMarker, String endMarker) {
|
||||
int start = source.indexOf(startMarker);
|
||||
int end = source.indexOf(endMarker, start);
|
||||
assertTrue("Missing source section starting with " + startMarker, start >= 0);
|
||||
assertTrue("Missing source section ending with " + endMarker, end > start);
|
||||
return source.substring(start, end);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user