[FEATURE] ClientZoneInfo

This commit is contained in:
Gravita 2021-11-18 18:09:43 +07:00
parent 9091c838e4
commit 837d27dd7b
9 changed files with 257 additions and 46 deletions

View file

@ -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<URL> classpath = resolveClassPath(clientDir, params.actions, params.profile).map(IOHelper::toURL).collect(Collectors.toList());
List<URL> 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<DirWatcher> 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<Path> resolveClassPathList(Path clientDir, String... classPath) throws IOException {
return resolveClassPathStream(clientDir, classPath).collect(Collectors.toCollection(LinkedList::new));
}
private static Stream<Path> resolveClassPathStream(Path clientDir, String... classPath) throws IOException {
Stream.Builder<Path> builder = Stream.builder();
for (String classPathEntry : classPath) {
@ -307,11 +320,41 @@ private static Stream<Path> resolveClassPathStream(Path clientDir, String... cla
return builder.build();
}
public static Stream<Path> resolveClassPath(Path clientDir, Set<OptionalAction> actions, ClientProfile profile) throws IOException {
private static Stream<Path> resolveZonedClassPath(Path clientDir, List<ClientLauncherProcess.ClientParams.ClientZoneInfo> zones, ClientProfile.ClientProfileLibrary... libraries) {
Stream.Builder<Path> 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<Path> resolveClassPath(Path clientDir, Set<OptionalAction> actions, List<ClientLauncherProcess.ClientParams.ClientZoneInfo> zones, ClientProfile profile) throws IOException {
Stream<Path> 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;
}

View file

@ -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<String> 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<ClientZoneInfo> zones;
public void addClientArgs(Collection<String> args) {
if (profile.getVersion().compareTo(ClientProfile.Version.MC164) >= 0)
addModernClientArgs(args);
@ -363,6 +346,58 @@ private void addModernClientArgs(Collection<String> 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<length;++i) {
params.zones.add(new ClientZoneInfo(input));
}
}
return params;
}
public static class ClientUserProperties {
@LauncherNetworkAPI
public String[] skinURL;
@ -373,5 +408,41 @@ public static class ClientUserProperties {
@LauncherNetworkAPI
public String[] cloakDigest;
}
public static class ClientZoneInfo {
public String name;
public String path;
public HashedDir dir;
public ClientZoneInfo(String name, String path, HashedDir dir) {
this.name = name;
this.path = path;
this.dir = dir;
}
public ClientZoneInfo(String name, String path) {
this.name = name;
this.path = path;
}
public ClientZoneInfo(DataInputStream input) throws IOException {
name = input.readUTF();
path = input.readUTF();
if(input.readBoolean()) {
dir = new HashedDir(input);
}
}
public void write(DataOutputStream stream) throws IOException {
stream.writeUTF(name);
stream.writeUTF(path);
if(dir == null) {
stream.writeBoolean(false);
} else {
stream.writeBoolean(true);
dir.write(stream);
}
}
}
}
}

View file

@ -87,30 +87,64 @@ public static class ClientProfileLibrary {
public final String zone;
public final String name;
public final String path;
public final String url;
public final LibraryType type;
public ClientProfileLibrary(String zone, String name, String path, String url, LibraryType type) {
this.zone = zone;
this.name = name;
this.path = path;
this.url = url;
this.type = type;
}
public ClientProfileLibrary(String zone, String name, String path, LibraryType type) {
this.zone = zone;
this.name = name;
this.path = path;
this.url = null;
this.type = type;
}
public ClientProfileLibrary(String zone, String name, String path) {
this.zone = zone;
this.name = name;
this.path = path;
this.url = null;
this.type = LibraryType.CLASSPATH;
}
public ClientProfileLibrary(String name, String path) {
this.zone = "libraries";
this.name = name;
this.path = path;
this.url = null;
this.type = LibraryType.CLASSPATH;
}
public ClientProfileLibrary(String name) {
this.zone = "libraries";
this.name = name;
this.path = convertMavenNameToPath(name);
this.url = null;
this.type = LibraryType.CLASSPATH;
}
public static String convertMavenNameToPath(String name) {
String[] mavenIdSplit = name.split(":");
if(mavenIdSplit.length < 3) {
throw new IllegalArgumentException(String.format("%s not valid maven library name", name));
}
return String.format("%s/%s/%s/%s-%s.jar", mavenIdSplit[0].replaceAll("\\.", "/"),
mavenIdSplit[1], mavenIdSplit[2], mavenIdSplit[1], mavenIdSplit[2]);
}
public enum LibraryType {
CLASSPATH, MODULEPATH, NATIVE, RUNTIME
}
}
public List<ClientProfileLibrary> getLibraries() {
return libraries;
}
public ClientProfile() {

View file

@ -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<OptionalFile> updateOptional) {
this.updateOptional = updateOptional;
return this;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<Entry<String, HashedEntry>> entries = map.entrySet();
output.writeInt(entries.size());
for (Entry<String, HashedEntry> 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);

View file

@ -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<Type> SERIALIZER = new EnumSerializer<>(Type.class);

View file

@ -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);