From 9481a9ecf0e1d0d47834c441d5e75bda66e9ac46 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 14 Oct 2026 21:08:23 -0400 Subject: [PATCH] Reserved Iris ingest namespaces - Mod ingest never selects the irisworldgen namespace or a namespace equal to a dimension key of the source pack or any installed Iris pack, so Iris's own scoped and legacy alias biomes are not adopted - Explicitly requesting one of those namespaces is refused - A namespace that belongs to a loaded mod is never treated as an Iris dimension alias namespace --- .../arcane/iris/pack/ingest/ModIngester.java | 46 ++++++++-- .../ingest/IngestReservedNamespaceTest.java | 88 +++++++++++++++++++ 2 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/ModIngester.java b/core/src/main/java/art/arcane/iris/pack/ingest/ModIngester.java index f3ce3aa2a..3870781f0 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/ModIngester.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/ModIngester.java @@ -19,6 +19,7 @@ package art.arcane.iris.pack.ingest; import art.arcane.iris.pack.AtomicDirectoryPublisher; +import art.arcane.iris.pack.PackDirectoryResolver; import art.arcane.iris.pack.ingest.catalog.CatalogBiome; import art.arcane.iris.pack.ingest.catalog.CatalogNamespace; import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature; @@ -29,6 +30,7 @@ import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl; import art.arcane.iris.studio.workspace.IrisProjectCopier; import com.google.gson.JsonObject; +import java.io.File; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; @@ -43,9 +45,11 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.UUID; +import java.util.stream.Stream; public final class ModIngester { - private static final Set RESERVED_NAMESPACES = Set.of("minecraft", "c", "iris"); + private static final Set RESERVED_NAMESPACES = Set.of("minecraft", "c", "iris", "irisworldgen"); + private static final String JSON = ".json"; private ModIngester() { } @@ -58,7 +62,7 @@ public final class ModIngester { if (Files.exists(source.resolve(IngestManifest.PATH), LinkOption.NOFOLLOW_LINKS)) { throw new IOException("Source pack " + source + " was written by mod ingest; ingest from its original pack instead."); } - Set namespaces = selectNamespaces(request); + Set namespaces = selectNamespaces(request, reservedNamespaces(request.catalog(), source, target.getParent())); requireReplaceableTarget(target, request.replaceTarget()); String targetKey = target.getFileName().toString(); Files.createDirectories(target.getParent()); @@ -96,13 +100,13 @@ public final class ModIngester { return new ModIngestReport(target, items, IngestManifest.summary(targetKey, pack, namespaces, items)); } - private static Set selectNamespaces(ModIngestRequest request) { + private static Set selectNamespaces(ModIngestRequest request, Set reserved) { Set selected = new TreeSet<>(); if (!request.namespaces().isEmpty()) { for (String namespace : request.namespaces()) { String normalized = namespace.trim().toLowerCase(Locale.ROOT); - if (RESERVED_NAMESPACES.contains(normalized)) { - throw new IllegalArgumentException("Namespace " + normalized + " cannot be ingested; it belongs to Minecraft or Iris."); + if (reserved.contains(normalized)) { + throw new IllegalArgumentException("Namespace " + normalized + " cannot be ingested; it belongs to Minecraft, Iris, or an installed Iris pack dimension."); } selected.add(normalized); } @@ -121,10 +125,40 @@ public final class ModIngester { for (CatalogStructure structure : catalog.structures()) { selected.add(WorldgenCatalogIndex.namespaceOf(structure.key())); } - selected.removeAll(RESERVED_NAMESPACES); + selected.removeAll(reserved); return selected; } + private static Set reservedNamespaces(WorldgenCatalog catalog, Path source, Path packsRoot) throws IOException { + Set dimensionKeys = new TreeSet<>(dimensionKeys(source)); + for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsRoot.toFile())) { + dimensionKeys.addAll(dimensionKeys(pack.toPath())); + } + for (CatalogNamespace namespace : catalog.namespaces()) { + dimensionKeys.remove(namespace.namespace()); + } + Set reserved = new TreeSet<>(RESERVED_NAMESPACES); + reserved.addAll(dimensionKeys); + return reserved; + } + + private static Set dimensionKeys(Path pack) throws IOException { + Path dimensions = pack.resolve("dimensions"); + if (!Files.isDirectory(dimensions, LinkOption.NOFOLLOW_LINKS)) { + return Set.of(); + } + Set keys = new TreeSet<>(); + try (Stream files = Files.list(dimensions)) { + for (Path file : files.toList()) { + String name = file.getFileName().toString(); + if (name.endsWith(JSON) && Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) { + keys.add(name.substring(0, name.length() - JSON.length()).toLowerCase(Locale.ROOT)); + } + } + } + return keys; + } + private static Set unselectedFeatureNamespaces(WorldgenCatalog catalog, Set selected) { Set unselected = new TreeSet<>(); for (CatalogPlacedFeature feature : catalog.placedFeatures()) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java new file mode 100644 index 000000000..8182df02c --- /dev/null +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java @@ -0,0 +1,88 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.pack.ingest; + +import art.arcane.iris.pack.ingest.catalog.CatalogBiome; +import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class IngestReservedNamespaceTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void defaultIngestNeverAdoptsIrisRegisteredBiomes() throws Exception { + Path root = folder.getRoot().toPath(); + Path source = ModIngestFixture.writeSourcePack(root); + ModIngestFixture.write(root.resolve("packs").resolve("underworld"), "dimensions/underworld.json", "{\"name\": \"Underworld\"}"); + Path target = root.resolve("packs").resolve("modded"); + + ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(), false)); + + assertNull(ModIngestFixture.item(report, "irisworldgen:packs/6f76/dimensions/6f76/biomes/dunes")); + assertNull(ModIngestFixture.item(report, "overworld:dunes")); + assertNull(ModIngestFixture.item(report, "underworld:ash_fields")); + assertTrue(report.items().stream().noneMatch(item -> Set.of("irisworldgen", "overworld", "underworld").contains(item.namespace()))); + assertEquals(List.of("examplemod", "othermod"), + ModIngestFixture.strings(ModIngestFixture.readJson(target.resolve("ingest/manifest.json")).get("namespaces"))); + } + + @Test + public void explicitIrisNamespacesAreRefused() throws Exception { + Path root = folder.getRoot().toPath(); + Path source = ModIngestFixture.writeSourcePack(root); + Path target = root.resolve("packs").resolve("modded"); + + for (String namespace : List.of("irisworldgen", "overworld")) { + assertThrows(namespace, IllegalArgumentException.class, () -> ModIngester.ingest( + new ModIngestRequest(source, target, "overworld", catalog(), Set.of(namespace), false))); + } + } + + private static WorldgenCatalog catalog() { + WorldgenCatalog fixture = ModIngestFixture.catalog(); + List biomes = new ArrayList<>(fixture.biomes()); + biomes.add(biome("irisworldgen:packs/6f76/dimensions/6f76/biomes/dunes")); + biomes.add(biome("overworld:dunes")); + biomes.add(biome("underworld:ash_fields")); + return new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(), + fixture.capturedAtEpochMillis(), fixture.contentFingerprint(), fixture.namespaces(), biomes, + fixture.placedFeatures(), fixture.structures(), fixture.dimensionTypes(), fixture.levelStems()); + } + + private static CatalogBiome biome(String key) { + List> steps = new ArrayList<>(11); + for (int index = 0; index < 11; index++) { + steps.add(List.of()); + } + return new CatalogBiome(key, 1.5F, 0.1F, true, List.of("minecraft:is_overworld"), steps, List.of(), null); + } +}