Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified gradlew
100644 → 100755
Empty file.
52 changes: 29 additions & 23 deletions src/main/java/cc/irori/hyinit/ConfigCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,45 @@ public final class ConfigCollector {
private ConfigCollector() {}

public static Result collectMixinConfigs(Path workingDir) {
Objects.requireNonNull(workingDir, "workingDir");
return collectMixinConfigs(workingDir, List.of(workingDir.resolve("earlyplugins")));
}

Path dir = workingDir.resolve("earlyplugins");
if (!Files.isDirectory(dir)) {
return new Result(List.of(), Map.of(), List.of());
}
public static Result collectMixinConfigs(Path workingDir, List<Path> earlyPluginDirs) {
Objects.requireNonNull(workingDir, "workingDir");
Objects.requireNonNull(earlyPluginDirs, "earlyPluginDirs");

List<Path> jars = listJars(dir);
List<String> warnings = new ArrayList<>();
Map<String, Path> origins = new LinkedHashMap<>();

// Preserve order, deduplicate
LinkedHashSet<String> configs = new LinkedHashSet<>();

for (Path jar : jars) {
try (JarFile jf = new JarFile(jar.toFile(), false)) {
JarEntry entry = jf.getJarEntry("manifest.json");
if (entry == null) continue;
for (Path dir : earlyPluginDirs) {
if (!Files.isDirectory(dir)) {
continue;
}

List<Path> jars = listJars(dir);

for (Path jar : jars) {
try (JarFile jf = new JarFile(jar.toFile(), false)) {
JarEntry entry = jf.getJarEntry("manifest.json");
if (entry == null) continue;

JsonObject root = readJsonObject(jf, entry);
List<String> found = extractMixinConfigs(root);
JsonObject root = readJsonObject(jf, entry);
List<String> found = extractMixinConfigs(root);

for (String cfg : found) {
if (cfg == null) continue;
String normalized = normalizeConfigPath(cfg);
if (normalized.isEmpty()) continue;
for (String cfg : found) {
if (cfg == null) continue;
String normalized = normalizeConfigPath(cfg);
if (normalized.isEmpty()) continue;

configs.add(normalized);
origins.putIfAbsent(normalized, jar);
configs.add(normalized);
origins.putIfAbsent(normalized, jar);
}
} catch (Exception e) {
warnings.add("Failed to read " + jar.getFileName() + ": "
+ e.getClass().getSimpleName()
+ (e.getMessage() != null ? (": " + e.getMessage()) : ""));
}
} catch (Exception e) {
warnings.add("Failed to read " + jar.getFileName() + ": "
+ e.getClass().getSimpleName() + (e.getMessage() != null ? (": " + e.getMessage()) : ""));
}
}

Expand Down
23 changes: 14 additions & 9 deletions src/main/java/cc/irori/hyinit/HyinitLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,19 +162,24 @@ public void log(Level level, String message, Throwable t) {

@Override
public <T extends Throwable> T throwing(T t) {
hytaleLogger.atWarning().withCause(t).log();
if (isHytaleLoggerAvailable()) {
hytaleLogger.atWarning().withCause(t).log();
} else {
JAVA_LOGGER.warn("throwing", t);
}
return t;
}

private boolean isHytaleLoggerAvailable() {
if (hytaleLogger == null) {
try {
hytaleLogger = HytaleLogger.get(LOGGER_NAME);
return true;
} catch (NoClassDefFoundError | Exception ignored) {
}
}
return false;
if (hytaleLogger != null) {
return true;
}
try {
hytaleLogger = HytaleLogger.get(LOGGER_NAME);
return hytaleLogger != null;
} catch (NoClassDefFoundError | Exception ignored) {
return false;
}
}

private static java.util.logging.Level toJavaLevel(Level level) {
Expand Down
29 changes: 26 additions & 3 deletions src/main/java/cc/irori/hyinit/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,19 @@ static void main(String[] args) throws Exception {
.getLocation()
.toURI()),
new SourceMetadata(false));
for (Path path : collectClasspathJars(serverJar, cwd.resolve("earlyplugins"))) {
classLoader.addCodeSource(path, new SourceMetadata(true));
List<Path> earlyPluginDirs = new java.util.ArrayList<>();
earlyPluginDirs.add(cwd.resolve("earlyplugins"));
earlyPluginDirs.addAll(parseEarlyPluginPaths(args));

for (Path dir : earlyPluginDirs) {
for (Path path : collectClasspathJars(serverJar, dir)) {
classLoader.addCodeSource(path, new SourceMetadata(true));
}
}

HyinitMixinService.setGameClassLoader(classLoader);

ConfigCollector.Result result = ConfigCollector.collectMixinConfigs(cwd);
ConfigCollector.Result result = ConfigCollector.collectMixinConfigs(cwd, earlyPluginDirs);
result.warnings().forEach(LOGGER::warn);

List<String> configs = result.configs();
Expand Down Expand Up @@ -112,6 +118,23 @@ private static List<Path> collectClasspathJars(Path serverJar, Path earlyPlugins
return List.of();
}

private static List<Path> parseEarlyPluginPaths(String[] args) {
List<Path> paths = new java.util.ArrayList<>();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("--early-plugins") && i + 1 < args.length) {
for (String pathStr : args[i + 1].split(",")) {
paths.add(Paths.get(pathStr.trim()));
}
} else if (args[i].startsWith("--early-plugins=")) {
String value = args[i].substring("--early-plugins=".length());
for (String pathStr : value.split(",")) {
paths.add(Paths.get(pathStr.trim()));
}
}
}
return paths;
}

private static void finishMixinBootstrapping() {
try {
Method m = MixinEnvironment.class.getDeclaredMethod("gotoPhase", MixinEnvironment.Phase.class);
Expand Down
118 changes: 95 additions & 23 deletions src/main/java/cc/irori/hyinit/mixin/HyinitClassLoader.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
package cc.irori.hyinit.mixin;

import cc.irori.hyinit.HyinitLogger;
import cc.irori.hyinit.shared.SourceMetaStore;
import cc.irori.hyinit.shared.SourceMetadata;
import cc.irori.hyinit.util.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.nio.file.*;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.util.*;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Manifest;

import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;

import cc.irori.hyinit.HyinitLogger;
import cc.irori.hyinit.shared.SourceMetaStore;
import cc.irori.hyinit.shared.SourceMetadata;
import cc.irori.hyinit.util.LoaderUtil;
import cc.irori.hyinit.util.ManifestUtil;
import cc.irori.hyinit.util.UrlConversionException;
import cc.irori.hyinit.util.UrlUtil;

public class HyinitClassLoader extends SecureClassLoader {

private static final boolean DEBUG = System.getProperty("hyinit.debugClassLoader") != null;
Expand Down Expand Up @@ -114,11 +134,15 @@ public InputStream getResourceAsStream(String name) {
public Enumeration<URL> getResources(String name) throws IOException {
Objects.requireNonNull(name, "name");

Enumeration<URL> resources = urlLoader.getResources(name);
if (!resources.hasMoreElements()) {
return originalLoader.getResources(name);
}
return resources;
Enumeration<URL> primary = urlLoader.getResources(name);
Enumeration<URL> fallback = originalLoader.getResources(name);

if (!primary.hasMoreElements()) return fallback;
if (!fallback.hasMoreElements()) return primary;

var merged = Collections.list(primary);
while (fallback.hasMoreElements()) merged.add(fallback.nextElement());
return Collections.enumeration(merged);
}

@Override
Expand Down Expand Up @@ -170,10 +194,6 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
}

private Class<?> tryLoadClass(String name, boolean allowFromParent) throws ClassNotFoundException {
if (name.startsWith(".java")) {
return null;
}

if (!allowFromParent && !parentSourcedClasses.isEmpty()) {
int pos = name.length();

Expand Down Expand Up @@ -219,11 +239,30 @@ private Class<?> tryLoadClass(String name, boolean allowFromParent) throws Class
}

public byte[] getClassByteArray(String name, boolean runTransformers) throws IOException {
byte[] bytes;
if (runTransformers) {
return getPreMixinClassBytes(name);
bytes = getPreMixinClassBytes(name);
} else {
return getRawClassBytes(name);
bytes = getRawClassBytes(name);
}

if (bytes != null) {
return bytes;
}

// No class file on disk, test for synthetic class generation for @Accessor and @Invoker
if (isTransformerInitialized()) {
try {
byte[] generated = transformer.generateClass(MixinEnvironment.getCurrentEnvironment(), name);
if (generated != null) {
return generated;
}
} catch (Throwable t) {
// Not a synthetic class — fall through
}
}

return null;
}

public byte[] getRawClassBytes(String name) throws IOException {
Expand Down Expand Up @@ -281,13 +320,30 @@ private byte[] getPostMixinClassByteArray(String name, boolean allowFromParent)
return original;
}

if (original != null) {
try {
return transformer.transformClassBytes(name, name, original);
} catch (Throwable t) {
String message = String.format("Mixin transformation of %s failed", name);
HyinitLogger.get().error(message, t);
throw new RuntimeException(message, t);
}
}

// No class file on disk, proceed with Mixin's generateClass for synthetics
try {
return transformer.transformClassBytes(name, name, original);
byte[] generated = transformer.generateClass(MixinEnvironment.getCurrentEnvironment(), name);
if (generated != null) {
if (DEBUG) {
HyinitLogger.get().info(String.format("Generated synthetic class: %s (%d bytes)", name, generated.length));
}
return generated;
}
} catch (Throwable t) {
String message = String.format("Mixin transformation of %s failed", name);
HyinitLogger.get().error(message, t);
throw new RuntimeException(message, t);
// fall through on no synthetic classes
}

return null;
}

public boolean isClassLoaded(String name) {
Expand Down Expand Up @@ -352,8 +408,24 @@ private Metadata getMetadata(Path sourcePath) {
});
}

private static final Set<String> TRANSFORM_EXCLUSIONS = Set.of(
"java.", "javax.", "jdk.", "sun.", "com.sun.",
"org.objectweb.asm.",
"org.spongepowered.asm.",
"com.llamalad7.mixinextras.",
"cc.irori.hyinit.mixin.", "cc.irori.hyinit.shared.",
"org.slf4j.", "org.apache.logging.", "ch.qos.logback.",
"com.google.gson.", "com.google.flogger.",
"org.bouncycastle.",
"com.hypixel.hytale.plugin.early.");

private static boolean canTransformClass(String name) {
return true; // Placeholder
for (String prefix : TRANSFORM_EXCLUSIONS) {
if (name.startsWith(prefix)) {
return false;
}
}
return true;
}

private static boolean hasRegularCodeSource(URL url) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public MixinEnvironment.CompatibilityLevel getMinCompatibilityLevel() {

@Override
public MixinEnvironment.CompatibilityLevel getMaxCompatibilityLevel() {
return MixinEnvironment.CompatibilityLevel.JAVA_22;
return MixinEnvironment.CompatibilityLevel.JAVA_25;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.util.Collections;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
Expand All @@ -33,8 +33,9 @@ public MixinPluginClassLoader(URL[] urls, ClassLoader parent) {
String name,
boolean useBridge,
CallbackInfoReturnable<Class<?>> cir,
@Local(name = "loadClass") Class<?> loadClass)
@Local(ordinal = 0) Class<?> loadClass)
throws ClassNotFoundException {
if (loadClass == null) return;
String fileName = LoaderUtil.getClassFileName(name);
URL url = super.getResource(fileName);
if (url != null) {
Expand Down Expand Up @@ -77,17 +78,7 @@ public MixinPluginClassLoader(URL[] urls, ClassLoader parent) {
private Enumeration<URL> hyinit$blockLoadingManifestResources(ClassLoader instance, String name)
throws IOException {
if (name.equalsIgnoreCase("manifest.json")) {
return new Enumeration<>() {
@Override
public boolean hasMoreElements() {
return false;
}

@Override
public URL nextElement() {
throw new NoSuchElementException();
}
};
return Collections.emptyEnumeration();
}
return instance.getResources(name);
}
Expand Down
Loading