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
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-29 09:33:30 -04:00
1 parent bf11d44370
commit 8d28ad4111
3 files changed
+52 -143

No files matched your search

@@ -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<Dependency, String> dependencies;
private final Repository repository;
private static final String GROUP = "com.github.VolmitSoftware.VolmLib";
private final List<Provider> providers;
private NativeRuntimeLibraries(Map<Dependency, String> dependencies, Repository repository) {
this.dependencies = dependencies;
this.repository = repository;
private NativeRuntimeLibraries(List<Provider> 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<String> modules = List.of(manifest.getProperty("modules", "").split(","));
Map<Dependency, String> selected = new LinkedHashMap<>();
List<Provider> 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 extends ApplicationBuilder<?>> 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<File> 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<Dependency> combined = new ArrayList<>(original.dependencies());
combined.addAll(dependencies.keySet());
List<Repository> repositories = new ArrayList<>(original.repositories());
if (!repositories.contains(repository)) {
repositories.add(repository);
}
return new DependencyData(original.mirrors(), repositories, combined, original.relocations());
}
List<URL> providerUrls(Path downloads, ClassLoader resources) throws IOException {
List<URL> urls = new ArrayList<>(dependencies.size());
for (Map.Entry<Dependency, String> 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<URL> 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) {
}
}
@@ -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)