mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-26 18:30:52 +01:00
Merge branch 'network-extensions-rewrite' of github.com:dorving/G-Earth into dorving-network-extensions-rewrite
This commit is contained in:
commit
df45404a36
@ -1,12 +1,14 @@
|
|||||||
package gearth.extensions;
|
package gearth.extensions;
|
||||||
|
|
||||||
import gearth.misc.HostInfo;
|
import gearth.misc.HostInfo;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing;
|
||||||
import gearth.services.packet_info.PacketInfoManager;
|
import gearth.services.packet_info.PacketInfoManager;
|
||||||
import gearth.protocol.HMessage;
|
import gearth.protocol.HMessage;
|
||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.HPacket;
|
||||||
import gearth.protocol.connection.HClient;
|
import gearth.protocol.connection.HClient;
|
||||||
import gearth.services.Constants;
|
import gearth.services.Constants;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionInfo;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
@ -108,10 +110,10 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
packet.fixLength();
|
packet.fixLength();
|
||||||
|
|
||||||
|
|
||||||
if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST) {
|
if (packet.headerId() == Outgoing.InfoRequest.HEADER_ID) {
|
||||||
ExtensionInfo info = getInfoAnnotations();
|
ExtensionInfo info = getInfoAnnotations();
|
||||||
|
|
||||||
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO);
|
HPacket response = new HPacket(Incoming.ExtensionInfo.HEADER_ID);
|
||||||
response.appendString(info.Title())
|
response.appendString(info.Title())
|
||||||
.appendString(info.Author())
|
.appendString(info.Author())
|
||||||
.appendString(info.Version())
|
.appendString(info.Version())
|
||||||
@ -124,7 +126,7 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
.appendBoolean(canDelete());
|
.appendBoolean(canDelete());
|
||||||
writeToStream(response.toBytes());
|
writeToStream(response.toBytes());
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) {
|
else if (packet.headerId() == Outgoing.ConnectionStart.HEADER_ID) {
|
||||||
String host = packet.readString();
|
String host = packet.readString();
|
||||||
int connectionPort = packet.readInteger();
|
int connectionPort = packet.readInteger();
|
||||||
String hotelVersion = packet.readString();
|
String hotelVersion = packet.readString();
|
||||||
@ -143,10 +145,10 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
);
|
);
|
||||||
onStartConnection();
|
onStartConnection();
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND) {
|
else if (packet.headerId() == Outgoing.ConnectionEnd.HEADER_ID) {
|
||||||
onEndConnection();
|
onEndConnection();
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK) {
|
else if (packet.headerId() == Outgoing.FlagsCheck.HEADER_ID) {
|
||||||
// body = an array of G-Earths gearth flags
|
// body = an array of G-Earths gearth flags
|
||||||
if (flagRequestCallback != null) {
|
if (flagRequestCallback != null) {
|
||||||
int arraysize = packet.readInteger();
|
int arraysize = packet.readInteger();
|
||||||
@ -158,7 +160,7 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
}
|
}
|
||||||
flagRequestCallback = null;
|
flagRequestCallback = null;
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT) {
|
else if (packet.headerId() == Outgoing.Init.HEADER_ID) {
|
||||||
delayed_init = packet.readBoolean();
|
delayed_init = packet.readBoolean();
|
||||||
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
||||||
updateHostInfo(hostInfo);
|
updateHostInfo(hostInfo);
|
||||||
@ -167,21 +169,21 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
}
|
}
|
||||||
writeToConsole("green","Extension \"" + getInfoAnnotations().Title() + "\" successfully initialized", false);
|
writeToConsole("green","Extension \"" + getInfoAnnotations().Title() + "\" successfully initialized", false);
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK) {
|
else if (packet.headerId() == Outgoing.OnDoubleClick.HEADER_ID) {
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT) {
|
else if (packet.headerId() == Outgoing.PacketIntercept.HEADER_ID) {
|
||||||
String stringifiedMessage = packet.readLongString();
|
String stringifiedMessage = packet.readLongString();
|
||||||
HMessage habboMessage = new HMessage(stringifiedMessage);
|
HMessage habboMessage = new HMessage(stringifiedMessage);
|
||||||
|
|
||||||
modifyMessage(habboMessage);
|
modifyMessage(habboMessage);
|
||||||
|
|
||||||
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET);
|
HPacket response = new HPacket(Incoming.ManipulatedPacket.MANIPULATED_PACKET);
|
||||||
response.appendLongString(habboMessage.stringify());
|
response.appendLongString(habboMessage.stringify());
|
||||||
|
|
||||||
writeToStream(response.toBytes());
|
writeToStream(response.toBytes());
|
||||||
}
|
}
|
||||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.UPDATEHOSTINFO) {
|
else if (packet.headerId() == Outgoing.UpdateHostInfo.HEADER_ID) {
|
||||||
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
||||||
updateHostInfo(hostInfo);
|
updateHostInfo(hostInfo);
|
||||||
}
|
}
|
||||||
@ -231,7 +233,7 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
if (!packet.isPacketComplete()) packet.completePacket(packetInfoManager);
|
if (!packet.isPacketComplete()) packet.completePacket(packetInfoManager);
|
||||||
if (!packet.isPacketComplete()) return false;
|
if (!packet.isPacketComplete()) return false;
|
||||||
|
|
||||||
HPacket packet1 = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE);
|
HPacket packet1 = new HPacket(Incoming.SendMessage.HEADER_ID);
|
||||||
packet1.appendByte(direction == HMessage.Direction.TOCLIENT ? (byte)0 : (byte)1);
|
packet1.appendByte(direction == HMessage.Direction.TOCLIENT ? (byte)0 : (byte)1);
|
||||||
packet1.appendInt(packet.getBytesLength());
|
packet1.appendInt(packet.getBytesLength());
|
||||||
packet1.appendBytes(packet.toBytes());
|
packet1.appendBytes(packet.toBytes());
|
||||||
@ -253,7 +255,7 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
if (this.flagRequestCallback != null) return false;
|
if (this.flagRequestCallback != null) return false;
|
||||||
this.flagRequestCallback = flagRequestCallback;
|
this.flagRequestCallback = flagRequestCallback;
|
||||||
try {
|
try {
|
||||||
writeToStream(new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS).toBytes());
|
writeToStream(new HPacket(Incoming.RequestFlags.HEADER_ID).toBytes());
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -279,7 +281,7 @@ public abstract class Extension extends ExtensionBase {
|
|||||||
private void writeToConsole(String colorClass, String s, boolean mentionTitle) {
|
private void writeToConsole(String colorClass, String s, boolean mentionTitle) {
|
||||||
String text = "[" + colorClass + "]" + (mentionTitle ? (getInfoAnnotations().Title() + " --> ") : "") + s;
|
String text = "[" + colorClass + "]" + (mentionTitle ? (getInfoAnnotations().Title() + " --> ") : "") + s;
|
||||||
|
|
||||||
HPacket packet = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG);
|
HPacket packet = new HPacket(Incoming.ExtensionConsoleLog.HEADER_ID);
|
||||||
packet.appendString(text);
|
packet.appendString(text);
|
||||||
try {
|
try {
|
||||||
writeToStream(packet.toBytes());
|
writeToStream(packet.toBytes());
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package gearth.services.extension_handler.extensions.extensionproducers;
|
package gearth.services.extension_handler.extensions.extensionproducers;
|
||||||
|
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer;
|
||||||
import gearth.services.extension_handler.extensions.implementations.simple.SimpleExtensionProducer;
|
import gearth.services.extension_handler.extensions.implementations.simple.SimpleExtensionProducer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -9,13 +9,17 @@ import java.util.List;
|
|||||||
public class ExtensionProducerFactory {
|
public class ExtensionProducerFactory {
|
||||||
// returns one of every ExtensionProducer class we have created, to support all types of extensions
|
// returns one of every ExtensionProducer class we have created, to support all types of extensions
|
||||||
|
|
||||||
|
private final static NetworkExtensionServer EXTENSION_SERVER = new NetworkExtensionServer();
|
||||||
|
|
||||||
public static List<ExtensionProducer> getAll() {
|
public static List<ExtensionProducer> getAll() {
|
||||||
List<ExtensionProducer> all = new ArrayList<>();
|
List<ExtensionProducer> all = new ArrayList<>();
|
||||||
all.add(new NetworkExtensionsProducer());
|
all.add(EXTENSION_SERVER);
|
||||||
all.add(new SimpleExtensionProducer());
|
all.add(new SimpleExtensionProducer());
|
||||||
|
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static NetworkExtensionServer getExtensionServer() {
|
||||||
|
return EXTENSION_SERVER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,258 +0,0 @@
|
|||||||
package gearth.services.extension_handler.extensions.implementations.network;
|
|
||||||
|
|
||||||
import gearth.misc.HostInfo;
|
|
||||||
import gearth.services.packet_info.PacketInfoManager;
|
|
||||||
import gearth.protocol.HMessage;
|
|
||||||
import gearth.protocol.connection.HClient;
|
|
||||||
import gearth.services.extension_handler.extensions.ExtensionType;
|
|
||||||
import gearth.services.extension_handler.extensions.GEarthExtension;
|
|
||||||
import gearth.protocol.HPacket;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Jonas on 21/06/18.
|
|
||||||
*/
|
|
||||||
public class NetworkExtension extends GEarthExtension {
|
|
||||||
|
|
||||||
private String title;
|
|
||||||
private String author;
|
|
||||||
private String version;
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
private boolean fireEventButtonVisible;
|
|
||||||
private boolean leaveButtonVisible;
|
|
||||||
private boolean deleteButtonVisible;
|
|
||||||
|
|
||||||
private boolean isInstalledExtension; // <- extension is in the extensions directory
|
|
||||||
private String fileName;
|
|
||||||
private String cookie;
|
|
||||||
|
|
||||||
private Socket connection;
|
|
||||||
|
|
||||||
NetworkExtension(HPacket extensionInfo, Socket connection) {
|
|
||||||
this.title = extensionInfo.readString();
|
|
||||||
this.author = extensionInfo.readString();
|
|
||||||
this.version = extensionInfo.readString();
|
|
||||||
this.description = extensionInfo.readString();
|
|
||||||
this.fireEventButtonVisible = extensionInfo.readBoolean();
|
|
||||||
|
|
||||||
this.isInstalledExtension = extensionInfo.readBoolean();
|
|
||||||
this.fileName = extensionInfo.readString();
|
|
||||||
this.cookie = extensionInfo.readString();
|
|
||||||
|
|
||||||
this.leaveButtonVisible = extensionInfo.readBoolean();
|
|
||||||
this.deleteButtonVisible = extensionInfo.readBoolean();
|
|
||||||
|
|
||||||
this.connection = connection;
|
|
||||||
|
|
||||||
NetworkExtension selff = this;
|
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
|
||||||
InputStream inputStream = connection.getInputStream();
|
|
||||||
DataInputStream dIn = new DataInputStream(inputStream);
|
|
||||||
|
|
||||||
while (!connection.isClosed()) {
|
|
||||||
int length = dIn.readInt();
|
|
||||||
byte[] headerandbody = new byte[length + 4];
|
|
||||||
|
|
||||||
int amountRead = 0;
|
|
||||||
while (amountRead < length) {
|
|
||||||
amountRead += dIn.read(headerandbody, 4 + amountRead, Math.min(dIn.available(), length - amountRead));
|
|
||||||
}
|
|
||||||
|
|
||||||
HPacket message = new HPacket(headerandbody);
|
|
||||||
message.fixLength();
|
|
||||||
|
|
||||||
synchronized (selff.extensionObservable) {
|
|
||||||
if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS) {
|
|
||||||
requestFlags();
|
|
||||||
}
|
|
||||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE) {
|
|
||||||
byte side = message.readByte();
|
|
||||||
int byteLength = message.readInteger();
|
|
||||||
byte[] packetAsByteArray = message.readBytes(byteLength);
|
|
||||||
|
|
||||||
HPacket packet = new HPacket(packetAsByteArray);
|
|
||||||
if (!packet.isCorrupted()) {
|
|
||||||
sendMessage(
|
|
||||||
side == 0 ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER,
|
|
||||||
packet
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET) {
|
|
||||||
String stringifiedresponse = message.readLongString(6);
|
|
||||||
HMessage responseMessage = new HMessage(stringifiedresponse);
|
|
||||||
sendManipulatedPacket(responseMessage);
|
|
||||||
}
|
|
||||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG) {
|
|
||||||
log(message.readString());
|
|
||||||
}
|
|
||||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.PACKETTOSTRING_REQUEST) {
|
|
||||||
HPacket p = new HPacket(new byte[0]);
|
|
||||||
p.constructFromString(message.readLongString());
|
|
||||||
packetToStringRequest(p);
|
|
||||||
}
|
|
||||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.STRINGTOPACKET_REQUEST) {
|
|
||||||
stringToPacketRequest(message.readLongString(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
// An extension disconnected, which is OK
|
|
||||||
} finally {
|
|
||||||
synchronized (selff.extensionObservable) {
|
|
||||||
hasClosed();
|
|
||||||
}
|
|
||||||
if (!connection.isClosed()) {
|
|
||||||
try {
|
|
||||||
connection.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getAuthor() {
|
|
||||||
return author;
|
|
||||||
}
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
public String getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
public boolean isFireButtonUsed() {
|
|
||||||
return fireEventButtonVisible;
|
|
||||||
}
|
|
||||||
public String getFileName() {
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
public String getCookie() {
|
|
||||||
return cookie;
|
|
||||||
}
|
|
||||||
public boolean isDeleteButtonVisible() {
|
|
||||||
return deleteButtonVisible;
|
|
||||||
}
|
|
||||||
public boolean isLeaveButtonVisible() {
|
|
||||||
return leaveButtonVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInstalledExtension() {
|
|
||||||
return isInstalledExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean sendMessage(HPacket message) {
|
|
||||||
try {
|
|
||||||
synchronized (this) {
|
|
||||||
connection.getOutputStream().write(message.toBytes());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doubleclick() {
|
|
||||||
sendMessage(new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void packetIntercept(HMessage hMessage) {
|
|
||||||
String stringified = hMessage.stringify();
|
|
||||||
HPacket manipulatePacketRequest = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT);
|
|
||||||
manipulatePacketRequest.appendLongString(stringified);
|
|
||||||
sendMessage(manipulatePacketRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void provideFlags(String[] flags) {
|
|
||||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK);
|
|
||||||
packet.appendInt(flags.length);
|
|
||||||
for (String flag : flags) {
|
|
||||||
packet.appendString(flag);
|
|
||||||
}
|
|
||||||
sendMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionStart(String host, int port, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) {
|
|
||||||
HPacket connectionStartPacket = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART)
|
|
||||||
.appendString(host)
|
|
||||||
.appendInt(port)
|
|
||||||
.appendString(hotelVersion)
|
|
||||||
.appendString(clientIdentifier)
|
|
||||||
.appendString(clientType.name());
|
|
||||||
|
|
||||||
packetInfoManager.appendToPacket(connectionStartPacket);
|
|
||||||
sendMessage(connectionStartPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionEnd() {
|
|
||||||
sendMessage(
|
|
||||||
new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(boolean isConnected, HostInfo hostInfo) {
|
|
||||||
HPacket initPacket = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT);
|
|
||||||
initPacket.appendBoolean(isConnected);
|
|
||||||
hostInfo.appendToPacket(initPacket);
|
|
||||||
|
|
||||||
sendMessage(initPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
try {
|
|
||||||
connection.close();
|
|
||||||
} catch (IOException ignored) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateHostInfo(HostInfo hostInfo) {
|
|
||||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.UPDATEHOSTINFO);
|
|
||||||
hostInfo.appendToPacket(packet);
|
|
||||||
sendMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void packetToStringResponse(String string, String expression) {
|
|
||||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETTOSTRING_RESPONSE);
|
|
||||||
packet.appendLongString(string);
|
|
||||||
packet.appendLongString(expression, StandardCharsets.UTF_8);
|
|
||||||
sendMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stringToPacketResponse(HPacket packetFromString) {
|
|
||||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.STRINGTOPACKET_RESPONSE);
|
|
||||||
packet.appendLongString(packetFromString.stringify());
|
|
||||||
sendMessage(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExtensionType extensionType() {
|
|
||||||
return ExtensionType.EXTERNAL;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package gearth.services.extension_handler.extensions.implementations.network.authentication;
|
package gearth.services.extension_handler.extensions.implementations.network;
|
||||||
|
|
||||||
import gearth.misc.ConfirmationDialog;
|
import gearth.misc.ConfirmationDialog;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtension;
|
|
||||||
import gearth.ui.titlebar.TitleBarController;
|
import gearth.ui.titlebar.TitleBarController;
|
||||||
import gearth.ui.translations.LanguageBundle;
|
import gearth.ui.translations.LanguageBundle;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@ -11,62 +10,59 @@ import javafx.scene.control.Label;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Jonas on 16/10/18.
|
* Created by Jonas on 16/10/18.
|
||||||
*/
|
*/
|
||||||
public class Authenticator {
|
public final class NetworkExtensionAuthenticator {
|
||||||
|
|
||||||
private static Map<String, String> cookies = new HashMap<>();
|
private static final Map<String, String> COOKIES = new HashMap<>();
|
||||||
private static Set<String> perma_cookies = new HashSet<>();
|
private static final Set<String> PERSISTENT_COOKIES = new HashSet<>();
|
||||||
|
|
||||||
public static String generateCookieForExtension(String filename) {
|
private static volatile boolean rememberOption = false;
|
||||||
String cookie = getRandomCookie();
|
|
||||||
cookies.put(filename, cookie);
|
|
||||||
return cookie;
|
|
||||||
}
|
|
||||||
public static String generatePermanentCookie() {
|
|
||||||
String cookie = getRandomCookie();
|
|
||||||
perma_cookies.add(cookie);
|
|
||||||
return cookie;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean evaluate(NetworkExtension extension) {
|
public static boolean evaluate(NetworkExtensionClient extension) {
|
||||||
if (extension.getCookie() != null && perma_cookies.contains(extension.getCookie())) {
|
|
||||||
|
final String cookie = extension.getCookie();
|
||||||
|
|
||||||
|
if (cookie != null && PERSISTENT_COOKIES.contains(cookie))
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
if (extension.isInstalledExtension()) {
|
return extension.isInstalledExtension()
|
||||||
return claimSession(extension.getFileName(), extension.getCookie());
|
? claimSession(extension.getFileName(), cookie)
|
||||||
}
|
: askForPermission(extension);
|
||||||
else {
|
|
||||||
return askForPermission(extension);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* authenticator: authenticate an extension and remove the cookie
|
* Authenticate an extension and remove the cookie
|
||||||
* @param filename
|
*
|
||||||
* @param cookie
|
* @return {@code true} if the extension is authenticated.
|
||||||
* @return if the extension is authenticated
|
|
||||||
*/
|
*/
|
||||||
private static boolean claimSession(String filename, String cookie) {
|
private static boolean claimSession(String filename, String cookie) {
|
||||||
if (cookies.containsKey(filename) && cookies.get(filename).equals(cookie)) {
|
if (COOKIES.containsKey(filename) && COOKIES.get(filename).equals(cookie)) {
|
||||||
cookies.remove(filename);
|
COOKIES.remove(filename);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static volatile boolean rememberOption = false;
|
/**
|
||||||
//for not-installed extensions, popup a dialog
|
* For not yet installed extensions, open a confirmation dialog.
|
||||||
private static boolean askForPermission(NetworkExtension extension) {
|
*
|
||||||
|
* @param extension the {@link NetworkExtensionClient extension} to ask permission for.
|
||||||
|
*
|
||||||
|
* @return {@code true} if permission is granted, {@code false} if not.
|
||||||
|
*/
|
||||||
|
private static boolean askForPermission(NetworkExtensionClient extension) {
|
||||||
boolean[] allowConnection = {true};
|
boolean[] allowConnection = {true};
|
||||||
|
|
||||||
final String connectExtensionKey = "allow_extension_connection";
|
final String connectExtensionKey = "allow_extension_connection";
|
||||||
|
|
||||||
if (ConfirmationDialog.showDialog(connectExtensionKey)) {
|
if (ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||||
boolean[] done = {false};
|
|
||||||
|
final CountDownLatch countDownLatch = new CountDownLatch(0);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, connectExtensionKey
|
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, connectExtensionKey
|
||||||
, LanguageBundle.get("alert.confirmation.windowtitle"), null,
|
, LanguageBundle.get("alert.confirmation.windowtitle"), null,
|
||||||
@ -84,33 +80,40 @@ public class Authenticator {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
done[0] = true;
|
countDownLatch.countDown();
|
||||||
if (!ConfirmationDialog.showDialog(connectExtensionKey)) {
|
if (!ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||||
rememberOption = allowConnection[0];
|
rememberOption = allowConnection[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
while (!done[0]) {
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1);
|
countDownLatch.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return allowConnection[0];
|
return allowConnection[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return rememberOption;
|
return rememberOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getRandomCookie() {
|
public static String generateCookieForExtension(String filename) {
|
||||||
StringBuilder builder = new StringBuilder();
|
final String cookie = generateRandomCookie();
|
||||||
Random r = new Random();
|
COOKIES.put(filename, cookie);
|
||||||
for (int i = 0; i < 40; i++) {
|
return cookie;
|
||||||
builder.append(r.nextInt(40));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String generatePermanentCookie() {
|
||||||
|
final String cookie = generateRandomCookie();
|
||||||
|
PERSISTENT_COOKIES.add(cookie);
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateRandomCookie() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
final Random r = new Random();
|
||||||
|
for (int i = 0; i < 40; i++)
|
||||||
|
builder.append(r.nextInt(40));
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
package gearth.services.extension_handler.extensions.implementations.network;
|
||||||
|
|
||||||
|
import gearth.misc.HostInfo;
|
||||||
|
import gearth.protocol.HMessage;
|
||||||
|
import gearth.protocol.HPacket;
|
||||||
|
import gearth.protocol.connection.HClient;
|
||||||
|
import gearth.services.extension_handler.extensions.ExtensionType;
|
||||||
|
import gearth.services.extension_handler.extensions.GEarthExtension;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing;
|
||||||
|
import gearth.services.packet_info.PacketInfoManager;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client for managing remote extensions.
|
||||||
|
*
|
||||||
|
* @author Dorving
|
||||||
|
*/
|
||||||
|
public final class NetworkExtensionClient extends GEarthExtension {
|
||||||
|
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionClient.class);
|
||||||
|
|
||||||
|
private final Channel channel;
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
private final String author;
|
||||||
|
private final String version;
|
||||||
|
private final String description;
|
||||||
|
private final String fileName;
|
||||||
|
private final String cookie;
|
||||||
|
private final boolean fireEventButtonVisible;
|
||||||
|
private final boolean leaveButtonVisible;
|
||||||
|
private final boolean deleteButtonVisible;
|
||||||
|
private final boolean isInstalledExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link NetworkExtensionClient} instance.
|
||||||
|
*
|
||||||
|
* @param channel the channel through which to communicate with the remote extension.
|
||||||
|
* @param info the {@link Incoming.ExtensionInfo} detailing the extension.
|
||||||
|
*/
|
||||||
|
public NetworkExtensionClient(Channel channel, Incoming.ExtensionInfo info) {
|
||||||
|
this.channel = channel;
|
||||||
|
title = info.getTitle();
|
||||||
|
author = info.getAuthor();
|
||||||
|
version = info.getVersion();
|
||||||
|
description = info.getDescription();
|
||||||
|
fireEventButtonVisible = info.isOnClickUsed();
|
||||||
|
leaveButtonVisible = info.isCanLeave();
|
||||||
|
deleteButtonVisible = info.isCanDelete();
|
||||||
|
isInstalledExtension = info.getFile() != null;
|
||||||
|
fileName = info.getFile();
|
||||||
|
cookie = info.getCookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles {@link Incoming incoming messages}.
|
||||||
|
*
|
||||||
|
* @param incoming the {@link Incoming message} to be handled.
|
||||||
|
*/
|
||||||
|
public void handleIncomingMessage(Incoming incoming) {
|
||||||
|
try {
|
||||||
|
if (incoming instanceof Incoming.RequestFlags)
|
||||||
|
requestFlags();
|
||||||
|
else if (incoming instanceof Incoming.SendMessage) {
|
||||||
|
final Incoming.SendMessage message = ((Incoming.SendMessage) incoming);
|
||||||
|
final HPacket packet = message.getPacket();
|
||||||
|
if (!packet.isCorrupted())
|
||||||
|
sendMessage(message.getDirection(), packet);
|
||||||
|
} else if (incoming instanceof Incoming.ManipulatedPacket) {
|
||||||
|
sendManipulatedPacket(((Incoming.ManipulatedPacket) incoming).gethMessage());
|
||||||
|
} else if (incoming instanceof Incoming.ExtensionConsoleLog) {
|
||||||
|
log(((Incoming.ExtensionConsoleLog) incoming).getContents());
|
||||||
|
} else if (incoming instanceof Incoming.PacketToStringRequest) {
|
||||||
|
final HPacket hPacket = new HPacket(new byte[0]);
|
||||||
|
hPacket.constructFromString(((Incoming.PacketToStringRequest) incoming).getString());
|
||||||
|
packetToStringRequest(hPacket);
|
||||||
|
} else if (incoming instanceof Incoming.StringToPacketRequest) {
|
||||||
|
stringToPacketRequest(((Incoming.StringToPacketRequest) incoming).getString());
|
||||||
|
}
|
||||||
|
} catch (Exception e){
|
||||||
|
LOGGER.error("Failed to handle incoming message {} (channel={})", incoming, channel, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(boolean isConnected, HostInfo hostInfo) {
|
||||||
|
channel.writeAndFlush(new Outgoing.Init(isConnected, hostInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
channel.close();
|
||||||
|
} catch (Exception e){
|
||||||
|
LOGGER.error("Failed to close client (channel={})", channel, e);
|
||||||
|
} finally {
|
||||||
|
hasClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionStart(String host, int port, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) {
|
||||||
|
channel.writeAndFlush(new Outgoing.ConnectionStart(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
hotelVersion,
|
||||||
|
clientIdentifier,
|
||||||
|
clientType,
|
||||||
|
packetInfoManager
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionEnd() {
|
||||||
|
channel.writeAndFlush(new Outgoing.ConnectionEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doubleclick() {
|
||||||
|
channel.writeAndFlush(new Outgoing.OnDoubleClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void provideFlags(String[] flags) {
|
||||||
|
channel.writeAndFlush(new Outgoing.FlagsCheck(Arrays.asList(flags)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateHostInfo(HostInfo hostInfo) {
|
||||||
|
channel.writeAndFlush(new Outgoing.UpdateHostInfo(hostInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetIntercept(HMessage hMessage) {
|
||||||
|
final String messageAsString = hMessage.stringify();
|
||||||
|
channel.writeAndFlush(new Outgoing.PacketIntercept(messageAsString));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetToStringResponse(String string, String expression) {
|
||||||
|
channel.writeAndFlush(new Outgoing.PacketToStringResponse(string, expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stringToPacketResponse(HPacket packet) {
|
||||||
|
channel.writeAndFlush(new Outgoing.StringToPacketResponse(packet.stringify()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtensionType extensionType() {
|
||||||
|
return ExtensionType.EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCookie() {
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFireButtonUsed() {
|
||||||
|
return fireEventButtonVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDeleteButtonVisible() {
|
||||||
|
return deleteButtonVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLeaveButtonVisible() {
|
||||||
|
return leaveButtonVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalledExtension() {
|
||||||
|
return isInstalledExtension;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,307 @@
|
|||||||
|
package gearth.services.extension_handler.extensions.implementations.network;
|
||||||
|
|
||||||
|
import gearth.misc.HostInfo;
|
||||||
|
import gearth.protocol.HMessage;
|
||||||
|
import gearth.protocol.HPacket;
|
||||||
|
import gearth.protocol.connection.HClient;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming;
|
||||||
|
import gearth.services.packet_info.PacketInfoManager;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static gearth.protocol.HMessage.Direction.TOCLIENT;
|
||||||
|
import static gearth.protocol.HMessage.Direction.TOSERVER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THE EXTENSION COMMUNICATION PRINCIPLES & PROTOCOL:
|
||||||
|
*
|
||||||
|
* You will be able to write extensions in ANY language you want, but we will only provide an interface
|
||||||
|
* for Java so if you write your own in for example Python, make SURE you do it correctly or it could fuck G-Earth.
|
||||||
|
*
|
||||||
|
* Also, don't let the method where you manipulate the packets block. Similiar as how you must not block things in an UI thread.
|
||||||
|
* Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up.
|
||||||
|
* That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done.
|
||||||
|
* TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send
|
||||||
|
* the edited packet when you're done.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java.
|
||||||
|
* -----------------------------------------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* (0. We recommend to use a cross-platform language for your extension)
|
||||||
|
*
|
||||||
|
* 1. An extension will run as a seperate process on your device and has to be called with the flag "-p <PORT>",
|
||||||
|
* where <PORT> is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server.
|
||||||
|
*
|
||||||
|
* 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension.
|
||||||
|
* Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth).
|
||||||
|
*
|
||||||
|
* You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth
|
||||||
|
* (but you have to configure the port yourself, which will be displayed in the extension page)
|
||||||
|
*
|
||||||
|
* 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does):
|
||||||
|
* (if an object is sent; the object will be sent with its String representation from the StringifyAble interface, so the object's class must implement that)
|
||||||
|
*
|
||||||
|
* INCOMING MESSAGES: (marked with * if you're required to correctly respond or take action, ** if it's a response on something you requested)
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | ID | TITLE | BODY & DESCRIPTION |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 1 | ON-DOUBLECLICK | No body, the extension has been double clicked from within G-Earth | ( <- typically for tanji-module-like extensions you will open the UI here)
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 2 | INFO-REQUEST* | Needs response with extension info (name, desc, author, version, ..), |
|
||||||
|
* | | | exact implementation is found in the Java abstract Extension class |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 3 | PACKET-INTERCEPT* | Includes the whole HMessage as body, needs response with the |
|
||||||
|
* | | | manipulated HMessage (OUTGOING id: 2) |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 4 | FLAGS-CHECK** | Body: String with G-Earth's boot flags (args from static gearth method) |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 5 | CONNECTION START | just a note that a new connection has been made, |
|
||||||
|
* | | | you could check this yourself as well (listen to out:4000 packet) |
|
||||||
|
* | | | host/port, hotel version |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 6 | CONNECTION END | Empty body, just a note that a connection has ended |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 7 | INIT | Empty body, a connection with G-Earth has been set up |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 99 | FREE FLOW | extension-specific body |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* OUTGOING MESSAGES: (marked with * if that is a response to one of the msgs above)
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | ID | TITLE | BODY & DESCRIPTION |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 1 | EXTENSION-INFO* | Response for INFO-REQUEST |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 2 | MANIPULATED-PACKET*| Response for PACKET-INTERCEPT |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 3 | REQUEST-FLAGS | Request G-Earth's flags, results in incoming FLAGS-CHECK response |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 4 | SEND-MESSAGE | Body: HMessage object. Sends the HPacket wrapped in the HMessage |
|
||||||
|
* | | | to the client/server |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
* | 99 | FREE FLOW | extension-specific body |
|
||||||
|
* -----------------------------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 4. Your extension will only appear in the extension list once the EXTENSION-INFO has been received by G-Earth
|
||||||
|
*/
|
||||||
|
public final class NetworkExtensionCodec {
|
||||||
|
|
||||||
|
private final static Map<Class<?>, PacketStructure> outgoingPacketStructures = new HashMap<>();
|
||||||
|
private final static Map<Integer, PacketStructure> incomingPacketStructures = new HashMap<>();
|
||||||
|
|
||||||
|
public static PacketStructure getIncomingStructure(int headerId) {
|
||||||
|
return incomingPacketStructures.get(headerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static<T extends NetworkExtensionMessage> PacketStructure getOutgoingStructure(T message) {
|
||||||
|
return outgoingPacketStructures.get(message.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
registerOutgoingMessages();
|
||||||
|
registerIncomingMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerIncomingMessages() {
|
||||||
|
register(Incoming.ExtensionInfo.HEADER_ID,
|
||||||
|
Incoming.ExtensionInfo.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
hPacket.appendString(message.getTitle());
|
||||||
|
hPacket.appendString(message.getAuthor());
|
||||||
|
hPacket.appendString(message.getVersion());
|
||||||
|
hPacket.appendString(message.getDescription());
|
||||||
|
hPacket.appendBoolean(message.isOnClickUsed());
|
||||||
|
hPacket.appendBoolean(message.getFile() != null);
|
||||||
|
hPacket.appendString(Optional.ofNullable(message.getFile()).orElse(""));
|
||||||
|
hPacket.appendString(Optional.ofNullable(message.getCookie()).orElse(""));
|
||||||
|
hPacket.appendBoolean(message.isCanLeave());
|
||||||
|
hPacket.appendBoolean(message.isCanDelete());
|
||||||
|
},
|
||||||
|
(hPacket -> {
|
||||||
|
final String title = hPacket.readString();
|
||||||
|
final String author = hPacket.readString();
|
||||||
|
final String version = hPacket.readString();
|
||||||
|
final String description = hPacket.readString();
|
||||||
|
final boolean isOnClickUsed = hPacket.readBoolean();
|
||||||
|
final boolean hasFile = hPacket.readBoolean();
|
||||||
|
String file = hPacket.readString();
|
||||||
|
if (!hasFile)
|
||||||
|
file = null;
|
||||||
|
String cookie = hPacket.readString();
|
||||||
|
if (cookie.isEmpty())
|
||||||
|
cookie = null;
|
||||||
|
final boolean canLeave = hPacket.readBoolean();
|
||||||
|
final boolean canDelete = hPacket.readBoolean();
|
||||||
|
return new Incoming.ExtensionInfo(title, author, version, description, isOnClickUsed, file, cookie, canLeave, canDelete);
|
||||||
|
}));
|
||||||
|
register(Incoming.ManipulatedPacket.MANIPULATED_PACKET,
|
||||||
|
Incoming.ManipulatedPacket.class,
|
||||||
|
(message, hPacket) -> hPacket.appendLongString(message.gethMessage().stringify()),
|
||||||
|
(hPacket -> {
|
||||||
|
final String packetString = hPacket.readLongString(6);
|
||||||
|
final HMessage hMessage = new HMessage(packetString);
|
||||||
|
return new Incoming.ManipulatedPacket(hMessage);
|
||||||
|
}));
|
||||||
|
register(Incoming.SendMessage.HEADER_ID,
|
||||||
|
Incoming.SendMessage.class,
|
||||||
|
((message, hPacket) -> {
|
||||||
|
hPacket.appendByte((byte) (message.getDirection() == TOCLIENT ? 0 : 1));
|
||||||
|
hPacket.appendInt(message.getPacket().getBytesLength());
|
||||||
|
hPacket.appendBytes(message.getPacket().toBytes());
|
||||||
|
}),
|
||||||
|
(hPacket -> {
|
||||||
|
final byte side = hPacket.readByte();
|
||||||
|
final int length = hPacket.readInteger();
|
||||||
|
final byte[] data = hPacket.readBytes(length);
|
||||||
|
final HPacket packet = new HPacket(data);
|
||||||
|
return new Incoming.SendMessage(packet, side == 0 ? TOCLIENT : TOSERVER);
|
||||||
|
}));
|
||||||
|
register(Incoming.RequestFlags.HEADER_ID,
|
||||||
|
Incoming.RequestFlags.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
},
|
||||||
|
(hPacket -> new Incoming.RequestFlags()));
|
||||||
|
register(Incoming.ExtensionConsoleLog.HEADER_ID,
|
||||||
|
Incoming.ExtensionConsoleLog.class,
|
||||||
|
(message, hPacket) -> hPacket.appendString(message.getContents()),
|
||||||
|
(hPacket -> new Incoming.ExtensionConsoleLog(hPacket.readString())));
|
||||||
|
register(Incoming.PacketToStringRequest.HEADER_ID,
|
||||||
|
Incoming.PacketToStringRequest.class,
|
||||||
|
(message, hPacket) -> hPacket.appendLongString(message.getString()),
|
||||||
|
(hPacket -> new Incoming.PacketToStringRequest(hPacket.readLongString())));
|
||||||
|
register(Incoming.StringToPacketRequest.HEADER_ID,
|
||||||
|
Incoming.StringToPacketRequest.class,
|
||||||
|
(message, hPacket) -> hPacket.appendLongString(message.getString(), StandardCharsets.UTF_8),
|
||||||
|
(hPacket -> new Incoming.StringToPacketRequest(hPacket.readLongString(StandardCharsets.UTF_8))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerOutgoingMessages() {
|
||||||
|
register(Outgoing.InfoRequest.HEADER_ID,
|
||||||
|
Outgoing.InfoRequest.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
},
|
||||||
|
(hPacket -> new Outgoing.InfoRequest()));
|
||||||
|
register(Outgoing.ConnectionStart.HEADER_ID,
|
||||||
|
Outgoing.ConnectionStart.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
hPacket.appendString(message.getHost());
|
||||||
|
hPacket.appendInt(message.getConnectionPort());
|
||||||
|
hPacket.appendString(message.getHotelVersion());
|
||||||
|
hPacket.appendString(message.getClientIdentifier());
|
||||||
|
hPacket.appendString(message.getClientType().name());
|
||||||
|
message.getPacketInfoManager().appendToPacket(hPacket);
|
||||||
|
},
|
||||||
|
(hPacket -> new Outgoing.ConnectionStart(
|
||||||
|
hPacket.readString(),
|
||||||
|
hPacket.readInteger(),
|
||||||
|
hPacket.readString(),
|
||||||
|
hPacket.readString(),
|
||||||
|
HClient.valueOf(hPacket.readString()),
|
||||||
|
PacketInfoManager.readFromPacket(hPacket)
|
||||||
|
)));
|
||||||
|
register(Outgoing.ConnectionEnd.HEADER_ID,
|
||||||
|
Outgoing.ConnectionEnd.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
},
|
||||||
|
(hPacket -> new Outgoing.ConnectionEnd()));
|
||||||
|
register(Outgoing.FlagsCheck.HEADER_ID,
|
||||||
|
Outgoing.FlagsCheck.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
hPacket.appendInt(message.getFlags().size());
|
||||||
|
message.getFlags().forEach(hPacket::appendString);
|
||||||
|
},
|
||||||
|
(hPacket -> {
|
||||||
|
final int size = hPacket.readInteger();
|
||||||
|
final List<String> flags = new ArrayList<>();
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
flags.add(hPacket.readString());
|
||||||
|
return new Outgoing.FlagsCheck(flags);
|
||||||
|
}));
|
||||||
|
register(Outgoing.Init.HEADER_ID,
|
||||||
|
Outgoing.Init.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
hPacket.appendBoolean(message.isDelayInit());
|
||||||
|
message.getHostInfo().appendToPacket(hPacket);
|
||||||
|
},
|
||||||
|
(hPacket -> new Outgoing.Init(hPacket.readBoolean(), HostInfo.fromPacket(hPacket))));
|
||||||
|
register(Outgoing.OnDoubleClick.HEADER_ID,
|
||||||
|
Outgoing.OnDoubleClick.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
},
|
||||||
|
(hPacket -> new Outgoing.OnDoubleClick()));
|
||||||
|
register(Outgoing.PacketIntercept.HEADER_ID,
|
||||||
|
Outgoing.PacketIntercept.class,
|
||||||
|
(message, hPacket) -> hPacket.appendLongString(message.getPacketString()),
|
||||||
|
(hPacket -> new Outgoing.PacketIntercept(hPacket.readLongString())));
|
||||||
|
register(Outgoing.UpdateHostInfo.HEADER_ID,
|
||||||
|
Outgoing.UpdateHostInfo.class,
|
||||||
|
(message, hPacket) -> message.getHostInfo().appendToPacket(hPacket),
|
||||||
|
(hPacket -> new Outgoing.UpdateHostInfo(HostInfo.fromPacket(hPacket))));
|
||||||
|
register(Outgoing.PacketToStringResponse.HEADER_ID,
|
||||||
|
Outgoing.PacketToStringResponse.class,
|
||||||
|
(message, hPacket) -> {
|
||||||
|
hPacket.appendLongString(message.getString());
|
||||||
|
hPacket.appendLongString(message.getExpression(), StandardCharsets.UTF_8);
|
||||||
|
},
|
||||||
|
(hPacket -> new Outgoing.PacketToStringResponse(hPacket.readLongString(), hPacket.readLongString(StandardCharsets.UTF_8)))
|
||||||
|
);
|
||||||
|
register(Outgoing.StringToPacketResponse.HEADER_ID,
|
||||||
|
Outgoing.StringToPacketResponse.class,
|
||||||
|
(message, hPacket) -> hPacket.appendLongString(message.getString()),
|
||||||
|
(hPacket -> new Outgoing.StringToPacketResponse(hPacket.readLongString()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends NetworkExtensionMessage> void register(final int headerId, Class<T> tClass, BiConsumer<T, HPacket> writer, Function<HPacket, T> reader) {
|
||||||
|
final PacketStructure packetStructure = new PacketStructure(headerId, tClass.getSimpleName(), writer, reader);
|
||||||
|
if (tClass.getSuperclass() == Outgoing.class)
|
||||||
|
outgoingPacketStructures.put(tClass, packetStructure);
|
||||||
|
else
|
||||||
|
incomingPacketStructures.put(headerId, packetStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the packet structure of a {@link NetworkExtensionMessage}.
|
||||||
|
*
|
||||||
|
* Can be used to {@link PacketStructure#writer write} messages to packets
|
||||||
|
* and {@link PacketStructure#reader read} messages from packets.
|
||||||
|
*
|
||||||
|
* @apiNote At the moment both outgoing and incoming messages have a reader and writer defined,
|
||||||
|
* this is so that in the future the same codec can be used for the Extensions API.
|
||||||
|
*/
|
||||||
|
static class PacketStructure {
|
||||||
|
|
||||||
|
private final int headerId;
|
||||||
|
private final String name;
|
||||||
|
private final BiConsumer<? extends NetworkExtensionMessage, HPacket> writer;
|
||||||
|
private final Function<HPacket, ? extends NetworkExtensionMessage> reader;
|
||||||
|
|
||||||
|
public PacketStructure(int headerId, String name, BiConsumer<? extends NetworkExtensionMessage, HPacket> writer, Function<HPacket, ? extends NetworkExtensionMessage> reader) {
|
||||||
|
this.headerId = headerId;
|
||||||
|
this.name = name;
|
||||||
|
this.writer = writer;
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeaderId() {
|
||||||
|
return headerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BiConsumer<? extends NetworkExtensionMessage, HPacket> getWriter() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<HPacket, ? extends NetworkExtensionMessage> getReader() {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,110 +0,0 @@
|
|||||||
package gearth.services.extension_handler.extensions.implementations.network;
|
|
||||||
|
|
||||||
public class NetworkExtensionInfo {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* THE EXTENSION COMMUNCATION PRINCIPLES & PROTOCOL:
|
|
||||||
*
|
|
||||||
* You will be able to write extensions in ANY language you want, but we will only provide an interface
|
|
||||||
* for Java so if you write your own in for example Python, make SURE you do it correctly or it could fuck G-Earth.
|
|
||||||
*
|
|
||||||
* Also, don't let the method where you manipulate the packets block. Similiar as how you must not block things in an UI thread.
|
|
||||||
* Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up.
|
|
||||||
* That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done.
|
|
||||||
* TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send
|
|
||||||
* the editted packet when you're done.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java.
|
|
||||||
* -----------------------------------------------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* (0. We recommend to use a cross-platform language for your extension)
|
|
||||||
*
|
|
||||||
* 1. An extension will run as a seperate process on your device and has to be called with the flag "-p <PORT>",
|
|
||||||
* where <PORT> is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server.
|
|
||||||
*
|
|
||||||
* 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension.
|
|
||||||
* Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth).
|
|
||||||
*
|
|
||||||
* You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth
|
|
||||||
* (but you have to configure the port yourself, which will be displayed in the extension page)
|
|
||||||
*
|
|
||||||
* 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does):
|
|
||||||
* (if an object is sent; the object will be sent with its String representation from the StringifyAble interface, so the object's class must implement that)
|
|
||||||
*
|
|
||||||
* INCOMING MESSAGES: (marked with * if you're required to correctly respond or take action, ** if it's a response on something you requested)
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | ID | TITLE | BODY & DESCRIPTION |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 1 | ON-DOUBLECLICK | No body, the extension has been double clicked from within G-Earth | ( <- typically for tanji-module-like extensions you will open the UI here)
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 2 | INFO-REQUEST* | Needs response with extension info (name, desc, author, version, ..), |
|
|
||||||
* | | | exact implementation is found in the Java abstract Extension class |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 3 | PACKET-INTERCEPT* | Includes the whole HMessage as body, needs response with the |
|
|
||||||
* | | | manipulated HMessage (OUTGOING id: 2) |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 4 | FLAGS-CHECK** | Body: String with G-Earth's boot flags (args from static gearth method) |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 5 | CONNECTION START | just a note that a new connection has been made, |
|
|
||||||
* | | | you could check this yourself as well (listen to out:4000 packet) |
|
|
||||||
* | | | host/port, hotel version |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 6 | CONNECTION END | Empty body, just a note that a connection has ended |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 7 | INIT | Empty body, a connection with G-Earth has been set up |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 99 | FREE FLOW | extension-specific body |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* OUTGOING MESSAGES: (marked with * if that is a response to one of the msgs above)
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | ID | TITLE | BODY & DESCRIPTION |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 1 | EXTENSION-INFO* | Response for INFO-REQUEST |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 2 | MANIPULATED-PACKET*| Response for PACKET-INTERCEPT |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 3 | REQUEST-FLAGS | Request G-Earth's flags, results in incoming FLAGS-CHECK response |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 4 | SEND-MESSAGE | Body: HMessage object. Sends the HPacket wrapped in the HMessage |
|
|
||||||
* | | | to the client/server |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
* | 99 | FREE FLOW | extension-specific body |
|
|
||||||
* -----------------------------------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* 4. Your extension will only appear in the extension list once the EXTENSION-INFO has been received by G-Earth
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
public static class OUTGOING_MESSAGES_IDS {
|
|
||||||
public static final int ONDOUBLECLICK = 1;
|
|
||||||
public static final int INFOREQUEST = 2;
|
|
||||||
public static final int PACKETINTERCEPT = 3;
|
|
||||||
public static final int FLAGSCHECK = 4;
|
|
||||||
public static final int CONNECTIONSTART = 5;
|
|
||||||
public static final int CONNECTIONEND = 6;
|
|
||||||
public static final int INIT = 7;
|
|
||||||
|
|
||||||
public static final int UPDATEHOSTINFO = 10;
|
|
||||||
|
|
||||||
public static final int PACKETTOSTRING_RESPONSE = 20;
|
|
||||||
public static final int STRINGTOPACKET_RESPONSE = 21;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class INCOMING_MESSAGES_IDS {
|
|
||||||
public static final int EXTENSIONINFO = 1;
|
|
||||||
public static final int MANIPULATEDPACKET = 2;
|
|
||||||
public static final int REQUESTFLAGS = 3;
|
|
||||||
public static final int SENDMESSAGE = 4;
|
|
||||||
|
|
||||||
public static final int PACKETTOSTRING_REQUEST = 20;
|
|
||||||
public static final int STRINGTOPACKET_REQUEST = 21;
|
|
||||||
|
|
||||||
public static final int EXTENSIONCONSOLELOG = 98;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,417 @@
|
|||||||
|
package gearth.services.extension_handler.extensions.implementations.network;
|
||||||
|
|
||||||
|
import gearth.misc.HostInfo;
|
||||||
|
import gearth.protocol.HMessage;
|
||||||
|
import gearth.protocol.HPacket;
|
||||||
|
import gearth.protocol.connection.HClient;
|
||||||
|
import gearth.services.packet_info.PacketInfoManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a message send or received by G-Earth and a remote extension.
|
||||||
|
*
|
||||||
|
* @see NetworkExtensionCodec the encoding/decoding structures
|
||||||
|
* @see Incoming messages coming from the remote extension to G-Earth
|
||||||
|
* @see Outgoing messages coming from G-Earth to the remote extension
|
||||||
|
*
|
||||||
|
* @author Dorving, Jonas
|
||||||
|
*/
|
||||||
|
public class NetworkExtensionMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link NetworkExtensionMessage messages} coming from the remote extension to G-Earth.
|
||||||
|
*/
|
||||||
|
public static class Incoming extends NetworkExtensionMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This contains info about the remote extension trying to connect.
|
||||||
|
*
|
||||||
|
* Once this {@link NetworkExtensionMessage message} is received,
|
||||||
|
* a new {@link NetworkExtensionClient} is created to handle the communication.
|
||||||
|
*
|
||||||
|
* @see Outgoing.InfoRequest the request.
|
||||||
|
*/
|
||||||
|
public static class ExtensionInfo extends Incoming {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 1;
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
private final String author;
|
||||||
|
private final String version;
|
||||||
|
private final String description;
|
||||||
|
private final boolean onClickUsed;
|
||||||
|
private final String file;
|
||||||
|
private final String cookie;
|
||||||
|
private final boolean canLeave;
|
||||||
|
private final boolean canDelete;
|
||||||
|
|
||||||
|
public ExtensionInfo(String title, String author, String version, String description, boolean onClickUsed, String file, String cookie, boolean canLeave, boolean canDelete) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.version = version;
|
||||||
|
this.description = description;
|
||||||
|
this.onClickUsed = onClickUsed;
|
||||||
|
this.file = file;
|
||||||
|
this.cookie = cookie;
|
||||||
|
this.canLeave = canLeave;
|
||||||
|
this.canDelete = canDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnClickUsed() {
|
||||||
|
return onClickUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCookie() {
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanLeave() {
|
||||||
|
return canLeave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanDelete() {
|
||||||
|
return canDelete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote extension request G-Earth's flags.
|
||||||
|
*
|
||||||
|
* @see Outgoing.FlagsCheck the response.
|
||||||
|
*/
|
||||||
|
public static class RequestFlags extends Incoming {
|
||||||
|
public static final int HEADER_ID = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Received a {@link HPacket} from the remote connection
|
||||||
|
* and forward it either to the game {@link HMessage.Direction#TOSERVER server}
|
||||||
|
* or game {@link HMessage.Direction#TOCLIENT client}.
|
||||||
|
*/
|
||||||
|
public static class SendMessage extends Incoming {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 4;
|
||||||
|
|
||||||
|
private final HPacket packet;
|
||||||
|
private final HMessage.Direction direction;
|
||||||
|
|
||||||
|
public SendMessage(HPacket packet, HMessage.Direction direction) {
|
||||||
|
this.packet = packet;
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HPacket getPacket() {
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HMessage.Direction getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: add documentation.
|
||||||
|
*
|
||||||
|
* @see Outgoing.PacketToStringResponse the response.
|
||||||
|
*/
|
||||||
|
public static class PacketToStringRequest extends Incoming {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 20;
|
||||||
|
|
||||||
|
private final String string;
|
||||||
|
|
||||||
|
public PacketToStringRequest(String string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: add documentation.
|
||||||
|
*
|
||||||
|
* @see Outgoing.StringToPacketResponse the response.
|
||||||
|
*/
|
||||||
|
public static class StringToPacketRequest extends Incoming {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 21;
|
||||||
|
|
||||||
|
private final String string;
|
||||||
|
|
||||||
|
public StringToPacketRequest(String string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: add documentation.
|
||||||
|
*/
|
||||||
|
public static class ExtensionConsoleLog extends Incoming {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 98;
|
||||||
|
|
||||||
|
private final String contents;
|
||||||
|
|
||||||
|
public ExtensionConsoleLog(String contents) {
|
||||||
|
this.contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContents() {
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a packet modified by the remote extension.
|
||||||
|
*
|
||||||
|
* @see Outgoing.PacketIntercept the ougoing message containing the original packet.
|
||||||
|
*/
|
||||||
|
public static class ManipulatedPacket extends Incoming {
|
||||||
|
public static final int MANIPULATED_PACKET = 2;
|
||||||
|
private final HMessage hMessage;
|
||||||
|
|
||||||
|
public ManipulatedPacket(HMessage hMessage) {
|
||||||
|
this.hMessage = hMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HMessage gethMessage() {
|
||||||
|
return hMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Represents {@link NetworkExtensionMessage messages} coming from G-Earth to the remote extension.
|
||||||
|
*/
|
||||||
|
public static class Outgoing extends NetworkExtensionMessage{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The extension has been double-clicked from within G-Earth.
|
||||||
|
*/
|
||||||
|
public static class OnDoubleClick extends Outgoing {
|
||||||
|
public static final int HEADER_ID = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for remote extension to send {@link Incoming.ExtensionInfo}.
|
||||||
|
*
|
||||||
|
* This is the very first message send after a connection is established.
|
||||||
|
*
|
||||||
|
* @see Incoming.ExtensionInfo the response.
|
||||||
|
*/
|
||||||
|
public static class InfoRequest extends Outgoing {
|
||||||
|
public static final int HEADER_ID = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards a packet intercepted by G-Earth to the remote extension.
|
||||||
|
*
|
||||||
|
* @see Incoming.ManipulatedPacket the response.
|
||||||
|
*/
|
||||||
|
public static class PacketIntercept extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 3;
|
||||||
|
|
||||||
|
private final String packetString;
|
||||||
|
|
||||||
|
public PacketIntercept(String packetString) {
|
||||||
|
this.packetString = packetString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPacketString() {
|
||||||
|
return packetString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains program arguments of G-Earth.
|
||||||
|
*
|
||||||
|
* @see Incoming.RequestFlags the request.
|
||||||
|
*/
|
||||||
|
public static class FlagsCheck extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 4;
|
||||||
|
|
||||||
|
private final List<String> flags;
|
||||||
|
|
||||||
|
public FlagsCheck(List<String> flags) {
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies remote extension that a connection to a hotel has been established.
|
||||||
|
*
|
||||||
|
* @apiNote could check this yourself as well (listen to out:4000 packet)
|
||||||
|
*/
|
||||||
|
public static class ConnectionStart extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 5;
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
private final int connectionPort;
|
||||||
|
private final String hotelVersion;
|
||||||
|
private final String clientIdentifier;
|
||||||
|
private final HClient clientType;
|
||||||
|
private final PacketInfoManager packetInfoManager;
|
||||||
|
|
||||||
|
public ConnectionStart(String host, int connectionPort, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) {
|
||||||
|
this.host = host;
|
||||||
|
this.connectionPort = connectionPort;
|
||||||
|
this.hotelVersion = hotelVersion;
|
||||||
|
this.clientIdentifier = clientIdentifier;
|
||||||
|
this.clientType = clientType;
|
||||||
|
this.packetInfoManager = packetInfoManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getConnectionPort() {
|
||||||
|
return connectionPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHotelVersion() {
|
||||||
|
return hotelVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientIdentifier() {
|
||||||
|
return clientIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HClient getClientType() {
|
||||||
|
return clientType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketInfoManager getPacketInfoManager() {
|
||||||
|
return packetInfoManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies a remote extension that the connection to the hotel has been closed.
|
||||||
|
*/
|
||||||
|
public static class ConnectionEnd extends Outgoing {
|
||||||
|
public static final int HEADER_ID = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies a remote extension that it has been accepted by G-Earth.
|
||||||
|
*/
|
||||||
|
public static class Init extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 7;
|
||||||
|
|
||||||
|
private final boolean delayInit;
|
||||||
|
private final HostInfo hostInfo;
|
||||||
|
|
||||||
|
public Init(boolean delayInit, HostInfo hostInfo) {
|
||||||
|
this.delayInit = delayInit;
|
||||||
|
this.hostInfo = hostInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDelayInit() {
|
||||||
|
return delayInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HostInfo getHostInfo() {
|
||||||
|
return hostInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: add documentation.
|
||||||
|
*/
|
||||||
|
public static class UpdateHostInfo extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 10;
|
||||||
|
|
||||||
|
private final HostInfo hostInfo;
|
||||||
|
|
||||||
|
public UpdateHostInfo(HostInfo hostInfo) {
|
||||||
|
this.hostInfo = hostInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HostInfo getHostInfo() {
|
||||||
|
return hostInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: add documentation.
|
||||||
|
*
|
||||||
|
* @see Incoming.PacketToStringRequest the request.
|
||||||
|
*/
|
||||||
|
public static class PacketToStringResponse extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 20;
|
||||||
|
|
||||||
|
private final String string;
|
||||||
|
private final String expression;
|
||||||
|
|
||||||
|
public PacketToStringResponse(String string, String expression) {
|
||||||
|
this.string = string;
|
||||||
|
this.expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExpression() {
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: add documentation.
|
||||||
|
*
|
||||||
|
* @see Incoming.StringToPacketRequest the request.
|
||||||
|
*/
|
||||||
|
public static class StringToPacketResponse extends Outgoing {
|
||||||
|
|
||||||
|
public static final int HEADER_ID = 21;
|
||||||
|
|
||||||
|
private final String string;
|
||||||
|
|
||||||
|
public StringToPacketResponse(String string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,277 @@
|
|||||||
|
package gearth.services.extension_handler.extensions.implementations.network;
|
||||||
|
|
||||||
|
import gearth.protocol.HPacket;
|
||||||
|
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer;
|
||||||
|
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
||||||
|
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerObserver;
|
||||||
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionCodec.PacketStructure;
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import io.netty.handler.codec.MessageToByteEncoder;
|
||||||
|
import io.netty.util.Attribute;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import static gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an {@link ExtensionProducer} that implements a server to which
|
||||||
|
* remotely-ran extensions can connect.
|
||||||
|
*
|
||||||
|
* @see ExtensionProducerFactory#getAll() for instance creation.
|
||||||
|
*
|
||||||
|
* @author Dorving, Jonas
|
||||||
|
*/
|
||||||
|
public final class NetworkExtensionServer implements ExtensionProducer {
|
||||||
|
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionServer.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial port server tries to listen at, if {@link ServerSocket} creation fails,
|
||||||
|
* it tries next port.
|
||||||
|
*/
|
||||||
|
private static final int PORT_ONSET = 9092;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port at which the server is listening.
|
||||||
|
*/
|
||||||
|
private int port = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startProducing(ExtensionProducerObserver observer) {
|
||||||
|
|
||||||
|
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
.childHandler(new Initializer(observer))
|
||||||
|
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||||
|
.group(new NioEventLoopGroup());
|
||||||
|
|
||||||
|
port = PORT_ONSET;
|
||||||
|
while (!available(port))
|
||||||
|
port++;
|
||||||
|
LOGGER.debug("Found open port {}, attempting to bind...", port);
|
||||||
|
|
||||||
|
final ChannelFuture channelFuture = bootstrap.bind(port).awaitUninterruptibly();
|
||||||
|
if (!channelFuture.isSuccess())
|
||||||
|
LOGGER.error("Failed to bind to port {}", port);
|
||||||
|
else
|
||||||
|
LOGGER.debug("Successfully bound to port {}", port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port that the server is bound to.
|
||||||
|
*
|
||||||
|
* @return the port number to which the server is bound or -1 if the socket is not bound (yet).
|
||||||
|
*/
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if a specific port is available.
|
||||||
|
*
|
||||||
|
* Taken from <a href="http://svn.apache.org/viewvc/camel/trunk/components/camel-test/src/main/java/org/apache/camel/test/AvailablePortFinder.java?view=markup#l130">http://svn.apache.org/viewvc/camel/trunk/components/camel-test/src/main/java/org/apache/camel/test/AvailablePortFinder.java?view=markup#l130</a>
|
||||||
|
*
|
||||||
|
* @param port the port to check for availability
|
||||||
|
*/
|
||||||
|
private static boolean available(int port) {
|
||||||
|
ServerSocket ss = null;
|
||||||
|
DatagramSocket ds = null;
|
||||||
|
try {
|
||||||
|
ss = new ServerSocket(port);
|
||||||
|
ss.setReuseAddress(true);
|
||||||
|
ds = new DatagramSocket(port);
|
||||||
|
ds.setReuseAddress(true);
|
||||||
|
return true;
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
} finally {
|
||||||
|
if (ds != null) {
|
||||||
|
ds.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ss != null) {
|
||||||
|
try {
|
||||||
|
ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
/* should not be thrown */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Initializer extends ChannelInitializer<SocketChannel> {
|
||||||
|
|
||||||
|
private final ExtensionProducerObserver observer;
|
||||||
|
|
||||||
|
public Initializer(ExtensionProducerObserver observer) {
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initChannel(SocketChannel ch) {
|
||||||
|
ch.pipeline()
|
||||||
|
.addLast("decoder", new Decoder())
|
||||||
|
.addLast("encoder", new Encoder())
|
||||||
|
.addLast("handler", new Handler(observer));
|
||||||
|
ch.writeAndFlush(new Outgoing.InfoRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Decoder extends ByteToMessageDecoder {
|
||||||
|
|
||||||
|
private final static int HEADER_LENGTH = Integer.BYTES;
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(Decoder.class);
|
||||||
|
|
||||||
|
private volatile Stage stage = Stage.LENGTH;
|
||||||
|
private volatile int payloadLength = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||||
|
switch (stage) {
|
||||||
|
case LENGTH:
|
||||||
|
|
||||||
|
if (in.readableBytes() < HEADER_LENGTH)
|
||||||
|
return;
|
||||||
|
|
||||||
|
payloadLength = in.readInt();
|
||||||
|
stage = Stage.PAYLOAD;
|
||||||
|
break;
|
||||||
|
case PAYLOAD:
|
||||||
|
|
||||||
|
if (in.readableBytes() < payloadLength)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
final byte[] data = new byte[HEADER_LENGTH + payloadLength];
|
||||||
|
in.readBytes(data, HEADER_LENGTH, payloadLength);
|
||||||
|
|
||||||
|
final HPacket hPacket = new HPacket(data);
|
||||||
|
hPacket.fixLength();
|
||||||
|
|
||||||
|
final PacketStructure incomingPacketStructure = NetworkExtensionCodec.getIncomingStructure(hPacket.headerId());
|
||||||
|
if (incomingPacketStructure != null) {
|
||||||
|
final NetworkExtensionMessage message = incomingPacketStructure.getReader().apply(hPacket);
|
||||||
|
out.add(message);
|
||||||
|
} else {
|
||||||
|
LOGGER.error("Did not find decoder for packet {}", hPacket);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to decode message", e);
|
||||||
|
} finally {
|
||||||
|
payloadLength = 0;
|
||||||
|
stage = Stage.LENGTH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Stage {
|
||||||
|
LENGTH,
|
||||||
|
PAYLOAD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Encoder extends MessageToByteEncoder<Outgoing> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, Outgoing msg, ByteBuf out) {
|
||||||
|
final PacketStructure structure = NetworkExtensionCodec.getOutgoingStructure(msg);
|
||||||
|
if (structure == null){
|
||||||
|
LOGGER.error("Structure for Outgoing message not defined (msg={})", msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final HPacket hPacket = new HPacket(structure.getHeaderId());
|
||||||
|
final BiConsumer<Outgoing, HPacket> writer = (BiConsumer<Outgoing, HPacket>) structure.getWriter();
|
||||||
|
writer.accept(msg, hPacket);
|
||||||
|
out.writeBytes(hPacket.toBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to encode Outgoing message as a HPacket (msg={})", msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Handler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
private static final AttributeKey<NetworkExtensionClient> CLIENT = AttributeKey.valueOf("client");
|
||||||
|
|
||||||
|
private final ExtensionProducerObserver observer;
|
||||||
|
|
||||||
|
public Handler(ExtensionProducerObserver observer) {
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
LOGGER.trace("Channel registered (channel={})", ctx.channel());
|
||||||
|
super.handlerAdded(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
LOGGER.trace("Channel unregistered (channel={})", ctx.channel());
|
||||||
|
close(ctx);
|
||||||
|
super.channelUnregistered(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
|
final Channel channel = ctx.channel();
|
||||||
|
final Attribute<NetworkExtensionClient> clientAttribute = ctx.attr(CLIENT);
|
||||||
|
NetworkExtensionClient client = clientAttribute.get();
|
||||||
|
if (msg instanceof Incoming.ExtensionInfo) {
|
||||||
|
if (client != null)
|
||||||
|
LOGGER.warn("Overriding pre-existing CLIENT for channel (client={}, channel={})", client, channel);
|
||||||
|
client = new NetworkExtensionClient(channel, (Incoming.ExtensionInfo) msg);
|
||||||
|
if (NetworkExtensionAuthenticator.evaluate(client)) {
|
||||||
|
LOGGER.info("Successfully authenticated client {}", client);
|
||||||
|
clientAttribute.set(client);
|
||||||
|
observer.onExtensionProduced(client);
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Failed to authenticate client {}, closing connection", client);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (client == null)
|
||||||
|
LOGGER.error("Client was null, could not handle incoming message {}, expected {} first", msg, Incoming.ExtensionInfo.class);
|
||||||
|
else if (msg instanceof Incoming)
|
||||||
|
client.handleIncomingMessage((Incoming) msg);
|
||||||
|
else
|
||||||
|
LOGGER.error("Read invalid message type (message={})", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
LOGGER.error("Channel exception caught (channel={}), closing channel", ctx.channel(), cause);
|
||||||
|
close(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(ChannelHandlerContext ctx) {
|
||||||
|
final Optional<NetworkExtensionClient> optionalClient = findClient(ctx);
|
||||||
|
if (optionalClient.isPresent())
|
||||||
|
optionalClient.get().close();
|
||||||
|
else
|
||||||
|
ctx.channel().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<NetworkExtensionClient> findClient(ChannelHandlerContext ctx) {
|
||||||
|
return Optional.ofNullable(ctx.attr(CLIENT).get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,167 +0,0 @@
|
|||||||
package gearth.services.extension_handler.extensions.implementations.network;
|
|
||||||
|
|
||||||
import gearth.protocol.HPacket;
|
|
||||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer;
|
|
||||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
|
||||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerObserver;
|
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an {@link ExtensionProducer} that implements a server to which
|
|
||||||
* remotely-ran extensions can connect.
|
|
||||||
*
|
|
||||||
* @see ExtensionProducerFactory#getAll() for instance creation.
|
|
||||||
*
|
|
||||||
* Created by Jonas on 21/06/18.
|
|
||||||
*/
|
|
||||||
public final class NetworkExtensionsProducer implements ExtensionProducer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial port server tries to listen at, if {@link ServerSocket} creation fails,
|
|
||||||
* it tries next port.
|
|
||||||
*/
|
|
||||||
private static final int PORT_ONSET = 9092;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the number of bytes per boolean encoded in an incoming packet.
|
|
||||||
*/
|
|
||||||
private static final int BOOLEAN_SIZE = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the maximum number of bytes per string encoded in an incoming packet.
|
|
||||||
*/
|
|
||||||
private static final int MAX_STRING_SIZE = Character.BYTES * 4_000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Length is encoded as an {@link Integer} and header id as an {@link Short}.
|
|
||||||
*/
|
|
||||||
private static final int PACKET_HEADER_SIZE = Integer.BYTES + Short.BYTES;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the maximum number of bytes in the body of an incoming packet.
|
|
||||||
* <p>
|
|
||||||
* Used as a form of validation for packets, prevents other Apps that connect
|
|
||||||
* with the server from sending unexpected data and inexplicably causing huge byte array allocations.
|
|
||||||
* <p>
|
|
||||||
* Since the server only accepts {@link NetworkExtensionInfo.INCOMING_MESSAGES_IDS#EXTENSIONINFO} packets,
|
|
||||||
* this value is calculated based on that packet.
|
|
||||||
*/
|
|
||||||
private static final int MAX_PACKET_BODY_SIZE = (MAX_STRING_SIZE * 6) + (BOOLEAN_SIZE * 4);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The port at which the {@link #serverSocket} is listening for incoming connections.
|
|
||||||
*/
|
|
||||||
public static int extensionPort = -1;
|
|
||||||
|
|
||||||
private ServerSocket serverSocket;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startProducing(ExtensionProducerObserver observer) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialise the serverSocket at the argued port.
|
|
||||||
*/
|
|
||||||
int port = PORT_ONSET;
|
|
||||||
while (!createServer(port))
|
|
||||||
++port;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Start connection listener thread.
|
|
||||||
*/
|
|
||||||
new Thread(() -> {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
while (!serverSocket.isClosed()) {
|
|
||||||
|
|
||||||
// accept a new connection
|
|
||||||
final Socket extensionSocket = serverSocket.accept();
|
|
||||||
extensionSocket.setTcpNoDelay(true);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Start client session handler thread.
|
|
||||||
*/
|
|
||||||
new Thread(() -> {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// write INFOREQUEST packet to client
|
|
||||||
synchronized (extensionSocket) {
|
|
||||||
extensionSocket.getOutputStream().write((new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
final DataInputStream dIn = new DataInputStream(extensionSocket.getInputStream());
|
|
||||||
|
|
||||||
// listen to incoming data from client
|
|
||||||
while (!extensionSocket.isClosed()) {
|
|
||||||
|
|
||||||
final int bodyLength = dIn.readInt() - Short.BYTES;
|
|
||||||
final short headerId = dIn.readShort();
|
|
||||||
|
|
||||||
if (headerId == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO) {
|
|
||||||
|
|
||||||
if (bodyLength > MAX_PACKET_BODY_SIZE) {
|
|
||||||
System.err.printf("Incoming packet(h=%d, l=%d) exceeds max packet body size %d.\n", headerId, bodyLength, MAX_PACKET_BODY_SIZE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final HPacket packet = readPacket(dIn, bodyLength, headerId);
|
|
||||||
|
|
||||||
final NetworkExtension gEarthExtension = new NetworkExtension(packet, extensionSocket);
|
|
||||||
|
|
||||||
if (Authenticator.evaluate(gEarthExtension))
|
|
||||||
observer.onExtensionProduced(gEarthExtension);
|
|
||||||
else
|
|
||||||
gEarthExtension.close();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean createServer(int port) {
|
|
||||||
try {
|
|
||||||
serverSocket = new ServerSocket(port);
|
|
||||||
extensionPort = port;
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HPacket readPacket(DataInputStream dIn, int amountToRead, short id) throws IOException {
|
|
||||||
final byte[] headerAndBody = new byte[amountToRead + PACKET_HEADER_SIZE];
|
|
||||||
|
|
||||||
int amountRead = 0;
|
|
||||||
while (amountRead < amountToRead)
|
|
||||||
amountRead += dIn.read(headerAndBody, amountRead + PACKET_HEADER_SIZE, Math.min(dIn.available(), amountToRead - amountRead));
|
|
||||||
|
|
||||||
final HPacket packet = new HPacket(headerAndBody);
|
|
||||||
packet.fixLength();
|
|
||||||
packet.replaceShort(4, id); // add header id
|
|
||||||
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the {@link ServerSocket#getLocalPort()} of {@link #serverSocket}.
|
|
||||||
*
|
|
||||||
* @return the port number to which {@link #serverSocket} is listening or -1 if the socket is not bound yet.
|
|
||||||
*/
|
|
||||||
public int getPort() {
|
|
||||||
return serverSocket.getLocalPort();
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,39 +8,39 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Created by Jonas on 22/09/18.
|
* Created by Jonas on 22/09/18.
|
||||||
*/
|
*/
|
||||||
public class ExecutionInfo {
|
public final class ExecutionInfo {
|
||||||
|
|
||||||
private static Map<String, String[]> extensionTypeToExecutionCommand;
|
private static final Map<String, String[]> EXTENSION_TYPE_TO_EXECUTION_COMMAND;
|
||||||
public final static List<String> ALLOWEDEXTENSIONTYPES;
|
|
||||||
public final static String EXTENSIONSDIRECTORY = "Extensions";
|
public final static List<String> ALLOWED_EXTENSION_TYPES;
|
||||||
|
public final static String EXTENSIONS_DIRECTORY = "Extensions";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
extensionTypeToExecutionCommand = new HashMap<>();
|
|
||||||
extensionTypeToExecutionCommand.put("*.jar", new String[]{"java", "-jar", "{path}"});
|
|
||||||
extensionTypeToExecutionCommand.put("*.py", new String[]{"python", "{path}"});
|
|
||||||
extensionTypeToExecutionCommand.put("*.py3", new String[]{"python3", "{path}"});
|
|
||||||
extensionTypeToExecutionCommand.put("*.sh", new String[]{"{path}"});
|
|
||||||
extensionTypeToExecutionCommand.put("*.exe", new String[]{"{path}"});
|
|
||||||
extensionTypeToExecutionCommand.put("*.js", new String[]{"node", "{path}"});
|
|
||||||
|
|
||||||
String[] extraArgs = {"-p", "{port}", "-f", "{filename}", "-c", "{cookie}"};
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND = new HashMap<>();
|
||||||
for(String type : extensionTypeToExecutionCommand.keySet()) {
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.jar", new String[]{"java", "-jar", "{path}"});
|
||||||
String[] commandShort = extensionTypeToExecutionCommand.get(type);
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py", new String[]{"python", "{path}"});
|
||||||
String[] combined = new String[extraArgs.length + commandShort.length];
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py3", new String[]{"python3", "{path}"});
|
||||||
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.sh", new String[]{"{path}"});
|
||||||
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.exe", new String[]{"{path}"});
|
||||||
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.js", new String[]{"node", "{path}"});
|
||||||
|
|
||||||
|
final String[] extraArgs = {"-p", "{port}", "-f", "{filename}", "-c", "{cookie}"};
|
||||||
|
|
||||||
|
for(String type : EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet()) {
|
||||||
|
|
||||||
|
final String[] commandShort = EXTENSION_TYPE_TO_EXECUTION_COMMAND.get(type);
|
||||||
|
final String[] combined = new String[extraArgs.length + commandShort.length];
|
||||||
System.arraycopy(commandShort, 0, combined, 0, commandShort.length);
|
System.arraycopy(commandShort, 0, combined, 0, commandShort.length);
|
||||||
System.arraycopy(extraArgs, 0, combined, commandShort.length, extraArgs.length);
|
System.arraycopy(extraArgs, 0, combined, commandShort.length, extraArgs.length);
|
||||||
|
|
||||||
extensionTypeToExecutionCommand.put(
|
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put(type, combined);
|
||||||
type,
|
|
||||||
combined
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ALLOWEDEXTENSIONTYPES = new ArrayList<>(extensionTypeToExecutionCommand.keySet());
|
ALLOWED_EXTENSION_TYPES = new ArrayList<>(EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getExecutionCommand(String type) {
|
public static String[] getExecutionCommand(String type) {
|
||||||
return extensionTypeToExecutionCommand.get(type);
|
return EXTENSION_TYPE_TO_EXECUTION_COMMAND.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package gearth.services.extension_handler.extensions.implementations.network.exe
|
|||||||
/**
|
/**
|
||||||
* Created by Jonas on 22/09/18.
|
* Created by Jonas on 22/09/18.
|
||||||
*/
|
*/
|
||||||
public class ExtensionRunnerFactory {
|
public final class ExtensionRunnerFactory {
|
||||||
|
|
||||||
private static ExtensionRunner runner = new NormalExtensionRunner();
|
private static final ExtensionRunner runner = new NormalExtensionRunner();
|
||||||
|
|
||||||
public static ExtensionRunner get() {
|
public static ExtensionRunner get() {
|
||||||
return runner;
|
return runner;
|
||||||
|
@ -1,102 +1,105 @@
|
|||||||
package gearth.services.extension_handler.extensions.implementations.network.executer;
|
package gearth.services.extension_handler.extensions.implementations.network.executer;
|
||||||
|
|
||||||
import gearth.GEarth;
|
import gearth.GEarth;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator;
|
||||||
import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTools;
|
import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTools;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.*;
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Jonas on 22/09/18.
|
* Created by Jonas on 22/09/18.
|
||||||
*/
|
*/
|
||||||
public class NormalExtensionRunner implements ExtensionRunner {
|
public final class NormalExtensionRunner implements ExtensionRunner {
|
||||||
|
|
||||||
public static final String JARPATH;
|
private final static Logger LOGGER = LoggerFactory.getLogger(NormalExtensionRunner.class);
|
||||||
|
|
||||||
|
public static final String JAR_PATH;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
final URL url = getLocation();
|
||||||
String value;
|
String value;
|
||||||
try {
|
try {
|
||||||
value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
|
value = new File(url.toURI()).getParent();
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParent();
|
value = new File(url.getPath()).getParent();
|
||||||
e.printStackTrace();
|
LOGGER.warn("Failed to load JAR_PATH from url {} as URI, using Path instead", url, e);
|
||||||
}
|
}
|
||||||
JARPATH = value;
|
JAR_PATH = value;
|
||||||
|
LOGGER.debug("Set JAR_PATH as {}", JAR_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void runAllExtensions(int port) {
|
public void runAllExtensions(int port) {
|
||||||
if (dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)){
|
|
||||||
File folder =
|
|
||||||
new File(JARPATH +
|
|
||||||
FileSystems.getDefault().getSeparator()+
|
|
||||||
ExecutionInfo.EXTENSIONSDIRECTORY);
|
|
||||||
|
|
||||||
File[] childs = folder.listFiles();
|
if (dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)) {
|
||||||
for (File file : childs) {
|
|
||||||
|
final File extensionsDirectory = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toFile();
|
||||||
|
final File[] extensionFiles = extensionsDirectory.listFiles();
|
||||||
|
|
||||||
|
if (extensionFiles == null) {
|
||||||
|
LOGGER.error("Provided extensionsDirectory does not exist (extensionsDirectory={})", extensionsDirectory);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : extensionFiles)
|
||||||
tryRunExtension(file.getPath(), port);
|
tryRunExtension(file.getPath(), port);
|
||||||
}
|
} else
|
||||||
}
|
LOGGER.warn("Did not run extensions because extensions directory does not exist at {}", ExecutionInfo.EXTENSIONS_DIRECTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void installAndRunExtension(String path, int port) {
|
public void installAndRunExtension(String stringPath, int port) {
|
||||||
if (!dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)) {
|
|
||||||
createDirectory(ExecutionInfo.EXTENSIONSDIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY))
|
||||||
|
tryCreateDirectory(ExecutionInfo.EXTENSIONS_DIRECTORY);
|
||||||
|
|
||||||
String name = Paths.get(path).getFileName().toString();
|
final Path path = Paths.get(stringPath);
|
||||||
String[] split = name.split("\\.");
|
final String name = path.getFileName().toString();
|
||||||
String ext = "*." + split[split.length - 1];
|
final String[] split = name.split("\\.");
|
||||||
|
final String ext = "*." + split[split.length - 1];
|
||||||
|
|
||||||
String realname = String.join(".",Arrays.copyOf(split, split.length-1));
|
final String realName = String.join(".", Arrays.copyOf(split, split.length - 1));
|
||||||
String newname = realname + "-" + getRandomString() + ext.substring(1);
|
final String newName = realName + "-" + getRandomString() + ext.substring(1);
|
||||||
|
|
||||||
Path originalPath = Paths.get(path);
|
final Path newPath = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, newName);
|
||||||
Path newPath = Paths.get(
|
|
||||||
JARPATH,
|
|
||||||
ExecutionInfo.EXTENSIONSDIRECTORY,
|
|
||||||
newname
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.copy(
|
|
||||||
originalPath,
|
|
||||||
newPath
|
|
||||||
);
|
|
||||||
|
|
||||||
// addExecPermission(newPath.toString());
|
Files.copy(path, newPath);
|
||||||
|
|
||||||
tryRunExtension(newPath.toString(), port);
|
tryRunExtension(newPath.toString(), port);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryRunExtension(String path, int port) {
|
public void tryRunExtension(String path, int port) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (new File(path).isDirectory()) {
|
if (new File(path).isDirectory()) {
|
||||||
// this extension is installed from the extension store and requires
|
// this extension is installed from the extension store and requires different behavior
|
||||||
// different behavior
|
|
||||||
StoreExtensionTools.executeExtension(path, port);
|
StoreExtensionTools.executeExtension(path, port);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String filename = Paths.get(path).getFileName().toString();
|
final String filename = Paths.get(path).getFileName().toString();
|
||||||
|
final String[] execCommand = ExecutionInfo
|
||||||
String[] execCommand = ExecutionInfo.getExecutionCommand(getFileExtension(path));
|
.getExecutionCommand(getFileExtension(path))
|
||||||
execCommand = Arrays.copyOf(execCommand, execCommand.length);
|
.clone();
|
||||||
String cookie = Authenticator.generateCookieForExtension(filename);
|
final String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(filename);
|
||||||
for (int i = 0; i < execCommand.length; i++) {
|
for (int i = 0; i < execCommand.length; i++) {
|
||||||
execCommand[i] = execCommand[i]
|
execCommand[i] = execCommand[i]
|
||||||
.replace("{path}", path)
|
.replace("{path}", path)
|
||||||
@ -104,104 +107,92 @@ public class NormalExtensionRunner implements ExtensionRunner {
|
|||||||
.replace("{filename}", filename)
|
.replace("{filename}", filename)
|
||||||
.replace("{cookie}", cookie);
|
.replace("{cookie}", cookie);
|
||||||
}
|
}
|
||||||
ProcessBuilder pb = new ProcessBuilder(execCommand);
|
|
||||||
// Process proc = Runtime.getRuntime().exec(execCommand);
|
|
||||||
Process proc = pb.start();
|
|
||||||
|
|
||||||
maybeLogExtension(path, proc);
|
final ProcessBuilder processBuilder = new ProcessBuilder(execCommand);
|
||||||
|
final Process process = processBuilder.start();
|
||||||
|
|
||||||
|
maybeLogExtension(path, process);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("Failed to run extension at path {} using port {}", path, port, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void maybeLogExtension(String path, Process proc) {
|
|
||||||
|
public static void maybeLogExtension(String path, Process process) {
|
||||||
if (GEarth.hasFlag(ExtensionRunner.SHOW_EXTENSIONS_LOG)) {
|
if (GEarth.hasFlag(ExtensionRunner.SHOW_EXTENSIONS_LOG)) {
|
||||||
String sep = "" + System.lineSeparator();
|
|
||||||
synchronized (System.out) {
|
|
||||||
System.out.println(path + sep + "Launching" + sep + "----------" + sep);
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedReader stdInput = new BufferedReader(new
|
final Logger logger = LoggerFactory.getLogger(path);
|
||||||
InputStreamReader(proc.getInputStream()));
|
|
||||||
|
|
||||||
|
logger.info("Launching...");
|
||||||
|
|
||||||
|
final BufferedReader processInputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
String line;
|
String line;
|
||||||
while((line = stdInput.readLine()) != null) {
|
while ((line = processInputReader.readLine()) != null)
|
||||||
synchronized (System.out) {
|
logger.info(line);
|
||||||
System.out.println(path + sep + "Output" + sep + line + sep + "----------" + sep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("Failed to read input line from process {}", process, e);
|
||||||
}
|
}
|
||||||
}).start();
|
}, path+"-input").start();
|
||||||
|
|
||||||
BufferedReader stdError = new BufferedReader(new
|
|
||||||
InputStreamReader(proc.getErrorStream()));
|
|
||||||
|
|
||||||
|
final BufferedReader processErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
String line;
|
String line;
|
||||||
while((line = stdError.readLine()) != null) {
|
while ((line = processErrorReader.readLine()) != null)
|
||||||
synchronized (System.out) {
|
logger.error(line);
|
||||||
System.out.println(path + sep + "Error" + sep + line + sep + "----------" + sep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("Failed to read error line from process {}", process, e);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uninstallExtension(String filename) {
|
public void uninstallExtension(String filename) {
|
||||||
try {
|
try {
|
||||||
Path path = Paths.get(JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY, filename);
|
final Path path = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, filename);
|
||||||
if (new File(path.toString()).isDirectory()) {
|
if (new File(path.toString()).isDirectory()) {
|
||||||
// is installed through extension store
|
// is installed through extension store
|
||||||
StoreExtensionTools.removeExtension(path.toString());
|
StoreExtensionTools.removeExtension(path.toString());
|
||||||
}
|
} else
|
||||||
else {
|
|
||||||
Files.delete(path);
|
Files.delete(path);
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("Failed to uninstall extension at {}", filename, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void addExecPermission(String path) {
|
private static void tryCreateDirectory(String path) {
|
||||||
// //not needed at first sight
|
if (!dirExists(path)) {
|
||||||
// }
|
try {
|
||||||
|
Files.createDirectories(Paths.get(JAR_PATH, path));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Failed to create directory at {}", path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getFileExtension(String path) {
|
private static boolean dirExists(String dir) {
|
||||||
String name = Paths.get(path).getFileName().toString();
|
return Files.isDirectory(Paths.get(JAR_PATH, dir));
|
||||||
String[] split = name.split("\\.");
|
}
|
||||||
|
|
||||||
|
private static URL getLocation() {
|
||||||
|
return GEarth.class.getProtectionDomain().getCodeSource().getLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFileExtension(String path) {
|
||||||
|
final String name = Paths.get(path).getFileName().toString();
|
||||||
|
final String[] split = name.split("\\.");
|
||||||
return "*." + split[split.length - 1];
|
return "*." + split[split.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean dirExists(String dir) {
|
private static String getRandomString() {
|
||||||
return Files.isDirectory(Paths.get(JARPATH, dir));
|
final StringBuilder builder = new StringBuilder();
|
||||||
}
|
final Random random = new Random();
|
||||||
private void createDirectory(String dir) {
|
for (int i = 0; i < 12; i++)
|
||||||
if (!dirExists(dir)) {
|
builder.append(random.nextInt(10));
|
||||||
try {
|
|
||||||
Files.createDirectories(Paths.get(JARPATH, dir));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private String getRandomString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
Random r = new Random();
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
builder.append(r.nextInt(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package gearth.services.internal_extensions.extensionstore.application.entities.extensiondetails;
|
package gearth.services.internal_extensions.extensionstore.application.entities.extensiondetails;
|
||||||
|
|
||||||
import gearth.GEarth;
|
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner;
|
|
||||||
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
||||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||||
import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
||||||
@ -16,13 +14,11 @@ import gearth.ui.titlebar.TitleBarController;
|
|||||||
import gearth.ui.translations.LanguageBundle;
|
import gearth.ui.translations.LanguageBundle;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -137,7 +133,7 @@ public class StoreExtensionDetailsOverview extends HOverview {
|
|||||||
@Override
|
@Override
|
||||||
public void success(String installationFolder) {
|
public void success(String installationFolder) {
|
||||||
Platform.runLater(() -> successPopup(modeString));
|
Platform.runLater(() -> successPopup(modeString));
|
||||||
StoreExtensionTools.executeExtension(installationFolder, NetworkExtensionsProducer.extensionPort);
|
StoreExtensionTools.executeExtension(installationFolder, ExtensionProducerFactory.getExtensionServer().getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,7 +2,7 @@ package gearth.services.internal_extensions.extensionstore.tools;
|
|||||||
|
|
||||||
import gearth.GEarth;
|
import gearth.GEarth;
|
||||||
import gearth.misc.OSValidator;
|
import gearth.misc.OSValidator;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo;
|
import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner;
|
import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner;
|
||||||
import gearth.services.internal_extensions.extensionstore.repository.StoreFetch;
|
import gearth.services.internal_extensions.extensionstore.repository.StoreFetch;
|
||||||
@ -34,7 +34,7 @@ public class StoreExtensionTools {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static String EXTENSIONS_PATH = Paths.get(NormalExtensionRunner.JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY).toString();
|
public final static String EXTENSIONS_PATH = Paths.get(NormalExtensionRunner.JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toString();
|
||||||
|
|
||||||
|
|
||||||
public static void executeExtension(String extensionPath, int port) {
|
public static void executeExtension(String extensionPath, int port) {
|
||||||
@ -42,7 +42,7 @@ public class StoreExtensionTools {
|
|||||||
String installedExtensionId = Paths.get(extensionPath).getFileName().toString();
|
String installedExtensionId = Paths.get(extensionPath).getFileName().toString();
|
||||||
|
|
||||||
String commandPath = Paths.get(extensionPath, "command.txt").toString();
|
String commandPath = Paths.get(extensionPath, "command.txt").toString();
|
||||||
String cookie = Authenticator.generateCookieForExtension(installedExtensionId);
|
String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(installedExtensionId);
|
||||||
List<String> command = new JSONArray(FileUtils.readFileToString(new File(commandPath), "UTF-8"))
|
List<String> command = new JSONArray(FileUtils.readFileToString(new File(commandPath), "UTF-8"))
|
||||||
.toList().stream().map(o -> (String)o).map(s -> s
|
.toList().stream().map(o -> (String)o).map(s -> s
|
||||||
.replace("{port}", port+"")
|
.replace("{port}", port+"")
|
||||||
|
@ -100,7 +100,7 @@ public class ExtensionItemContainer extends GridPane {
|
|||||||
reloadButton.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
reloadButton.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||||
reloadButton.setVisible(false);
|
reloadButton.setVisible(false);
|
||||||
ExtensionRunner runner = ExtensionRunnerFactory.get();
|
ExtensionRunner runner = ExtensionRunnerFactory.get();
|
||||||
runner.tryRunExtension(Paths.get(NormalExtensionRunner.JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY, item.getFileName()).toString(), port);
|
runner.tryRunExtension(Paths.get(NormalExtensionRunner.JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, item.getFileName()).toString(), port);
|
||||||
});
|
});
|
||||||
|
|
||||||
DeleteButton deleteButton = new DeleteButton();
|
DeleteButton deleteButton = new DeleteButton();
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package gearth.ui.subforms.extensions;
|
package gearth.ui.subforms.extensions;
|
||||||
|
|
||||||
import gearth.GEarth;
|
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
import gearth.services.extension_handler.extensions.ExtensionListener;
|
import gearth.services.extension_handler.extensions.ExtensionListener;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo;
|
import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunner;
|
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunner;
|
||||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunnerFactory;
|
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunnerFactory;
|
||||||
@ -39,7 +38,7 @@ public class ExtensionsController extends SubForm {
|
|||||||
|
|
||||||
private ExtensionRunner extensionRunner = null;
|
private ExtensionRunner extensionRunner = null;
|
||||||
private ExtensionHandler extensionHandler;
|
private ExtensionHandler extensionHandler;
|
||||||
private NetworkExtensionsProducer networkExtensionsProducer; // needed for port
|
private NetworkExtensionServer networkExtensionsProducer; // needed for port
|
||||||
private ExtensionLogger extensionLogger = null;
|
private ExtensionLogger extensionLogger = null;
|
||||||
|
|
||||||
public Label lbl_tableTitle, lbl_tableDesc, lbl_tableAuthor, lbl_tableVersion, lbl_tableEdit, lbl_port;
|
public Label lbl_tableTitle, lbl_tableDesc, lbl_tableAuthor, lbl_tableVersion, lbl_tableEdit, lbl_port;
|
||||||
@ -61,8 +60,8 @@ public class ExtensionsController extends SubForm {
|
|||||||
|
|
||||||
//noinspection OptionalGetWithoutIsPresent
|
//noinspection OptionalGetWithoutIsPresent
|
||||||
networkExtensionsProducer
|
networkExtensionsProducer
|
||||||
= (NetworkExtensionsProducer) extensionHandler.getExtensionProducers().stream()
|
= (NetworkExtensionServer) extensionHandler.getExtensionProducers().stream()
|
||||||
.filter(producer1 -> producer1 instanceof NetworkExtensionsProducer)
|
.filter(producer1 -> producer1 instanceof NetworkExtensionServer)
|
||||||
.findFirst().get();
|
.findFirst().get();
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +86,7 @@ public class ExtensionsController extends SubForm {
|
|||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
fileChooser.setTitle(LanguageBundle.get("tab.extensions.button.install.windowtitle"));
|
fileChooser.setTitle(LanguageBundle.get("tab.extensions.button.install.windowtitle"));
|
||||||
fileChooser.getExtensionFilters().addAll(
|
fileChooser.getExtensionFilters().addAll(
|
||||||
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.extensions.button.install.filetype"), ExecutionInfo.ALLOWEDEXTENSIONTYPES));
|
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.extensions.button.install.filetype"), ExecutionInfo.ALLOWED_EXTENSION_TYPES));
|
||||||
File selectedFile = fileChooser.showOpenDialog(parentController.getStage());
|
File selectedFile = fileChooser.showOpenDialog(parentController.getStage());
|
||||||
if (selectedFile != null) {
|
if (selectedFile != null) {
|
||||||
extensionRunner.installAndRunExtension(selectedFile.getPath(), networkExtensionsProducer.getPort());
|
extensionRunner.installAndRunExtension(selectedFile.getPath(), networkExtensionsProducer.getPort());
|
||||||
@ -123,7 +122,7 @@ public class ExtensionsController extends SubForm {
|
|||||||
GPythonShell shell = new GPythonShell(
|
GPythonShell shell = new GPythonShell(
|
||||||
String.format("%s %d", LanguageBundle.get("tab.extensions.button.pythonshell.windowtitle"),gpytonShellCounter++),
|
String.format("%s %d", LanguageBundle.get("tab.extensions.button.pythonshell.windowtitle"),gpytonShellCounter++),
|
||||||
networkExtensionsProducer.getPort(),
|
networkExtensionsProducer.getPort(),
|
||||||
Authenticator.generatePermanentCookie()
|
NetworkExtensionAuthenticator.generatePermanentCookie()
|
||||||
);
|
);
|
||||||
shell.launch((b) -> {
|
shell.launch((b) -> {
|
||||||
pythonShellLaunching = false;
|
pythonShellLaunching = false;
|
||||||
|
Loading…
Reference in New Issue
Block a user