From 8d28ad4111e83d53d8eacadb86d8a2b581d594d2 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Tue, 29 Sep 2026 09:33:30 -0400 Subject: [PATCH] Embedded-only native loader - Drop the repository storage and slimjar download path from NativeRuntimeLibraries - Reject native manifests that do not use embedded storage - Report provider hash mismatches as IOException --- .../bootstrap/NativeRuntimeLibraries.java | 123 ++++-------------- .../iris/platform/bootstrap/SlimJar.java | 4 +- .../bootstrap/NativeRuntimeLibrariesTest.java | 68 ++++------ 3 files changed, 52 insertions(+), 143 deletions(-) diff --git a/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java b/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java index e66d980c6..54619f8d9 100644 --- a/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java +++ b/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java @@ -2,26 +2,9 @@ package art.arcane.iris.platform.bootstrap; import art.arcane.volmlib.nativelib.NativeVersion; import art.arcane.volmlib.nativelib.NativeAdapters; -import io.github.slimjar.app.builder.ApplicationBuilder; -import io.github.slimjar.downloader.DependencyDownloader; -import io.github.slimjar.downloader.URLDependencyDownloader; -import io.github.slimjar.downloader.strategy.FilePathStrategy; -import io.github.slimjar.downloader.verify.FileChecksumCalculator; -import io.github.slimjar.relocation.helper.RelocationHelper; -import io.github.slimjar.relocation.helper.VerifyingRelocationHelperFactory; -import io.github.slimjar.relocation.meta.FlatFileMetaMediatorFactory; -import io.github.slimjar.exceptions.DownloaderException; -import io.github.slimjar.resolver.data.Dependency; -import io.github.slimjar.resolver.data.DependencyData; -import io.github.slimjar.resolver.data.Repository; -import io.github.slimjar.resolver.reader.dependency.DependencyDataProvider; -import io.github.slimjar.resolver.reader.dependency.DependencyReader; -import io.github.slimjar.resolver.reader.dependency.WrappingDependencyDataProviderFactory; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; @@ -31,20 +14,16 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HexFormat; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.Properties; public final class NativeRuntimeLibraries { private static final String MANIFEST = "META-INF/iris/native-runtime.properties"; - private final Map dependencies; - private final Repository repository; + private static final String GROUP = "com.github.VolmitSoftware.VolmLib"; + private final List providers; - private NativeRuntimeLibraries(Map dependencies, Repository repository) { - this.dependencies = dependencies; - this.repository = repository; + private NativeRuntimeLibraries(List providers) { + this.providers = providers; } public static NativeRuntimeLibraries load(String minecraftVersion) { @@ -60,60 +39,27 @@ public final class NativeRuntimeLibraries { } } - static NativeRuntimeLibraries from(Properties manifest, String minecraftVersion) throws IOException { - String storage = manifest.getProperty("storage", "repository"); - if (!storage.equals("repository") && !storage.equals("embedded")) { + static NativeRuntimeLibraries from(Properties manifest, String minecraftVersion) { + String storage = manifest.getProperty("storage"); + if (!"embedded".equals(storage)) { throw new IllegalStateException("Invalid native runtime storage: " + storage); } - boolean embedded = storage.equals("embedded"); NativeVersion version = NativeVersion.resolve(minecraftVersion).orElseThrow( () -> new UnsupportedOperationException("No native provider for Minecraft " + minecraftVersion)); List modules = List.of(manifest.getProperty("modules", "").split(",")); - Map selected = new LinkedHashMap<>(); + List selected = new ArrayList<>(2); for (String module : List.of("native-common", "native-" + version.packageName())) { if (!modules.contains(module)) { throw new UnsupportedOperationException("Native provider is not distributed: " + module); } - String[] coordinate = manifest.getProperty(module + ".coordinate", "").split(":"); String digest = manifest.getProperty(module + ".sha256", ""); - if (coordinate.length != 3 || !coordinate[0].equals("com.github.VolmitSoftware.VolmLib") - || !coordinate[1].equals(module) || coordinate[2].isBlank() || !digest.matches("[0-9a-f]{64}") - || embedded && !coordinate[2].equals("embedded-" + digest)) { + String coordinate = GROUP + ":" + module + ":embedded-" + digest; + if (!digest.matches("[0-9a-f]{64}") || !coordinate.equals(manifest.getProperty(module + ".coordinate"))) { throw new IllegalStateException("Invalid native dependency manifest entry: " + module); } - selected.put(new Dependency(coordinate[0], coordinate[1], coordinate[2], null, List.of()), digest); + selected.add(new Provider(module, "embedded-" + digest, digest)); } - Repository repository = embedded ? null : new Repository(URI.create(manifest.getProperty("repository")).toURL()); - return new NativeRuntimeLibraries(Map.copyOf(selected), repository); - } - - public > T configure(T builder, Path downloads) { - WrappingDependencyDataProviderFactory defaults = new WrappingDependencyDataProviderFactory(DependencyReader.DEFAULT); - builder.dataProviderFactory(url -> { - DependencyDataProvider provider = defaults.create(url); - return () -> merge(provider.get()); - }); - VerifyingRelocationHelperFactory relocation = new VerifyingRelocationHelperFactory( - new FileChecksumCalculator("SHA-256"), - FilePathStrategy.createRelocationStrategy(downloads.toFile(), "Iris"), - new FlatFileMetaMediatorFactory()); - builder.relocationHelperFactory(relocator -> { - RelocationHelper helper = relocation.create(relocator); - return (dependency, file) -> dependencies.containsKey(dependency) ? file : helper.relocate(dependency, file); - }); - builder.downloaderFactory((output, resolver, verifier) -> { - DependencyDownloader delegate = new URLDependencyDownloader(output, resolver, verifier); - return dependency -> { - Optional result = delegate.download(dependency); - String expected = dependencies.get(dependency); - if (expected != null) { - File file = result.orElseThrow(() -> new DownloaderException("Native dependency has no jar: " + dependency)); - verify(file, expected); - } - return result; - }; - }); - return builder; + return new NativeRuntimeLibraries(List.copyOf(selected)); } public URLClassLoader openProviderLoader(Path downloads) { @@ -128,31 +74,13 @@ public final class NativeRuntimeLibraries { } } - DependencyData merge(DependencyData original) { - if (repository == null) { - return original; - } - List combined = new ArrayList<>(original.dependencies()); - combined.addAll(dependencies.keySet()); - List repositories = new ArrayList<>(original.repositories()); - if (!repositories.contains(repository)) { - repositories.add(repository); - } - return new DependencyData(original.mirrors(), repositories, combined, original.relocations()); - } - List providerUrls(Path downloads, ClassLoader resources) throws IOException { - List urls = new ArrayList<>(dependencies.size()); - for (Map.Entry entry : dependencies.entrySet()) { - Dependency dependency = entry.getKey(); - Path artifact = downloads.resolve(dependency.groupId().replace('.', '/')) - .resolve(dependency.artifactId()).resolve(dependency.version()) - .resolve(dependency.artifactId() + "-" + dependency.version() + ".jar"); - if (repository == null) { - extractEmbedded(resources, dependency.artifactId(), artifact, entry.getValue()); - } else { - verify(artifact.toFile(), entry.getValue()); - } + List urls = new ArrayList<>(providers.size()); + for (Provider provider : providers) { + Path artifact = downloads.resolve(GROUP.replace('.', '/')) + .resolve(provider.module()).resolve(provider.version()) + .resolve(provider.module() + "-" + provider.version() + ".jar"); + extractEmbedded(resources, provider.module(), artifact, provider.sha256()); urls.add(artifact.toUri().toURL()); } return urls; @@ -171,7 +99,7 @@ public final class NativeRuntimeLibraries { Path temporary = Files.createTempFile(artifact.getParent(), module + "-", ".tmp"); try { Files.copy(input, temporary, StandardCopyOption.REPLACE_EXISTING); - verify(temporary.toFile(), expected); + verify(temporary, expected); Files.move(temporary, artifact, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); } finally { Files.deleteIfExists(temporary); @@ -187,15 +115,12 @@ public final class NativeRuntimeLibraries { } } - static void verify(File file, String expected) { - try { - String actual = digest(file.toPath()); - if (!actual.equals(expected)) { - throw new DownloaderException("Native dependency does not match this Iris build: " + file.getName()); - } - } catch (IOException exception) { - throw new DownloaderException("Cannot verify native dependency: " + file, exception); + static void verify(Path file, String expected) throws IOException { + if (!digest(file).equals(expected)) { + throw new IOException("Native dependency does not match this Iris build: " + file.getFileName()); } } + private record Provider(String module, String version, String sha256) { + } } diff --git a/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java b/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java index 437d4ed75..046c4a15b 100644 --- a/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java +++ b/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java @@ -33,7 +33,7 @@ public class SlimJar { return; } NativeRuntimeLibraries libraries = NativeRuntimeLibraries.load(minecraftVersion); - libraries.configure(ApplicationBuilder.appending("Iris"), downloadPath) + ApplicationBuilder.appending("Iris") .injectableFactory(InjectableFactory.selecting( InjectableFactory.ERROR, InjectableFactory.INJECTABLE, @@ -78,7 +78,7 @@ public class SlimJar { Path downloadPath = plugin.getDataFolder("cache", "libraries").toPath(); debug(plugin, "Loading libraries..."); NativeRuntimeLibraries libraries = NativeRuntimeLibraries.load(Bukkit.getBukkitVersion()); - libraries.configure(ApplicationBuilder.appending(plugin.getName()), downloadPath) + ApplicationBuilder.appending(plugin.getName()) .injectableFactory(InjectableFactory.selecting(InjectableFactory.ERROR, InjectableFactory.INJECTABLE, InjectableFactory.WRAPPED, InjectableFactory.UNSAFE)) .downloadDirectoryPath(downloadPath) diff --git a/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java b/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java index e41353fd8..5ba346183 100644 --- a/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java +++ b/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java @@ -1,8 +1,5 @@ package art.arcane.iris.platform.bootstrap; -import io.github.slimjar.exceptions.DownloaderException; -import io.github.slimjar.resolver.data.Dependency; -import io.github.slimjar.resolver.data.DependencyData; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -28,7 +25,6 @@ import java.util.stream.Stream; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -37,37 +33,36 @@ public class NativeRuntimeLibrariesTest { public TemporaryFolder temporary = new TemporaryFolder(); @Test - public void selectsOnlyCommonAndMatchingProvider() throws Exception { - Properties manifest = manifest(); - Dependency original = new Dependency("example", "library", "1", null, List.of()); - DependencyData data = new DependencyData(List.of(), List.of(), List.of(original), List.of()); - List selected = NativeRuntimeLibraries.from(manifest, "26.3.0").merge(data).dependencies() - .stream().map(Dependency::artifactId).sorted().toList(); - assertEquals(List.of("library", "native-common", "native-v26_3_R1"), selected); - assertThrows(UnsupportedOperationException.class, () -> NativeRuntimeLibraries.from(manifest, "1.21.11")); + public void providersMustBeEmbedded() throws Exception { + EmbeddedFixture fixture = embeddedFixture(); + fixture.manifest().setProperty("storage", "repository"); + assertThrows(IllegalStateException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0")); + fixture.manifest().remove("storage"); + assertThrows(IllegalStateException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0")); + } + + @Test + public void rejectsMinecraftVersionsWithoutProvider() throws Exception { + EmbeddedFixture fixture = embeddedFixture(); + assertThrows(UnsupportedOperationException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "1.21.11")); + fixture.manifest().setProperty("modules", "native-common,native-v26_2_R1"); + assertThrows(UnsupportedOperationException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.3.0")); } @Test public void rejectsProviderBytesFromDifferentBuild() throws Exception { - Path jar = Files.createTempFile("native-runtime", ".jar"); - try { - Files.write(jar, new byte[0]); - NativeRuntimeLibraries.verify(jar.toFile(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - Files.writeString(jar, "different build"); - assertThrows(DownloaderException.class, () -> NativeRuntimeLibraries.verify(jar.toFile(), - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); - } finally { - Files.deleteIfExists(jar); - } + Path jar = temporary.newFile("native-runtime.jar").toPath(); + Files.write(jar, new byte[0]); + NativeRuntimeLibraries.verify(jar, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + Files.writeString(jar, "different build"); + assertThrows(IOException.class, () -> NativeRuntimeLibraries.verify(jar, + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); } @Test - public void embeddedProvidersLoadWithoutRepositoryAndSelectOnlyCurrentVersion() throws Exception { + public void embeddedProvidersSelectOnlyCommonAndCurrentVersion() throws Exception { EmbeddedFixture fixture = embeddedFixture(); NativeRuntimeLibraries libraries = NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0"); - Dependency original = new Dependency("example", "library", "1", null, List.of()); - DependencyData data = new DependencyData(List.of(), List.of(), List.of(original), List.of()); - assertSame(data, libraries.merge(data)); Path downloads = temporary.newFolder("downloads").toPath(); try (URLClassLoader resources = new URLClassLoader(new URL[]{fixture.resources().toUri().toURL()}, null)) { List providers = libraries.providerUrls(downloads, resources); @@ -125,7 +120,7 @@ public class NativeRuntimeLibrariesTest { NativeRuntimeLibraries libraries = NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0"); Path downloads = temporary.newFolder("downloads").toPath(); try (URLClassLoader resources = new URLClassLoader(new URL[]{fixture.resources().toUri().toURL()}, null)) { - DownloaderException failure = assertThrows(DownloaderException.class, () -> libraries.providerUrls(downloads, resources)); + IOException failure = assertThrows(IOException.class, () -> libraries.providerUrls(downloads, resources)); assertTrue(failure.getMessage().contains("native-v26_2_R1")); assertNoFailedExtract(downloads); } @@ -139,12 +134,13 @@ public class NativeRuntimeLibrariesTest { } private EmbeddedFixture embeddedFixture() throws Exception { - Properties properties = manifest(); + Properties properties = new Properties(); + List modules = List.of("native-common", "native-v26_2_R1", "native-v26_3_R1"); + properties.setProperty("modules", String.join(",", modules)); properties.setProperty("storage", "embedded"); - properties.remove("repository"); Path resources = temporary.newFolder("resources").toPath(); Path providers = Files.createDirectories(resources.resolve("META-INF/iris/native")); - for (String module : properties.getProperty("modules").split(",")) { + for (String module : modules) { ByteArrayOutputStream output = new ByteArrayOutputStream(); try (JarOutputStream jar = new JarOutputStream(output)) { jar.putNextEntry(new JarEntry("provider.txt")); @@ -169,16 +165,4 @@ public class NativeRuntimeLibrariesTest { private record EmbeddedFixture(Properties manifest, Path resources) { } - - private static Properties manifest() { - Properties properties = new Properties(); - List modules = List.of("native-common", "native-v26_2_R1", "native-v26_3_R1"); - properties.setProperty("modules", String.join(",", modules)); - properties.setProperty("repository", "https://jitpack.io/"); - for (String module : modules) { - properties.setProperty(module + ".coordinate", "com.github.VolmitSoftware.VolmLib:" + module + ":revision"); - properties.setProperty(module + ".sha256", "0".repeat(64)); - } - return properties; - } }