Skip to content

Commit

Permalink
feat: try to add mixin support
Browse files Browse the repository at this point in the history
  • Loading branch information
MC-XiaoHei committed Jun 21, 2024
1 parent 3136423 commit 28c3ac4
Show file tree
Hide file tree
Showing 8 changed files with 1,051 additions and 20 deletions.
22 changes: 22 additions & 0 deletions java21/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,37 @@ java {
withSourcesJar()
}

val joptSimpleVersion = "5.0.4"
val asmVersion = "5.2"
val slf4jVersion = "1.8.0-beta2"
val jbAnnotationsVersion = "24.1.0"

val gradleWrapperVersion = "8.5"

val lwtsVersion = "1.1.0-SNAPSHOT"
val shurikenVersion = "0.0.1-SNAPSHOT"

val mixinVersion = "0.8.7-SNAPSHOT"
val leavesApiVersion = "1.20.6-R0.1-SNAPSHOT"

tasks.withType<JavaCompile>().configureEach {
options.release.set(21)
}

repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-releases/")
maven("https://repo.spongepowered.org/repository/maven-public/")
maven("https://repo.leavesmc.org/snapshots")
}

dependencies {
implementation("net.sf.jopt-simple:jopt-simple:$joptSimpleVersion")
implementation("org.ow2.asm:asm:$asmVersion")
implementation("org.slf4j:slf4j-api:$slf4jVersion")
implementation("org.jetbrains:annotations:$jbAnnotationsVersion")
implementation("org.spongepowered:mixin:$mixinVersion")
implementation("org.leavesmc.leaves:leaves-api:$leavesApiVersion")
implementation("io.sigpipe:jbsdiff:1.0")
}

Expand Down
53 changes: 53 additions & 0 deletions java21/src/main/java/launchwrapper/IClassNameTransformer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* This file is part of project Orion, licensed under the MIT License (MIT).
*
* Copyright (c) Original contributors ("I don't care" license? See https://github.com/Mojang/LegacyLauncher/issues/1)
* Copyright (c) 2017-2018 Mark Vainomaa <[email protected]>
* Copyright (c) Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package launchwrapper;

import org.jetbrains.annotations.NotNull;


/**
* Class name transformer, useful for mostly obfuscated &lt;-&gt; mapped names
*/
public interface IClassNameTransformer {
/**
* Remap class name to mapped name
*
* @param name Unmapped class name
* @return Remapped class name; or supplied class name if mapping was not found
*/
@NotNull
String remapClassName(@NotNull String name);

/**
* Unmap class name from remapped name
*
* @param name Mapped class name
* @return Unmapped (original) class name; or supplied class name if mapping was not found
*/
@NotNull
String unmapClassName(@NotNull String name);
}
49 changes: 49 additions & 0 deletions java21/src/main/java/launchwrapper/IClassTransformer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* This file is part of project Orion, licensed under the MIT License (MIT).
*
* Copyright (c) Original contributors ("I don't care" license? See https://github.com/Mojang/LegacyLauncher/issues/1)
* Copyright (c) 2017-2018 Mark Vainomaa <[email protected]>
* Copyright (c) Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package launchwrapper;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;


/**
* Class transformer interface
*/
public interface IClassTransformer {
/**
* Transforms class
*
* Note that class names are using dots instead of slashes.
*
* @param name Class unmapped name
* @param transformedName Class mapped name, may equal to unmapped name
* @param classData Raw class data or null
* @return Transformed (or supplied) class data or null
*/
@Nullable
byte[] transform(@NotNull String name, @NotNull String transformedName, @Nullable byte[] classData);
}
84 changes: 84 additions & 0 deletions java21/src/main/java/launchwrapper/ITweaker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* This file is part of project Orion, licensed under the MIT License (MIT).
*
* Copyright (c) Original contributors ("I don't care" license? See https://github.com/Mojang/LegacyLauncher/issues/1)
* Copyright (c) 2017-2018 Mark Vainomaa <[email protected]>
* Copyright (c) Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package launchwrapper;

import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.util.List;

/**
* A Tweaker
*/
public interface ITweaker {
/**
* Parses application's parameters (which you'll supply to app on command line)
*
* @param args {@link List} of application's parameters
*/
default void acceptOptions(@NotNull List<String> args) {
acceptOptions(args, null, null, null);
}

/**
* Old {@code acceptOptions} method retained for compatibility reasons.
*
* @param args Application's arguments
* @param gameDir unknown
* @param assetsDir unknown
* @param profile unknown
* @deprecated This method is not used internally. See {@link ITweaker#acceptOptions(List)}
*/
@Deprecated
default void acceptOptions(@NotNull List<String> args, File gameDir, final File assetsDir, String profile) {
throw new IllegalStateException("Please implement this method.");
}

/**
* Asks tweaker for transformers and other options
*
* @param classLoader Current {@link LaunchClassLoader} instance
*/
void injectIntoClassLoader(@NotNull LaunchClassLoader classLoader);

/**
* Gets arguments which will be added to endpoint's {@code main(String[])}
*
* @return String array of arguments
*/
@NotNull
String[] getLaunchArguments();

/**
* Gets main endpoint class to pass arguments to and start the wrapped application. Only used
* for main tweaker.
*
* @return Endpoint class name
*/
@NotNull
String getLaunchTarget();
}
154 changes: 154 additions & 0 deletions java21/src/main/java/launchwrapper/Launch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package launchwrapper;

import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.MixinEnvironment.Side;
import org.spongepowered.asm.mixin.Mixins;

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

public class Launch {
private static final Logger logger = LoggerFactory.getLogger("LaunchWrapper");
private static final String DEFAULT_TWEAK = "org.spongepowered.asm.launch.MixinTweaker";
public static LaunchClassLoader classLoader;
public static Map<String, Object> blackboard = new HashMap<>();

Launch(URL[] urls, ClassLoader parent) {
classLoader = new LaunchClassLoader(urls, parent);
Thread.currentThread().setContextClassLoader(classLoader);
}

private void configureMixin() {
MixinEnvironment.getDefaultEnvironment().setSide(Side.SERVER);
Mixins.addConfiguration("mixins.akarin.core.json");
}

private URL[] getURLs() {
String cp = System.getProperty("java.class.path");
String[] elements = cp.split(File.pathSeparator);
if (elements.length == 0) {
elements = new String[]{""};
}
URL[] urls = new URL[elements.length];
for (int i = 0; i < elements.length; i++) {
try {
URL url = new File(elements[i]).toURI().toURL();
urls[i] = url;
} catch (MalformedURLException ignore) {
// malformed file string or class path element does not exist
}
}
return urls;
}

private void launch(String[] args) {
final OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();

final OptionSpec<String> tweakClassOption = parser
.accepts("tweakClass", "Tweak class(es) to load")
.withRequiredArg()
.defaultsTo(DEFAULT_TWEAK);
final OptionSpec<String> nonOption = parser.nonOptions();

final OptionSet options = parser.parse(args);
final List<String> tweakClassNames = new ArrayList<>(options.valuesOf(tweakClassOption));

final List<String> argumentList = new ArrayList<>();
// This list of names will be interacted with through tweakers. They can append to this list
// any 'discovered' tweakers from their preferred mod loading mechanism
// By making this object discoverable and accessible it's possible to perform
// things like cascading of tweakers
blackboard.put("TweakClasses", tweakClassNames);

// This argument list will be constructed from all tweakers. It is visible here so
// all tweakers can figure out if a particular argument is present, and add it if not
blackboard.put("ArgumentList", argumentList);

// This is to prevent duplicates - in case a tweaker decides to add itself or something
final Set<String> visitedTweakerNames = new HashSet<>();
// The 'definitive' list of tweakers
final List<ITweaker> allTweakers = new ArrayList<>();
try {
final List<ITweaker> pendingTweakers = new ArrayList<>(tweakClassNames.size() + 1);
// The list of tweak instances - may be useful for interoperability
blackboard.put("Tweaks", pendingTweakers);
// The primary tweaker (the first one specified on the command line) will actually
// be responsible for providing the 'main' name and generally gets called first
ITweaker primaryTweaker = null;
// This loop will terminate, unless there is some sort of pathological tweaker
// that reinserts itself with a new identity every pass
// It is here to allow tweakers to "push" new tweak classes onto the 'stack' of
// tweakers to evaluate allowing for cascaded discovery and injection of tweakers
while (!tweakClassNames.isEmpty()) {
for (final Iterator<String> it = tweakClassNames.iterator(); it.hasNext(); ) {
final String tweakName = it.next();
// Safety check - don't reprocess something we've already visited
if (visitedTweakerNames.contains(tweakName)) {
logger.warn("Tweak class name {} has already been visited -- skipping", tweakName);
// remove the tweaker from the stack otherwise it will create an infinite loop
it.remove();
continue;
} else {
visitedTweakerNames.add(tweakName);
}
logger.info("Loading tweak class name {}", tweakName);

// Ensure we allow the tweak class to load with the parent classloader
classLoader.getClassLoaderExclusions().add(tweakName.substring(0, tweakName.lastIndexOf('.')));
final ITweaker tweaker = (ITweaker) Class.forName(tweakName, true, classLoader)
.getConstructor().newInstance();
pendingTweakers.add(tweaker);

// Remove the tweaker from the list of tweaker names we've processed this pass
it.remove();
// If we haven't visited a tweaker yet, the first will become the 'primary' tweaker
if (primaryTweaker == null) {
logger.info("Using primary tweak class name {}", tweakName);
primaryTweaker = tweaker;
}
}

// Configure environment to avoid warn
configureMixin();

// Now, iterate all the tweakers we just instantiated
while (!pendingTweakers.isEmpty()) {
final ITweaker tweaker = pendingTweakers.remove(0);
logger.info("Calling tweak class {}", tweaker.getClass().getName());
tweaker.acceptOptions(options.valuesOf(nonOption));
tweaker.injectIntoClassLoader(classLoader);
allTweakers.add(tweaker);
}
// continue around the loop until there's no tweak classes
}

// Once we're done, we then ask all the tweakers for their arguments and add them all to the
// master argument list
for (final ITweaker tweaker : allTweakers) {
argumentList.addAll(Arrays.asList(tweaker.getLaunchArguments()));
}

final String launchTarget = "org.bukkit.craftbukkit.Main";
final Class<?> clazz = Class.forName(launchTarget, false, classLoader);
final Method mainMethod = clazz.getMethod("main", String[].class);

logger.info("Launching wrapped Minecraft {{}}", launchTarget);
// Pass original server arguments
argumentList.addAll(Arrays.asList(args));
mainMethod.invoke(null, (Object) argumentList.toArray(new String[0]));
} catch (Exception e) {
logger.error("Unable to launch", e);
System.exit(1);
}
}
}
Loading

1 comment on commit 28c3ac4

@s-yh-china
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下次进行这种程度的更改前 记得更改版本号
然后许可证是什么情况 单独拿一个文件放吧

Please sign in to comment.