Imagemap sanitization

This commit is contained in:
Brian Neumann-Fopiano committed 2026-08-24 13:44:44 -04:00
1 parent e20bb86f40
commit 37b01b73c4
112 files changed
+11127 -670

No files matched your search

@@ -184,6 +184,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
Engine replacement = ModdedWorldEngines.prepareReplacement(level, pack, packDimensionKey, seed);
try {
ModdedWorldEngines.installReplacement(level, replacement);
replacement.getPlatformHooks().applyWorldBoundary(replacement);
} catch (Throwable error) {
try {
ModdedWorldEngines.closeUnregistered(replacement);
@@ -50,7 +50,8 @@ public final class ModdedWorldEngines {
if (existing != null) {
return existing;
}
return ENGINES.computeIfAbsent(level, (ServerLevel l) -> create(l, pack, dimensionKey, seedOverride));
return ENGINES.computeIfAbsent(level,
(ServerLevel l) -> createAndApplyWorldBoundary(l, pack, dimensionKey, seedOverride));
}
public static Collection<Engine> activeEngines() {
@@ -98,9 +99,33 @@ public final class ModdedWorldEngines {
static void closeUnregistered(Engine engine) {
close(engine);
ENGINES.entrySet().removeIf((Map.Entry<ServerLevel, Engine> entry) -> entry.getValue() == engine);
ModdedGuiHost.unbind(engine);
}
private static Engine createAndApplyWorldBoundary(ServerLevel level, String pack,
String dimensionKey, long seedOverride) {
Engine engine = create(level, pack, dimensionKey, seedOverride);
try {
engine.getPlatformHooks().applyWorldBoundary(engine);
return engine;
} catch (Throwable failure) {
try {
close(engine);
} catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError);
}
if (failure instanceof Error fatal) {
throw fatal;
}
if (failure instanceof RuntimeException runtimeException) {
throw runtimeException;
}
throw new IllegalStateException("Failed to apply Iris world boundary for '"
+ level.dimension().identifier() + "'.", failure);
}
}
private static Engine create(ServerLevel level, String pack, String dimensionKey, long seedOverride) {
ModdedEngineBootstrap.bind();
File packDir = resolvePack(pack, dimensionKey);
@@ -21,10 +21,14 @@ package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.gui.ImageMapStudioGUI;
import art.arcane.iris.core.gui.NoiseExplorerGUI;
import art.arcane.iris.core.gui.VisionGUI;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.StructurePackageClosure;
import art.arcane.iris.core.pack.ImageMapPackageClosure;
import art.arcane.iris.core.pack.PackValidationResult;
import art.arcane.iris.core.pack.PackValidator;
import art.arcane.iris.core.project.IrisProjectCopier;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
@@ -151,6 +155,8 @@ public final class ModdedStudioCommands {
root.then(noiseTree("nmap"));
root.then(mapTree("map"));
root.then(mapTree("render"));
root.then(imageMapTree("imagemap"));
root.then(imageMapTree("imap"));
root.then(message("loot", "Loot simulation opens a Bukkit chest inventory GUI; it is not available on modded servers."));
root.then(message("profile", "Pack performance profiling is part of the Bukkit studio toolchain and is not ported to modded servers."));
root.then(message("spawn", "Iris entity spawning uses the Bukkit entity pipeline and is not ported to modded servers."));
@@ -217,6 +223,11 @@ public final class ModdedStudioCommands {
.executes((CommandContext<CommandSourceStack> context) -> map(context.getSource()));
}
private static LiteralArgumentBuilder<CommandSourceStack> imageMapTree(String name) {
return Commands.literal(name)
.executes((CommandContext<CommandSourceStack> context) -> imageMap(context.getSource()));
}
private static int noise(CommandSourceStack source, String generatorKey, long seed) {
ServerLevel level = source.getLevel();
Engine engine = IrisModdedCommands.engineFor(level);
@@ -266,6 +277,29 @@ public final class ModdedStudioCommands {
return 1;
}
private static int imageMap(CommandSourceStack source) {
ServerLevel level = source.getLevel();
Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_IMAGE_MAP_REQUIRES_IRIS_DIMENSION
));
return 0;
}
if (!ImageMapStudioGUI.isAvailable()) {
IrisModdedCommands.fail(source, guiUnavailableMessage());
return 0;
}
ServerPlayer player = source.getPlayer();
ModdedGuiHost.bindContext(source.getServer(), level, engine, player == null ? null : player.getUUID());
ImageMapStudioGUI.launch(engine);
IrisModdedCommands.ok(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_IMAGE_MAP_STUDIO_ON_SERVER_DISPLAY,
MessageArgument.untrusted("value", level.dimension().identifier())
));
return 1;
}
private static LiteralArgumentBuilder<CommandSourceStack> workspaceTree(String name, boolean open) {
return Commands.literal(name)
.executes((CommandContext<CommandSourceStack> context) -> workspace(context.getSource(), null, open))
@@ -640,6 +674,11 @@ public final class ModdedStudioCommands {
}
private static File compilePackage(File packFolder, String dimKey) throws IOException {
PackValidationResult validation = PackValidator.validateForPackaging(packFolder);
if (!validation.isLoadable()) {
throw new IOException("Pack validation failed before packaging: "
+ String.join("; ", validation.getBlockingErrors()));
}
IrisData dm = IrisData.get(packFolder);
IrisDimension dimension = dm.getDimensionLoader().load(dimKey);
if (dimension == null) {
@@ -660,7 +699,16 @@ public final class ModdedStudioCommands {
LinkedHashSet<String> markerKeys = new LinkedHashSet<>();
LinkedHashSet<String> structureKeys = new LinkedHashSet<>();
regionKeys.addAll(dimension.getRegions());
dimension.getAllRegions(() -> dm).forEach((IrisRegion region) -> {
if (region != null && region.getLoadKey() != null) {
regionKeys.add(region.getLoadKey());
}
});
dimension.getReachableBiomes(() -> dm).forEach((IrisBiome biome) -> {
if (biome != null && biome.getLoadKey() != null) {
biomeKeys.add(biome.getLoadKey());
}
});
lootKeys.addAll(dimension.getLoot().getTables());
spawnerKeys.addAll(dimension.getEntitySpawners());
collectStructureKeys(structureKeys, dimension.getStructures());
@@ -722,6 +770,7 @@ public final class ModdedStudioCommands {
throw new IOException("Structure package closure is invalid: " + String.join("; ", structureClosure.errors()));
}
hashes.append(structureClosure.writeTo(folder, true));
hashes.append(ImageMapPackageClosure.writeAll(dm, folder, true));
for (String objectKey : objectKeys) {
try {
File objectFile = dm.getObjectLoader().findFile(objectKey);
@@ -28,6 +28,7 @@ import art.arcane.iris.engine.framework.NativeStructureVolume;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.engine.object.IrisWorldBoundary;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedDimensionManager;
import art.arcane.iris.modded.ModdedForcedDatapack;
@@ -38,6 +39,8 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.io.ReactiveFolder;
import art.arcane.volmlib.util.scheduling.ChronoLatch;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.border.WorldBorder;
import java.io.File;
import java.util.HashSet;
@@ -120,6 +123,42 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
"irisworldgen");
}
@Override
public void applyWorldBoundary(Engine engine) {
IrisDimension expectedDimension = engine.getDimension();
IrisWorldBoundary configuredBoundary = expectedDimension.getWorldBoundary();
if (configuredBoundary == null) {
return;
}
IrisWorld expectedIrisWorld = engine.getWorld();
if (expectedIrisWorld == null || !expectedIrisWorld.hasPlatformWorld()
|| !(expectedIrisWorld.platformWorld().nativeHandle() instanceof ServerLevel expectedLevel)) {
return;
}
IrisWorldBoundary boundary;
try {
boundary = IrisWorldBoundary.snapshot(configuredBoundary);
} catch (Throwable error) {
ModdedIrisLog.error("Invalid Iris world boundary for "
+ expectedLevel.dimension().identifier() + ".", error);
throw propagateBoundaryFailure("Invalid Iris world boundary for '"
+ expectedLevel.dimension().identifier() + "'.", error);
}
MinecraftServer server = expectedLevel.getServer();
if (server.isSameThread()) {
applyWorldBoundary(engine, expectedDimension, expectedIrisWorld, expectedLevel, boundary);
return;
}
try {
server.execute(() -> applyWorldBoundary(engine, expectedDimension, expectedIrisWorld, expectedLevel, boundary));
} catch (Throwable error) {
ModdedIrisLog.error("Failed to schedule Iris world-boundary application for "
+ expectedLevel.dimension().identifier() + ".", error);
throw propagateBoundaryFailure("Failed to schedule Iris world-boundary application for '"
+ expectedLevel.dimension().identifier() + "'.", error);
}
}
@Override
public void onServerTick(MinecraftServer server) {
ExecutorService active = executor;
@@ -268,6 +307,45 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
return false;
}
static void applyWorldBoundary(WorldBorder worldBorder, IrisWorldBoundary boundary) {
if (boundary == null) {
return;
}
worldBorder.setCenter(boundary.getCenter().getX(), boundary.getCenter().getZ());
worldBorder.setSize(boundary.getSize());
worldBorder.setWarningBlocks(boundary.getWarningDistance());
worldBorder.setSafeZone(boundary.getDamageBuffer());
worldBorder.setDamagePerBlock(boundary.getDamageAmount());
}
private static void applyWorldBoundary(Engine engine, IrisDimension expectedDimension,
IrisWorld expectedIrisWorld, ServerLevel expectedLevel,
IrisWorldBoundary boundary) {
if (engine.isClosed() || engine.isClosing() || engine.getDimension() != expectedDimension
|| engine.getWorld() != expectedIrisWorld
|| expectedIrisWorld.platformWorld().nativeHandle() != expectedLevel) {
return;
}
try {
applyWorldBoundary(expectedLevel.getWorldBorder(), boundary);
} catch (Throwable error) {
ModdedIrisLog.error("Failed to apply Iris world boundary to "
+ expectedLevel.dimension().identifier() + ".", error);
throw propagateBoundaryFailure("Failed to apply Iris world boundary to '"
+ expectedLevel.dimension().identifier() + "'.", error);
}
}
private static RuntimeException propagateBoundaryFailure(String message, Throwable error) {
if (error instanceof Error fatal) {
throw fatal;
}
if (error instanceof RuntimeException runtimeException) {
return runtimeException;
}
return new IllegalStateException(message, error);
}
private void writeWorkspace(Engine engine, String operation) {
try {
IrisData data = engine.getData();
@@ -0,0 +1,49 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.engine.object.IrisWorldBoundary;
import art.arcane.iris.engine.object.IrisWorldBoundaryCenter;
import net.minecraft.world.level.border.WorldBorder;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ModdedWorldBoundaryTest {
@Test
public void omittedBoundaryDoesNotTouchTheNativeLevel() {
WorldBorder worldBorder = new WorldBorder();
worldBorder.setCenter(3.5D, -4.5D);
worldBorder.setSize(1024D);
worldBorder.setWarningBlocks(9);
worldBorder.setSafeZone(2.5D);
worldBorder.setDamagePerBlock(0.75D);
ModdedStudioHotloadService.applyWorldBoundary(worldBorder, null);
assertEquals(3.5D, worldBorder.getCenterX(), 0D);
assertEquals(-4.5D, worldBorder.getCenterZ(), 0D);
assertEquals(1024D, worldBorder.getSize(), 0D);
assertEquals(9, worldBorder.getWarningBlocks());
assertEquals(2.5D, worldBorder.getSafeZone(), 0D);
assertEquals(0.75D, worldBorder.getDamagePerBlock(), 0D);
}
@Test
public void mapsEveryBoundaryValueToMinecraft() {
WorldBorder worldBorder = new WorldBorder();
IrisWorldBoundary boundary = new IrisWorldBoundary()
.setCenter(new IrisWorldBoundaryCenter(12.5D, -7.25D))
.setSize(4096D)
.setWarningDistance(24)
.setDamageBuffer(6.5D)
.setDamageAmount(0.4D);
ModdedStudioHotloadService.applyWorldBoundary(worldBorder, boundary);
assertEquals(12.5D, worldBorder.getCenterX(), 0D);
assertEquals(-7.25D, worldBorder.getCenterZ(), 0D);
assertEquals(4096D, worldBorder.getSize(), 0D);
assertEquals(24, worldBorder.getWarningBlocks());
assertEquals(6.5D, worldBorder.getSafeZone(), 0D);
assertEquals(0.4D, worldBorder.getDamagePerBlock(), 0D);
}
}