Launcher/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java

343 lines
17 KiB
Java
Raw Normal View History

package pro.gravit.launcher.client;
2021-06-05 02:30:56 +03:00
import pro.gravit.launcher.*;
import pro.gravit.launcher.api.AuthService;
import pro.gravit.launcher.api.ClientService;
2020-04-03 11:12:31 +03:00
import pro.gravit.launcher.client.events.client.*;
import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.hasher.FileNameMatcher;
import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry;
import pro.gravit.launcher.managers.ClientGsonManager;
2021-04-29 19:56:48 +03:00
import pro.gravit.launcher.managers.ConsoleManager;
import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.patches.FMLPatcher;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.profiles.optional.actions.OptionalActionClassPath;
2020-09-26 20:52:43 +03:00
import pro.gravit.launcher.profiles.optional.actions.OptionalActionClientArgs;
2021-06-18 07:34:32 +03:00
import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.auth.AuthRequest;
2021-04-13 15:30:06 +03:00
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launcher.serialize.HInput;
import pro.gravit.launcher.utils.DirWatcher;
import pro.gravit.utils.helper.*;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ClientLauncherEntryPoint {
private static ClassLoader classLoader;
2020-04-05 10:27:04 +03:00
private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException {
2020-04-05 10:27:04 +03:00
try (Socket socket = IOHelper.newSocket()) {
socket.connect(address);
2020-04-05 10:27:04 +03:00
try (HInput input = new HInput(socket.getInputStream())) {
byte[] serialized = input.readByteArray(0);
ClientLauncherProcess.ClientParams params = Launcher.gsonManager.gson.fromJson(new String(serialized, IOHelper.UNICODE_CHARSET), ClientLauncherProcess.ClientParams.class);
params.clientHDir = new HashedDir(input);
params.assetHDir = new HashedDir(input);
boolean isNeedReadJavaDir = input.readBoolean();
2020-09-25 18:48:33 +03:00
if (isNeedReadJavaDir)
params.javaHDir = new HashedDir(input);
return params;
}
}
}
2020-04-05 10:27:04 +03:00
public static void main(String[] args) throws Throwable {
LauncherEngine.IS_CLIENT.set(true);
LauncherEngine engine = LauncherEngine.clientInstance();
2020-04-03 11:12:31 +03:00
JVMHelper.verifySystemProperties(ClientLauncherEntryPoint.class, true);
EnvHelper.checkDangerousParams();
JVMHelper.checkStackTrace(ClientLauncherEntryPoint.class);
LogHelper.printVersion("Client Launcher");
LauncherEngine.checkClass(LauncherEngine.class);
LauncherEngine.checkClass(LauncherAgent.class);
LauncherEngine.checkClass(ClientLauncherEntryPoint.class);
LauncherEngine.modulesManager = new ClientModuleManager();
LauncherEngine.modulesManager.loadModule(new ClientLauncherCoreModule());
LauncherConfig.initModules(LauncherEngine.modulesManager); //INIT
LauncherEngine.modulesManager.initModules(null);
initGson(LauncherEngine.modulesManager);
2021-04-29 19:56:48 +03:00
ConsoleManager.initConsole();
LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase());
engine.readKeys();
LauncherGuardManager.initGuard(true);
LogHelper.debug("Reading ClientLauncher params");
ClientLauncherProcess.ClientParams params = readParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort));
2021-05-07 15:25:04 +03:00
if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) {
LauncherEngine.verifyNoAgent();
}
ClientProfile profile = params.profile;
Launcher.profile = profile;
AuthService.profile = profile;
LauncherEngine.clientParams = params;
2021-05-25 12:17:29 +03:00
if (params.oauth != null) {
2021-05-23 17:11:27 +03:00
LogHelper.info("Using OAuth");
2021-05-25 12:17:29 +03:00
if (params.oauthExpiredTime != 0) {
Request.setOAuth(params.authId, params.oauth, params.oauthExpiredTime);
} else {
Request.setOAuth(params.authId, params.oauth);
}
2021-05-25 12:17:29 +03:00
if (params.extendedTokens != null) {
Request.addAllExtendedToken(params.extendedTokens);
}
2021-05-25 12:17:29 +03:00
} else if (params.session != null) {
2021-05-23 17:11:27 +03:00
LogHelper.info("Using Sessions");
Request.setSession(params.session);
}
checkJVMBitsAndVersion(params.profile.getMinJavaVersion(), params.profile.getRecommendJavaVersion(), params.profile.getMaxJavaVersion(), params.profile.isWarnMissJavaVersion());
2020-04-03 11:12:31 +03:00
LauncherEngine.modulesManager.invokeEvent(new ClientProcessInitPhase(engine, params));
Path clientDir = Paths.get(params.clientDir);
Path assetDir = Paths.get(params.assetDir);
// Verify ClientLauncher sign and classpath
LogHelper.debug("Verifying ClientLauncher sign and classpath");
List<URL> classpath = new LinkedList<>();
resolveClassPathStream(clientDir, params.profile.getClassPath()).map(IOHelper::toURL).collect(Collectors.toCollection(() -> classpath));
2020-09-25 18:48:33 +03:00
for (OptionalAction a : params.actions) {
if (a instanceof OptionalActionClassPath)
resolveClassPathStream(clientDir, ((OptionalActionClassPath) a).args).map(IOHelper::toURL).collect(Collectors.toCollection(() -> classpath));
}
// Start client with WatchService monitoring
boolean digest = !profile.isUpdateFastCheck();
LogHelper.debug("Restore sessions");
Request.restore();
2021-06-09 06:01:07 +03:00
Request.service.registerEventHandler(new BasicLauncherEventHandler());
Request.service.reconnectCallback = () ->
{
LogHelper.debug("WebSocket connect closed. Try reconnect");
try {
Request.reconnect();
} catch (Exception e) {
LogHelper.error(e);
throw new RequestException("Connection failed", e);
}
};
2021-05-07 15:25:04 +03:00
if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.LAUNCHER) {
ClientClassLoader classLoader = new ClientClassLoader(classpath.toArray(new URL[0]), ClassLoader.getSystemClassLoader());
ClientLauncherEntryPoint.classLoader = classLoader;
Thread.currentThread().setContextClassLoader(classLoader);
classLoader.nativePath = clientDir.resolve("natives").toString();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
AuthService.username = params.playerProfile.username;
AuthService.uuid = params.playerProfile.uuid;
ClientService.classLoader = classLoader;
ClientService.nativePath = classLoader.nativePath;
classLoader.addURL(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL());
ClientService.baseURLs = classLoader.getURLs();
2021-05-07 15:25:04 +03:00
} else if (params.profile.getClassLoaderConfig() == ClientProfile.ClassLoaderConfig.AGENT) {
ClientLauncherEntryPoint.classLoader = ClassLoader.getSystemClassLoader();
classpath.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL());
2021-03-20 11:53:22 +03:00
for (URL url : classpath) {
LauncherAgent.addJVMClassPath(Paths.get(url.toURI()));
}
ClientService.instrumentation = LauncherAgent.inst;
ClientService.nativePath = clientDir.resolve("natives").toString();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
AuthService.username = params.playerProfile.username;
AuthService.uuid = params.playerProfile.uuid;
ClientService.classLoader = classLoader;
ClientService.baseURLs = classpath.toArray(new URL[0]);
}
2021-05-25 12:17:29 +03:00
if (params.profile.getRuntimeInClientConfig() != ClientProfile.RuntimeInClientConfig.NONE) {
2021-04-29 19:56:48 +03:00
CommonHelper.newThread("Client Launcher Thread", true, () -> {
try {
engine.start(args);
} catch (Throwable throwable) {
LogHelper.error(throwable);
}
}).start();
}
2020-04-03 11:12:31 +03:00
LauncherEngine.modulesManager.invokeEvent(new ClientProcessReadyEvent(engine, params));
LogHelper.debug("Starting JVM and client WatchService");
FileNameMatcher assetMatcher = profile.getAssetUpdateMatcher();
FileNameMatcher clientMatcher = profile.getClientUpdateMatcher();
Path javaDir = Paths.get(System.getProperty("java.home"));
try (DirWatcher assetWatcher = new DirWatcher(assetDir, params.assetHDir, assetMatcher, digest);
DirWatcher clientWatcher = new DirWatcher(clientDir, params.clientHDir, clientMatcher, digest);
DirWatcher javaWatcher = params.javaHDir == null ? null : new DirWatcher(javaDir, params.javaHDir, null, digest)) {
// Verify current state of all dirs
//verifyHDir(IOHelper.JVM_DIR, jvmHDir.object, null, digest);
//for (OptionalFile s : Launcher.profile.getOptional()) {
// if (params.updateOptional.contains(s)) s.mark = true;
// else hdir.removeR(s.file);
//}
// Start WatchService, and only then client
CommonHelper.newThread("Asset Directory Watcher", true, assetWatcher).start();
CommonHelper.newThread("Client Directory Watcher", true, clientWatcher).start();
2020-09-25 18:48:33 +03:00
if (javaWatcher != null)
CommonHelper.newThread("Java Directory Watcher", true, clientWatcher).start();
verifyHDir(assetDir, params.assetHDir, assetMatcher, digest);
verifyHDir(clientDir, params.clientHDir, clientMatcher, digest);
2020-09-25 18:48:33 +03:00
if (javaWatcher != null)
verifyHDir(javaDir, params.javaHDir, null, digest);
2020-10-27 21:55:08 +03:00
LauncherEngine.modulesManager.invokeEvent(new ClientProcessLaunchEvent(engine, params));
launch(profile, params);
}
}
2020-04-05 10:27:04 +03:00
private static void initGson(ClientModuleManager moduleManager) {
AuthRequest.registerProviders();
2021-04-13 15:30:06 +03:00
GetAvailabilityAuthRequest.registerProviders();
OptionalAction.registerProviders();
2021-06-18 07:34:32 +03:00
OptionalTrigger.registerProviders();
Launcher.gsonManager = new ClientGsonManager(moduleManager);
Launcher.gsonManager.initGson();
}
2020-04-05 10:27:04 +03:00
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
//if (matcher != null)
// matcher = matcher.verifyOnly();
// Hash directory and compare (ignore update-only matcher entries, it will break offline-mode)
HashedDir currentHDir = new HashedDir(dir, matcher, true, digest);
HashedDir.Diff diff = hdir.diff(currentHDir, matcher);
if (!diff.isSame()) {
2021-03-20 11:53:22 +03:00
if (LogHelper.isDebugEnabled()) {
diff.extra.walk(File.separator, (e, k, v) -> {
2021-03-20 11:53:22 +03:00
if (v.getType().equals(HashedEntry.Type.FILE)) {
LogHelper.error("Extra file %s", e);
} else LogHelper.error("Extra %s", e);
return HashedDir.WalkAction.CONTINUE;
});
2021-03-20 11:53:22 +03:00
diff.mismatch.walk(File.separator, (e, k, v) -> {
if (v.getType().equals(HashedEntry.Type.FILE)) {
LogHelper.error("Mismatch file %s", e);
} else LogHelper.error("Mismatch %s", e);
return HashedDir.WalkAction.CONTINUE;
});
}
throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir)));
}
}
2020-04-05 10:27:04 +03:00
public static boolean checkJVMBitsAndVersion(int minVersion, int recommendVersion, int maxVersion, boolean showMessage) {
boolean ok = true;
if (JVMHelper.JVM_BITS != JVMHelper.OS_BITS) {
String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS);
LogHelper.error(error);
if (showMessage)
JOptionPane.showMessageDialog(null, error);
ok = false;
}
String jvmVersion = JVMHelper.RUNTIME_MXBEAN.getVmVersion();
LogHelper.info(jvmVersion);
int version = JVMHelper.getVersion();
if (version < minVersion || version > maxVersion) {
String error = String.format("У Вас установлена Java %s. Для правильной работы необходима Java %d", JVMHelper.RUNTIME_MXBEAN.getVmVersion(), recommendVersion);
LogHelper.error(error);
if (showMessage)
JOptionPane.showMessageDialog(null, error);
ok = false;
}
return ok;
}
2020-04-05 10:27:04 +03:00
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) {
Path path = clientDir.resolve(IOHelper.toPath(classPathEntry.replace(IOHelper.CROSS_SEPARATOR, IOHelper.PLATFORM_SEPARATOR)));
if (IOHelper.isDir(path)) { // Recursive walking and adding
IOHelper.walk(path, new ClassPathFileVisitor(builder), false);
continue;
}
builder.accept(path);
}
return builder.build();
}
private static void launch(ClientProfile profile, ClientLauncherProcess.ClientParams params) throws Throwable {
// Add client args
Collection<String> args = new LinkedList<>();
if (profile.getVersion().compareTo(ClientProfile.Version.MC164) >= 0)
params.addClientArgs(args);
else {
params.addClientLegacyArgs(args);
System.setProperty("minecraft.applet.TargetDirectory", params.clientDir);
}
Collections.addAll(args, profile.getClientArgs());
2021-03-20 11:53:22 +03:00
for (OptionalAction action : params.actions) {
if (action instanceof OptionalActionClientArgs) {
2020-09-26 20:52:43 +03:00
args.addAll(((OptionalActionClientArgs) action).args);
}
}
List<String> copy = new ArrayList<>(args);
for (int i = 0, l = copy.size(); i < l; i++) {
String s = copy.get(i);
if (i + 1 < l && ("--accessToken".equals(s) || "--session".equals(s))) {
copy.set(i + 1, "censored");
}
}
LogHelper.debug("Args: " + copy);
// Resolve main class and method
Class<?> mainClass = classLoader.loadClass(profile.getMainClass());
2021-03-20 11:53:22 +03:00
if (LogHelper.isDevEnabled() && classLoader instanceof URLClassLoader) {
for (URL u : ((URLClassLoader) classLoader).getURLs()) {
LogHelper.dev("ClassLoader URL: %s", u.toString());
}
}
FMLPatcher.apply();
2020-04-03 11:12:31 +03:00
LauncherEngine.modulesManager.invokeEvent(new ClientProcessPreInvokeMainClassEvent(params, profile, args));
{
List<String> compatClasses = profile.getCompatClasses();
2021-03-20 11:53:22 +03:00
for (String e : compatClasses) {
Class<?> clazz = classLoader.loadClass(e);
MethodHandle runMethod = MethodHandles.publicLookup().findStatic(clazz, "run", MethodType.methodType(void.class));
runMethod.invoke();
}
}
MethodHandle mainMethod = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
Launcher.LAUNCHED.set(true);
JVMHelper.fullGC();
// Invoke main method
try {
mainMethod.invokeWithArguments((Object) args.toArray(new String[0]));
LogHelper.debug("Main exit successful");
} catch (Throwable e) {
LogHelper.error(e);
throw e;
} finally {
LauncherEngine.exitLauncher(0);
}
}
2020-04-05 10:27:04 +03:00
private static final class ClassPathFileVisitor extends SimpleFileVisitor<Path> {
private final Stream.Builder<Path> result;
private ClassPathFileVisitor(Stream.Builder<Path> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (IOHelper.hasExtension(file, "jar") || IOHelper.hasExtension(file, "zip"))
result.accept(file);
return super.visitFile(file, attrs);
}
}
}