complete rework of extensions internally

This commit is contained in:
sirjonasxx 2020-04-26 02:35:54 +02:00
parent 9b3a45b68f
commit 15628b5eec
24 changed files with 1016 additions and 648 deletions

View File

@ -2,6 +2,8 @@ package gearth.extensions;
import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
import gearth.services.extensionserver.ExtensionServer;
import gearth.services.extensionserver.extensions.network.NetworkExtensionInfo;
import gearth.ui.extensions.Extensions;
import java.io.*;
@ -118,10 +120,10 @@ public abstract class Extension implements IExtension{
packet.fixLength();
if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.INFOREQUEST) {
if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST) {
ExtensionInfo info = getInfoAnnotations();
HPacket response = new HPacket(Extensions.INCOMING_MESSAGES_IDS.EXTENSIONINFO);
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO);
response.appendString(info.Title())
.appendString(info.Author())
.appendString(info.Version())
@ -134,7 +136,7 @@ public abstract class Extension implements IExtension{
.appendBoolean(canDelete);
writeToStream(response.toBytes());
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) {
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) {
String host = packet.readString();
int connectionPort = packet.readInteger();
String hotelVersion = packet.readString();
@ -142,10 +144,10 @@ public abstract class Extension implements IExtension{
notifyConnectionListeners(host, connectionPort, hotelVersion, harbleMessagesPath);
onStartConnection();
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.CONNECTIONEND) {
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND) {
onEndConnection();
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.FLAGSCHECK) {
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK) {
// body = an array of G-Earths gearth flags
if (flagRequestCallback != null) {
int arraysize = packet.readInteger();
@ -157,16 +159,13 @@ public abstract class Extension implements IExtension{
}
flagRequestCallback = null;
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.INIT) {
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT) {
initExtension();
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.FREEFLOW) {
// nothing to be done yet
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK) {
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK) {
onClick();
}
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT) {
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT) {
String stringifiedMessage = packet.readLongString();
HMessage habboMessage = new HMessage(stringifiedMessage);
HPacket habboPacket = habboMessage.getPacket();
@ -200,7 +199,7 @@ public abstract class Extension implements IExtension{
}
habboMessage.getPacket().resetReadIndex();
HPacket response = new HPacket(Extensions.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET);
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET);
response.appendLongString(habboMessage.stringify());
writeToStream(response.toBytes());
@ -247,7 +246,7 @@ public abstract class Extension implements IExtension{
return send(packet, HMessage.Direction.TOSERVER);
}
private boolean send(HPacket packet, HMessage.Direction direction) {
HPacket packet1 = new HPacket(Extensions.INCOMING_MESSAGES_IDS.SENDMESSAGE);
HPacket packet1 = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE);
packet1.appendByte(direction == HMessage.Direction.TOCLIENT ? (byte)0 : (byte)1);
packet1.appendInt(packet.getBytesLength());
packet1.appendBytes(packet.toBytes());
@ -307,7 +306,7 @@ public abstract class Extension implements IExtension{
* @param s the text to be written
*/
public void writeToConsole(String s) {
HPacket packet = new HPacket(Extensions.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG);
HPacket packet = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG);
packet.appendString(s);
try {
writeToStream(packet.toBytes());

View File

@ -3,9 +3,6 @@ package gearth.extensions.parsers;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Jeunez on 8/01/2019.
*/
public enum HEntityType {
HABBO(1),
PET(2),

View File

@ -0,0 +1,220 @@
package gearth.services.extensionserver;
import gearth.Main;
import gearth.misc.harble_api.HarbleAPIFetcher;
import gearth.protocol.HConnection;
import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
import gearth.services.extensionserver.extensions.ExtensionListener;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducer;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducerFactory;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducerObserver;
import gearth.services.extensionserver.extensions.GEarthExtension;
import java.util.*;
public class ExtensionServer {
private final List<GEarthExtension> gEarthExtensions = new ArrayList<>();
private final HConnection hConnection;
private List<ExtensionProducer> extensionProducers;
public ExtensionServer(HConnection hConnection) {
this.hConnection = hConnection;
initialize();
}
private void initialize() {
hConnection.addStateChangeListener((oldState, newState) -> {
if (newState == HConnection.State.CONNECTED) {
HarbleAPIFetcher.fetch(hConnection.getHotelVersion());
synchronized (gEarthExtensions) {
for (GEarthExtension extension : gEarthExtensions) {
extension.connectionStart(
hConnection.getDomain(),
hConnection.getServerPort(),
hConnection.getHotelVersion(),
HarbleAPIFetcher.HARBLEAPI == null ? "null" : HarbleAPIFetcher.HARBLEAPI.getPath()
);
}
}
}
if (oldState == HConnection.State.CONNECTED) {
synchronized (hConnection) {
for (GEarthExtension extension : gEarthExtensions) {
extension.connectionEnd();
}
}
}
});
hConnection.addTrafficListener(1, message -> {
Set<GEarthExtension> collection;
synchronized (gEarthExtensions) {
collection = new HashSet<>(gEarthExtensions);
}
HMessage result = new HMessage(message);
boolean[] isblock = new boolean[1];
synchronized (collection) {
for (GEarthExtension extension : collection) {
ExtensionListener respondCallback = new ExtensionListener() {
@Override
public void manipulatedPacket(HMessage responseMessage) {
if (responseMessage.getDestination() == message.getDestination() && responseMessage.getIndex() == message.getIndex()) {
synchronized (result) {
if (!message.equals(responseMessage)) {
result.constructFromHMessage(responseMessage);
}
if (responseMessage.isBlocked()) {
isblock[0] = true;
}
synchronized (collection) {
collection.remove(extension);
}
extension.removeExtensionListener(this);
}
}
}
};
extension.registerExtensionListener(respondCallback);
}
}
Set<GEarthExtension> collection2;
synchronized (collection) {
collection2 = new HashSet<>(collection);
}
synchronized (collection2) {
for (GEarthExtension extension : collection2) {
extension.packetIntercept(new HMessage(message));
}
}
//block untill all extensions have responded
List<GEarthExtension> willdelete = new ArrayList<>();
while (true) {
synchronized (collection) {
if (collection.isEmpty()) {
break;
}
synchronized (gEarthExtensions) {
for (GEarthExtension extension : collection) {
if (!gEarthExtensions.contains(extension)) willdelete.add(extension);
}
}
for (int i = willdelete.size() - 1; i >= 0; i--) {
collection.remove(willdelete.get(i));
willdelete.remove(i);
}
}
try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
}
message.constructFromHMessage(result);
if (isblock[0]) {
message.setBlocked(true);
}
});
extensionProducers = ExtensionProducerFactory.getAll();
extensionProducers.forEach(this::initializeExtensionProducer);
}
private void initializeExtensionProducer(ExtensionProducer producer) {
producer.startProducing(new ExtensionProducerObserver() {
@Override
public void onExtensionConnect(GEarthExtension extension) {
synchronized (gEarthExtensions) {
gEarthExtensions.add(extension);
}
ExtensionListener listener = new ExtensionListener() {
@Override
public void flagsRequest() {
extension.provideFlags(Main.args);
}
@Override
public void sendMessage(HMessage.Direction direction, HPacket packet) {
if (direction == HMessage.Direction.TOCLIENT) {
hConnection.sendToClientAsync(packet);
}
else {
hConnection.sendToServerAsync(packet);
}
}
@Override
public void log(String text) {
// todo make simple logger for Extensions, the implementation now is temporary
System.out.println(text);
}
@Override
public void hasClosed() {
synchronized (gEarthExtensions) {
gEarthExtensions.remove(extension);
}
extension.removeExtensionListener(this);
extension.delete();
}
};
extension.registerExtensionListener(listener);
extension.init();
if (hConnection.getState() == HConnection.State.CONNECTED) {
extension.connectionStart(
hConnection.getDomain(),
hConnection.getServerPort(),
hConnection.getHotelVersion(),
HarbleAPIFetcher.HARBLEAPI == null ? "null" : HarbleAPIFetcher.HARBLEAPI.getPath()
);
}
extension.onRemoveClick(observable -> extension.close());
extension.onClick(observable -> extension.doubleclick());
notifyExtensionConnectListeners(extension);
}
});
}
public List<ExtensionProducer> getExtensionProducers() {
return extensionProducers;
}
public interface ExtensionConnectListener {
void extensionConnected(GEarthExtension e);
}
private List<ExtensionConnectListener> listeners = new ArrayList<>();
public void onExtensionConnected(ExtensionConnectListener extensionConnectListener) {
synchronized (gEarthExtensions) {
for (GEarthExtension gEarthExtension : gEarthExtensions) {
extensionConnectListener.extensionConnected(gEarthExtension);
}
}
listeners.add(extensionConnectListener);
}
public void removeExtensionConnectListener(ExtensionConnectListener extensionConnectListener) {
listeners.remove(extensionConnectListener);
}
private void notifyExtensionConnectListeners(GEarthExtension extension) {
for (int i = listeners.size() - 1; i >= 0; i--) {
listeners.get(i).extensionConnected(extension);
}
}
}

View File

@ -0,0 +1,15 @@
package gearth.services.extensionserver.extensions;
import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
public abstract class ExtensionListener {
// override whatever you need
protected void manipulatedPacket(HMessage hMessage) {}
protected void flagsRequest() {}
protected void sendMessage(HMessage.Direction direction, HPacket packet) {}
protected void log(String text) {}
protected void hasClosed() {}
}

View File

@ -0,0 +1,143 @@
package gearth.services.extensionserver.extensions;
import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
import javafx.beans.InvalidationListener;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public abstract class GEarthExtension {
// ------ static extension information --------
public abstract String getAuthor();
public abstract String getDescription();
public abstract String getTitle();
public abstract String getVersion();
public String getFileName() {
return ""; // override in extensions over network if executed from file
}
public abstract boolean isFireButtonUsed();
public abstract boolean isDeleteButtonVisible();
public abstract boolean isLeaveButtonVisible();
public abstract boolean isInstalledExtension();
// --------------------------------------------
// ------- actions you can perform towards the extension ---------
public abstract void doubleclick();
public abstract void packetIntercept(HMessage hMessage);
public abstract void provideFlags(String[] flags);
public abstract void connectionStart(String host, int port, String hotelVersion, String harbleMessagesPath);
public abstract void connectionEnd();
public abstract void init();
public abstract void close();
// ---------------------------------------------------------------
// ----------------- listen to the extension ---------------------
protected final List<ExtensionListener> extensionListeners = new ArrayList<>();
public void registerExtensionListener(ExtensionListener listener) {
this.extensionListeners.add(listener);
}
public void removeExtensionListener(ExtensionListener listener) {
this.extensionListeners.remove(listener);
}
private void notifyListeners(Consumer<ExtensionListener> consumer) {
for (int i = extensionListeners.size() - 1; i >= 0; i--) {
consumer.accept(extensionListeners.get(i));
}
extensionListeners.forEach(consumer);
}
protected void sendManipulatedPacket(HMessage hMessage) {
int orgIndex = hMessage.getPacket().getReadIndex();
notifyListeners(listener -> {
hMessage.getPacket().setReadIndex(6);
listener.manipulatedPacket(hMessage);
});
hMessage.getPacket().setReadIndex(orgIndex);
}
protected void requestFlags() {
notifyListeners(ExtensionListener::flagsRequest);
}
protected void sendMessage(HMessage.Direction direction, HPacket packet) {
int orgIndex = packet.getReadIndex();
notifyListeners(listener -> {
packet.setReadIndex(6);
listener.sendMessage(direction, packet);
});
packet.setReadIndex(orgIndex);
}
protected void log(String text) {
notifyListeners(listener -> listener.log(text));
}
protected void hasClosed() {
notifyListeners(ExtensionListener::hasClosed);
}
// --------------------------------------------------------------------
// ----------- methods for interaction with G-Earth UI, don't use/change them ----------------
private final List<InvalidationListener> onRemoveClickListener = new ArrayList<>();
public void onRemoveClick(InvalidationListener listener) {
synchronized (onRemoveClickListener) {
onRemoveClickListener.add(listener);
}
}
public void isRemoveClickTrigger() {
synchronized (onRemoveClickListener) {
for (int i = onRemoveClickListener.size() - 1; i >= 0; i--) {
onRemoveClickListener.get(i).invalidated(null);
}
}
}
private final List<InvalidationListener> onClickListener = new ArrayList<>();
public void onClick(InvalidationListener listener) {
synchronized (onClickListener) {
onClickListener.add(listener);
}
}
public void isClickTrigger() {
synchronized (onClickListener) {
for (int i = onClickListener.size() - 1; i >= 0; i--) {
onClickListener.get(i).invalidated(null);
}
}
}
private final List<InvalidationListener> onDeleteListeners = new ArrayList<>();
public void onDelete(InvalidationListener listener) {
synchronized (onDeleteListeners) {
onDeleteListeners.add(listener);
}
}
public void delete() {
synchronized (onDeleteListeners) {
for (int i = onDeleteListeners.size() - 1; i >= 0; i--) {
onDeleteListeners.get(i).invalidated(null);
}
}
}
// ----------------------------------------------------------------------------------------
}

View File

@ -0,0 +1,7 @@
package gearth.services.extensionserver.extensions.extensionproducers;
public interface ExtensionProducer {
void startProducing(ExtensionProducerObserver observer);
}

View File

@ -0,0 +1,20 @@
package gearth.services.extensionserver.extensions.extensionproducers;
import gearth.services.extensionserver.extensions.network.NetworkExtensionsProducer;
import gearth.services.extensionserver.extensions.simple.SimpleExtensionProducer;
import java.util.Arrays;
import java.util.List;
public class ExtensionProducerFactory {
// returns one of every ExtensionProducer class we have created, to support all types of extensions
public static List<ExtensionProducer> getAll() {
return Arrays.asList(
new NetworkExtensionsProducer(),
new SimpleExtensionProducer()
);
}
}

View File

@ -0,0 +1,8 @@
package gearth.services.extensionserver.extensions.extensionproducers;
import gearth.services.extensionserver.extensions.GEarthExtension;
import gearth.services.extensionserver.extensions.network.NetworkExtension;
public interface ExtensionProducerObserver {
void onExtensionConnect(GEarthExtension extension);
}

View File

@ -0,0 +1,219 @@
package gearth.services.extensionserver.extensions.network;
import gearth.Main;
import gearth.protocol.HMessage;
import gearth.services.extensionserver.extensions.GEarthExtension;
import javafx.beans.InvalidationListener;
import gearth.protocol.HPacket;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import gearth.services.extensionserver.extensions.network.authentication.Authenticator;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 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.extensionListeners) {
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());
}
}
}
} catch (IOException e) {
// An extension disconnected, which is OK
} finally {
synchronized (selff.extensionListeners) {
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 harbleMessagesPath) {
sendMessage(
new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART)
.appendString(host)
.appendInt(port)
.appendString(hotelVersion)
.appendString(harbleMessagesPath)
);
}
@Override
public void connectionEnd() {
sendMessage(
new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND)
);
}
@Override
public void init() {
sendMessage(
new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT)
);
}
@Override
public void close() {
try {
connection.close();
} catch (IOException ignored) { }
}
}

View File

@ -0,0 +1,101 @@
package gearth.services.extensionserver.extensions.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; // backend: implemented
public static final int PACKETINTERCEPT = 3; // backend: implemented
public static final int FLAGSCHECK = 4; // backend: implemented
public static final int CONNECTIONSTART = 5; // backend: implemented
public static final int CONNECTIONEND = 6; // backend: implemented
public static final int INIT = 7; // backend: implemented
}
public static class INCOMING_MESSAGES_IDS {
public static final int EXTENSIONINFO = 1; // backend: implemented
public static final int MANIPULATEDPACKET = 2; // backend: implemented
public static final int REQUESTFLAGS = 3; // backend: implemented
public static final int SENDMESSAGE = 4; // backend: implemented
public static final int EXTENSIONCONSOLELOG = 98;
}
}

View File

@ -0,0 +1,95 @@
package gearth.services.extensionserver.extensions.network;
import gearth.protocol.HPacket;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducer;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducerObserver;
import gearth.services.extensionserver.extensions.network.authentication.Authenticator;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Jonas on 21/06/18.
*/
public class NetworkExtensionsProducer implements ExtensionProducer {
private ServerSocket serverSocket;
@Override
public void startProducing(ExtensionProducerObserver observer) {
// serverSocket = new ServerSocket(0);
int port = 9092;
boolean serverSetup = false;
while (!serverSetup) {
serverSetup = createServer(port);
port++;
}
new Thread(() -> {
try {
while (!serverSocket.isClosed()) {
Socket extensionSocket = serverSocket.accept();
new Thread(() -> {
try {
synchronized (extensionSocket) {
extensionSocket.getOutputStream().write((new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes());
}
InputStream inputStream = extensionSocket.getInputStream();
DataInputStream dIn = new DataInputStream(inputStream);
while (!extensionSocket.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 packet = new HPacket(headerandbody);
packet.fixLength();
if (packet.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO) {
NetworkExtension gEarthExtension = new NetworkExtension(
packet,
extensionSocket
);
if (Authenticator.evaluate(gEarthExtension)) {
observer.onExtensionConnect(gEarthExtension);
}
else {
gEarthExtension.close(); //you shall not pass...
}
break;
}
}
} catch (IOException ignored) {}
}).start();
}
} catch (IOException e) {e.printStackTrace();}
}).start();
}
private boolean createServer(int port) {
try {
serverSocket = new ServerSocket(port);
return true;
} catch (IOException e) {
return false;
}
}
public int getPort() {
return serverSocket.getLocalPort();
}
}

View File

@ -1,10 +1,7 @@
package gearth.ui.extensions.authentication;
package gearth.services.extensionserver.extensions.network.authentication;
import gearth.extensions.Extension;
import gearth.misc.ConfirmationDialog;
import gearth.ui.extensions.GEarthExtension;
import gearth.ui.extensions.executer.ExtensionRunner;
import gearth.ui.extensions.executer.ExtensionRunnerFactory;
import gearth.services.extensionserver.extensions.network.NetworkExtension;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
@ -27,7 +24,7 @@ public class Authenticator {
return cookie;
}
public static boolean evaluate(GEarthExtension extension) {
public static boolean evaluate(NetworkExtension extension) {
if (extension.isInstalledExtension()) {
return claimSession(extension.getFileName(), extension.getCookie());
}
@ -52,7 +49,7 @@ public class Authenticator {
private static volatile boolean rememberOption = false;
//for not-installed extensions, popup a dialog
private static boolean askForPermission(GEarthExtension extension) {
private static boolean askForPermission(NetworkExtension extension) {
boolean[] allowConnection = {true};
final String connectExtensionKey = "allow_extension_connection";

View File

@ -1,4 +1,4 @@
package gearth.ui.extensions.executer;
package gearth.services.extensionserver.extensions.network.executer;
import java.util.ArrayList;
import java.util.HashMap;

View File

@ -1,4 +1,4 @@
package gearth.ui.extensions.executer;
package gearth.services.extensionserver.extensions.network.executer;
/**
* Created by Jonas on 21/09/18.

View File

@ -1,4 +1,4 @@
package gearth.ui.extensions.executer;
package gearth.services.extensionserver.extensions.network.executer;
/**
* Created by Jonas on 22/09/18.

View File

@ -1,7 +1,7 @@
package gearth.ui.extensions.executer;
package gearth.services.extensionserver.extensions.network.executer;
import gearth.Main;
import gearth.ui.extensions.authentication.Authenticator;
import gearth.services.extensionserver.extensions.network.authentication.Authenticator;
import java.io.BufferedReader;
import java.io.File;

View File

@ -0,0 +1,111 @@
package gearth.services.extensionserver.extensions.simple;
import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
import gearth.services.extensionserver.extensions.GEarthExtension;
import java.util.Arrays;
public class ExampleExtension extends GEarthExtension {
@Override
public String getAuthor() {
return "sirjonasxx";
}
@Override
public String getDescription() {
return "example internal extension";
}
@Override
public String getTitle() {
return "Example";
}
@Override
public String getVersion() {
return "0.1";
}
@Override
public boolean isFireButtonUsed() {
return false;
// with this button, you could for example open an UI, or use it if your extension has a single purpose
// that needs a click to be executed
// will only be visible if you return True here
}
@Override
public boolean isDeleteButtonVisible() {
return false;
// can you delete this extension? Can be useful to disable this if you want to provide a built-in tool
// in G-Earth
}
@Override
public boolean isLeaveButtonVisible() {
return false;
// can you disconnect from the extension? (will be connected again when re-opening G-Earth or clicking the
// "refresh" button in the Extensions GUI)
}
@Override
public boolean isInstalledExtension() {
return false;
// is this an extension that is located under the /Extensions folder?
}
@Override
public void doubleclick() {
System.out.println("wtf dont click me");
}
private void intercept(HMessage message) {
message.getPacket().replaceAllStrings(
"What is this extension?",
"It's an example extension showing how extensions work internally"
);
}
@Override
public void packetIntercept(HMessage hMessage) {
intercept(hMessage);
// every packetIntercept needs to be responded with the manipulated version, even if you didn't change anything
sendManipulatedPacket(hMessage);
}
@Override
public void provideFlags(String[] flags) {
// If you call "requestFlags()", this function will provide you the execution flags for G-Earth
// in case you want to do something with it
}
@Override
public void connectionStart(String host, int port, String hotelVersion, String harbleMessagesPath) {
// a new habbo client has connected
System.out.println("Connected to " + host);
}
@Override
public void connectionEnd() {
// the habbo connection has ended
}
@Override
public void init() {
System.out.println("Example extension is connected to G-Earth");
// the extension is now connected with G-Earth
}
@Override
public void close() {
// if this function is called, its a heads up that you should close your extension
// finish up and call "hasClosed()"
hasClosed();
}
}

View File

@ -0,0 +1,6 @@
This package is an example implementation for a new type of extensions (just like Network extensions),
in this case, pure Java classes included in the G-Earth source
If you maintain a fork of G-Earth and want a builtin feature, this is the place to add it

View File

@ -0,0 +1,15 @@
package gearth.services.extensionserver.extensions.simple;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducer;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducerObserver;
public class SimpleExtensionProducer implements ExtensionProducer {
@Override
public void startProducing(ExtensionProducerObserver observer) {
// uncomment the next line if you want to see an embedded example extension in G-Earth
// observer.onExtensionConnect(new ExampleExtension());
}
}

View File

@ -1,5 +1,7 @@
package gearth.ui.extensions;
import gearth.services.extensionserver.extensions.GEarthExtension;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -10,10 +12,10 @@ import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import gearth.misc.ConfirmationDialog;
import gearth.ui.buttons.*;
import gearth.ui.extensions.executer.ExecutionInfo;
import gearth.ui.extensions.executer.ExtensionRunner;
import gearth.ui.extensions.executer.ExtensionRunnerFactory;
import gearth.ui.extensions.executer.NormalExtensionRunner;
import gearth.services.extensionserver.extensions.network.executer.ExecutionInfo;
import gearth.services.extensionserver.extensions.network.executer.ExtensionRunner;
import gearth.services.extensionserver.extensions.network.executer.ExtensionRunnerFactory;
import gearth.services.extensionserver.extensions.network.executer.NormalExtensionRunner;
import java.nio.file.Paths;
@ -166,7 +168,7 @@ public class ExtensionItemContainer extends GridPane {
clickButton.addEventHandler(MouseEvent.MOUSE_CLICKED, onClick);
ExtensionItemContainer this2 = this;
item.onDelete(observable -> {
item.onDelete(observable -> Platform.runLater(() -> {
if (item.isInstalledExtension()) {
setBackground(new Background(new BackgroundFill(Paint.valueOf("#cccccc"),null, null)));
getChildren().remove(buttonsBox);
@ -176,7 +178,7 @@ public class ExtensionItemContainer extends GridPane {
else {
parent.getChildren().remove(this2);
}
});
}));
}
void hasReconnected(GEarthExtension extension) {

View File

@ -1,5 +1,7 @@
package gearth.ui.extensions;
import gearth.services.extensionserver.extensions.GEarthExtension;
import gearth.services.extensionserver.extensions.network.NetworkExtension;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
@ -26,7 +28,7 @@ public class ExtensionItemContainerProducer {
for (Node n : parent.getChildren()) {
if (n instanceof ExtensionItemContainer) {
ExtensionItemContainer container = (ExtensionItemContainer) n;
if (container.getExtensionFileName().equals(extension.getFileName())) {
if (container.getExtensionFileName() != null && container.getExtensionFileName().equals(extension.getFileName())) {
container.hasReconnected(extension);
return;
}

View File

@ -1,6 +1,12 @@
package gearth.ui.extensions;
import gearth.misc.harble_api.HarbleAPIFetcher;
import gearth.services.extensionserver.ExtensionServer;
import gearth.services.extensionserver.extensions.extensionproducers.ExtensionProducer;
import gearth.services.extensionserver.extensions.network.NetworkExtensionsProducer;
import gearth.services.extensionserver.extensions.network.executer.ExecutionInfo;
import gearth.services.extensionserver.extensions.network.executer.ExtensionRunner;
import gearth.services.extensionserver.extensions.network.executer.ExtensionRunnerFactory;
import gearth.ui.SubForm;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
@ -9,100 +15,14 @@ import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import gearth.Main;
import gearth.protocol.*;
import gearth.ui.SubForm;
import gearth.ui.extensions.executer.ExecutionInfo;
import gearth.ui.extensions.executer.ExtensionRunner;
import gearth.ui.extensions.executer.ExtensionRunnerFactory;
import gearth.ui.scheduler.ScheduleItem;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
/**
* Created by Jonas on 06/04/18.
*/
/**
* 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 class Extensions extends SubForm {
@ -114,32 +34,9 @@ public class Extensions extends SubForm {
public ScrollPane scroller;
private ExtensionRunner extensionRunner = null;
private GEarthExtensionsRegistrer extensionsRegistrer = null;
private ExtensionServer extensionServer;
private NetworkExtensionsProducer networkExtensionsProducer; // needed for port
public static class OUTGOING_MESSAGES_IDS {
public static final int ONDOUBLECLICK = 1;
public static final int INFOREQUEST = 2; // backend: implemented
public static final int PACKETINTERCEPT = 3; // backend: implemented
public static final int FLAGSCHECK = 4; // backend: implemented
public static final int CONNECTIONSTART = 5; // backend: implemented
public static final int CONNECTIONEND = 6; // backend: implemented
public static final int INIT = 7; // backend: implemented
public static final int FREEFLOW = 99; // no implementation needed yet
}
public static class INCOMING_MESSAGES_IDS {
public static final int EXTENSIONINFO = 1; // backend: implemented
public static final int MANIPULATEDPACKET = 2; // backend: implemented
public static final int REQUESTFLAGS = 3; // backend: implemented
public static final int SENDMESSAGE = 4; // backend: implemented
public static final int EXTENSIONCONSOLELOG = 98;
public static final int FREEFLOW = 99; // no implementation needed yet
}
private final List<GEarthExtension> gEarthExtensions = new ArrayList<>();
public void initialize() {
scroller.widthProperty().addListener(observable -> header_ext.setPrefWidth(scroller.getWidth()));
@ -147,206 +44,35 @@ public class Extensions extends SubForm {
protected void onParentSet() {
ExtensionItemContainerProducer producer = new ExtensionItemContainerProducer(extensioncontainer, scroller);
extensionServer = new ExtensionServer(getHConnection());
extensionServer.onExtensionConnected((e -> {
Platform.runLater(() -> producer.extensionConnected(e));
}));
getHConnection().addStateChangeListener((oldState, newState) -> {
if (newState == HConnection.State.CONNECTED) {
HarbleAPIFetcher.fetch(getHConnection().getHotelVersion());
synchronized (gEarthExtensions) {
for (GEarthExtension extension : gEarthExtensions) {
extension.sendMessage(
new HPacket(OUTGOING_MESSAGES_IDS.CONNECTIONSTART)
.appendString(getHConnection().getDomain())
.appendInt(getHConnection().getServerPort())
.appendString(getHConnection().getHotelVersion())
.appendString(HarbleAPIFetcher.HARBLEAPI == null ? "null" : HarbleAPIFetcher.HARBLEAPI.getPath())
);
}
}
}
if (oldState == HConnection.State.CONNECTED) {
synchronized (getHConnection()) {
for (GEarthExtension extension : gEarthExtensions) {
extension.sendMessage(new HPacket(OUTGOING_MESSAGES_IDS.CONNECTIONEND));
}
}
}
});
getHConnection().addTrafficListener(1, message -> {
Set<GEarthExtension> collection;
synchronized (gEarthExtensions) {
collection = new HashSet<>(gEarthExtensions);
}
String stringified = message.stringify();
HPacket manipulatePacketRequest = new HPacket(OUTGOING_MESSAGES_IDS.PACKETINTERCEPT);
manipulatePacketRequest.appendLongString(stringified);
HMessage result = new HMessage(message);
boolean[] isblock = new boolean[1];
synchronized (collection) {
for (GEarthExtension extension : collection) {
GEarthExtension.ReceiveMessageListener respondCallback = new GEarthExtension.ReceiveMessageListener() {
@Override
public void act(HPacket packet) {
if (packet.headerId() == INCOMING_MESSAGES_IDS.MANIPULATEDPACKET) {
String stringifiedresponse = packet.readLongString(6);
HMessage responseMessage = new HMessage(stringifiedresponse);
if (responseMessage.getDestination() == message.getDestination() && responseMessage.getIndex() == message.getIndex()) {
synchronized (result) {
if (!message.equals(responseMessage)) {
result.constructFromString(stringifiedresponse);
}
if (responseMessage.isBlocked()) {
isblock[0] = true;
}
synchronized (collection) {
collection.remove(extension);
}
extension.removeOnReceiveMessageListener(this);
}
}
}
}
};
extension.addOnReceiveMessageListener(respondCallback);
extension.sendMessage(manipulatePacketRequest);
}
}
//block untill all extensions have responded
List<GEarthExtension> willdelete = new ArrayList<>();
while (true) {
synchronized (collection) {
if (collection.isEmpty()) {
break;
}
synchronized (gEarthExtensions) {
for (GEarthExtension extension : collection) {
if (!gEarthExtensions.contains(extension)) willdelete.add(extension);
}
}
for (int i = willdelete.size() - 1; i >= 0; i--) {
collection.remove(willdelete.get(i));
willdelete.remove(i);
}
}
//noinspection OptionalGetWithoutIsPresent
networkExtensionsProducer
= (NetworkExtensionsProducer) extensionServer.getExtensionProducers().stream()
.filter(producer1 -> producer1 instanceof NetworkExtensionsProducer)
.findFirst().get();
try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
}
message.constructFromHMessage(result);
if (isblock[0]) {
message.setBlocked(true);
}
});
HashMap<GEarthExtension, GEarthExtension.ReceiveMessageListener> messageListeners = new HashMap<>();
try {
extensionsRegistrer = new GEarthExtensionsRegistrer(new GEarthExtensionsRegistrer.ExtensionRegisterObserver() {
@Override
public void onConnect(GEarthExtension extension) {
synchronized (gEarthExtensions) {
gEarthExtensions.add(extension);
}
GEarthExtension.ReceiveMessageListener receiveMessageListener = message -> {
if (message.headerId() == INCOMING_MESSAGES_IDS.REQUESTFLAGS) { // no body
HPacket packet = new HPacket(OUTGOING_MESSAGES_IDS.FLAGSCHECK);
packet.appendInt(Main.args.length);
for (String arg : Main.args) {
packet.appendString(arg);
}
extension.sendMessage(packet);
}
else if (message.headerId() == 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()) {
if (side == 0) { // toclient
getHConnection().sendToClientAsync(packet);
}
else if (side == 1) { // toserver
getHConnection().sendToServerAsync(packet);
}
}
}
};
synchronized (messageListeners) {
messageListeners.put(extension, receiveMessageListener);
}
extension.addOnReceiveMessageListener(receiveMessageListener);
extension.sendMessage(new HPacket(OUTGOING_MESSAGES_IDS.INIT));
if (getHConnection().getState() == HConnection.State.CONNECTED) {
extension.sendMessage(
new HPacket(OUTGOING_MESSAGES_IDS.CONNECTIONSTART)
.appendString(getHConnection().getDomain())
.appendInt(getHConnection().getServerPort())
.appendString(getHConnection().getHotelVersion())
.appendString(HarbleAPIFetcher.HARBLEAPI == null ? "null" : HarbleAPIFetcher.HARBLEAPI.getPath())
);
}
extension.onRemoveClick(observable -> {
try {
extension.getConnection().close();
} catch (IOException e) {
e.printStackTrace();
}
});
extension.onClick(observable -> extension.sendMessage(new HPacket(OUTGOING_MESSAGES_IDS.ONDOUBLECLICK)));
Platform.runLater(() -> producer.extensionConnected(extension));
}
@Override
public void onDisconnect(GEarthExtension extension) {
synchronized (gEarthExtensions) {
gEarthExtensions.remove(extension);
}
synchronized (messageListeners) {
extension.removeOnReceiveMessageListener(messageListeners.get(extension));
messageListeners.remove(extension);
}
Platform.runLater(extension::delete);
}
});
} catch (IOException e) {
e.printStackTrace();
}
producer.setPort(extensionsRegistrer.getPort());
ext_port.setText(extensionsRegistrer.getPort()+"");
producer.setPort(networkExtensionsProducer.getPort());
ext_port.setText(networkExtensionsProducer.getPort()+"");
// System.out.println("Extension server registered on port: " + extensionsRegistrer.getPort());
extensionRunner = ExtensionRunnerFactory.get();
extensionRunner.runAllExtensions(extensionsRegistrer.getPort());
extensionRunner.runAllExtensions(networkExtensionsProducer.getPort());
}
public void installBtnClicked(ActionEvent actionEvent) {
List<ScheduleItem> list = new ArrayList<>();
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Install extension");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("G-Earth extensions", ExecutionInfo.ALLOWEDEXTENSIONTYPES));
File selectedFile = fileChooser.showOpenDialog(parentController.getStage());
if (selectedFile != null) {
extensionRunner.installAndRunExtension(selectedFile.getPath(), extensionsRegistrer.getPort());
extensionRunner.installAndRunExtension(selectedFile.getPath(), networkExtensionsProducer.getPort());
}
}
}

View File

@ -1,263 +0,0 @@
package gearth.ui.extensions;
import javafx.beans.InvalidationListener;
import gearth.protocol.HPacket;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import gearth.ui.extensions.authentication.Authenticator;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Jonas on 21/06/18.
*/
public class 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;
//calls callback when the extension is creatd
static void create(Socket connection, OnCreatedCallback callback, OnDisconnectedCallback onDisconnectedCallback) {
new Thread(() -> {
try {
synchronized (connection) {
connection.getOutputStream().write((new HPacket(Extensions.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes());
}
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 packet = new HPacket(headerandbody);
packet.fixLength();
if (packet.headerId() == Extensions.INCOMING_MESSAGES_IDS.EXTENSIONINFO) {
GEarthExtension gEarthExtension = new GEarthExtension(
packet,
connection,
onDisconnectedCallback
);
if (Authenticator.evaluate(gEarthExtension)) {
callback.act(gEarthExtension);
}
else {
gEarthExtension.closeConnection(); //you shall not pass...
}
break;
}
}
} catch (IOException ignored) {}
}).start();
}
private GEarthExtension(HPacket extensionInfo, Socket connection, OnDisconnectedCallback onDisconnectedCallback) {
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;
GEarthExtension 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 packet = new HPacket(headerandbody);
packet.fixLength();
synchronized (receiveMessageListeners) {
for (int i = receiveMessageListeners.size() - 1; i >= 0; i--) {
receiveMessageListeners.get(i).act(packet);
packet.setReadIndex(6);
}
}
}
} catch (IOException e) {
// An extension disconnected, which is OK
} finally {
onDisconnectedCallback.act(selff);
if (!connection.isClosed()) {
try {
connection.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
}
}).start();
}
public Socket getConnection() {
return connection;
}
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;
}
public boolean closeConnection() {
try {
connection.close();
return true;
} catch (IOException e) {
return false;
}
}
public boolean sendMessage(HPacket message) {
try {
synchronized (this) {
connection.getOutputStream().write(message.toBytes());
}
return true;
} catch (IOException e) {
return false;
}
}
private final List<ReceiveMessageListener> receiveMessageListeners = new ArrayList<>();
public void addOnReceiveMessageListener(ReceiveMessageListener receiveMessageListener) {
synchronized (receiveMessageListeners) {
receiveMessageListeners.add(receiveMessageListener);
}
}
public void removeOnReceiveMessageListener(ReceiveMessageListener receiveMessageListener) {
synchronized (receiveMessageListeners) {
receiveMessageListeners.remove(receiveMessageListener);
}
}
public interface ReceiveMessageListener {
void act(HPacket message);
}
public interface OnCreatedCallback {
void act(GEarthExtension extension); // returns itself
}
public interface OnDisconnectedCallback {
void act(GEarthExtension extension); // returns itself
}
private final List<InvalidationListener> onRemoveClickListener = new ArrayList<>();
public void onRemoveClick(InvalidationListener listener) {
synchronized (onRemoveClickListener) {
onRemoveClickListener.add(listener);
}
}
public void isRemoveClickTrigger() {
synchronized (onRemoveClickListener) {
for (int i = onRemoveClickListener.size() - 1; i >= 0; i--) {
onRemoveClickListener.get(i).invalidated(null);
}
}
}
private final List<InvalidationListener> onClickListener = new ArrayList<>();
public void onClick(InvalidationListener listener) {
synchronized (onClickListener) {
onClickListener.add(listener);
}
}
public void isClickTrigger() {
synchronized (onClickListener) {
for (int i = onClickListener.size() - 1; i >= 0; i--) {
onClickListener.get(i).invalidated(null);
}
}
}
private final List<InvalidationListener> onDeleteListeners = new ArrayList<>();
public void onDelete(InvalidationListener listener) {
synchronized (onDeleteListeners) {
onDeleteListeners.add(listener);
}
}
public void delete() {
synchronized (onDeleteListeners) {
for (int i = onDeleteListeners.size() - 1; i >= 0; i--) {
onDeleteListeners.get(i).invalidated(null);
}
}
}
}

View File

@ -1,52 +0,0 @@
package gearth.ui.extensions;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Jonas on 21/06/18.
*/
public class GEarthExtensionsRegistrer {
private ServerSocket serverSocket;
GEarthExtensionsRegistrer(ExtensionRegisterObserver observer) throws IOException {
// serverSocket = new ServerSocket(0);
int port = 9092;
boolean serverSetup = false;
while (!serverSetup) {
serverSetup = createServer(port);
port++;
}
new Thread(() -> {
try {
while (!serverSocket.isClosed()) {
Socket extensionSocket = serverSocket.accept();
GEarthExtension.create(extensionSocket, observer::onConnect, observer::onDisconnect);
}
} catch (IOException e) {e.printStackTrace();}
}).start();
}
private boolean createServer(int port) {
try {
serverSocket = new ServerSocket(port);
return true;
} catch (IOException e) {
return false;
}
}
public int getPort() {
return serverSocket.getLocalPort();
}
public interface ExtensionRegisterObserver {
void onConnect(GEarthExtension extension);
void onDisconnect(GEarthExtension extension);
}
}