Launcher/LaunchServer/src/main/java/ru/gravit/launchserver/LaunchServer.java

940 lines
35 KiB
Java
Raw Normal View History

2018-09-17 10:07:32 +03:00
package ru.gravit.launchserver;
import io.netty.handler.logging.LogLevel;
import ru.gravit.launcher.Launcher;
2018-12-19 14:24:50 +03:00
import ru.gravit.launcher.LauncherConfig;
2019-04-03 13:09:53 +03:00
import ru.gravit.launcher.NeedGarbageCollection;
2019-05-15 14:00:02 +03:00
import ru.gravit.launcher.config.JsonConfigurable;
2018-09-17 10:07:32 +03:00
import ru.gravit.launcher.hasher.HashedDir;
2019-04-03 13:09:53 +03:00
import ru.gravit.launcher.managers.ConfigManager;
2018-09-17 10:07:32 +03:00
import ru.gravit.launcher.managers.GarbageManager;
import ru.gravit.launcher.profiles.ClientProfile;
import ru.gravit.launcher.serialize.signed.SignedObjectHolder;
import ru.gravit.launchserver.auth.AuthProviderPair;
2018-09-17 10:07:32 +03:00
import ru.gravit.launchserver.auth.handler.AuthHandler;
import ru.gravit.launchserver.auth.handler.MemoryAuthHandler;
import ru.gravit.launchserver.auth.hwid.AcceptHWIDHandler;
2018-09-17 10:07:32 +03:00
import ru.gravit.launchserver.auth.hwid.HWIDHandler;
import ru.gravit.launchserver.auth.permissions.DefaultPermissionsHandler;
import ru.gravit.launchserver.auth.permissions.JsonFilePermissionsHandler;
import ru.gravit.launchserver.auth.permissions.PermissionsHandler;
2019-04-03 16:27:40 +03:00
import ru.gravit.launchserver.auth.protect.NoProtectHandler;
import ru.gravit.launchserver.auth.protect.ProtectHandler;
2018-09-17 10:07:32 +03:00
import ru.gravit.launchserver.auth.provider.AuthProvider;
import ru.gravit.launchserver.auth.provider.RejectAuthProvider;
2019-04-20 01:14:02 +03:00
import ru.gravit.launchserver.auth.texture.RequestTextureProvider;
import ru.gravit.launchserver.auth.texture.TextureProvider;
2019-01-15 06:35:39 +03:00
import ru.gravit.launchserver.binary.*;
2019-04-03 16:27:40 +03:00
import ru.gravit.launchserver.components.AuthLimiterComponent;
2019-03-13 12:13:21 +03:00
import ru.gravit.launchserver.components.Component;
2019-04-12 00:58:45 +03:00
import ru.gravit.launchserver.config.LaunchServerRuntimeConfig;
2019-04-20 01:14:02 +03:00
import ru.gravit.launchserver.legacy.Response;
2019-01-15 06:35:39 +03:00
import ru.gravit.launchserver.manangers.*;
import ru.gravit.launchserver.manangers.hook.AuthHookManager;
import ru.gravit.launchserver.manangers.hook.BuildHookManager;
import ru.gravit.launchserver.manangers.hook.SocketHookManager;
2018-09-17 10:07:32 +03:00
import ru.gravit.launchserver.socket.ServerSocketHandler;
2019-04-20 01:14:02 +03:00
import ru.gravit.launchserver.websocket.NettyServerSocketHandler;
import ru.gravit.utils.Version;
2019-04-03 16:27:40 +03:00
import ru.gravit.utils.command.CommandHandler;
import ru.gravit.utils.command.JLineCommandHandler;
import ru.gravit.utils.command.StdCommandHandler;
2019-01-15 06:35:39 +03:00
import ru.gravit.utils.helper.*;
2019-04-12 00:58:45 +03:00
import java.io.*;
2019-01-15 06:35:39 +03:00
import java.lang.ProcessBuilder.Redirect;
import java.lang.reflect.InvocationTargetException;
2019-01-15 06:35:39 +03:00
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.CRC32;
2018-09-17 10:07:32 +03:00
public final class LaunchServer implements Runnable, AutoCloseable, Reloadable {
@Override
public void reload() throws Exception {
config.close();
LogHelper.info("Reading LaunchServer config file");
try (BufferedReader reader = IOHelper.newReader(configFile)) {
2019-04-20 01:03:06 +03:00
config = Launcher.gsonManager.gson.fromJson(reader, Config.class);
}
config.verify();
config.init();
}
public static final class Config {
public int legacyPort;
2018-09-17 10:07:32 +03:00
private String legacyAddress;
2018-10-13 11:01:10 +03:00
private String legacyBindAddress;
public String projectName;
public String[] mirrors;
2019-01-15 06:35:39 +03:00
public String binaryName;
2019-05-15 14:11:22 +03:00
public boolean copyBinaries = true;
public LauncherConfig.LauncherEnvironment env;
// Handlers & Providers
2018-10-13 11:01:10 +03:00
public AuthProviderPair[] auth;
2018-10-13 11:01:10 +03:00
private transient AuthProviderPair authDefault;
2019-04-03 16:27:40 +03:00
public AuthProviderPair getAuthProviderPair(String name) {
for (AuthProviderPair pair : auth) {
if (pair.name.equals(name)) return pair;
}
return null;
}
2019-04-03 16:27:40 +03:00
2019-03-14 19:54:08 +03:00
public ProtectHandler protectHandler;
public PermissionsHandler permissionsHandler;
2019-04-03 16:27:40 +03:00
public AuthProviderPair getAuthProviderPair() {
if (authDefault != null) return authDefault;
for (AuthProviderPair pair : auth) {
if (pair.isDefault) {
authDefault = pair;
return pair;
}
}
return null;
}
public HWIDHandler hwidHandler;
2018-09-17 10:07:32 +03:00
public Map<String, Component> components;
2018-09-17 10:07:32 +03:00
// Misc options
public int threadCount;
public int threadCoreCount;
public ExeConf launch4j;
2019-01-29 18:38:23 +03:00
public NettyConfig netty;
2019-02-06 11:46:58 +03:00
public GuardLicenseConf guardLicense;
public String whitelistRejectString;
public boolean genMappings;
2019-04-12 01:15:05 +03:00
public LauncherConf launcher;
public boolean isWarningMissArchJava;
public boolean enabledProGuard;
public boolean enabledRadon;
public boolean stripLineNumbers;
2019-01-15 06:35:39 +03:00
public boolean deleteTempFiles;
public String startScript;
2019-01-04 14:32:16 +03:00
public String getLegacyAddress() {
return legacyAddress;
2018-09-17 10:07:32 +03:00
}
2018-10-13 11:01:10 +03:00
public String getLegacyBindAddress() {
return legacyBindAddress;
2018-09-17 10:07:32 +03:00
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void setBinaryName(String binaryName) {
this.binaryName = binaryName;
}
public void setEnv(LauncherConfig.LauncherEnvironment env) {
this.env = env;
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public SocketAddress getSocketAddress() {
return new InetSocketAddress(legacyBindAddress, legacyPort);
2018-09-17 10:07:32 +03:00
}
2018-10-13 11:01:10 +03:00
public void setLegacyAddress(String legacyAddress) {
this.legacyAddress = legacyAddress;
2018-09-17 10:07:32 +03:00
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public void verify() {
VerifyHelper.verify(getLegacyAddress(), VerifyHelper.NOT_EMPTY, "LaunchServer address can't be empty");
if (auth == null || auth[0] == null) {
throw new NullPointerException("AuthHandler must not be null");
}
boolean isOneDefault = false;
2019-04-03 16:27:40 +03:00
for (AuthProviderPair pair : auth) {
2019-03-22 09:19:58 +03:00
if (pair.isDefault) {
isOneDefault = true;
break;
}
2019-03-22 09:19:58 +03:00
}
2019-04-03 16:27:40 +03:00
if (protectHandler == null) {
2019-03-14 19:54:08 +03:00
throw new NullPointerException("ProtectHandler must not be null");
}
2019-04-03 16:27:40 +03:00
if (!isOneDefault) {
throw new IllegalStateException("No auth pairs declared by default.");
}
2019-01-15 06:35:39 +03:00
if (permissionsHandler == null) {
2019-01-12 03:13:24 +03:00
throw new NullPointerException("PermissionsHandler must not be null");
}
2019-01-15 06:35:39 +03:00
if (env == null) {
2019-01-12 03:13:24 +03:00
throw new NullPointerException("Env must not be null");
}
2019-04-03 16:27:40 +03:00
if (netty == null) {
throw new NullPointerException("Netty must not be null");
}
2018-09-17 10:07:32 +03:00
}
2019-05-15 14:11:22 +03:00
public void init() {
Launcher.applyLauncherEnv(env);
for (AuthProviderPair provider : auth) {
provider.init();
}
permissionsHandler.init();
hwidHandler.init();
if (protectHandler != null) {
protectHandler.checkLaunchServerLicense();
}
LaunchServer.server.registerObject("permissionsHandler", permissionsHandler);
2019-05-15 14:09:32 +03:00
for (AuthProviderPair pair : auth) {
LaunchServer.server.registerObject("auth.".concat(pair.name).concat(".provider"), pair.provider);
LaunchServer.server.registerObject("auth.".concat(pair.name).concat(".handler"), pair.handler);
LaunchServer.server.registerObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider);
}
Arrays.stream(mirrors).forEach(LaunchServer.server.mirrorManager::addMirror);
}
2019-04-03 16:27:40 +03:00
public void close() {
try {
LaunchServer.server.unregisterObject("permissionsHandler", permissionsHandler);
2019-05-15 14:09:32 +03:00
for (AuthProviderPair pair : auth) {
LaunchServer.server.unregisterObject("auth.".concat(pair.name).concat(".provider"), pair.provider);
LaunchServer.server.unregisterObject("auth.".concat(pair.name).concat(".handler"), pair.handler);
LaunchServer.server.unregisterObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider);
}
2019-05-15 14:11:22 +03:00
} catch (Exception e) {
LogHelper.error(e);
}
try {
for (AuthProviderPair p : auth) p.close();
} catch (IOException e) {
LogHelper.error(e);
}
try {
hwidHandler.close();
} catch (Exception e) {
LogHelper.error(e);
}
try {
permissionsHandler.close();
} catch (Exception e) {
LogHelper.error(e);
}
}
2018-09-17 10:07:32 +03:00
}
2018-09-22 17:33:00 +03:00
public static class ExeConf {
public boolean enabled;
public String productName;
public String productVer;
public String fileDesc;
public String fileVer;
public String internalName;
public String copyright;
public String trademarks;
public String txtFileVersion;
public String txtProductVersion;
2018-09-17 10:07:32 +03:00
}
public static class NettyUpdatesBind
{
public String url;
public boolean zip;
}
2019-04-03 16:27:40 +03:00
2019-05-15 14:11:22 +03:00
public class LauncherConf {
2019-04-12 01:15:05 +03:00
public String guardType;
public boolean attachLibraryBeforeProGuard;
2019-04-12 01:15:05 +03:00
}
2019-04-03 16:27:40 +03:00
public class NettyConfig {
public boolean fileServerEnabled;
public boolean sendExceptionEnabled;
2019-05-09 16:00:10 +03:00
public boolean ipForwarding;
public String launcherURL;
public String downloadURL;
public String launcherEXEURL;
2019-02-20 13:20:00 +03:00
public String address;
public Map<String, NettyUpdatesBind> bindings = new HashMap<>();
public NettyPerformanceConfig performance;
public NettyBindAddress[] binds;
public LogLevel logLevel = LogLevel.DEBUG;
public NettyProxyConfig proxy = new NettyProxyConfig();
}
2019-05-15 14:11:22 +03:00
public class NettyPerformanceConfig {
public int bossThread;
public int workerThread;
}
2019-05-15 14:11:22 +03:00
public class NettyProxyConfig {
public boolean enabled;
public String address = "ws://localhost:9275/api";
public String login = "login";
public String password = "password";
public String auth_id = "std";
public ArrayList<String> requests = new ArrayList<>();
}
2019-05-15 14:11:22 +03:00
public class NettyBindAddress {
public String address;
public int port;
public NettyBindAddress(String address, int port) {
this.address = address;
this.port = port;
}
2019-01-29 18:38:23 +03:00
}
2019-04-03 16:27:40 +03:00
public class GuardLicenseConf {
2019-02-06 11:46:58 +03:00
public String name;
public String key;
public String encryptKey;
}
2018-09-22 17:33:00 +03:00
2018-09-17 10:07:32 +03:00
private final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Collection<ClientProfile> result;
2018-09-17 10:07:32 +03:00
private ProfilesFileVisitor(Collection<ClientProfile> result) {
2018-09-17 10:07:32 +03:00
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
LogHelper.info("Syncing '%s' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
2019-04-20 01:03:06 +03:00
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
2018-09-17 10:07:32 +03:00
}
profile.verify();
// Add SIGNED profile to result list
result.add(profile);
2018-09-17 10:07:32 +03:00
return super.visitFile(file, attrs);
}
}
2018-09-22 17:33:00 +03:00
2018-09-17 10:07:32 +03:00
public static void main(String... args) throws Throwable {
JVMHelper.checkStackTrace(LaunchServer.class);
2018-09-17 10:07:32 +03:00
JVMHelper.verifySystemProperties(LaunchServer.class, true);
LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer");
if (!StarterAgent.isAgentStarted()) {
2019-05-15 14:11:22 +03:00
LogHelper.error("StarterAgent is not started!");
LogHelper.error("Your should add to JVM options this option: `-javaagent:LaunchServer.jar`");
}
2018-09-17 10:07:32 +03:00
// Start LaunchServer
long startTime = System.currentTimeMillis();
2018-09-17 10:07:32 +03:00
try {
2019-03-15 17:32:01 +03:00
@SuppressWarnings("resource")
LaunchServer launchserver = new LaunchServer(IOHelper.WORKING_DIR, false, args);
2019-04-03 16:27:40 +03:00
if (args.length == 0) launchserver.run();
else { //Обработка команды
2019-04-03 16:27:40 +03:00
launchserver.commandHandler.eval(args, false);
}
2018-09-17 10:07:32 +03:00
} catch (Throwable exc) {
LogHelper.error(exc);
return;
}
long endTime = System.currentTimeMillis();
LogHelper.debug("LaunchServer started in %dms", endTime - startTime);
2018-09-17 10:07:32 +03:00
}
2018-09-22 17:33:00 +03:00
2018-09-17 10:07:32 +03:00
// Constant paths
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final Path dir;
2019-05-15 14:11:22 +03:00
public final boolean testEnv;
2019-01-15 06:35:39 +03:00
public final Path launcherLibraries;
2019-04-03 16:27:40 +03:00
public final Path launcherLibrariesCompile;
2019-03-15 17:32:01 +03:00
2019-01-15 06:35:39 +03:00
public final List<String> args;
2018-09-17 10:07:32 +03:00
public final Path configFile;
2019-04-12 00:58:45 +03:00
public final Path runtimeConfigFile;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final Path publicKeyFile;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final Path privateKeyFile;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final Path updatesDir;
2019-01-15 06:35:39 +03:00
public static LaunchServer server = null;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final Path profilesDir;
// Server config
2018-10-13 11:01:10 +03:00
public Config config;
2019-04-12 00:58:45 +03:00
public LaunchServerRuntimeConfig runtime;
2018-09-17 10:07:32 +03:00
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final RSAPublicKey publicKey;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final RSAPrivateKey privateKey;
// Launcher binary
2018-10-13 11:01:10 +03:00
public final JARLauncherBinary launcherBinary;
2018-10-13 11:01:10 +03:00
public Class<? extends LauncherBinary> launcherEXEBinaryClass;
2018-09-17 10:07:32 +03:00
public final LauncherBinary launcherEXEBinary;
// HWID ban + anti-brutforce
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final SessionManager sessionManager;
2018-12-29 13:00:50 +03:00
public final SocketHookManager socketHookManager;
2018-12-31 10:51:49 +03:00
public final AuthHookManager authHookManager;
2018-09-17 10:07:32 +03:00
// Server
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final ModulesManager modulesManager;
public final MirrorManager mirrorManager;
2018-12-26 14:54:24 +03:00
public final ReloadManager reloadManager;
public final ReconfigurableManager reconfigurableManager;
2019-04-03 13:09:53 +03:00
public final ConfigManager configManager;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final BuildHookManager buildHookManager;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final ProguardConf proguardConf;
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public final CommandHandler commandHandler;
public final ServerSocketHandler serverSocketHandler;
public final NettyServerSocketHandler nettyServerSocketHandler;
2018-09-22 17:33:00 +03:00
private final AtomicBoolean started = new AtomicBoolean(false);
2018-09-17 10:07:32 +03:00
// Updates and profiles
private volatile List<ClientProfile> profilesList;
public volatile Map<String, SignedObjectHolder<HashedDir>> updatesDirMap;
2018-09-17 10:07:32 +03:00
2019-04-03 16:27:40 +03:00
public final Timer taskPool;
public static Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException, InvalidKeySpecException {
2018-09-17 10:07:32 +03:00
this.dir = dir;
this.testEnv = testEnv;
taskPool = new Timer("Timered task worker thread", true);
launcherLibraries = dir.resolve("launcher-libraries");
2019-03-15 17:32:01 +03:00
launcherLibrariesCompile = dir.resolve("launcher-libraries-compile");
2019-01-04 14:32:16 +03:00
this.args = Arrays.asList(args);
configFile = dir.resolve("LaunchServer.conf");
2019-04-12 00:58:45 +03:00
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.conf");
2018-09-17 10:07:32 +03:00
publicKeyFile = dir.resolve("public.key");
privateKeyFile = dir.resolve("private.key");
updatesDir = dir.resolve("updates");
profilesDir = dir.resolve("profiles");
//Registration handlers and providers
AuthHandler.registerHandlers();
AuthProvider.registerProviders();
TextureProvider.registerProviders();
HWIDHandler.registerHandlers();
PermissionsHandler.registerHandlers();
2018-09-17 10:07:32 +03:00
Response.registerResponses();
Component.registerComponents();
2019-03-14 19:54:08 +03:00
ProtectHandler.registerHandlers();
2018-10-01 10:48:24 +03:00
LaunchServer.server = this;
2018-09-17 10:07:32 +03:00
// Set command handler
CommandHandler localCommandHandler;
if (testEnv)
localCommandHandler = new StdCommandHandler(false);
else
2019-05-15 14:11:22 +03:00
try {
Class.forName("org.jline.terminal.Terminal");
// JLine2 available
localCommandHandler = new JLineCommandHandler();
LogHelper.info("JLine2 terminal enabled");
} catch (ClassNotFoundException ignored) {
localCommandHandler = new StdCommandHandler(true);
LogHelper.warning("JLine2 isn't in classpath, using std");
}
ru.gravit.launchserver.command.handler.CommandHandler.registerCommands(localCommandHandler);
2018-09-17 10:07:32 +03:00
commandHandler = localCommandHandler;
// Set key pair
if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) {
LogHelper.info("Reading RSA keypair");
publicKey = SecurityHelper.toPublicRSAKey(IOHelper.read(publicKeyFile));
privateKey = SecurityHelper.toPrivateRSAKey(IOHelper.read(privateKeyFile));
if (!publicKey.getModulus().equals(privateKey.getModulus()))
2018-09-22 17:33:00 +03:00
throw new IOException("Private and public key modulus mismatch");
2018-09-17 10:07:32 +03:00
} else {
LogHelper.info("Generating RSA keypair");
KeyPair pair = SecurityHelper.genRSAKeyPair();
publicKey = (RSAPublicKey) pair.getPublic();
privateKey = (RSAPrivateKey) pair.getPrivate();
// Write key pair list
LogHelper.info("Writing RSA keypair list");
2018-09-17 10:07:32 +03:00
IOHelper.write(publicKeyFile, publicKey.getEncoded());
IOHelper.write(privateKeyFile, privateKey.getEncoded());
}
// Print keypair fingerprints
CRC32 crc = new CRC32();
crc.update(publicKey.getModulus().toByteArray()); // IDEA говорит, что это Java 9 API. WTF?
2018-09-17 10:07:32 +03:00
LogHelper.subInfo("Modulus CRC32: 0x%08x", crc.getValue());
2018-09-22 17:33:00 +03:00
// Load class bindings.
launcherEXEBinaryClass = defaultLauncherEXEBinaryClass;
2018-09-17 10:07:32 +03:00
// pre init modules
modulesManager = new ModulesManager(this);
2018-09-19 15:03:52 +03:00
modulesManager.autoload(dir.resolve("modules"));
2018-09-17 10:07:32 +03:00
modulesManager.preInitModules();
initGson();
2018-09-22 17:33:00 +03:00
2018-09-17 10:07:32 +03:00
// Read LaunchServer config
generateConfigIfNotExists(testEnv);
2018-09-17 10:07:32 +03:00
LogHelper.info("Reading LaunchServer config file");
try (BufferedReader reader = IOHelper.newReader(configFile)) {
2019-04-20 01:03:06 +03:00
config = Launcher.gsonManager.gson.fromJson(reader, Config.class);
2018-09-17 10:07:32 +03:00
}
2019-05-15 14:11:22 +03:00
if (!Files.exists(runtimeConfigFile)) {
2019-04-12 00:58:45 +03:00
LogHelper.info("Reset LaunchServer runtime config file");
runtime = new LaunchServerRuntimeConfig();
runtime.reset();
2019-05-15 14:11:22 +03:00
} else {
2019-04-12 00:58:45 +03:00
LogHelper.info("Reading LaunchServer runtime config file");
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
2019-04-20 01:03:06 +03:00
runtime = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
2019-04-12 00:58:45 +03:00
}
}
runtime.verify();
2018-09-17 10:07:32 +03:00
config.verify();
Launcher.applyLauncherEnv(config.env);
for (AuthProviderPair provider : config.auth) {
2018-12-26 15:24:38 +03:00
provider.init();
}
config.permissionsHandler.init();
config.hwidHandler.init();
2019-04-03 16:27:40 +03:00
if (config.protectHandler != null) {
2019-03-14 19:54:08 +03:00
config.protectHandler.checkLaunchServerLicense();
}
2019-04-03 16:27:40 +03:00
if (config.components != null) {
LogHelper.debug("PreInit components");
2019-04-03 16:27:40 +03:00
config.components.forEach((k, v) -> {
LogHelper.subDebug("PreInit component %s", k);
v.preInit(this);
});
LogHelper.debug("PreInit components successful");
}
2018-09-17 10:07:32 +03:00
// build hooks, anti-brutforce and other
buildHookManager = new BuildHookManager();
proguardConf = new ProguardConf(this);
sessionManager = new SessionManager();
mirrorManager = new MirrorManager();
2018-12-26 14:54:24 +03:00
reloadManager = new ReloadManager();
reconfigurableManager = new ReconfigurableManager();
2018-12-29 13:00:50 +03:00
socketHookManager = new SocketHookManager();
2018-12-31 10:51:49 +03:00
authHookManager = new AuthHookManager();
2019-04-03 13:09:53 +03:00
configManager = new ConfigManager();
2018-09-17 10:07:32 +03:00
GarbageManager.registerNeedGC(sessionManager);
reloadManager.registerReloadable("launchServer", this);
2019-04-03 13:09:53 +03:00
registerObject("permissionsHandler", config.permissionsHandler);
for (int i = 0; i < config.auth.length; ++i) {
AuthProviderPair pair = config.auth[i];
2019-04-03 13:09:53 +03:00
registerObject("auth.".concat(pair.name).concat(".provider"), pair.provider);
registerObject("auth.".concat(pair.name).concat(".handler"), pair.handler);
registerObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider);
}
Arrays.stream(config.mirrors).forEach(mirrorManager::addMirror);
2018-09-22 17:33:00 +03:00
2018-09-17 10:07:32 +03:00
// init modules
modulesManager.initModules();
2019-04-03 16:27:40 +03:00
if (config.components != null) {
LogHelper.debug("Init components");
2019-04-03 16:27:40 +03:00
config.components.forEach((k, v) -> {
LogHelper.subDebug("Init component %s", k);
2019-04-03 16:27:40 +03:00
registerObject("component.".concat(k), v);
v.init(this);
});
LogHelper.debug("Init components successful");
}
2018-09-17 10:07:32 +03:00
// Set launcher EXE binary
launcherBinary = new JARLauncherBinary(this);
launcherEXEBinary = binary();
2019-01-15 06:35:39 +03:00
2019-01-08 16:57:01 +03:00
launcherBinary.init();
launcherEXEBinary.init();
2018-09-17 10:07:32 +03:00
syncLauncherBinaries();
// Sync updates dir
if (!IOHelper.isDir(updatesDir))
2018-09-22 17:33:00 +03:00
Files.createDirectory(updatesDir);
2018-09-17 10:07:32 +03:00
syncUpdatesDir(null);
// Sync profiles dir
if (!IOHelper.isDir(profilesDir))
2018-09-22 17:33:00 +03:00
Files.createDirectory(profilesDir);
2018-09-17 10:07:32 +03:00
syncProfilesDir();
// Set server socket thread
serverSocketHandler = new ServerSocketHandler(this, sessionManager);
2018-09-22 17:33:00 +03:00
2018-09-17 10:07:32 +03:00
// post init modules
modulesManager.postInitModules();
2019-04-03 16:27:40 +03:00
if (config.components != null) {
LogHelper.debug("PostInit components");
2019-04-03 16:27:40 +03:00
config.components.forEach((k, v) -> {
LogHelper.subDebug("PostInit component %s", k);
v.postInit(this);
});
LogHelper.debug("PostInit components successful");
}
// start updater
2019-04-03 16:27:40 +03:00
if (config.netty != null)
nettyServerSocketHandler = new NettyServerSocketHandler(this);
else
nettyServerSocketHandler = null;
2018-09-17 10:07:32 +03:00
}
2019-01-15 06:35:39 +03:00
public static void initGson() {
2019-04-20 01:03:06 +03:00
Launcher.gsonManager = new LaunchServerGsonManager();
Launcher.gsonManager.initGson();
}
2018-09-17 10:07:32 +03:00
private LauncherBinary binary() {
2019-05-15 14:11:22 +03:00
if (launcherEXEBinaryClass != null) {
try {
return launcherEXEBinaryClass.getConstructor(LaunchServer.class).newInstance(this);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
LogHelper.error(e);
}
}
2019-01-15 06:35:39 +03:00
try {
Class.forName("net.sf.launch4j.Builder");
2018-12-26 16:17:47 +03:00
if (config.launch4j.enabled) return new EXEL4JLauncherBinary(this);
2019-01-15 06:35:39 +03:00
} catch (ClassNotFoundException ignored) {
LogHelper.warning("Launch4J isn't in classpath.");
}
2018-09-22 17:33:00 +03:00
return new EXELauncherBinary(this);
2018-09-17 10:07:32 +03:00
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public void buildLauncherBinaries() throws IOException {
launcherBinary.build();
launcherEXEBinary.build();
}
public void close() {
serverSocketHandler.close();
// Close handlers & providers
config.close();
2018-09-17 10:07:32 +03:00
modulesManager.close();
2019-04-12 00:58:45 +03:00
LogHelper.info("Save LaunchServer runtime config");
2019-05-15 14:11:22 +03:00
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
if (Launcher.gsonManager.configGson != null) {
2019-04-20 01:03:06 +03:00
Launcher.gsonManager.configGson.toJson(runtime, writer);
2019-04-12 00:58:45 +03:00
} else {
LogHelper.error("Error writing LaunchServer runtime config file. Gson is null");
}
} catch (IOException e) {
LogHelper.error(e);
}
2018-09-17 10:07:32 +03:00
// Print last message before death :(
LogHelper.info("LaunchServer stopped");
}
private void generateConfigIfNotExists(boolean testEnv) throws IOException {
2018-09-17 10:07:32 +03:00
if (IOHelper.isFile(configFile))
2018-09-22 17:33:00 +03:00
return;
2018-09-17 10:07:32 +03:00
// Create new config
LogHelper.info("Creating LaunchServer config");
Config newConfig = new Config();
2019-04-03 16:27:40 +03:00
newConfig.mirrors = new String[]{"http://mirror.gravitlauncher.ml/", "https://mirror.gravit.pro/"};
newConfig.launch4j = new ExeConf();
newConfig.launch4j.enabled = true;
newConfig.launch4j.copyright = "© GravitLauncher Team";
newConfig.launch4j.fileDesc = "GravitLauncher ".concat(Version.getVersion().getVersionString());
newConfig.launch4j.fileVer = Version.getVersion().getVersionString().concat(".").concat(String.valueOf(Version.getVersion().patch));
newConfig.launch4j.internalName = "Launcher";
newConfig.launch4j.trademarks = "This product is licensed under GPLv3";
newConfig.launch4j.txtFileVersion = "%s, build %d";
newConfig.launch4j.txtProductVersion = "%s, build %d";
2018-12-31 10:51:49 +03:00
newConfig.launch4j.productName = "GravitLauncher";
newConfig.launch4j.productVer = newConfig.launch4j.fileVer;
newConfig.env = LauncherConfig.LauncherEnvironment.STD;
newConfig.startScript = JVMHelper.OS_TYPE.equals(JVMHelper.OS.MUSTDIE) ? "." + File.separator + "start.bat" : "." + File.separator + "start.sh";
newConfig.hwidHandler = new AcceptHWIDHandler();
2019-04-03 16:27:40 +03:00
newConfig.auth = new AuthProviderPair[]{new AuthProviderPair(new RejectAuthProvider("Настройте authProvider"),
new MemoryAuthHandler(),
new RequestTextureProvider("http://example.com/skins/%username%.png", "http://example.com/cloaks/%username%.png")
2019-04-03 16:27:40 +03:00
, "std")};
2019-04-13 20:55:01 +03:00
newConfig.auth[0].displayName = "Default";
2019-03-14 19:54:08 +03:00
newConfig.protectHandler = new NoProtectHandler();
if (testEnv) newConfig.permissionsHandler = new DefaultPermissionsHandler();
else newConfig.permissionsHandler = new JsonFilePermissionsHandler();
newConfig.legacyPort = 7240;
newConfig.legacyBindAddress = "0.0.0.0";
newConfig.binaryName = "Launcher";
newConfig.whitelistRejectString = "Вас нет в белом списке";
2019-01-15 06:35:39 +03:00
newConfig.netty = new NettyConfig();
newConfig.netty.fileServerEnabled = true;
2019-05-15 14:11:22 +03:00
newConfig.netty.binds = new NettyBindAddress[]{new NettyBindAddress("0.0.0.0", 9274)};
newConfig.netty.performance = new NettyPerformanceConfig();
newConfig.netty.performance.bossThread = 2;
newConfig.netty.performance.workerThread = 8;
newConfig.launcher = new LauncherConf();
newConfig.launcher.guardType = "no";
newConfig.threadCoreCount = 0; // on your own
2019-01-08 16:50:40 +03:00
newConfig.threadCount = JVMHelper.OPERATING_SYSTEM_MXBEAN.getAvailableProcessors() >= 4 ? JVMHelper.OPERATING_SYSTEM_MXBEAN.getAvailableProcessors() / 2 : JVMHelper.OPERATING_SYSTEM_MXBEAN.getAvailableProcessors();
2019-01-15 06:35:39 +03:00
newConfig.enabledRadon = true;
newConfig.genMappings = true;
newConfig.enabledProGuard = true;
2019-01-09 12:20:31 +03:00
newConfig.stripLineNumbers = true;
newConfig.deleteTempFiles = true;
newConfig.isWarningMissArchJava = true;
newConfig.components = new HashMap<>();
AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent();
authLimiterComponent.rateLimit = 3;
authLimiterComponent.rateLimitMilis = 8000;
authLimiterComponent.message = "Превышен лимит авторизаций";
newConfig.components.put("authLimiter", authLimiterComponent);
2018-09-17 10:07:32 +03:00
// Set server address
String address;
if (testEnv) {
2019-05-15 14:11:22 +03:00
address = "localhost";
newConfig.setProjectName("test");
} else {
2019-05-15 14:11:22 +03:00
System.out.println("LaunchServer address(default: localhost): ");
address = commandHandler.readLine();
System.out.println("LaunchServer projectName: ");
newConfig.setProjectName(commandHandler.readLine());
}
2019-05-15 14:11:22 +03:00
if (address == null || address.isEmpty()) {
LogHelper.error("Address null. Using localhost");
address = "localhost";
}
2019-05-15 14:11:22 +03:00
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
LogHelper.error("ProjectName null. Using MineCraft");
newConfig.projectName = "MineCraft";
}
2019-05-15 14:11:22 +03:00
newConfig.legacyAddress = address;
newConfig.netty.address = "ws://" + address + ":9274/api";
newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
newConfig.netty.sendExceptionEnabled = true;
2018-09-17 10:07:32 +03:00
// Write LaunchServer config
LogHelper.info("Writing LaunchServer config file");
try (BufferedWriter writer = IOHelper.newWriter(configFile)) {
2019-04-20 01:03:06 +03:00
Launcher.gsonManager.configGson.toJson(newConfig, writer);
2018-09-17 10:07:32 +03:00
}
}
public List<ClientProfile> getProfiles() {
2018-09-17 10:07:32 +03:00
return profilesList;
}
public void setProfiles(List<ClientProfile> profilesList) {
this.profilesList = Collections.unmodifiableList(profilesList);
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public SignedObjectHolder<HashedDir> getUpdateDir(String name) {
return updatesDirMap.get(name);
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public Set<Entry<String, SignedObjectHolder<HashedDir>>> getUpdateDirs() {
return updatesDirMap.entrySet();
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public void rebindServerSocket() {
serverSocketHandler.close();
CommonHelper.newThread("Server Socket Thread", false, serverSocketHandler).start();
}
public void rebindNettyServerSocket() {
nettyServerSocketHandler.close();
CommonHelper.newThread("Netty Server Socket Thread", false, nettyServerSocketHandler).start();
}
2018-09-17 10:07:32 +03:00
@Override
public void run() {
if (started.getAndSet(true))
2018-09-22 17:33:00 +03:00
throw new IllegalStateException("LaunchServer has been already started");
2018-09-17 10:07:32 +03:00
// Add shutdown hook, then start LaunchServer
if (!this.testEnv) {
2019-05-15 14:11:22 +03:00
JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::close));
CommonHelper.newThread("Command Thread", true, commandHandler).start();
}
2018-09-17 10:07:32 +03:00
rebindServerSocket();
2019-04-03 16:27:40 +03:00
if (config.netty != null)
rebindNettyServerSocket();
modulesManager.finishModules();
2018-09-17 10:07:32 +03:00
}
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public void syncLauncherBinaries() throws IOException {
LogHelper.info("Syncing launcher binaries");
// Syncing launcher binary
LogHelper.info("Syncing launcher binary file");
if (!launcherBinary.sync()) LogHelper.warning("Missing launcher binary file");
// Syncing launcher EXE binary
LogHelper.info("Syncing launcher EXE binary file");
if (!launcherEXEBinary.sync() && config.launch4j.enabled)
LogHelper.warning("Missing launcher EXE binary file");
}
2018-09-22 17:33:00 +03:00
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public void syncProfilesDir() throws IOException {
LogHelper.info("Syncing profiles dir");
List<ClientProfile> newProfies = new LinkedList<>();
2018-09-17 10:07:32 +03:00
IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false);
// Sort and set new profiles
newProfies.sort(Comparator.comparing(a -> a));
2018-09-17 10:07:32 +03:00
profilesList = Collections.unmodifiableList(newProfies);
}
2018-09-22 17:33:00 +03:00
2018-10-13 11:01:10 +03:00
2018-09-17 10:07:32 +03:00
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
LogHelper.info("Syncing updates dir");
Map<String, SignedObjectHolder<HashedDir>> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(updatesDir)) {
for (final Path updateDir : dirStream) {
2018-09-17 10:07:32 +03:00
if (Files.isHidden(updateDir))
2018-09-22 17:33:00 +03:00
continue; // Skip hidden
2018-09-17 10:07:32 +03:00
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
2019-05-15 14:11:22 +03:00
if (!IOHelper.isFile(updateDir) && Arrays.asList(".jar", ".exe", ".hash").stream().noneMatch(e -> updateDir.toString().endsWith(e)))
LogHelper.warning("Not update dir: '%s'", name);
2018-09-17 10:07:32 +03:00
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
SignedObjectHolder<HashedDir> hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
LogHelper.info("Syncing '%s' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, new SignedObjectHolder<>(updateHDir, privateKey));
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
}
2019-01-15 06:35:39 +03:00
2019-01-04 14:32:16 +03:00
public void restart() {
2019-01-15 06:35:39 +03:00
ProcessBuilder builder = new ProcessBuilder();
if (config.startScript != null) builder.command(Collections.singletonList(config.startScript));
2019-01-15 06:35:39 +03:00
else throw new IllegalArgumentException("Please create start script and link it as startScript in config.");
2019-01-04 14:32:16 +03:00
builder.directory(this.dir.toFile());
builder.inheritIO();
builder.redirectErrorStream(true);
builder.redirectOutput(Redirect.PIPE);
try {
2019-01-15 06:35:39 +03:00
builder.start();
} catch (IOException e) {
LogHelper.error(e);
}
2019-01-04 14:32:16 +03:00
}
2019-04-03 16:27:40 +03:00
public void registerObject(String name, Object object) {
if (object instanceof Reloadable) {
2019-04-03 13:09:53 +03:00
reloadManager.registerReloadable(name, (Reloadable) object);
}
2019-04-03 16:27:40 +03:00
if (object instanceof Reconfigurable) {
2019-04-03 13:09:53 +03:00
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
}
2019-04-03 16:27:40 +03:00
if (object instanceof NeedGarbageCollection) {
2019-04-03 13:09:53 +03:00
GarbageManager.registerNeedGC((NeedGarbageCollection) object);
}
2019-04-03 16:27:40 +03:00
if (object instanceof JsonConfigurable) {
2019-04-03 13:09:53 +03:00
}
}
2019-05-15 14:11:22 +03:00
public void unregisterObject(String name, Object object) {
if (object instanceof Reloadable) {
reloadManager.unregisterReloadable(name);
}
if (object instanceof Reconfigurable) {
reconfigurableManager.unregisterReconfigurable(name);
}
if (object instanceof NeedGarbageCollection) {
GarbageManager.unregisterNeedGC((NeedGarbageCollection) object);
}
if (object instanceof JsonConfigurable) {
}
}
2019-04-03 13:09:53 +03:00
2019-01-15 06:35:39 +03:00
public void fullyRestart() {
restart();
2019-01-04 14:32:16 +03:00
JVMHelper.RUNTIME.exit(0);
2019-01-15 06:35:39 +03:00
}
2018-09-17 10:07:32 +03:00
}