diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java index d3967ea3..251d9374 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java @@ -18,7 +18,6 @@ import pro.gravit.launchserver.manangers.ReconfigurableManager; import pro.gravit.launchserver.manangers.SessionManager; import pro.gravit.launchserver.manangers.hook.AuthHookManager; -import pro.gravit.launchserver.manangers.hook.BuildHookManager; import pro.gravit.launchserver.modules.events.LaunchServerFullInitEvent; import pro.gravit.launchserver.modules.events.LaunchServerInitPhase; import pro.gravit.launchserver.modules.events.LaunchServerPostInitPhase; @@ -226,9 +225,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO public final CertificateManager certificateManager; - - public final BuildHookManager buildHookManager; - public final ProguardConf proguardConf; @@ -310,7 +306,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La } // build hooks, anti-brutforce and other - buildHookManager = new BuildHookManager(); proguardConf = new ProguardConf(this); sessionManager = new SessionManager(); mirrorManager = new MirrorManager(); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/BuildContext.java b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/BuildContext.java index 3f8f77a2..2adc9a08 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/BuildContext.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/BuildContext.java @@ -1,28 +1,91 @@ package pro.gravit.launchserver.binary; +import pro.gravit.launcher.Launcher; +import pro.gravit.launcher.serialize.HOutput; +import pro.gravit.launcher.serialize.stream.StreamObject; import pro.gravit.launchserver.binary.tasks.MainBuildTask; import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.JarHelper; +import pro.gravit.utils.helper.LogHelper; +import pro.gravit.utils.helper.SecurityHelper; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Type; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Predicate; +import java.util.jar.JarFile; import java.util.zip.ZipEntry; +import java.util.zip.ZipException; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import static pro.gravit.utils.helper.IOHelper.UNICODE_CHARSET; +import static pro.gravit.utils.helper.IOHelper.newZipEntry; + public class BuildContext { public final ZipOutputStream output; public final LauncherConfigurator config; - public final MainBuildTask data; + public final List readerClassPath; + public final MainBuildTask task; public final HashSet fileList; + public final HashSet clientModules; + + private final static class RuntimeDirVisitor extends SimpleFileVisitor { + private final ZipOutputStream output; + private final Map hashs; + private final Path sourceDir; + private final String targetDir; + + private RuntimeDirVisitor(ZipOutputStream output, Map hashs, Path sourceDir, String targetDir) { + this.output = output; + this.hashs = hashs; + this.sourceDir = sourceDir; + this.targetDir = targetDir; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + String dirName = IOHelper.toString(sourceDir.relativize(dir)); + output.putNextEntry(newEntry(dirName + '/')); + return super.preVisitDirectory(dir, attrs); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String fileName = IOHelper.toString(sourceDir.relativize(file)); + if(hashs != null) + hashs.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file)); + + // Create zip entry and transfer contents + output.putNextEntry(newEntry(fileName)); + IOHelper.transfer(file, output); + + // Return result + return super.visitFile(file, attrs); + } + + private ZipEntry newEntry(String fileName) { + return newZipEntry( targetDir + IOHelper.CROSS_SEPARATOR + fileName); + } + } - public BuildContext(ZipOutputStream output, LauncherConfigurator config, MainBuildTask data) { + public BuildContext(ZipOutputStream output, LauncherConfigurator config, List readerClassPath, MainBuildTask task) { this.output = output; this.config = config; - this.data = data; + this.readerClassPath = readerClassPath; + this.task = task; fileList = new HashSet<>(1024); + clientModules = new HashSet<>(); } public void pushFile(String filename, InputStream inputStream) throws IOException { @@ -32,6 +95,26 @@ public void pushFile(String filename, InputStream inputStream) throws IOExceptio output.closeEntry(); fileList.add(filename); } + + public void pushFile(String filename, StreamObject object) throws IOException + { + ZipEntry zip = IOHelper.newZipEntry(filename); + output.putNextEntry(zip); + object.write(new HOutput(output)); + output.closeEntry(); + fileList.add(filename); + } + public void pushFile(String filename, Object object, Type type) throws IOException + { + String bytes = Launcher.gsonManager.gson.toJson(object, type); + pushBytes(filename, bytes.getBytes(UNICODE_CHARSET)); + } + + public void pushDir(Path dir, String targetDir, Map hashMap, boolean hidden) throws IOException + { + IOHelper.walk(dir, new RuntimeDirVisitor(output, hashMap, dir, targetDir), hidden); + } + public void pushBytes(String filename, byte[] bytes) throws IOException { ZipEntry zip = IOHelper.newZipEntry(filename); output.putNextEntry(zip); @@ -39,7 +122,7 @@ public void pushBytes(String filename, byte[] bytes) throws IOException { output.closeEntry(); fileList.add(filename); } - + @Deprecated public void pushJarFile(ZipInputStream input) throws IOException { ZipEntry e = input.getNextEntry(); while (e != null) { @@ -53,7 +136,7 @@ public void pushJarFile(ZipInputStream input) throws IOException { e = input.getNextEntry(); } } - + @Deprecated public void pushJarFile(ZipInputStream input, Set blacklist) throws IOException { ZipEntry e = input.getNextEntry(); while (e != null) { @@ -67,4 +150,47 @@ public void pushJarFile(ZipInputStream input, Set blacklist) throws IOEx e = input.getNextEntry(); } } + public void pushJarFile(Path jarfile, Predicate filter, Predicate needTransform) throws IOException + { + pushJarFile(jarfile.toUri().toURL(), filter, needTransform); + } + public void pushJarFile(URL jarfile, Predicate filter, Predicate needTransform) throws IOException + { + try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) { + ZipEntry e = input.getNextEntry(); + while(e != null) + { + String filename = e.getName(); + if(e.isDirectory() || fileList.contains(filename) || filter.test(e)) + { + e = input.getNextEntry(); + continue; + } + try { + output.putNextEntry(IOHelper.newZipEntry(e)); + } catch (ZipException ex) { + LogHelper.warning("Write %s failed: %s", filename, ex.getMessage() == null ? "null" : ex.getMessage()); + e = input.getNextEntry(); + continue; + } + if (filename.endsWith(".class")) { + String classname = filename.replace('/', '.').substring(0, + filename.length() - ".class".length()); + if(!needTransform.test(classname)) + { + IOHelper.transfer(input, output); + } + else + { + byte[] bytes = IOHelper.read(input); + bytes = task.transformClass(bytes, classname, this); + output.write(bytes); + } + } else + IOHelper.transfer(input, output); + fileList.add(filename); + e = input.getNextEntry(); + } + } + } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java index e06540f3..dbb9ec3c 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java @@ -9,23 +9,20 @@ import pro.gravit.launcher.Launcher; import pro.gravit.launcher.LauncherConfig; import pro.gravit.launcher.SecureAutogenConfig; -import pro.gravit.launcher.serialize.HOutput; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.asm.ClassMetadataReader; import pro.gravit.launchserver.asm.ConfigGenerator; import pro.gravit.launchserver.binary.BuildContext; import pro.gravit.launchserver.binary.LauncherConfigurator; +import pro.gravit.utils.HookException; +import pro.gravit.utils.HookSet; import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.JarHelper; import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.SecurityHelper; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.file.FileVisitResult; import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; import java.security.cert.CertificateEncodingException; import java.util.*; import java.util.jar.JarFile; @@ -44,6 +41,38 @@ public class MainBuildTask implements LauncherBuildTask { public interface Transformer { byte[] transform(byte[] input, String classname, BuildContext context); } + public static class IOHookSet { + public final Set> list = new HashSet<>(); + + @FunctionalInterface + public interface IOHook { + /** + * @param context custom param + * False to continue processing hook + * @throws HookException The hook may return the error text throwing this exception + */ + void hook(R context) throws HookException, IOException; + } + + public void registerHook(IOHook hook) { + list.add(hook); + } + + public boolean unregisterHook(IOHook hook) { + return list.remove(hook); + } + + /** + * @param context custom param + * False to continue + * @throws HookException The hook may return the error text throwing this exception + */ + public void hook(R context) throws HookException, IOException { + for (IOHook hook : list) { + hook.hook(context); + } + } + } public interface ASMTransformer extends Transformer { default byte[] transform(byte[] input, String classname, BuildContext context) @@ -89,46 +118,10 @@ public void transform(ClassNode cn, String classname, BuildContext context) { } abstract public void transformField(AnnotationNode an, FieldNode fn, ClassNode cn, String classname, BuildContext context); } - - private final static class RuntimeDirVisitor extends SimpleFileVisitor { - private final ZipOutputStream output; - private final Map hashs; - private final Path sourceDir; - private final String targetDir; - - private RuntimeDirVisitor(ZipOutputStream output, Map hashs, Path sourceDir, String targetDir) { - this.output = output; - this.hashs = hashs; - this.sourceDir = sourceDir; - this.targetDir = targetDir; - } - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - String dirName = IOHelper.toString(sourceDir.relativize(dir)); - output.putNextEntry(newEntry(dirName + '/')); - return super.preVisitDirectory(dir, attrs); - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - String fileName = IOHelper.toString(sourceDir.relativize(file)); - hashs.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file)); - - // Create zip entry and transfer contents - output.putNextEntry(newEntry(fileName)); - IOHelper.transfer(file, output); - - // Return result - return super.visitFile(file, attrs); - } - - private ZipEntry newEntry(String fileName) { - return newZipEntry( targetDir + IOHelper.CROSS_SEPARATOR + fileName); - } - } public Set blacklist = new HashSet<>(); public List transformers = new ArrayList<>(); + public IOHookSet preBuildHook = new IOHookSet<>(); + public IOHookSet postBuildHook = new IOHookSet<>(); public MainBuildTask(LaunchServer srv) { server = srv; @@ -150,8 +143,8 @@ public Path process(Path inputJar) throws IOException { ClassNode cn1 = new ClassNode(); new ClassReader(JarHelper.getClassBytes(SecureAutogenConfig.class)).accept(cn1, 0); ConfigGenerator secureConfigurator = new ConfigGenerator(cn1); - BuildContext context = new BuildContext(output, launcherConfigurator, this); - server.buildHookManager.hook(context); + BuildContext context = new BuildContext(output, launcherConfigurator, reader.getCp(), this); + preBuildHook.hook(context); launcherConfigurator.setStringField("address", server.config.netty.address); launcherConfigurator.setStringField("projectname", server.config.projectName); launcherConfigurator.setStringField("secretKeyClient", SecurityHelper.randomStringAESKey()); @@ -186,7 +179,7 @@ public Path process(Path inputJar) throws IOException { //LogHelper.debug("[checkSecure] %s: %s", launcherSalt, Arrays.toString(launcherSecureHash)); if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken(); launcherConfigurator.setStringField("oemUnlockKey", server.runtime.oemUnlockKey); - server.buildHookManager.registerAllClientModuleClass(launcherConfigurator); + context.clientModules.forEach(launcherConfigurator::addModuleClass); reader.getCp().add(new JarFile(inputJar.toFile())); server.launcherBinary.coreLibs.forEach(e -> { try { @@ -197,54 +190,18 @@ public Path process(Path inputJar) throws IOException { }); context.pushBytes(launcherConfigurator.getZipEntryPath(), launcherConfigurator.getBytecode(reader)); context.pushBytes(secureConfigurator.getZipEntryPath(), secureConfigurator.getBytecode(reader)); - try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(inputJar))) { - ZipEntry e = input.getNextEntry(); - while (e != null) { - String filename = e.getName(); - if (e.isDirectory() || blacklist.contains(filename) || context.fileList.contains(filename)) { - e = input.getNextEntry(); - continue; - } - try { - output.putNextEntry(IOHelper.newZipEntry(e)); - } catch (ZipException ex) { - LogHelper.error(ex); - e = input.getNextEntry(); - continue; - } - if (filename.endsWith(".class")) { - String classname = filename.replace('/', '.').substring(0, - filename.length() - ".class".length()); - byte[] bytes = IOHelper.read(input); - bytes = transformClass(bytes, classname, context); - output.write(bytes); - } else - IOHelper.transfer(input, output); - context.fileList.add(filename); - e = input.getNextEntry(); - } - } + + context.pushJarFile(inputJar, (e) -> blacklist.contains(e.getName()), (e) -> true); // map for guard Map runtime = new HashMap<>(256); - if (server.buildHookManager.buildRuntime()) { - // Write launcher guard dir - IOHelper.walk(server.launcherBinary.runtimeDir, new RuntimeDirVisitor(output, runtime, server.launcherBinary.runtimeDir, "runtime"), false); - IOHelper.walk(server.launcherBinary.guardDir, new RuntimeDirVisitor(output, runtime, server.launcherBinary.guardDir, "guard"), false); - } - // Create launcher config file - byte[] launcherConfigBytes; - try (ByteArrayOutputStream configArray = IOHelper.newByteArrayOutput()) { - try (HOutput configOutput = new HOutput(configArray)) { - new LauncherConfig(server.config.netty.address, server.publicKey, runtime, server.config.projectName) - .write(configOutput); - } - launcherConfigBytes = configArray.toByteArray(); - } + // Write launcher guard dir + context.pushDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, runtime, false); + context.pushDir(server.launcherBinary.guardDir, Launcher.GUARD_DIR, runtime, false); - // Write launcher config file - output.putNextEntry(newZipEntry(Launcher.CONFIG_FILE)); - output.write(launcherConfigBytes); + LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.publicKey, runtime, server.config.projectName); + context.pushFile(Launcher.CONFIG_FILE, launcherConfig); + postBuildHook.hook(context); } reader.close(); return outputJar; diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/hook/BuildHookManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/hook/BuildHookManager.java deleted file mode 100644 index db810c7d..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/hook/BuildHookManager.java +++ /dev/null @@ -1,99 +0,0 @@ -package pro.gravit.launchserver.manangers.hook; - -import pro.gravit.launcher.AutogenConfig; -import pro.gravit.launchserver.binary.BuildContext; -import pro.gravit.launchserver.binary.LauncherConfigurator; -import pro.gravit.launchserver.binary.tasks.MainBuildTask; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class BuildHookManager { - - @FunctionalInterface - public interface BuildHook { - void build(BuildContext context); - } - - @FunctionalInterface - public interface Transformer { - byte[] transform(byte[] input, String classname, MainBuildTask data); - } - - private boolean BUILDRUNTIME; - private final Set HOOKS; - private final Set CLASS_TRANSFORMER; - private final Set CLASS_BLACKLIST; - private final Set MODULE_CLASS; - - public BuildHookManager() { - HOOKS = new HashSet<>(4); - CLASS_BLACKLIST = new HashSet<>(4); - MODULE_CLASS = new HashSet<>(4); - CLASS_TRANSFORMER = new HashSet<>(4); - BUILDRUNTIME = true; - autoRegisterIgnoredClass(AutogenConfig.class.getName()); - registerIgnoredClass("META-INF/DEPENDENCIES"); - registerIgnoredClass("META-INF/LICENSE"); - registerIgnoredClass("META-INF/NOTICE"); - } - - public void autoRegisterIgnoredClass(String clazz) { - CLASS_BLACKLIST.add(clazz.replace('.', '/').concat(".class")); - } - - public boolean buildRuntime() { - return BUILDRUNTIME; - } - - public byte[] classTransform(byte[] clazz, String classname, MainBuildTask reader) { - byte[] result = clazz; - for (Transformer transformer : CLASS_TRANSFORMER) result = transformer.transform(result, classname, reader); - return result; - } - - public boolean isContainsBlacklist(String clazz) { - for (String classB : CLASS_BLACKLIST) { - if (clazz.startsWith(classB)) return true; - } - return false; - } - - public void hook(BuildContext context) { - for (BuildHook hook : HOOKS) hook.build(context); - } - - public void registerAllClientModuleClass(LauncherConfigurator cfg) { - for (String clazz : MODULE_CLASS) cfg.addModuleClass(clazz); - } - - public void registerClassTransformer(Transformer transformer) { - CLASS_TRANSFORMER.add(transformer); - } - - public void registerClientModuleClass(String clazz) { - MODULE_CLASS.add(clazz); - } - - public void unregisterClientModuleClass(String clazz) { - MODULE_CLASS.remove(clazz); - } - - public void clearClientModuleClassList() { - MODULE_CLASS.clear(); - } - - public void registerIgnoredClass(String clazz) { - CLASS_BLACKLIST.add(clazz); - } - - public void registerHook(BuildHook hook) { - HOOKS.add(hook); - } - - public void setBuildRuntime(boolean runtime) { - BUILDRUNTIME = runtime; - } -} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/config/JsonConfigurableInterface.java b/LauncherAPI/src/main/java/pro/gravit/launcher/config/JsonConfigurableInterface.java index 31ba780e..3bbe52e2 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/config/JsonConfigurableInterface.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/config/JsonConfigurableInterface.java @@ -29,6 +29,16 @@ default void saveConfig(Gson gson, Path configPath) throws IOException { } } + default String toJsonString(Gson gson) + { + return gson.toJson(getConfig(), getType()); + } + + default String toJsonString() + { + return toJsonString(Launcher.gsonManager.configGson); + } + default void loadConfig(Gson gson, Path configPath) throws IOException { if (generateConfigIfNotExists(configPath)) return; diff --git a/modules b/modules index 9cb09c54..5f6dc831 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 9cb09c549abea86348597137e05a0ec29bc36b62 +Subproject commit 5f6dc8312dcb91b92ee96040830a914d6038773d