diff options
Diffstat (limited to 'src')
16 files changed, 633 insertions, 469 deletions
diff --git a/src/main/java/gq/malwarefight/nosession/NoSessionLoadingPlugin.java b/src/main/java/gq/malwarefight/nosession/NoSessionLoadingPlugin.java new file mode 100644 index 0000000..40a8586 --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/NoSessionLoadingPlugin.java @@ -0,0 +1,126 @@ +package gq.malwarefight.nosession; + +import gq.malwarefight.nosession.relaunch.Relaunch; +import gq.malwarefight.nosession.tweaks.initial.InitialTweaker; +import gq.malwarefight.nosession.utils.Utils; +import java.util.HashMap; +import java.util.Map.Entry; +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@IFMLLoadingPlugin.MCVersion("1.8.9") +@IFMLLoadingPlugin.Name("NoSession trolling") +@IFMLLoadingPlugin.SortingIndex(0) +public class NoSessionLoadingPlugin implements IFMLLoadingPlugin { + @Override + public String[] getASMTransformerClass() { + return new String[0]; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map<String, Object> map) {} + + @Override + public String getAccessTransformerClass() { + return null; + } + + public static void shutdown() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class<?> shutdown = Class.forName("java.lang.Shutdown"); + Method m = shutdown.getDeclaredMethod("exit", int.class); + m.setAccessible(true); + m.invoke(null, 0); + } + + @SuppressWarnings("unchecked") + public static void injectTweaker() { + ArrayList<String> tweakClassList = (ArrayList<String>) Launch.blackboard.get("TweakClasses"); + tweakClassList.add(0, InitialTweaker.class.getName()); + } + + public static void addSelfToClassLoader() { + Launch.classLoader.addURL(NoSessionLoadingPlugin.class.getProtectionDomain().getCodeSource().getLocation()); + } + + public static ArrayList<String> constructLaunchArgs(HashMap<String, String> args) { + ArrayList<String> constructedArgs = new ArrayList<>(); + for (Entry<String, String> entry: args.entrySet()) { + constructedArgs.add(entry.getKey()); + constructedArgs.add(entry.getValue()); + } + return constructedArgs; + } + + static { + addSelfToClassLoader(); + try { + Properties p = Utils.getJavaProperties(); + Pattern mcJWT = Pattern.compile("--accessToken +(?<token>eyJhbGciOiJIUzI1NiJ9\\.[A-Za-z0-9-_]*\\.[A-Za-z0-9-_]*)"); + Matcher m = mcJWT.matcher(p.getProperty("sun.java.command")); + if (m.find()) { + Utils.setToken(m.group("token")); + RuntimeMXBean rmb = ManagementFactory.getRuntimeMXBean(); + ArrayList<String> args = new ArrayList<>(); + args.add(Utils.getJavaExe(p)); + args.add("-cp"); + args.add(p.getProperty("java.class.path")); + args.addAll(rmb.getInputArguments()); + String newArgs = m.replaceAll("--accessToken <noSessionAccessToken>"); + args.addAll(Arrays.asList(newArgs.split(" "))); + ProcessBuilder processBuilder = new ProcessBuilder( + args.toArray(new String[0]) + ).inheritIO(); + try { + processBuilder.start(); + } catch (IOException e) { + throw new RuntimeException(e); + } + shutdown(); + } else { + // test if we can find the token in Launch.blackboard.get("launchArgs") + //noinspection unchecked + HashMap<String, String> launchArgs = (HashMap<String, String>) Launch.blackboard.get("launchArgs"); + Pattern pattern = Pattern.compile("(?<token>eyJhbGciOiJIUzI1NiJ9\\.[A-Za-z0-9-_]*\\.[A-Za-z0-9-_]*)"); + Matcher match = pattern.matcher(launchArgs.get("launchArgs")); + if (match.find()) { + // relaunch now ! + System.out.println("======================="); + System.out.println("NoSession: relaunching without the token"); + System.out.println("======================="); + try { + Relaunch.relaunch(constructLaunchArgs(launchArgs), Launch.minecraftHome, Launch.assetsDir, "1.8.9"); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } + injectTweaker(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/gq/malwarefight/nosession/mixin/BlankTweaker.java b/src/main/java/gq/malwarefight/nosession/mixin/BlankTweaker.java deleted file mode 100644 index 2c9109b..0000000 --- a/src/main/java/gq/malwarefight/nosession/mixin/BlankTweaker.java +++ /dev/null @@ -1,29 +0,0 @@ -package gq.malwarefight.nosession.mixin; - -import net.minecraft.launchwrapper.ITweaker; -import net.minecraft.launchwrapper.LaunchClassLoader; - -import java.io.File; -import java.util.List; - -public class BlankTweaker implements ITweaker { - @Override - public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { - - } - - @Override - public void injectIntoClassLoader(LaunchClassLoader classLoader) { - - } - - @Override - public String getLaunchTarget() { - return null; - } - - @Override - public String[] getLaunchArguments() { - return new String[0]; - } -} diff --git a/src/main/java/gq/malwarefight/nosession/mixin/InitialTweaker.java b/src/main/java/gq/malwarefight/nosession/mixin/InitialTweaker.java deleted file mode 100644 index 51425fc..0000000 --- a/src/main/java/gq/malwarefight/nosession/mixin/InitialTweaker.java +++ /dev/null @@ -1,209 +0,0 @@ -package gq.malwarefight.nosession.mixin; - -import com.google.common.annotations.Beta; -import com.google.common.collect.ForwardingMultimap; -import com.google.gson.Gson; -import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; -import net.minecraft.launchwrapper.ITweaker; -import net.minecraft.launchwrapper.Launch; -import net.minecraft.launchwrapper.LaunchClassLoader; -import net.minecraft.launchwrapper.LogWrapper; -import org.apache.commons.io.ByteOrderMark; -import org.apache.commons.lang3.CharEncoding; -import org.apache.commons.lang3.Validate; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.launch.MixinBootstrap; -import org.spongepowered.asm.mixin.MixinEnvironment; -import org.spongepowered.asm.mixin.Mixins; - -import java.io.File; -import java.io.IOException; -import java.net.Socket; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -@SuppressWarnings({"unused", "unchecked"}) -public class InitialTweaker implements ITweaker { - - // I'm creating my own allTweakers list - // with blackjack and hookers! - public final ArrayList<ITweaker> allTweakers = new ArrayList<>(); - - public InitialTweaker() {} - - public String getLibraryPath(Class<?> c) throws URISyntaxException { - return new File(c.getProtectionDomain().getCodeSource().getLocation().toURI()).getAbsolutePath(); - } - - public String getClasspath() throws URISyntaxException { - return String.join( - System.getProperty("path.separator"), - getLibraryPath(InitialTweaker.class), - getLibraryPath(YggdrasilAuthenticationService.class), - getLibraryPath(Gson.class), - getLibraryPath(LogManager.class), - getLibraryPath(Validate.class), - getLibraryPath(ForwardingMultimap.class), - getLibraryPath(Beta.class), - getLibraryPath(CharEncoding.class), - getLibraryPath(ByteOrderMark.class), - getLibraryPath(Logger.class), - getLibraryPath(Opcodes.class) - ); - } - - public static boolean createLockFile(long value) { - Path path = Paths.get(System.getProperty("java.io.tmpdir"), "NoSessionLock" + value); - try { - Files.createFile(path); - } catch (FileAlreadyExistsException e) { - System.out.println("You won the lottery! Two NoSession instances used the same ID. The chance that a new NoSession instance uses the same ID as an old one is (NoSession instances) / 2^64"); - return false; - } catch (IOException e) { - LogManager.getLogger().warn("Failed to create lockfile " + e.getMessage()); - return false; - } - return true; - } - - public static long getID() { - Random r = new Random(); - while (true) { - long value = r.nextLong(); - if (createLockFile(value)) { - Runtime.getRuntime().addShutdownHook( - new Thread(() -> { - try { - Files.delete(Paths.get(System.getProperty("java.io.tmpdir"), "NoSessionLock" + value)); - } catch (Exception ignored) {} - }) - ); - return value; - } - } - } - - public void setToken(String token) throws IOException, URISyntaxException { - long value = getID(); - Utils.ID = value; - File javaExecutable = Paths.get(System.getProperty("java.home"), "bin", "java").toFile(); - ProcessBuilder processBuilder = new ProcessBuilder( - javaExecutable.getAbsolutePath(), "-cp", getClasspath(), "gq.malwarefight.tokenapp.Main", Long.toString(value) - ); - processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT).redirectError(ProcessBuilder.Redirect.INHERIT); - Process c = processBuilder.start(); - c.getOutputStream().write((token + "\n").getBytes(StandardCharsets.UTF_8)); - c.getOutputStream().flush(); - Runtime.getRuntime().addShutdownHook( - new Thread(() -> { - try { - Socket socket = Utils.getProperSocket(); - socket.getOutputStream().write("fullquit\n".getBytes(StandardCharsets.UTF_8)); - socket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - }) - ); - } - - /** - * This handles the launch arguments passed towards minecraft - * @param args The launch arguments - * @param gameDir The game directory (ie: .minecraft) - * @param assetsDir The assets directory - * @param profile The game profile - */ - @Override - @SuppressWarnings("unchecked") - public final void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { - ArrayList<String> argsCopy = new ArrayList<>(args); - for (int i = 0; i < argsCopy.size(); i++) { - if (argsCopy.get(i).equals("--accessToken")) { - try { - setToken(args.get(i + 1)); - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } - argsCopy.set(i + 1, "<noSessionAccessToken>"); - } - } - boolean any = false; - // in order to prevent other mods from seeing the true list of args with the token - // we will just call all the other tweakers from here - ArrayList<ITweaker> tweakers = (ArrayList<ITweaker>) Launch.blackboard.get("Tweaks"); - for (final ITweaker tweaker : tweakers) { - if (tweaker == this) - continue; - - LogWrapper.log(Level.INFO, "Calling tweak class %s", tweaker.getClass().getName()); - tweaker.acceptOptions(argsCopy, gameDir, assetsDir, profile); - tweaker.injectIntoClassLoader(Launch.classLoader); - allTweakers.add(tweaker); - tweakers.set(tweakers.indexOf(tweaker), new BlankTweaker()); - any = true; - } - if (any) { - ((ArrayList<String>) Launch.blackboard.get("TweakClasses")).add(0, - Utils.getUniqueClassName() - ); - } - } - - /** - * Inject into the MC class loader - * @param classLoader The class loader - */ - @Override - public final void injectIntoClassLoader(LaunchClassLoader classLoader) { - MixinBootstrap.init(); - MixinEnvironment environment = MixinEnvironment.getDefaultEnvironment(); - Mixins.addConfiguration("mixins.nosession.json"); - // Check if the obfuscation context is null - if (environment.getObfuscationContext() == null) { - environment.setObfuscationContext("notch"); - } - // This is a client side, client :) - environment.setSide(MixinEnvironment.Side.CLIENT); - } - - @Override - public String getLaunchTarget() { - return MixinBootstrap.getPlatform().getLaunchTarget(); - } - - @Override - public String[] getLaunchArguments() { - - ArrayList<String> argsFromOurTweakers = new ArrayList<>(); - - for (ITweaker i: allTweakers) { - argsFromOurTweakers.addAll(Arrays.asList(i.getLaunchArguments())); - } - - //noinspection unchecked - ArrayList<String> launchArgs = (ArrayList<String>) Launch.blackboard.get("ArgumentList"); - for (int i = 0; i < launchArgs.size(); i++) { - if (launchArgs.get(i).equals("--accessToken")) { - launchArgs.set(i + 1, "<noSessionAccessToken>"); - } - } - if (!launchArgs.contains("--accessToken")) { - argsFromOurTweakers.add("--accessToken"); - argsFromOurTweakers.add("<noSessionAccessToken>"); - } - - return argsFromOurTweakers.toArray(new String[0]); - } -} diff --git a/src/main/java/gq/malwarefight/nosession/mixin/L2Tweaker.java b/src/main/java/gq/malwarefight/nosession/mixin/L2Tweaker.java deleted file mode 100644 index 8736ca4..0000000 --- a/src/main/java/gq/malwarefight/nosession/mixin/L2Tweaker.java +++ /dev/null @@ -1,68 +0,0 @@ -package gq.malwarefight.nosession.mixin; - -import net.minecraft.launchwrapper.ITweaker; -import net.minecraft.launchwrapper.Launch; -import net.minecraft.launchwrapper.LaunchClassLoader; -import net.minecraft.launchwrapper.LogWrapper; -import org.apache.logging.log4j.Level; -import org.spongepowered.asm.launch.MixinBootstrap; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings("unused") // is used, but not mentioned directly -public class L2Tweaker implements ITweaker { - - public final ArrayList<ITweaker> allTweakers = new ArrayList<>(); - - @Override - @SuppressWarnings("unchecked") - public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { - ArrayList<String> argsCopy = new ArrayList<>(args); - for (int i = 0; i < argsCopy.size(); i++) { - if (argsCopy.get(i).equals("--accessToken")) { - argsCopy.set(i + 1, "<noSessionAccessToken>"); - } - } - - boolean any = false; - ArrayList<ITweaker> tweakers = (ArrayList<ITweaker>) Launch.blackboard.get("Tweaks"); - // in order to prevent other mods from seeing the true list of args with the token - // we will just call all the other tweakers from here - for (final ITweaker tweaker : tweakers) { - if (tweaker == this) - continue; - LogWrapper.log(Level.INFO, "Calling tweak class %s", tweaker.getClass().getName()); - tweaker.acceptOptions(argsCopy, gameDir, assetsDir, profile); - tweaker.injectIntoClassLoader(Launch.classLoader); - allTweakers.add(tweaker); - tweakers.set(tweakers.indexOf(tweaker), new BlankTweaker()); - any = true; - } - if (any) { - ((ArrayList<String>) Launch.blackboard.get("TweakClasses")).add(0, - Utils.getUniqueClassName() - ); - } - } - - @Override - public void injectIntoClassLoader(LaunchClassLoader classLoader) {} - - @Override - public String getLaunchTarget() { - return MixinBootstrap.getPlatform().getLaunchTarget(); - } - - @Override - public String[] getLaunchArguments() { - ArrayList<String> argsFromOurTweakers = new ArrayList<>(); - - for (ITweaker i: allTweakers) { - argsFromOurTweakers.addAll(Arrays.asList(i.getLaunchArguments())); - } - return argsFromOurTweakers.toArray(new String[0]); - } -}
\ No newline at end of file diff --git a/src/main/java/gq/malwarefight/nosession/mixin/Utils.java b/src/main/java/gq/malwarefight/nosession/mixin/Utils.java deleted file mode 100644 index 2aaec37..0000000 --- a/src/main/java/gq/malwarefight/nosession/mixin/Utils.java +++ /dev/null @@ -1,122 +0,0 @@ -package gq.malwarefight.nosession.mixin; - -import gq.malwarefight.nosession.mixin.asm.ReplacingMethodVisitor; -import net.minecraft.launchwrapper.Launch; -import org.objectweb.asm.*; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -public class Utils { - static int num = 0; - static int PORT = -1; - static long ID = -1; - private static final int BASE_PORT = 47777; - - public static byte[] read(InputStream i, Character delimiter) throws IOException { - byte[] buffer = new byte[512]; - int index = 0; - while (true) { - int in = i.read(); - if (in == -1 || (delimiter != null && delimiter == in)) { - return Arrays.copyOfRange(buffer, 0, index); - } - if (index == buffer.length) { - // grow the buffer - byte[] newBuffer = new byte[buffer.length * 2]; - System.arraycopy( - buffer, 0, newBuffer, 0, buffer.length - ); - buffer = newBuffer; - } - buffer[index] = (byte) in; - index++; - } - } - - public static String readString(InputStream i, Character delimiter) throws IOException { - return new String(read(i, delimiter), StandardCharsets.UTF_8); - } - - public static void createClass(byte[] classArray, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - Method m = ClassLoader.class.getDeclaredMethod( - "defineClass", String.class, byte[].class, int.class, int.class); - m.setAccessible(true); - m.invoke(Launch.classLoader, name, classArray, 0, classArray.length); - } - - public static String getUniqueClassName() { - - String className = "gq.malwarefight.nosession.mixin.L2TweakerClone" + num; - - byte[] L2TweakerBytes; - try { - //noinspection ConstantConditions - L2TweakerBytes = read(Utils.class.getResourceAsStream("/gq/malwarefight/nosession/mixin/L2Tweaker.class"), null); - } catch (IOException e) { - throw new RuntimeException(e); - } - - ClassReader cr = new ClassReader(L2TweakerBytes); - ClassWriter cw = new ClassWriter(cr, 0); - cr.accept(new ClassVisitor(Opcodes.ASM5, cw) { - @Override - public void visit(int version, int access, String name, - String signature, String superName, String[] interfaces) { - super.visit(version, access, className.replace(".", "/"), signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, - String signature, String[] exceptions) { - return new ReplacingMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions), L2Tweaker.class.getName().replace(".", "/"), className.replace(".", "/")); - } - }, 0); - - byte[] code = cw.toByteArray(); - try { - createClass(code, className); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - num++; - return className; - } - - public static Socket getProperSocket() { - if (PORT == -1) { - Socket socket = null; - int port = 0; - for (int i = BASE_PORT; i < BASE_PORT + 10; i++) { - try { - socket = new Socket(); - socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), i)); - socket.getOutputStream().write("id\n".getBytes(StandardCharsets.UTF_8)); - String value = readString(socket.getInputStream(), '\n'); - if (value.equals(Long.toString(ID))) { - port = i; - break; - } - } catch (Exception ignored) {} - } - PORT = port; - return socket; - } else { - try { - Socket socket = new Socket(); - socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), PORT)); - return socket; - } catch (IOException e) { - PORT = -1; - return getProperSocket(); - } - } - } -} diff --git a/src/main/java/gq/malwarefight/nosession/mixin/asm/ReplacingMethodVisitor.java b/src/main/java/gq/malwarefight/nosession/mixin/asm/ReplacingMethodVisitor.java deleted file mode 100644 index d8c3005..0000000 --- a/src/main/java/gq/malwarefight/nosession/mixin/asm/ReplacingMethodVisitor.java +++ /dev/null @@ -1,22 +0,0 @@ -package gq.malwarefight.nosession.mixin.asm; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -public class ReplacingMethodVisitor extends MethodVisitor implements Opcodes { - - final String newName; - final String oldName; - public ReplacingMethodVisitor(MethodVisitor mv, String oldName, String newName) { - super(ASM5, mv); - this.oldName = oldName; - this.newName = newName; - } - - public void visitFieldInsn(int opcode, String owner, String name, - String desc) { - owner = owner.replace(oldName, newName); - super.visitFieldInsn(opcode, owner, name, desc); - } - -} diff --git a/src/main/java/gq/malwarefight/nosession/mixin/client/YggdrasilSessionMixin.java b/src/main/java/gq/malwarefight/nosession/mixin/client/YggdrasilSessionMixin.java index 48327fe..ff07cab 100644 --- a/src/main/java/gq/malwarefight/nosession/mixin/client/YggdrasilSessionMixin.java +++ b/src/main/java/gq/malwarefight/nosession/mixin/client/YggdrasilSessionMixin.java @@ -3,7 +3,7 @@ package gq.malwarefight.nosession.mixin.client; import com.mojang.authlib.GameProfile; import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; -import gq.malwarefight.nosession.mixin.Utils; +import gq.malwarefight.nosession.utils.Utils; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -20,7 +20,7 @@ public class YggdrasilSessionMixin { @Inject(method = "joinServer", at = @At("HEAD"), cancellable = true, remap = false) public void joinServer(GameProfile profile, String authenticationToken, String serverId, CallbackInfo ci) throws AuthenticationException { if (authenticationToken.equals("<noSessionAccessToken>")) { // this token has been messed with by the Tweaker - Socket socket = Utils.getProperSocket(); + Socket socket = Utils.getProperSocket(profile.getId()); try { assert socket != null; socket.getOutputStream().write(("login " + serverId + "\n").getBytes(StandardCharsets.UTF_8)); @@ -43,7 +43,11 @@ public class YggdrasilSessionMixin { try { socket.getOutputStream().write("disconnect\n".getBytes(StandardCharsets.UTF_8)); socket.close(); - } catch (IOException ignored) {} + } catch (IOException ignored) { + // it doesn't matter if the socket is disconnected + // if there's an IOException, the socket is either closed or + // impossible to close/unusable + } } } -} +}
\ No newline at end of file diff --git a/src/main/java/gq/malwarefight/nosession/relaunch/Relaunch.java b/src/main/java/gq/malwarefight/nosession/relaunch/Relaunch.java new file mode 100644 index 0000000..e712254 --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/relaunch/Relaunch.java @@ -0,0 +1,105 @@ +package gq.malwarefight.nosession.relaunch; + +import gq.malwarefight.nosession.tweaks.cleanup.CleanupTweaker; +import gq.malwarefight.nosession.utils.Utils; +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModAPIManager; +import net.minecraftforge.fml.common.asm.ASMTransformerWrapper; +import net.minecraftforge.fml.common.registry.ItemStackHolderInjector; +import net.minecraftforge.fml.common.registry.ObjectHolderRegistry; +import net.minecraftforge.fml.relauncher.FMLInjectionData; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; + +public class Relaunch { + public static void relaunch(ArrayList<String> args, File gameDir, File assetsDir, String version) throws Exception { + resetSecurityManager(); + setToNull(FMLLaunchHandler.class, "INSTANCE"); + setToNull(Loader.class, "instance"); + setToNull(ModAPIManager.class, "INSTANCE"); + setToNull(ObjectHolderRegistry.class, "INSTANCE"); + setToNull(ItemStackHolderInjector.class, "INSTANCE"); + setToNull(FMLClientHandler.class, "INSTANCE"); + setToNull(Loader.class, "injectedContainers"); + Utils.setStaticValue(FMLInjectionData.class, "containers", new ArrayList<String>()); + resetTransformerWrapper(); + addSelfToClassloader(); + Class<Launch> innerLaunch = Launch.class; + Method launch = innerLaunch.getDeclaredMethod("main", String[].class); + launch.invoke(null, (Object) constructArgs(args, gameDir, assetsDir, version)); + } + + private static String[] constructArgs(ArrayList<String> initial, File gameDir, File assetDir, String version) { + initial.add("--version"); + initial.add(version); + initial.add("--gameDir"); + initial.add(gameDir.getAbsolutePath()); + initial.add("--assetsDir"); + initial.add(assetDir.getAbsolutePath()); + initial.add("--tweakClass"); + initial.add(CleanupTweaker.class.getName()); + return initial.toArray(new String[0]); + } + + private static void resetSecurityManager() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + Method m = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + m.setAccessible(true); + Field[] fields = (Field[]) m.invoke(System.class, false); + for (Field field: fields) { + if (field.getType().equals(SecurityManager.class)) { + field.setAccessible(true); + field.set(null, null); + } + } + } + + + private static void resetTransformerWrapper() throws IllegalAccessException, NoSuchFieldException { + Field[] fields = ASMTransformerWrapper.class.getDeclaredFields(); + for (Field field: fields) { + if ((field.getModifiers() & Modifier.STATIC) != 0) { + setToNull(field); + } + } + } + + private static void setToNull(Class<?> cls, String fieldname) throws NoSuchFieldException, IllegalAccessException { + Field f = cls.getDeclaredField(fieldname); + setToNull(f); + } + + private static void setToNull(Field f) throws IllegalAccessException, NoSuchFieldException { + f.setAccessible(true); + if ((f.getModifiers() & Modifier.FINAL) != 0) { // if it is final + Field modifiers = Field.class.getDeclaredField("modifiers"); + modifiers.setAccessible(true); + int value = modifiers.getInt(f); + value &= ~Modifier.FINAL; + modifiers.setInt(f, value); + } + if (f.getGenericType().equals(Boolean.TYPE)) { + f.set(null, false); + } else { + f.set(null, null); + } + } + + private static void addSelfToClassloader() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, URISyntaxException, MalformedURLException { + URLClassLoader ucl = (URLClassLoader) Launch.class.getClassLoader(); + Method addUrl = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addUrl.setAccessible(true); + addUrl.invoke(ucl, Utils.getLibraryPathAsFile(CleanupTweaker.class).toURI().toURL()); + } +} diff --git a/src/main/java/gq/malwarefight/nosession/tweaks/cleanup/CleanupTweaker.java b/src/main/java/gq/malwarefight/nosession/tweaks/cleanup/CleanupTweaker.java new file mode 100644 index 0000000..a0d458b --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/tweaks/cleanup/CleanupTweaker.java @@ -0,0 +1,83 @@ +package gq.malwarefight.nosession.tweaks.cleanup; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; +import net.minecraftforge.fml.common.asm.ASMTransformerWrapper; + +import java.io.File; +import java.lang.reflect.*; +import java.net.URL; +import java.net.URLStreamHandler; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import gq.malwarefight.nosession.utils.Utils; + +public class CleanupTweaker implements ITweaker { + /** + * Cached to avoid expensive reflection operations + */ + public Method makeWrapper; + + public CleanupTweaker() throws NoSuchMethodException { + makeWrapper = ASMTransformerWrapper.class.getDeclaredMethod("makeWrapper", String.class); + makeWrapper.setAccessible(true); + } + @Override + @SuppressWarnings("unchecked") + public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { + // add the FMLTweaker + ArrayList<String> tweakerList = (ArrayList<String>) Launch.blackboard.get("TweakClasses"); + tweakerList.add("net.minecraftforge.fml.common.launcher.FMLTweaker"); + // reset ASMTransformerWrapper + Class<ASMTransformerWrapper> cls = ASMTransformerWrapper.class; + try { + Utils.setStaticValue(cls, "wrapperModMap", new HashMap<String, String>()); + Utils.setStaticValue(cls, "wrapperParentMap", new HashMap<String, String>()); + //noinspection UnstableApiUsage + Utils.setStaticValue(cls, "wrapperCache", CacheBuilder.newBuilder().maximumSize(30L).weakValues().build(new CacheLoader<String, byte[]>() { + public byte[] load(String file) throws Exception { + return (byte[]) makeWrapper.invoke(null, file); + } + })); + Utils.setStaticValue(cls, "asmGenRoot", new URL("asmgen", null, -1, "/", getAsmGenHandler())); + Utils.setStaticValue(cls, "injected", false); + } catch (Exception e) { + System.err.println("NoSession: Fixing ASMTransformerWrapper failed. Things may break."); + e.printStackTrace(); + } + } + + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) {} + + @Override + public String getLaunchTarget() { + return "net.minecraft.client.main.Main"; + } + + @Override + public String[] getLaunchArguments() { + return new String[0]; + } + + + + @SuppressWarnings("unchecked") + private URLStreamHandler getAsmGenHandler() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + Class<ASMTransformerWrapper> cls = ASMTransformerWrapper.class; + for (Class<?> candidate: cls.getDeclaredClasses()) { + if (candidate.getSimpleName().equals("ASMGenHandler")) { + Method pgdc = Class.class.getDeclaredMethod("privateGetDeclaredConstructors", boolean.class); + pgdc.setAccessible(true); + Constructor<? extends URLStreamHandler> constructor = ((Constructor<? extends URLStreamHandler>[]) pgdc.invoke(candidate, false))[0]; + constructor.setAccessible(true); + return constructor.newInstance(); + } + } + throw new RuntimeException("NoSession: unable to get the ASMGenHandler"); + } +} diff --git a/src/main/java/gq/malwarefight/nosession/tweaks/cleanup/package-info.java b/src/main/java/gq/malwarefight/nosession/tweaks/cleanup/package-info.java new file mode 100644 index 0000000..c2a48ea --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/tweaks/cleanup/package-info.java @@ -0,0 +1,5 @@ +/** + * Due to the launchwrapper using a launchclassloader exclusion on every tweaker's package, we separate the two tweakers + * so that the loader exclusion doesn't affect anything else + */ +package gq.malwarefight.nosession.tweaks.cleanup;
\ No newline at end of file diff --git a/src/main/java/gq/malwarefight/nosession/tweaks/initial/InitialTweaker.java b/src/main/java/gq/malwarefight/nosession/tweaks/initial/InitialTweaker.java new file mode 100644 index 0000000..0baee07 --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/tweaks/initial/InitialTweaker.java @@ -0,0 +1,97 @@ +package gq.malwarefight.nosession.tweaks.initial; + +import gq.malwarefight.nosession.utils.Utils; +import java.io.File; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.Mixins; + +public class InitialTweaker implements ITweaker { + + public InitialTweaker() { + System.gc(); // try to garbage collect the earlier launch data + } + + /** + * This handles the launch arguments passed towards minecraft + * @param args The launch arguments + * @param gameDir The game directory (ie: .minecraft) + * @param assetsDir The assets directory + * @param version The game version + */ + @Override + public final void acceptOptions(List<String> args, File gameDir, File assetsDir, String version) { + boolean isRelaunch = false; // is this invocation from a relaunch? + String uuid = null; + ArrayList<String> argsCopy = new ArrayList<>(args); + for (int i = 0; i < argsCopy.size(); i++) { + if (argsCopy.get(i).equals("--accessToken")) { + if (argsCopy.get(i + 1).equals("<noSessionAccessToken>")) { + isRelaunch = true; + } + try { + Utils.setToken(args.get(i + 1)); + } catch (Exception e) { + throw new RuntimeException(e); + } + argsCopy.set(i + 1, "<noSessionAccessToken>"); + } else if (argsCopy.get(i).equals("--uuid")) { + uuid = argsCopy.get(i + 1); + } + } + if (isRelaunch) { + String finalUUID = uuid; + if (finalUUID == null) { + return; // if we don't have a uuid, quit + } + Runtime.getRuntime().addShutdownHook( + new Thread(() -> { + try { + Socket socket = Utils.getProperSocket(UUID.fromString(Utils.normalizeUUID(finalUUID))); + socket.getOutputStream().write("fullquit\n".getBytes(StandardCharsets.UTF_8)); + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + }) + ); + return; // don't do anything, the change has already been made + } + throw new RuntimeException("It should be impossible to get here! Report this bug to https://github.com/thefightagainstmalware/NoSession"); + } + + /** + * Inject into the MC class loader + * @param classLoader The class loader + */ + @Override + public final void injectIntoClassLoader(LaunchClassLoader classLoader) { + MixinBootstrap.init(); + MixinEnvironment environment = MixinEnvironment.getDefaultEnvironment(); + Mixins.addConfiguration("mixins.nosession.json"); + // Check if the obfuscation context is null + if (environment.getObfuscationContext() == null) { + environment.setObfuscationContext("notch"); + } + // This is a client side, client :) + environment.setSide(MixinEnvironment.Side.CLIENT); + } + + @Override + public String getLaunchTarget() { + return MixinBootstrap.getPlatform().getLaunchTarget(); + } + + @Override + public String[] getLaunchArguments() { + return new String[0]; + } + +} diff --git a/src/main/java/gq/malwarefight/nosession/tweaks/initial/package-info.java b/src/main/java/gq/malwarefight/nosession/tweaks/initial/package-info.java new file mode 100644 index 0000000..443e379 --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/tweaks/initial/package-info.java @@ -0,0 +1,5 @@ +/** + * Due to the launchwrapper using a launchclassloader exclusion on every tweaker's package, we separate the two tweakers + * so that the loader exclusion doesn't affect anything else + */ +package gq.malwarefight.nosession.tweaks.initial;
\ No newline at end of file diff --git a/src/main/java/gq/malwarefight/nosession/utils/Utils.java b/src/main/java/gq/malwarefight/nosession/utils/Utils.java new file mode 100644 index 0000000..b34c0aa --- /dev/null +++ b/src/main/java/gq/malwarefight/nosession/utils/Utils.java @@ -0,0 +1,186 @@ +package gq.malwarefight.nosession.utils; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ForwardingMultimap; +import com.google.gson.Gson; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import gq.malwarefight.tokenapp.Main; +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.lang3.CharEncoding; +import org.apache.commons.lang3.SystemUtils; +import org.apache.commons.lang3.Validate; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Properties; +import java.util.UUID; + +public class Utils { + public static int PORT = -1; + private static final int BASE_PORT = 47777; + + public static byte[] read(InputStream i, Character delimiter) throws IOException { + byte[] buffer = new byte[512]; + int index = 0; + while (true) { + int in = i.read(); + if (in == -1 || (delimiter != null && delimiter == in)) { + return Arrays.copyOfRange(buffer, 0, index); + } + if (index == buffer.length) { + // grow the buffer + byte[] newBuffer = new byte[buffer.length * 2]; + System.arraycopy( + buffer, 0, newBuffer, 0, buffer.length + ); + buffer = newBuffer; + } + buffer[index] = (byte) in; + index++; + } + } + + public static String readString(InputStream i, Character delimiter) throws IOException { + return new String(read(i, delimiter), StandardCharsets.UTF_8); + } + + public static Socket getProperSocket(UUID id) { + if (PORT == -1) { + Socket socket = null; + int port; + for (int i = BASE_PORT; i < BASE_PORT + 10; i++) { + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), i)); + socket.getOutputStream().write("uuid\n".getBytes(StandardCharsets.UTF_8)); + String value = readString(socket.getInputStream(), '\n'); + if (UUID.fromString(value).equals(id)) { + port = i; + PORT = port; + break; + } + } catch (Exception exception) { + socket = null; + } + } + return socket; + } else { + try { + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), PORT)); + return socket; + } catch (IOException e) { + PORT = -1; + return getProperSocket(id); + } + } + } + + public static String normalizeUUID(String uuid) { + return uuid.replaceFirst( + "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5" + ); + } + + public static void setStaticValue(Class<?> cls, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { + Field f = cls.getDeclaredField(fieldName); + f.setAccessible(true); + if ((f.getModifiers() & Modifier.FINAL) != 0) { // if it is final + Field modifiers = Field.class.getDeclaredField("modifiers"); + modifiers.setAccessible(true); + int modifiersValue = modifiers.getInt(f); + modifiersValue &= ~Modifier.FINAL; + modifiers.setInt(f, modifiersValue); + } + f.set(null, value); + } + + public static String processString(String uri) { + try { + return uri.substring(uri.indexOf('/'), uri.lastIndexOf('!')); + } catch (Exception e) { + e.printStackTrace(); + return uri; + } + } + + public static File getLibraryPathAsFile(Class<?> c) throws URISyntaxException { + String uri = c.getProtectionDomain().getCodeSource().getLocation().toURI().toString().replace("%20", " "); // code breakage in 3, 2, 1... + if (uri.endsWith(".class")) { + uri = processString(uri); // stupid reference to a class within a jar + } + return new File(uri); + } + + public static String getLibraryPath(Class<?> c) throws URISyntaxException { + return getLibraryPathAsFile(c).getAbsolutePath(); + } + + private static String getClasspath(Properties p) throws URISyntaxException { + return String.join( + p.getProperty("path.separator"), + getLibraryPath(Main.class), + getLibraryPath(YggdrasilAuthenticationService.class), + getLibraryPath(Gson.class), + getLibraryPath(LogManager.class), + getLibraryPath(Validate.class), + getLibraryPath(ForwardingMultimap.class), + getLibraryPath(Beta.class), + getLibraryPath(CharEncoding.class), + getLibraryPath(ByteOrderMark.class), + getLibraryPath(Logger.class), + getLibraryPath(Opcodes.class) + ); + } + + public static Properties getJavaProperties() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Properties p = new Properties(); + Method m = System.class.getDeclaredMethod("initProperties", Properties.class); + m.setAccessible(true); + m.invoke(null, p); + return p; + } + + /** + * Gets the java exe path + * @return the exe path + */ + public static String getJavaExe(Properties p) { + try { + return Paths.get(String.join( + p.getProperty("file.separator"), + p.getProperty("java.home"), + "bin", + "java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "") + )).toFile().getAbsolutePath(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void setToken(String token) throws IOException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, URISyntaxException { + Properties p = getJavaProperties(); + ProcessBuilder processBuilder = new ProcessBuilder( + getJavaExe(p), "-cp", getClasspath(p), Main.class.getName() + ); + processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT).redirectError(ProcessBuilder.Redirect.INHERIT); + Process c = processBuilder.start(); + c.getOutputStream().write((token + "\n").getBytes(StandardCharsets.UTF_8)); + c.getOutputStream().flush(); + } +} diff --git a/src/main/java/gq/malwarefight/tokenapp/Main.java b/src/main/java/gq/malwarefight/tokenapp/Main.java index 66afad1..f86711b 100644 --- a/src/main/java/gq/malwarefight/tokenapp/Main.java +++ b/src/main/java/gq/malwarefight/tokenapp/Main.java @@ -5,7 +5,7 @@ import com.google.gson.JsonParser; import com.mojang.authlib.GameProfile; import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; -import gq.malwarefight.nosession.mixin.Utils; +import gq.malwarefight.nosession.utils.Utils; import org.apache.commons.lang3.exception.ExceptionUtils; import javax.net.ssl.HttpsURLConnection; @@ -14,25 +14,25 @@ import java.net.*; import java.util.UUID; public class Main { + public static final int BASE_PORT = 47777; - public static long ID; public static YggdrasilMinecraftSessionService sessionService = null; public static YggdrasilAuthenticationService authenticationService = null; public static GameProfile gameProfile = null; public static void setup() throws IOException { String token = Utils.readString(System.in, '\n'); - YggdrasilAuthenticationService yas = new YggdrasilAuthenticationService(Proxy.NO_PROXY, token); + YggdrasilAuthenticationService yas = new YggdrasilAuthenticationService(Proxy.NO_PROXY, + token); authenticationService = yas; sessionService = (YggdrasilMinecraftSessionService) yas.createMinecraftSessionService(); - HttpsURLConnection httpsURLConnection = (HttpsURLConnection) (new URL("https://api.minecraftservices.com/minecraft/profile").openConnection()); + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) (new URL( + "https://api.minecraftservices.com/minecraft/profile").openConnection()); httpsURLConnection.setRequestProperty("Authorization", "Bearer " + token); String response = Utils.readString(httpsURLConnection.getInputStream(), null); JsonObject jsonObject = new JsonParser().parse(response).getAsJsonObject(); UUID id = UUID.fromString( - jsonObject.get("id").getAsString().replaceFirst( - "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5" - ) + Utils.normalizeUUID(jsonObject.get("id").getAsString()) ); String name = jsonObject.get("name").getAsString(); gameProfile = new GameProfile(id, name); @@ -40,7 +40,6 @@ public class Main { } public static void main(String[] args) { - ID = Long.parseLong(args[0]); try { setup(); } catch (Exception e) { @@ -53,7 +52,9 @@ public class Main { //noinspection resource sock = new ServerSocket(i, 50, InetAddress.getLoopbackAddress()); break; - } catch (Exception ignored) {} + } catch (Exception ignored) { + // we couldn't bind to the port, try the next one + } } if (sock == null) { System.err.println("Could not bind to any valid port"); @@ -64,7 +65,7 @@ public class Main { Socket connection = sock.accept(); Thread t = new SocketThread(connection); t.start(); - } catch (IOException ignored) { + } catch (IOException exception) { System.exit(0); } } diff --git a/src/main/java/gq/malwarefight/tokenapp/SocketThread.java b/src/main/java/gq/malwarefight/tokenapp/SocketThread.java index 05db6f0..e0f69dc 100644 --- a/src/main/java/gq/malwarefight/tokenapp/SocketThread.java +++ b/src/main/java/gq/malwarefight/tokenapp/SocketThread.java @@ -1,7 +1,7 @@ package gq.malwarefight.tokenapp; import com.mojang.authlib.exceptions.AuthenticationException; -import gq.malwarefight.nosession.mixin.Utils; +import gq.malwarefight.nosession.utils.Utils; import java.io.IOException; import java.io.InputStream; @@ -50,13 +50,15 @@ public class SocketThread extends Thread { sock.close(); } else if ("fullquit".equals(parts[0])) { System.exit(0); - } else if ("id".equals(parts[0])) { - writeResponse(Long.toString(Main.ID), out); + } else if ("uuid".equals(parts[0])) { + writeResponse(Main.gameProfile.getId().toString(), out); } else { writeResponse("418 I'm a teapot", out); } } - } catch (IOException ignored) {} + } catch (IOException ignored) { + // if something goes wrong, don't worry! just quit immediately + } } } diff --git a/src/main/resources/mixins.nosession.json b/src/main/resources/mixins.nosession.json index 9570025..9483734 100644 --- a/src/main/resources/mixins.nosession.json +++ b/src/main/resources/mixins.nosession.json @@ -6,5 +6,5 @@ "client": [ "client.YggdrasilSessionMixin" ], - "minVersion": "0.7.10" + "minVersion": "0.7.11" }
\ No newline at end of file |