Skip to content

Commit 6e45ff5

Browse files
committed
Finalize Hyxin support and extends Mixin capabilities!
1 parent dbbf9d8 commit 6e45ff5

11 files changed

Lines changed: 161 additions & 53 deletions

File tree

init/src/main/java/com/fox2code/hypertale/init/late/HookClassLoader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public Class<?> loadClass(String name) throws ClassNotFoundException {
6363
@Override
6464
protected Class<?> findClass(String name) throws ClassNotFoundException {
6565
URL resource;
66-
if (!name.startsWith("com.hypixel.hytale.") ||
66+
if (name.startsWith("com.fox2code.hypertale.") || name.startsWith("org.objectweb.asm.") ||
67+
name.startsWith("org.spongepowered.asm.") || name.startsWith("org.bouncycastle.") ||
6768
(resource = getResource(name.replace('.', '/') + ".class")) == null) {
6869
return super.findClass(name);
6970
}

launcher/src/main/java/com/build_9/hyxin/LaunchEnvironment.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
package com.build_9.hyxin;
2525

2626
import com.fox2code.hypertale.loader.HypertaleModGatherer;
27+
import com.fox2code.hypertale.loader.HypertaleURLResourceLoader;
28+
import com.google.errorprone.annotations.DoNotCall;
2729
import org.objectweb.asm.ClassReader;
2830

2931
import java.io.FileNotFoundException;
@@ -42,10 +44,12 @@ public static LaunchEnvironment get() {
4244
return INSTANCE;
4345
}
4446

47+
@DoNotCall
4548
public static void create(ClassLoader systemLoader, ClassLoader earlyPluginLoader) {
4649
throw new IllegalStateException("Cannot call create() on Hypertale!");
4750
}
4851

52+
@DoNotCall
4953
public void captureRuntimeLoader(ClassLoader loader) {
5054
throw new IllegalStateException("Cannot call captureRuntimeLoader() on Hypertale!");
5155
}
@@ -55,7 +59,7 @@ public ClassLoader getSystemLoader() {
5559
}
5660

5761
public ClassLoader getEarlyPluginLoader() {
58-
return HypertaleModGatherer.class.getClassLoader();
62+
return HypertaleURLResourceLoader.EARLY_PLUGINS;
5963
}
6064

6165
public ClassLoader getRuntimeLoader() {
@@ -74,6 +78,8 @@ public ClassLoader findLoaderFor(String resourceName) throws IOException {
7478
ClassLoader mainClassLoader = HypertaleModGatherer.class.getClassLoader();
7579
if (mainClassLoader != null && mainClassLoader.getResource(resourceName) != null) {
7680
return mainClassLoader;
81+
} else if (HypertaleURLResourceLoader.EARLY_PLUGINS.getResource(resourceName) != null) {
82+
return HypertaleURLResourceLoader.EARLY_PLUGINS;
7783
} else {
7884
throw new FileNotFoundException("Could not find resource '" + resourceName + "' on any class loader.");
7985
}
@@ -84,11 +90,11 @@ public InputStream findResourceStream(String resourceName) throws IOException {
8490
}
8591

8692
public ClassReader getClassReader(String name) throws IOException, ClassNotFoundException {
87-
String fileName = name.replace(".", "/").concat(".class");
88-
try (InputStream inputStream = this.findResourceStream(fileName)) {
93+
String filePath = name.replace('.', '/') + ".class";
94+
try (InputStream inputStream = this.findResourceStream(filePath)) {
8995
return new ClassReader(inputStream);
9096
} catch (RuntimeException var3) {
91-
throw new ClassNotFoundException("Could not find class '" + fileName + "'.");
97+
throw new ClassNotFoundException("Could not find class '" + filePath + "'.");
9298
}
9399
}
94100
}

launcher/src/main/java/com/fox2code/hypertale/launcher/Main.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ private static void launchGame(String[] args, boolean dev) throws IOException {
239239
HypertaleModGatherer.gatherMods(args);
240240
if (modGatherer.isUsingMixins()) {
241241
EarlyLogger.log("Detected Mixin usage! (Mixin system will be enabled)");
242+
} else if (modGatherer.hasHyxin()) {
243+
EarlyLogger.log("No Mixin usage detected, but Hyxin is present! (Mixin system will be enabled)");
242244
} else {
243245
EarlyLogger.log("No Mixin usage detected! (Mixin system will be disabled)");
244246
}
@@ -268,10 +270,10 @@ private static void launchGame(String[] args, boolean dev) throws IOException {
268270
if (MainPlus.checkHaltLaunchGame(args)) {
269271
return;
270272
}
271-
if (modGatherer.isUsingMixins()) {
273+
if (modGatherer.shouldEnableMixinSubsystem()) {
272274
MixinLoader.preInitializeMixin();
273275
}
274-
PatchHelper.install(modGatherer.isUsingMixins());
276+
PatchHelper.install(modGatherer.shouldEnableMixinSubsystem());
275277
if (modGatherer.getModSyncBootstrap() != null &&
276278
// Don't run modSyncBootstrap twice if init already called it!
277279
!Boolean.getBoolean("hypertale.modSyncBootstrapInit")) {
@@ -280,11 +282,11 @@ private static void launchGame(String[] args, boolean dev) throws IOException {
280282
Class.forName(HypertaleCompatibility.classModSyncBootstrap, true, urlClassLoader);
281283
} catch (LinkageError | ClassNotFoundException _) {}
282284
}
283-
if (modGatherer.isUsingMixins()) {
285+
if (modGatherer.shouldEnableMixinSubsystem()) {
284286
MixinLoader.initialize();
285287
}
286288
HypertaleModLoader.loadHypertaleMods(modGatherer);
287-
if (modGatherer.isUsingMixins()) {
289+
if (modGatherer.shouldEnableMixinSubsystem()) {
288290
MixinLoader.postInitialize();
289291
}
290292
startHytale(args);

launcher/src/main/java/com/fox2code/hypertale/launcher/PatchHelper.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,16 @@ static void install(final boolean useMixins) {
5656
@Override
5757
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
5858
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
59-
if (useMixins && className.startsWith("com/hypixel/hytale/")) {
59+
if (loader == null || loader != PatchHelper.class.getClassLoader() ||
60+
loader.getParent() != PatchHelper.class.getClassLoader() ||
61+
className.startsWith("jdk/internal/")) {
62+
return classfileBuffer;
63+
}
64+
65+
if (useMixins) {
6066
classfileBuffer = MixinLoader.transformClass(className.replace('/', '.'), classfileBuffer);
6167
}
62-
if (loader == null || loader == Optimizer.class.getClassLoader() ||
63-
loader.getClass().getName().startsWith("jdk.internal.") ||
64-
!Optimizer.canOptimize(className)) {
68+
if (!Optimizer.canOptimize(className)) {
6569
return classfileBuffer;
6670
}
6771
ClassReader classReader = new ClassReader(classfileBuffer);

launcher/src/main/java/com/fox2code/hypertale/loader/HypertaleCompatibility.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@
2626
public final class HypertaleCompatibility {
2727
public static final String entryModSyncBootstrap = "de/onyxmoon/modsync/bootstrap/ModSyncBootstrap.class";
2828
public static final String classModSyncBootstrap = "de.onyxmoon.modsync.bootstrap.ModSyncBootstrap";
29+
public static final String entryHyxinMixinService = "com/build_9/hyxin/mixin/MixinService.class";
30+
public static final String entryHyxinTransformer = "com/build_9/hyxin/mixin/HyxinTransformer.class";
2931
}

launcher/src/main/java/com/fox2code/hypertale/loader/HypertaleModGatherer.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ public boolean isUsingMixins() {
8888
return this.usingMixins;
8989
}
9090

91+
public boolean hasHyxin() {
92+
return this.hasHyxin;
93+
}
94+
95+
public boolean shouldEnableMixinSubsystem() {
96+
// Hypertale is a Mixin backend on its own.
97+
return this.usingMixins || this.hasHyxin;
98+
}
99+
91100
public static HypertaleModGatherer gatherModsDev() {
92101
return gatherMods(EmptyArrays.EMPTY_STRING_ARRAY);
93102
}
@@ -100,7 +109,7 @@ public static HypertaleModGatherer gatherMods(String[] args) {
100109
ArrayList<File> libraries = new ArrayList<>();
101110
File modSyncBootstrap = null;
102111
if (HypertalePaths.hytaleEarlyPlugins.isDirectory()) {
103-
modSyncBootstrap = appendEarlyLoaderMods(mods);
112+
modSyncBootstrap = appendEarlyLoaderMods(mods, useMixins);
104113
}
105114
if (HypertalePaths.hytaleMods.isDirectory()) {
106115
appendMods(hypertaleMods, mods, libraries, useMixins);
@@ -155,11 +164,16 @@ private static List<ClassPathModCandidate> gatherClassPathMods(boolean[] useMixi
155164
}
156165
}
157166

158-
private static File appendEarlyLoaderMods(ArrayList<File> mods) {
167+
private static File appendEarlyLoaderMods(ArrayList<File> mods, boolean[] useMixins) {
159168
File modSyncBootstrap = null;
160169
for (File file : Objects.requireNonNull(HypertalePaths.hytaleEarlyPlugins.listFiles())) {
161170
if (file.isFile() && file.getName().endsWith(".jar")) {
162171
try(ZipFile zipFile = new ZipFile(file)) {
172+
if (zipFile.getEntry(HypertaleCompatibility.entryHyxinMixinService) != null ||
173+
zipFile.getEntry(HypertaleCompatibility.entryHyxinTransformer) != null) {
174+
useMixins[1] = true;
175+
continue; // <- We already implement Hyxin APIs
176+
}
163177
if (zipFile.getEntry(HypertaleCompatibility.entryModSyncBootstrap) != null) {
164178
modSyncBootstrap = file; // Mod sync bootstrap need special handling
165179
} else {
@@ -181,8 +195,8 @@ private static void appendMods(ArrayList<File> hypertaleMods, ArrayList<File> mo
181195
"com/fox2code/hypertale/init/Main.class") != null) {
182196
continue; // <- Do not consider HypertaleInit as a mod!
183197
}
184-
if (zipFile.getEntry(
185-
"com/build_9/hyxin/mixin/MixinService.class") != null) {
198+
if (zipFile.getEntry(HypertaleCompatibility.entryHyxinMixinService) != null ||
199+
zipFile.getEntry(HypertaleCompatibility.entryHyxinTransformer) != null) {
186200
useMixins[1] = true;
187201
continue; // <- We already implement Hyxin APIs
188202
}

launcher/src/main/java/com/fox2code/hypertale/loader/HypertaleModLoader.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ private static void processJsonAsMod(LinkedHashMap<File, JsonObject> loadedModsE
116116
throw new IllegalArgumentException("Tried to pre-load duplicate plugin");
117117
}
118118
} else {
119+
if (!isClassPath) {
120+
HypertaleURLResourceLoader.EARLY_PLUGINS.loaderAddURL(file.toURI().toURL());
121+
}
119122
loadedModsLate.put(file, jsonObject);
120123
}
121124
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2026 Fox2Code
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package com.fox2code.hypertale.loader;
25+
26+
import java.net.URL;
27+
import java.net.URLClassLoader;
28+
29+
public final class HypertaleURLResourceLoader extends URLClassLoader {
30+
public static final HypertaleURLResourceLoader EARLY_PLUGINS = new HypertaleURLResourceLoader();
31+
32+
private HypertaleURLResourceLoader() {
33+
super("HypertaleURLResourceLoader", new URL[0], HypertaleURLResourceLoader.class.getClassLoader());
34+
}
35+
36+
@Override
37+
public Class<?> loadClass(String name) throws ClassNotFoundException {
38+
throw new ClassNotFoundException(name);
39+
}
40+
41+
@Override
42+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
43+
throw new ClassNotFoundException(name);
44+
}
45+
46+
@Override
47+
protected Class<?> findClass(String name) throws ClassNotFoundException {
48+
throw new ClassNotFoundException(name);
49+
}
50+
51+
@Override
52+
public String toString() {
53+
return "HypertaleURLResourceLoader";
54+
}
55+
56+
void loaderAddURL(URL url) {
57+
super.addURL(url);
58+
}
59+
}

launcher/src/main/java/com/fox2code/hypertale/patcher/mixin/MixinLoader.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ public final class MixinLoader {
4747
private static final HashSet<String> modWithMixins = new HashSet<>();
4848
private static boolean preInitialized = false, initialized = false, postInitialized = false;
4949
private static IMixinTransformer mixinTransformer;
50+
private static final HashSet<String> mixinExclusions = new HashSet<>();
51+
52+
static {
53+
// Hypertale + Mixin system packages that must be excluded from mixin processing
54+
mixinExclusions.add("com.fox2code.hypertale.");
55+
mixinExclusions.add("org.objectweb.asm.");
56+
mixinExclusions.add("org.spongepowered.asm.");
57+
mixinExclusions.add("com.google.gson.");
58+
mixinExclusions.add("com.build_9.hyxin.");
59+
// Hytale classes that have miscellaneous reason to be excluded from mixin processing
60+
mixinExclusions.add("org.bouncycastle.");
61+
mixinExclusions.add("com.hypixel.hytale.plugin.early.");
62+
}
5063

5164
private MixinLoader() {}
5265

@@ -70,6 +83,7 @@ public static void preInitializeMixin() {
7083
MixinBootstrap.getPlatform().inject();
7184
mixinTransformer = // Inject mixin transformer into the class loader.
7285
(IMixinTransformer) MixinEnvironment.getCurrentEnvironment().getActiveTransformer();
86+
com.build_9.hyxin.mixin.MixinService.transformer = mixinTransformer; // Hyxin support
7387
MixinExtrasBootstrap.init();
7488
preInitialized = true;
7589
}
@@ -153,6 +167,7 @@ public static void addMixinConfigurationSafe(
153167
}
154168

155169
public static byte[] transformClass(String name, byte[] bytes) {
170+
if (isMixinExcluded(name)) return bytes;
156171
return mixinTransformer.transformClass(MixinEnvironment.getDefaultEnvironment(), name, bytes);
157172
}
158173

@@ -163,4 +178,27 @@ public static boolean isPostInitialized() {
163178
public static boolean modHasMixins(String modId) {
164179
return modWithMixins.contains(modId);
165180
}
181+
182+
static boolean isMixinExcluded(String className) {
183+
for (String mixinPackage : mixinExclusions) {
184+
if (className.startsWith(mixinPackage)) {
185+
return true;
186+
}
187+
}
188+
return false;
189+
}
190+
191+
static void addMixinExclusion(String packageName) {
192+
if (packageName == null || packageName.startsWith(".") ||
193+
!packageName.endsWith(".") || packageName.indexOf('/') != -1) {
194+
throw new IllegalArgumentException("Invalid package name: " + packageName);
195+
}
196+
if (packageName.startsWith("com.hypixel.hytale.") ||
197+
"com.hypixel.hytale.".startsWith(packageName)) {
198+
throw new IllegalArgumentException("Cannot exclude hytale itself: " + packageName);
199+
}
200+
if (!isMixinExcluded(packageName)) {
201+
mixinExclusions.add(packageName);
202+
}
203+
}
166204
}

0 commit comments

Comments
 (0)