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:
dorving 2023-03-01 10:07:47 +01:00
commit cee78fd0f5
139 changed files with 6400 additions and 1554 deletions

View File

@ -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>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.0-alpha0</version>
<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>

View File

@ -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);

View File

@ -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());

View File

@ -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) {

View File

@ -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);
}

View 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;
}
}

View File

@ -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);

View File

@ -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 +
'}';
}
}

View File

@ -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) {

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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);
}
});
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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()) {

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -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;

View File

@ -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();

View File

@ -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,56 +48,32 @@ 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();
final ClientUpgradeRequest request = new ClientUpgradeRequest();
clientHeaders.forEach((key, value) -> {
if (SKIP_HEADERS.contains(key)) {
return;
}
request.setHeader(key, value);
});
}
ClientEndpointConfig config = builder.build();
logger.info("Connecting to origin websocket at {}", websocketUrl);
ContainerProvider.getWebSocketContainer().connectToServer(this, config, URI.create(websocketUrl));
} catch (DeploymentException e) {
throw new IOException("Failed to deploy websocket client", e);
}
}
client.start();
client.connect(this, URI.create(websocketUrl), request);
@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();
logger.info("Connected to origin websocket");
} catch (Exception e) {
throw new IOException("Failed to start websocket client to origin " + websocketUrl, e);
}
}
});
}
@Override
public void onClose(Session session, CloseReason closeReason) {
// Hotel closed connection.
client.shutdownProxy();
}
@Override
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
// Shutdown.
client.shutdownProxy();
}
@Override
public Session getSession() {
@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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())));
}
}

View File

@ -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) {}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -1,102 +1,105 @@
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) {
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)
@ -104,104 +107,92 @@ public class NormalExtensionRunner implements ExtensionRunner {
.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();
}
}

View File

@ -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 -> {

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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>")

View File

@ -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>")

View File

@ -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");
}
};
}

View File

@ -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>")

View File

@ -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

View File

@ -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");
}
};
}

View File

@ -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>")

View File

@ -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");
}
};
}

View File

@ -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");
}
};
}

View File

@ -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");
}
};
}

View File

@ -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();
}
};
}

View File

@ -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");
}
};
}

View File

@ -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>");

View File

@ -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");
}
};
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -7,6 +7,8 @@ package gearth.services.internal_extensions.uilogger;
import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
import gearth.ui.subforms.logger.loggerdisplays.PacketLogger;
import gearth.ui.translations.LanguageBundle;
import gearth.ui.translations.TranslatableString;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.Initializable;
@ -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);
}
}

View File

@ -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;

View File

@ -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"));
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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"));
}
}

View File

@ -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);

View File

@ -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,16 +127,21 @@ 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() {
if (txt_socksIp.getText().contains(":")) {
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());
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() {
if (!cbx_advanced.isSelected()) {
@ -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"));
}
}

View File

@ -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"));
}
}

View File

@ -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());
}
}

View File

@ -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 {
lbl_pcktInfo.setText("");
pcktInfo.setFormatAndKeys("%s (%s: " + packet.headerId() + ", %s: " + packet.length() + ")",
"tab.injection.description.header",
"tab.injection.description.id",
"tab.injection.description.length");
}
}
else {
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());

View File

@ -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")
);
}
}

View File

@ -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"));
}
}

View File

@ -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"));
}
}

View File

@ -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();
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
@ -30,16 +31,17 @@
<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 />

View File

@ -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 &amp; Mac" textFill="#000000b2">
<Label fx:id="lbl_description" layoutX="260.0" layoutY="57.0" text="Habbo packet manipulator for Linux, Windows &amp; 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>

View File

@ -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>

View File

@ -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>

View File

@ -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 &lt;-&gt; expression" textFill="#000000cc">
<Label fx:id="lbl_packetToExpression" text="Packet &lt;-&gt; Expression" textFill="#000000cc">
<GridPane.margin>
<Insets left="6.0" top="2.0"/>
</GridPane.margin>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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