From bc3c7ba171b2d284975346e0d88491f4c9bdb8b4 Mon Sep 17 00:00:00 2001 From: Gravita Date: Mon, 13 Jun 2022 19:37:40 +0700 Subject: [PATCH] [FEATURE] InstallAuthlib command --- .../gravit/launcher/server/ServerWrapper.java | 16 +- .../server/authlib/InstallAuthlib.java | 251 ++++++++++++++++++ .../authlib/LibrariesHashFileModifier.java | 8 + .../server/authlib/LibrariesLstModifier.java | 22 ++ .../authlib/PatchPropertiesModifier.java | 55 ++++ 5 files changed, 344 insertions(+), 8 deletions(-) create mode 100644 ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/InstallAuthlib.java create mode 100644 ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesHashFileModifier.java create mode 100644 ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesLstModifier.java create mode 100644 ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/PatchPropertiesModifier.java diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java index 97eddf74..45c9aeeb 100644 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java @@ -6,8 +6,6 @@ import pro.gravit.launcher.config.JsonConfigurable; import pro.gravit.launcher.events.request.AuthRequestEvent; import pro.gravit.launcher.events.request.ProfilesRequestEvent; -import pro.gravit.launcher.modules.events.PostInitPhase; -import pro.gravit.launcher.modules.events.PreConfigPhase; import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launcher.profiles.PlayerProfile; import pro.gravit.launcher.profiles.optional.actions.OptionalAction; @@ -15,9 +13,9 @@ import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest; -import pro.gravit.launcher.request.auth.RestoreRequest; import pro.gravit.launcher.request.update.ProfilesRequest; import pro.gravit.launcher.request.websockets.StdWebSocketService; +import pro.gravit.launcher.server.authlib.InstallAuthlib; import pro.gravit.launcher.server.launch.ClasspathLaunch; import pro.gravit.launcher.server.launch.Launch; import pro.gravit.launcher.server.launch.ModuleLaunch; @@ -27,12 +25,7 @@ import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.LogHelper; -import java.io.IOException; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Type; -import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @@ -112,6 +105,13 @@ public void run(String... args) throws Throwable { setup.run(); System.exit(0); } + if (args.length > 1 && args[0].equals("installAuthlib") && !disableSetup) { + LogHelper.debug("Read ServerWrapperConfig.json"); + loadConfig(); + InstallAuthlib command = new InstallAuthlib(); + command. run(args[1]); + System.exit(0); + } LogHelper.debug("Read ServerWrapperConfig.json"); loadConfig(); updateLauncherConfig(); diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/InstallAuthlib.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/InstallAuthlib.java new file mode 100644 index 00000000..32f4c470 --- /dev/null +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/InstallAuthlib.java @@ -0,0 +1,251 @@ +package pro.gravit.launcher.server.authlib; + +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.LogHelper; + +import java.io.*; +import java.net.URL; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.LocalDateTime; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class InstallAuthlib { + private static Map modifierMap; + static { + modifierMap = new HashMap<>(); + modifierMap.put("META-INF/libraries.list", new LibrariesLstModifier()); + modifierMap.put("patch.properties", new PatchPropertiesModifier()); + } + public void run(String... args) throws Exception { + boolean deleteAuthlibAfterInstall = false; + InstallAuthlibContext context = new InstallAuthlibContext(); + if(args[0].startsWith("http://") || args[0].startsWith("https://")) { + Path tempAuthlib = Paths.get("authlib.jar"); + LogHelper.info("Download %s to %s", args[0], tempAuthlib); + try(InputStream input = IOHelper.newInput(new URL(args[0]))) { + IOHelper.transfer(input, tempAuthlib); + } + context.pathToAuthlib = tempAuthlib; + deleteAuthlibAfterInstall = true; + } else { + context.pathToAuthlib = Paths.get(args[0]); + } + if(Files.notExists(context.pathToAuthlib)) { + throw new FileNotFoundException(context.pathToAuthlib.toString()); + } + context.workdir = IOHelper.WORKING_DIR; + LogHelper.info("Search .jar files in %s", context.workdir.toAbsolutePath()); + IOHelper.walk(context.workdir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if(file.getFileName().toString().endsWith(".jar")) { + context.files.add(file); + } + return FileVisitResult.CONTINUE; + } + }, true); + LogHelper.info("Search authlib in %d files", context.files.size()); + for(Path path : context.files) { + boolean foundAuthlib = false; + try(ZipInputStream input = IOHelper.newZipInput(path)) { + ZipEntry e = input.getNextEntry(); + while(e != null) { + String name = e.getName(); + if(!e.isDirectory() && name.contains("com/mojang/authlib") && !foundAuthlib) { + boolean isJarFile = name.endsWith(".jar"); + String prefix = isJarFile ? name : name.substring(0, name.indexOf("com/mojang/authlib")); + context.repack.add(new RepackInfo(path, prefix, isJarFile)); + foundAuthlib = true; + } + if(!e.isDirectory() && modifierMap.containsKey(name)) { + context.hashes.add(new HashFile(path, name, modifierMap.get(name))); + } + e = input.getNextEntry(); + } + } + } + Path tmpFile = Paths.get("repack.tmp"); + for(RepackInfo ri : context.repack) { + LogHelper.info("Found authlib in %s (prefix '%s' jar %s)", ri.path, ri.prefix, ri.isJarFile ? "true" : "false"); + try(ZipInputStream input = IOHelper.newZipInput(ri.path)) { + try(ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(tmpFile))) { + ZipEntry e; + e = input.getNextEntry(); + while(e != null) { + if(!e.getName().equals("META-INF") && !e.getName().equals("META-INF/") && !e.getName().equals("META-INF/MANIFEST.MF")) { + break; + } + ZipEntry newEntry = IOHelper.newZipEntry(e); + output.putNextEntry(newEntry); + IOHelper.transfer(input, output); + e = input.getNextEntry(); + } + if(!ri.isJarFile) { + try(ZipInputStream input2 = new ZipInputStream(IOHelper.newInput(context.pathToAuthlib))) { + ZipEntry e2 = input2.getNextEntry(); + while(e2 != null) { + if(e2.getName().startsWith("META-INF")) { + e2 = input2.getNextEntry(); + continue; + } + String newName = !ri.prefix.endsWith("/") && !e2.getName().startsWith("/") && !ri.prefix.isEmpty() ? + ri.prefix.concat("/").concat(e2.getName()) : ri.prefix.concat(e2.getName()); + ZipEntry newEntry = IOHelper.newZipEntry(newName); + output.putNextEntry(newEntry); + IOHelper.transfer(input2, output); + e2 = input2.getNextEntry(); + } + } + } + while(e != null) { + if(e.getName().startsWith(ri.prefix)) { + if(ri.isJarFile) { + if(context.repackedAuthlibBytes == null) { + byte[] orig = IOHelper.read(input); + context.repackedAuthlibBytes = repackAuthlibJar(orig, context.pathToAuthlib); + } + ZipEntry newEntry = IOHelper.newZipEntry(e); + output.putNextEntry(newEntry); + output.write(context.repackedAuthlibBytes); + e = input.getNextEntry(); + continue; + } else { + if(context.repackedAuthlibFiles == null) { + context.repackedAuthlibFiles = getNames(context.pathToAuthlib); + } + if(context.repackedAuthlibFiles.contains(e.getName().substring(ri.prefix.length()))) { + e = input.getNextEntry(); + continue; + } + } + } + ZipEntry newEntry = IOHelper.newZipEntry(e); + output.putNextEntry(newEntry); + IOHelper.transfer(input, output); + e = input.getNextEntry(); + } + } + } + Files.delete(ri.path); + Files.move(tmpFile, ri.path); + } + LogHelper.info("%d authlib files repacked", context.repack.size()); + for(HashFile hf : context.hashes) { + LogHelper.info("Found hash file %s in %s", hf.prefix, hf.path); + try(ZipInputStream input = IOHelper.newZipInput(hf.path)) { + try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(tmpFile))) { + ZipEntry e = input.getNextEntry(); + while(e != null) { + ZipEntry newEntry = IOHelper.newZipEntry(e); + output.putNextEntry(newEntry); + if(e.getName().equals(hf.prefix)) { + byte[] orig = IOHelper.read(input); + byte[] bytes = hf.modifier.apply(orig, context); + output.write(bytes); + } else { + IOHelper.transfer(input, output); + } + e = input.getNextEntry(); + } + } + } + Files.delete(hf.path); + Files.move(tmpFile, hf.path); + } + LogHelper.info("%d hash files repacked", context.hashes.size()); + if(deleteAuthlibAfterInstall) { + LogHelper.info("Delete %s", context.pathToAuthlib); + Files.delete(context.pathToAuthlib); + } + LogHelper.info("Completed"); + } + + private Set getNames(Path path) throws IOException { + Set set = new HashSet<>(); + try(ZipInputStream input = IOHelper.newZipInput(path)) { + ZipEntry e = input.getNextEntry(); + while(e != null) { + if(!e.getName().startsWith("META-INF")) { + set.add(e.getName()); + } + e = input.getNextEntry(); + } + } + return set; + } + + private byte[] repackAuthlibJar(byte[] data, Path path) throws IOException { + try(ZipInputStream input = new ZipInputStream(new ByteArrayInputStream(data))) { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try(ZipOutputStream output = new ZipOutputStream(result)) { + Set blacklist = new HashSet<>(); + try(ZipInputStream input2 = IOHelper.newZipInput(path)) { + ZipEntry e = input2.getNextEntry(); + while(e != null) { + if(e.getName().startsWith("META-INF")) { + e = input2.getNextEntry(); + continue; + } + ZipEntry newEntry = IOHelper.newZipEntry(e); + output.putNextEntry(newEntry); + IOHelper.transfer(input2, output); + blacklist.add(e.getName()); + e = input2.getNextEntry(); + } + } + ZipEntry e = input.getNextEntry(); + while(e != null) { + if(blacklist.contains(e.getName())) { + e = input.getNextEntry(); + continue; + } + ZipEntry newEntry = IOHelper.newZipEntry(e); + output.putNextEntry(newEntry); + IOHelper.transfer(input, output); + e = input.getNextEntry(); + } + } + return result.toByteArray(); + } + } + + public static class RepackInfo { + public Path path; + public String prefix; + public boolean isJarFile; + + public RepackInfo(Path path, String prefix, boolean isJarFile) { + this.path = path; + this.prefix = prefix; + this.isJarFile = isJarFile; + } + } + + public static class HashFile { + public Path path; + public String prefix; + public LibrariesHashFileModifier modifier; + + public HashFile(Path path, String prefix, LibrariesHashFileModifier modifier) { + this.path = path; + this.prefix = prefix; + this.modifier = modifier; + } + } + + public static class InstallAuthlibContext { + public Path pathToAuthlib; + public Path workdir = IOHelper.WORKING_DIR; + public List files = new ArrayList<>(); + public List repack = new ArrayList<>(); + public List hashes = new ArrayList<>(); + public byte[] repackedAuthlibBytes = null; + public Set repackedAuthlibFiles = null; + + public LocalDateTime timestamp = LocalDateTime.now(); + } +} diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesHashFileModifier.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesHashFileModifier.java new file mode 100644 index 00000000..4d842081 --- /dev/null +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesHashFileModifier.java @@ -0,0 +1,8 @@ +package pro.gravit.launcher.server.authlib; + +import java.io.IOException; + +@FunctionalInterface +public interface LibrariesHashFileModifier { + byte[] apply(byte[] data, InstallAuthlib.InstallAuthlibContext context) throws IOException; +} diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesLstModifier.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesLstModifier.java new file mode 100644 index 00000000..b65f375c --- /dev/null +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/authlib/LibrariesLstModifier.java @@ -0,0 +1,22 @@ +package pro.gravit.launcher.server.authlib; + +import pro.gravit.utils.helper.SecurityHelper; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class LibrariesLstModifier implements LibrariesHashFileModifier { + + @Override + public byte[] apply(byte[] data, InstallAuthlib.InstallAuthlibContext context) throws IOException { + String[] lines = new String(data).split("\n"); + for(int i=0;i