mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-11-15 11:39:11 +03:00
[FEATURE] Поддержка старого протокола перенесена в модуль SashokSupport
This commit is contained in:
parent
94b2fb1424
commit
96c2aac849
14 changed files with 2 additions and 701 deletions
|
@ -7,8 +7,6 @@
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.ProcessBuilder.Redirect;
|
import java.lang.ProcessBuilder.Redirect;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -38,7 +36,6 @@
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.Launcher;
|
||||||
import pro.gravit.launcher.LauncherConfig;
|
import pro.gravit.launcher.LauncherConfig;
|
||||||
import pro.gravit.launcher.NeedGarbageCollection;
|
import pro.gravit.launcher.NeedGarbageCollection;
|
||||||
import pro.gravit.launcher.config.JsonConfigurable;
|
|
||||||
import pro.gravit.launcher.hasher.HashedDir;
|
import pro.gravit.launcher.hasher.HashedDir;
|
||||||
import pro.gravit.launcher.managers.ConfigManager;
|
import pro.gravit.launcher.managers.ConfigManager;
|
||||||
import pro.gravit.launcher.managers.GarbageManager;
|
import pro.gravit.launcher.managers.GarbageManager;
|
||||||
|
@ -67,7 +64,6 @@
|
||||||
import pro.gravit.launchserver.components.Component;
|
import pro.gravit.launchserver.components.Component;
|
||||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||||
import pro.gravit.launchserver.dao.UserService;
|
import pro.gravit.launchserver.dao.UserService;
|
||||||
import pro.gravit.launchserver.legacy.Response;
|
|
||||||
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
|
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
|
||||||
import pro.gravit.launchserver.manangers.MirrorManager;
|
import pro.gravit.launchserver.manangers.MirrorManager;
|
||||||
import pro.gravit.launchserver.manangers.ModulesManager;
|
import pro.gravit.launchserver.manangers.ModulesManager;
|
||||||
|
@ -76,8 +72,6 @@
|
||||||
import pro.gravit.launchserver.manangers.SessionManager;
|
import pro.gravit.launchserver.manangers.SessionManager;
|
||||||
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
||||||
import pro.gravit.launchserver.manangers.hook.BuildHookManager;
|
import pro.gravit.launchserver.manangers.hook.BuildHookManager;
|
||||||
import pro.gravit.launchserver.manangers.hook.SocketHookManager;
|
|
||||||
import pro.gravit.launchserver.socket.ServerSocketHandler;
|
|
||||||
import pro.gravit.launchserver.websocket.NettyServerSocketHandler;
|
import pro.gravit.launchserver.websocket.NettyServerSocketHandler;
|
||||||
import pro.gravit.utils.Version;
|
import pro.gravit.utils.Version;
|
||||||
import pro.gravit.utils.command.CommandHandler;
|
import pro.gravit.utils.command.CommandHandler;
|
||||||
|
@ -197,11 +191,6 @@ public void setEnv(LauncherConfig.LauncherEnvironment env) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SocketAddress getSocketAddress() {
|
|
||||||
return new InetSocketAddress(legacyBindAddress, legacyPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setLegacyAddress(String legacyAddress) {
|
public void setLegacyAddress(String legacyAddress) {
|
||||||
this.legacyAddress = legacyAddress;
|
this.legacyAddress = legacyAddress;
|
||||||
}
|
}
|
||||||
|
@ -452,8 +441,6 @@ public static void main(String... args) throws Throwable {
|
||||||
|
|
||||||
public final SessionManager sessionManager;
|
public final SessionManager sessionManager;
|
||||||
|
|
||||||
public final SocketHookManager socketHookManager;
|
|
||||||
|
|
||||||
public final AuthHookManager authHookManager;
|
public final AuthHookManager authHookManager;
|
||||||
// Server
|
// Server
|
||||||
|
|
||||||
|
@ -477,8 +464,6 @@ public static void main(String... args) throws Throwable {
|
||||||
|
|
||||||
public final CommandHandler commandHandler;
|
public final CommandHandler commandHandler;
|
||||||
|
|
||||||
public final ServerSocketHandler serverSocketHandler;
|
|
||||||
|
|
||||||
public final NettyServerSocketHandler nettyServerSocketHandler;
|
public final NettyServerSocketHandler nettyServerSocketHandler;
|
||||||
|
|
||||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||||
|
@ -511,7 +496,6 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
|
||||||
TextureProvider.registerProviders();
|
TextureProvider.registerProviders();
|
||||||
HWIDHandler.registerHandlers();
|
HWIDHandler.registerHandlers();
|
||||||
PermissionsHandler.registerHandlers();
|
PermissionsHandler.registerHandlers();
|
||||||
Response.registerResponses();
|
|
||||||
Component.registerComponents();
|
Component.registerComponents();
|
||||||
ProtectHandler.registerHandlers();
|
ProtectHandler.registerHandlers();
|
||||||
//LaunchServer.server = this;
|
//LaunchServer.server = this;
|
||||||
|
@ -611,7 +595,6 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
|
||||||
mirrorManager = new MirrorManager();
|
mirrorManager = new MirrorManager();
|
||||||
reloadManager = new ReloadManager();
|
reloadManager = new ReloadManager();
|
||||||
reconfigurableManager = new ReconfigurableManager();
|
reconfigurableManager = new ReconfigurableManager();
|
||||||
socketHookManager = new SocketHookManager();
|
|
||||||
authHookManager = new AuthHookManager();
|
authHookManager = new AuthHookManager();
|
||||||
configManager = new ConfigManager();
|
configManager = new ConfigManager();
|
||||||
userService = new UserService(this);
|
userService = new UserService(this);
|
||||||
|
@ -657,10 +640,6 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
|
||||||
Files.createDirectory(profilesDir);
|
Files.createDirectory(profilesDir);
|
||||||
syncProfilesDir();
|
syncProfilesDir();
|
||||||
|
|
||||||
|
|
||||||
// Set server socket thread
|
|
||||||
serverSocketHandler = new ServerSocketHandler(this, sessionManager);
|
|
||||||
|
|
||||||
// post init modules
|
// post init modules
|
||||||
modulesManager.postInitModules();
|
modulesManager.postInitModules();
|
||||||
if (config.components != null) {
|
if (config.components != null) {
|
||||||
|
@ -708,7 +687,6 @@ public void buildLauncherBinaries() throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
serverSocketHandler.close();
|
|
||||||
|
|
||||||
// Close handlers & providers
|
// Close handlers & providers
|
||||||
config.close();
|
config.close();
|
||||||
|
@ -841,12 +819,6 @@ public Set<Entry<String, SignedObjectHolder<HashedDir>>> getUpdateDirs() {
|
||||||
return updatesDirMap.entrySet();
|
return updatesDirMap.entrySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void rebindServerSocket() {
|
|
||||||
serverSocketHandler.close();
|
|
||||||
CommonHelper.newThread("Server Socket Thread", false, serverSocketHandler).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rebindNettyServerSocket() {
|
public void rebindNettyServerSocket() {
|
||||||
nettyServerSocketHandler.close();
|
nettyServerSocketHandler.close();
|
||||||
CommonHelper.newThread("Netty Server Socket Thread", false, nettyServerSocketHandler).start();
|
CommonHelper.newThread("Netty Server Socket Thread", false, nettyServerSocketHandler).start();
|
||||||
|
@ -862,7 +834,6 @@ public void run() {
|
||||||
JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::close));
|
JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::close));
|
||||||
CommonHelper.newThread("Command Thread", true, commandHandler).start();
|
CommonHelper.newThread("Command Thread", true, commandHandler).start();
|
||||||
}
|
}
|
||||||
rebindServerSocket();
|
|
||||||
if (config.netty != null)
|
if (config.netty != null)
|
||||||
rebindNettyServerSocket();
|
rebindNettyServerSocket();
|
||||||
modulesManager.finishModules();
|
modulesManager.finishModules();
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
package pro.gravit.launchserver.command.basic;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.command.Command;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
public final class LogConnectionsCommand extends Command {
|
|
||||||
public LogConnectionsCommand(LaunchServer server) {
|
|
||||||
super(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getArgsDescription() {
|
|
||||||
return "[true/false]";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsageDescription() {
|
|
||||||
return "Enable or disable logging connections";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invoke(String... args) {
|
|
||||||
boolean newValue;
|
|
||||||
if (args.length >= 1) {
|
|
||||||
newValue = Boolean.parseBoolean(args[0]);
|
|
||||||
server.serverSocketHandler.logConnections = newValue;
|
|
||||||
} else
|
|
||||||
newValue = server.serverSocketHandler.logConnections;
|
|
||||||
LogHelper.subInfo("Log connections: " + newValue);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package pro.gravit.launchserver.command.basic;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.command.Command;
|
|
||||||
|
|
||||||
public final class RebindCommand extends Command {
|
|
||||||
public RebindCommand(LaunchServer server) {
|
|
||||||
super(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getArgsDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsageDescription() {
|
|
||||||
return "Rebind server socket";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invoke(String... args) {
|
|
||||||
server.rebindServerSocket();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,9 +8,7 @@
|
||||||
import pro.gravit.launchserver.command.auth.UnbanCommand;
|
import pro.gravit.launchserver.command.auth.UnbanCommand;
|
||||||
import pro.gravit.launchserver.command.auth.UsernameToUUIDCommand;
|
import pro.gravit.launchserver.command.auth.UsernameToUUIDCommand;
|
||||||
import pro.gravit.launchserver.command.basic.BuildCommand;
|
import pro.gravit.launchserver.command.basic.BuildCommand;
|
||||||
import pro.gravit.launchserver.command.basic.LogConnectionsCommand;
|
|
||||||
import pro.gravit.launchserver.command.basic.ProguardCleanCommand;
|
import pro.gravit.launchserver.command.basic.ProguardCleanCommand;
|
||||||
import pro.gravit.launchserver.command.basic.RebindCommand;
|
|
||||||
import pro.gravit.launchserver.command.basic.RegenProguardDictCommand;
|
import pro.gravit.launchserver.command.basic.RegenProguardDictCommand;
|
||||||
import pro.gravit.launchserver.command.basic.RemoveMappingsProguardCommand;
|
import pro.gravit.launchserver.command.basic.RemoveMappingsProguardCommand;
|
||||||
import pro.gravit.launchserver.command.basic.RestartCommand;
|
import pro.gravit.launchserver.command.basic.RestartCommand;
|
||||||
|
@ -60,14 +58,12 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
|
||||||
basic.registerCommand("build", new BuildCommand(server));
|
basic.registerCommand("build", new BuildCommand(server));
|
||||||
basic.registerCommand("stop", new StopCommand(server));
|
basic.registerCommand("stop", new StopCommand(server));
|
||||||
basic.registerCommand("restart", new RestartCommand(server));
|
basic.registerCommand("restart", new RestartCommand(server));
|
||||||
basic.registerCommand("rebind", new RebindCommand(server));
|
|
||||||
basic.registerCommand("debug", new DebugCommand());
|
basic.registerCommand("debug", new DebugCommand());
|
||||||
basic.registerCommand("clear", new ClearCommand(handler));
|
basic.registerCommand("clear", new ClearCommand(handler));
|
||||||
basic.registerCommand("gc", new GCCommand());
|
basic.registerCommand("gc", new GCCommand());
|
||||||
basic.registerCommand("proguardClean", new ProguardCleanCommand(server));
|
basic.registerCommand("proguardClean", new ProguardCleanCommand(server));
|
||||||
basic.registerCommand("proguardDictRegen", new RegenProguardDictCommand(server));
|
basic.registerCommand("proguardDictRegen", new RegenProguardDictCommand(server));
|
||||||
basic.registerCommand("proguardMappingsRemove", new RemoveMappingsProguardCommand(server));
|
basic.registerCommand("proguardMappingsRemove", new RemoveMappingsProguardCommand(server));
|
||||||
basic.registerCommand("logConnections", new LogConnectionsCommand(server));
|
|
||||||
basic.registerCommand("loadModule", new LoadModuleCommand(server));
|
basic.registerCommand("loadModule", new LoadModuleCommand(server));
|
||||||
basic.registerCommand("modules", new ModulesCommand(server));
|
basic.registerCommand("modules", new ModulesCommand(server));
|
||||||
basic.registerCommand("test", new TestCommand(server));
|
basic.registerCommand("test", new TestCommand(server));
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package pro.gravit.launchserver.legacy;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.serialize.HInput;
|
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
|
||||||
import pro.gravit.launcher.serialize.SerializeLimits;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
|
||||||
|
|
||||||
public final class PingResponse extends Response {
|
|
||||||
public PingResponse(LaunchServer server, long id, HInput input, HOutput output, String ip, Client clientData) {
|
|
||||||
super(server, id, input, output, ip, clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reply() throws IOException {
|
|
||||||
output.writeUnsignedByte(SerializeLimits.EXPECTED_BYTE);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package pro.gravit.launchserver.legacy;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.request.RequestException;
|
|
||||||
import pro.gravit.launcher.request.RequestType;
|
|
||||||
import pro.gravit.launcher.serialize.HInput;
|
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.legacy.update.LauncherResponse;
|
|
||||||
import pro.gravit.launchserver.legacy.update.LegacyLauncherResponse;
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
public abstract class Response {
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface Factory<R> {
|
|
||||||
|
|
||||||
Response newResponse(LaunchServer server, long id, HInput input, HOutput output, String ip, Client clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Map<Integer, Factory<?>> RESPONSES = new ConcurrentHashMap<>(8);
|
|
||||||
|
|
||||||
public static Response getResponse(int type, LaunchServer server, long session, HInput input, HOutput output, String ip, Client clientData) {
|
|
||||||
return RESPONSES.get(type).newResponse(server, session, input, output, ip, clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void registerResponse(int type, Factory<?> factory) {
|
|
||||||
RESPONSES.put(type, factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void registerResponses() {
|
|
||||||
registerResponse(RequestType.PING.getNumber(), PingResponse::new);
|
|
||||||
registerResponse(RequestType.LEGACYLAUNCHER.getNumber(), LegacyLauncherResponse::new);
|
|
||||||
registerResponse(RequestType.LAUNCHER.getNumber(), LauncherResponse::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void requestError(String message) throws RequestException {
|
|
||||||
throw new RequestException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected final LaunchServer server;
|
|
||||||
|
|
||||||
|
|
||||||
protected final HInput input;
|
|
||||||
|
|
||||||
|
|
||||||
protected final HOutput output;
|
|
||||||
|
|
||||||
|
|
||||||
protected final String ip;
|
|
||||||
|
|
||||||
protected final Client clientData;
|
|
||||||
|
|
||||||
|
|
||||||
protected final long session;
|
|
||||||
|
|
||||||
protected Response(LaunchServer server, long session, HInput input, HOutput output, String ip, Client clientData) {
|
|
||||||
this.server = server;
|
|
||||||
this.input = input;
|
|
||||||
this.output = output;
|
|
||||||
this.ip = ip;
|
|
||||||
this.session = session;
|
|
||||||
this.clientData = clientData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected final void debug(String message) {
|
|
||||||
LogHelper.subDebug("#%d %s", session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected final void debug(String message, Object... args) {
|
|
||||||
debug(String.format(message, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract void reply() throws Exception;
|
|
||||||
|
|
||||||
|
|
||||||
protected static void writeNoError(HOutput output) throws IOException {
|
|
||||||
output.writeString("", 0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package pro.gravit.launchserver.legacy.update;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.serialize.HInput;
|
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
|
||||||
import pro.gravit.launcher.serialize.SerializeLimits;
|
|
||||||
import pro.gravit.launcher.serialize.signed.DigestBytesHolder;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.legacy.Response;
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
|
||||||
|
|
||||||
public final class LauncherResponse extends Response {
|
|
||||||
|
|
||||||
public LauncherResponse(LaunchServer server, long session, HInput input, HOutput output, String ip, Client clientData) {
|
|
||||||
super(server, session, input, output, ip, clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reply() throws IOException {
|
|
||||||
// Resolve launcher binary
|
|
||||||
DigestBytesHolder bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary).getBytes();
|
|
||||||
if (bytes == null) {
|
|
||||||
requestError("Missing launcher binary");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
byte[] digest = input.readByteArray(SerializeLimits.MAX_DIGEST);
|
|
||||||
if (!Arrays.equals(bytes.getDigest(), digest)) {
|
|
||||||
writeNoError(output);
|
|
||||||
output.writeBoolean(true);
|
|
||||||
output.writeByteArray(bytes.getBytes(), 0);
|
|
||||||
clientData.checkSign = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeNoError(output);
|
|
||||||
output.writeBoolean(false);
|
|
||||||
clientData.checkSign = true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package pro.gravit.launchserver.legacy.update;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.serialize.HInput;
|
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.binary.LauncherBinary;
|
|
||||||
import pro.gravit.launchserver.legacy.Response;
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
public final class LegacyLauncherResponse extends Response {
|
|
||||||
|
|
||||||
public LegacyLauncherResponse(LaunchServer server, long session, HInput input, HOutput output, String ip, Client clientData) {
|
|
||||||
super(server, session, input, output, ip, clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reply() throws IOException {
|
|
||||||
// Resolve launcher binary
|
|
||||||
LauncherBinary bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary);
|
|
||||||
if (bytes == null) {
|
|
||||||
requestError("Missing launcher binary");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeNoError(output);
|
|
||||||
|
|
||||||
// Update launcher binary
|
|
||||||
output.writeByteArray(bytes.getSign(), -SecurityHelper.RSA_KEY_LENGTH);
|
|
||||||
output.flush();
|
|
||||||
if (input.readBoolean()) {
|
|
||||||
output.writeByteArray(bytes.getBytes().getBytes(), 0);
|
|
||||||
return; // Launcher will be restarted
|
|
||||||
}
|
|
||||||
requestError("You must update");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
package pro.gravit.launchserver.manangers.hook;
|
|
||||||
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.request.RequestException;
|
|
||||||
import pro.gravit.launchserver.socket.SocketContext;
|
|
||||||
|
|
||||||
public class SocketHookManager {
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface SocketPreHook {
|
|
||||||
boolean preHook(SocketContext context); //Вернуть true если необходимо продолжть обработку, false если остановить обработку
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface SocketPostHook {
|
|
||||||
void postHook(SocketContext context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface SocketErrorHook {
|
|
||||||
boolean errorHook(SocketContext context, RequestException e); //Вернуть true если необходимо продолжть обработку, false если остановить обработку
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface SocketFatalErrorHook {
|
|
||||||
boolean fatalErrorHook(Socket socket, Exception e); //Вернуть true если необходимо продолжть обработку, false если остановить обработку
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<SocketPostHook> POST_HOOKS;
|
|
||||||
private Set<SocketPreHook> PRE_HOOKS;
|
|
||||||
private Set<SocketErrorHook> ERROR_HOOKS;
|
|
||||||
private Set<SocketFatalErrorHook> FATALERROR_HOOKS;
|
|
||||||
|
|
||||||
public void registerPostHook(SocketPostHook hook) {
|
|
||||||
if (POST_HOOKS == null) POST_HOOKS = new HashSet<>();
|
|
||||||
POST_HOOKS.add(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerPreHook(SocketPreHook hook) {
|
|
||||||
if (PRE_HOOKS == null) PRE_HOOKS = new HashSet<>();
|
|
||||||
PRE_HOOKS.add(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerErrorHook(SocketErrorHook hook) {
|
|
||||||
if (ERROR_HOOKS == null) ERROR_HOOKS = new HashSet<>();
|
|
||||||
ERROR_HOOKS.add(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerFatalErrorHook(SocketFatalErrorHook hook) {
|
|
||||||
if (FATALERROR_HOOKS == null) FATALERROR_HOOKS = new HashSet<>();
|
|
||||||
FATALERROR_HOOKS.add(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean preHook(SocketContext context) {
|
|
||||||
if (PRE_HOOKS == null) return true;
|
|
||||||
for (SocketPreHook preHook : PRE_HOOKS) {
|
|
||||||
if (!preHook.preHook(context)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void postHook(SocketContext context) {
|
|
||||||
if (POST_HOOKS == null) return;
|
|
||||||
for (SocketPostHook postHook : POST_HOOKS) {
|
|
||||||
postHook.postHook(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean errorHook(SocketContext context, RequestException e) {
|
|
||||||
if (ERROR_HOOKS == null) return true;
|
|
||||||
for (SocketErrorHook errorHook : ERROR_HOOKS) {
|
|
||||||
if (!errorHook.errorHook(context, e)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean fatalErrorHook(Socket socket, Exception e) {
|
|
||||||
if (FATALERROR_HOOKS == null) return true;
|
|
||||||
for (SocketFatalErrorHook errorHook : FATALERROR_HOOKS) {
|
|
||||||
if (!errorHook.fatalErrorHook(socket, e)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package pro.gravit.launchserver.socket;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketException;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
|
||||||
import pro.gravit.launcher.request.RequestException;
|
|
||||||
import pro.gravit.launcher.serialize.HInput;
|
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.legacy.Response;
|
|
||||||
import pro.gravit.launchserver.manangers.SessionManager;
|
|
||||||
import pro.gravit.launchserver.manangers.hook.SocketHookManager;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
public final class ResponseThread implements Runnable {
|
|
||||||
class Handshake {
|
|
||||||
int type;
|
|
||||||
long session;
|
|
||||||
|
|
||||||
public Handshake(int type, long session) {
|
|
||||||
this.type = type;
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final LaunchServer server;
|
|
||||||
private final Socket socket;
|
|
||||||
|
|
||||||
private final SessionManager sessions;
|
|
||||||
private final SocketHookManager socketHookManager;
|
|
||||||
|
|
||||||
public ResponseThread(LaunchServer server, long id, Socket socket, SessionManager sessionManager, SocketHookManager socketHookManager) throws SocketException {
|
|
||||||
this.server = server;
|
|
||||||
this.socket = socket;
|
|
||||||
sessions = sessionManager;
|
|
||||||
this.socketHookManager = socketHookManager;
|
|
||||||
// Fix socket flags
|
|
||||||
IOHelper.setSocketFlags(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Handshake readHandshake(HInput input, HOutput output) throws IOException {
|
|
||||||
boolean legacy = false;
|
|
||||||
long session = 0;
|
|
||||||
// Verify magic number
|
|
||||||
int magicNumber = input.readInt();
|
|
||||||
if (magicNumber != Launcher.PROTOCOL_MAGIC)
|
|
||||||
if (magicNumber == Launcher.PROTOCOL_MAGIC_LEGACY - 1) { // Previous launcher protocol
|
|
||||||
session = 0;
|
|
||||||
legacy = true;
|
|
||||||
} else if (magicNumber == ServerSocketHandler.LEGACY_LAUNCHER_MAGIC) { // Previous launcher protocol
|
|
||||||
session = 0;
|
|
||||||
legacy = true;
|
|
||||||
} else if (magicNumber == Launcher.PROTOCOL_MAGIC_LEGACY) {
|
|
||||||
|
|
||||||
} else
|
|
||||||
throw new IOException("Invalid Handshake");
|
|
||||||
// Verify key modulus
|
|
||||||
BigInteger keyModulus = input.readBigInteger(SecurityHelper.RSA_KEY_LENGTH + 1);
|
|
||||||
if (!legacy) {
|
|
||||||
session = input.readLong();
|
|
||||||
sessions.updateClient(session);
|
|
||||||
}
|
|
||||||
if (!keyModulus.equals(server.privateKey.getModulus())) {
|
|
||||||
output.writeBoolean(false);
|
|
||||||
throw new IOException(String.format("#%d Key modulus mismatch", session));
|
|
||||||
}
|
|
||||||
// Read request type
|
|
||||||
int type = input.readVarInt();
|
|
||||||
if (!server.serverSocketHandler.onHandshake(session, type)) {
|
|
||||||
output.writeBoolean(false);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protocol successfully verified
|
|
||||||
output.writeBoolean(true);
|
|
||||||
output.flush();
|
|
||||||
return new Handshake(type, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void respond(Integer type, HInput input, HOutput output, long session, String ip, Client clientData) throws Exception {
|
|
||||||
if (server.serverSocketHandler.logConnections)
|
|
||||||
LogHelper.info("Connection #%d from %s", session, ip);
|
|
||||||
|
|
||||||
// Choose response based on type
|
|
||||||
Response response = Response.getResponse(type, server, session, input, output, ip, clientData);
|
|
||||||
|
|
||||||
// Reply
|
|
||||||
response.reply();
|
|
||||||
LogHelper.subDebug("#%d Replied", session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!server.serverSocketHandler.logConnections)
|
|
||||||
LogHelper.debug("Connection from %s", IOHelper.getIP(socket.getRemoteSocketAddress()));
|
|
||||||
|
|
||||||
// Process connection
|
|
||||||
boolean cancelled = false;
|
|
||||||
Exception savedError = null;
|
|
||||||
try (HInput input = new HInput(socket.getInputStream());
|
|
||||||
HOutput output = new HOutput(socket.getOutputStream())) {
|
|
||||||
Handshake handshake = readHandshake(input, output);
|
|
||||||
if (handshake == null) { // Not accepted
|
|
||||||
cancelled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SocketContext context = new SocketContext();
|
|
||||||
context.input = input;
|
|
||||||
context.output = output;
|
|
||||||
context.ip = IOHelper.getIP(socket.getRemoteSocketAddress());
|
|
||||||
context.session = handshake.session;
|
|
||||||
context.type = handshake.type;
|
|
||||||
Client clientData = server.sessionManager.getOrNewClient(context.session);
|
|
||||||
context.client = clientData;
|
|
||||||
|
|
||||||
// Start response
|
|
||||||
if (socketHookManager.preHook(context)) {
|
|
||||||
try {
|
|
||||||
respond(handshake.type, input, output, handshake.session, context.ip, clientData);
|
|
||||||
socketHookManager.postHook(context);
|
|
||||||
} catch (RequestException e) {
|
|
||||||
if (server.socketHookManager.errorHook(context, e)) {
|
|
||||||
LogHelper.subDebug(String.format("#%d Request error: %s", handshake.session, e.getMessage()));
|
|
||||||
if (e.getMessage() == null) LogHelper.error(e);
|
|
||||||
output.writeString(e.getMessage(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
savedError = e;
|
|
||||||
if (server.socketHookManager.fatalErrorHook(socket, e))
|
|
||||||
LogHelper.error(e);
|
|
||||||
} finally {
|
|
||||||
IOHelper.close(socket);
|
|
||||||
if (!cancelled)
|
|
||||||
server.serverSocketHandler.onDisconnect(savedError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
package pro.gravit.launchserver.socket;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.SynchronousQueue;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
|
||||||
import pro.gravit.launcher.managers.GarbageManager;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.manangers.SessionManager;
|
|
||||||
import pro.gravit.utils.helper.CommonHelper;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
|
||||||
|
|
||||||
public final class ServerSocketHandler implements Runnable, AutoCloseable {
|
|
||||||
public interface Listener {
|
|
||||||
|
|
||||||
boolean onConnect(InetAddress address);
|
|
||||||
|
|
||||||
|
|
||||||
void onDisconnect(Exception e);
|
|
||||||
|
|
||||||
|
|
||||||
boolean onHandshake(long session, int type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ThreadFactory THREAD_FACTORY = r -> CommonHelper.newThread("Network Thread", true, r);
|
|
||||||
|
|
||||||
|
|
||||||
public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections");
|
|
||||||
// Instance
|
|
||||||
private final LaunchServer server;
|
|
||||||
private final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
|
||||||
private final ExecutorService threadPool;
|
|
||||||
|
|
||||||
public final SessionManager sessionManager;
|
|
||||||
private final AtomicLong idCounter = new AtomicLong(0L);
|
|
||||||
public static int LEGACY_LAUNCHER_MAGIC = Launcher.PROTOCOL_MAGIC_LEGACY - 2;
|
|
||||||
|
|
||||||
private volatile Listener listener;
|
|
||||||
|
|
||||||
public ServerSocketHandler(LaunchServer server) {
|
|
||||||
this(server, new SessionManager());
|
|
||||||
GarbageManager.registerNeedGC(sessionManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerSocketHandler(LaunchServer server, SessionManager sessionManager) {
|
|
||||||
this.server = server;
|
|
||||||
threadPool = new ThreadPoolExecutor(server.config.threadCoreCount, Integer.MAX_VALUE,
|
|
||||||
server.config.threadCount, TimeUnit.SECONDS,
|
|
||||||
new SynchronousQueue<>(),
|
|
||||||
THREAD_FACTORY);
|
|
||||||
this.sessionManager = sessionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
ServerSocket socket = serverSocket.getAndSet(null);
|
|
||||||
if (socket != null) {
|
|
||||||
LogHelper.info("Closing server socket listener");
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogHelper.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ void onDisconnect(Exception e) {
|
|
||||||
if (listener != null)
|
|
||||||
listener.onDisconnect(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ boolean onHandshake(long session, int type) {
|
|
||||||
return listener == null || listener.onHandshake(session, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
LogHelper.info("Starting server socket thread");
|
|
||||||
try (ServerSocket serverSocket = new ServerSocket()) {
|
|
||||||
if (!this.serverSocket.compareAndSet(null, serverSocket))
|
|
||||||
throw new IllegalStateException("Previous socket wasn't closed");
|
|
||||||
|
|
||||||
// Set socket params
|
|
||||||
serverSocket.setReuseAddress(true);
|
|
||||||
serverSocket.setPerformancePreferences(1, 0, 2);
|
|
||||||
//serverSocket.setReceiveBufferSize(0x10000);
|
|
||||||
serverSocket.bind(server.config.getSocketAddress());
|
|
||||||
LogHelper.info("Server socket thread successfully started");
|
|
||||||
|
|
||||||
// Listen for incoming connections
|
|
||||||
while (serverSocket.isBound()) {
|
|
||||||
Socket socket = serverSocket.accept();
|
|
||||||
|
|
||||||
// Invoke pre-connect listener
|
|
||||||
long id = idCounter.incrementAndGet();
|
|
||||||
if (listener != null && !listener.onConnect(socket.getInetAddress())) {
|
|
||||||
socket.close();
|
|
||||||
continue; // Listener didn't accepted this connection
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Reply in separate thread
|
|
||||||
threadPool.execute(new ResponseThread(server, id, socket, sessionManager, server.socketHookManager));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignore error after close/rebind
|
|
||||||
if (serverSocket.get() != null)
|
|
||||||
LogHelper.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setListener(Listener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package pro.gravit.launchserver.socket;
|
|
||||||
|
|
||||||
import pro.gravit.launcher.serialize.HInput;
|
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
|
||||||
|
|
||||||
public class SocketContext {
|
|
||||||
public HInput input;
|
|
||||||
public HOutput output;
|
|
||||||
public long session;
|
|
||||||
public String ip;
|
|
||||||
public Integer type;
|
|
||||||
public Client client;
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
package pro.gravit.launchserver.websocket;
|
package pro.gravit.launchserver.websocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
@ -12,12 +10,7 @@
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.UnrecoverableKeyException;
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
@ -27,9 +20,7 @@
|
||||||
import pro.gravit.launcher.ssl.LauncherKeyStore;
|
import pro.gravit.launcher.ssl.LauncherKeyStore;
|
||||||
import pro.gravit.launcher.ssl.LauncherTrustManager;
|
import pro.gravit.launcher.ssl.LauncherTrustManager;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.legacy.Response;
|
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
import pro.gravit.utils.helper.VerifyHelper;
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "rawtypes"})
|
@SuppressWarnings({"unused", "rawtypes"})
|
||||||
public final class NettyServerSocketHandler implements Runnable, AutoCloseable {
|
public final class NettyServerSocketHandler implements Runnable, AutoCloseable {
|
||||||
|
@ -39,13 +30,8 @@ public final class NettyServerSocketHandler implements Runnable, AutoCloseable {
|
||||||
|
|
||||||
public LauncherNettyServer nettyServer;
|
public LauncherNettyServer nettyServer;
|
||||||
|
|
||||||
private final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
|
|
||||||
|
|
||||||
// API
|
// API
|
||||||
private final Map<String, Response.Factory> customResponses = new ConcurrentHashMap<>(2);
|
|
||||||
private final AtomicLong idCounter = new AtomicLong(0L);
|
|
||||||
private Set<Socket> sockets;
|
private Set<Socket> sockets;
|
||||||
private volatile Listener listener;
|
|
||||||
|
|
||||||
private transient final LaunchServer server;
|
private transient final LaunchServer server;
|
||||||
|
|
||||||
|
@ -55,15 +41,7 @@ public NettyServerSocketHandler(LaunchServer server) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
ServerSocket socket = serverSocket.getAndSet(null);
|
//TODO: Close Impl
|
||||||
if (socket != null) {
|
|
||||||
LogHelper.info("Closing server socket listener");
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogHelper.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SSLContext SSLContextInit() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, KeyManagementException, IOException, CertificateException {
|
public SSLContext SSLContextInit() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, KeyManagementException, IOException, CertificateException {
|
||||||
|
@ -149,37 +127,4 @@ public void run() {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void registerCustomResponse(String name, Response.Factory factory) {
|
|
||||||
VerifyHelper.verifyIDName(name);
|
|
||||||
VerifyHelper.putIfAbsent(customResponses, name, Objects.requireNonNull(factory, "factory"),
|
|
||||||
String.format("Custom response has been already registered: '%s'", name));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setListener(Listener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ void onDisconnect(long id, Exception e) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onDisconnect(id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ boolean onHandshake(long id, Integer type) {
|
|
||||||
return listener == null || listener.onHandshake(id, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
|
|
||||||
boolean onConnect(long id, InetAddress address);
|
|
||||||
|
|
||||||
|
|
||||||
void onDisconnect(long id, Exception e);
|
|
||||||
|
|
||||||
|
|
||||||
boolean onHandshake(long id, Integer type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
2
modules
2
modules
|
@ -1 +1 @@
|
||||||
Subproject commit b9c9d2675c081a8af2c54b00f5907e3c82f73c4d
|
Subproject commit 2c4f44c30106bfb83334d28ecb5aebbd58a62c08
|
Loading…
Reference in a new issue