diff --git a/bridge/README.MD b/bridge/README.MD new file mode 100644 index 0000000..49ba934 --- /dev/null +++ b/bridge/README.MD @@ -0,0 +1,11 @@ +# Bridge + +Данный плагин служит "мостом" между сервером **Spigot** и **ASys** + +## Настройка + +Настройки хранятся в файле `config.yml`. + +`port` - порт для подключения к **Zond**. + +`second` - раз в сколько секунд отправлять пинг-сигнал \ No newline at end of file diff --git a/bridge/build.gradle b/bridge/build.gradle index 656a8ed..71993fe 100644 --- a/bridge/build.gradle +++ b/bridge/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.7.1-SNAPSHOT' +version = '0.8.4' repositories { maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' } @@ -22,6 +22,7 @@ compileJava { jar { dependsOn configurations.include + baseName = project.group + '.' + project.name from { configurations.include.collect { it.isDirectory() ? it : zipTree(it) } } } diff --git a/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java b/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java index 53b856e..cdfbbc8 100644 --- a/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java +++ b/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java @@ -8,22 +8,21 @@ import asys.bridge.client.AbstractBridge; import asys.bridge.client.IBridge; import asys.bridge.client.IConfig; import asys.bridge.client.ILogger; -import io.netty.channel.Channel; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.Logger; +import asys.mcsmanager.packets.CS_Ping; public class BridgeBukkitImpl extends AbstractBridge implements IBridge { private BridgePlugin plugin; private LoggerBukkitImpl logger; private ConfigBukkitImpl configBukkit; - private BridgeLoggerAppender loggerAppender; + private int scheduleId; + private int second; - public BridgeBukkitImpl(BridgePlugin plugin) { + BridgeBukkitImpl(BridgePlugin plugin) { this.plugin = plugin; this.logger = new LoggerBukkitImpl(plugin.getLogger()); this.configBukkit = new ConfigBukkitImpl(plugin.getConfig()); - ((Logger) LogManager.getRootLogger()).addAppender(loggerAppender = new BridgeLoggerAppender()); + this.second = this.configBukkit.getInt("second"); } @Override @@ -50,7 +49,17 @@ public class BridgeBukkitImpl extends AbstractBridge implements IBridge { } @Override - public void setChannelFromConsoleMessages(Channel channel) { - this.loggerAppender.setChannel(channel); + public void startPing() { + scheduleId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin,() -> { + this.client.sendPacket(new CS_Ping( + System.currentTimeMillis(), + 20.0D, //FIXME fake tps + getCountOnlinePlayers())); + }, 0, 20L * second); + } + + @Override + public void stopPing() { + plugin.getServer().getScheduler().cancelTask(scheduleId); } } diff --git a/bridge/src/main/java/asys/bridge/bukkit/BridgeLoggerAppender.java b/bridge/src/main/java/asys/bridge/bukkit/BridgeLoggerAppender.java deleted file mode 100644 index 2ab12b2..0000000 --- a/bridge/src/main/java/asys/bridge/bukkit/BridgeLoggerAppender.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * DmitriyMX - * 2017-05-17 - */ -package asys.bridge.bukkit; - -import asys.mcsmanager.packets.CS_ConsoleMessage; -import io.netty.channel.Channel; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractAppender; - -public class BridgeLoggerAppender extends AbstractAppender { - private Channel channel; - - BridgeLoggerAppender() { - super("ASysBridge", null, null); - super.start(); - } - - @Override - public void append(LogEvent event) { - if (channel == null) return; - - long timeMillis = event.getMillis(); - int intLevel = event.getLevel().intLevel(); - String loggerName = event.getLoggerName(); - String message = event.getMessage().getFormattedMessage(); - - CS_ConsoleMessage messagePkg = new CS_ConsoleMessage( - timeMillis, - intLevel, - loggerName, - message - ); - - channel.writeAndFlush(messagePkg); - } - - void setChannel(Channel channel) { - this.channel = channel; - } -} diff --git a/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java b/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java index 4d73b8f..d417b5e 100644 --- a/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java +++ b/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java @@ -18,8 +18,14 @@ public class BridgePlugin extends JavaPlugin { this.bridgeBukkit.startReconnect(); } + @Override + public void onEnable() { + this.bridgeBukkit.startPing(); + } + @Override public void onDisable() { + this.bridgeBukkit.sendCorrectShutdownPacket(); this.bridgeBukkit.setNeedReconnect(false); this.bridgeBukkit.stopPing(); this.bridgeBukkit.stopReconnect(); diff --git a/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java b/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java index be56291..e85ad70 100644 --- a/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java +++ b/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java @@ -10,7 +10,7 @@ import org.bukkit.configuration.file.FileConfiguration; public class ConfigBukkitImpl implements IConfig { private FileConfiguration fileConfiguration; - public ConfigBukkitImpl(FileConfiguration fileConfiguration) { + ConfigBukkitImpl(FileConfiguration fileConfiguration) { this.fileConfiguration = fileConfiguration; } diff --git a/bridge/src/main/java/asys/bridge/client/AbstractBridge.java b/bridge/src/main/java/asys/bridge/client/AbstractBridge.java index 0cf592b..335f010 100644 --- a/bridge/src/main/java/asys/bridge/client/AbstractBridge.java +++ b/bridge/src/main/java/asys/bridge/client/AbstractBridge.java @@ -5,11 +5,11 @@ package asys.bridge.client; import asys.bridge.bukkit.TaskTicker; +import asys.mcsmanager.packets.CS_CorrectShutdown; import asys.mcsmanager.packets.CS_Ping; -import io.netty.channel.Channel; public abstract class AbstractBridge implements IBridge { - private Client client; + protected Client client; private TaskTicker connectTicker, pingTicker; private int tryConnect = 0; private boolean needReconnect = true; @@ -19,8 +19,8 @@ public abstract class AbstractBridge implements IBridge { client = new Client(this); connectTicker = new TaskTicker().setStepTimeMs(5000L); connectTicker.setTask(() -> { - getLogger().info(String.format("Connect(%d) to ASys...", ++tryConnect)); - client.connect(getConfig().getString("host"), getConfig().getInt("port")); + getLogger().info(String.format("Connect(%d) to Zond...", ++tryConnect)); + client.connect(getConfig().getInt("port")); if (client.isConnected()) { stopReconnect(); } else { @@ -39,6 +39,13 @@ public abstract class AbstractBridge implements IBridge { this.needReconnect = value; } + @Override + public void sendCorrectShutdownPacket() { + if (client != null && client.isConnected()) { + client.sendPacket(new CS_CorrectShutdown()); + } + } + @Override public void stopReconnect() { if (connectTicker != null) { @@ -48,24 +55,13 @@ public abstract class AbstractBridge implements IBridge { } @Override - public void startPing(Channel channel) { + public void startPing() { pingTicker = new TaskTicker().setStepTimeMs(5000L); pingTicker.setTask(() -> { - channel.write(new CS_Ping( + client.sendPacket(new CS_Ping( System.currentTimeMillis(), 20.0D, //FIXME fake tps - getCountOnlinePlayers() - )); - if (channel.isWritable()) { - channel.flush(); - } else { - getLogger().warn("Lost connection!"); - channel.close(); - stopPing(); - - getLogger().warn("Try reconnect..."); - startReconnect(); - } + getCountOnlinePlayers())); }).start(); } diff --git a/bridge/src/main/java/asys/bridge/client/Client.java b/bridge/src/main/java/asys/bridge/client/Client.java index b9d354b..084e816 100644 --- a/bridge/src/main/java/asys/bridge/client/Client.java +++ b/bridge/src/main/java/asys/bridge/client/Client.java @@ -4,6 +4,7 @@ */ package asys.bridge.client; +import asys.mcsmanager.packets.Packet; import asys.mcsmanager.packets.codec.PacketDecoder; import asys.mcsmanager.packets.codec.PacketEncoder; import asys.mcsmanager.packets.codec.PacketHandler; @@ -21,25 +22,25 @@ public class Client { private ChannelFuture channelFuture; private IBridge bridge; - public Client(IBridge bridge) { + Client(IBridge bridge) { this.bridge = bridge; } - public void connect(String host, int port) { + void connect(int port) { if (group == null || bootstrap == null) { group = new NioEventLoopGroup(); bootstrap = createBootstrap(); } - channelFuture = bootstrap.connect(host, port); + channelFuture = bootstrap.connect("127.0.0.1", port); channelFuture.awaitUninterruptibly(5000); } - public boolean isConnected() { + boolean isConnected() { return (channelFuture != null && channelFuture.isSuccess()); } - public void disconnect() { + void disconnect() { group.shutdownGracefully(); } @@ -65,4 +66,10 @@ public class Client { } }; } + + public void sendPacket(Packet packet) { + if (isConnected()) { + channelFuture.channel().writeAndFlush(packet); + } + } } diff --git a/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java b/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java index 5aa5213..5d1a25d 100644 --- a/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java +++ b/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java @@ -17,27 +17,20 @@ import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { - private static final BiMap> handshakePackets = ImmutableBiMap.of( - 1, CS_Handshake.class, - 2, SC_HandshakeResult.class - ); - private static Map, IPacketHandler> handshakeHandlers; + private static Map, IPacketHandler> knownHandlers; private static final BiMap> knownPackets = ImmutableBiMap.of( 3, CS_Ping.class, - 4, CS_ConsoleMessage.class, 5, SC_Command.class, - 6, SC_ToggleSendMessages.class + 6, CS_CorrectShutdown.class ); private IBridge bridge; ClientPacketHandler(IBridge bridge) { this.bridge = bridge; - if (handshakeHandlers == null) { - handshakeHandlers = ImmutableMap.of( - SC_HandshakeResult.class, this, - SC_Command.class, this, - SC_ToggleSendMessages.class, this + if (knownHandlers == null) { + knownHandlers = ImmutableMap.of( + SC_Command.class, this ); } } @@ -45,14 +38,8 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements @Override public void channelActive(ChannelHandlerContext context) throws Exception { bridge.getLogger().info("channelActive"); - context.channel().attr(KNOWN_PACKETS).set(handshakePackets); - context.channel().attr(KNOWN_HANDLERS).set(handshakeHandlers); - - CS_Handshake packet = new CS_Handshake( - bridge.getConfig().getString("clientId"), - bridge.getConfig().getString("passcode")); - bridge.getLogger().info("send Handshake packet..."); - context.channel().writeAndFlush(packet); + context.channel().attr(KNOWN_PACKETS).set(knownPackets); + context.channel().attr(KNOWN_HANDLERS).set(knownHandlers); super.channelActive(context); } @@ -71,24 +58,8 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements @Override public void handle(Packet packet, ChannelHandlerContext context) { bridge.getLogger().info("handle : " + packet.getClass().getSimpleName()); - if (packet instanceof SC_HandshakeResult) { - handleHandshakeResult((SC_HandshakeResult) packet, context); - } else if (packet instanceof SC_Command) { + if (packet instanceof SC_Command) { handleCommand((SC_Command) packet); - } else if (packet instanceof SC_ToggleSendMessages) { - handleToggleSendMessages((SC_ToggleSendMessages) packet, context); - } - } - - private void handleHandshakeResult(SC_HandshakeResult packet, ChannelHandlerContext context) { - if (packet.getErrorCode() != 0) { - bridge.getLogger().error( - String.format("Handshake: #%d %s", packet.getErrorCode(), packet.getMessage())); - bridge.setNeedReconnect(false); - } else { - context.channel().attr(KNOWN_PACKETS).set(knownPackets); - bridge.getLogger().info("Handshake: OK"); - bridge.startPing(context.channel()); } } @@ -96,8 +67,4 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements bridge.getLogger().info("Command: " + packet.getCommand()); bridge.dispatchCommand(packet.getCommand()); } - - private void handleToggleSendMessages(SC_ToggleSendMessages packet, ChannelHandlerContext context) { - bridge.setChannelFromConsoleMessages(packet.isNeedSend() ? context.channel() : null); - } } diff --git a/bridge/src/main/java/asys/bridge/client/IBridge.java b/bridge/src/main/java/asys/bridge/client/IBridge.java index f0944a8..22a1308 100644 --- a/bridge/src/main/java/asys/bridge/client/IBridge.java +++ b/bridge/src/main/java/asys/bridge/client/IBridge.java @@ -4,19 +4,17 @@ */ package asys.bridge.client; -import io.netty.channel.Channel; - public interface IBridge { ILogger getLogger(); IConfig getConfig(); int getCountOnlinePlayers(); void dispatchCommand(String command); - void setChannelFromConsoleMessages(Channel channel); void startReconnect(); boolean isNeedReconnect(); void setNeedReconnect(boolean value); + void sendCorrectShutdownPacket(); void stopReconnect(); - void startPing(Channel channel); + void startPing(); void stopPing(); void disconnect(); } diff --git a/bridge/src/main/resources/config.yml b/bridge/src/main/resources/config.yml index 5d80f21..66f3bc3 100644 --- a/bridge/src/main/resources/config.yml +++ b/bridge/src/main/resources/config.yml @@ -1,4 +1,2 @@ -clientId: SpigotServer0 -host: 127.0.0.1 -port: 8779 -passcode: testpassphrase \ No newline at end of file +port: 8710 +second: 5 \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8a0e739..9d0cf9b 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ subprojects { } subprojects { - if (it.name != 'bridge' || !it.name.startsWith('lib')) { + if (it.name != 'bridge' && it.name != 'zond' && !it.name.startsWith('lib')) { ext { slf4jVersion = '1.7.21' } diff --git a/libprotocol/build.gradle b/libprotocol/build.gradle index 1e11dd2..2783c19 100644 --- a/libprotocol/build.gradle +++ b/libprotocol/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.5-SNAPSHOT' +version = '0.6-SNAPSHOT' task jar(type: Jar, overwrite: true) { // не собирать jar diff --git a/libprotocol/src/main/java/asys/mcsmanager/packets/CS_CorrectShutdown.java b/libprotocol/src/main/java/asys/mcsmanager/packets/CS_CorrectShutdown.java new file mode 100644 index 0000000..85b334f --- /dev/null +++ b/libprotocol/src/main/java/asys/mcsmanager/packets/CS_CorrectShutdown.java @@ -0,0 +1,17 @@ +/* + * DmitriyMX + * 2017-07-22 + */ +package asys.mcsmanager.packets; + +import io.netty.buffer.ByteBuf; + +public class CS_CorrectShutdown extends Packet { + @Override + public void readSelfData(ByteBuf buffer) { + } + + @Override + public void writeSelfData(ByteBuf buffer) { + } +} diff --git a/mcserver-manager/build.gradle b/mcserver-manager/build.gradle index 878a093..2d7d636 100644 --- a/mcserver-manager/build.gradle +++ b/mcserver-manager/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.10.3-SNAPSHOT' +version = '0.10.6-SNAPSHOT' apply plugin: 'osgi' diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java b/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java index f171b08..205bac4 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java @@ -12,6 +12,7 @@ import com.google.common.collect.ImmutableMap; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.AttributeKey; +import org.slf4j.LoggerFactory; import java.util.Map; diff --git a/mcserver-manager/src/main/resources/components.js b/mcserver-manager/src/main/resources/components.js index f07bea5..ae8a9b1 100644 --- a/mcserver-manager/src/main/resources/components.js +++ b/mcserver-manager/src/main/resources/components.js @@ -199,7 +199,10 @@ var WebConsole = React.createClass({ ce('div', {id: 'webconsole'}, ce(ScrollingContent, {className: 'output'}, this.state.lines.map(function(line){ - return ce('p', {dangerouslySetInnerHTML: {__html: ansi_up.ansi_to_html(line)}}); + var clazz = ""; + if (line.indexOf('ERROR') !== -1) { clazz = "error"; } + else if (line.indexOf('WARN') !== -1) { clazz = "warn"; } + return ce('p', {className: clazz, dangerouslySetInnerHTML: {__html: ansi_up.ansi_to_html(line)}}); }) ), ce('input', {ref: 'input', 'onKeyPress': this.handleKeyInput}) diff --git a/mcserver-manager/src/main/resources/moduleStyle.css b/mcserver-manager/src/main/resources/moduleStyle.css index 063e814..e7a15b0 100644 --- a/mcserver-manager/src/main/resources/moduleStyle.css +++ b/mcserver-manager/src/main/resources/moduleStyle.css @@ -3,7 +3,7 @@ color: #eee; min-height: 500px; height: 1px; - padding: 8px; + padding: 0 8px 0 8px; font-family: monospace; position: relative; } @@ -13,16 +13,48 @@ height: 100%; } +#webconsole .output .wrapper::before { + content: ''; + position: absolute; + z-index: 1; + top: 0; + left: 0; + right: 0; + height: 5%; + background: linear-gradient(#1e1e1e 0%, rgba(30,30,30,0) 100%); +} + +#webconsole .output .wrapper::after { + content: ''; + position: absolute; + z-index: 1; + bottom: 0; + left: 0; + right: 0; + height: 5%; + background: linear-gradient(rgba(30,30,30,0) 0%, #1e1e1e 100%); +} + #webconsole .output .wrapper .content { overflow: auto; height: 100%; position: relative; right: -18px; margin-left: -18px; + padding: 8px 0 8px 0; } #webconsole .output .wrapper .content p { margin: 0; + word-wrap: break-word; +} + +#webconsole .output .wrapper .content p.error { + background-color: rgba(255,0,0,0.4); +} + +#webconsole .output .wrapper .content p.warn { + background-color: rgba(255,200,0,0.3); } #webconsole .output .scroll { @@ -34,6 +66,7 @@ cursor: -webkit-grab; cursor: -moz-grab; right: 0; + z-index: 1; } .scroll-grabbed, diff --git a/settings.gradle b/settings.gradle index 751366b..d2df509 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,4 @@ include 'webinterface' include 'mcserver-manager' include 'libprotocol' include 'bridge' +include 'zond' diff --git a/zond/README.MD b/zond/README.MD new file mode 100644 index 0000000..89c114d --- /dev/null +++ b/zond/README.MD @@ -0,0 +1,54 @@ +# Zond + +Проксирующий запускатор, дающий больший контроль над процессом игрового сервера. + +## Настройка + +Настройки храняться в файле `zond.properties`. +Если файл не будет найден, **Zond** создаст файл настроек по-умолчанию + +`asys.serverId` - идентификатор запускаемого сервера. Одновременно является именем и уникальным ID + +`asys.host` - IP/Hostname для подключения к **ASys** + +`asys.port` - порт для подключения к **ASys** + +`asys.passcode` - секретное слово для предотвращения несанкционированного подключения + +`bridge.port` - порт для подключения **Bridge** + +`bridge.second` - раз в сколько секунд **Bridge** будет присылать пинги + +`pingmonitor.delay` - задержка старта пинг-монитора + +`pingmonitor.maxlost` - сколько максимум пинг-сигналов позволено пропустить. По достижению этого числа, игровой сервер считается зависшим + +`cmd.killer` - комманда для "убийства" процесса сервера + +`cmd.start` - комманда для запуска провесса сервера + +`cmd.prestart` - комманда выполняющаяся перед запуском сервера + +`cmd.errorstart` - комманда выполняющаяся в случае проблем с запуском сервера + +## Запуск + +``` +java -jar asys.zond.jar +``` + +## Комманды + +`:reload` - перезагрузить настройки + +`:ticker` - вкл/выкл тикера. Нужен для корректировки задержки старта пинг-монитора + +`:start` - запуск процесса сервера + +`:connect` - подключиться к **ASys** + +`:disconnect` - отключиться от **ASys** + +`:kill` - принудительно завершить процесс сервера + +`:exit` - завершить работу **Zond**. \ No newline at end of file diff --git a/zond/build.gradle b/zond/build.gradle new file mode 100644 index 0000000..488eaae --- /dev/null +++ b/zond/build.gradle @@ -0,0 +1,49 @@ +group = 'asys' +version = '0.7.17' + +apply plugin: 'application' + +mainClassName = "asys.zond.Main" + +configurations { + included + includedEx + compile.extendsFrom included + compile.extendsFrom includedEx +} + +def zp(FileTree ft) { + return ft.matching { + exclude 'org/apache/commons/exec/StreamPumper.class' + } +} + +compileJava { + dependsOn ':libprotocol:compileJava' +} + +jar { + dependsOn configurations.included + dependsOn configurations.includedEx + manifest { + attributes 'Implementation-Title': 'ASys Zond', + 'Implementation-Version': version, + 'Main-Class': mainClassName + } + baseName = project.group + '.' + project.name + from { configurations.included.collect { it.isDirectory() ? it : zipTree(it) } } + from { configurations.includedEx.collect { it.isDirectory() ? it : zp(zipTree(it)) } } +} + +ext { + nettyVersion = '4.0.23.Final' +} + +dependencies { + included files(project(':libprotocol').sourceSets.main.output.classesDir) + included group: 'jline', name: 'jline', version: '2.14.3' + includedEx group: 'org.apache.commons', name: 'commons-exec', version: '1.3' + included group: 'io.netty', name: 'netty-codec', version: nettyVersion + included group: 'com.google.guava', name: 'guava', version: '21.0' + included group: 'com.sun.jna', name: 'jna', version: '3.0.9' +} diff --git a/zond/src/main/java/asys/zond/Config.java b/zond/src/main/java/asys/zond/Config.java new file mode 100644 index 0000000..1c72aa3 --- /dev/null +++ b/zond/src/main/java/asys/zond/Config.java @@ -0,0 +1,66 @@ +/* + * DmitriyMX + * 2017-06-08 + */ +package asys.zond; + +import java.io.*; +import java.util.Properties; + +public class Config { + private static final Config instance = new Config(); + private Properties properties = new Properties(); + + public static Config getInstance() { + return instance; + } + + private Config(){ + } + + private void load(InputStream inputStream) throws IOException { + properties.load(inputStream); + if (properties.size() == 0) { + throw new IOException("Config empty!"); + } + } + + void load() throws IOException { + File zondPropertiesFile = new File("zond.properties"); + if (!zondPropertiesFile.exists()) { + InputStream stream = Config.class.getResourceAsStream("/zond.properties"); + FileOutputStream fos = new FileOutputStream(zondPropertiesFile); + byte[] buff = new byte[128]; + int len; + while ((len = stream.read(buff)) > 0) { + fos.write(buff, 0, len); + } + fos.flush(); + fos.close(); + } + + FileInputStream fis = new FileInputStream(zondPropertiesFile); + load(fis); + fis.close(); + } + + public String getString(String key) { + return properties.getProperty(key); + } + + public int getInt(String key) { + try { + return Integer.parseInt(properties.getProperty(key)); + } catch (NumberFormatException e) { + return 0; + } + } + + public long getLong(String key) { + try { + return Long.parseLong(properties.getProperty(key)); + } catch (NumberFormatException e) { + return 0; + } + } +} diff --git a/zond/src/main/java/asys/zond/Main.java b/zond/src/main/java/asys/zond/Main.java new file mode 100644 index 0000000..0dbbd8f --- /dev/null +++ b/zond/src/main/java/asys/zond/Main.java @@ -0,0 +1,72 @@ +/* + * DmitriyMX + * 2017-06-15 + * Idea by Daniil on 2017-06-07 + */ +package asys.zond; + +import asys.zond.shell.Shell; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.Executor; +import org.apache.commons.exec.PumpStreamHandler; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.AnsiConsole; + +import java.io.*; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class Main { + private Executor executor; + private CommandLine commandLine; + private PipeInputStream proxyStdIn; + + public static void main(String[] args) throws IOException { + new Main().start(); + } + + private String printLogo() { + return Ansi.ansi().bold().fg(Ansi.Color.WHITE).a("ASys") + .boldOff().a(":// ") + .fg(Ansi.Color.RED).a("Zond") + .reset().newline().toString(); + } + + private void start() { + if (Boolean.getBoolean("ansi.install")) + AnsiConsole.systemInstall(); + System.out.println(printLogo()); + + try { + Config.getInstance().load(); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-2); + return; + } + proxyStdIn = new PipeInputStream(); + ZondCommandHandler commandHandler = new ZondCommandHandler(proxyStdIn); + startShell(commandHandler); + initExecCommand(Shell.getInstance().getOutput()); + commandHandler.setExecutor(executor, commandLine); + } + + private void startShell(ZondCommandHandler commandHandler) { + Shell shell = Shell.getInstance(); + try { + shell.setCommandHandler(commandHandler); + shell.start(System.in); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void initExecCommand(PrintStream stdout) { + String cmdLine = Config.getInstance().getString("cmd.start"); + commandLine = CommandLine.parse(cmdLine); + executor = new DefaultExecutor(); + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdout, stdout, proxyStdIn); + executor.setStreamHandler(pumpStreamHandler); + } +} \ No newline at end of file diff --git a/zond/src/main/java/asys/zond/PingMonitor.java b/zond/src/main/java/asys/zond/PingMonitor.java new file mode 100644 index 0000000..eb228f7 --- /dev/null +++ b/zond/src/main/java/asys/zond/PingMonitor.java @@ -0,0 +1,67 @@ +/* + * DmitriyMX + * 2017-07-21 + */ +package asys.zond; + +public class PingMonitor { + private final long step_ping; + private final long delayStart; + private long lastPingTime; + private Thread threadPingMon; + private boolean correctShutdown = false; + private final int maxlost; + + PingMonitor(int delay, int second, int maxlost) { + this.delayStart = delay*1000; + this.step_ping = second*1000; + this.maxlost = maxlost; + } + + void start(final Runnable callback) { + this.threadPingMon = new Thread(() -> { + correctShutdown = false; + try { + Thread.sleep(delayStart); + } catch (InterruptedException e) { + return; + } + + while (!Thread.currentThread().isInterrupted()) { + long currentTimeMillis = System.currentTimeMillis(); + if ((currentTimeMillis - lastPingTime) >= (maxlost*step_ping)) { // если пропущено N пингов + callback.run(); // запускаем код завершения процесса + return; // завершаем поток + } + + try { + Thread.sleep(step_ping); + } catch (InterruptedException e) { + return; + } + } + + lastPingTime = 0; + }); + this.threadPingMon.start(); + } + + void stop() { + if (threadPingMon != null) { + threadPingMon.interrupt(); + threadPingMon = null; + } + } + + public void checkPing() { + lastPingTime = System.currentTimeMillis(); + } + + public void correctShutdown() { + correctShutdown = true; + } + + boolean isCorrectShutdown() { + return correctShutdown; + } +} diff --git a/zond/src/main/java/asys/zond/PipeInputStream.java b/zond/src/main/java/asys/zond/PipeInputStream.java new file mode 100644 index 0000000..78accf8 --- /dev/null +++ b/zond/src/main/java/asys/zond/PipeInputStream.java @@ -0,0 +1,107 @@ +/* + * DmitriyMX + * 2017-07-13 + */ +package asys.zond; + +import java.io.IOException; +import java.io.InputStream; + +public class PipeInputStream extends InputStream { + private byte[] buffer = new byte[1024]; + private int lastWritePos = 0, + lastReadPos = 0, + wallPos = buffer.length-1; + private boolean closed = false; + + public synchronized void write(String s) { + byte[] strBytes = s.getBytes(); + + while ((lastWritePos < lastReadPos) && ((lastReadPos - lastWritePos) >= strBytes.length)) { + try { + wait(); + } catch (InterruptedException e) { + return; + } + } + + if ((lastWritePos + strBytes.length) >= buffer.length) { + wallPos = lastWritePos; + lastWritePos = 0; + if (lastReadPos == wallPos) { + lastReadPos = 0; + } + } + + System.arraycopy(strBytes, 0, this.buffer, lastWritePos, strBytes.length); + lastWritePos += strBytes.length; + notify(); + } + + @Override + public synchronized int read() throws IOException { + if (lastReadPos == lastWritePos) { + try { + wait(); + } catch (InterruptedException ignore) { + return 0; + } + } + if (closed) return 0; + + if (lastReadPos == wallPos) { + lastReadPos = 0; + } + + return this.buffer[lastReadPos++]; + } + + @Override + public int read(byte[] b) throws IOException { + return this.read(b, 0, b.length); + } + + @Override + public synchronized int read(byte[] buffOut, int off, int len) throws IOException { + if (lastReadPos == lastWritePos) { + try { + wait(); + } catch (InterruptedException ignore) { + return 0; + } + } + if (closed) return 0; + int actualLen = len; + + if (lastReadPos > lastWritePos) { + if ((lastReadPos + len) > wallPos) { + actualLen = (wallPos - lastReadPos); + } + } else { + if ((lastReadPos + len) > lastWritePos) { + actualLen = (lastWritePos - lastReadPos); + } + } + + System.arraycopy(this.buffer, lastReadPos, buffOut, off, actualLen); + lastReadPos += actualLen; + if (lastReadPos == wallPos) { + lastReadPos = 0; + } + notify(); + return actualLen; + } + + public void open() { + closed = false; + lastWritePos = 0; + lastReadPos = 0; + wallPos = buffer.length-1; + } + + @Override + public synchronized void close() { + this.closed = true; + notify(); + } +} diff --git a/zond/src/main/java/asys/zond/ZondCommandHandler.java b/zond/src/main/java/asys/zond/ZondCommandHandler.java new file mode 100644 index 0000000..3d9f9bb --- /dev/null +++ b/zond/src/main/java/asys/zond/ZondCommandHandler.java @@ -0,0 +1,262 @@ +/* + * DmitriyMX + * 2017-06-15 + */ +package asys.zond; + +import asys.zond.proxy.client.Connector; +import asys.zond.proxy.server.Server; +import asys.zond.shell.CommandHandler; +import asys.zond.shell.Shell; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.Executor; +import org.fusesource.jansi.Ansi; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + +public class ZondCommandHandler implements CommandHandler { + private Executor executor; + private CommandLine commandLine; + private ExecuteWatchdog watchdog; + private Thread threadExec; + private Server server; + private PipeInputStream proxyStdIn; + private PingMonitor pingMonitor; + private boolean flagForceRestartProcess = false; + private boolean flagManualKill = false; + private boolean flagTicker = false; + private Thread threadTicker; + + ZondCommandHandler(PipeInputStream proxyStdIn) { + this.proxyStdIn = proxyStdIn; + } + + private static String zondColored(String string) { + return zondColored(string, Ansi.Color.CYAN); + } + + private static String zondColored(String string, Ansi.Color color) { + return Ansi.ansi().bg(Ansi.Color.WHITE).fg(Ansi.Color.RED).a("[Zond]") + .reset().fgBright(color).a(' ').a(string) + .reset().toString(); + } + + private void buildThreadTicker() { + threadTicker = new Thread(() -> { + int sec = 0; + while (!Thread.currentThread().isInterrupted() && flagTicker) { + Shell.getInstance().getOutput().println(zondColored("Tick "+(sec++)+" sec")); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + } + }, "Thread Ticker"); + } + + private void preStartScript() { + if (Config.getInstance().getString("cmd.prestart").isEmpty()) + return; + + ProcessBuilder builder = new ProcessBuilder(Arrays.asList( + Config.getInstance().getString("cmd.prestart").split(" ", 2))); + builder.redirectErrorStream(true); + try { + Process process = builder.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + Shell.getInstance().getOutput().println(zondColored(line)); + } + process.waitFor(); + } catch (IOException e) { + Shell.getInstance().getOutput() + .println(zondColored("[!] PreStart script error: " + e.getMessage(), Ansi.Color.RED)); + } catch (InterruptedException ignore) { + } + } + + private void errorStartScript() { + if (Config.getInstance().getString("cmd.errorstart").isEmpty()) + return; + + ProcessBuilder builder = new ProcessBuilder(Arrays.asList( + Config.getInstance().getString("cmd.errorstart").split(" ", 2))); + builder.redirectErrorStream(true); + try { + Process process = builder.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + Shell.getInstance().getOutput().println(zondColored(line, Ansi.Color.RED)); + } + process.waitFor(); + } catch (IOException e) { + Shell.getInstance().getOutput() + .println(zondColored("[!] ErrorStart script error: " + e.getMessage(), Ansi.Color.RED)); + } catch (InterruptedException ignore) { + } + } + + @Override + public void handle(String commandLine) { + if (commandLine.startsWith(":")) { + internalCommand(commandLine.substring(1)); + } else { + Shell.getInstance().getOutput().println(commandLine); + proxyStdIn.write(commandLine+"\n"); + } + } + + private void internalCommand(String line) { + if (line.equalsIgnoreCase("exit") || line.equalsIgnoreCase("quit")) { + flagManualKill = true; + killProcess(); + Connector.getInstance().shutdown(); + Shell.getInstance().shutdown(); + } else if (line.equalsIgnoreCase("start")) { + startProcess(); + } else if (line.equalsIgnoreCase("kill")) { + flagManualKill = true; + killProcess(); + } else if (line.equalsIgnoreCase("connect")) { + Connector.getInstance().startReconnect(); + } else if (line.equalsIgnoreCase("disconnect")) { + Shell.getInstance().getOutput().println(zondColored("Disconnect")); + Connector.getInstance().shutdown(); + } else if (line.equalsIgnoreCase("reload")) { + Shell.getInstance().getOutput().println(zondColored("Reload config")); + try { + Config.getInstance().load(); + } catch (IOException e) { + e.printStackTrace(); + } + } else if (line.equalsIgnoreCase("ticker")) { + flagTicker = !flagTicker; + Shell.getInstance().getOutput().println(zondColored("Ticker " + (flagTicker?"on":"off"))); + } + } + + void setExecutor(Executor executor, CommandLine commandLine) { + this.executor = executor; + this.commandLine = commandLine; + } + + private void startProcess() { + if (watchdog == null || !watchdog.isWatching()) { + preStartScript(); + + if (watchdog == null) { + watchdog = new ZondExecuteWatchdog( + ExecuteWatchdog.INFINITE_TIMEOUT, + proxyStdIn, + Config.getInstance().getString("cmd.killer") + ); + + executor.setWatchdog(watchdog); + } + pingMonitor = new PingMonitor( + Config.getInstance().getInt("pingmonitor.delay"), + Config.getInstance().getInt("bridge.second"), + Config.getInstance().getInt("pingmonitor.maxlost") + ); + flagManualKill = false; + + Runnable task = () -> { + short _try = 0; + + do { + int code; + long deadTime = 0; + + try { + server = new Server(); + server.setPingMonitor(pingMonitor); + server.start(Config.getInstance().getInt("bridge.port")); + + deadTime = (System.currentTimeMillis()/1000) + (Config.getInstance().getInt("pingmonitor.delay")*1000); + Runnable callback; + if (flagTicker) { + callback = () -> { + Shell.getInstance().getOutput().println(zondColored("[!] Process - zobie?", Ansi.Color.RED)); + }; + + buildThreadTicker(); + threadTicker.start(); + } else { + callback = () -> { + Shell.getInstance().getOutput().println(zondColored("[!] Process - zobie?", Ansi.Color.RED)); + Shell.getInstance().getOutput().println(zondColored("[!] Force shutdown process.", Ansi.Color.RED)); + flagForceRestartProcess = true; + killProcess(); + }; + } + pingMonitor.start(callback); + code = executor.execute(commandLine); + } catch (ExecuteException e) { + code = e.getExitValue(); + } catch (IOException e) { + Shell.getInstance().getOutput().println(zondColored("[!] Exception message: " + e.getMessage(), Ansi.Color.RED)); + code = -99; + } + long currTime = System.currentTimeMillis()/1000; + + server.shutdown(); + pingMonitor.stop(); + server = null; + Shell.getInstance().getOutput().println(zondColored("[i] Process finished. Code: " + code, Ansi.Color.RED)); + + if (pingMonitor.isCorrectShutdown()) { + flagForceRestartProcess = false; + _try = 0; + if (flagTicker) + threadTicker.interrupt(); + break; + } else { + if (currTime <= deadTime && !flagManualKill) { + Shell.getInstance().getOutput().println(zondColored("[!] Premature end process.", Ansi.Color.RED)); + _try++; + if (_try < 2) { + Shell.getInstance().getOutput().println(zondColored("[!] Try start process again...", Ansi.Color.RED)); + } + } else if (flagForceRestartProcess) { + _try = 0; + flagForceRestartProcess = false; + } else if (code != 0 && code != -99 && !flagManualKill) { + Shell.getInstance().getOutput().println(zondColored("[!] Try start process again...", Ansi.Color.RED)); + _try = 0; + } else { + break; + } + } + } while (_try < 2); + + if (_try == 2) { + Shell.getInstance().getOutput().println(zondColored("[!] Discovered the problem when starting the process", Ansi.Color.RED)); + errorStartScript(); + } + }; + threadExec = new Thread(task, "Zond Exec"); + threadExec.start(); + } + } + + private void killProcess() { + if (watchdog != null && watchdog.isWatching()) { + server.shutdown(); + pingMonitor.stop(); + watchdog.destroyProcess(); + threadExec.interrupt(); + if (threadTicker != null) + threadTicker.interrupt(); + } + } +} diff --git a/zond/src/main/java/asys/zond/ZondExecuteWatchdog.java b/zond/src/main/java/asys/zond/ZondExecuteWatchdog.java new file mode 100644 index 0000000..b153757 --- /dev/null +++ b/zond/src/main/java/asys/zond/ZondExecuteWatchdog.java @@ -0,0 +1,77 @@ +/* + * DmitriyMX + * 2017-07-19 + */ +package asys.zond; + +import asys.zond.shell.Shell; +import asys.zond.win32.Kernel32; +import asys.zond.win32.W32API; +import com.sun.jna.Pointer; +import org.apache.commons.exec.ExecuteWatchdog; + +import java.io.IOException; +import java.lang.reflect.Field; + +public class ZondExecuteWatchdog extends ExecuteWatchdog { + private final PipeInputStream inputStream; + private long pid; + private String cmdkiller; + + ZondExecuteWatchdog(long timeout, PipeInputStream inputStream, String cmdkiller) { + super(timeout); + this.inputStream = inputStream; + this.cmdkiller = cmdkiller.trim(); + } + + private long get_pid(Process process) { + if (process.getClass().getName().equals("java.lang.Win32Process") || + process.getClass().getName().equals("java.lang.ProcessImpl")) { + try { + Field f = process.getClass().getDeclaredField("handle"); + f.setAccessible(true); + long handl = f.getLong(process); + + Kernel32 kernel32 = Kernel32.INSTANCE; + W32API.HANDLE handle = new W32API.HANDLE(); + handle.setPointer(Pointer.createConstant(handl)); + return kernel32.GetProcessId(handle); //pid + } catch (Throwable ignore) { + } + } else if (process.getClass().getName().equals("java.lang.UNIXProcess")) { + try { + Field f = process.getClass().getDeclaredField("pid"); + f.setAccessible(true); + return f.getLong(process); //pid + } catch (Throwable ignore) { + } + } + + return -1; + } + + @Override + public synchronized void start(Process processToMonitor) { + super.start(processToMonitor); + pid = get_pid(processToMonitor); + inputStream.open(); + } + + @Override + public synchronized void stop() { + super.stop(); + inputStream.close(); + } + + @Override + public synchronized void destroyProcess() { + super.destroyProcess(); + if (pid != -1 && cmdkiller != null && !cmdkiller.isEmpty()) { + try { + String cmd = cmdkiller.replace("%PID", String.valueOf(pid)); + Runtime.getRuntime().exec(cmd); + } catch (IOException ignore) { + } + } + } +} diff --git a/zond/src/main/java/asys/zond/proxy/TaskTicker.java b/zond/src/main/java/asys/zond/proxy/TaskTicker.java new file mode 100644 index 0000000..b2d59e3 --- /dev/null +++ b/zond/src/main/java/asys/zond/proxy/TaskTicker.java @@ -0,0 +1,52 @@ +/* + * DmitriyMX + * 2017-05-18 + */ +package asys.zond.proxy; + +public class TaskTicker implements Runnable { + private Runnable task; + private long stepTimeMs = 1000L; + private Thread thread; + private boolean loop = false; + + public TaskTicker setTask(Runnable task) { + this.task = task; + return this; + } + + public TaskTicker setStepTimeMs(long stepTimeMs) { + this.stepTimeMs = stepTimeMs; + return this; + } + + public void start() { + thread = new Thread(this, "TaskTicker"); + loop = true; + thread.start(); + } + + public void stop() { + loop = false; + if (thread != null) { + thread.interrupt(); + } + } + + public boolean isActive() { + return loop; + } + + @Override + public void run() { + while (loop || !Thread.currentThread().isInterrupted()) { + task.run(); + + try { + Thread.sleep(stepTimeMs); + } catch (InterruptedException e) { + break; + } + } + } +} diff --git a/zond/src/main/java/asys/zond/proxy/client/Client.java b/zond/src/main/java/asys/zond/proxy/client/Client.java new file mode 100644 index 0000000..ab57fac --- /dev/null +++ b/zond/src/main/java/asys/zond/proxy/client/Client.java @@ -0,0 +1,75 @@ +/* + * DmitriyMX + * 2017-06-16 + */ +package asys.zond.proxy.client; + +import asys.mcsmanager.packets.Packet; +import asys.mcsmanager.packets.codec.PacketDecoder; +import asys.mcsmanager.packets.codec.PacketEncoder; +import asys.mcsmanager.packets.codec.PacketHandler; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +public class Client { + private EventLoopGroup group; + private Bootstrap bootstrap; + private ChannelFuture channelFuture; + + public void connect(String host, int port) { + if (group == null || bootstrap == null) { + group = new NioEventLoopGroup(); + bootstrap = createBootstrap(); + } + + channelFuture = bootstrap.connect(host, port); + channelFuture.awaitUninterruptibly(5000); + } + + public void disconnect() { + if (isConnected()) { + channelFuture.channel().close(); + channelFuture = null; + group.shutdownGracefully(); + } + } + + public boolean isConnected() { + return (channelFuture != null && channelFuture.isSuccess()); + } + + public void sendPacket(Packet packet) { + if (isConnected()) { + channelFuture.channel().writeAndFlush(packet); + } + } + + private Bootstrap createBootstrap() { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioSocketChannel.class) + .handler(createChannelInitializer()); + + return bootstrap; + } + + private ChannelHandler createChannelInitializer() { + return new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast( + new PacketEncoder(), + new PacketDecoder(), + new PacketHandler(), + new ClientPacketHandler() + ); + } + }; + } +} diff --git a/zond/src/main/java/asys/zond/proxy/client/ClientPacketHandler.java b/zond/src/main/java/asys/zond/proxy/client/ClientPacketHandler.java new file mode 100644 index 0000000..202d416 --- /dev/null +++ b/zond/src/main/java/asys/zond/proxy/client/ClientPacketHandler.java @@ -0,0 +1,76 @@ +/* + * DmitriyMX + * 2017-06-16 + */ +package asys.zond.proxy.client; + +import asys.mcsmanager.packets.*; +import asys.zond.Config; +import asys.zond.shell.Shell; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.util.Map; + +import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; +import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; + +public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { + private final BiMap> handshakePackets; + private final Map, IPacketHandler> handshakeHandlers; + private final BiMap> knownPackets; + + ClientPacketHandler() { + handshakePackets = ImmutableBiMap.of( + 1, CS_Handshake.class, + 2, SC_HandshakeResult.class + ); + handshakeHandlers = ImmutableMap.of( + SC_HandshakeResult.class, this + ); + + knownPackets = ImmutableBiMap.of( + 3, CS_Ping.class, + 4, CS_ConsoleMessage.class, + 5, SC_Command.class + ); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.channel().attr(KNOWN_PACKETS).set(handshakePackets); + ctx.channel().attr(KNOWN_HANDLERS).set(handshakeHandlers); + + ctx.channel().writeAndFlush(new CS_Handshake( + Config.getInstance().getString("asys.serverId"), + Config.getInstance().getString("asys.passcode") + )); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + ctx.channel().attr(KNOWN_PACKETS).remove(); + ctx.channel().attr(KNOWN_HANDLERS).remove(); + } + + @Override + public void handle(Packet packet, ChannelHandlerContext context) { + if (packet instanceof SC_HandshakeResult) { + handleHandshakeResult((SC_HandshakeResult) packet, context); + } + } + + private void handleHandshakeResult(SC_HandshakeResult packet, ChannelHandlerContext context) { + if (packet.getErrorCode() != 0) { + Shell.getInstance().getOutput() + .println(String.format("Handshake: #%d %s", packet.getErrorCode(), packet.getMessage())); + Connector.getInstance().shutdown(); + } else { + context.channel().attr(KNOWN_PACKETS).set(knownPackets); + Shell.getInstance().getOutput().println("Handshake: OK"); + } + } +} diff --git a/zond/src/main/java/asys/zond/proxy/client/Connector.java b/zond/src/main/java/asys/zond/proxy/client/Connector.java new file mode 100644 index 0000000..191bee3 --- /dev/null +++ b/zond/src/main/java/asys/zond/proxy/client/Connector.java @@ -0,0 +1,64 @@ +/* + * DmitriyMX + * 2017-06-16 + */ +package asys.zond.proxy.client; + +import asys.mcsmanager.packets.Packet; +import asys.zond.Config; +import asys.zond.proxy.TaskTicker; +import asys.zond.shell.Shell; + +public class Connector { + private static Connector instance; + private Client client; + private TaskTicker connectTicker; + private int tryConnect = 0; + + public static Connector getInstance() { + if (instance == null) instance = new Connector(); + return instance; + } + + private Connector() { + } + + public void startReconnect() { + if ((connectTicker != null && connectTicker.isActive()) || + (client != null && client.isConnected())) return; + client = new Client(); + connectTicker = new TaskTicker().setStepTimeMs(5000L); + connectTicker.setTask(() -> { + Shell.getInstance().getOutput() + .println(String.format("Connect(%d) to ASys...", ++tryConnect)); + client.connect(Config.getInstance().getString("asys.host"), + Config.getInstance().getInt("asys.port")); + if (client.isConnected()) { + stopReconnect(); + } else { + Shell.getInstance().getOutput() + .println(String.format("Connection(%d) fail. Try reconnect...", tryConnect)); + } + }).start(); + } + + public void stopReconnect() { + if (connectTicker != null) { + connectTicker.stop(); + tryConnect = 0; + } + } + + public void shutdown() { + stopReconnect(); + if (client != null) { + client.disconnect(); + } + } + + public void sendPacket(Packet packet) { + if (client != null) { + client.sendPacket(packet); + } + } +} diff --git a/zond/src/main/java/asys/zond/proxy/server/Server.java b/zond/src/main/java/asys/zond/proxy/server/Server.java new file mode 100644 index 0000000..a288e83 --- /dev/null +++ b/zond/src/main/java/asys/zond/proxy/server/Server.java @@ -0,0 +1,63 @@ +/* + * DmitriyMX + * 2017-06-17 + */ +package asys.zond.proxy.server; + +import asys.mcsmanager.packets.codec.PacketDecoder; +import asys.mcsmanager.packets.codec.PacketEncoder; +import asys.mcsmanager.packets.codec.PacketHandler; +import asys.zond.PingMonitor; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; + +public class Server { + private EventLoopGroup bossGroup, workerGroup; + private PingMonitor pingMonitor; + + public void start(int port) { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + + ServerBootstrap serverBootstrap = createServerBootstrap(); + serverBootstrap.bind("127.0.0.1", port); + } + + public void shutdown() { + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + + private ServerBootstrap createServerBootstrap() { + ServerBootstrap bootstrap = new ServerBootstrap(); + + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(createChannelInitializer()); + + return bootstrap; + } + + private ChannelHandler createChannelInitializer() { + return new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast( + new PacketEncoder(), + new PacketDecoder(), + new PacketHandler(), + new ServerPacketHandler(pingMonitor) + ); + } + }; + } + + public void setPingMonitor(PingMonitor pingMonitor) { + this.pingMonitor = pingMonitor; + } +} diff --git a/zond/src/main/java/asys/zond/proxy/server/ServerPacketHandler.java b/zond/src/main/java/asys/zond/proxy/server/ServerPacketHandler.java new file mode 100644 index 0000000..1a0d8f5 --- /dev/null +++ b/zond/src/main/java/asys/zond/proxy/server/ServerPacketHandler.java @@ -0,0 +1,69 @@ +/* + * DmitriyMX + * 2017-06-17 + */ +package asys.zond.proxy.server; + +import asys.mcsmanager.packets.*; +import asys.zond.PingMonitor; +import asys.zond.proxy.client.Connector; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.util.Map; + +import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; +import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; + +public class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { + private final BiMap> knownPackets; + private final Map, IPacketHandler> knownHandlers; + private final PingMonitor pingMonitor; + + ServerPacketHandler(PingMonitor pingMonitor) { + this.pingMonitor = pingMonitor; + knownPackets = ImmutableBiMap.of( + 3, CS_Ping.class, + 5, SC_Command.class, + 6, CS_CorrectShutdown.class + ); + knownHandlers = ImmutableMap.of( + CS_Ping.class, this, + CS_ConsoleMessage.class, this, + CS_CorrectShutdown.class, this + ); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.channel().attr(KNOWN_PACKETS).set(knownPackets); + ctx.channel().attr(KNOWN_HANDLERS).set(knownHandlers); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + ctx.channel().attr(KNOWN_PACKETS).remove(); + ctx.channel().attr(KNOWN_HANDLERS).remove(); + } + + @Override + public void handle(Packet packet, ChannelHandlerContext context) { + if (packet instanceof CS_Ping) { + handleCSPing((CS_Ping) packet); + } else if (packet instanceof CS_CorrectShutdown) { + handleCSCorrectShutdown(); + } + } + + private void handleCSPing(CS_Ping packet) { + pingMonitor.checkPing(); + Connector.getInstance().sendPacket(packet); + } + + private void handleCSCorrectShutdown() { + pingMonitor.correctShutdown(); + } +} diff --git a/zond/src/main/java/asys/zond/shell/CommandHandler.java b/zond/src/main/java/asys/zond/shell/CommandHandler.java new file mode 100644 index 0000000..197ead7 --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/CommandHandler.java @@ -0,0 +1,9 @@ +/* + * DmitriyMX + * 2017-06-15 + */ +package asys.zond.shell; + +public interface CommandHandler { + void handle(String commandLine); +} diff --git a/zond/src/main/java/asys/zond/shell/CommandLooper.java b/zond/src/main/java/asys/zond/shell/CommandLooper.java new file mode 100644 index 0000000..0350453 --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/CommandLooper.java @@ -0,0 +1,35 @@ +/* + * DmitriyMX + * 2017-06-15 + */ +package asys.zond.shell; + +import jline.console.ConsoleReader; + +import java.io.IOException; + +public class CommandLooper implements Runnable { + private ConsoleReader consoleReader; + private CommandHandler commandHandler; + + CommandLooper(ConsoleReader consoleReader, CommandHandler commandHandler) { + this.consoleReader = consoleReader; + this.commandHandler = commandHandler; + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + String line; + try { + line = consoleReader.readLine(); + } catch (IOException e) { + break; + } + if (line == null) break; + if (line.trim().isEmpty()) continue; + + commandHandler.handle(line); + } + } +} diff --git a/zond/src/main/java/asys/zond/shell/EchoCommandHandler.java b/zond/src/main/java/asys/zond/shell/EchoCommandHandler.java new file mode 100644 index 0000000..fa3741c --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/EchoCommandHandler.java @@ -0,0 +1,12 @@ +/* + * DmitriyMX + * 2017-06-15 + */ +package asys.zond.shell; + +public class EchoCommandHandler implements CommandHandler { + @Override + public void handle(String commandLine) { + Shell.getInstance().getOutput().println(commandLine); + } +} diff --git a/zond/src/main/java/asys/zond/shell/Shell.java b/zond/src/main/java/asys/zond/shell/Shell.java new file mode 100644 index 0000000..ed850c7 --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/Shell.java @@ -0,0 +1,60 @@ +/* + * DmitriyMX + * 2017-06-15 + */ +package asys.zond.shell; + +import jline.console.ConsoleReader; + +import java.io.*; + +public class Shell { + private static final String DEFAULT_PROMPT = ":"; + private static Shell instance; + private ConsoleReader consoleReader; + private Thread threadCommandLoop; + private CommandHandler commandHandler; + private ShellStdOut shellStdOut; + + public static Shell getInstance() { + if (instance == null) instance = new Shell(); + return instance; + } + + private Shell() { + } + + public void start(InputStream inputStream) throws IOException { + consoleReader = new ConsoleReader(inputStream, (shellStdOut = new ShellStdOut(System.out))); + shellStdOut.setConsoleReader(consoleReader); + consoleReader.setPrompt(DEFAULT_PROMPT); + + if (commandHandler == null) commandHandler = new EchoCommandHandler(); + + threadCommandLoop = new Thread( + new CommandLooper(consoleReader, commandHandler), + "Zond shell looper"); + threadCommandLoop.start(); + } + + public void shutdown() { + threadCommandLoop.interrupt(); + consoleReader.close(); + } + + public void setPrompt(String value) { + consoleReader.setPrompt(value); + } + + public void resetPrompt() { + consoleReader.setPrompt(DEFAULT_PROMPT); + } + + public PrintStream getOutput() { + return shellStdOut; + } + + public void setCommandHandler(CommandHandler commandHandler) { + this.commandHandler = commandHandler; + } +} diff --git a/zond/src/main/java/asys/zond/shell/ShellStdOut.java b/zond/src/main/java/asys/zond/shell/ShellStdOut.java new file mode 100644 index 0000000..851657c --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/ShellStdOut.java @@ -0,0 +1,99 @@ +/* + * DmitriyMX + * 2017-07-23 + */ +package asys.zond.shell; + +import jline.console.ConsoleReader; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +public class ShellStdOut extends PrintStream { + private ConsoleReader consoleReader; + + ShellStdOut(OutputStream out) { + super(out); + } + + private void _write(char c) { + try { + this.out.write(c); + } catch (IOException ignore) { + } + } + + private void _write(byte[] buf) { + try { + this.out.write(buf); + } catch (IOException ignore) { + } + } + + private void _write(byte[] buf, int off, int len) { + try { + this.out.write(buf, off, len); + } catch (IOException ignore) { + } + } + + private void _flush() { + try { + this.out.flush(); + } catch (IOException ignore) { + } + } + + /** + * Очистка печатной строки от мусора + */ + private void cleanTrashLine(int len) throws IOException { + // очищает полностью строку + if (consoleReader.getCursorBuffer().buffer.length() + consoleReader.getPrompt().length() > len) { + for (int i = len; i <= consoleReader.getCursorBuffer().buffer.length() + 2; i++) { + this.out.write(' '); + } + this.out.flush(); + } + } + + public void printEx(byte[] buf, int len) { + _write(ConsoleReader.RESET_LINE); + _write(buf, 0, len); + try { + cleanTrashLine(len); + consoleReader.drawLine(); + consoleReader.flush(); + } catch (Throwable ignore) { + } + _flush(); + } + + @Override + public void write(byte[] buf, int off, int len) { + _write(buf, off, len); + } + + @Override + public void print(String s) { + _write(ConsoleReader.RESET_LINE); + _write(s.getBytes()); + } + + @Override + public void println(String x) { + print(x.concat("\n")); + try { + cleanTrashLine(x.length()); + consoleReader.drawLine(); + consoleReader.flush(); + } catch (Throwable ignore) { + } + _flush(); + } + + void setConsoleReader(ConsoleReader consoleReader) { + this.consoleReader = consoleReader; + } +} diff --git a/zond/src/main/java/asys/zond/win32/Kernel32.java b/zond/src/main/java/asys/zond/win32/Kernel32.java new file mode 100644 index 0000000..7f8cc23 --- /dev/null +++ b/zond/src/main/java/asys/zond/win32/Kernel32.java @@ -0,0 +1,11 @@ +package asys.zond.win32; + +import com.sun.jna.Native; +/* https://jna.dev.java.net/ */ +public interface Kernel32 extends W32API { + Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, DEFAULT_OPTIONS); + /* http://msdn.microsoft.com/en-us/library/ms683179(VS.85).aspx */ + HANDLE GetCurrentProcess(); + /* http://msdn.microsoft.com/en-us/library/ms683215.aspx */ + int GetProcessId(HANDLE Process); +} diff --git a/zond/src/main/java/asys/zond/win32/W32API.java b/zond/src/main/java/asys/zond/win32/W32API.java new file mode 100644 index 0000000..ad72ea5 --- /dev/null +++ b/zond/src/main/java/asys/zond/win32/W32API.java @@ -0,0 +1,65 @@ +/* Copyright (c) 2007 Timothy Wall, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package asys.zond.win32; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.jna.*; +import com.sun.jna.win32.StdCallLibrary; +import com.sun.jna.win32.W32APIFunctionMapper; +import com.sun.jna.win32.W32APITypeMapper; + +/** Base type for most W32 API libraries. Provides standard options + * for unicode/ASCII mappings. Set the system property w32.ascii + * to true to default to the ASCII mappings. + */ +public interface W32API extends StdCallLibrary, W32Errors { + + /** Standard options to use the unicode version of a w32 API. */ + Map UNICODE_OPTIONS = new HashMap() { + { + put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE); + put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE); + } + }; + + /** Standard options to use the ASCII/MBCS version of a w32 API. */ + Map ASCII_OPTIONS = new HashMap() { + { + put(OPTION_TYPE_MAPPER, W32APITypeMapper.ASCII); + put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.ASCII); + } + }; + + Map DEFAULT_OPTIONS = Boolean.getBoolean("w32.ascii") ? ASCII_OPTIONS : UNICODE_OPTIONS; + + class HANDLE extends PointerType { + @Override + public Object fromNative(Object nativeValue, FromNativeContext context) { + Object o = super.fromNative(nativeValue, context); + if (INVALID_HANDLE_VALUE.equals(o)) + return INVALID_HANDLE_VALUE; + return o; + } + } + + /** Constant value representing an invalid HANDLE. */ + HANDLE INVALID_HANDLE_VALUE = new HANDLE() { + { super.setPointer(Pointer.createConstant(-1)); } + @Override + public void setPointer(Pointer p) { + throw new UnsupportedOperationException("Immutable reference"); + } + }; +} diff --git a/zond/src/main/java/asys/zond/win32/W32Errors.java b/zond/src/main/java/asys/zond/win32/W32Errors.java new file mode 100644 index 0000000..ed20a36 --- /dev/null +++ b/zond/src/main/java/asys/zond/win32/W32Errors.java @@ -0,0 +1,23 @@ +/* Copyright (c) 2007 Timothy Wall, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package asys.zond.win32; + +public interface W32Errors { + + int NO_ERROR = 0; + int ERROR_INVALID_FUNCTION = 1; + int ERROR_FILE_NOT_FOUND = 2; + int ERROR_PATH_NOT_FOUND = 3; + +} diff --git a/zond/src/main/java/org/apache/commons/exec/StreamPumper.java b/zond/src/main/java/org/apache/commons/exec/StreamPumper.java new file mode 100644 index 0000000..5bc73ec --- /dev/null +++ b/zond/src/main/java/org/apache/commons/exec/StreamPumper.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.commons.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import asys.zond.shell.ShellStdOut; +import org.apache.commons.exec.util.DebugUtils; + +/** + * Copies all data from an input stream to an output stream. + * + * @version $Id: StreamPumper.java 1557263 2014-01-10 21:18:09Z ggregory $ + */ +public class StreamPumper implements Runnable { + + /** the default size of the internal buffer for copying the streams */ + private static final int DEFAULT_SIZE = 1024; + + /** the input stream to pump from */ + private final InputStream is; + + /** the output stream to pmp into */ + private final OutputStream os; + + /** the size of the internal buffer for copying the streams */ + private final int size; + + /** was the end of the stream reached */ + private boolean finished; + + /** close the output stream when exhausted */ + private final boolean closeWhenExhausted; + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted. + */ + public StreamPumper(final InputStream is, final OutputStream os, + final boolean closeWhenExhausted) { + this.is = is; + this.os = os; + this.size = DEFAULT_SIZE; + this.closeWhenExhausted = closeWhenExhausted; + } + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted. + * @param size the size of the internal buffer for copying the streams + */ + public StreamPumper(final InputStream is, final OutputStream os, + final boolean closeWhenExhausted, final int size) { + this.is = is; + this.os = os; + this.size = size > 0 ? size : DEFAULT_SIZE; + this.closeWhenExhausted = closeWhenExhausted; + } + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper(final InputStream is, final OutputStream os) { + this(is, os, false); + } + + /** + * Copies data from the input stream to the output stream. Terminates as + * soon as the input stream is closed or an error occurs. + */ + public void run() { + synchronized (this) { + // Just in case this object is reused in the future + finished = false; + } + + final byte[] buf = new byte[this.size]; + + int length; + try { + //hack: пропатчили алгоритм + while (!Thread.currentThread().isInterrupted() && (length = is.read(buf)) > 0) { + if (os instanceof ShellStdOut) { + ((ShellStdOut)os).printEx(buf, length); + } else { + os.write(buf, 0, length); + } + os.flush(); + } + } catch (final Exception e) { + // nothing to do - happens quite often with watchdog + } finally { + if (closeWhenExhausted) { + try { + os.close(); + } catch (final IOException e) { + final String msg = "Got exception while closing exhausted output stream"; + DebugUtils.handleException(msg ,e); + } + } + synchronized (this) { + finished = true; + notifyAll(); + } + } + } + + /** + * Tells whether the end of the stream has been reached. + * + * @return true is the stream has been exhausted. + */ + public synchronized boolean isFinished() { + return finished; + } + + /** + * This method blocks until the stream pumper finishes. + * + * @exception InterruptedException + * if any thread interrupted the current thread before or while the current thread was waiting for a + * notification. + * @see #isFinished() + */ + public synchronized void waitFor() throws InterruptedException { + while (!isFinished()) { + wait(); + } + } +} diff --git a/zond/src/main/resources/zond.properties b/zond/src/main/resources/zond.properties new file mode 100644 index 0000000..2b8cd15 --- /dev/null +++ b/zond/src/main/resources/zond.properties @@ -0,0 +1,15 @@ +asys.serverId = SpigotServer0 +asys.host = 127.0.0.1 +asys.port = 8779 +asys.passcode = testpassphrase +bridge.port = 8710 +bridge.second = 5 +pingmonitor.delay = 5 +pingmonitor.maxlost = 6 +#Windows +#cmd.killer = taskkill /F /PID %PID +#Linux +cmd.killer = kill -KILL %PID +cmd.start = java -jar server.jar +cmd.prestart = echo Hello +cmd.errorstart = echo Error \ No newline at end of file