mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-23 08:50:52 +01:00
Merge branch 'fix/macos'
# Conflicts: # G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtension.java # G-Earth/src/main/java/gearth/services/extension_handler/extensions/implementations/network/NetworkExtensionsProducer.java # G-Earth/src/main/java/gearth/services/internal_extensions/extensionstore/application/entities/StoreExtensionItem.java
This commit is contained in:
commit
cee78fd0f5
@ -10,13 +10,14 @@
|
||||
|
||||
<properties>
|
||||
<javafx.version>1.8</javafx.version>
|
||||
<jettyVersion>9.4.48.v20220622</jettyVersion>
|
||||
<jettyVersion>9.4.50.v20221201</jettyVersion>
|
||||
<logback.version>1.3.5</logback.version>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>G-Earth</groupId>
|
||||
<artifactId>G-Earth-Parent</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.5.3</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
@ -218,17 +219,18 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.14.2</version>
|
||||
<version>1.15.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tulskiy</groupId>
|
||||
<artifactId>jkeymaster</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>2.0.0-alpha0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.maven/maven-artifact -->
|
||||
<dependency>
|
||||
@ -236,14 +238,11 @@
|
||||
<artifactId>maven-artifact</artifactId>
|
||||
<version>3.6.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.websocket</groupId>
|
||||
<artifactId>javax.websocket-api</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@ -274,17 +273,32 @@
|
||||
<groupId>com.github.ganskef</groupId>
|
||||
<artifactId>littleproxy-mitm</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>G-Earth</groupId>
|
||||
<artifactId>G-Wasm-Minimal</artifactId>
|
||||
<version>1.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
@ -22,7 +22,7 @@ import javafx.stage.StageStyle;
|
||||
public class GEarth extends Application {
|
||||
|
||||
public static GEarth main;
|
||||
public static String version = "1.5.2";
|
||||
public static String version = "1.5.3";
|
||||
public static String gitApi = "https://api.github.com/repos/sirjonasxx/G-Earth/releases/latest";
|
||||
public static ObservableObject<Theme> observableTheme;
|
||||
|
||||
@ -43,7 +43,13 @@ public class GEarth extends Application {
|
||||
stage = primaryStage;
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/gearth/ui/G-Earth.fxml"));
|
||||
Parent root = loader.load();
|
||||
Parent root;
|
||||
try {
|
||||
root = loader.load();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
controller = loader.getController();
|
||||
controller.setStage(primaryStage);
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
|
@ -1,12 +1,14 @@
|
||||
package gearth.extensions;
|
||||
|
||||
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.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.connection.HClient;
|
||||
import gearth.services.Constants;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionInfo;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
@ -108,10 +110,10 @@ public abstract class Extension extends ExtensionBase {
|
||||
packet.fixLength();
|
||||
|
||||
|
||||
if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST) {
|
||||
if (packet.headerId() == Outgoing.InfoRequest.HEADER_ID) {
|
||||
ExtensionInfo info = getInfoAnnotations();
|
||||
|
||||
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO);
|
||||
HPacket response = new HPacket(Incoming.ExtensionInfo.HEADER_ID);
|
||||
response.appendString(info.Title())
|
||||
.appendString(info.Author())
|
||||
.appendString(info.Version())
|
||||
@ -124,7 +126,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
.appendBoolean(canDelete());
|
||||
writeToStream(response.toBytes());
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) {
|
||||
else if (packet.headerId() == Outgoing.ConnectionStart.HEADER_ID) {
|
||||
String host = packet.readString();
|
||||
int connectionPort = packet.readInteger();
|
||||
String hotelVersion = packet.readString();
|
||||
@ -143,10 +145,10 @@ public abstract class Extension extends ExtensionBase {
|
||||
);
|
||||
onStartConnection();
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND) {
|
||||
else if (packet.headerId() == Outgoing.ConnectionEnd.HEADER_ID) {
|
||||
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
|
||||
if (flagRequestCallback != null) {
|
||||
int arraysize = packet.readInteger();
|
||||
@ -158,7 +160,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
}
|
||||
flagRequestCallback = null;
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT) {
|
||||
else if (packet.headerId() == Outgoing.Init.HEADER_ID) {
|
||||
delayed_init = packet.readBoolean();
|
||||
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
||||
updateHostInfo(hostInfo);
|
||||
@ -167,21 +169,21 @@ public abstract class Extension extends ExtensionBase {
|
||||
}
|
||||
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();
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT) {
|
||||
else if (packet.headerId() == Outgoing.PacketIntercept.HEADER_ID) {
|
||||
String stringifiedMessage = packet.readLongString();
|
||||
HMessage habboMessage = new HMessage(stringifiedMessage);
|
||||
|
||||
modifyMessage(habboMessage);
|
||||
|
||||
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET);
|
||||
HPacket response = new HPacket(Incoming.ManipulatedPacket.MANIPULATED_PACKET);
|
||||
response.appendLongString(habboMessage.stringify());
|
||||
|
||||
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);
|
||||
updateHostInfo(hostInfo);
|
||||
}
|
||||
@ -231,7 +233,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
if (!packet.isPacketComplete()) packet.completePacket(packetInfoManager);
|
||||
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.appendInt(packet.getBytesLength());
|
||||
packet1.appendBytes(packet.toBytes());
|
||||
@ -253,7 +255,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
if (this.flagRequestCallback != null) return false;
|
||||
this.flagRequestCallback = flagRequestCallback;
|
||||
try {
|
||||
writeToStream(new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS).toBytes());
|
||||
writeToStream(new HPacket(Incoming.RequestFlags.HEADER_ID).toBytes());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@ -279,7 +281,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
private void writeToConsole(String colorClass, String s, boolean mentionTitle) {
|
||||
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);
|
||||
try {
|
||||
writeToStream(packet.toBytes());
|
||||
|
@ -1,11 +1,11 @@
|
||||
package gearth.misc;
|
||||
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
@ -47,7 +47,7 @@ public class AdminValidator {
|
||||
if (!AdminValidator.isAdmin()) {
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, "", ButtonType.OK);
|
||||
alert.getDialogPane().setContent(new Label("G-Earth needs admin privileges in order to work on Flash,\nplease restart G-Earth with admin permissions unless\nyou're using Unity"));
|
||||
alert.getDialogPane().setContent(new Label(LanguageBundle.get("alert.adminvalidator.content").replaceAll("\\\\n", System.lineSeparator())));
|
||||
try {
|
||||
TitleBarController.create(alert).showAlert();
|
||||
} catch (IOException e) {
|
||||
|
@ -85,6 +85,11 @@ public class Cacher {
|
||||
object.put(key, val);
|
||||
updateCache(object, cache_filename);
|
||||
}
|
||||
public static void remove(String key, String cache_filename) {
|
||||
JSONObject object = getCacheContents(cache_filename);
|
||||
if (object.has(key)) object.remove(key);
|
||||
updateCache(object, cache_filename);
|
||||
}
|
||||
public static Object get(String key, String cache_filename) {
|
||||
JSONObject object = getCacheContents(cache_filename);
|
||||
if (object.has(key)) return object.get(key);
|
||||
@ -113,6 +118,9 @@ public class Cacher {
|
||||
public static void put(String key, Object val) {
|
||||
put(key, val, DEFAULT_CACHE_FILENAME);
|
||||
}
|
||||
public static void remove(String key) {
|
||||
remove(key, DEFAULT_CACHE_FILENAME);
|
||||
}
|
||||
public static Object get(String key) {
|
||||
return get(key, DEFAULT_CACHE_FILENAME);
|
||||
}
|
||||
|
22
G-Earth/src/main/java/gearth/misc/StringUtils.java
Normal file
22
G-Earth/src/main/java/gearth/misc/StringUtils.java
Normal file
@ -0,0 +1,22 @@
|
||||
package gearth.misc;
|
||||
|
||||
/**
|
||||
* Contains utility methods for {@link String} conversions.
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
/**
|
||||
* Interprets the argued {@link String} as a hex-string and converts it to a byte array.
|
||||
* @param hexString the {@link String} to be converted.
|
||||
* @return a byte array containing the converted hex string values.
|
||||
*/
|
||||
public static byte[] hexStringToByteArray(String hexString) {
|
||||
int len = hexString.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
|
||||
+ Character.digit(hexString.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package gearth.misc;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
@ -37,10 +38,10 @@ public class UpdateChecker {
|
||||
String body = (String)object.get("body");
|
||||
boolean isForcedUpdate = body.contains("(!)");
|
||||
|
||||
Alert alert = new Alert(isForcedUpdate ? Alert.AlertType.ERROR : Alert.AlertType.INFORMATION, "G-Earth is outdated!", ButtonType.OK);
|
||||
Alert alert = new Alert(isForcedUpdate ? Alert.AlertType.ERROR : Alert.AlertType.INFORMATION, LanguageBundle.get("alert.outdated.title"), ButtonType.OK);
|
||||
|
||||
FlowPane fp = new FlowPane();
|
||||
Label lbl = new Label("A new version of G-Earth has been found ("+gitv+")" + System.lineSeparator()+ System.lineSeparator() + "Update to the latest version:");
|
||||
Label lbl = new Label(LanguageBundle.get("alert.outdated.content.newversion") + " ("+gitv+")" + System.lineSeparator()+ System.lineSeparator() + LanguageBundle.get("alert.outdated.content.update") + ":");
|
||||
Hyperlink link = new Hyperlink("https://github.com/sirjonasxx/G-Earth/releases");
|
||||
fp.getChildren().addAll( lbl, link);
|
||||
link.setOnAction(event -> {
|
||||
@ -51,7 +52,7 @@ public class UpdateChecker {
|
||||
|
||||
|
||||
WebView webView = new WebView();
|
||||
webView.getEngine().loadContent("<html>A new version of G-Earth has been found ("+gitv+")<br><br>Update to the latest version:<br><a href=\"https://github.com/sirjonasxx/G-Earth/releases\">https://github.com/sirjonasxx/G-Earth/releases</a></html>");
|
||||
webView.getEngine().loadContent(String.format("<html>%s (%s)<br><br>%s:<br><a href=\"https://github.com/sirjonasxx/G-Earth/releases\">https://github.com/sirjonasxx/G-Earth/releases</a></html>", LanguageBundle.get("alert.outdated.content.newversion"), gitv, LanguageBundle.get("alert.outdated.content.update")));
|
||||
webView.setPrefSize(500, 200);
|
||||
|
||||
alert.setResizable(false);
|
||||
|
@ -1,6 +1,8 @@
|
||||
package gearth.protocol;
|
||||
|
||||
import gearth.misc.listenerpattern.Observable;
|
||||
import gearth.protocol.connection.packetsafety.PacketSafetyManager;
|
||||
import gearth.protocol.connection.packetsafety.SafePacketsContainer;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.protocol.connection.HClient;
|
||||
@ -13,16 +15,18 @@ import gearth.protocol.connection.proxy.unity.UnityProxyProvider;
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class HConnection {
|
||||
|
||||
public static volatile boolean DECRYPTPACKETS = true;
|
||||
public static volatile boolean DEBUG = false;
|
||||
public static volatile boolean DEBUG = true;
|
||||
|
||||
private volatile ExtensionHandler extensionHandler = null;
|
||||
|
||||
private volatile Object[] trafficObservables = {new Observable<TrafficListener>(), new Observable<TrafficListener>(), new Observable<TrafficListener>()};
|
||||
private volatile Observable<StateChangeListener> stateObservable = new Observable<>();
|
||||
private volatile Observable<Consumer<Boolean>> developerModeChangeObservable = new Observable<>();
|
||||
|
||||
private volatile HState state = HState.NOT_CONNECTED;
|
||||
private volatile HProxy proxy = null;
|
||||
@ -30,6 +34,8 @@ public class HConnection {
|
||||
private ProxyProviderFactory proxyProviderFactory;
|
||||
private ProxyProvider proxyProvider = null;
|
||||
|
||||
private volatile boolean developerMode = false;
|
||||
|
||||
public HConnection() {
|
||||
HConnection selff = this;
|
||||
proxyProviderFactory = new ProxyProviderFactory(
|
||||
@ -37,6 +43,8 @@ public class HConnection {
|
||||
selff::setState,
|
||||
this
|
||||
);
|
||||
|
||||
PacketSafetyManager.PACKET_SAFETY_MANAGER.initialize(this);
|
||||
}
|
||||
|
||||
public HState getState() {
|
||||
@ -147,30 +155,54 @@ public class HConnection {
|
||||
|
||||
|
||||
public boolean sendToClient(HPacket packet) {
|
||||
HProxy proxy = this.proxy;
|
||||
if (proxy == null) return false;
|
||||
|
||||
if (!packet.isPacketComplete()) {
|
||||
PacketInfoManager packetInfoManager = getPacketInfoManager();
|
||||
packet.completePacket(packetInfoManager);
|
||||
|
||||
if (!packet.isPacketComplete() || !packet.canSendToClient()) return false;
|
||||
}
|
||||
|
||||
if (!canSendPacket(HMessage.Direction.TOCLIENT, packet)) return false;
|
||||
return proxy.sendToClient(packet);
|
||||
}
|
||||
|
||||
public boolean sendToServer(HPacket packet) {
|
||||
if (!canSendPacket(HMessage.Direction.TOSERVER, packet)) return false;
|
||||
return proxy.sendToServer(packet);
|
||||
}
|
||||
|
||||
public boolean canSendPacket(HMessage.Direction direction, HPacket packet) {
|
||||
return isPacketSendingAllowed(direction, packet) && (developerMode || isPacketSendingSafe(direction, packet));
|
||||
}
|
||||
|
||||
public boolean isPacketSendingAllowed(HMessage.Direction direction, HPacket packet) {
|
||||
if (state != HState.CONNECTED) return false;
|
||||
|
||||
HProxy proxy = this.proxy;
|
||||
if (proxy == null) return false;
|
||||
if (packet.isCorrupted()) return false;
|
||||
|
||||
if (!packet.isPacketComplete()) {
|
||||
PacketInfoManager packetInfoManager = getPacketInfoManager();
|
||||
packet.completePacket(packetInfoManager);
|
||||
|
||||
if (!packet.isPacketComplete() || !packet.canSendToServer()) return false;
|
||||
return packet.isPacketComplete() &&
|
||||
(packet.canSendToClient() || direction != HMessage.Direction.TOCLIENT) &&
|
||||
(packet.canSendToServer() || direction != HMessage.Direction.TOSERVER);
|
||||
}
|
||||
|
||||
return proxy.sendToServer(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isPacketSendingSafe(HMessage.Direction direction, HPacket packet) {
|
||||
if (proxy == null) return true; // do not mark unsafe, but won't pass "isPacketSendingAllowed()" check
|
||||
String hotelVersion = proxy.getHotelVersion();
|
||||
if (hotelVersion == null) return true;
|
||||
|
||||
SafePacketsContainer packetsContainer = PacketSafetyManager.PACKET_SAFETY_MANAGER.getPacketContainer(hotelVersion);
|
||||
return packetsContainer.isPacketSafe(packet.headerId(), direction);
|
||||
}
|
||||
|
||||
public void setDeveloperMode(boolean developerMode) {
|
||||
this.developerMode = developerMode;
|
||||
developerModeChangeObservable.fireEvent(listener -> listener.accept(developerMode));
|
||||
}
|
||||
|
||||
public void onDeveloperModeChange(Consumer<Boolean> onChange) {
|
||||
developerModeChangeObservable.addListener(onChange);
|
||||
}
|
||||
|
||||
public String getClientHost() {
|
||||
@ -205,7 +237,7 @@ public class HConnection {
|
||||
if (proxy == null) {
|
||||
return null;
|
||||
}
|
||||
return proxy.gethClient();
|
||||
return proxy.getHClient();
|
||||
}
|
||||
|
||||
public PacketInfoManager getPacketInfoManager() {
|
||||
@ -223,4 +255,12 @@ public class HConnection {
|
||||
public ProxyProvider getProxyProvider() {
|
||||
return proxyProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HConnection{" +
|
||||
"state=" + state +
|
||||
", proxy=" + proxy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class HPacket implements StringifyAble {
|
||||
}
|
||||
|
||||
public void completePacket(PacketInfoManager packetInfoManager) {
|
||||
if (isCorrupted() || identifier == null) return;
|
||||
if (isCorrupted() || identifier == null || packetInfoManager == null) return;
|
||||
|
||||
PacketInfo packetInfo = packetInfoManager.getPacketInfoFromName(identifierDirection, identifier);
|
||||
if (packetInfo == null) {
|
||||
@ -718,7 +718,15 @@ public class HPacket implements StringifyAble {
|
||||
|
||||
public String toExpression() {
|
||||
if (isCorrupted()) return "";
|
||||
return PacketStringUtils.predictedExpression(this, null);
|
||||
return PacketStringUtils.predictedExpression(this, dummyPacketInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides dummy packet information for a packet that hasn't been completed with headerId yet
|
||||
*/
|
||||
private PacketInfo dummyPacketInfo() {
|
||||
if (isPacketComplete()) return null;
|
||||
return new PacketInfo(identifierDirection, -1, "", identifier, null, "");
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
package gearth.protocol.connection;
|
||||
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.connection.packetsafety.PacketSafetyManager;
|
||||
import gearth.protocol.connection.packetsafety.SafePacketsContainer;
|
||||
import gearth.services.packet_info.PacketInfo;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.protocol.packethandler.PacketHandler;
|
||||
|
||||
@ -45,6 +48,11 @@ public class HProxy {
|
||||
this.hotelVersion = hotelVersion;
|
||||
this.clientIdentifier = clientIdentifier;
|
||||
this.packetInfoManager = PacketInfoManager.fromHotelVersion(hotelVersion, hClient);
|
||||
|
||||
SafePacketsContainer packetsContainer = PacketSafetyManager.PACKET_SAFETY_MANAGER.getPacketContainer(hotelVersion);
|
||||
for (PacketInfo packetInfo : packetInfoManager.getPacketInfoList()) {
|
||||
packetsContainer.validateSafePacket(packetInfo.getHeaderId(), packetInfo.getDestination());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean sendToServer(HPacket packet) {
|
||||
@ -101,11 +109,28 @@ public class HProxy {
|
||||
return hotelVersion;
|
||||
}
|
||||
|
||||
public HClient gethClient() {
|
||||
public HClient getHClient() {
|
||||
return hClient;
|
||||
}
|
||||
|
||||
public PacketInfoManager getPacketInfoManager() {
|
||||
return packetInfoManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HProxy{" +
|
||||
"hClient=" + hClient +
|
||||
", input_domain='" + input_domain + '\'' +
|
||||
", actual_domain='" + actual_domain + '\'' +
|
||||
", actual_port=" + actual_port +
|
||||
", intercept_port=" + intercept_port +
|
||||
", intercept_host='" + intercept_host + '\'' +
|
||||
", proxy_server=" + proxy_server +
|
||||
", inHandler=" + inHandler +
|
||||
", outHandler=" + outHandler +
|
||||
", hotelVersion='" + hotelVersion + '\'' +
|
||||
", clientIdentifier='" + clientIdentifier + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
package gearth.protocol.connection.packetsafety;
|
||||
|
||||
import gearth.protocol.HConnection;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PacketSafetyManager {
|
||||
|
||||
public static final PacketSafetyManager PACKET_SAFETY_MANAGER = new PacketSafetyManager();
|
||||
|
||||
private final Map<String, SafePacketsContainer> safePacketContainers = new ConcurrentHashMap<>();
|
||||
|
||||
private PacketSafetyManager() {
|
||||
|
||||
}
|
||||
|
||||
public void initialize(HConnection connection) {
|
||||
connection.addTrafficListener(0, message -> {
|
||||
String hotelVersion = connection.getHotelVersion();
|
||||
if (hotelVersion.equals("")) return;
|
||||
|
||||
SafePacketsContainer safePacketsContainer = getPacketContainer(hotelVersion);
|
||||
safePacketsContainer.validateSafePacket(message.getPacket().headerId(), message.getDestination());
|
||||
});
|
||||
}
|
||||
|
||||
public SafePacketsContainer getPacketContainer(String hotelVersion) {
|
||||
return safePacketContainers.computeIfAbsent(hotelVersion, v -> new SafePacketsContainer());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package gearth.protocol.connection.packetsafety;
|
||||
|
||||
import gearth.protocol.HMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SafePacketsContainer {
|
||||
|
||||
private final Set<Integer> safeIncomingIds = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Set<Integer> safeOutgoingIds = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public void validateSafePacket(int headerId, HMessage.Direction direction) {
|
||||
Set<Integer> headerIds = direction == HMessage.Direction.TOCLIENT ? safeIncomingIds : safeOutgoingIds;
|
||||
headerIds.add(headerId);
|
||||
}
|
||||
|
||||
public boolean isPacketSafe(int headerId, HMessage.Direction direction) {
|
||||
Set<Integer> headerIds = direction == HMessage.Direction.TOCLIENT ? safeIncomingIds : safeOutgoingIds;
|
||||
return headerIds.contains(headerId);
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package gearth.protocol.connection.proxy;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.misc.OSValidator;
|
||||
import gearth.protocol.HConnection;
|
||||
@ -10,6 +9,7 @@ import gearth.protocol.connection.proxy.flash.NormalFlashProxyProvider;
|
||||
import gearth.protocol.connection.proxy.flash.unix.LinuxRawIpFlashProxyProvider;
|
||||
import gearth.protocol.connection.proxy.flash.windows.WindowsRawIpFlashProxyProvider;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
@ -108,9 +108,7 @@ public class ProxyProviderFactory {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "", ButtonType.OK);
|
||||
alert.getDialogPane().getChildren().add(new Label("G-Earth is already connected to this hotel.\n" +
|
||||
"Due to current limitations you can only connect one session per hotel to G-Earth in Raw IP mode on Windows.\n\n" +
|
||||
"You can bypass this by using a SOCKS proxy [Extra -> Advanced -> SOCKS]"));
|
||||
alert.getDialogPane().getChildren().add(new Label(LanguageBundle.get("alert.alreadyconnected.content").replaceAll("\\\\n", System.lineSeparator())));
|
||||
alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
|
||||
alert.setResizable(false);
|
||||
try {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package gearth.protocol.connection.proxy.flash;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.connection.HProxy;
|
||||
import gearth.protocol.connection.HProxySetter;
|
||||
@ -8,17 +7,18 @@ import gearth.protocol.connection.HState;
|
||||
import gearth.protocol.connection.HStateSetter;
|
||||
import gearth.protocol.connection.proxy.ProxyProvider;
|
||||
import gearth.protocol.memory.Rc4Obtainer;
|
||||
import gearth.protocol.packethandler.flash.FlashPacketHandler;
|
||||
import gearth.protocol.packethandler.flash.IncomingFlashPacketHandler;
|
||||
import gearth.protocol.packethandler.flash.OutgoingFlashPacketHandler;
|
||||
import gearth.protocol.packethandler.flash.FlashPacketHandler;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
@ -27,6 +27,7 @@ import java.util.concurrent.Semaphore;
|
||||
|
||||
public abstract class FlashProxyProvider implements ProxyProvider {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
protected final HProxySetter proxySetter;
|
||||
protected final HStateSetter stateSetter;
|
||||
protected final HConnection hConnection;
|
||||
@ -47,7 +48,8 @@ public abstract class FlashProxyProvider implements ProxyProvider {
|
||||
client.setSoTimeout(0);
|
||||
server.setSoTimeout(0);
|
||||
|
||||
if (HConnection.DEBUG) System.out.println(server.getLocalAddress().getHostAddress() + ": " + server.getLocalPort());
|
||||
logger.debug("Starting proxy thread at {}:{}", server.getLocalAddress().getHostAddress(), server.getLocalPort());
|
||||
|
||||
Rc4Obtainer rc4Obtainer = new Rc4Obtainer(hConnection);
|
||||
|
||||
OutgoingFlashPacketHandler outgoingHandler = new OutgoingFlashPacketHandler(server.getOutputStream(), hConnection.getTrafficObservables(), hConnection.getExtensionHandler());
|
||||
@ -73,13 +75,13 @@ public abstract class FlashProxyProvider implements ProxyProvider {
|
||||
try {
|
||||
if (!server.isClosed()) server.close();
|
||||
if (!client.isClosed()) client.close();
|
||||
if (HConnection.DEBUG) System.out.println("STOP");
|
||||
logger.debug("Closed server {} and client {}, dataStreams {}", server, client, datastream);
|
||||
if (datastream[0]) {
|
||||
onConnectEnd();
|
||||
};
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to gracefully stop", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,8 +97,7 @@ public abstract class FlashProxyProvider implements ProxyProvider {
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// System.err.println(packetHandler instanceof IncomingPacketHandler ? "incoming" : "outgoing");
|
||||
// ignore.printStackTrace();
|
||||
// logger.error("Failed to read input stream from socket {}", socket, ignore);
|
||||
} finally {
|
||||
abort.release();
|
||||
}
|
||||
@ -126,15 +127,14 @@ public abstract class FlashProxyProvider implements ProxyProvider {
|
||||
protected void showInvalidConnectionError() {
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "", ButtonType.OK);
|
||||
alert.getDialogPane().getChildren().add(new Label("You entered invalid connection information, G-Earth could not connect"));
|
||||
alert.getDialogPane().getChildren().add(new Label(LanguageBundle.get("alert.invalidconnection.content")));
|
||||
alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
|
||||
alert.setResizable(false);
|
||||
try {
|
||||
TitleBarController.create(alert).showAlert();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to create invalid connection error alert", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package gearth.protocol.connection.proxy.flash;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.connection.*;
|
||||
@ -14,8 +13,6 @@ import gearth.ui.titlebar.TitleBarController;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
@ -26,7 +23,6 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
|
||||
|
||||
private List<String> potentialHosts;
|
||||
|
||||
|
||||
private static final HostReplacer hostsReplacer = HostReplacerFactory.get();
|
||||
private volatile boolean hostRedirected = false;
|
||||
|
||||
@ -113,7 +109,7 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
|
||||
try {
|
||||
TitleBarController.create(a).showAlertAndWait();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
logger.error("Failed to create port in use error alert", ex);
|
||||
}
|
||||
});
|
||||
throw new IOException(e);
|
||||
@ -128,8 +124,7 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
|
||||
Socket client = proxy_server.accept();
|
||||
proxy = potentialProxy;
|
||||
closeAllProxies(proxy);
|
||||
if (HConnection.DEBUG) System.out.println("accepted a proxy");
|
||||
|
||||
logger.debug("Accepted proxy {}, starting proxy thread (useSocks={})",proxy, useSocks);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Socket server;
|
||||
@ -152,18 +147,14 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
|
||||
// should only happen when SOCKS configured badly
|
||||
showInvalidConnectionError();
|
||||
abort();
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to configure SOCKS proxy", e);
|
||||
}
|
||||
catch (InterruptedException | IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
logger.error("An unexpected exception occurred", e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
||||
} catch (IOException e1) {
|
||||
// TODO Auto-generated catch block
|
||||
// e1.printStackTrace();
|
||||
logger.error("An unexpected exception occurred", e1);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -171,10 +162,7 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
if (HConnection.DEBUG) System.out.println("done waiting for clients with: " + hConnection.getState() );
|
||||
|
||||
logger.debug("Done waiting for clients with connection state {}", hConnection.getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -233,12 +221,10 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
|
||||
try {
|
||||
proxy.getProxy_server().close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to close all proxies", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// potentialProxies = Collections.singletonList(except);
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
|
||||
maybeAddMapping();
|
||||
|
||||
if (HConnection.DEBUG) System.out.println("Added mapping for raw IP");
|
||||
logger.debug("Added mapping for raw IP");
|
||||
|
||||
ServerSocket proxy_server = new ServerSocket(proxy.getIntercept_port(), 10, InetAddress.getByName(proxy.getIntercept_host()));
|
||||
proxy.initProxy(proxy_server);
|
||||
@ -62,10 +62,10 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
stateSetter.setState(HState.WAITING_FOR_CLIENT);
|
||||
while ((hConnection.getState() == HState.WAITING_FOR_CLIENT) && !proxy_server.isClosed()) {
|
||||
try {
|
||||
if (HConnection.DEBUG) System.out.println("try accept proxy");
|
||||
logger.debug("Trying to accept a new proxy from {}", proxy_server);
|
||||
Socket client = proxy_server.accept();
|
||||
|
||||
if (HConnection.DEBUG) System.out.println("accepted a proxy");
|
||||
logger.debug("Accepted a proxy {}", client);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
@ -82,7 +82,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("An unexpected exception occurred in proxy listener", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@ -114,7 +114,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
try {
|
||||
proxy.getProxy_server().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to close proxy server", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,6 +136,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
}
|
||||
catch (SocketTimeoutException e) {
|
||||
showInvalidConnectionError();
|
||||
logger.error("Connection to proxy {} timed out", proxy, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -151,7 +152,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
createSocksProxyThread(client);
|
||||
}
|
||||
else if (preConnectedServerConnections.isEmpty()) {
|
||||
if (HConnection.DEBUG) System.out.println("pre-made server connections ran out of stock");
|
||||
logger.error("pre-made server connections ran out of stock");
|
||||
}
|
||||
else {
|
||||
startProxyThread(client, preConnectedServerConnections.poll(), proxy);
|
||||
@ -165,6 +166,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
maybeRemoveMapping();
|
||||
stateSetter.setState(HState.NOT_CONNECTED);
|
||||
showInvalidConnectionError();
|
||||
logger.error("Failed to create socks proxy thread because configuration is null");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -177,7 +179,7 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
maybeRemoveMapping();
|
||||
stateSetter.setState(HState.NOT_CONNECTED);
|
||||
showInvalidConnectionError();
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to create socks proxy thread", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +190,5 @@ public class LinuxRawIpFlashProxyProvider extends FlashProxyProvider {
|
||||
|
||||
protected void maybeRemoveMapping() {
|
||||
ipMapper.deleteMapping(proxy.getActual_domain(), proxy.getActual_port(), proxy.getIntercept_port());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ public final class NitroConstants {
|
||||
public static final int HTTP_PORT = 9090;
|
||||
public static final int HTTP_BUFFER_SIZE = 1024 * 1024 * 10;
|
||||
|
||||
public static final int WEBSOCKET_PORT = 2096;
|
||||
public static final int WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 10;
|
||||
public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768";
|
||||
public static final String WEBSOCKET_CLIENT_IDENTIFIER = "HTML5";
|
||||
|
@ -9,14 +9,17 @@ import gearth.protocol.connection.proxy.ProxyProvider;
|
||||
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy;
|
||||
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback;
|
||||
import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NitroProxyProvider.class);
|
||||
|
||||
private final HProxySetter proxySetter;
|
||||
private final HStateSetter stateSetter;
|
||||
private final HConnection connection;
|
||||
@ -24,8 +27,9 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
||||
private final NitroWebsocketProxy nitroWebsocketProxy;
|
||||
private final AtomicBoolean abortLock;
|
||||
|
||||
private int websocketPort;
|
||||
private String originalWebsocketUrl;
|
||||
private String originalOriginUrl;
|
||||
private String originalCookies;
|
||||
|
||||
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
||||
this.proxySetter = proxySetter;
|
||||
@ -40,26 +44,38 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
||||
return originalWebsocketUrl;
|
||||
}
|
||||
|
||||
public String getOriginalOriginUrl() {
|
||||
return originalOriginUrl;
|
||||
public String getOriginalCookies() {
|
||||
return originalCookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws IOException {
|
||||
originalWebsocketUrl = null;
|
||||
originalCookies = null;
|
||||
|
||||
connection.getStateObservable().addListener(this);
|
||||
|
||||
logger.info("Starting http proxy");
|
||||
|
||||
if (!nitroHttpProxy.start()) {
|
||||
System.out.println("Failed to start nitro proxy");
|
||||
logger.error("Failed to start nitro proxy");
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Starting websocket proxy");
|
||||
|
||||
if (!nitroWebsocketProxy.start()) {
|
||||
System.out.println("Failed to start nitro websocket proxy");
|
||||
logger.error("Failed to start nitro websocket proxy");
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
websocketPort = nitroWebsocketProxy.getPort();
|
||||
|
||||
logger.info("Websocket proxy is listening on port {}", websocketPort);
|
||||
logger.info("Nitro proxy started");
|
||||
|
||||
stateSetter.setState(HState.WAITING_FOR_CLIENT);
|
||||
}
|
||||
|
||||
@ -69,19 +85,25 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
||||
return;
|
||||
}
|
||||
|
||||
if (abortLock.compareAndSet(true, true)) {
|
||||
if (!abortLock.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Aborting nitro proxy");
|
||||
|
||||
stateSetter.setState(HState.ABORTING);
|
||||
|
||||
new Thread(() -> {
|
||||
logger.info("Stopping nitro http proxy");
|
||||
|
||||
try {
|
||||
nitroHttpProxy.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
logger.info("Stopping nitro websocket proxy");
|
||||
|
||||
try {
|
||||
nitroWebsocketProxy.stop();
|
||||
} catch (Exception e) {
|
||||
@ -91,15 +113,21 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
||||
stateSetter.setState(HState.NOT_CONNECTED);
|
||||
|
||||
connection.getStateObservable().removeListener(this);
|
||||
|
||||
logger.info("Nitro proxy stopped");
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String replaceWebsocketServer(String configUrl, String websocketUrl) {
|
||||
originalWebsocketUrl = websocketUrl;
|
||||
originalOriginUrl = extractOriginUrl(configUrl);
|
||||
|
||||
return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT);
|
||||
return String.format("ws://127.0.0.1:%d", websocketPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOriginCookies(String cookieHeaderValue) {
|
||||
originalCookies = cookieHeaderValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,15 +143,4 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractOriginUrl(String url) {
|
||||
try {
|
||||
final URI uri = new URI(url);
|
||||
return String.format("%s://%s/", uri.getScheme(), uri.getHost());
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
package gearth.protocol.connection.proxy.nitro.http;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.misc.ConfirmationDialog;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
||||
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.littleshoot.proxy.HttpProxyServer;
|
||||
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
|
||||
import org.littleshoot.proxy.mitm.Authority;
|
||||
@ -53,14 +51,12 @@ public class NitroHttpProxy {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, ADMIN_WARNING_KEY,
|
||||
"Root certificate installation", null,
|
||||
"", "Remember my choice",
|
||||
LanguageBundle.get("alert.rootcertificate.title"), null,
|
||||
"", LanguageBundle.get("alert.rootcertificate.remember"),
|
||||
ButtonType.YES, ButtonType.NO
|
||||
);
|
||||
|
||||
alert.getDialogPane().setContent(new Label("G-Earth detected that you do not have the root certificate authority installed.\n" +
|
||||
"This is required for Nitro to work, do you want to continue?\n" +
|
||||
"G-Earth will ask you for Administrator permission if you do so."));
|
||||
alert.getDialogPane().setContent(new Label(LanguageBundle.get("alert.rootcertificate.content").replaceAll("\\\\n", System.lineSeparator())));
|
||||
|
||||
try {
|
||||
shouldInstall.set(TitleBarController.create(alert).showAlertAndWait()
|
||||
@ -109,6 +105,7 @@ public class NitroHttpProxy {
|
||||
.withPort(NitroConstants.HTTP_PORT)
|
||||
.withManInTheMiddle(new NitroCertificateSniffingManager(authority))
|
||||
.withFiltersSource(new NitroHttpProxyFilterSource(serverCallback))
|
||||
.withTransparent(true)
|
||||
.start();
|
||||
|
||||
if (!initializeCertificate()) {
|
||||
|
@ -7,6 +7,10 @@ import io.netty.util.CharsetUtil;
|
||||
import org.littleshoot.proxy.HttpFiltersAdapter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -16,6 +20,17 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
||||
private static final String NitroClientSearch = "configurationUrls:";
|
||||
private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE);
|
||||
|
||||
// https://developers.cloudflare.com/fundamentals/get-started/reference/cloudflare-cookies/
|
||||
private static final HashSet<String> CloudflareCookies = new HashSet<>(Arrays.asList(
|
||||
"__cflb",
|
||||
"__cf_bm",
|
||||
"cf_ob_info",
|
||||
"cf_use_ob",
|
||||
"__cfwaitingroom",
|
||||
"__cfruid",
|
||||
"cf_clearance"
|
||||
));
|
||||
|
||||
private static final String HeaderAcceptEncoding = "Accept-Encoding";
|
||||
private static final String HeaderAge = "Age";
|
||||
private static final String HeaderCacheControl = "Cache-Control";
|
||||
@ -27,6 +42,7 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
||||
|
||||
private final NitroHttpProxyServerCallback callback;
|
||||
private final String url;
|
||||
private String cookies;
|
||||
|
||||
public NitroHttpProxyFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, NitroHttpProxyServerCallback callback, String url) {
|
||||
super(originalRequest, ctx);
|
||||
@ -58,6 +74,9 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
||||
|
||||
// Disable caching.
|
||||
stripCacheHeaders(headers);
|
||||
|
||||
// Find relevant cookies for the WebSocket connection.
|
||||
this.cookies = parseCookies(request);
|
||||
}
|
||||
|
||||
return super.clientToProxyRequest(httpObject);
|
||||
@ -77,13 +96,15 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
||||
|
||||
if (matcher.find()) {
|
||||
final String originalWebsocket = matcher.group(1);
|
||||
final String replacementWebsocket = callback.replaceWebsocketServer(url, originalWebsocket);
|
||||
final String replacementWebsocket = callback.replaceWebsocketServer(this.url, originalWebsocket);
|
||||
|
||||
if (replacementWebsocket != null) {
|
||||
responseBody = responseBody.replace(originalWebsocket, replacementWebsocket);
|
||||
responseModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
callback.setOriginCookies(this.cookies);
|
||||
}
|
||||
|
||||
// Apply changes.
|
||||
@ -100,6 +121,32 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
||||
return httpObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cookies from the request need to be recorded for the websocket connection to the origin server.
|
||||
*/
|
||||
private String parseCookies(final HttpRequest request) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
final List<String> cookieHeaders = request.headers().getAll("Cookie");
|
||||
|
||||
for (final String cookieHeader : cookieHeaders) {
|
||||
final String[] cookies = cookieHeader.split(";");
|
||||
|
||||
for (final String cookie : cookies) {
|
||||
final String[] parts = cookie.trim().split("=");
|
||||
|
||||
if (CloudflareCookies.contains(parts[0])) {
|
||||
result.add(cookie.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.join("; ", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify Content-Security-Policy header, which could prevent Nitro from connecting with G-Earth.
|
||||
*/
|
||||
@ -148,5 +195,4 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter {
|
||||
headers.remove(HeaderIfModifiedSince);
|
||||
headers.remove(HeaderLastModified);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,4 +11,9 @@ public interface NitroHttpProxyServerCallback {
|
||||
*/
|
||||
String replaceWebsocketServer(String configUrl, String websocketUrl);
|
||||
|
||||
/**
|
||||
* Sets the parsed cookies for the origin WebSocket connection.
|
||||
*/
|
||||
void setOriginCookies(String cookieHeaderValue);
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package gearth.protocol.connection.proxy.nitro.websocket;
|
||||
|
||||
import javax.websocket.Session;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
||||
public interface NitroSession {
|
||||
|
||||
|
@ -6,15 +6,24 @@ import gearth.protocol.connection.*;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
|
||||
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
|
||||
import org.eclipse.jetty.websocket.jsr356.JsrSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@ServerEndpoint(value = "/")
|
||||
public class NitroWebsocketClient implements NitroSession {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NitroWebsocketClient.class);
|
||||
|
||||
private final HProxySetter proxySetter;
|
||||
private final HStateSetter stateSetter;
|
||||
private final HConnection connection;
|
||||
@ -23,7 +32,7 @@ public class NitroWebsocketClient implements NitroSession {
|
||||
private final NitroPacketHandler packetHandler;
|
||||
private final AtomicBoolean shutdownLock;
|
||||
|
||||
private Session activeSession = null;
|
||||
private JsrSession activeSession = null;
|
||||
|
||||
public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) {
|
||||
this.proxySetter = proxySetter;
|
||||
@ -36,11 +45,22 @@ public class NitroWebsocketClient implements NitroSession {
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
public void onOpen(Session session) throws IOException {
|
||||
activeSession = session;
|
||||
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||
public void onOpen(Session session) throws Exception {
|
||||
logger.info("WebSocket connection accepted");
|
||||
|
||||
server.connect(proxyProvider.getOriginalWebsocketUrl(), proxyProvider.getOriginalOriginUrl());
|
||||
activeSession = (JsrSession) session;
|
||||
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||
activeSession.setMaxTextMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||
|
||||
// Set proper headers to spoof being a real client.
|
||||
final Map<String, List<String>> headers = new HashMap<>(activeSession.getUpgradeRequest().getHeaders());
|
||||
|
||||
if (proxyProvider.getOriginalCookies() != null) {
|
||||
headers.put("Cookie", Collections.singletonList(proxyProvider.getOriginalCookies()));
|
||||
}
|
||||
|
||||
// Connect to origin server.
|
||||
server.connect(proxyProvider.getOriginalWebsocketUrl(), headers);
|
||||
|
||||
final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, "");
|
||||
|
||||
@ -75,7 +95,7 @@ public class NitroWebsocketClient implements NitroSession {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession() {
|
||||
public org.eclipse.jetty.websocket.api.Session getSession() {
|
||||
return activeSession;
|
||||
}
|
||||
|
||||
@ -89,7 +109,7 @@ public class NitroWebsocketClient implements NitroSession {
|
||||
|
||||
try {
|
||||
activeSession.close();
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
activeSession = null;
|
||||
|
@ -3,10 +3,11 @@ package gearth.protocol.connection.proxy.nitro.websocket;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.connection.HProxySetter;
|
||||
import gearth.protocol.connection.HStateSetter;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
||||
@ -28,7 +29,7 @@ public class NitroWebsocketProxy {
|
||||
this.stateSetter = stateSetter;
|
||||
this.connection = connection;
|
||||
this.proxyProvider = proxyProvider;
|
||||
this.server = new Server(NitroConstants.WEBSOCKET_PORT);
|
||||
this.server = new Server(0);
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
@ -56,6 +57,12 @@ public class NitroWebsocketProxy {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
final ServerConnector serverConnector = (ServerConnector) server.getConnectors()[0];
|
||||
|
||||
return serverConnector.getLocalPort();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
server.stop();
|
||||
|
@ -5,15 +5,39 @@ import gearth.protocol.HMessage;
|
||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||
import gearth.protocol.packethandler.PacketHandler;
|
||||
import gearth.protocol.packethandler.nitro.NitroPacketHandler;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.websocket.*;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
||||
public class NitroWebsocketServer implements WebSocketListener, NitroSession {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NitroWebsocketServer.class);
|
||||
|
||||
private static final HashSet<String> SKIP_HEADERS = new HashSet<>(Arrays.asList(
|
||||
"Sec-WebSocket-Extensions",
|
||||
"Sec-WebSocket-Key",
|
||||
"Sec-WebSocket-Version",
|
||||
"Host",
|
||||
"Connection",
|
||||
"Upgrade"
|
||||
));
|
||||
|
||||
private final PacketHandler packetHandler;
|
||||
private final NitroWebsocketClient client;
|
||||
@ -24,55 +48,31 @@ public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
||||
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables());
|
||||
}
|
||||
|
||||
public void connect(String websocketUrl, String originUrl) throws IOException {
|
||||
public void connect(String websocketUrl, Map<String, List<String>> clientHeaders) throws IOException {
|
||||
try {
|
||||
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||
logger.info("Building origin websocket connection ({})", websocketUrl);
|
||||
|
||||
if (originUrl != null) {
|
||||
builder.configurator(new ClientEndpointConfig.Configurator() {
|
||||
@Override
|
||||
public void beforeRequest(Map<String, List<String>> headers) {
|
||||
headers.put("Origin", Collections.singletonList(originUrl));
|
||||
}
|
||||
});
|
||||
}
|
||||
final WebSocketClient client = createWebSocketClient();
|
||||
|
||||
ClientEndpointConfig config = builder.build();
|
||||
final ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
|
||||
ContainerProvider.getWebSocketContainer().connectToServer(this, config, URI.create(websocketUrl));
|
||||
} catch (DeploymentException e) {
|
||||
throw new IOException("Failed to deploy websocket client", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
this.activeSession = session;
|
||||
this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||
this.activeSession.addMessageHandler(new MessageHandler.Whole<byte[]>() {
|
||||
@Override
|
||||
public void onMessage(byte[] message) {
|
||||
try {
|
||||
packetHandler.act(message);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
clientHeaders.forEach((key, value) -> {
|
||||
if (SKIP_HEADERS.contains(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, CloseReason closeReason) {
|
||||
// Hotel closed connection.
|
||||
client.shutdownProxy();
|
||||
}
|
||||
request.setHeader(key, value);
|
||||
});
|
||||
|
||||
@Override
|
||||
public void onError(Session session, Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
logger.info("Connecting to origin websocket at {}", websocketUrl);
|
||||
|
||||
// Shutdown.
|
||||
client.shutdownProxy();
|
||||
client.start();
|
||||
client.connect(this, URI.create(websocketUrl), request);
|
||||
|
||||
logger.info("Connected to origin websocket");
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to start websocket client to origin " + websocketUrl, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -94,10 +94,86 @@ public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
||||
|
||||
try {
|
||||
activeSession.close();
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
activeSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketBinary(byte[] bytes, int i, int i1) {
|
||||
try {
|
||||
packetHandler.act(bytes);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to handle packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String s) {
|
||||
logger.warn("Received text message from hotel");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int i, String s) {
|
||||
// Hotel closed connection.
|
||||
client.shutdownProxy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(org.eclipse.jetty.websocket.api.Session session) {
|
||||
activeSession = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
|
||||
// Shutdown.
|
||||
client.shutdownProxy();
|
||||
}
|
||||
|
||||
private SSLContext createSSLContext() {
|
||||
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
}};
|
||||
|
||||
try {
|
||||
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
|
||||
sslContext.init(null, trustAllCerts, new SecureRandom());
|
||||
|
||||
return sslContext;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to setup ssl context", e);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpClient createHttpClient() {
|
||||
final SslContextFactory.Client factory = new SslContextFactory.Client();
|
||||
|
||||
factory.setSslContext(createSSLContext());
|
||||
|
||||
return new HttpClient(factory);
|
||||
}
|
||||
|
||||
private WebSocketClient createWebSocketClient() {
|
||||
final WebSocketClient client = new WebSocketClient(createHttpClient());
|
||||
|
||||
client.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||
client.setMaxTextMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,21 @@
|
||||
package gearth.protocol.hostreplacer.hostsfile;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 04/04/18.
|
||||
*/
|
||||
class UnixHostReplacer implements HostReplacer {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(UnixHostReplacer.class);
|
||||
|
||||
protected String hostsFileLocation;
|
||||
|
||||
UnixHostReplacer() {
|
||||
@ -17,23 +24,24 @@ class UnixHostReplacer implements HostReplacer {
|
||||
|
||||
@Override
|
||||
public void addRedirect(String[] lines) {
|
||||
List<String> adders = new ArrayList<>();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
adders.add(lines[i] + "\t# G-Earth replacement");
|
||||
}
|
||||
|
||||
FileReader fr = null;
|
||||
BufferedReader br = null;
|
||||
FileWriter fw = null;
|
||||
BufferedWriter out = null;
|
||||
final List<String> adders = appendCommentToEachLine(lines);
|
||||
|
||||
FileReader fr;
|
||||
BufferedReader br;
|
||||
FileWriter fw;
|
||||
BufferedWriter out;
|
||||
try
|
||||
{
|
||||
ArrayList<String> fileLines = new ArrayList<>();
|
||||
File f1 = new File(hostsFileLocation);
|
||||
fr = new FileReader(f1);
|
||||
final ArrayList<String> fileLines = new ArrayList<>();
|
||||
final File hostsFile = new File(hostsFileLocation);
|
||||
|
||||
LOGGER.debug("Replacing hosts at {}", hostsFile.getAbsolutePath());
|
||||
|
||||
fr = new FileReader(hostsFile);
|
||||
br = new BufferedReader(fr);
|
||||
String line = null;
|
||||
|
||||
String line;
|
||||
while ((line = br.readLine()) != null)
|
||||
{
|
||||
adders.remove(line);
|
||||
@ -42,7 +50,7 @@ class UnixHostReplacer implements HostReplacer {
|
||||
fr.close();
|
||||
br.close();
|
||||
|
||||
fw = new FileWriter(f1);
|
||||
fw = new FileWriter(hostsFile);
|
||||
out = new BufferedWriter(fw);
|
||||
|
||||
for (String li : adders) {
|
||||
@ -58,29 +66,26 @@ class UnixHostReplacer implements HostReplacer {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
LOGGER.error("Failed to add host redirects", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRedirect(String[] lines) {
|
||||
ArrayList<String> removers = new ArrayList<>();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
removers.add(lines[i] + "\t# G-Earth replacement");
|
||||
}
|
||||
final List<String> removers = appendCommentToEachLine(lines);
|
||||
|
||||
FileReader fr = null;
|
||||
BufferedReader br = null;
|
||||
FileWriter fw = null;
|
||||
BufferedWriter out = null;
|
||||
FileReader fr;
|
||||
BufferedReader br;
|
||||
FileWriter fw;
|
||||
BufferedWriter out;
|
||||
|
||||
try
|
||||
{
|
||||
ArrayList<String> fileLines = new ArrayList<String>();
|
||||
File f1 = new File(hostsFileLocation);
|
||||
fr = new FileReader(f1);
|
||||
final ArrayList<String> fileLines = new ArrayList<>();
|
||||
final File hostsFile = new File(hostsFileLocation);
|
||||
fr = new FileReader(hostsFile);
|
||||
br = new BufferedReader(fr);
|
||||
String line = null;
|
||||
String line;
|
||||
while ((line = br.readLine()) != null)
|
||||
{
|
||||
if (!removers.contains(line))
|
||||
@ -89,12 +94,13 @@ class UnixHostReplacer implements HostReplacer {
|
||||
fr.close();
|
||||
br.close();
|
||||
|
||||
fw = new FileWriter(f1);
|
||||
fw = new FileWriter(hostsFile);
|
||||
out = new BufferedWriter(fw);
|
||||
|
||||
for (int i = 0; i < fileLines.size(); i++) {
|
||||
out.write(fileLines.get(i));
|
||||
if (i != fileLines.size() - 1) out.write(System.getProperty("line.separator"));
|
||||
if (i != fileLines.size() - 1)
|
||||
out.write(System.getProperty("line.separator"));
|
||||
}
|
||||
out.flush();
|
||||
fw.close();
|
||||
@ -102,7 +108,14 @@ class UnixHostReplacer implements HostReplacer {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
LOGGER.error("Failed to remove host replace lines", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> appendCommentToEachLine(String[] lines) {
|
||||
return Arrays
|
||||
.stream(lines)
|
||||
.map(line -> line + "\t# G-Earth replacement")
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import gearth.protocol.packethandler.PayloadBuffer;
|
||||
import gearth.protocol.packethandler.flash.BufferChangeListener;
|
||||
import gearth.protocol.packethandler.flash.FlashPacketHandler;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
@ -17,6 +18,8 @@ import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@ -24,7 +27,9 @@ import java.util.List;
|
||||
|
||||
public class Rc4Obtainer {
|
||||
|
||||
public static final boolean DEBUG = false;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Rc4Obtainer.class);
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
private final HabboClient client;
|
||||
private List<FlashPacketHandler> flashPacketHandlers;
|
||||
@ -57,8 +62,7 @@ public class Rc4Obtainer {
|
||||
new Thread(() -> {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
if (DEBUG) System.out.println("[+] send encrypted");
|
||||
|
||||
LOGGER.debug("[+] send encrypted");
|
||||
boolean worked = false;
|
||||
int i = 0;
|
||||
while (!worked && i < 4) {
|
||||
@ -69,13 +73,12 @@ public class Rc4Obtainer {
|
||||
}
|
||||
|
||||
if (!worked) {
|
||||
System.err.println("COULD NOT FIND RC4 TABLE");
|
||||
|
||||
LOGGER.error("COULD NOT FIND RC4 TABLE");
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, "Something went wrong!", ButtonType.OK);
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("alert.somethingwentwrong.title"), ButtonType.OK);
|
||||
|
||||
FlowPane fp = new FlowPane();
|
||||
Label lbl = new Label("G-Earth has experienced an issue" + System.lineSeparator() + System.lineSeparator() + "Head over to our Troubleshooting page to solve the problem:");
|
||||
Label lbl = new Label(LanguageBundle.get("alert.somethingwentwrong.content").replaceAll("\\\\n", System.lineSeparator()));
|
||||
Hyperlink link = new Hyperlink("https://github.com/sirjonasxx/G-Earth/wiki/Troubleshooting");
|
||||
fp.getChildren().addAll(lbl, link);
|
||||
link.setOnAction(event -> {
|
||||
@ -89,14 +92,13 @@ public class Rc4Obtainer {
|
||||
try {
|
||||
TitleBarController.create(alert).showAlert();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to create error alert", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final long endTime = System.currentTimeMillis();
|
||||
if (DEBUG)
|
||||
System.out.println("Cracked RC4 in " + (endTime - startTime) + "ms");
|
||||
LOGGER.debug("Cracked RC4 in " + (endTime - startTime) + "ms");
|
||||
|
||||
flashPacketHandlers.forEach(FlashPacketHandler::unblock);
|
||||
}).start();
|
||||
|
@ -3,7 +3,7 @@ package gearth.protocol.memory.habboclient;
|
||||
import gearth.misc.OSValidator;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.memory.habboclient.linux.LinuxHabboClient;
|
||||
import gearth.protocol.memory.habboclient.macOs.MacOsHabboClient;
|
||||
import gearth.protocol.memory.habboclient.macos.MacOSHabboClient;
|
||||
import gearth.protocol.memory.habboclient.windows.WindowsHabboClient;
|
||||
|
||||
/**
|
||||
@ -15,7 +15,7 @@ public class HabboClientFactory {
|
||||
public static HabboClient get(HConnection connection) {
|
||||
if (OSValidator.isUnix()) return new LinuxHabboClient(connection);
|
||||
if (OSValidator.isWindows()) return new WindowsHabboClient(connection);
|
||||
if (OSValidator.isMac()) return new MacOsHabboClient(connection);
|
||||
if (OSValidator.isMac()) return new MacOSHabboClient(connection);
|
||||
|
||||
// todo use rust if beneficial
|
||||
|
||||
|
@ -1,144 +0,0 @@
|
||||
package gearth.protocol.memory.habboclient.macOs;
|
||||
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.memory.habboclient.HabboClient;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class MacOsHabboClient extends HabboClient {
|
||||
|
||||
public MacOsHabboClient(HConnection connection) {
|
||||
super(connection);
|
||||
|
||||
connection.addTrafficListener(0, message -> {
|
||||
if (message.getDestination() == HMessage.Direction.TOSERVER && message.getPacket().headerId() == PRODUCTION_ID) {
|
||||
production = message.getPacket().readString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static final String OFFSETS_CACHE_KEY = "RC4Offsets";
|
||||
private static final int PRODUCTION_ID = 4000;
|
||||
private String production = "";
|
||||
|
||||
@Override
|
||||
public List<byte[]> getRC4cached() {
|
||||
List<byte[]> result = new ArrayList<>();
|
||||
try {
|
||||
List<String> possibleResults = readPossibleBytes(true);
|
||||
|
||||
if (possibleResults == null)
|
||||
return new ArrayList<>();
|
||||
|
||||
for (String s : possibleResults)
|
||||
result.add(hexStringToByteArray(s));
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ArrayList<String> readPossibleBytes(boolean useCache) throws IOException, URISyntaxException {
|
||||
ProcessBuilder pb;
|
||||
|
||||
JSONObject revisionList = (JSONObject) Cacher.get(OFFSETS_CACHE_KEY);
|
||||
if (revisionList == null) {
|
||||
Cacher.put(OFFSETS_CACHE_KEY, new JSONObject());
|
||||
revisionList = (JSONObject) Cacher.get(OFFSETS_CACHE_KEY);
|
||||
}
|
||||
|
||||
assert revisionList != null;
|
||||
JSONArray cachedOffsets;
|
||||
if (revisionList.has(production))
|
||||
cachedOffsets = (JSONArray) revisionList.get(production);
|
||||
else
|
||||
cachedOffsets = null;
|
||||
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
|
||||
if (useCache) {
|
||||
if (cachedOffsets == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Object s : cachedOffsets) {
|
||||
joiner.add((String)s);
|
||||
}
|
||||
}
|
||||
|
||||
String g_mem = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/G-Mem";
|
||||
if (!useCache)
|
||||
pb = new ProcessBuilder(g_mem, hConnection.getClientHost() , Integer.toString(hConnection.getClientPort()));
|
||||
else
|
||||
pb = new ProcessBuilder(g_mem, hConnection.getClientHost() , Integer.toString(hConnection.getClientPort()), "-c" + joiner.toString());
|
||||
|
||||
|
||||
Process p = pb.start();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
|
||||
String line;
|
||||
ArrayList<String> possibleData = new ArrayList<>();
|
||||
|
||||
if (cachedOffsets == null) {
|
||||
cachedOffsets = new JSONArray();
|
||||
}
|
||||
|
||||
|
||||
int count = 0;
|
||||
while((line = reader.readLine()) != null) {
|
||||
if (line.length() > 1) {
|
||||
if (!useCache && (count++ % 2 == 0)) {
|
||||
if (!cachedOffsets.toList().contains(line)) {
|
||||
cachedOffsets.put(line);
|
||||
System.out.println("[+] " + line);
|
||||
}
|
||||
}
|
||||
else
|
||||
possibleData.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
revisionList.put(production, cachedOffsets);
|
||||
Cacher.put(OFFSETS_CACHE_KEY, revisionList);
|
||||
p.destroy();
|
||||
return possibleData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getRC4possibilities() {
|
||||
List<byte[]> result = new ArrayList<>();
|
||||
try {
|
||||
ArrayList<String> possibleData = readPossibleBytes(false);
|
||||
|
||||
for (String possibleHexStr : possibleData) {
|
||||
result.add(hexStringToByteArray(possibleHexStr));
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package gearth.protocol.memory.habboclient.macos;
|
||||
|
||||
import gearth.misc.StringUtils;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.memory.habboclient.HabboClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a {@link HabboClient} implementation for the MacOS operating system.
|
||||
*
|
||||
* @author sirjonasxx / dorving (revised)
|
||||
*/
|
||||
public class MacOSHabboClient extends HabboClient {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MacOSHabboClient.class);
|
||||
|
||||
private static final String G_MEM_EXECUTABLE_FILE_NAME = "/g_mem_mac";
|
||||
|
||||
/**
|
||||
* The header id (opcode) of the packet that contains the value for the {@link #production} field.
|
||||
*/
|
||||
private static final int PRODUCTION_ID = 4000;
|
||||
|
||||
private String production = "";
|
||||
|
||||
/**
|
||||
* Create a new {@link MacOSHabboClient} instance.
|
||||
*
|
||||
* @param connection the {@link HConnection connection} with the Habbo server.
|
||||
*/
|
||||
public MacOSHabboClient(HConnection connection) {
|
||||
super(connection);
|
||||
listenForProductionPacket(connection);
|
||||
}
|
||||
|
||||
private void listenForProductionPacket(HConnection connection) {
|
||||
connection.addTrafficListener(0, message -> {
|
||||
if (message.getDestination() == HMessage.Direction.TOSERVER) {
|
||||
final HPacket packet = message.getPacket();
|
||||
if (packet.headerId() == PRODUCTION_ID) {
|
||||
production = packet.readString();
|
||||
LOGGER.debug("Read production packet from connection {}, set `production` to {}", connection, production);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getRC4cached() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<byte[]> getRC4possibilities() {
|
||||
final List<byte[]> result = new ArrayList<>();
|
||||
try {
|
||||
for (String possibleHexStr : readPossibleBytes())
|
||||
result.add(StringUtils.hexStringToByteArray(possibleHexStr));
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
LOGGER.error("Failed to parse line as hex string", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ArrayList<String> readPossibleBytes() throws IOException, URISyntaxException {
|
||||
final String pathToGMemExecutable = getPathToGMemExecutable();
|
||||
final String clientHost = hConnection.getClientHost();
|
||||
final String clientPort = Integer.toString(hConnection.getClientPort());
|
||||
LOGGER.debug("Attempting to execute G-Mem executable {} with host {} at port {}", pathToGMemExecutable, clientHost, clientPort);
|
||||
final Process process = new ProcessBuilder(pathToGMemExecutable, clientHost, clientPort)
|
||||
.start();
|
||||
final ArrayList<String> possibleData = new ArrayList<>();
|
||||
try(BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
int count = 0;
|
||||
String line;
|
||||
while((line = reader.readLine()) != null) {
|
||||
if (line.length() > 1 && (count++ % 2 != 0))
|
||||
possibleData.add(line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to execute G-Mem", e);
|
||||
} finally {
|
||||
process.destroy();
|
||||
}
|
||||
LOGGER.debug("Read {} from G-Mem output stream", possibleData);
|
||||
return possibleData;
|
||||
}
|
||||
|
||||
private String getPathToGMemExecutable() throws URISyntaxException {
|
||||
return new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + G_MEM_EXECUTABLE_FILE_NAME;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package gearth.protocol.memory.habboclient.rust;
|
||||
|
||||
import gearth.misc.StringUtils;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.memory.habboclient.HabboClient;
|
||||
|
||||
@ -48,18 +49,9 @@ public class RustHabboClient extends HabboClient {
|
||||
List<byte[]> ret = new ArrayList<>();
|
||||
|
||||
for (String possibleHexStr : possibleData)
|
||||
ret.add(hexStringToByteArray(possibleHexStr));
|
||||
ret.add(StringUtils.hexStringToByteArray(possibleHexStr));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package gearth.protocol.memory.habboclient.windows;
|
||||
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.misc.StringUtils;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.memory.habboclient.HabboClient;
|
||||
@ -44,7 +45,7 @@ public class WindowsHabboClient extends HabboClient {
|
||||
return new ArrayList<>();
|
||||
|
||||
for (String s : possibleResults)
|
||||
result.add(hexStringToByteArray(s));
|
||||
result.add(StringUtils.hexStringToByteArray(s));
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -124,23 +125,12 @@ public class WindowsHabboClient extends HabboClient {
|
||||
List<byte[]> result = new ArrayList<>();
|
||||
try {
|
||||
ArrayList<String> possibleData = readPossibleBytes(false);
|
||||
|
||||
for (String possibleHexStr : possibleData) {
|
||||
result.add(hexStringToByteArray(possibleHexStr));
|
||||
result.add(StringUtils.hexStringToByteArray(possibleHexStr));
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import java.util.List;
|
||||
|
||||
public abstract class FlashPacketHandler extends PacketHandler {
|
||||
|
||||
protected static final boolean DEBUG = false;
|
||||
protected static final boolean DEBUG = true;
|
||||
|
||||
private volatile OutputStream out;
|
||||
private volatile boolean isTempBlocked = false;
|
||||
|
@ -6,13 +6,17 @@ import gearth.protocol.connection.proxy.nitro.websocket.NitroSession;
|
||||
import gearth.protocol.packethandler.PacketHandler;
|
||||
import gearth.protocol.packethandler.PayloadBuffer;
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.websocket.Session;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class NitroPacketHandler extends PacketHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NitroPacketHandler.class);
|
||||
|
||||
private final HMessage.Direction direction;
|
||||
private final NitroSession session;
|
||||
private final PayloadBuffer payloadBuffer;
|
||||
@ -39,7 +43,13 @@ public class NitroPacketHandler extends PacketHandler {
|
||||
buffer = buffer.clone();
|
||||
}
|
||||
|
||||
localSession.getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer));
|
||||
try {
|
||||
localSession.getRemote().sendBytes(ByteBuffer.wrap(buffer));
|
||||
} catch (IOException e) {
|
||||
logger.error("Error sending packet to nitro client", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ public class WindowsPortChecker implements PortChecker{
|
||||
}
|
||||
|
||||
private String getProcessNameFromPid(String pid) throws IOException {
|
||||
String task = getCommandOutput(new String[] {"tasklist /fi \"pid eq " + pid + "\" /nh /fo:CSV"});
|
||||
String task = getCommandOutput(new String[] {"cmd", "/c", "tasklist", "/fi", String.format("pid eq %s", pid) ,"/nh", "/fo:CSV"});
|
||||
int index = task.indexOf(',');
|
||||
return task.substring(0, index);
|
||||
}
|
||||
|
@ -200,11 +200,18 @@ public class ExtensionHandler {
|
||||
|
||||
@Override
|
||||
public void sendMessage(HMessage.Direction direction, HPacket packet) {
|
||||
boolean success;
|
||||
if (direction == HMessage.Direction.TOCLIENT) {
|
||||
hConnection.sendToClient(packet);
|
||||
success = hConnection.sendToClient(packet);
|
||||
}
|
||||
else {
|
||||
hConnection.sendToServer(packet);
|
||||
success = hConnection.sendToServer(packet);
|
||||
}
|
||||
|
||||
if (!success && hConnection.isPacketSendingAllowed(direction, packet) && !hConnection.isPacketSendingSafe(direction, packet)) {
|
||||
extension.getExtensionObservable().fireEvent(extensionListener ->
|
||||
extensionListener.log(String.format("Extension %s attempted to send an unsafe packet, but had no permission",
|
||||
extension.getTitle())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ public abstract class ExtensionListener {
|
||||
protected void manipulatedPacket(HMessage hMessage) {}
|
||||
protected void flagsRequest() {}
|
||||
protected void sendMessage(HMessage.Direction direction, HPacket packet) {}
|
||||
protected void log(String text) {}
|
||||
public void log(String text) {}
|
||||
protected void hasClosed() {}
|
||||
|
||||
protected void packetToStringRequest(HPacket packet) {}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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 java.util.ArrayList;
|
||||
@ -9,13 +9,17 @@ import java.util.List;
|
||||
public class ExtensionProducerFactory {
|
||||
// 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() {
|
||||
List<ExtensionProducer> all = new ArrayList<>();
|
||||
all.add(new NetworkExtensionsProducer());
|
||||
all.add(EXTENSION_SERVER);
|
||||
all.add(new SimpleExtensionProducer());
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
|
||||
public static NetworkExtensionServer getExtensionServer() {
|
||||
return EXTENSION_SERVER;
|
||||
}
|
||||
}
|
||||
|
@ -1,260 +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()) {
|
||||
log("Forwarding incoming packet (packet="+packet+")");
|
||||
sendMessage(
|
||||
side == 0 ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER,
|
||||
packet
|
||||
);
|
||||
} else {
|
||||
log("Received corrupted packet (packet="+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;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.misc.ConfirmationDialog;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 16/10/18.
|
||||
*/
|
||||
public final class NetworkExtensionAuthenticator {
|
||||
|
||||
private static final Map<String, String> COOKIES = new HashMap<>();
|
||||
private static final Set<String> PERSISTENT_COOKIES = new HashSet<>();
|
||||
|
||||
private static volatile boolean rememberOption = false;
|
||||
|
||||
public static boolean evaluate(NetworkExtensionClient extension) {
|
||||
|
||||
final String cookie = extension.getCookie();
|
||||
|
||||
if (cookie != null && PERSISTENT_COOKIES.contains(cookie))
|
||||
return true;
|
||||
|
||||
return extension.isInstalledExtension()
|
||||
? claimSession(extension.getFileName(), cookie)
|
||||
: askForPermission(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate an extension and remove the cookie
|
||||
*
|
||||
* @return {@code true} if the extension is authenticated.
|
||||
*/
|
||||
private static boolean claimSession(String filename, String cookie) {
|
||||
if (COOKIES.containsKey(filename) && COOKIES.get(filename).equals(cookie)) {
|
||||
COOKIES.remove(filename);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For not yet installed extensions, open a confirmation dialog.
|
||||
*
|
||||
* @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};
|
||||
|
||||
final String connectExtensionKey = "allow_extension_connection";
|
||||
|
||||
if (ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(0);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, connectExtensionKey
|
||||
, LanguageBundle.get("alert.confirmation.windowtitle"), null,
|
||||
"", LanguageBundle.get("alert.confirmation.button.remember"),
|
||||
ButtonType.YES, ButtonType.NO
|
||||
);
|
||||
|
||||
alert.getDialogPane().setContent(new Label(String.format(LanguageBundle.get("alert.extconnection.content"), extension.getTitle()).replaceAll("\\\\n", System.lineSeparator())));
|
||||
|
||||
try {
|
||||
if (!(TitleBarController.create(alert).showAlertAndWait()
|
||||
.filter(t -> t == ButtonType.YES).isPresent())) {
|
||||
allowConnection[0] = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
if (!ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||
rememberOption = allowConnection[0];
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
countDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return allowConnection[0];
|
||||
}
|
||||
|
||||
return rememberOption;
|
||||
}
|
||||
|
||||
public static String generateCookieForExtension(String filename) {
|
||||
final String cookie = generateRandomCookie();
|
||||
COOKIES.put(filename, cookie);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network.authentication;
|
||||
|
||||
import gearth.misc.ConfirmationDialog;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtension;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 16/10/18.
|
||||
*/
|
||||
public class Authenticator {
|
||||
|
||||
private static Map<String, String> cookies = new HashMap<>();
|
||||
private static Set<String> perma_cookies = new HashSet<>();
|
||||
|
||||
public static String generateCookieForExtension(String filename) {
|
||||
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) {
|
||||
if (extension.getCookie() != null && perma_cookies.contains(extension.getCookie())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (extension.isInstalledExtension()) {
|
||||
return claimSession(extension.getFileName(), extension.getCookie());
|
||||
}
|
||||
else {
|
||||
return askForPermission(extension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticator: authenticate an extension and remove the cookie
|
||||
* @param filename
|
||||
* @param cookie
|
||||
* @return if the extension is authenticated
|
||||
*/
|
||||
private static boolean claimSession(String filename, String cookie) {
|
||||
if (cookies.containsKey(filename) && cookies.get(filename).equals(cookie)) {
|
||||
cookies.remove(filename);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static volatile boolean rememberOption = false;
|
||||
//for not-installed extensions, popup a dialog
|
||||
private static boolean askForPermission(NetworkExtension extension) {
|
||||
boolean[] allowConnection = {true};
|
||||
|
||||
final String connectExtensionKey = "allow_extension_connection";
|
||||
|
||||
if (ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||
boolean[] done = {false};
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, connectExtensionKey
|
||||
,"Confirmation Dialog", null,
|
||||
"", "Remember my choice",
|
||||
ButtonType.YES, ButtonType.NO
|
||||
);
|
||||
|
||||
alert.getDialogPane().setContent(new Label("Extension \""+extension.getTitle()+"\" tries to connect but isn't known to G-Earth,\n" +
|
||||
"accept this connection?"));
|
||||
|
||||
try {
|
||||
if (!(TitleBarController.create(alert).showAlertAndWait()
|
||||
.filter(t -> t == ButtonType.YES).isPresent())) {
|
||||
allowConnection[0] = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
done[0] = true;
|
||||
if (!ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||
rememberOption = allowConnection[0];
|
||||
}
|
||||
});
|
||||
|
||||
while (!done[0]) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return allowConnection[0];
|
||||
}
|
||||
|
||||
return rememberOption;
|
||||
}
|
||||
|
||||
private static String getRandomCookie() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < 40; i++) {
|
||||
builder.append(r.nextInt(40));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -8,39 +8,39 @@ import java.util.Map;
|
||||
/**
|
||||
* Created by Jonas on 22/09/18.
|
||||
*/
|
||||
public class ExecutionInfo {
|
||||
public final class ExecutionInfo {
|
||||
|
||||
private static Map<String, String[]> extensionTypeToExecutionCommand;
|
||||
public final static List<String> ALLOWEDEXTENSIONTYPES;
|
||||
public final static String EXTENSIONSDIRECTORY = "Extensions";
|
||||
private static final Map<String, String[]> EXTENSION_TYPE_TO_EXECUTION_COMMAND;
|
||||
|
||||
public final static List<String> ALLOWED_EXTENSION_TYPES;
|
||||
public final static String EXTENSIONS_DIRECTORY = "Extensions";
|
||||
|
||||
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}"};
|
||||
for(String type : extensionTypeToExecutionCommand.keySet()) {
|
||||
String[] commandShort = extensionTypeToExecutionCommand.get(type);
|
||||
String[] combined = new String[extraArgs.length + commandShort.length];
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND = new HashMap<>();
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.jar", new String[]{"java", "-jar", "{path}"});
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py", new String[]{"python", "{path}"});
|
||||
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(extraArgs, 0, combined, commandShort.length, extraArgs.length);
|
||||
|
||||
extensionTypeToExecutionCommand.put(
|
||||
type,
|
||||
combined
|
||||
);
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put(type, combined);
|
||||
}
|
||||
|
||||
ALLOWEDEXTENSIONTYPES = new ArrayList<>(extensionTypeToExecutionCommand.keySet());
|
||||
ALLOWED_EXTENSION_TYPES = new ArrayList<>(EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet());
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public class ExtensionRunnerFactory {
|
||||
public final class ExtensionRunnerFactory {
|
||||
|
||||
private static ExtensionRunner runner = new NormalExtensionRunner();
|
||||
private static final ExtensionRunner runner = new NormalExtensionRunner();
|
||||
|
||||
public static ExtensionRunner get() {
|
||||
return runner;
|
||||
|
@ -1,207 +1,198 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network.executer;
|
||||
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
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.Random;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
final URL url = getLocation();
|
||||
String value;
|
||||
try {
|
||||
value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
|
||||
value = new File(url.toURI()).getParent();
|
||||
} catch (URISyntaxException e) {
|
||||
value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParent();
|
||||
e.printStackTrace();
|
||||
value = new File(url.getPath()).getParent();
|
||||
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
|
||||
public void runAllExtensions(int port) {
|
||||
if (dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)){
|
||||
File folder =
|
||||
new File(JARPATH +
|
||||
FileSystems.getDefault().getSeparator()+
|
||||
ExecutionInfo.EXTENSIONSDIRECTORY);
|
||||
|
||||
File[] childs = folder.listFiles();
|
||||
for (File file : childs) {
|
||||
tryRunExtension(file.getPath(), port);
|
||||
if (dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)) {
|
||||
|
||||
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);
|
||||
} else
|
||||
LOGGER.warn("Did not run extensions because extensions directory does not exist at {}", ExecutionInfo.EXTENSIONS_DIRECTORY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installAndRunExtension(String path, int port) {
|
||||
if (!dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)) {
|
||||
createDirectory(ExecutionInfo.EXTENSIONSDIRECTORY);
|
||||
}
|
||||
public void installAndRunExtension(String stringPath, int port) {
|
||||
|
||||
if (!dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY))
|
||||
tryCreateDirectory(ExecutionInfo.EXTENSIONS_DIRECTORY);
|
||||
|
||||
String name = Paths.get(path).getFileName().toString();
|
||||
String[] split = name.split("\\.");
|
||||
String ext = "*." + split[split.length - 1];
|
||||
final Path path = Paths.get(stringPath);
|
||||
final String name = path.getFileName().toString();
|
||||
final String[] split = name.split("\\.");
|
||||
final String ext = "*." + split[split.length - 1];
|
||||
|
||||
String realname = String.join(".",Arrays.copyOf(split, split.length-1));
|
||||
String newname = realname + "-" + getRandomString() + ext.substring(1);
|
||||
final String realName = String.join(".", Arrays.copyOf(split, split.length - 1));
|
||||
final String newName = realName + "-" + getRandomString() + ext.substring(1);
|
||||
|
||||
Path originalPath = Paths.get(path);
|
||||
Path newPath = Paths.get(
|
||||
JARPATH,
|
||||
ExecutionInfo.EXTENSIONSDIRECTORY,
|
||||
newname
|
||||
);
|
||||
final Path newPath = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, newName);
|
||||
|
||||
try {
|
||||
Files.copy(
|
||||
originalPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
// addExecPermission(newPath.toString());
|
||||
Files.copy(path, newPath);
|
||||
|
||||
tryRunExtension(newPath.toString(), port);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void tryRunExtension(String path, int port) {
|
||||
try {
|
||||
|
||||
if (new File(path).isDirectory()) {
|
||||
// this extension is installed from the extension store and requires
|
||||
// different behavior
|
||||
// this extension is installed from the extension store and requires different behavior
|
||||
StoreExtensionTools.executeExtension(path, port);
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = Paths.get(path).getFileName().toString();
|
||||
|
||||
String[] execCommand = ExecutionInfo.getExecutionCommand(getFileExtension(path));
|
||||
execCommand = Arrays.copyOf(execCommand, execCommand.length);
|
||||
String cookie = Authenticator.generateCookieForExtension(filename);
|
||||
final String filename = Paths.get(path).getFileName().toString();
|
||||
final String[] execCommand = ExecutionInfo
|
||||
.getExecutionCommand(getFileExtension(path))
|
||||
.clone();
|
||||
final String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(filename);
|
||||
for (int i = 0; i < execCommand.length; i++) {
|
||||
execCommand[i] = execCommand[i]
|
||||
.replace("{path}", path)
|
||||
.replace("{port}", port+"")
|
||||
.replace("{port}", port + "")
|
||||
.replace("{filename}", filename)
|
||||
.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) {
|
||||
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)) {
|
||||
String sep = "" + System.lineSeparator();
|
||||
synchronized (System.out) {
|
||||
System.out.println(path + sep + "Launching" + sep + "----------" + sep);
|
||||
}
|
||||
|
||||
BufferedReader stdInput = new BufferedReader(new
|
||||
InputStreamReader(proc.getInputStream()));
|
||||
final Logger logger = LoggerFactory.getLogger(path);
|
||||
|
||||
logger.info("Launching...");
|
||||
|
||||
final BufferedReader processInputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String line;
|
||||
while((line = stdInput.readLine()) != null) {
|
||||
synchronized (System.out) {
|
||||
System.out.println(path + sep + "Output" + sep + line + sep + "----------" + sep);
|
||||
}
|
||||
}
|
||||
while ((line = processInputReader.readLine()) != null)
|
||||
logger.info(line);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to read input line from process {}", process, e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
BufferedReader stdError = new BufferedReader(new
|
||||
InputStreamReader(proc.getErrorStream()));
|
||||
}, path+"-input").start();
|
||||
|
||||
final BufferedReader processErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String line;
|
||||
while((line = stdError.readLine()) != null) {
|
||||
synchronized (System.out) {
|
||||
System.out.println(path + sep + "Error" + sep + line + sep + "----------" + sep);
|
||||
}
|
||||
}
|
||||
while ((line = processErrorReader.readLine()) != null)
|
||||
logger.error(line);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to read error line from process {}", process, e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallExtension(String filename) {
|
||||
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()) {
|
||||
// is installed through extension store
|
||||
StoreExtensionTools.removeExtension(path.toString());
|
||||
}
|
||||
else {
|
||||
} else
|
||||
Files.delete(path);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to uninstall extension at {}", filename, e);
|
||||
}
|
||||
}
|
||||
|
||||
// private void addExecPermission(String path) {
|
||||
// //not needed at first sight
|
||||
// }
|
||||
private static void tryCreateDirectory(String path) {
|
||||
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) {
|
||||
String name = Paths.get(path).getFileName().toString();
|
||||
String[] split = name.split("\\.");
|
||||
private static boolean dirExists(String dir) {
|
||||
return Files.isDirectory(Paths.get(JAR_PATH, dir));
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
private boolean dirExists(String dir) {
|
||||
return Files.isDirectory(Paths.get(JARPATH, dir));
|
||||
}
|
||||
private void createDirectory(String dir) {
|
||||
if (!dirExists(dir)) {
|
||||
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));
|
||||
}
|
||||
|
||||
private static String getRandomString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 12; i++)
|
||||
builder.append(random.nextInt(10));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package gearth.services.g_python;
|
||||
import gearth.GEarth;
|
||||
import gearth.ui.subforms.extra.ExtraController;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
@ -170,13 +171,11 @@ public class GPythonShell {
|
||||
|
||||
private void showError() {
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "G-Python error", ButtonType.OK);
|
||||
alert.setTitle("G-Python error");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, LanguageBundle.get("alert.gpythonerror.title"), ButtonType.OK);
|
||||
alert.setTitle(LanguageBundle.get("alert.gpythonerror.title"));
|
||||
|
||||
FlowPane fp = new FlowPane();
|
||||
Label lbl = new Label("Something went wrong launching the G-Python shell," +
|
||||
System.lineSeparator() + "are you sure you followed the installation guide correctly?" +
|
||||
System.lineSeparator() + System.lineSeparator() + "More information here:");
|
||||
Label lbl = new Label(LanguageBundle.get("alert.gpythonerror.content").replaceAll("\\\\n", System.lineSeparator()));
|
||||
Hyperlink link = new Hyperlink(ExtraController.INFO_URL_GPYTHON);
|
||||
fp.getChildren().addAll(lbl, link);
|
||||
link.setOnAction(event -> {
|
||||
|
@ -23,6 +23,7 @@ import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class GExtensionStoreController implements Initializable {
|
||||
private static GExtensionStoreController instance;
|
||||
|
||||
private GExtensionStore extensionStore = null;
|
||||
|
||||
@ -37,6 +38,8 @@ public class GExtensionStoreController implements Initializable {
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
instance = this;
|
||||
|
||||
webView = new WebView();
|
||||
borderPane.setCenter(webView);
|
||||
|
||||
@ -106,6 +109,7 @@ public class GExtensionStoreController implements Initializable {
|
||||
});
|
||||
|
||||
webView.getEngine().load(GExtensionStoreController.class.getResource("webview/index.html").toString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -236,4 +240,8 @@ public class GExtensionStoreController implements Initializable {
|
||||
public String getContentItemsContainer() {
|
||||
return contentItemsContainer;
|
||||
}
|
||||
|
||||
public static void reloadPage() {
|
||||
instance.webView.getEngine().reload();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@ -43,19 +45,19 @@ public class WebUtils {
|
||||
}
|
||||
|
||||
public static String elapsedTime(long time) {
|
||||
if (time < 60) return time + (time == 1 ? " second" : " seconds");
|
||||
if (time < 60) return time + " " + LanguageBundle.get("ext.store.elapsedtime.second." + (time == 1 ? "single": "multiple"));
|
||||
time = time/60;
|
||||
if (time < 60) return time + (time == 1 ? " minute" : " minutes");
|
||||
if (time < 60) return time + " " + LanguageBundle.get("ext.store.elapsedtime.minute." + (time == 1 ? "single": "multiple"));
|
||||
time = time/60;
|
||||
if (time < 24) return time + (time == 1 ? " hour" : " hours");
|
||||
if (time < 24) return time + " " + LanguageBundle.get("ext.store.elapsedtime.hour." + (time == 1 ? "single": "multiple"));
|
||||
long days = time/24;
|
||||
if (days < 7) return days + (days == 1 ? " day" : " days");
|
||||
if (days < 7) return days + " " + LanguageBundle.get("ext.store.elapsedtime.day." + (days == 1 ? "single": "multiple"));
|
||||
long weeks = days/7;
|
||||
if (weeks < 6) return weeks + (weeks == 1 ? " week" : " weeks");
|
||||
if (weeks < 6) return weeks + " " + LanguageBundle.get("ext.store.elapsedtime.week." + (weeks == 1 ? "single": "multiple"));
|
||||
long months = days/31;
|
||||
if (months < 12) return months + (months == 1 ? " month" : " months");
|
||||
if (months < 12) return months + " " + LanguageBundle.get("ext.store.elapsedtime.month." + (months == 1 ? "single": "multiple"));
|
||||
long years = days/365;
|
||||
return years + (years == 1 ? " year" : " years");
|
||||
return years + " " + LanguageBundle.get("ext.store.elapsedtime.year." + (years == 1 ? "single": "multiple"));
|
||||
}
|
||||
|
||||
public static String escapeMessage(String text) {
|
||||
|
@ -1,11 +1,16 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.extensiondetails.StoreExtensionDetailsOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.queriedoverviews.CategorizedOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import netscape.javascript.JSObject;
|
||||
|
||||
public class StoreExtensionItem implements ContentItem {
|
||||
@ -22,7 +27,7 @@ public class StoreExtensionItem implements ContentItem {
|
||||
new StoreExtensionDetailsOverview(
|
||||
gExtensionStore.getController().getCurrentOverview(),
|
||||
0,
|
||||
GExtensionStore.MAX_PAGES,
|
||||
GExtensionStore.PAGESIZE,
|
||||
storeExtension,
|
||||
gExtensionStore.getRepository()
|
||||
)
|
||||
@ -53,12 +58,12 @@ public class StoreExtensionItem implements ContentItem {
|
||||
|
||||
.append("<div class=\"overview_item_info\">")
|
||||
.append("<div onclick=\"").append(id).append(".onClick()\" class=\"oii_name clickable\">").append(WebUtils.escapeMessage(storeExtension.getTitle())).append("</div>")
|
||||
.append("<div class=\"oii_desc\">By ").append(storeExtension.getAuthors().get(0).getName()).append(", last updated ").append(WebUtils.elapsedSince(storeExtension.getUpdateDate())).append(" ago</div>")
|
||||
.append("<div class=\"oii_desc\">").append(String.format(LanguageBundle.get("ext.store.extension.madeby"), storeExtension.getAuthors().get(0).getName())).append(", ").append(String.format(LanguageBundle.get("ext.store.extension.lastupdated"), WebUtils.elapsedSince(storeExtension.getUpdateDate()))).append("</div>")
|
||||
.append("</div>")
|
||||
|
||||
.append("<div onclick=\"").append(id).append(".onClick()\" class=\"overview_item_msgs clickable\">")
|
||||
.append("<div class=\"oim_top\">").append("Version: ").append(displayVersion()).append("</div>")
|
||||
.append("<div class=\"oim_bottom\">").append("Rating: ").append(storeExtension.getRating()).append("</div>")
|
||||
.append("<div class=\"oim_top\">").append(LanguageBundle.get("ext.store.extension.version")).append(": ").append(displayVersion()).append("</div>")
|
||||
.append("<div class=\"oim_bottom\">").append(LanguageBundle.get("ext.store.extension.rating")).append(": ").append(storeExtension.getRating()).append("</div>")
|
||||
// .append("<div class=\"oim_bottom\">").append(storeExtension.getFramework().getFramework().getName().replace("Native", "")).append(" </div>")
|
||||
.append("</div>")
|
||||
|
||||
|
@ -8,6 +8,7 @@ import gearth.services.internal_extensions.extensionstore.application.entities.q
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.ExtCategory;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import netscape.javascript.JSObject;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -57,7 +58,7 @@ public class CategoryItem implements ContentItem {
|
||||
.append("</div>")
|
||||
|
||||
.append("<div onclick=\"").append(id).append(".onClick()\" class=\"overview_item_msgs clickable\">")
|
||||
.append("<div class=\"oim_top\">").append(releasesCount).append(" releases").append("</div>")
|
||||
.append("<div class=\"oim_top\">").append(releasesCount).append(" ").append(LanguageBundle.get("ext.store.extension.author.releases")).append("</div>")
|
||||
// .append("<div class=\"oim_bottom\">").append(storeExtension.getFramework().getFramework().getName().replace("Native", "")).append(" </div>")
|
||||
.append("</div>")
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.categories;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.ContentItem;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.HOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.ExtCategory;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@ -57,17 +59,17 @@ public class CategoryOverview extends HOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "Categories";
|
||||
return LanguageBundle.get("ext.store.categories.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Explore the different kinds of extensions G-Earth has to offer";
|
||||
return LanguageBundle.get("ext.store.categories.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Categories";
|
||||
return LanguageBundle.get("ext.store.categories.contenttitle");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import gearth.services.internal_extensions.extensionstore.application.entities.H
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.ExtCategory;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.tools.EncodingUtil;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import netscape.javascript.JSObject;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.json.JSONObject;
|
||||
@ -45,32 +46,32 @@ public class StoreExtensionDetailsItem implements ContentItem {
|
||||
|
||||
contentBuilder
|
||||
.append(String.format("*%s*", storeExtension.getTitle())).append(" - v").append(storeExtension.getVersion()).append("\n\n")
|
||||
.append("*Description*\n").append(descriptionQuoted).append("\n \n")
|
||||
.append("*Author(s):* ").append(storeExtension.getAuthors().stream().map(StoreExtension.Author::getName).collect(Collectors.joining(", "))).append("\n\n")
|
||||
.append("*Categories:* ").append(storeExtension.getCategories().stream().map(ExtCategory::getName).collect(Collectors.joining(", "))).append("\n\n");
|
||||
.append("*").append(LanguageBundle.get("ext.store.extension.details.description")).append(":*\n").append(descriptionQuoted).append("\n \n")
|
||||
.append("*").append(LanguageBundle.get("ext.store.extension.details.authors")).append(":* ").append(storeExtension.getAuthors().stream().map(StoreExtension.Author::getName).collect(Collectors.joining(", "))).append("\n\n")
|
||||
.append("*").append(LanguageBundle.get("ext.store.extension.details.categories")).append(":* ").append(storeExtension.getCategories().stream().map(ExtCategory::getName).collect(Collectors.joining(", "))).append("\n\n");
|
||||
|
||||
contentBuilder.append("*Technical information*").append("\n");
|
||||
contentBuilder.append("*").append(LanguageBundle.get("ext.store.extension.details.technical_information")).append("*").append("\n");
|
||||
|
||||
if(storeExtension.getReleases() != null)
|
||||
contentBuilder.append("> Releases: --url:Click Here-").append(storeExtension.getReleases()).append("\n");
|
||||
contentBuilder.append("> ").append(LanguageBundle.get("ext.store.extension.details.releases")).append(": --url:").append(LanguageBundle.get("ext.store.extension.details.click_here")).append("-").append(storeExtension.getReleases()).append("\n");
|
||||
|
||||
contentBuilder.append("> Language: ").append(storeExtension.getLanguage()).append("\n")
|
||||
.append("> Source: --url:Click Here-").append(storeExtension.getSource()).append("\n")
|
||||
.append("> Framework: ").append(storeExtension.getFramework().getFramework().getName()).append(" - v").append(storeExtension.getFramework().getVersion()).append("\n")
|
||||
.append("> Systems: ").append(String.join(", ", storeExtension.getCompatibility().getSystems())).append("\n \n");
|
||||
contentBuilder.append("> ").append(LanguageBundle.get("ext.store.extension.details.language")).append(": ").append(storeExtension.getLanguage()).append("\n")
|
||||
.append("> ").append(LanguageBundle.get("ext.store.extension.details.source")).append(": --url:").append(LanguageBundle.get("ext.store.extension.details.click_here")).append("-").append(storeExtension.getSource()).append("\n")
|
||||
.append("> ").append(LanguageBundle.get("ext.store.extension.details.framework")).append(": ").append(storeExtension.getFramework().getFramework().getName()).append(" - v").append(storeExtension.getFramework().getVersion()).append("\n")
|
||||
.append("> ").append(LanguageBundle.get("ext.store.extension.details.systems")).append(": ").append(String.join(", ", storeExtension.getCompatibility().getSystems())).append("\n \n");
|
||||
|
||||
contentBuilder.append("*Compatible clients:* ").append(String.join(", ", storeExtension.getCompatibility().getClients())).append("\n\n");
|
||||
contentBuilder.append("*").append(LanguageBundle.get("ext.store.extension.details.clients")).append(":* ").append(String.join(", ", storeExtension.getCompatibility().getClients())).append("\n\n");
|
||||
|
||||
if (storeExtension.getFramework().getFramework().isInstallationRequired()) {
|
||||
contentBuilder.append("Warning: the framework requires --url:additional installations-")
|
||||
contentBuilder.append(LanguageBundle.get("ext.store.extension.warning.requirement"))
|
||||
.append(storeExtension.getFramework().getFramework().getInstallationInstructions()).append(" !\n\n");
|
||||
}
|
||||
if (!storeExtension.isStable()) {
|
||||
contentBuilder.append("Warning: this extension has been marked unstable!\n\n");
|
||||
contentBuilder.append(LanguageBundle.get("ext.store.extension.warning.unstable")).append("\n\n");
|
||||
}
|
||||
|
||||
contentBuilder.append("--startdiv--")
|
||||
.append("\n*Screenshot: *").append("\n")
|
||||
.append("\n*").append(LanguageBundle.get("ext.store.extension.details.screenshot")).append(": *").append("\n")
|
||||
.append("--img:").append(gExtensionStore.getRepository().getResourceUrl("store", "extensions", storeExtension.getTitle(), "screenshot.png"))
|
||||
.append(" --enddiv--");
|
||||
|
||||
@ -151,8 +152,8 @@ public class StoreExtensionDetailsItem implements ContentItem {
|
||||
.append("<div class=\"comment_body comment_open\">")
|
||||
.append("<div class=\"cb_author\">")
|
||||
.append("<div class=\"cba_name\">").append(WebUtils.escapeMessage(mainAuthor.getName())).append("</div>")
|
||||
.append("<div class=\"cba_text\">").append(mainAuthor.getReputation()).append(" reputation</div>")
|
||||
.append("<div class=\"cba_text\">").append(mainAuthor.getExtensionsCount()).append(" releases</div>")
|
||||
.append("<div class=\"cba_text\">").append(mainAuthor.getReputation()).append(" ").append(LanguageBundle.get("ext.store.extension.author.reputation")).append("</div>")
|
||||
.append("<div class=\"cba_text\">").append(mainAuthor.getExtensionsCount()).append(" ").append(LanguageBundle.get("ext.store.extension.author.releases")).append("</div>")
|
||||
.append("<div class=\"cba_look\"><img src=\"").append(avatarLook).append("\" alt=\"\"></div>") // todo look
|
||||
.append("</div>")
|
||||
.append("<div class=\"cb_content\">").append(contentsInHtml()).append("</div>")
|
||||
|
@ -1,7 +1,6 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.extensiondetails;
|
||||
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
||||
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
||||
@ -12,15 +11,14 @@ import gearth.services.internal_extensions.extensionstore.repository.models.Stor
|
||||
import gearth.services.internal_extensions.extensionstore.tools.InstalledExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTools;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -62,7 +60,7 @@ public class StoreExtensionDetailsOverview extends HOverview {
|
||||
public String buttonText() {
|
||||
int mode = mode();
|
||||
// return mode == 2 ? "Update" : "Install";
|
||||
return mode == 0 ? "Install" : (mode == 1 ? "Installed" : "Update");
|
||||
return LanguageBundle.get("ext.store.button." + (mode == 0 ? "install" : (mode == 1 ? "installed" : "update")));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -91,23 +89,23 @@ public class StoreExtensionDetailsOverview extends HOverview {
|
||||
|
||||
private void awaitPopup(String mode) {
|
||||
popup(Alert.AlertType.WARNING,
|
||||
String.format("%s extension", mode),
|
||||
String.format("%s extension [%s]", mode, extension.getTitle()),
|
||||
String.format("Press \"OK\" and wait while the extension is being %sed", mode.toLowerCase()));
|
||||
LanguageBundle.get(String.format("ext.store.extension.status.await.%s", mode)),
|
||||
String.format("%s [%s]", LanguageBundle.get(String.format("ext.store.extension.status.await.%s", mode)), extension.getTitle()),
|
||||
LanguageBundle.get(String.format("ext.store.extension.status.await.%s.message", mode)));
|
||||
}
|
||||
|
||||
private void successPopup(String mode) {
|
||||
popup(Alert.AlertType.INFORMATION,
|
||||
String.format("%s completed", mode),
|
||||
String.format("%s completed [%s]", mode, extension.getTitle()),
|
||||
String.format("Extension %s completed successfully", mode.toLowerCase()));
|
||||
LanguageBundle.get(String.format("ext.store.extension.status.success.%s", mode)),
|
||||
String.format("%s [%s]", LanguageBundle.get(String.format("ext.store.extension.status.success.%s", mode)), extension.getTitle()),
|
||||
LanguageBundle.get(String.format("ext.store.extension.status.success.%s.message", mode)));
|
||||
}
|
||||
|
||||
private void errorPopup(String mode, String error) {
|
||||
popup(Alert.AlertType.ERROR,
|
||||
String.format("%s failed", mode),
|
||||
String.format("%s failed [%s]", mode, extension.getTitle()),
|
||||
String.format("%s failed with the following message: %s", mode, error));
|
||||
LanguageBundle.get(String.format("ext.store.extension.status.success.%s", mode)),
|
||||
String.format("%s [%s]", LanguageBundle.get(String.format("ext.store.extension.status.success.%s", mode)), extension.getTitle()),
|
||||
String.format("%s: %s", LanguageBundle.get(String.format("ext.store.extension.status.success.%s.message", mode)), error));
|
||||
}
|
||||
|
||||
private void popup(Alert.AlertType alertType, String title, String header, String context) {
|
||||
@ -128,14 +126,14 @@ public class StoreExtensionDetailsOverview extends HOverview {
|
||||
int mode = mode();
|
||||
if (mode == 1) return;
|
||||
|
||||
String modeString = mode() == 0 ? "Install" : "Update";
|
||||
String modeString = mode() == 0 ? "install" : "update";
|
||||
HOverview selff = this;
|
||||
|
||||
StoreExtensionTools.InstallExtListener listener = new StoreExtensionTools.InstallExtListener() {
|
||||
@Override
|
||||
public void success(String installationFolder) {
|
||||
Platform.runLater(() -> successPopup(modeString));
|
||||
StoreExtensionTools.executeExtension(installationFolder, NetworkExtensionsProducer.extensionPort);
|
||||
StoreExtensionTools.executeExtension(installationFolder, ExtensionProducerFactory.getExtensionServer().getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,7 @@ import gearth.services.internal_extensions.extensionstore.repository.StoreReposi
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.tools.InstalledExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTools;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.awt.*;
|
||||
@ -30,7 +31,7 @@ public class InstalledOverview extends HOverview {
|
||||
|
||||
@Override
|
||||
public String buttonText() {
|
||||
return "Open folder";
|
||||
return LanguageBundle.get("ext.store.overview.folder");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -75,17 +76,17 @@ public class InstalledOverview extends HOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "Installed Extensions";
|
||||
return LanguageBundle.get("ext.store.overview.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Extensions that are already installed into G-Earth";
|
||||
return LanguageBundle.get("ext.store.overview.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Installed extensions";
|
||||
return LanguageBundle.get("ext.store.overview.contenttitle");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.StoreExtensionItem;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.tools.InstalledExtension;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
public class StoreExtensionInstalledItem extends StoreExtensionItem {
|
||||
@ -55,11 +56,11 @@ public class StoreExtensionInstalledItem extends StoreExtensionItem {
|
||||
|
||||
.append("<div class=\"overview_item_info\">")
|
||||
.append("<div class=\"oii_name\">").append(WebUtils.escapeMessage(installedExtension.getName())).append("</div>")
|
||||
.append("<div class=\"oii_desc\">Not found in G-ExtensionStore</div>")
|
||||
.append("<div class=\"oii_desc\">").append(LanguageBundle.get("ext.store.extension.notinstore")).append("</div>")
|
||||
.append("</div>")
|
||||
|
||||
.append("<div class=\"overview_item_msgs\">")
|
||||
.append("<div class=\"oim_top\">").append("Version: ").append(displayVersion()).append("</div>")
|
||||
.append("<div class=\"oim_top\">").append(LanguageBundle.get("ext.store.extension.version")).append(": ").append(displayVersion()).append("</div>")
|
||||
.append("<div class=\"oim_bottom\"></div>")
|
||||
.append("</div>")
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.queriedoverviews;
|
||||
|
||||
import gearth.misc.OSValidator;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.HOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -32,17 +32,17 @@ public class ByDateOverview extends QueriedExtensionOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "New Releases";
|
||||
return LanguageBundle.get("ext.store.search.ordering.bydate.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Extensions that were recently added to the G-ExtensionStore";
|
||||
return LanguageBundle.get("ext.store.search.ordering.bydate.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "New Releases";
|
||||
return LanguageBundle.get("ext.store.search.ordering.bydate.contenttitle");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.queriedoverviews;
|
||||
|
||||
import gearth.misc.OSValidator;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.HOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -32,17 +32,17 @@ public class ByRatingOverview extends QueriedExtensionOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "Popular Extensions";
|
||||
return LanguageBundle.get("ext.store.search.ordering.byrating.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Extensions sorted by rating";
|
||||
return LanguageBundle.get("ext.store.search.ordering.byrating.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Popular Extensions";
|
||||
return LanguageBundle.get("ext.store.search.ordering.byrating.contenttitle");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import gearth.services.internal_extensions.extensionstore.application.entities.H
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -31,17 +32,17 @@ public class ByUpdateOverview extends QueriedExtensionOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "Recently Updated";
|
||||
return LanguageBundle.get("ext.store.search.ordering.byupdate.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Extensions that were recently updated";
|
||||
return LanguageBundle.get("ext.store.search.ordering.byupdate.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Recently Updated";
|
||||
return LanguageBundle.get("ext.store.search.ordering.byupdate.contenttitle");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.queriedoverviews;
|
||||
|
||||
import gearth.misc.OSValidator;
|
||||
import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.HOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.ExtCategory;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -47,7 +47,7 @@ public class CategorizedOverview extends QueriedExtensionOverview {
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Category: " + category.getName();
|
||||
return LanguageBundle.get("ext.store.category") + ": " + category.getName();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import gearth.services.internal_extensions.extensionstore.application.entities.H
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -48,17 +49,17 @@ public class SearchedQueryOverview extends QueriedExtensionOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "Search";
|
||||
return LanguageBundle.get("ext.store.search.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Find the extension that fits your needs";
|
||||
return LanguageBundle.get("ext.store.search.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Search results";
|
||||
return LanguageBundle.get("ext.store.search.results");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.search;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.ContentItem;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.querying.ExtensionOrdering;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import netscape.javascript.JSObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -92,13 +94,13 @@ public class SearchComponent implements ContentItem {
|
||||
.append("<div class=\"searchInnerContainer\">")
|
||||
|
||||
.append("<div class=\"centeredFlex\">")
|
||||
.append("<label for=\"keyword\">Search by keyword:</label>")
|
||||
.append("<label for=\"keyword\">").append(LanguageBundle.get("ext.store.search.bykeyword")).append(":</label>")
|
||||
.append(String.format("<input id=\"keyword\" value=\"%s\" name=\"keyword\" class=\"inputBox\" type=\"text\" " +
|
||||
"oninput=\"%s.setSearchKeyword(this.value);\">", searchKeyword, id))
|
||||
.append("</div>")
|
||||
|
||||
.append("<div class=\"centeredFlex\">")
|
||||
.append("<label for=\"ordering\">Extensions ordering:</label>");
|
||||
.append("<label for=\"ordering\">").append(LanguageBundle.get("ext.store.search.ordering")).append("</label>");
|
||||
|
||||
// add ordering stuff
|
||||
htmlBuilder.append(String.format("<select class=\"inputBox\" name=\"ordering\" id=\"ordering\" " +
|
||||
@ -119,13 +121,13 @@ public class SearchComponent implements ContentItem {
|
||||
|
||||
.append("<div class=\"filterStuff\">");
|
||||
|
||||
addFilterBoxHtml(htmlBuilder, "Client", "Clients:", clients, id);
|
||||
addFilterBoxHtml(htmlBuilder, "Category", "Categories:", categories, id);
|
||||
addFilterBoxHtml(htmlBuilder, "Framework", "Frameworks:", frameworks, id);
|
||||
addFilterBoxHtml(htmlBuilder, "Client", LanguageBundle.get("ext.store.search.filter.clients") + ":", clients, id);
|
||||
addFilterBoxHtml(htmlBuilder, "Category", LanguageBundle.get("ext.store.search.filter.categories") + ":", categories, id);
|
||||
addFilterBoxHtml(htmlBuilder, "Framework", LanguageBundle.get("ext.store.search.filter.frameworks") + ":", frameworks, id);
|
||||
|
||||
htmlBuilder
|
||||
.append("</div>")
|
||||
.append("<br><p>Info: you are automatically filtering on the OS you use</p>")
|
||||
.append("<br><p>").append(LanguageBundle.get("ext.store.search.info.automaticosfiltering")).append("</p>")
|
||||
|
||||
.append("</div>")
|
||||
.append("</div>");
|
||||
|
@ -5,6 +5,7 @@ import gearth.services.internal_extensions.extensionstore.application.entities.C
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.HOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.application.entities.queriedoverviews.SearchedQueryOverview;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -26,7 +27,7 @@ public class SearchOverview extends HOverview {
|
||||
|
||||
@Override
|
||||
public String buttonText() {
|
||||
return "Search";
|
||||
return LanguageBundle.get("ext.store.button.search");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -73,17 +74,17 @@ public class SearchOverview extends HOverview {
|
||||
|
||||
@Override
|
||||
public String title() {
|
||||
return "Search";
|
||||
return LanguageBundle.get("ext.store.search.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Find the extension that fits your needs";
|
||||
return LanguageBundle.get("ext.store.search.description");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentTitle() {
|
||||
return "Search extensions";
|
||||
return LanguageBundle.get("ext.store.search.contenttitle");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,27 +1,29 @@
|
||||
package gearth.services.internal_extensions.extensionstore.repository.querying;
|
||||
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
|
||||
public enum ExtensionOrdering {
|
||||
|
||||
RATING("Rating"),
|
||||
ALPHABETICAL("Alphabetical"),
|
||||
LAST_UPDATED("Last updated"),
|
||||
NEW_RELEASES("New releases");
|
||||
RATING("ext.store.ordering.rating"),
|
||||
ALPHABETICAL("ext.store.ordering.alphabetical"),
|
||||
LAST_UPDATED("ext.store.ordering.lastupdated"),
|
||||
NEW_RELEASES("ext.store.ordering.newreleases");
|
||||
|
||||
|
||||
|
||||
private String orderName;
|
||||
private String orderKey;
|
||||
|
||||
ExtensionOrdering(String orderName) {
|
||||
this.orderName = orderName;
|
||||
ExtensionOrdering(String orderKey) {
|
||||
this.orderKey = orderKey;
|
||||
}
|
||||
|
||||
public String getOrderName() {
|
||||
return orderName;
|
||||
return LanguageBundle.get(orderKey);
|
||||
}
|
||||
|
||||
public static ExtensionOrdering fromString(String text) {
|
||||
for (ExtensionOrdering b : ExtensionOrdering.values()) {
|
||||
if (b.orderName.equalsIgnoreCase(text)) {
|
||||
if (LanguageBundle.get(b.orderKey).equalsIgnoreCase(text)) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ package gearth.services.internal_extensions.extensionstore.tools;
|
||||
|
||||
import gearth.GEarth;
|
||||
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.NormalExtensionRunner;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreFetch;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreRepository;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.models.StoreExtension;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
import org.json.JSONArray;
|
||||
@ -33,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) {
|
||||
@ -41,7 +42,7 @@ public class StoreExtensionTools {
|
||||
String installedExtensionId = Paths.get(extensionPath).getFileName().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"))
|
||||
.toList().stream().map(o -> (String)o).map(s -> s
|
||||
.replace("{port}", port+"")
|
||||
@ -137,29 +138,29 @@ public class StoreExtensionTools {
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
listener.fail("Error while unzipping");
|
||||
listener.fail(LanguageBundle.get("ext.store.fail.unzip"));
|
||||
removeExtension(path);
|
||||
}
|
||||
|
||||
} catch (MalformedURLException e) {
|
||||
listener.fail("Invalid extension URL");
|
||||
listener.fail(LanguageBundle.get("ext.store.fail.invalidurl"));
|
||||
try {
|
||||
removeExtension(path); // cleanup
|
||||
} catch (IOException ignore) { }
|
||||
} catch (IOException e) {
|
||||
listener.fail("Extension not available in repository");
|
||||
listener.fail(LanguageBundle.get("ext.store.fail.notavailable"));
|
||||
try {
|
||||
removeExtension(path); // cleanup
|
||||
} catch (IOException ignore) { }
|
||||
}
|
||||
}
|
||||
else {
|
||||
listener.fail("Something went wrong creating the extension directory, does the extension already exist?");
|
||||
listener.fail(LanguageBundle.get("ext.store.fail.alreadyexists"));
|
||||
// don't do cleanup since you might not want removal of current extension files
|
||||
}
|
||||
}
|
||||
else {
|
||||
listener.fail("Extension wasn't found");
|
||||
listener.fail(LanguageBundle.get("ext.store.fail.notfound"));
|
||||
}
|
||||
|
||||
}).start();
|
||||
@ -233,8 +234,7 @@ public class StoreExtensionTools {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
listener.fail("Something went wrong with uninstalling the extension, make sure to disconnect" +
|
||||
" the extension if it was still running.");
|
||||
listener.fail(LanguageBundle.get("ext.store.fail.uninstall"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,9 @@ package gearth.services.internal_extensions.uilogger;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.ui.subforms.logger.loggerdisplays.PacketLogger;
|
||||
import javafx.application.Platform;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.*;
|
||||
@ -39,7 +41,7 @@ public class UiLoggerController implements Initializable {
|
||||
public CheckMenuItem chkViewIncoming;
|
||||
public CheckMenuItem chkViewOutgoing;
|
||||
public CheckMenuItem chkDisplayStructure;
|
||||
public Label lblAutoScrolll;
|
||||
public Label lblAutoScroll;
|
||||
public CheckMenuItem chkAutoscroll;
|
||||
public CheckMenuItem chkSkipBigPackets;
|
||||
public CheckMenuItem chkMessageName;
|
||||
@ -71,6 +73,7 @@ public class UiLoggerController implements Initializable {
|
||||
public RadioMenuItem chkReprHex;
|
||||
public RadioMenuItem chkReprRawHex;
|
||||
public RadioMenuItem chkReprNone;
|
||||
public MenuItem menuItem_clear, menuItem_exportAll;
|
||||
|
||||
private Map<Integer, LinkedList<Long>> filterTimestamps = new HashMap<>();
|
||||
|
||||
@ -86,6 +89,11 @@ public class UiLoggerController implements Initializable {
|
||||
private List<MenuItem> allMenuItems = new ArrayList<>();
|
||||
private UiLogger uiLogger;
|
||||
|
||||
public Menu menu_window, menu_window_onConnect, menu_window_onDisconnect, menu_view, menu_packets,
|
||||
menu_packets_details, menu_packets_details_byteRep, menu_packets_details_message, menu_packets_antiSpam;
|
||||
|
||||
private TranslatableString viewIncoming, viewOutgoing, autoScroll, packetInfo, filtered;
|
||||
|
||||
private boolean isSelected(MenuItem item) {
|
||||
if (item instanceof CheckMenuItem) {
|
||||
return ((CheckMenuItem)item).isSelected();
|
||||
@ -157,6 +165,8 @@ public class UiLoggerController implements Initializable {
|
||||
appendLater.clear();
|
||||
}
|
||||
}
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
private static String cleanTextContent(String text) {
|
||||
@ -219,7 +229,7 @@ public class UiLoggerController implements Initializable {
|
||||
|
||||
|
||||
if (chkTimestamp.isSelected()) {
|
||||
elements.add(new Element(String.format("(timestamp: %d)\n", System.currentTimeMillis()), "timestamp"));
|
||||
elements.add(new Element(String.format("(%s: %d)\n", LanguageBundle.get("ext.logger.element.timestamp"), System.currentTimeMillis()), "timestamp"));
|
||||
}
|
||||
|
||||
boolean packetInfoAvailable = uiLogger.getPacketInfoManager().getPacketInfoList().size() > 0;
|
||||
@ -256,8 +266,8 @@ public class UiLoggerController implements Initializable {
|
||||
elements.add(new Element("\n", ""));
|
||||
}
|
||||
|
||||
if (isBlocked) elements.add(new Element("[Blocked]\n", "blocked"));
|
||||
else if (isReplaced) elements.add(new Element("[Replaced]\n", "replaced"));
|
||||
if (isBlocked) elements.add(new Element(String.format("[%s]\n", LanguageBundle.get("ext.logger.element.blocked")), "blocked"));
|
||||
else if (isReplaced) elements.add(new Element(String.format("[%s]\n", LanguageBundle.get("ext.logger.element.replaced")), "replaced"));
|
||||
|
||||
if (!chkReprNone.isSelected()) {
|
||||
boolean isSkipped = chkSkipBigPackets.isSelected() && (packet.length() > 4000 || (packet.length() > 1000 && chkReprHex.isSelected()));
|
||||
@ -265,20 +275,23 @@ public class UiLoggerController implements Initializable {
|
||||
Hexdump.hexdump(packet.toBytes()) :
|
||||
(chkReprRawHex.isSelected() ? Bytes.wrap(packet.toBytes()).encodeHex() : packet.toString());
|
||||
|
||||
String type = isIncoming ? "Incoming" : "Outgoing";
|
||||
String packetType = isIncoming ? "incoming" : "outgoing";
|
||||
String type = isIncoming ?
|
||||
LanguageBundle.get("ext.logger.element.direction.incoming") :
|
||||
LanguageBundle.get("ext.logger.element.direction.outgoing");
|
||||
|
||||
if (!chkReprHex.isSelected()) {
|
||||
elements.add(new Element(String.format("%s[", type), type.toLowerCase()));
|
||||
elements.add(new Element(String.format("%s[", type), packetType));
|
||||
elements.add(new Element(String.valueOf(packet.headerId()), ""));
|
||||
elements.add(new Element("]", type.toLowerCase()));
|
||||
elements.add(new Element("]", packetType));
|
||||
|
||||
elements.add(new Element(" -> ", ""));
|
||||
}
|
||||
|
||||
if (isSkipped) {
|
||||
elements.add(new Element("<packet skipped>", "skipped"));
|
||||
elements.add(new Element(String.format("<%s>", LanguageBundle.get("ext.logger.element.skipped")), "skipped"));
|
||||
} else
|
||||
elements.add(new Element(packetRepresentation, String.format(chkReprHex.isSelected() ? "%sHex": "%s", type.toLowerCase())));
|
||||
elements.add(new Element(packetRepresentation, String.format(chkReprHex.isSelected() ? "%sHex": "%s", packetType)));
|
||||
elements.add(new Element("\n", ""));
|
||||
}
|
||||
|
||||
@ -343,13 +356,13 @@ public class UiLoggerController implements Initializable {
|
||||
|
||||
public void updateLoggerInfo() {
|
||||
Platform.runLater(() -> {
|
||||
lblViewIncoming.setText("View Incoming: " + (chkViewIncoming.isSelected() ? "True" : "False"));
|
||||
lblViewOutgoing.setText("View Outgoing: " + (chkViewOutgoing.isSelected() ? "True" : "False"));
|
||||
lblAutoScrolll.setText("Autoscroll: " + (chkAutoscroll.isSelected() ? "True" : "False"));
|
||||
lblFiltered.setText("Filtered: " + filteredAmount);
|
||||
viewIncoming.setKey(1, "ext.logger.state." + (chkViewIncoming.isSelected() ? "true" : "false"));
|
||||
viewIncoming.setKey(1, "ext.logger.state." + (chkViewOutgoing.isSelected() ? "true" : "false"));
|
||||
autoScroll.setKey(1, "ext.logger.state." + (chkAutoscroll.isSelected() ? "true" : "false"));
|
||||
filtered.setFormat("%s: " + filteredAmount);
|
||||
|
||||
boolean packetInfoAvailable = uiLogger.getPacketInfoManager().getPacketInfoList().size() > 0;
|
||||
lblPacketInfo.setText("Packet info: " + (packetInfoAvailable ? "True" : "False"));
|
||||
packetInfo.setKey(1, "ext.logger.state." + (packetInfoAvailable ? "true" : "false"));
|
||||
});
|
||||
}
|
||||
|
||||
@ -389,9 +402,9 @@ public class UiLoggerController implements Initializable {
|
||||
|
||||
//Set extension filter
|
||||
FileChooser.ExtensionFilter extFilter =
|
||||
new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt");
|
||||
new FileChooser.ExtensionFilter(String.format("%s (*.txt)", LanguageBundle.get("ext.logger.menu.packets.exportall.filetype")), "*.txt");
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
fileChooser.setTitle("Save Packets");
|
||||
fileChooser.setTitle(LanguageBundle.get("ext.logger.menu.packets.exportall.windowtitle"));
|
||||
|
||||
//Show save file dialog
|
||||
File file = fileChooser.showSaveDialog(stage);
|
||||
@ -416,4 +429,62 @@ public class UiLoggerController implements Initializable {
|
||||
public void init(UiLogger uiLogger) {
|
||||
this.uiLogger = uiLogger;
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
menu_window.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window"));
|
||||
chkAlwaysOnTop.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.alwaysontop"));
|
||||
|
||||
menu_window_onConnect.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.onconnect"));
|
||||
chkOpenOnConnect.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.onconnect.openwindow"));
|
||||
chkResetOnConnect.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.onconnect.reset"));
|
||||
|
||||
menu_window_onDisconnect.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.ondisconnect"));
|
||||
chkHideOnDisconnect.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.ondisconnect.hidewindow"));
|
||||
chkResetOnDisconnect.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.window.ondisconnect.reset"));
|
||||
|
||||
menu_view.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.view"));
|
||||
chkViewIncoming.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.view.incoming"));
|
||||
chkViewOutgoing.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.view.outgoing"));
|
||||
chkAutoscroll.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.view.autoscroll"));
|
||||
menuItem_clear.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.view.cleartext"));
|
||||
|
||||
menu_packets.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets"));
|
||||
menu_packets_details.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails"));
|
||||
chkDisplayStructure.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.structure"));
|
||||
chkTimestamp.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.timestamp"));
|
||||
|
||||
menu_packets_details_byteRep.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.byterep"));
|
||||
chkReprLegacy.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.byterep.legacy"));
|
||||
chkReprHex.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.byterep.hexdump"));
|
||||
chkReprRawHex.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.byterep.rawhex"));
|
||||
chkReprNone.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.byterep.none"));
|
||||
|
||||
menu_packets_details_message.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.message"));
|
||||
chkMessageHash.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.message.hash"));
|
||||
chkMessageName.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.message.name"));
|
||||
chkMessageId.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.displaydetails.message.id"));
|
||||
|
||||
menu_packets_antiSpam.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.antispam"));
|
||||
chkAntiSpam_none.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.antispam.none"));
|
||||
chkAntiSpam_low.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.antispam.low"));
|
||||
chkAntiSpam_medium.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.antispam.med"));
|
||||
chkAntiSpam_high.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.antispam.high"));
|
||||
chkAntiSpam_ultra.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.antispam.ultra"));
|
||||
|
||||
chkSkipBigPackets.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.skipbig"));
|
||||
|
||||
menuItem_exportAll.textProperty().bind(new TranslatableString("%s", "ext.logger.menu.packets.exportall"));
|
||||
|
||||
viewIncoming = new TranslatableString("%s: %s", "ext.logger.menu.view.incoming", "ext.logger.state.true");
|
||||
viewOutgoing = new TranslatableString("%s: %s", "ext.logger.menu.view.outgoing", "ext.logger.state.true");
|
||||
autoScroll = new TranslatableString("%s: %s", "ext.logger.menu.view.autoscroll", "ext.logger.state.true");
|
||||
packetInfo = new TranslatableString("%s: %s", "ext.logger.state.packetinfo", "ext.logger.state.false");
|
||||
filtered = new TranslatableString("%s: 0", "ext.logger.state.filtered");
|
||||
|
||||
lblViewIncoming.textProperty().bind(viewIncoming);
|
||||
lblViewOutgoing.textProperty().bind(viewOutgoing);
|
||||
lblAutoScroll.textProperty().bind(autoScroll);
|
||||
lblPacketInfo.textProperty().bind(packetInfo);
|
||||
lblFiltered.textProperty().bind(filtered);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package gearth.services.internal_extensions.uilogger;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.extensions.InternalExtensionFormCreator;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
|
@ -3,8 +3,8 @@ package gearth.ui;
|
||||
import gearth.protocol.connection.proxy.ProxyProviderFactory;
|
||||
import gearth.protocol.connection.proxy.SocksConfiguration;
|
||||
import gearth.ui.subforms.logger.loggerdisplays.PacketLoggerFactory;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.ui.subforms.connection.ConnectionController;
|
||||
@ -21,7 +21,7 @@ import java.util.List;
|
||||
|
||||
public class GEarthController {
|
||||
|
||||
public Tab tab_Logger;
|
||||
public Tab tab_Connection, tab_Logger, tab_Injection, tab_Tools, tab_Scheduler, tab_Extensions, tab_Extra, tab_Info;
|
||||
public TabPane tabBar;
|
||||
private Stage stage = null;
|
||||
private volatile HConnection hConnection;
|
||||
@ -67,6 +67,10 @@ public class GEarthController {
|
||||
trySetController();
|
||||
}
|
||||
|
||||
if (PacketLoggerFactory.usesUIlogger()) {
|
||||
tabBar.getTabs().remove(tab_Logger);
|
||||
}
|
||||
|
||||
List<Tab> uiTabs = tabBar.getTabs();
|
||||
for (int i = 0; i < uiTabs.size(); i++) {
|
||||
Tab tab = uiTabs.get(i);
|
||||
@ -79,11 +83,7 @@ public class GEarthController {
|
||||
});
|
||||
}
|
||||
|
||||
if (PacketLoggerFactory.usesUIlogger()) {
|
||||
tabBar.getTabs().remove(tab_Logger);
|
||||
}
|
||||
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
public void setStage(Stage stage) {
|
||||
@ -120,4 +120,13 @@ public class GEarthController {
|
||||
hConnection.abort();
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
tab_Connection.textProperty().bind(new TranslatableString("%s", "tab.connection"));
|
||||
tab_Injection.textProperty().bind(new TranslatableString("%s", "tab.injection"));
|
||||
tab_Tools.textProperty().bind(new TranslatableString("%s", "tab.tools"));
|
||||
tab_Scheduler.textProperty().bind(new TranslatableString("%s", "tab.scheduler"));
|
||||
tab_Extensions.textProperty().bind(new TranslatableString("%s", "tab.extensions"));
|
||||
tab_Extra.textProperty().bind(new TranslatableString("%s", "tab.extra"));
|
||||
tab_Info.textProperty().bind(new TranslatableString("%s", "tab.info"));
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ public class SubForm {
|
||||
}
|
||||
|
||||
protected HConnection getHConnection() {
|
||||
if (parentController == null) {
|
||||
return null;
|
||||
}
|
||||
return parentController.getHConnection();
|
||||
}
|
||||
protected void writeToLog(javafx.scene.paint.Color color, String text) {
|
||||
|
@ -7,6 +7,7 @@ import gearth.protocol.connection.HClient;
|
||||
import gearth.protocol.connection.HState;
|
||||
import gearth.protocol.connection.proxy.ProxyProviderFactory;
|
||||
import gearth.services.Constants;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
@ -31,7 +32,7 @@ public class ConnectionController extends SubForm {
|
||||
public ComboBox<String> inpPort;
|
||||
public ComboBox<String> inpHost;
|
||||
public Button btnConnect;
|
||||
public Label lblState;
|
||||
public Label lblInpPort, lblInpHost, lblPort, lblHost, lblHotelVersion, lblClient, lblStateHead, lblState;
|
||||
public TextField outHost;
|
||||
public TextField outPort;
|
||||
public CheckBox cbx_autodetect;
|
||||
@ -54,6 +55,8 @@ public class ConnectionController extends SubForm {
|
||||
|
||||
private volatile int initcount = 0;
|
||||
|
||||
private TranslatableString connect, state;
|
||||
|
||||
public void initialize() {
|
||||
|
||||
Constants.UNITY_PACKETS = rd_unity.isSelected();
|
||||
@ -137,6 +140,8 @@ public class ConnectionController extends SubForm {
|
||||
synchronized (this) {
|
||||
tryMaybeConnectOnInit();
|
||||
}
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
|
||||
@ -189,20 +194,20 @@ public class ConnectionController extends SubForm {
|
||||
getHConnection().getStateObservable().addListener((oldState, newState) -> Platform.runLater(() -> {
|
||||
updateInputUI();
|
||||
if (newState == HState.NOT_CONNECTED) {
|
||||
lblState.setText("Not connected");
|
||||
btnConnect.setText("Connect");
|
||||
state.setKey(0, "tab.connection.state.notconnected");
|
||||
connect.setKey(0, "tab.connection.button.connect");
|
||||
outHost.setText("");
|
||||
outPort.setText("");
|
||||
}
|
||||
else if (oldState == HState.NOT_CONNECTED) {
|
||||
btnConnect.setText("Abort");
|
||||
connect.setKey(0, "tab.connection.button.abort");
|
||||
}
|
||||
|
||||
if (newState == HState.CONNECTED) {
|
||||
lblState.setText("Connected");
|
||||
state.setKey(0, "tab.connection.state.connected");
|
||||
}
|
||||
if (newState == HState.WAITING_FOR_CLIENT) {
|
||||
lblState.setText("Waiting for connection");
|
||||
state.setKey(0, "tab.connection.state.waiting");
|
||||
}
|
||||
|
||||
if (newState == HState.CONNECTED && useFlash()) {
|
||||
@ -327,4 +332,24 @@ public class ConnectionController extends SubForm {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
TranslatableString port = new TranslatableString("%s", "tab.connection.port");
|
||||
TranslatableString host = new TranslatableString("%s", "tab.connection.host");
|
||||
lblInpPort.textProperty().bind(port);
|
||||
lblInpHost.textProperty().bind(host);
|
||||
lblPort.textProperty().bind(port);
|
||||
lblHost.textProperty().bind(host);
|
||||
cbx_autodetect.textProperty().bind(new TranslatableString("%s", "tab.connection.autodetect"));
|
||||
connect = new TranslatableString("%s", "tab.connection.button.connect");
|
||||
btnConnect.textProperty().bind(connect);
|
||||
lblHotelVersion.textProperty().bind(new TranslatableString("%s", "tab.connection.version"));
|
||||
lblClient.textProperty().bind(new TranslatableString("%s", "tab.connection.client"));
|
||||
rd_unity.textProperty().bind(new TranslatableString("%s", "tab.connection.client.unity"));
|
||||
rd_flash.textProperty().bind(new TranslatableString("%s", "tab.connection.client.flash"));
|
||||
rd_nitro.textProperty().bind(new TranslatableString("%s", "tab.connection.client.nitro"));
|
||||
lblStateHead.textProperty().bind(new TranslatableString("%s", "tab.connection.state"));
|
||||
state = new TranslatableString("%s", "tab.connection.state.notconnected");
|
||||
lblState.textProperty().bind(state);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package gearth.ui.subforms.extensions;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.services.extension_handler.extensions.ExtensionType;
|
||||
import gearth.services.extension_handler.extensions.GEarthExtension;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
@ -80,7 +83,8 @@ public class ExtensionItemContainer extends GridPane {
|
||||
add(versionLabel, 3, 0);
|
||||
|
||||
exitButton = new ExitButton();
|
||||
Tooltip delete = new Tooltip("Close connection with this extension");
|
||||
Tooltip delete = new Tooltip();
|
||||
delete.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.edit.delete.tooltip"));
|
||||
Tooltip.install(exitButton,delete);
|
||||
exitButton.show();
|
||||
clickButton = new SimpleClickButton();
|
||||
@ -89,18 +93,17 @@ public class ExtensionItemContainer extends GridPane {
|
||||
buttonsBox = new HBox(clickButton, exitButton);
|
||||
|
||||
reloadButton = new ReloadButton();
|
||||
Tooltip reload = new Tooltip("Restart this extension");
|
||||
Tooltip reload = new Tooltip();
|
||||
reload.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.edit.restart.tooltip"));
|
||||
Tooltip.install(reloadButton, reload);
|
||||
reloadButton.show();
|
||||
reloadButton.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
reloadButton.setVisible(false);
|
||||
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();
|
||||
Tooltip uninstall = new Tooltip("Uninstall this extension");
|
||||
Tooltip.install(deleteButton, uninstall);
|
||||
deleteButton.show();
|
||||
GridPane this2 = this;
|
||||
|
||||
@ -110,8 +113,8 @@ public class ExtensionItemContainer extends GridPane {
|
||||
|
||||
if (ConfirmationDialog.showDialog(uninstallKey)) {
|
||||
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.CONFIRMATION, uninstallKey
|
||||
,"Confirmation Dialog", null,
|
||||
"Are you sure want to uninstall this extension?", "Do not ask again",
|
||||
, LanguageBundle.get("alert.confirmation.windowtitle"), null,
|
||||
LanguageBundle.get("tab.extensions.table.edit.uninstall.confirmation"), LanguageBundle.get("alert.confirmation.button.donotaskagain"),
|
||||
ButtonType.YES, ButtonType.NO
|
||||
);
|
||||
|
||||
@ -217,4 +220,8 @@ public class ExtensionItemContainer extends GridPane {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,16 @@ package gearth.ui.subforms.extensions;
|
||||
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
import gearth.services.extension_handler.extensions.ExtensionListener;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer;
|
||||
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.ExtensionRunner;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunnerFactory;
|
||||
import gearth.services.g_python.GPythonShell;
|
||||
import gearth.ui.SubForm;
|
||||
import gearth.ui.subforms.extensions.logger.ExtensionLogger;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
@ -36,15 +38,17 @@ public class ExtensionsController extends SubForm {
|
||||
|
||||
private ExtensionRunner extensionRunner = null;
|
||||
private ExtensionHandler extensionHandler;
|
||||
private NetworkExtensionsProducer networkExtensionsProducer; // needed for port
|
||||
private NetworkExtensionServer networkExtensionsProducer; // needed for port
|
||||
private ExtensionLogger extensionLogger = null;
|
||||
|
||||
|
||||
public Label lbl_tableTitle, lbl_tableDesc, lbl_tableAuthor, lbl_tableVersion, lbl_tableEdit, lbl_port;
|
||||
|
||||
|
||||
public void initialize() {
|
||||
scroller.widthProperty().addListener(observable -> header_ext.setPrefWidth(scroller.getWidth()));
|
||||
extensionLogger = new ExtensionLogger();
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
protected void onParentSet() {
|
||||
@ -56,8 +60,8 @@ public class ExtensionsController extends SubForm {
|
||||
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
networkExtensionsProducer
|
||||
= (NetworkExtensionsProducer) extensionHandler.getExtensionProducers().stream()
|
||||
.filter(producer1 -> producer1 instanceof NetworkExtensionsProducer)
|
||||
= (NetworkExtensionServer) extensionHandler.getExtensionProducers().stream()
|
||||
.filter(producer1 -> producer1 instanceof NetworkExtensionServer)
|
||||
.findFirst().get();
|
||||
|
||||
|
||||
@ -71,18 +75,20 @@ public class ExtensionsController extends SubForm {
|
||||
|
||||
extensionHandler.getObservable().addListener(e -> e.getExtensionObservable().addListener(new ExtensionListener() {
|
||||
@Override
|
||||
protected void log(String text) {
|
||||
public void log(String text) {
|
||||
extensionLogger.log(text);
|
||||
}
|
||||
}));
|
||||
|
||||
getHConnection().onDeveloperModeChange(this::setLocalInstallingEnabled);
|
||||
}
|
||||
|
||||
|
||||
public void installBtnClicked(ActionEvent actionEvent) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Install extension");
|
||||
fileChooser.setTitle(LanguageBundle.get("tab.extensions.button.install.windowtitle"));
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("G-Earth extensions", ExecutionInfo.ALLOWEDEXTENSIONTYPES));
|
||||
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.extensions.button.install.filetype"), ExecutionInfo.ALLOWED_EXTENSION_TYPES));
|
||||
File selectedFile = fileChooser.showOpenDialog(parentController.getStage());
|
||||
if (selectedFile != null) {
|
||||
extensionRunner.installAndRunExtension(selectedFile.getPath(), networkExtensionsProducer.getPort());
|
||||
@ -109,6 +115,9 @@ public class ExtensionsController extends SubForm {
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocalInstallingEnabled(boolean enabled) {
|
||||
btn_install.setDisable(!enabled);
|
||||
}
|
||||
|
||||
private volatile int gpytonShellCounter = 1;
|
||||
private volatile boolean pythonShellLaunching = false;
|
||||
@ -116,9 +125,9 @@ public class ExtensionsController extends SubForm {
|
||||
pythonShellLaunching = true;
|
||||
Platform.runLater(() -> btn_gpython.setDisable(true));
|
||||
GPythonShell shell = new GPythonShell(
|
||||
"Scripting shell " + gpytonShellCounter++,
|
||||
String.format("%s %d", LanguageBundle.get("tab.extensions.button.pythonshell.windowtitle"),gpytonShellCounter++),
|
||||
networkExtensionsProducer.getPort(),
|
||||
Authenticator.generatePermanentCookie()
|
||||
NetworkExtensionAuthenticator.generatePermanentCookie()
|
||||
);
|
||||
shell.launch((b) -> {
|
||||
pythonShellLaunching = false;
|
||||
@ -129,4 +138,18 @@ public class ExtensionsController extends SubForm {
|
||||
public ExtensionHandler getExtensionHandler() {
|
||||
return extensionHandler;
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
lbl_tableTitle.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.title"));
|
||||
lbl_tableDesc.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.description"));
|
||||
lbl_tableAuthor.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.author"));
|
||||
lbl_tableVersion.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.version"));
|
||||
lbl_tableEdit.textProperty().bind(new TranslatableString("%s", "tab.extensions.table.edit"));
|
||||
|
||||
lbl_port.textProperty().bind(new TranslatableString("%s:", "tab.extensions.port"));
|
||||
|
||||
btn_gpython.textProperty().bind(new TranslatableString("%s", "tab.extensions.button.pythonshell"));
|
||||
btn_viewExtensionConsole.textProperty().bind(new TranslatableString("%s", "tab.extensions.button.logs"));
|
||||
btn_install.textProperty().bind(new TranslatableString("%s", "tab.extensions.button.install"));
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import gearth.GEarth;
|
||||
import gearth.ui.titlebar.DefaultTitleBarConfig;
|
||||
import gearth.ui.titlebar.GEarthThemedTitleBarConfig;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
@ -38,7 +39,7 @@ public class ExtensionLogger {
|
||||
|
||||
|
||||
stage = new Stage();
|
||||
stage.setTitle("G-Earth | Extension Console");
|
||||
stage.titleProperty().bind(new TranslatableString("G-Earth | %s", "tab.extensions.button.logs.windowtitle"));
|
||||
stage.initModality(Modality.NONE);
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.setMinHeight(235);
|
||||
|
@ -11,17 +11,18 @@ import gearth.services.g_python.GPythonVersionUtils;
|
||||
import gearth.ui.SubForm;
|
||||
import gearth.ui.subforms.info.InfoController;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 06/04/18.
|
||||
@ -31,6 +32,7 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
public static final String INFO_URL_GPYTHON = "https://github.com/sirjonasxx/G-Earth/wiki/G-Python-qtConsole";
|
||||
|
||||
public static final String NOTEPAD_CACHE_KEY = "notepad_text";
|
||||
public static final String DEVELOP_CACHE_KEY = "develop_mode";
|
||||
public static final String ALWAYS_ADMIN_KEY = "always_admin";
|
||||
public static final String SOCKS_CACHE_KEY = "socks_config";
|
||||
public static final String GPYTHON_CACHE_KEY = "use_gpython";
|
||||
@ -56,9 +58,10 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
|
||||
public CheckBox cbx_useSocks;
|
||||
public GridPane grd_socksInfo;
|
||||
public TextField txt_socksPort;
|
||||
public TextField txt_socksIp;
|
||||
public CheckBox cbx_admin;
|
||||
public Label lbl_notepad, lbl_proxyIp;
|
||||
public CheckBox cbx_develop;
|
||||
|
||||
private AdminService adminService;
|
||||
|
||||
@ -73,8 +76,7 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
|
||||
if (Cacher.getCacheContents().has(SOCKS_CACHE_KEY)) {
|
||||
JSONObject socksInitValue = Cacher.getCacheContents().getJSONObject(SOCKS_CACHE_KEY);
|
||||
txt_socksIp.setText(socksInitValue.getString(SOCKS_IP));
|
||||
txt_socksPort.setText(socksInitValue.getString(SOCKS_PORT));
|
||||
txt_socksIp.setText(socksInitValue.getString(SOCKS_IP) + ":" + socksInitValue.getInt(SOCKS_PORT));
|
||||
// cbx_socksUseIfNeeded.setSelected(socksInitValue.getBoolean(IGNORE_ONCE));
|
||||
}
|
||||
|
||||
@ -92,6 +94,8 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
cbx_useSocks.selectedProperty().addListener(observable -> grd_socksInfo.setDisable(!cbx_useSocks.isSelected()));
|
||||
|
||||
ProxyProviderFactory.setSocksConfig(this);
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,6 +114,11 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
}
|
||||
});
|
||||
|
||||
if (Cacher.getCacheContents().has(DEVELOP_CACHE_KEY)) {
|
||||
boolean inDevelopMode = Cacher.getCacheContents().getBoolean(DEVELOP_CACHE_KEY);
|
||||
setDevelopMode(inDevelopMode);
|
||||
}
|
||||
|
||||
updateAdvancedUI();
|
||||
}
|
||||
|
||||
@ -118,15 +127,20 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
Cacher.put(NOTEPAD_CACHE_KEY, txtarea_notepad.getText());
|
||||
Cacher.put(GPYTHON_CACHE_KEY, cbx_gpython.isSelected());
|
||||
Cacher.put(ALWAYS_ADMIN_KEY, cbx_admin.isSelected());
|
||||
Cacher.put(DEVELOP_CACHE_KEY, cbx_develop.isSelected());
|
||||
saveSocksConfig();
|
||||
}
|
||||
|
||||
private void saveSocksConfig() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put(SOCKS_IP, txt_socksIp.getText());
|
||||
jsonObject.put(SOCKS_PORT, txt_socksPort.getText());
|
||||
// jsonObject.put(IGNORE_ONCE, cbx_socksUseIfNeeded.isSelected());
|
||||
Cacher.put(SOCKS_CACHE_KEY, jsonObject);
|
||||
if (txt_socksIp.getText().contains(":")) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put(SOCKS_IP, getSocksHost());
|
||||
jsonObject.put(SOCKS_PORT, getSocksPort());
|
||||
Cacher.put(SOCKS_CACHE_KEY, jsonObject);
|
||||
}
|
||||
else {
|
||||
Cacher.remove(SOCKS_CACHE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAdvancedUI() {
|
||||
@ -150,12 +164,16 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
|
||||
@Override
|
||||
public int getSocksPort() {
|
||||
return Integer.parseInt(txt_socksPort.getText());
|
||||
String socksString = txt_socksIp.getText();
|
||||
if (socksString.contains(":")) {
|
||||
return Integer.parseInt(socksString.split(":")[1]);
|
||||
}
|
||||
return 1337;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSocksHost() {
|
||||
return txt_socksIp.getText();
|
||||
return txt_socksIp.getText().split(":")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -177,12 +195,13 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
});
|
||||
if (!GPythonVersionUtils.validInstallation()) {
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "G-Python installation", ButtonType.OK);
|
||||
alert.setTitle("G-Python installation");
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, LanguageBundle.get("tab.extra.options.pythonscripting.alert.title"), ButtonType.OK);
|
||||
alert.setTitle(LanguageBundle.get("tab.extra.options.pythonscripting.alert.title"));
|
||||
|
||||
FlowPane fp = new FlowPane();
|
||||
Label lbl = new Label("Before using G-Python, install the right packages using pip!" +
|
||||
System.lineSeparator() + System.lineSeparator() + "More information here:");
|
||||
Label lbl = new Label(LanguageBundle.get("tab.extra.options.pythonscripting.alert.content") +
|
||||
System.lineSeparator() + System.lineSeparator() +
|
||||
LanguageBundle.get("tab.extra.options.pythonscripting.alert.moreinformation"));
|
||||
Hyperlink link = new Hyperlink(INFO_URL_GPYTHON);
|
||||
fp.getChildren().addAll( lbl, link);
|
||||
link.setOnAction(event -> {
|
||||
@ -215,8 +234,59 @@ public class ExtraController extends SubForm implements SocksConfiguration {
|
||||
|
||||
}
|
||||
|
||||
public void developCbxClick(ActionEvent actionEvent) {
|
||||
if (cbx_develop.isSelected()) {
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("tab.extra.options.developmode.alert.title"), ButtonType.NO, ButtonType.YES);
|
||||
alert.setTitle(LanguageBundle.get("tab.extra.options.developmode.alert.title"));
|
||||
|
||||
Label lbl = new Label(LanguageBundle.get("tab.extra.options.developmode.alert.content"));
|
||||
|
||||
alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
|
||||
alert.getDialogPane().setContent(lbl);
|
||||
|
||||
try {
|
||||
Optional<ButtonType> result = TitleBarController.create(alert).showAlertAndWait();
|
||||
if (!result.isPresent() || result.get() == ButtonType.NO) {
|
||||
cbx_develop.setSelected(false);
|
||||
}
|
||||
else {
|
||||
setDevelopMode(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
setDevelopMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDevelopMode(boolean enabled) {
|
||||
cbx_develop.setSelected(enabled);
|
||||
getHConnection().setDeveloperMode(enabled);
|
||||
}
|
||||
|
||||
public void adminCbxClick(ActionEvent actionEvent) {
|
||||
adminService.setEnabled(cbx_admin.isSelected());
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
url_troubleshooting.textProperty().bind(new TranslatableString("%s", "tab.extra.troubleshooting"));
|
||||
|
||||
lbl_notepad.textProperty().bind(new TranslatableString("%s:", "tab.extra.notepad"));
|
||||
lbl_proxyIp.textProperty().bind(new TranslatableString("%s:", "tab.extra.options.advanced.proxy.ip"));
|
||||
|
||||
cbx_alwaysOnTop.textProperty().bind(new TranslatableString("%s", "tab.extra.options.alwaysontop"));
|
||||
|
||||
cbx_develop.textProperty().bind(new TranslatableString("%s", "tab.extra.options.developmode"));
|
||||
cbx_admin.textProperty().bind(new TranslatableString("%s", "tab.extra.options.staffpermissions"));
|
||||
cbx_gpython.textProperty().bind(new TranslatableString("%s", "tab.extra.options.pythonscripting"));
|
||||
cbx_advanced.textProperty().bind(new TranslatableString("%s", "tab.extra.options.advanced"));
|
||||
|
||||
cbx_useSocks.textProperty().bind(new TranslatableString("%s", "tab.extra.options.advanced.socks"));
|
||||
cbx_disableDecryption.textProperty().bind(new TranslatableString("%s", "tab.extra.options.advanced.disabledecryption"));
|
||||
cbx_debug.textProperty().bind(new TranslatableString("%s", "tab.extra.options.advanced.debugstdout"));
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package gearth.ui.subforms.info;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import gearth.ui.SubForm;
|
||||
@ -24,7 +26,8 @@ public class InfoController extends SubForm {
|
||||
public Hyperlink link_g_store;
|
||||
public Hyperlink link_t_gearth;
|
||||
|
||||
public Label version;
|
||||
public Label version, lbl_description, lbl_createdBy, lbl_contrib, lbl_links;
|
||||
public Button btn_donate;
|
||||
|
||||
public static void activateHyperlink(Hyperlink link) {
|
||||
link.setOnAction((ActionEvent event) -> {
|
||||
@ -49,16 +52,18 @@ public class InfoController extends SubForm {
|
||||
activateHyperlink(link_g_tanji);
|
||||
activateHyperlink(link_g_store);
|
||||
activateHyperlink(link_t_gearth);
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
public void donate(ActionEvent actionEvent) {
|
||||
String pubkey = "1GEarthEV9Ua3RcixsKTcuc1PPZd9hqri3";
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Donate Bitcoins", ButtonType.OK);
|
||||
alert.setHeaderText("Donate Bitcoins");
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, LanguageBundle.get("tab.info.donate.alert.title"), ButtonType.OK);
|
||||
alert.setHeaderText(LanguageBundle.get("tab.info.donate.alert.title"));
|
||||
|
||||
VBox test = new VBox();
|
||||
test.getChildren().add(new Label("Bitcoin public address:"));
|
||||
test.getChildren().add(new Label(LanguageBundle.get("tab.info.donate.alert.content")));
|
||||
TextArea pubText = new TextArea(pubkey);
|
||||
pubText.setPrefHeight(28);
|
||||
pubText.setMaxWidth(250);
|
||||
@ -73,4 +78,13 @@ public class InfoController extends SubForm {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
lbl_description.textProperty().bind(new TranslatableString("%s", "tab.info.description"));
|
||||
lbl_createdBy.textProperty().bind(new TranslatableString("%s:", "tab.info.createdby"));
|
||||
lbl_contrib.textProperty().bind(new TranslatableString("%s:", "tab.info.contributors"));
|
||||
lbl_links.textProperty().bind(new TranslatableString("%s:", "tab.info.links"));
|
||||
|
||||
btn_donate.textProperty().bind(new TranslatableString("%s", "tab.info.donate"));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
package gearth.ui.subforms.injection;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.misc.StringifyAble;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.services.packet_info.PacketInfo;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -19,7 +21,7 @@ public class InjectedPackets implements StringifyAble {
|
||||
public InjectedPackets(String packetsAsString, int amountPackets, PacketInfoManager packetInfoManager, HMessage.Direction direction) {
|
||||
String description;
|
||||
if (amountPackets > 1) {
|
||||
description = String.format("(packets: %d, length: %d)", amountPackets, packetsAsString.length());
|
||||
description = String.format("(%s: %d, %s: %d)", LanguageBundle.get("tab.injection.description.packets"), amountPackets, LanguageBundle.get("tab.injection.description.length"), packetsAsString.length());
|
||||
}
|
||||
else { // assume 1 packet
|
||||
HPacket packet = new HPacket(packetsAsString);
|
||||
@ -40,7 +42,7 @@ public class InjectedPackets implements StringifyAble {
|
||||
description = String.format("%s", identifier);
|
||||
}
|
||||
else {
|
||||
description = String.format("(id: %d, length: %d)", packet.headerId(), packet.length());
|
||||
description = String.format("(%s: %d, %s: %d)", LanguageBundle.get("tab.injection.description.id"), packet.headerId(), LanguageBundle.get("tab.injection.description.length"), packet.length());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
package gearth.ui.subforms.injection;
|
||||
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.connection.HState;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.ui.SubForm;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
@ -11,8 +14,6 @@ import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.text.Text;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.ui.SubForm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -26,14 +27,18 @@ public class InjectionController extends SubForm {
|
||||
private static final int historylimit = 69;
|
||||
|
||||
public TextArea inputPacket;
|
||||
public Text lbl_corrruption;
|
||||
public Text lbl_corruption;
|
||||
public Text lbl_pcktInfo;
|
||||
public Button btn_sendToServer;
|
||||
public Button btn_sendToClient;
|
||||
public ListView<InjectedPackets> history;
|
||||
public Label lblHistory;
|
||||
public Hyperlink lnk_clearHistory;
|
||||
|
||||
private TranslatableString corruption, pcktInfo;
|
||||
|
||||
protected void onParentSet() {
|
||||
getHConnection().onDeveloperModeChange(developMode -> updateUI());
|
||||
getHConnection().getStateObservable().addListener((oldState, newState) -> Platform.runLater(this::updateUI));
|
||||
|
||||
inputPacket.textProperty().addListener(event -> Platform.runLater(this::updateUI));
|
||||
@ -52,14 +57,14 @@ public class InjectionController extends SubForm {
|
||||
}
|
||||
});
|
||||
|
||||
lblHistory.setTooltip(new Tooltip("Double click a packet to restore it"));
|
||||
|
||||
List<Object> rawHistory = Cacher.getList(HISTORY_CACHE_KEY);
|
||||
if (rawHistory != null) {
|
||||
List<InjectedPackets> history = rawHistory.stream()
|
||||
.map(o -> (String)o).limit(historylimit - 1).map(InjectedPackets::new).collect(Collectors.toList());
|
||||
updateHistoryView(history);
|
||||
}
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
private static boolean isPacketIncomplete(String line) {
|
||||
@ -103,62 +108,87 @@ public class InjectionController extends SubForm {
|
||||
private void updateUI() {
|
||||
boolean dirty = false;
|
||||
|
||||
lbl_corrruption.setText("isCorrupted: False");
|
||||
lbl_corrruption.getStyleClass().clear();
|
||||
lbl_corrruption.getStyleClass().add("not-corrupted-label");
|
||||
corruption.setFormat("%s: %s");
|
||||
corruption.setKey(1, "tab.injection.corrupted.false");
|
||||
lbl_corruption.getStyleClass().clear();
|
||||
lbl_corruption.getStyleClass().add("not-corrupted-label");
|
||||
|
||||
HPacket[] packets = parsePackets(inputPacket.getText());
|
||||
|
||||
if (packets.length == 0) {
|
||||
dirty = true;
|
||||
lbl_corrruption.setFill(Paint.valueOf("#ee0404b2"));
|
||||
lbl_corrruption.getStyleClass().clear();
|
||||
lbl_corrruption.getStyleClass().add("corrupted-label");
|
||||
lbl_corruption.setFill(Paint.valueOf("#ee0404b2"));
|
||||
lbl_corruption.getStyleClass().clear();
|
||||
lbl_corruption.getStyleClass().add("corrupted-label");
|
||||
}
|
||||
|
||||
for (int i = 0; i < packets.length; i++) {
|
||||
if (packets[i].isCorrupted()) {
|
||||
if (!dirty) {
|
||||
lbl_corrruption.setText("isCorrupted: True -> " + i);
|
||||
lbl_corrruption.getStyleClass().clear();
|
||||
lbl_corrruption.getStyleClass().add("corrupted-label");
|
||||
corruption.setFormat("%s: %s -> " + i);
|
||||
corruption.setKey(1, "tab.injection.corrupted.true");
|
||||
lbl_corruption.getStyleClass().clear();
|
||||
lbl_corruption.getStyleClass().add("corrupted-label");
|
||||
dirty = true;
|
||||
} else
|
||||
lbl_corrruption.setText(lbl_corrruption.getText() + ", " + i);
|
||||
corruption.setFormat(corruption.getFormat() + ", " + i);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty && packets.length == 1) {
|
||||
lbl_corrruption.setText("isCorrupted: True"); // no index needed
|
||||
corruption.setFormatAndKeys("%s: %s", "tab.injection.corrupted", "tab.injection.corrupted.true");
|
||||
}
|
||||
|
||||
if (!dirty) {
|
||||
PacketInfoManager packetInfoManager = getHConnection().getPacketInfoManager();
|
||||
|
||||
for (HPacket packet : packets) {
|
||||
packet.completePacket(packetInfoManager);
|
||||
}
|
||||
HConnection connection = getHConnection();
|
||||
|
||||
boolean canSendToClient = Arrays.stream(packets).allMatch(packet ->
|
||||
packet.isPacketComplete() && packet.canSendToClient());
|
||||
connection.canSendPacket(HMessage.Direction.TOCLIENT, packet));
|
||||
boolean canSendToServer = Arrays.stream(packets).allMatch(packet ->
|
||||
packet.isPacketComplete() && packet.canSendToServer());
|
||||
connection.canSendPacket(HMessage.Direction.TOSERVER, packet));
|
||||
|
||||
btn_sendToClient.setDisable(!canSendToClient);
|
||||
btn_sendToServer.setDisable(!canSendToServer);
|
||||
|
||||
// mark packet sending unsafe if there is any packet that is unsafe for both TOSERVER and TOCLIENT
|
||||
boolean isUnsafe = Arrays.stream(packets).anyMatch(packet ->
|
||||
!connection.isPacketSendingSafe(HMessage.Direction.TOCLIENT, packet) &&
|
||||
!connection.isPacketSendingSafe(HMessage.Direction.TOSERVER, packet));
|
||||
|
||||
btn_sendToClient.setDisable(!canSendToClient || getHConnection().getState() != HState.CONNECTED);
|
||||
btn_sendToServer.setDisable(!canSendToServer || getHConnection().getState() != HState.CONNECTED);
|
||||
if (packets.length == 1) {
|
||||
lbl_pcktInfo.setText("header (id:" + packets[0].headerId() + ", length:" +
|
||||
packets[0].length() + ")");
|
||||
HPacket packet = packets[0];
|
||||
if (isUnsafe) {
|
||||
pcktInfo.setFormatAndKeys("%s (%s: " + packet.headerId() + ", %s: " + packet.length() + "), %s",
|
||||
"tab.injection.description.header",
|
||||
"tab.injection.description.id",
|
||||
"tab.injection.description.length",
|
||||
"tab.injection.description.unsafe");
|
||||
}
|
||||
else {
|
||||
pcktInfo.setFormatAndKeys("%s (%s: " + packet.headerId() + ", %s: " + packet.length() + ")",
|
||||
"tab.injection.description.header",
|
||||
"tab.injection.description.id",
|
||||
"tab.injection.description.length");
|
||||
}
|
||||
}
|
||||
else {
|
||||
lbl_pcktInfo.setText("");
|
||||
if (isUnsafe) {
|
||||
pcktInfo.setFormatAndKeys("%s", "tab.injection.description.unsafe");
|
||||
}
|
||||
else {
|
||||
pcktInfo.setFormatAndKeys("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (packets.length == 1) {
|
||||
lbl_pcktInfo.setText("header (id:NULL, length:" + packets[0].getBytesLength()+")");
|
||||
pcktInfo.setFormatAndKeys("%s (%s:NULL, %s: " + packets[0].getBytesLength() + ")",
|
||||
"tab.injection.description.header",
|
||||
"tab.injection.description.id",
|
||||
"tab.injection.description.length");
|
||||
}
|
||||
else {
|
||||
lbl_pcktInfo.setText("");
|
||||
pcktInfo.setFormatAndKeys("");
|
||||
}
|
||||
|
||||
btn_sendToClient.setDisable(true);
|
||||
@ -171,7 +201,7 @@ public class InjectionController extends SubForm {
|
||||
HPacket[] packets = parsePackets(inputPacket.getText());
|
||||
for (HPacket packet : packets) {
|
||||
getHConnection().sendToServer(packet);
|
||||
writeToLog(Color.BLUE, "SS -> packet with id: " + packet.headerId());
|
||||
writeToLog(Color.BLUE, String.format("SS -> %s: %d", LanguageBundle.get("tab.injection.log.packetwithid"), packet.headerId()));
|
||||
}
|
||||
|
||||
addToHistory(packets, inputPacket.getText(), HMessage.Direction.TOSERVER);
|
||||
@ -181,7 +211,7 @@ public class InjectionController extends SubForm {
|
||||
HPacket[] packets = parsePackets(inputPacket.getText());
|
||||
for (HPacket packet : packets) {
|
||||
getHConnection().sendToClient(packet);
|
||||
writeToLog(Color.RED, "CS -> packet with id: " + packet.headerId());
|
||||
writeToLog(Color.RED, String.format("CS -> %s: %d", LanguageBundle.get("tab.injection.log.packetwithid"), packet.headerId()));
|
||||
}
|
||||
|
||||
addToHistory(packets, inputPacket.getText(), HMessage.Direction.TOCLIENT);
|
||||
@ -224,6 +254,23 @@ public class InjectionController extends SubForm {
|
||||
updateHistoryView(new ArrayList<>());
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
lblHistory.textProperty().bind(new TranslatableString("%s", "tab.injection.history"));
|
||||
lblHistory.setTooltip(new Tooltip());
|
||||
lblHistory.getTooltip().textProperty().bind(new TranslatableString("%s", "tab.injection.history.tooltip"));
|
||||
|
||||
corruption = new TranslatableString("%s: %s", "tab.injection.corrupted", "tab.injection.corrupted.true");
|
||||
lbl_corruption.textProperty().bind(corruption);
|
||||
|
||||
pcktInfo = new TranslatableString("%s (%s:NULL, %s:0)", "tab.injection.description.header", "tab.injection.description.id", "tab.injection.description.length");
|
||||
lbl_pcktInfo.textProperty().bind(pcktInfo);
|
||||
|
||||
btn_sendToServer.textProperty().bind(new TranslatableString("%s", "tab.injection.send.toserver"));
|
||||
btn_sendToClient.textProperty().bind(new TranslatableString("%s", "tab.injection.send.toclient"));
|
||||
|
||||
lnk_clearHistory.textProperty().bind(new TranslatableString("%s", "tab.injection.history.clear"));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
HPacket[] packets = parsePackets("{l}{h:3}{i:967585}{i:9589}{s:\"furni_inscriptionfuckfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionfurni_inscriptionsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss\"}{s:\"sirjonasxx-II\"}{s:\"\"}{i:188}{i:0}{i:0}{b:false}");
|
||||
System.out.println(new HPacket("{l}{h:2550}{s:\"ClientPerf\"\"ormance\\\"}\"}{s:\"23\"}{s:\"fps\"}{s:\"Avatars: 1, Objects: 0\"}{i:76970180}").toExpression());
|
||||
|
@ -55,7 +55,7 @@ class LinuxTerminalLogger extends SimpleTerminalLogger {
|
||||
packet.toString()
|
||||
);
|
||||
|
||||
output.append(colorizePackets.get("DEFAULT"));
|
||||
output.append(colorizePackets.get("ENGLISH"));
|
||||
|
||||
System.out.println(output.toString());
|
||||
}
|
||||
@ -67,7 +67,7 @@ class LinuxTerminalLogger extends SimpleTerminalLogger {
|
||||
System.out.println(
|
||||
colorizePackets.get("EXPRESSION") +
|
||||
expr +
|
||||
colorizePackets.get("DEFAULT")
|
||||
colorizePackets.get("ENGLISH")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
package gearth.ui.subforms.scheduler;
|
||||
|
||||
import com.tulskiy.keymaster.common.Provider;
|
||||
import gearth.extensions.parsers.HDirection;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.StateChangeListener;
|
||||
import gearth.protocol.connection.HState;
|
||||
import gearth.services.scheduler.Interval;
|
||||
import gearth.services.scheduler.Scheduler;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
@ -17,6 +23,7 @@ import javax.swing.*;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 06/04/18.
|
||||
@ -24,7 +31,7 @@ import java.util.List;
|
||||
public class SchedulerController extends SubForm {
|
||||
|
||||
private static final Interval defaultInterval = new Interval(0, 500);
|
||||
private static final HPacket defaultPacket = new HPacket(0);
|
||||
private static final HPacket defaultPacket = new HPacket("Chat", HMessage.Direction.TOCLIENT, -1, "Frank loves G-Earth", 0, 33, 0, 0);
|
||||
|
||||
public VBox schedulecontainer;
|
||||
public GridPane header;
|
||||
@ -48,6 +55,9 @@ public class SchedulerController extends SubForm {
|
||||
|
||||
private Scheduler<InteractableScheduleItem> scheduler = null;
|
||||
|
||||
private TranslatableString addoredit;
|
||||
public Label lbl_tableIndex, lbl_tablePacket, lbl_tableInterval, lbl_tableDest, lbl_tableEdit, lbl_setupPacket, lbl_setupInterval;
|
||||
|
||||
|
||||
public void initialize() {
|
||||
scrollpane.widthProperty().addListener(observable -> header.setPrefWidth(scrollpane.getWidth()));
|
||||
@ -56,12 +66,6 @@ public class SchedulerController extends SubForm {
|
||||
txt_packet.textProperty().addListener(event -> Platform.runLater(this::updateUI));
|
||||
txt_delay.textProperty().addListener(event -> Platform.runLater(this::updateUI));
|
||||
|
||||
btn_clear.setTooltip(new Tooltip("Clear all items"));
|
||||
btn_save.setTooltip(new Tooltip("Save to file"));
|
||||
btn_load.setTooltip(new Tooltip("Load from file"));
|
||||
|
||||
updateUI();
|
||||
|
||||
//register hotkeys
|
||||
//disable some output things
|
||||
PrintStream err = System.err;
|
||||
@ -76,11 +80,17 @@ public class SchedulerController extends SubForm {
|
||||
provider.register(KeyStroke.getKeyStroke("control shift " + ii[0]), hotKey -> switchPauseHotkey(ii[0]));
|
||||
}
|
||||
System.setErr(err);
|
||||
|
||||
initLanguageBinding();
|
||||
setInputDefault(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onParentSet() {
|
||||
scheduler = new Scheduler<>(getHConnection());
|
||||
getHConnection().onDeveloperModeChange(developMode -> updateUI());
|
||||
getHConnection().getStateObservable().addListener((oldState, newState) -> updateUI());
|
||||
updateUI();
|
||||
}
|
||||
|
||||
private void switchPauseHotkey(int index) {
|
||||
@ -100,7 +110,14 @@ public class SchedulerController extends SubForm {
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
btn_addoredit.setDisable(!Interval.isValid(txt_delay.getText()) || new HPacket(txt_packet.getText()).isCorrupted());
|
||||
HConnection connection = getHConnection();
|
||||
if (connection == null) return;
|
||||
|
||||
HMessage.Direction direction = rb_incoming.isSelected() ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER;
|
||||
HPacket packet = new HPacket(txt_packet.getText());
|
||||
boolean isPacketOk = connection.canSendPacket(direction, packet);
|
||||
|
||||
btn_addoredit.setDisable(!Interval.isValid(txt_delay.getText()) || !isPacketOk);
|
||||
}
|
||||
|
||||
public void scheduleBtnClicked(ActionEvent actionEvent) {
|
||||
@ -125,7 +142,7 @@ public class SchedulerController extends SubForm {
|
||||
isBeingEdited.isUpdatedTrigger();
|
||||
|
||||
isBeingEdited = null;
|
||||
setInputDefault();
|
||||
setInputDefault(false);
|
||||
}
|
||||
|
||||
}
|
||||
@ -137,7 +154,7 @@ public class SchedulerController extends SubForm {
|
||||
|
||||
newItem.onDelete(() -> {
|
||||
if (isBeingEdited == newItem) {
|
||||
setInputDefault();
|
||||
setInputDefault(false);
|
||||
isBeingEdited = null;
|
||||
}
|
||||
scheduler.remove(newItem);
|
||||
@ -157,25 +174,25 @@ public class SchedulerController extends SubForm {
|
||||
rb_outgoing.setSelected(newItem.getDestinationProperty().get() == HMessage.Direction.TOSERVER);
|
||||
|
||||
isBeingEdited = newItem;
|
||||
btn_addoredit.setText("Edit");
|
||||
addoredit.setKey(0, "tab.scheduler.button.edit");
|
||||
updateUI();
|
||||
newItem.onIsBeingUpdatedTrigger();
|
||||
}
|
||||
else {
|
||||
setInputDefault();
|
||||
setInputDefault(false);
|
||||
isBeingEdited.isUpdatedTrigger();
|
||||
isBeingEdited = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setInputDefault() {
|
||||
private void setInputDefault(boolean showDummyPacket) {
|
||||
txt_delay.setText(defaultInterval.toString());
|
||||
txt_packet.setText(defaultPacket.toString());
|
||||
txt_packet.setText(showDummyPacket ? defaultPacket.toExpression() : "");
|
||||
rb_incoming.setSelected(true);
|
||||
rb_outgoing.setSelected(false);
|
||||
|
||||
btn_addoredit.setText("Add");
|
||||
addoredit.setKey(0, "tab.scheduler.button.add");
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@ -203,9 +220,9 @@ public class SchedulerController extends SubForm {
|
||||
|
||||
//Set extension filter
|
||||
FileChooser.ExtensionFilter extFilter =
|
||||
new FileChooser.ExtensionFilter("SCHED files (*.sched)", "*.sched");
|
||||
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.scheduler.filetype"), "*.sched");
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
fileChooser.setTitle("Save Schedule File");
|
||||
fileChooser.setTitle(LanguageBundle.get("tab.scheduler.button.save.windowtitle"));
|
||||
|
||||
//Show save file dialog
|
||||
File file = fileChooser.showSaveDialog(parentController.getStage());
|
||||
@ -234,9 +251,9 @@ public class SchedulerController extends SubForm {
|
||||
List<InteractableScheduleItem> list = new ArrayList<>();
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Load Schedule File");
|
||||
fileChooser.setTitle(LanguageBundle.get("tab.scheduler.button.load.windowtitle"));
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Schedule Files", "*.sched"));
|
||||
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.scheduler.filetype"), "*.sched"));
|
||||
File selectedFile = fileChooser.showOpenDialog(parentController.getStage());
|
||||
if (selectedFile != null) {
|
||||
|
||||
@ -262,4 +279,35 @@ public class SchedulerController extends SubForm {
|
||||
load(list);
|
||||
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
addoredit = new TranslatableString("%s", "tab.scheduler.button.add");
|
||||
btn_addoredit.textProperty().bind(addoredit);
|
||||
|
||||
btn_clear.textProperty().bind(new TranslatableString("%s", "tab.scheduler.button.clear"));
|
||||
btn_clear.setTooltip(new Tooltip());
|
||||
btn_clear.getTooltip().textProperty().bind(new TranslatableString("%s", "tab.scheduler.button.clear.tooltip"));
|
||||
|
||||
btn_save.textProperty().bind(new TranslatableString("%s", "tab.scheduler.button.save"));
|
||||
btn_save.setTooltip(new Tooltip());
|
||||
btn_save.getTooltip().textProperty().bind(new TranslatableString("%s", "tab.scheduler.button.save.tooltip"));
|
||||
|
||||
btn_load.textProperty().bind(new TranslatableString("%s", "tab.scheduler.button.load"));
|
||||
btn_load.setTooltip(new Tooltip());
|
||||
btn_load.getTooltip().textProperty().bind(new TranslatableString("%s", "tab.scheduler.button.load.tooltip"));
|
||||
|
||||
lbl_tableIndex.textProperty().bind(new TranslatableString("%s", "tab.scheduler.table.index"));
|
||||
lbl_tablePacket.textProperty().bind(new TranslatableString("%s", "tab.scheduler.table.packet"));
|
||||
lbl_tableInterval.textProperty().bind(new TranslatableString("%s", "tab.scheduler.table.interval"));
|
||||
lbl_tableDest.textProperty().bind(new TranslatableString("%s", "tab.scheduler.table.destination"));
|
||||
lbl_tableEdit.textProperty().bind(new TranslatableString("%s", "tab.scheduler.table.edit"));
|
||||
|
||||
lbl_setupPacket.textProperty().bind(new TranslatableString("%s:", "tab.scheduler.setup.packet"));
|
||||
lbl_setupInterval.textProperty().bind(new TranslatableString("%s:", "tab.scheduler.setup.interval"));
|
||||
|
||||
rb_incoming.textProperty().bind(new TranslatableString("%s", "tab.scheduler.direction.in"));
|
||||
rb_outgoing.textProperty().bind(new TranslatableString("%s", "tab.scheduler.direction.out"));
|
||||
|
||||
cbx_hotkeys.textProperty().bind(new TranslatableString("%s", "tab.scheduler.hotkeys"));
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package gearth.ui.subforms.tools;
|
||||
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.ui.translations.TranslatableString;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
@ -27,6 +29,8 @@ public class ToolsController extends SubForm {
|
||||
public Button btn_toPacket;
|
||||
public TextArea txt_exprArea;
|
||||
|
||||
public Label lbl_integer, lbl_uShort, lbl_encodingDecoding, lbl_packetToExpression;
|
||||
|
||||
//TODO: toExpression() without bytelength limit for this use only
|
||||
|
||||
public void initialize() {
|
||||
@ -115,6 +119,7 @@ public class ToolsController extends SubForm {
|
||||
}
|
||||
});
|
||||
|
||||
initLanguageBinding();
|
||||
}
|
||||
|
||||
public void btnEncodeInt_clicked(ActionEvent actionEvent) {
|
||||
@ -156,4 +161,20 @@ public class ToolsController extends SubForm {
|
||||
public void btn_toPacket_clicked(ActionEvent actionEvent) {
|
||||
txt_packetArea.setText(parseToPacket(txt_exprArea.getText()).toString());
|
||||
}
|
||||
|
||||
private void initLanguageBinding() {
|
||||
lbl_integer.textProperty().bind(new TranslatableString("%s:", "tab.tools.type.integer"));
|
||||
lbl_uShort.textProperty().bind(new TranslatableString("%s:", "tab.tools.type.ushort"));
|
||||
|
||||
TranslatableString encode = new TranslatableString("%s", "tab.tools.button.encode");
|
||||
TranslatableString decode = new TranslatableString("%s", "tab.tools.button.decode");
|
||||
btnEncodeInt.textProperty().bind(encode);
|
||||
btnEncodeUShort.textProperty().bind(encode);
|
||||
btnDecodeInt.textProperty().bind(decode);
|
||||
btnDecodeUshort.textProperty().bind(decode);
|
||||
|
||||
lbl_encodingDecoding.textProperty().bind(new TranslatableString("%s/%s", "tab.tools.encoding", "tab.tools.decoding"));
|
||||
|
||||
lbl_packetToExpression.textProperty().bind(new TranslatableString("%s <-> %s", "tab.tools.packet", "tab.tools.expression"));
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,18 @@ package gearth.ui.titlebar;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.ui.themes.ThemeFactory;
|
||||
import gearth.ui.translations.Language;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
@ -19,6 +21,7 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
@ -30,6 +33,7 @@ public class TitleBarController {
|
||||
public ImageView titleIcon;
|
||||
public ImageView themeBtn;
|
||||
public ImageView minimizeBtn;
|
||||
public MenuButton languagePicker;
|
||||
|
||||
private Stage stage;
|
||||
private TitleBarConfig config;
|
||||
@ -83,6 +87,9 @@ public class TitleBarController {
|
||||
stage.titleProperty().addListener((i) -> controller.setTitle(stage.getTitle()));
|
||||
controller.setTitle(stage.getTitle());
|
||||
|
||||
controller.languagePicker.getItems().addAll(Language.getMenuItems());
|
||||
controller.languagePicker.setGraphic(LanguageBundle.getLanguage().getIcon());
|
||||
|
||||
stage.getIcons().addListener((InvalidationListener) observable -> controller.updateIcon());
|
||||
controller.updateIcon();
|
||||
|
||||
@ -96,11 +103,16 @@ public class TitleBarController {
|
||||
|
||||
if (!config.displayThemePicker()) {
|
||||
((GridPane) controller.themeBtn.getParent()).getChildren().remove(controller.themeBtn);
|
||||
((GridPane) controller.languagePicker.getParent()).getChildren().remove(controller.languagePicker);
|
||||
}
|
||||
});
|
||||
return controller;
|
||||
}
|
||||
|
||||
private static void initLanguagePicker() {
|
||||
|
||||
}
|
||||
|
||||
public void updateIcon() {
|
||||
Platform.runLater(() -> titleIcon.setImage(stage.getIcons().size() > 0 ? stage.getIcons().get(0) :
|
||||
new Image(GEarth.class.getResourceAsStream(
|
||||
@ -158,5 +170,4 @@ public class TitleBarController {
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
81
G-Earth/src/main/java/gearth/ui/translations/Language.java
Normal file
81
G-Earth/src/main/java/gearth/ui/translations/Language.java
Normal file
@ -0,0 +1,81 @@
|
||||
package gearth.ui.translations;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public enum Language {
|
||||
ENGLISH ("en"),
|
||||
DUTCH ("nl"),
|
||||
FRENCH ("fr"),
|
||||
GERMAN ("de"),
|
||||
SPANISH ("es"),
|
||||
PORTUGUESE ("pt"),
|
||||
ITALIAN ("it"),
|
||||
FINNISH ("fi"),
|
||||
TURKISH ("tr");
|
||||
|
||||
public final ResourceBundle messages;
|
||||
private final String locale;
|
||||
|
||||
Language(String locale) {
|
||||
this.locale = locale;
|
||||
|
||||
ResourceBundle resBundle;
|
||||
try {
|
||||
InputStream stream = Language.class.getResourceAsStream(String.format("/gearth/ui/translations/messages_%s.properties", locale));
|
||||
InputStreamReader isr = new InputStreamReader(stream, StandardCharsets.UTF_8);
|
||||
resBundle = new PropertyResourceBundle(isr);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("/gearth/ui/translations/messages_%s.properties%n", locale);
|
||||
System.out.println("Couldn't load language file: " + locale);
|
||||
resBundle = null;
|
||||
}
|
||||
|
||||
this.messages = resBundle;
|
||||
}
|
||||
|
||||
public MenuItem asMenuItem() {
|
||||
MenuItem menuItem = new MenuItem(null, getIcon());
|
||||
menuItem.setOnAction(this::onSelect);
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
private void onSelect(ActionEvent actionEvent) {
|
||||
ContextMenu ctxMenu = ((MenuItem) actionEvent.getSource()).getParentPopup();
|
||||
((MenuButton) ctxMenu.getOwnerNode()).setGraphic(getIcon());
|
||||
LanguageBundle.setLanguage(this);
|
||||
}
|
||||
|
||||
public ImageView getIcon() {
|
||||
ImageView icon = new ImageView();
|
||||
icon.getStyleClass().addAll("language-icon", locale);
|
||||
icon.setFitWidth(18);
|
||||
icon.setFitHeight(18);
|
||||
return icon;
|
||||
}
|
||||
|
||||
public static MenuItem[] getMenuItems() {
|
||||
return Arrays.stream(values())
|
||||
.map(Language::asMenuItem)
|
||||
.toArray(MenuItem[]::new);
|
||||
}
|
||||
|
||||
public static Language getSystemLanguage() {
|
||||
String locale = System.getProperty("user.language");
|
||||
for (Language l : values())
|
||||
if (l.locale.equals(locale))
|
||||
return l;
|
||||
|
||||
return ENGLISH;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package gearth.ui.translations;
|
||||
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
public class LanguageBundle extends ResourceBundle {
|
||||
|
||||
private static final String LANGUAGE_CACHE_KEY = "language";
|
||||
private static Language current;
|
||||
private static final Set<TranslatableString> requireUpdate = new HashSet<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
current = Language.valueOf((String) Cacher.get(LANGUAGE_CACHE_KEY));
|
||||
} catch (Exception e) {
|
||||
current = Language.getSystemLanguage();
|
||||
Cacher.put(LANGUAGE_CACHE_KEY, current.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTranslatableString(TranslatableString translatableString) {
|
||||
requireUpdate.add(translatableString);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleGetObject(String key) {
|
||||
return current.messages.getObject(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getKeys() {
|
||||
return current.messages.getKeys();
|
||||
}
|
||||
|
||||
public static void setLanguage(Language lang) {
|
||||
current = lang;
|
||||
requireUpdate.forEach(TranslatableString::trigger);
|
||||
GExtensionStoreController.reloadPage();
|
||||
Cacher.put(LANGUAGE_CACHE_KEY, current.toString());
|
||||
}
|
||||
|
||||
public static Language getLanguage() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public static String get(String key) {
|
||||
return current.messages.getString(key);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package gearth.ui.translations;
|
||||
|
||||
import gearth.misc.StringifyAble;
|
||||
import javafx.beans.value.ObservableValueBase;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TranslatableString extends ObservableValueBase<String> implements StringifyAble {
|
||||
private String format;
|
||||
private String[] keys;
|
||||
|
||||
public TranslatableString(String format, String... keys) {
|
||||
this.format = format;
|
||||
this.keys = keys;
|
||||
this.fireValueChangedEvent();
|
||||
LanguageBundle.addTranslatableString(this);
|
||||
}
|
||||
|
||||
private TranslatableString(String fromString) {
|
||||
constructFromString(fromString);
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return String.format(format, Arrays.stream(keys).map(LanguageBundle::get).toArray());
|
||||
}
|
||||
|
||||
public void setKey(int index, String key) {
|
||||
keys[index] = key;
|
||||
fireValueChangedEvent();
|
||||
}
|
||||
|
||||
public void setKeys(String... keys) {
|
||||
this.keys = keys;
|
||||
fireValueChangedEvent();
|
||||
}
|
||||
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
fireValueChangedEvent();
|
||||
}
|
||||
|
||||
public void setFormatAndKeys(String format, String... keys) {
|
||||
this.format = format;
|
||||
this.keys = keys;
|
||||
fireValueChangedEvent();
|
||||
}
|
||||
|
||||
protected void trigger() {
|
||||
fireValueChangedEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String stringify() {
|
||||
return new JSONObject()
|
||||
.put("format", format)
|
||||
.put("keys", keys)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void constructFromString(String str) {
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
this.format = jsonObject.getString("format");
|
||||
this.keys = jsonObject.getJSONArray("keys")
|
||||
.toList().stream()
|
||||
.map(k -> (String) k)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public static TranslatableString fromString(String str) {
|
||||
return new TranslatableString(str);
|
||||
}
|
||||
}
|
@ -5,19 +5,19 @@
|
||||
<?import javafx.scene.input.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<BorderPane fx:id="borderPane" prefHeight="560.0" prefWidth="820.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.services.internal_extensions.uilogger.UiLoggerController">
|
||||
<BorderPane fx:id="borderPane" prefHeight="560.0" prefWidth="820.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.services.internal_extensions.uilogger.UiLoggerController">
|
||||
<top>
|
||||
<MenuBar BorderPane.alignment="CENTER">
|
||||
<Menu mnemonicParsing="false" text="Window">
|
||||
<Menu fx:id="menu_window" mnemonicParsing="false" text="Window">
|
||||
<items>
|
||||
<CheckMenuItem fx:id="chkAlwaysOnTop" mnemonicParsing="false" onAction="#toggleAlwaysOnTop" text="Always on top" />
|
||||
<Menu mnemonicParsing="false" text="On connect">
|
||||
<Menu fx:id="menu_window_onConnect" mnemonicParsing="false" text="On connect">
|
||||
<items>
|
||||
<CheckMenuItem fx:id="chkOpenOnConnect" mnemonicParsing="false" text="Open window" />
|
||||
<CheckMenuItem fx:id="chkResetOnConnect" mnemonicParsing="false" selected="true" text="Reset packetlogger" />
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="On disconnect">
|
||||
<Menu fx:id="menu_window_onDisconnect" mnemonicParsing="false" text="On disconnect">
|
||||
<items>
|
||||
<CheckMenuItem fx:id="chkHideOnDisconnect" mnemonicParsing="false" selected="true" text="Hide window" />
|
||||
<CheckMenuItem fx:id="chkResetOnDisconnect" mnemonicParsing="false" text="Reset packetlogger" />
|
||||
@ -25,12 +25,12 @@
|
||||
</Menu>
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="View">
|
||||
<CheckMenuItem fx:id="chkViewIncoming" mnemonicParsing="false" text="View Incoming">
|
||||
<Menu fx:id="menu_view" mnemonicParsing="false" text="View">
|
||||
<CheckMenuItem fx:id="chkViewIncoming" mnemonicParsing="false" text="View incoming">
|
||||
<accelerator>
|
||||
<KeyCodeCombination alt="UP" code="I" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
|
||||
</accelerator></CheckMenuItem>
|
||||
<CheckMenuItem fx:id="chkViewOutgoing" mnemonicParsing="false" selected="true" text="View Outgoing">
|
||||
<CheckMenuItem fx:id="chkViewOutgoing" mnemonicParsing="false" selected="true" text="View outgoing">
|
||||
<accelerator>
|
||||
<KeyCodeCombination alt="UP" code="O" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
|
||||
</accelerator></CheckMenuItem>
|
||||
@ -38,16 +38,16 @@
|
||||
<accelerator>
|
||||
<KeyCodeCombination alt="UP" code="L" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
|
||||
</accelerator></CheckMenuItem>
|
||||
<MenuItem mnemonicParsing="false" onAction="#clearText" text="Clear text">
|
||||
<MenuItem fx:id="menuItem_clear" mnemonicParsing="false" onAction="#clearText" text="Clear text">
|
||||
<accelerator>
|
||||
<KeyCodeCombination alt="UP" code="E" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
|
||||
</accelerator></MenuItem>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Packets">
|
||||
<Menu fx:id="menu_packets" mnemonicParsing="false" text="Packets">
|
||||
<items>
|
||||
<Menu mnemonicParsing="false" text="Display Details">
|
||||
<Menu fx:id="menu_packets_details" mnemonicParsing="false" text="Display details">
|
||||
<items>
|
||||
<Menu mnemonicParsing="false" text="Byte representation">
|
||||
<Menu fx:id="menu_packets_details_byteRep" mnemonicParsing="false" text="Byte representation">
|
||||
<items>
|
||||
<RadioMenuItem fx:id="chkReprLegacy" mnemonicParsing="false" selected="true" text="Legacy">
|
||||
<toggleGroup>
|
||||
@ -59,7 +59,7 @@
|
||||
<RadioMenuItem fx:id="chkReprNone" mnemonicParsing="false" text="None" toggleGroup="$byterepr" />
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Message">
|
||||
<Menu fx:id="menu_packets_details_message" mnemonicParsing="false" text="Message">
|
||||
<items>
|
||||
<CheckMenuItem fx:id="chkMessageName" mnemonicParsing="false" selected="true" text="Name" />
|
||||
<CheckMenuItem fx:id="chkMessageHash" mnemonicParsing="false" text="Hash" />
|
||||
@ -70,7 +70,7 @@
|
||||
<CheckMenuItem fx:id="chkTimestamp" mnemonicParsing="false" text="Timestamp" />
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Anti-spam filter">
|
||||
<Menu fx:id="menu_packets_antiSpam" mnemonicParsing="false" text="Anti-spam filter">
|
||||
<items>
|
||||
<RadioMenuItem fx:id="chkAntiSpam_none" mnemonicParsing="false" selected="true" text="None">
|
||||
<toggleGroup>
|
||||
@ -84,7 +84,7 @@
|
||||
</items>
|
||||
</Menu>
|
||||
<CheckMenuItem fx:id="chkSkipBigPackets" mnemonicParsing="false" selected="true" text="Skip big packets" />
|
||||
<MenuItem mnemonicParsing="false" onAction="#exportAll" text="Export all" />
|
||||
<MenuItem fx:id="menuItem_exportAll" mnemonicParsing="false" onAction="#exportAll" text="Export all" />
|
||||
</items>
|
||||
</Menu>
|
||||
</MenuBar>
|
||||
@ -94,7 +94,7 @@
|
||||
<padding>
|
||||
<Insets left="10.0" top="1.0" />
|
||||
</padding>
|
||||
<Label fx:id="lblViewIncoming" style="-fx-text-fill: black !important" text="View Incoming: True">
|
||||
<Label fx:id="lblViewIncoming" style="-fx-text-fill: black !important" text="View incoming: True">
|
||||
<FlowPane.margin>
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin>
|
||||
@ -104,7 +104,7 @@
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin>
|
||||
</Label>
|
||||
<Label fx:id="lblViewOutgoing" style="-fx-text-fill: black !important" text="View Outgoing: True">
|
||||
<Label fx:id="lblViewOutgoing" style="-fx-text-fill: black !important" text="View outgoing: True">
|
||||
<FlowPane.margin>
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin>
|
||||
@ -114,7 +114,7 @@
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin>
|
||||
</Label>
|
||||
<Label fx:id="lblAutoScrolll" layoutX="151.0" layoutY="11.0" style="-fx-text-fill: black !important" text="Autoscroll: True">
|
||||
<Label fx:id="lblAutoScroll" layoutX="151.0" layoutY="11.0" style="-fx-text-fill: black !important" text="Autoscroll: True">
|
||||
<FlowPane.margin>
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin></Label>
|
||||
@ -123,7 +123,7 @@
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin>
|
||||
</Label>
|
||||
<Label fx:id="lblPacketInfo" layoutX="283.0" layoutY="11.0" style="-fx-text-fill: black !important" text="Packet info: False">
|
||||
<Label fx:id="lblPacketInfo" layoutX="283.0" layoutY="11.0" style="-fx-text-fill: black !important" text="Packetinfo: False">
|
||||
<FlowPane.margin>
|
||||
<Insets right="10.0" />
|
||||
</FlowPane.margin>
|
||||
|
@ -5,30 +5,31 @@
|
||||
|
||||
<!--maxHeight="19.0" minHeight="19.0"-->
|
||||
|
||||
<VBox prefWidth="650.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.GEarthController">
|
||||
<VBox prefWidth="650.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.GEarthController">
|
||||
<TabPane id="main-tab-pane" fx:id="tabBar" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="295.0" prefWidth="650.0" tabClosingPolicy="UNAVAILABLE">
|
||||
<Tab text="Connection">
|
||||
<Tab fx:id="tab_Connection" text="Connection">
|
||||
<text>tab.connection</text>
|
||||
<fx:include fx:id="connection" source="subforms/connection/Connection.fxml" />
|
||||
</Tab>
|
||||
<Tab fx:id="tab_Logger" text="Logger">
|
||||
<fx:include fx:id="logger" source="subforms/logger/Logger.fxml" />
|
||||
</Tab>
|
||||
<Tab text="Injection">
|
||||
<Tab fx:id="tab_Injection" text="Injection">
|
||||
<fx:include fx:id="injection" source="subforms/injection/Injection.fxml" />
|
||||
</Tab>
|
||||
<Tab text="Tools">
|
||||
<Tab fx:id="tab_Tools" text="Tools">
|
||||
<fx:include fx:id="tools" source="subforms/tools/Tools.fxml" />
|
||||
</Tab>
|
||||
<Tab text="Scheduler">
|
||||
<Tab fx:id="tab_Scheduler" text="Scheduler">
|
||||
<fx:include fx:id="scheduler" source="subforms/scheduler/Scheduler.fxml" />
|
||||
</Tab>
|
||||
<Tab text="Extensions">
|
||||
<Tab fx:id="tab_Extensions" text="Extensions">
|
||||
<fx:include fx:id="extensions" source="subforms/extensions/Extensions.fxml" />
|
||||
</Tab>
|
||||
<Tab text="Extra">
|
||||
<Tab fx:id="tab_Extra" text="Extra">
|
||||
<fx:include fx:id="extra" source="subforms/extra/Extra.fxml" />
|
||||
</Tab>
|
||||
<Tab text="Info">
|
||||
<Tab fx:id="tab_Info" text="Info">
|
||||
<fx:include fx:id="info" source="subforms/info/Info.fxml" />
|
||||
</Tab>
|
||||
</TabPane>
|
||||
|
@ -53,7 +53,7 @@
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<Label text="Port:" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
|
||||
<Label fx:id="lblInpPort" text="Port:" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
|
||||
<ComboBox fx:id="inpPort" disable="true" editable="true" prefWidth="183.0" GridPane.columnIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets right="15.0" />
|
||||
@ -68,7 +68,7 @@
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<Label text="Host:" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
|
||||
<Label fx:id="lblInpHost" text="Host:" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
|
||||
<ComboBox fx:id="inpHost" disable="true" editable="true" GridPane.columnIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets right="15.0" />
|
||||
@ -86,13 +86,10 @@
|
||||
<RowConstraints />
|
||||
<RowConstraints />
|
||||
</rowConstraints>
|
||||
<CheckBox fx:id="cbx_autodetect" mnemonicParsing="false" selected="true" text="Auto-detect" textFill="#000000a9" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||
<CheckBox fx:id="cbx_autodetect" mnemonicParsing="false" selected="true" text="Auto-detect" textAlignment="CENTER" textFill="#000000a9" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||
<GridPane.margin>
|
||||
<Insets left="-5.0" />
|
||||
</GridPane.margin>
|
||||
<font>
|
||||
<Font size="12.0" />
|
||||
</font>
|
||||
</CheckBox>
|
||||
<Button fx:id="btnConnect" alignment="CENTER" maxWidth="1.7976931348623157E308" onAction="#btnConnect_clicked" text="Connect" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER">
|
||||
<GridPane.margin>
|
||||
@ -122,7 +119,7 @@
|
||||
<RowConstraints maxHeight="86.0" minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="Hotel version:" textFill="#000000cc" />
|
||||
<Label fx:id="lblHotelVersion" text="Hotel version:" textFill="#000000cc" />
|
||||
<TextField fx:id="txtfield_hotelversion" editable="false" prefHeight="17.0" prefWidth="285.0" GridPane.rowIndex="1">
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
@ -148,10 +145,13 @@
|
||||
<RowConstraints minHeight="20.0" prefHeight="34.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Client type:" textFill="#000000cd">
|
||||
<Label fx:id="lblClient" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="22.0" prefWidth="94.0" text="Client type:" textFill="#000000cd">
|
||||
<padding>
|
||||
<Insets left="12.0" />
|
||||
<Insets left="8.0" />
|
||||
</padding>
|
||||
<GridPane.margin>
|
||||
<Insets right="-20.0" />
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
<RadioButton fx:id="rd_unity" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Unity" GridPane.columnIndex="3" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
|
||||
<toggleGroup>
|
||||
@ -205,7 +205,7 @@
|
||||
<GridPane.margin>
|
||||
<Insets bottom="14.0" left="20.0" right="20.0" />
|
||||
</GridPane.margin>
|
||||
<Label text="Connection state:" textFill="#000000ba" GridPane.valignment="BOTTOM">
|
||||
<Label fx:id="lblStateHead" text="Connection state:" textFill="#000000ba" GridPane.valignment="BOTTOM">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="5.0" left="2.0" />
|
||||
</GridPane.margin>
|
||||
@ -234,12 +234,12 @@
|
||||
<padding>
|
||||
<Insets bottom="7.0" top="7.0" />
|
||||
</padding>
|
||||
<Label text="Game host:" textFill="#000000cc">
|
||||
<Label fx:id="lblHost" text="Host:" textFill="#000000cc">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0" />
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
<Label text="Port:" textFill="#000000cc" GridPane.rowIndex="1">
|
||||
<Label fx:id="lblPort" text="Port:" textFill="#000000cc" GridPane.rowIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0" />
|
||||
</GridPane.margin>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extensions.ExtensionsController">
|
||||
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extensions.ExtensionsController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="277.0" />
|
||||
</columnConstraints>
|
||||
@ -31,11 +31,11 @@
|
||||
<VBox.margin>
|
||||
<Insets bottom="-2.0" left="-2.0" right="-2.0" top="-2.0" />
|
||||
</VBox.margin>
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Title" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Description" GridPane.columnIndex="1" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Author" GridPane.columnIndex="2" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Version" GridPane.columnIndex="3" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Edit" GridPane.columnIndex="4" />
|
||||
<Label fx:id="lbl_tableTitle" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Title" />
|
||||
<Label fx:id="lbl_tableDesc" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Description" GridPane.columnIndex="1" />
|
||||
<Label fx:id="lbl_tableAuthor" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Author" GridPane.columnIndex="2" />
|
||||
<Label fx:id="lbl_tableVersion" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Version" GridPane.columnIndex="3" />
|
||||
<Label fx:id="lbl_tableEdit" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Edit" GridPane.columnIndex="4" />
|
||||
</GridPane>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
@ -61,18 +61,18 @@
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<Button fx:id="btn_gpython" disable="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#gpythonBtnClicked" text="G-Python shell" GridPane.columnIndex="3" />
|
||||
<Button fx:id="btn_gpython" disable="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#gpythonBtnClicked" text="G-Python Shell" GridPane.columnIndex="3" />
|
||||
<TextField fx:id="ext_port" editable="false" prefHeight="26.0" prefWidth="157.0" GridPane.columnIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets left="-7.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Port:" textFill="#000000bb">
|
||||
<Label fx:id="lbl_port" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Port:" textFill="#000000bb">
|
||||
<GridPane.margin>
|
||||
<Insets left="3.0" />
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
<Button fx:id="btn_install" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#installBtnClicked" text="Install" GridPane.columnIndex="5" />
|
||||
<Button fx:id="btn_install" disable="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#installBtnClicked" text="Install" GridPane.columnIndex="5" />
|
||||
<Button fx:id="btn_viewExtensionConsole" layoutX="401.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#extConsoleBtnClicked" text="View logs" GridPane.columnIndex="4" />
|
||||
</GridPane>
|
||||
</GridPane>
|
||||
|
@ -3,8 +3,9 @@
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
|
||||
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extra.ExtraController">
|
||||
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.extra.ExtraController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="331.0" minWidth="10.0" prefWidth="318.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="390.0" minWidth="10.0" prefWidth="247.0" />
|
||||
@ -16,8 +17,8 @@
|
||||
<AnchorPane prefHeight="262.0" prefWidth="318.0">
|
||||
<children>
|
||||
<CheckBox fx:id="cbx_alwaysOnTop" layoutX="14.0" layoutY="232.0" mnemonicParsing="false" text="Always on top" />
|
||||
<Hyperlink fx:id="url_troubleshooting" layoutX="223.0" layoutY="232.0" text="Troubleshooting" />
|
||||
<Label layoutX="14.0" layoutY="8.0" text="Notepad:" textFill="#000000bd" />
|
||||
<Hyperlink fx:id="url_troubleshooting" alignment="CENTER_RIGHT" layoutX="170.0" layoutY="232.0" prefWidth="150.0" text="Troubleshooting" />
|
||||
<Label fx:id="lbl_notepad" layoutX="14.0" layoutY="8.0" text="Notepad:" textFill="#000000bd" />
|
||||
<TextArea fx:id="txtarea_notepad" layoutX="11.0" layoutY="31.0" prefHeight="197.0" prefWidth="312.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
@ -29,17 +30,18 @@
|
||||
<RowConstraints maxHeight="7.0" minHeight="7.0" prefHeight="7.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" prefHeight="232.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<GridPane fx:id="grd_advanced" disable="true" prefHeight="161.0" prefWidth="299.0" style="-fx-border-color: #888888; -fx-border-radius: 5px;" GridPane.rowIndex="4">
|
||||
<GridPane fx:id="grd_advanced" disable="true" prefHeight="161.0" prefWidth="299.0" style="-fx-border-color: #888888; -fx-border-radius: 5px;" GridPane.rowIndex="5">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="60.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="29.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="28.0" minHeight="28.0" prefHeight="28.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
@ -58,7 +60,7 @@
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints maxHeight="60.0" minHeight="60.0" prefHeight="60.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="29.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<GridPane.margin>
|
||||
<Insets />
|
||||
@ -69,25 +71,15 @@
|
||||
<children>
|
||||
<GridPane prefHeight="119.0" prefWidth="259.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="159.0" minWidth="10.0" prefWidth="68.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="193.0" minWidth="10.0" prefWidth="145.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="159.0" minWidth="10.0" prefWidth="72.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="240.0" minWidth="10.0" prefWidth="213.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints maxHeight="2.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="29.0" minHeight="29.0" prefHeight="29.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="Proxy IP:" />
|
||||
<Label text="Proxy port:" GridPane.rowIndex="1" />
|
||||
<TextField fx:id="txt_socksPort" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
<GridPane.margin>
|
||||
<Insets bottom="3.0" right="7.0" top="3.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<TextField fx:id="txt_socksIp" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1">
|
||||
<Label fx:id="lbl_proxyIp" text="Proxy IP:" />
|
||||
<TextField fx:id="txt_socksIp" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" promptText="ip:port" GridPane.columnIndex="1">
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
@ -104,7 +96,7 @@
|
||||
</GridPane>
|
||||
</children>
|
||||
</GridPane>
|
||||
<CheckBox fx:id="cbx_advanced" mnemonicParsing="false" text="Advanced" textFill="#000000ba" GridPane.rowIndex="3">
|
||||
<CheckBox fx:id="cbx_advanced" mnemonicParsing="false" text="Advanced" textFill="#000000ba" GridPane.rowIndex="4">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0" top="2.0" />
|
||||
</GridPane.margin>
|
||||
@ -112,16 +104,24 @@
|
||||
<Insets top="2.0" />
|
||||
</padding>
|
||||
</CheckBox>
|
||||
<CheckBox fx:id="cbx_gpython" mnemonicParsing="false" onAction="#gpythonCbxClick" text="G-Python scripting" textFill="#000000ba" GridPane.rowIndex="2">
|
||||
<CheckBox fx:id="cbx_gpython" mnemonicParsing="false" onAction="#gpythonCbxClick" text="G-Python scripting" textFill="#000000ba" GridPane.rowIndex="3">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0" top="2.0" />
|
||||
</GridPane.margin>
|
||||
</CheckBox>
|
||||
<CheckBox fx:id="cbx_admin" layoutX="20.0" layoutY="25.0" mnemonicParsing="false" onAction="#adminCbxClick" selected="true" text="Client-side staff permissions" textFill="#000000ba" GridPane.rowIndex="1">
|
||||
<CheckBox fx:id="cbx_admin" layoutX="20.0" layoutY="25.0" mnemonicParsing="false" onAction="#adminCbxClick" selected="true" text="Client-side staff permissions" textFill="#000000ba" GridPane.rowIndex="2">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0" top="2.0" />
|
||||
</GridPane.margin>
|
||||
</CheckBox>
|
||||
<CheckBox fx:id="cbx_develop" layoutX="20.0" layoutY="52.0" mnemonicParsing="false" onAction="#developCbxClick" text="Developer mode" textFill="#000000ba" GridPane.rowIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0" />
|
||||
</GridPane.margin>
|
||||
<font>
|
||||
<Font name="System Bold" size="12.0" />
|
||||
</font>
|
||||
</CheckBox>
|
||||
</children>
|
||||
<GridPane.margin>
|
||||
<Insets />
|
||||
|
@ -5,7 +5,7 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
|
||||
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.info.InfoController">
|
||||
<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.info.InfoController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="332.0" />
|
||||
</columnConstraints>
|
||||
@ -20,22 +20,22 @@
|
||||
<Font size="18.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="260.0" layoutY="57.0" text="Habbo packet manipulator for Linux, Windows & Mac" textFill="#000000b2">
|
||||
<Label fx:id="lbl_description" layoutX="260.0" layoutY="57.0" text="Habbo packet manipulator for Linux, Windows & Mac" textFill="#000000b2">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="260.0" layoutY="92.0" text="Created by:" textFill="#000000b2">
|
||||
<Label fx:id="lbl_createdBy" layoutX="260.0" layoutY="92.0" text="Created by:" textFill="#000000b2">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="344.0" layoutY="92.0" text="sirjonasxx" textFill="#000000b2">
|
||||
<Label layoutX="363.0" layoutY="92.0" text="sirjonasxx" textFill="#000000b2">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="260.0" layoutY="124.0" text="Contributors:" textFill="#000000b2">
|
||||
<Label fx:id="lbl_contrib" layoutX="260.0" layoutY="124.0" text="Contributors:" textFill="#000000b2">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font>
|
||||
@ -75,7 +75,7 @@
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="491.0" layoutY="92.0" text="Links:" textFill="#000000b2">
|
||||
<Label fx:id="lbl_links" layoutX="491.0" layoutY="92.0" text="Links:" textFill="#000000b2">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font>
|
||||
@ -85,8 +85,13 @@
|
||||
<Hyperlink fx:id="link_g_tanji" layoutX="487.0" layoutY="152.0" text="Github - Tanji" />
|
||||
<Hyperlink fx:id="link_d_gearth" layoutX="487.0" layoutY="172.0" text="Discord - G-Earth" />
|
||||
<Hyperlink fx:id="link_g_store" layoutX="487.0" layoutY="212.0" text="G-ExtensionStore" />
|
||||
<Button layoutX="537.0" layoutY="14.0" mnemonicParsing="false" onAction="#donate" prefHeight="20.0" prefWidth="100.0" text="Donate BTC" />
|
||||
<Button fx:id="btn_donate" layoutX="537.0" layoutY="14.0" mnemonicParsing="false" onAction="#donate" prefHeight="20.0" prefWidth="100.0" text="Donate BTC" />
|
||||
<Hyperlink fx:id="link_t_gearth" layoutX="487.0" layoutY="192.0" text="Twitter - G-Earth" />
|
||||
<Label layoutX="363.0" layoutY="204.0" text="Dorving" textFill="#000000b2">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<!--<Hyperlink fx:id="link_d_bonnie" layoutX="400.0" layoutY="221.0" text="Discord - BonnieScripting (pt/br)" />-->
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
|
||||
<GridPane prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.injection.InjectionController">
|
||||
<GridPane prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.injection.InjectionController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="180.0" minWidth="180.0" prefWidth="180.0" />
|
||||
@ -37,12 +37,12 @@
|
||||
<GridPane.margin>
|
||||
<Insets left="13.0" right="13.0" top="4.0" />
|
||||
</GridPane.margin>
|
||||
<Text fx:id="lbl_corrruption" styleClass="corrupted-label" strokeType="OUTSIDE" strokeWidth="0.0" text="isCorrupted: True">
|
||||
<Text fx:id="lbl_corruption" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="corrupted-label" text="isCorrupted: True">
|
||||
<font>
|
||||
<Font name="System Italic" size="11.0" />
|
||||
</font>
|
||||
</Text>
|
||||
<Text fx:id="lbl_pcktInfo" fill="#000000b2" styleClass="pckt-info" nodeOrientation="LEFT_TO_RIGHT" strokeType="OUTSIDE" strokeWidth="0.0" text="header (id:NULL, length:0)" GridPane.columnIndex="1" GridPane.halignment="RIGHT">
|
||||
<Text fx:id="lbl_pcktInfo" fill="#000000b2" nodeOrientation="LEFT_TO_RIGHT" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="pckt-info" text="header (id=NULL, length=0)" GridPane.columnIndex="1" GridPane.halignment="RIGHT">
|
||||
<font>
|
||||
<Font name="System Italic" size="11.0" />
|
||||
</font>
|
||||
@ -92,7 +92,7 @@
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Hyperlink alignment="CENTER_RIGHT" onAction="#clearHistoryClick" text="Clear" GridPane.columnIndex="1">
|
||||
<Hyperlink fx:id="lnk_clearHistory" alignment="CENTER_RIGHT" onAction="#clearHistoryClick" text="Clear" GridPane.columnIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets right="6.0" />
|
||||
</GridPane.margin></Hyperlink>
|
||||
|
@ -31,11 +31,11 @@
|
||||
<VBox.margin>
|
||||
<Insets bottom="-2.0" left="-2.0" right="-2.0" top="-2.0" />
|
||||
</VBox.margin>
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Index" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Packet" GridPane.columnIndex="1" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Interval" GridPane.columnIndex="2" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Destination" GridPane.columnIndex="3" />
|
||||
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Edit" GridPane.columnIndex="4" />
|
||||
<Label fx:id="lbl_tableIndex" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Index" />
|
||||
<Label fx:id="lbl_tablePacket" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Packet" GridPane.columnIndex="1" />
|
||||
<Label fx:id="lbl_tableInterval" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Interval" GridPane.columnIndex="2" />
|
||||
<Label fx:id="lbl_tableDest" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Destination" GridPane.columnIndex="3" />
|
||||
<Label fx:id="lbl_tableEdit" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-text-fill: #666666; -fx-background-color: #F7F7F7;" text="Edit" GridPane.columnIndex="4" />
|
||||
</GridPane>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
@ -67,7 +67,7 @@
|
||||
<Insets left="-7.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Packet:" textFill="#000000bb">
|
||||
<Label fx:id="lbl_setupPacket" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Packet:" textFill="#000000bb">
|
||||
<GridPane.margin>
|
||||
<Insets left="3.0" />
|
||||
</GridPane.margin>
|
||||
@ -96,7 +96,7 @@
|
||||
<Insets right="5.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Interval:" textFill="#000000bb">
|
||||
<Label fx:id="lbl_setupInterval" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="Interval:" textFill="#000000bb">
|
||||
<GridPane.margin>
|
||||
<Insets left="3.0" />
|
||||
</GridPane.margin>
|
||||
|
@ -46,7 +46,7 @@
|
||||
<GridPane.margin>
|
||||
<Insets/>
|
||||
</GridPane.margin>
|
||||
<Label alignment="CENTER" text="Integer:">
|
||||
<Label fx:id="lbl_integer" alignment="CENTER" text="Integer:">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0"/>
|
||||
</GridPane.margin>
|
||||
@ -80,7 +80,7 @@
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
|
||||
<Label alignment="CENTER" text="Ushort:" GridPane.rowIndex="1">
|
||||
<Label fx:id="lbl_uShort" alignment="CENTER" text="Ushort:" GridPane.rowIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets left="10.0"/>
|
||||
</GridPane.margin>
|
||||
@ -116,7 +116,7 @@
|
||||
</TextField>
|
||||
|
||||
</GridPane>
|
||||
<Label text="Encoding/decoding" textFill="#000000cc">
|
||||
<Label fx:id="lbl_encodingDecoding" text="Encoding/Decoding" textFill="#000000cc">
|
||||
<GridPane.margin>
|
||||
<Insets left="6.0" top="5.0"/>
|
||||
</GridPane.margin>
|
||||
@ -179,7 +179,7 @@
|
||||
</GridPane.margin>
|
||||
</TextArea>
|
||||
</GridPane>
|
||||
<Label text="Packet <-> expression" textFill="#000000cc">
|
||||
<Label fx:id="lbl_packetToExpression" text="Packet <-> Expression" textFill="#000000cc">
|
||||
<GridPane.margin>
|
||||
<Insets left="6.0" top="2.0"/>
|
||||
</GridPane.margin>
|
||||
|
BIN
G-Earth/src/main/resources/gearth/ui/themes/G-Earth/lang/DE.png
Normal file
BIN
G-Earth/src/main/resources/gearth/ui/themes/G-Earth/lang/DE.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
BIN
G-Earth/src/main/resources/gearth/ui/themes/G-Earth/lang/EN.png
Normal file
BIN
G-Earth/src/main/resources/gearth/ui/themes/G-Earth/lang/EN.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
G-Earth/src/main/resources/gearth/ui/themes/G-Earth/lang/ES.png
Normal file
BIN
G-Earth/src/main/resources/gearth/ui/themes/G-Earth/lang/ES.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user