From 837d27dd7bcf2f2eaf37aeedfc0c7f58c604212f Mon Sep 17 00:00:00 2001 From: Gravita Date: Thu, 18 Nov 2021 18:09:43 +0700 Subject: [PATCH] [FEATURE] ClientZoneInfo --- .../client/ClientLauncherEntryPoint.java | 55 +++++++- .../client/ClientLauncherProcess.java | 121 ++++++++++++++---- .../launcher/profiles/ClientProfile.java | 34 +++++ .../profiles/ClientProfileBuilder.java | 15 --- .../optional/actions/OptionalAction.java | 1 + .../actions/OptionalLibraryAction.java | 14 ++ .../pro/gravit/launcher/hasher/HashedDir.java | 36 ++++++ .../gravit/launcher/hasher/HashedEntry.java | 3 + .../gravit/launcher/hasher/HashedFile.java | 24 ++++ 9 files changed, 257 insertions(+), 46 deletions(-) create mode 100644 LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalLibraryAction.java diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java index 8453a7d1..f1e37cde 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java @@ -20,6 +20,7 @@ import pro.gravit.launcher.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.profiles.optional.actions.OptionalActionClassPath; import pro.gravit.launcher.profiles.optional.actions.OptionalActionClientArgs; +import pro.gravit.launcher.profiles.optional.actions.OptionalLibraryAction; import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.RequestException; @@ -119,7 +120,7 @@ public static void main(String[] args) throws Throwable { // Verify ClientLauncher sign and classpath LogHelper.debug("Verifying ClientLauncher sign and classpath"); - List classpath = resolveClassPath(clientDir, params.actions, params.profile).map(IOHelper::toURL).collect(Collectors.toList()); + List classpath = resolveClassPath(clientDir, params.actions, params.zones, params.profile).map(IOHelper::toURL).collect(Collectors.toList()); // Start client with WatchService monitoring boolean digest = !profile.isUpdateFastCheck(); RequestService service; @@ -204,8 +205,24 @@ public static void main(String[] args) throws Throwable { verifyHDir(clientDir, params.clientHDir, clientMatcher, digest); if (javaWatcher != null) verifyHDir(javaDir, params.javaHDir, null, digest); + List watchers = null; + if(params.zones != null) { + watchers = new ArrayList<>(params.zones.size()); + for(ClientLauncherProcess.ClientParams.ClientZoneInfo info : params.zones) { + if(info.dir == null) + continue; + DirWatcher watcher = new DirWatcher(Paths.get(info.path), info.dir, null, digest); + watchers.add(watcher); + CommonHelper.newThread(String.format("Zone '%s' Watcher", info.name), true, watcher).start(); + } + } LauncherEngine.modulesManager.invokeEvent(new ClientProcessLaunchEvent(engine, params)); launch(profile, params); + if(watchers != null) { + for(DirWatcher watcher : watchers) { + watcher.close(); + } + } } } @@ -290,10 +307,6 @@ public static boolean checkJVMBitsAndVersion(int minVersion, int recommendVersio return ok; } - private static LinkedList resolveClassPathList(Path clientDir, String... classPath) throws IOException { - return resolveClassPathStream(clientDir, classPath).collect(Collectors.toCollection(LinkedList::new)); - } - private static Stream resolveClassPathStream(Path clientDir, String... classPath) throws IOException { Stream.Builder builder = Stream.builder(); for (String classPathEntry : classPath) { @@ -307,11 +320,41 @@ private static Stream resolveClassPathStream(Path clientDir, String... cla return builder.build(); } - public static Stream resolveClassPath(Path clientDir, Set actions, ClientProfile profile) throws IOException { + private static Stream resolveZonedClassPath(Path clientDir, List zones, ClientProfile.ClientProfileLibrary... libraries) { + Stream.Builder builder = Stream.builder(); + for(ClientProfile.ClientProfileLibrary library : libraries) { + if(library.type == ClientProfile.ClientProfileLibrary.LibraryType.CLASSPATH) { + Path zone = null; + if(library.zone == null || library.zone.isEmpty()) { + zone = clientDir; + } else { + for(ClientLauncherProcess.ClientParams.ClientZoneInfo info : zones) { + if(info.name.equals(library.zone)) { + zone = Paths.get(info.path); + } + } + } + if(zone == null) { + LogHelper.warning("Library %s not enabled, because zone %s not found", library.name, library.zone); + continue; + } + Path path = zone.resolve(library.path); + builder.accept(path); + } + } + return builder.build(); + } + + public static Stream resolveClassPath(Path clientDir, Set actions, List zones, ClientProfile profile) throws IOException { Stream result = resolveClassPathStream(clientDir, profile.getClassPath()); + if(profile.getLibraries() != null) { + result = Stream.concat(result, resolveZonedClassPath(clientDir, zones, profile.getLibraries().toArray(new ClientProfile.ClientProfileLibrary[0]))); + } for (OptionalAction a : actions) { if (a instanceof OptionalActionClassPath) result = Stream.concat(result, resolveClassPathStream(clientDir, ((OptionalActionClassPath) a).args)); + if(a instanceof OptionalLibraryAction) + result = Stream.concat(result, resolveZonedClassPath(clientDir, zones, ((OptionalLibraryAction) a).libraries)); } return result; } diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java index f2a6e111..683c8266 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java @@ -20,10 +20,14 @@ import pro.gravit.utils.Version; import pro.gravit.utils.helper.*; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @@ -99,6 +103,7 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, Path javaDir, Path r applyClientProfile(); } + public static String getPathSeparator() { if (JVMHelper.OS_TYPE == JVMHelper.OS.MUSTDIE) return ";"; @@ -154,7 +159,7 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException { if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.AGENT) { processArgs.add("-javaagent:".concat(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString())); } else if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) { - systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(workDir, params.actions, params.profile).map(Path::toString).collect(Collectors.toList())); + systemClassPath.addAll(ClientLauncherEntryPoint.resolveClassPath(workDir, params.actions, params.zones, params.profile).map(Path::toString).collect(Collectors.toList())); } if (useLegacyJavaClassPathProperty) { processArgs.add("-Djava.class.path=".concat(String.join(getPathSeparator(), systemClassPath))); @@ -212,30 +217,6 @@ private void applyJava9Params(List processArgs) { } } - public void runWriteParams(SocketAddress address) throws IOException { - try (ServerSocket serverSocket = new ServerSocket()) { - serverSocket.bind(address); - synchronized (waitWriteParams) { - waitWriteParams[0] = true; - waitWriteParams.notifyAll(); - } - Socket socket = serverSocket.accept(); - try (HOutput output = new HOutput(socket.getOutputStream())) { - byte[] serializedMainParams = IOHelper.encode(Launcher.gsonManager.gson.toJson(params)); - output.writeByteArray(serializedMainParams, 0); - params.clientHDir.write(output); - params.assetHDir.write(output); - if (params.javaHDir == null || params.javaHDir == params.assetHDir) { //OLD RUNTIME USE params.assetHDir AS NULL IN java.javaHDir - output.writeBoolean(false); - } else { - output.writeBoolean(true); - params.javaHDir.write(output); - } - } - } - LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderParamsWrittedEvent(this)); - } - public Process getProcess() { return process; } @@ -289,6 +270,8 @@ public static class ClientParams { public transient HashedDir javaHDir; + public transient List zones; + public void addClientArgs(Collection args) { if (profile.getVersion().compareTo(ClientProfile.Version.MC164) >= 0) addModernClientArgs(args); @@ -363,6 +346,58 @@ private void addModernClientArgs(Collection args) { } } + public void write(DataOutputStream stream) throws IOException { + stream.writeUTF(Launcher.gsonManager.gson.toJson(this)); + if(clientHDir == null) { + stream.writeBoolean(false); + } else { + stream.writeBoolean(true); + clientHDir.write(stream); + } + if(assetHDir == null) { + stream.writeBoolean(false); + } else { + stream.writeBoolean(true); + assetHDir.write(stream); + } + if(javaHDir == null) { + stream.writeBoolean(false); + } else { + stream.writeBoolean(true); + javaHDir.write(stream); + } + if(zones == null) { + stream.writeBoolean(false); + } else { + stream.writeBoolean(true); + stream.writeInt(zones.size()); + for(ClientZoneInfo zone : zones) { + zone.write(stream); + } + } + } + + public static ClientParams read(DataInputStream input) throws IOException { + ClientParams params = Launcher.gsonManager.gson.fromJson(input.readUTF(), ClientParams.class); + if(input.readBoolean()) { + params.clientHDir = new HashedDir(input); + } + if(input.readBoolean()) { + params.assetHDir = new HashedDir(input); + } + if(input.readBoolean()) { + params.javaHDir = new HashedDir(input); + } + if(input.readBoolean()) { + int length = input.readInt(); + params.zones = new ArrayList<>(length); + for(int i=0;i getLibraries() { + return libraries; } public ClientProfile() { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfileBuilder.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfileBuilder.java index e59b7494..f42567fb 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfileBuilder.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfileBuilder.java @@ -77,21 +77,6 @@ public ClientProfileBuilder library(ClientProfile.ClientProfileLibrary library) return this; } - public ClientProfileBuilder library(String zone, String name, String path) { - this.libraries.add(new ClientProfile.ClientProfileLibrary(zone, name, path)); - return this; - } - - public ClientProfileBuilder library(String name, String path) { - this.libraries.add(new ClientProfile.ClientProfileLibrary(name, path)); - return this; - } - - public ClientProfileBuilder library(String name) { - this.libraries.add(new ClientProfile.ClientProfileLibrary(name)); - return this; - } - public ClientProfileBuilder setUpdateOptional(Set updateOptional) { this.updateOptional = updateOptional; return this; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalAction.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalAction.java index aabc8ccc..db2817dd 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalAction.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalAction.java @@ -12,6 +12,7 @@ public static void registerProviders() { providers.register("clientArgs", OptionalActionClientArgs.class); providers.register("jvmArgs", OptionalActionJvmArgs.class); providers.register("classpath", OptionalActionClassPath.class); + providers.register("library", OptionalLibraryAction.class); registerProviders = true; } } diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalLibraryAction.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalLibraryAction.java new file mode 100644 index 00000000..5441d13f --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/actions/OptionalLibraryAction.java @@ -0,0 +1,14 @@ +package pro.gravit.launcher.profiles.optional.actions; + +import pro.gravit.launcher.profiles.ClientProfile; + +public class OptionalLibraryAction extends OptionalAction { + public ClientProfile.ClientProfileLibrary[] libraries; + + public OptionalLibraryAction() { + } + + public OptionalLibraryAction(ClientProfile.ClientProfileLibrary[] libraries) { + this.libraries = libraries; + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedDir.java b/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedDir.java index 657795f7..e8dedfbe 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedDir.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedDir.java @@ -7,7 +7,10 @@ import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.VerifyHelper; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.Paths; @@ -48,6 +51,27 @@ public HashedDir(HInput input) throws IOException { } } + public HashedDir(DataInputStream input) throws IOException { + int entriesCount = input.readInt(); + for (int i = 0; i < entriesCount; i++) { + String name = input.readUTF(); + + // Read entry + HashedEntry entry; + int type = input.readByte(); + if(type == Type.FILE.getNumber()) { + entry = new HashedFile(input); + } else if(type == Type.DIR.getNumber()) { + entry = new HashedDir(input); + } else { + throw new AssertionError("Unsupported hashed entry type: " + type); + } + + // Try add entry to map + VerifyHelper.putIfAbsent(map, name, entry, String.format("Duplicate dir entry: '%s'", name)); + } + } + public HashedDir(Path dir, FileNameMatcher matcher, boolean allowSymlinks, boolean digest) throws IOException { IOHelper.walk(dir, new HashFileVisitor(dir, matcher, allowSymlinks, digest), true); @@ -314,6 +338,18 @@ public void write(HOutput output) throws IOException { } } + public void write(DataOutputStream output) throws IOException { + Set> entries = map.entrySet(); + output.writeInt(entries.size()); + for (Entry mapEntry : entries) { + output.writeUTF(mapEntry.getKey()); + // Write hashed entry + HashedEntry entry = mapEntry.getValue(); + output.writeByte(entry.getType().getNumber()); + entry.write(output); + } + } + public void walk(CharSequence separator, WalkCallback callback) throws IOException { String append = ""; walk(append, separator, callback, true); diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedEntry.java b/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedEntry.java index dec01ff3..86870ac3 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedEntry.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedEntry.java @@ -5,6 +5,7 @@ import pro.gravit.launcher.serialize.stream.EnumSerializer; import pro.gravit.launcher.serialize.stream.StreamObject; +import java.io.DataOutputStream; import java.io.IOException; public abstract class HashedEntry extends StreamObject { @@ -16,6 +17,8 @@ public abstract class HashedEntry extends StreamObject { public abstract long size(); + public abstract void write(DataOutputStream stream) throws IOException; + public enum Type implements EnumSerializer.Itf { DIR(1), FILE(2); private static final EnumSerializer SERIALIZER = new EnumSerializer<>(Type.class); diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedFile.java b/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedFile.java index 396265e7..791fc773 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedFile.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/hasher/HashedFile.java @@ -8,6 +8,8 @@ import pro.gravit.utils.helper.SecurityHelper.DigestAlgorithm; import pro.gravit.utils.helper.VerifyHelper; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; @@ -26,6 +28,16 @@ public HashedFile(HInput input) throws IOException { this(input.readVarLong(), input.readBoolean() ? input.readByteArray(-DIGEST_ALGO.bytes) : null); } + public HashedFile(DataInputStream input) throws IOException { + this(input.readLong(), input.readBoolean() ? readDigest(input) : null); + } + + private static byte[] readDigest(DataInputStream input) throws IOException { + byte[] bytes = new byte[input.readInt()]; + input.readFully(bytes); + return bytes; + } + public HashedFile(long size, byte[] digest) { this.size = VerifyHelper.verifyLong(size, VerifyHelper.L_NOT_NEGATIVE, "Illegal size: " + size); @@ -69,6 +81,18 @@ public long size() { return size; } + @Override + public void write(DataOutputStream stream) throws IOException { + stream.writeLong(size); + if(digest == null) { + stream.writeBoolean(false); + } else { + stream.writeBoolean(true); + stream.writeInt(digest.length); + stream.write(digest); + } + } + @Override public void write(HOutput output) throws IOException { output.writeVarLong(size);