From ab263a6cfd0d1a451b0d6e21bb51ec4720dff6ad Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 21 Jun 2024 07:09:32 +0200 Subject: [PATCH] Shockwave progress --- .../java/gearth/encoding/Base64Encoding.java | 38 +++++ .../java/gearth/encoding/VL64Encoding.java | 63 ++++++++ .../java/gearth/protocol/HConnection.java | 10 +- .../java/gearth/protocol/TrafficListener.java | 4 + .../gearth/protocol/connection/HClient.java | 3 +- .../proxy/ProxyProviderFactory.java | 24 +-- .../connection/proxy/flash/FlashProxy.java | 53 +++++++ .../proxy/shockwave/ShockwaveProxy.java | 142 ++++++++++++++++++ .../format/shockwave/ShockMessage.java | 35 +++++ .../format/shockwave/ShockPacket.java | 15 ++ .../format/shockwave/ShockPacketIn.java | 4 + .../format/shockwave/ShockPacketOut.java | 4 + .../ConnectionInterceptor.java} | 111 +++++++------- .../ConnectionInterceptorCallbacks.java | 14 ++ .../protocol/packethandler/PacketHandler.java | 20 +++ .../shockwave/ShockwavePacketHandler.java | 65 ++++++++ .../ShockwavePacketIncomingHandler.java | 13 ++ .../ShockwavePacketOutgoingHandler.java | 13 ++ .../shockwave/buffers/ShockwaveBuffer.java | 11 ++ .../shockwave/buffers/ShockwaveInBuffer.java | 42 ++++++ .../shockwave/buffers/ShockwaveOutBuffer.java | 28 ++++ .../extension_handler/ExtensionHandler.java | 21 ++- .../packet_info/PacketInfoManager.java | 4 +- .../SulekPacketInfoProvider.java | 15 +- .../connection/ConnectionController.java | 23 ++- .../ui/subforms/connection/Connection.fxml | 21 ++- .../ui/translations/messages_en.properties | 3 +- 27 files changed, 705 insertions(+), 94 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/encoding/Base64Encoding.java create mode 100644 G-Earth/src/main/java/gearth/encoding/VL64Encoding.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxy.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java create mode 100644 G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockMessage.java create mode 100644 G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacket.java create mode 100644 G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketIn.java create mode 100644 G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketOut.java rename G-Earth/src/main/java/gearth/protocol/{connection/proxy/flash/NormalFlashProxyProvider.java => interceptor/ConnectionInterceptor.java} (75%) create mode 100644 G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptorCallbacks.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java diff --git a/G-Earth/src/main/java/gearth/encoding/Base64Encoding.java b/G-Earth/src/main/java/gearth/encoding/Base64Encoding.java new file mode 100644 index 0000000..ba1812e --- /dev/null +++ b/G-Earth/src/main/java/gearth/encoding/Base64Encoding.java @@ -0,0 +1,38 @@ +package gearth.encoding; + +/** + * Kepler Copyright (C) 2018 Quackster + * Kepler + */ +public class Base64Encoding { + public byte NEGATIVE = 64; + public byte POSITIVE = 65; + + public static byte[] encode(int i, int numBytes) { + byte[] bzRes = new byte[numBytes]; + for (int j = 1; j <= numBytes; j++) + { + int k = ((numBytes - j) * 6); + bzRes[j - 1] = (byte)(0x40 + ((i >> k) & 0x3f)); + } + + return bzRes; + } + + public static int decode(byte[] bzData) { + int i = 0; + int j = 0; + for (int k = bzData.length - 1; k >= 0; k--) + { + int x = bzData[k] - 0x40; + if (j > 0) + x *= (int)Math.pow(64.0, (double)j); + + i += x; + j++; + } + + return i; + } +} + diff --git a/G-Earth/src/main/java/gearth/encoding/VL64Encoding.java b/G-Earth/src/main/java/gearth/encoding/VL64Encoding.java new file mode 100644 index 0000000..33d6415 --- /dev/null +++ b/G-Earth/src/main/java/gearth/encoding/VL64Encoding.java @@ -0,0 +1,63 @@ +package gearth.encoding; + +/** + * Kepler Copyright (C) 2018 Quackster + * Kepler + */ +public class VL64Encoding { + public static byte NEGATIVE = 72; + public static byte POSITIVE = 73; + public static int MAX_INTEGER_BYTE_AMOUNT = 6; + + public static byte[] encode(int i) { + byte[] wf = new byte[VL64Encoding.MAX_INTEGER_BYTE_AMOUNT]; + + int pos = 0; + int numBytes = 1; + int startPos = pos; + int negativeMask = i >= 0 ? 0 : 4; + + i = Math.abs(i); + + wf[pos++] = (byte)(64 + (i & 3)); + + for (i >>= 2; i != 0; i >>= VL64Encoding.MAX_INTEGER_BYTE_AMOUNT) + { + numBytes++; + wf[pos++] = (byte)(64 + (i & 0x3f)); + } + wf[startPos] = (byte)(wf[startPos] | numBytes << 3 | negativeMask); + + byte[] bzData = new byte[numBytes]; + + System.arraycopy(wf, 0, bzData, 0, numBytes); + return bzData; + } + + public static int decode(byte[] bzData) { + int pos = 0; + int v = 0; + + boolean negative = (bzData[pos] & 4) == 4; + int totalBytes = bzData[pos] >> 3 & 7; + + v = bzData[pos] & 3; + + pos++; + + int shiftAmount = 2; + + for (int b = 1; b < totalBytes; b++) + { + v |= (bzData[pos] & 0x3f) << shiftAmount; + shiftAmount = 2 + 6 * b; + pos++; + } + + if (negative) { + v *= -1; + } + + return v; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/HConnection.java b/G-Earth/src/main/java/gearth/protocol/HConnection.java index 495481f..fc544f5 100644 --- a/G-Earth/src/main/java/gearth/protocol/HConnection.java +++ b/G-Earth/src/main/java/gearth/protocol/HConnection.java @@ -20,7 +20,7 @@ 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; @@ -60,14 +60,14 @@ public class HConnection { } // autodetect mode - public void start() { - proxyProvider = proxyProviderFactory.provide(); + public void start(HClient client) { + proxyProvider = proxyProviderFactory.provide(client); startMITM(); } // manual input mode - public void start(String host, int port) { - proxyProvider = proxyProviderFactory.provide(host, port); + public void start(HClient client, String host, int port) { + proxyProvider = proxyProviderFactory.provide(client, host, port); startMITM(); } diff --git a/G-Earth/src/main/java/gearth/protocol/TrafficListener.java b/G-Earth/src/main/java/gearth/protocol/TrafficListener.java index da5a11c..f660f8f 100644 --- a/G-Earth/src/main/java/gearth/protocol/TrafficListener.java +++ b/G-Earth/src/main/java/gearth/protocol/TrafficListener.java @@ -1,7 +1,11 @@ package gearth.protocol; +import gearth.protocol.format.shockwave.ShockMessage; + public interface TrafficListener { void onCapture(HMessage message); + void onCapture(ShockMessage message); + } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java index 027fafd..540ebfa 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java @@ -3,5 +3,6 @@ package gearth.protocol.connection; public enum HClient { UNITY, FLASH, - NITRO + NITRO, + SHOCKWAVE } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java index acd04a7..21f3065 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/ProxyProviderFactory.java @@ -3,20 +3,20 @@ package gearth.protocol.connection.proxy; import gearth.misc.Cacher; import gearth.misc.OSValidator; import gearth.protocol.HConnection; +import gearth.protocol.connection.HClient; import gearth.protocol.connection.HProxySetter; import gearth.protocol.connection.HStateSetter; -import gearth.protocol.connection.proxy.flash.NormalFlashProxyProvider; +import gearth.protocol.connection.proxy.flash.FlashProxy; import gearth.protocol.connection.proxy.flash.unix.LinuxRawIpFlashProxyProvider; import gearth.protocol.connection.proxy.flash.windows.WindowsRawIpFlashProxyProvider; +import gearth.protocol.connection.proxy.shockwave.ShockwaveProxy; 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 java.io.IOException; import java.util.ArrayList; @@ -41,6 +41,8 @@ public class ProxyProviderFactory { autoDetectHosts.add("game-us.habbo.com:30000"); autoDetectHosts.add("game-s2.habbo.com:30000"); + autoDetectHosts.add("game-ous.habbo.com:40001"); + List additionalCachedHotels = Cacher.getList(HOTELS_CACHE_KEY); if (additionalCachedHotels != null) { for (Object additionalHotel : additionalCachedHotels) { @@ -83,10 +85,11 @@ public class ProxyProviderFactory { return true; } - public ProxyProvider provide() { - return provide(autoDetectHosts); + public ProxyProvider provide(HClient client) { + return provide(client, autoDetectHosts); } - public ProxyProvider provide(String domain, int port) { + + public ProxyProvider provide(HClient client, String domain, int port) { List additionalCachedHotels = Cacher.getList(HOTELS_CACHE_KEY); if (additionalCachedHotels == null) { additionalCachedHotels = new ArrayList<>(); @@ -129,11 +132,14 @@ public class ProxyProviderFactory { else { List potentialHost = new ArrayList<>(); potentialHost.add(domain+":"+port); - return provide(potentialHost); + return provide(client, potentialHost); } } - private ProxyProvider provide(List potentialHosts) { - return new NormalFlashProxyProvider(proxySetter, stateSetter, hConnection, potentialHosts, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded()); + + private ProxyProvider provide(HClient client, List potentialHosts) { + return client == HClient.FLASH + ? new FlashProxy(proxySetter, stateSetter, hConnection, potentialHosts, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded()) + : new ShockwaveProxy(proxySetter, stateSetter, hConnection, potentialHosts); } public static void setSocksConfig(SocksConfiguration configuration) { diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxy.java new file mode 100644 index 0000000..ab90d03 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/FlashProxy.java @@ -0,0 +1,53 @@ +package gearth.protocol.connection.proxy.flash; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.*; +import gearth.protocol.interceptor.ConnectionInterceptor; +import gearth.protocol.interceptor.ConnectionInterceptorCallbacks; + +import java.io.IOException; +import java.net.Socket; +import java.util.List; + +public class FlashProxy extends FlashProxyProvider implements ConnectionInterceptorCallbacks { + + private final ConnectionInterceptor interceptor; + + public FlashProxy(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, List potentialHosts, boolean useSocks) { + super(proxySetter, stateSetter, hConnection); + this.interceptor = new ConnectionInterceptor(HClient.FLASH, stateSetter, hConnection, this, potentialHosts, useSocks); + } + + @Override + public void start() throws IOException { + if (hConnection.getState() != HState.NOT_CONNECTED) { + return; + } + + interceptor.start(); + } + + @Override + public void abort() { + stateSetter.setState(HState.ABORTING); + interceptor.stop(false); + super.abort(); + } + + @Override + protected void onConnect() { + super.onConnect(); + interceptor.stop(true); + } + + @Override + public void onInterceptorConnected(Socket client, Socket server, HProxy proxy) throws IOException, InterruptedException { + startProxyThread(client, server, proxy); + } + + @Override + public void onInterceptorError() { + showInvalidConnectionError(); + abort(); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java new file mode 100644 index 0000000..d9a3001 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java @@ -0,0 +1,142 @@ +package gearth.protocol.connection.proxy.shockwave; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HClient; +import gearth.protocol.connection.HProxy; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HState; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.interceptor.ConnectionInterceptor; +import gearth.protocol.interceptor.ConnectionInterceptorCallbacks; +import gearth.protocol.packethandler.PacketHandler; +import gearth.protocol.packethandler.shockwave.ShockwavePacketIncomingHandler; +import gearth.protocol.packethandler.shockwave.ShockwavePacketOutgoingHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.Socket; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Semaphore; + +public class ShockwaveProxy implements ProxyProvider, ConnectionInterceptorCallbacks { + + private static final Logger logger = LoggerFactory.getLogger(ShockwaveProxy.class); + + private final HProxySetter hProxySetter; + private final HStateSetter hStateSetter; + private final HConnection hConnection; + private final ConnectionInterceptor interceptor; + private final Semaphore abortSemaphore; + + public ShockwaveProxy(HProxySetter hProxySetter, HStateSetter hStateSetter, HConnection hConnection, List potentialHosts) { + this.hProxySetter = hProxySetter; + this.hStateSetter = hStateSetter; + this.hConnection = hConnection; + this.interceptor = new ConnectionInterceptor(HClient.SHOCKWAVE, hStateSetter, hConnection, this, potentialHosts, false); + this.abortSemaphore = new Semaphore(0); + } + + @Override + public void start() throws IOException { + if (hConnection.getState() != HState.NOT_CONNECTED) { + return; + } + + interceptor.start(); + logger.info("Intercepting shockwave connection."); + } + + @Override + public void abort() { + logger.warn("Aborting shockwave proxy."); + hStateSetter.setState(HState.ABORTING); + interceptor.stop(false); + abortSemaphore.release(); + + // Let the stopProxyThread handle the rest of the aborting. + if (hConnection.getState() != HState.CONNECTED) { + hStateSetter.setState(HState.NOT_CONNECTED); + } + } + + @Override + public void onInterceptorConnected(Socket client, Socket server, HProxy proxy) throws IOException, InterruptedException { + logger.info("Shockwave connection has been intercepted."); + + startProxyThread(client, server, proxy); + } + + @Override + public void onInterceptorError() { + logger.error("Error occurred while intercepting shockwave connection. Aborting."); + + abort(); + } + + private void startProxyThread(Socket client, Socket server, HProxy proxy) throws IOException, InterruptedException { + server.setSoTimeout(0); + server.setTcpNoDelay(true); + + client.setSoTimeout(0); + client.setTcpNoDelay(true); + + logger.info("Connected to shockwave server {}:{}", server.getRemoteSocketAddress(), server.getPort()); + + final Semaphore abort = new Semaphore(0); + + final ShockwavePacketOutgoingHandler outgoingHandler = new ShockwavePacketOutgoingHandler(server.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables()); + final ShockwavePacketIncomingHandler incomingHandler = new ShockwavePacketIncomingHandler(client.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables()); + + // TODO: Non hardcoded version "20". Not exactly sure yet how to deal with this for now. + // Lets revisit when origins is more mature. + proxy.verifyProxy(incomingHandler, outgoingHandler, "20", "SHOCKWAVE"); + hProxySetter.setProxy(proxy); + onConnect(); + + handleInputStream(client, outgoingHandler, abort); + handleInputStream(server, incomingHandler, abort); + + // abort can be acquired as soon as one of the sockets is closed + abort.acquire(); + + try { + if (!server.isClosed()) server.close(); + if (!client.isClosed()) client.close(); + if (HConnection.DEBUG) System.out.println("STOP"); + onConnectEnd(); + } catch (IOException e) { + logger.error("Error occurred while closing sockets.", e); + } + } + + private void handleInputStream(Socket socket, PacketHandler packetHandler, Semaphore abort) { + new Thread(() -> { + try { + int readLength; + byte[] buffer = new byte[8192]; + while (!socket.isClosed() && + (hConnection.getState() == HState.WAITING_FOR_CLIENT || hConnection.getState() == HState.CONNECTED) && + (readLength = socket.getInputStream().read(buffer)) != -1) { + packetHandler.act(Arrays.copyOf(buffer, readLength)); + } + } catch (IOException ignore) { + // ignore + } finally { + abort.release(); + } + }).start(); + } + + private void onConnect() { + interceptor.stop(true); + hStateSetter.setState(HState.CONNECTED); + } + + private void onConnectEnd() { + hProxySetter.setProxy(null); + hStateSetter.setState(HState.NOT_CONNECTED); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockMessage.java b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockMessage.java new file mode 100644 index 0000000..3a18e95 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockMessage.java @@ -0,0 +1,35 @@ +package gearth.protocol.format.shockwave; + +import gearth.protocol.HMessage; + +public class ShockMessage { + + private final ShockPacket packet; + private final HMessage.Direction direction; + private final int index; + + private boolean isBlocked; + + public ShockMessage(ShockPacket packet, HMessage.Direction direction, int index) { + this.packet = packet; + this.direction = direction; + this.index = index; + this.isBlocked = false; + } + + public ShockPacket getPacket() { + return packet; + } + + public HMessage.Direction getDirection() { + return direction; + } + + public int getIndex() { + return index; + } + + public boolean isBlocked() { + return isBlocked; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacket.java b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacket.java new file mode 100644 index 0000000..41164f0 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacket.java @@ -0,0 +1,15 @@ +package gearth.protocol.format.shockwave; + +import gearth.protocol.HMessage; + +public class ShockPacket { + + public ShockPacket(HMessage.Direction direction, byte[] data) { + + } + + public void resetReadIndex() { + + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketIn.java b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketIn.java new file mode 100644 index 0000000..8f10c6f --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketIn.java @@ -0,0 +1,4 @@ +package gearth.protocol.format.shockwave; + +public class ShockPacketIn { +} diff --git a/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketOut.java b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketOut.java new file mode 100644 index 0000000..72805b5 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/format/shockwave/ShockPacketOut.java @@ -0,0 +1,4 @@ +package gearth.protocol.format.shockwave; + +public class ShockPacketOut { +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/NormalFlashProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptor.java similarity index 75% rename from G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/NormalFlashProxyProvider.java rename to G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptor.java index 49b4c6e..b986271 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/flash/NormalFlashProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptor.java @@ -1,9 +1,11 @@ -package gearth.protocol.connection.proxy.flash; +package gearth.protocol.interceptor; -import gearth.GEarth; import gearth.misc.Cacher; import gearth.protocol.HConnection; -import gearth.protocol.connection.*; +import gearth.protocol.connection.HClient; +import gearth.protocol.connection.HProxy; +import gearth.protocol.connection.HState; +import gearth.protocol.connection.HStateSetter; import gearth.protocol.connection.proxy.ProxyProviderFactory; import gearth.protocol.connection.proxy.SocksConfiguration; import gearth.protocol.hostreplacer.hostsfile.HostReplacer; @@ -14,20 +16,30 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; -import java.net.*; +import java.net.BindException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; -public class NormalFlashProxyProvider extends FlashProxyProvider { - - private List potentialHosts; - +public class ConnectionInterceptor { + private static final Logger logger = LoggerFactory.getLogger(ConnectionInterceptor.class); private static final HostReplacer hostsReplacer = HostReplacerFactory.get(); + + private final HClient hClient; + private final HStateSetter hStateSetter; + private final HConnection hConnection; + private final ConnectionInterceptorCallbacks callbacks; + private final List potentialHosts; + private volatile boolean hostRedirected = false; private volatile List potentialProxies = new ArrayList<>(); @@ -35,28 +47,31 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { private boolean useSocks; - - public NormalFlashProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, List potentialHosts, boolean useSocks) { - super(proxySetter, stateSetter, hConnection); + public ConnectionInterceptor(HClient client, HStateSetter stateSetter, HConnection hConnection, ConnectionInterceptorCallbacks callbacks, List potentialHosts, boolean useSocks) { + this.hClient = client; + this.hStateSetter = stateSetter; + this.hConnection = hConnection; + this.callbacks = callbacks; this.potentialHosts = potentialHosts; this.useSocks = useSocks; } - - @Override public void start() throws IOException { - if (hConnection.getState() != HState.NOT_CONNECTED) { - return; - } - prepare(); addToHosts(); launchProxy(); + } + public void stop(boolean forceRemoveFromHosts) { + if (forceRemoveFromHosts || hostRedirected) { + removeFromHosts(); + } + + clearAllProxies(); } private void prepare() { - stateSetter.setState(HState.PREPARING); + hStateSetter.setState(HState.PREPARING); List willremove = new ArrayList<>(); int c = 0; @@ -79,7 +94,7 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { int intercept_port = port; String intercept_host = "127.0." + (c / 254) + "." + (1 + c % 254); - potentialProxies.add(new HProxy(HClient.FLASH, input_dom, actual_dom, port, intercept_port, intercept_host)); + potentialProxies.add(new HProxy(hClient, input_dom, actual_dom, port, intercept_port, intercept_host)); c++; } } @@ -92,11 +107,11 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { Cacher.put(ProxyProviderFactory.HOTELS_CACHE_KEY, additionalCachedHotels); } - stateSetter.setState(HState.PREPARED); + hStateSetter.setState(HState.PREPARED); } private void launchProxy() throws IOException { - stateSetter.setState(HState.WAITING_FOR_CLIENT); + hStateSetter.setState(HState.WAITING_FOR_CLIENT); for (int c = 0; c < potentialProxies.size(); c++) { HProxy potentialProxy = potentialProxies.get(c); @@ -128,46 +143,37 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { Socket client = proxy_server.accept(); proxy = potentialProxy; closeAllProxies(proxy); - if (HConnection.DEBUG) System.out.println("accepted a proxy"); + if (HConnection.DEBUG) logger.debug("Accepted a proxy"); new Thread(() -> { try { Socket server; if (!useSocks) { - server = new Socket(proxy.getActual_domain(), proxy.getActual_port()); + server = new Socket(proxy.getActual_domain(), proxy.getActual_port()); } else { SocksConfiguration configuration = ProxyProviderFactory.getSocksConfig(); if (configuration == null) { - showInvalidConnectionError(); - abort(); + callbacks.onInterceptorError(); return; } server = configuration.createSocket(); server.connect(new InetSocketAddress(proxy.getActual_domain(), proxy.getActual_port()), 5000); } - startProxyThread(client, server, proxy); - } catch (SocketException | SocketTimeoutException e) { + callbacks.onInterceptorConnected(client, server, proxy); + } catch (Exception e) { // should only happen when SOCKS configured badly - showInvalidConnectionError(); - abort(); - e.printStackTrace(); - } - catch (InterruptedException | IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + callbacks.onInterceptorError(); + logger.error("Error occurred while intercepting connection", e); } }).start(); - - - } catch (IOException e1) { - // TODO Auto-generated catch block -// e1.printStackTrace(); + } catch (IOException e) { + // ignore } } } catch (Exception e) { - e.printStackTrace(); + logger.error("Proxy server thread error", e); } }).start(); } @@ -177,24 +183,6 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { } - @Override - public void abort() { - stateSetter.setState(HState.ABORTING); - if (hostRedirected) { - removeFromHosts(); - } - - clearAllProxies(); - super.abort(); - } - - @Override - protected void onConnect() { - super.onConnect(); - removeFromHosts(); - clearAllProxies(); - } - private void addToHosts() { List linesTemp = new ArrayList<>(); for (HProxy proxy : potentialProxies) { @@ -208,6 +196,7 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { hostsReplacer.addRedirect(lines); hostRedirected = true; } + private void removeFromHosts(){ List linesTemp = new ArrayList<>(); for (HProxy proxy : potentialProxies) { @@ -226,6 +215,7 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { closeAllProxies(null); // potentialProxies = new ArrayList<>(); } + private void closeAllProxies(HProxy except) { for (HProxy proxy : potentialProxies) { if (except != proxy) { @@ -241,4 +231,5 @@ public class NormalFlashProxyProvider extends FlashProxyProvider { } // potentialProxies = Collections.singletonList(except); } + } diff --git a/G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptorCallbacks.java b/G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptorCallbacks.java new file mode 100644 index 0000000..17af614 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/interceptor/ConnectionInterceptorCallbacks.java @@ -0,0 +1,14 @@ +package gearth.protocol.interceptor; + +import gearth.protocol.connection.HProxy; + +import java.io.IOException; +import java.net.Socket; + +public interface ConnectionInterceptorCallbacks { + + void onInterceptorConnected(Socket client, Socket server, HProxy proxy) throws IOException, InterruptedException; + + void onInterceptorError(); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java index 9748dd3..78bca47 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java @@ -3,6 +3,7 @@ package gearth.protocol.packethandler; import gearth.misc.listenerpattern.Observable; import gearth.protocol.HMessage; import gearth.protocol.TrafficListener; +import gearth.protocol.format.shockwave.ShockMessage; import gearth.services.extension_handler.ExtensionHandler; import java.io.IOException; @@ -32,6 +33,14 @@ public abstract class PacketHandler { message.getPacket().resetReadIndex(); } + protected void notifyListeners(int i, ShockMessage message) { + ((Observable) trafficObservables[i]).fireEvent(trafficListener -> { + message.getPacket().resetReadIndex(); + trafficListener.onCapture(message); + }); + message.getPacket().resetReadIndex(); + } + protected void awaitListeners(HMessage message, PacketSender packetSender) { notifyListeners(0, message); notifyListeners(1, message); @@ -43,4 +52,15 @@ public abstract class PacketHandler { }); } + protected void awaitListeners(ShockMessage message, PacketSender packetSender) { + notifyListeners(0, message); + notifyListeners(1, message); + extensionHandler.handle(message, message2 -> { + notifyListeners(2, message2); + if (!message2.isBlocked()) { + packetSender.send(message2); + } + }); + } + } diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java new file mode 100644 index 0000000..8a29682 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java @@ -0,0 +1,65 @@ +package gearth.protocol.packethandler.shockwave; + +import gearth.protocol.HMessage; +import gearth.protocol.format.shockwave.ShockMessage; +import gearth.protocol.format.shockwave.ShockPacket; +import gearth.protocol.packethandler.PacketHandler; +import gearth.protocol.packethandler.shockwave.buffers.ShockwaveBuffer; +import gearth.services.extension_handler.ExtensionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.OutputStream; + +public abstract class ShockwavePacketHandler extends PacketHandler { + + private static final Logger logger = LoggerFactory.getLogger(ShockwavePacketHandler.class); + + private final HMessage.Direction direction; + private final ShockwaveBuffer payloadBuffer; + private final OutputStream outputStream; + private final Object flushLock; + + ShockwavePacketHandler(HMessage.Direction direction, ShockwaveBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Object[] trafficObservables) { + super(extensionHandler, trafficObservables); + this.direction = direction; + this.payloadBuffer = payloadBuffer; + this.outputStream = outputStream; + this.flushLock = new Object(); + } + + @Override + public boolean sendToStream(byte[] buffer) { + synchronized (sendLock) { + try { + outputStream.write(buffer); + return true; + } catch (IOException e) { + logger.error("Error while sending packet to stream.", e); + return false; + } + } + } + + @Override + public void act(byte[] buffer) throws IOException { + payloadBuffer.push(buffer); + + flush(); + } + + public void flush() throws IOException { + synchronized (flushLock) { + final ShockPacket[] packets = payloadBuffer.receive(); + + for (final ShockPacket packet : packets){ + final ShockMessage message = new ShockMessage(packet, direction, currentIndex); + + awaitListeners(message, x -> sendToStream(x.getPacket().toBytes())); + + currentIndex++; + } + } + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java new file mode 100644 index 0000000..7e9a2da --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java @@ -0,0 +1,13 @@ +package gearth.protocol.packethandler.shockwave; + +import gearth.protocol.HMessage; +import gearth.protocol.packethandler.shockwave.buffers.ShockwaveInBuffer; +import gearth.services.extension_handler.ExtensionHandler; + +import java.io.OutputStream; + +public class ShockwavePacketIncomingHandler extends ShockwavePacketHandler { + public ShockwavePacketIncomingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Object[] trafficObservables) { + super(HMessage.Direction.TOCLIENT, new ShockwaveInBuffer(), outputStream, extensionHandler, trafficObservables); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java new file mode 100644 index 0000000..7ea0a66 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java @@ -0,0 +1,13 @@ +package gearth.protocol.packethandler.shockwave; + +import gearth.protocol.HMessage; +import gearth.protocol.packethandler.shockwave.buffers.ShockwaveOutBuffer; +import gearth.services.extension_handler.ExtensionHandler; + +import java.io.OutputStream; + +public class ShockwavePacketOutgoingHandler extends ShockwavePacketHandler { + public ShockwavePacketOutgoingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Object[] trafficObservables) { + super(HMessage.Direction.TOSERVER, new ShockwaveOutBuffer(), outputStream, extensionHandler, trafficObservables); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java new file mode 100644 index 0000000..36c0c09 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java @@ -0,0 +1,11 @@ +package gearth.protocol.packethandler.shockwave.buffers; + +import gearth.protocol.format.shockwave.ShockPacket; + +public interface ShockwaveBuffer { + + void push(byte[] data); + + ShockPacket[] receive(); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java new file mode 100644 index 0000000..94d43d4 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java @@ -0,0 +1,42 @@ +package gearth.protocol.packethandler.shockwave.buffers; + +import gearth.protocol.format.shockwave.ShockPacket; +import gearth.protocol.packethandler.ByteArrayUtils; + +import java.util.ArrayList; +import java.util.Arrays; + +public class ShockwaveInBuffer implements ShockwaveBuffer { + + private byte[] buffer = new byte[0]; + + @Override + public void push(byte[] data) { + buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data); + } + + @Override + public ShockPacket[] receive() { + if (buffer.length < 3) { + return new ShockPacket[0]; + } + + // Incoming packets are delimited by chr(1). + // We need to split the buffer by chr(1) and then parse each packet. + ArrayList packets = new ArrayList<>(); + + int curPos = 0; + + for (int i = 0; i < buffer.length; i++) { + if (buffer[i] == 1) { + byte[] packetData = Arrays.copyOfRange(buffer, curPos, i); + packets.add(new ShockPacket(packetData)); + curPos = i + 1; + } + } + + buffer = Arrays.copyOfRange(buffer, curPos, buffer.length); + + return packets.toArray(new ShockPacket[0]); + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java new file mode 100644 index 0000000..ef62fc1 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java @@ -0,0 +1,28 @@ +package gearth.protocol.packethandler.shockwave.buffers; + +import gearth.protocol.HPacket; +import gearth.protocol.format.shockwave.ShockPacket; +import gearth.protocol.packethandler.ByteArrayUtils; + +import java.util.ArrayList; + +public class ShockwaveOutBuffer implements ShockwaveBuffer { + + private byte[] buffer = new byte[0]; + + @Override + public void push(byte[] data) { + buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data); + } + + @Override + public ShockPacket[] receive() { + if (buffer.length < 5) { + return new ShockPacket[0]; + } + + ArrayList all = new ArrayList<>(); + + return all.toArray(new ShockPacket[all.size()]); + } +} diff --git a/G-Earth/src/main/java/gearth/services/extension_handler/ExtensionHandler.java b/G-Earth/src/main/java/gearth/services/extension_handler/ExtensionHandler.java index 87a57d8..a7d1e53 100644 --- a/G-Earth/src/main/java/gearth/services/extension_handler/ExtensionHandler.java +++ b/G-Earth/src/main/java/gearth/services/extension_handler/ExtensionHandler.java @@ -7,6 +7,7 @@ import gearth.protocol.HConnection; import gearth.protocol.HMessage; import gearth.protocol.HPacket; import gearth.protocol.connection.HState; +import gearth.protocol.format.shockwave.ShockMessage; import gearth.services.extension_handler.extensions.ExtensionListener; import gearth.services.extension_handler.extensions.GEarthExtension; import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer; @@ -160,6 +161,7 @@ public class ExtensionHandler { } } } + public void handle(HMessage hMessage, OnHMessageHandled callback) { synchronized (hMessageStuffLock) { Pair msgDirectionAndId = new Pair<>(hMessage.getDestination(), hMessage.getIndex()); @@ -177,11 +179,28 @@ public class ExtensionHandler { } } - maybeFinishHmessage(hMessage); } + public void handle(ShockMessage hMessage, OnHMessageHandled callback) { + synchronized (hMessageStuffLock) { + Pair msgDirectionAndId = new Pair<>(hMessage.getDirection(), hMessage.getIndex()); + originalMessages.put(msgDirectionAndId, hMessage); + finishManipulationCallback.put(hMessage, callback); + editedMessages.put(hMessage, null); + allAwaitingMessages.add(hMessage); + synchronized (gEarthExtensions) { + awaitManipulation.put(hMessage, new HashSet<>(gEarthExtensions)); + + for (GEarthExtension extension : gEarthExtensions) { + extension.packetIntercept(new HMessage(hMessage)); + } + } + } + + maybeFinishHmessage(hMessage); + } private ExtensionProducerObserver createExtensionProducerObserver() { return new ExtensionProducerObserver() { diff --git a/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java b/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java index af1fda8..81e2b79 100644 --- a/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java +++ b/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java @@ -114,11 +114,11 @@ public class PacketInfoManager { if (clientType == HClient.UNITY) { result.addAll(new GEarthUnityPacketInfoProvider(hotelversion).provide()); - } else if (clientType == HClient.FLASH || clientType == HClient.NITRO) { + } else if (clientType == HClient.FLASH || clientType == HClient.NITRO || clientType == HClient.SHOCKWAVE) { try { List providers = new ArrayList<>(); providers.add(new HarblePacketInfoProvider(hotelversion)); - providers.add(new SulekPacketInfoProvider(hotelversion)); + providers.add(new SulekPacketInfoProvider(clientType, hotelversion)); Semaphore blockUntilComplete = new Semaphore(providers.size()); blockUntilComplete.acquire(providers.size()); diff --git a/G-Earth/src/main/java/gearth/services/packet_info/providers/implementations/SulekPacketInfoProvider.java b/G-Earth/src/main/java/gearth/services/packet_info/providers/implementations/SulekPacketInfoProvider.java index f28427c..48e8f45 100644 --- a/G-Earth/src/main/java/gearth/services/packet_info/providers/implementations/SulekPacketInfoProvider.java +++ b/G-Earth/src/main/java/gearth/services/packet_info/providers/implementations/SulekPacketInfoProvider.java @@ -1,5 +1,6 @@ package gearth.services.packet_info.providers.implementations; +import gearth.protocol.connection.HClient; import gearth.services.packet_info.PacketInfo; import gearth.services.packet_info.providers.RemotePacketInfoProvider; import gearth.protocol.HMessage; @@ -12,15 +13,23 @@ import java.util.List; public class SulekPacketInfoProvider extends RemotePacketInfoProvider { public static final String CACHE_PREFIX = "SULEK_API-"; - public static final String SULEK_API_URL = "https://api.sulek.dev/releases/$hotelversion$/messages"; + public static final String SULEK_API_URL_GLOBAL = "https://api.sulek.dev/releases/$hotelversion$/messages"; + public static final String SULEK_API_URL_VARIANT = "https://api.sulek.dev/releases/$variant$/$hotelversion$/messages"; - public SulekPacketInfoProvider(String hotelVersion) { + private final HClient client; + + public SulekPacketInfoProvider(HClient client, String hotelVersion) { super(hotelVersion); + this.client = client; } @Override protected String getRemoteUrl() { - return SULEK_API_URL.replace("$hotelversion$", hotelVersion); + if (client == HClient.SHOCKWAVE) { + return SULEK_API_URL_VARIANT.replace("$variant$", "shockwave-windows").replace("$hotelversion$", hotelVersion); + } + + return SULEK_API_URL_GLOBAL.replace("$hotelversion$", hotelVersion); } @Override diff --git a/G-Earth/src/main/java/gearth/ui/subforms/connection/ConnectionController.java b/G-Earth/src/main/java/gearth/ui/subforms/connection/ConnectionController.java index 1a38967..f05ffe5 100644 --- a/G-Earth/src/main/java/gearth/ui/subforms/connection/ConnectionController.java +++ b/G-Earth/src/main/java/gearth/ui/subforms/connection/ConnectionController.java @@ -43,6 +43,7 @@ public class ConnectionController extends SubForm { public static final String CLIENT_CACHE_KEY = "last_client_mode"; public ToggleGroup tgl_clientMode; public RadioButton rd_unity; + public RadioButton rd_origins; public RadioButton rd_flash; public RadioButton rd_nitro; public GridPane grd_clientSelection; @@ -64,6 +65,9 @@ public class ConnectionController extends SubForm { case FLASH: rd_flash.setSelected(true); break; + case SHOCKWAVE: + rd_origins.setSelected(true); + break; case UNITY: rd_unity.setSelected(true); break; @@ -240,13 +244,17 @@ public class ConnectionController extends SubForm { inpPort.getSelectionModel().select(port); cbx_autodetect.setSelected(false); }); - getHConnection().start(host, Integer.parseInt(port)); + getHConnection().start(HClient.FLASH, host, Integer.parseInt(port)); } else { Platform.runLater(() -> cbx_autodetect.setSelected(true)); - getHConnection().start(); + getHConnection().start(HClient.FLASH); } } + else if (connectMode.equals("origins")) { + Platform.runLater(() -> rd_origins.setSelected(true)); + getHConnection().start(HClient.SHOCKWAVE); + } else if (connectMode.equals("unity")) { Platform.runLater(() -> rd_unity.setSelected(true)); getHConnection().startUnity(); @@ -266,10 +274,12 @@ public class ConnectionController extends SubForm { new Thread(() -> { if (isClientMode(HClient.FLASH)) { if (cbx_autodetect.isSelected()) { - getHConnection().start(); + getHConnection().start(HClient.FLASH); } else { - getHConnection().start(inpHost.getEditor().getText(), Integer.parseInt(inpPort.getEditor().getText())); + getHConnection().start(HClient.FLASH, inpHost.getEditor().getText(), Integer.parseInt(inpPort.getEditor().getText())); } + } else if (isClientMode(HClient.SHOCKWAVE)) { + getHConnection().start(HClient.SHOCKWAVE); } else if (isClientMode(HClient.UNITY)) { getHConnection().startUnity(); } else if (isClientMode(HClient.NITRO)) { @@ -291,6 +301,8 @@ public class ConnectionController extends SubForm { protected void onExit() { if (rd_flash.isSelected()) { Cacher.put(CLIENT_CACHE_KEY, HClient.FLASH); + } else if (rd_origins.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.SHOCKWAVE); } else if (rd_unity.isSelected()) { Cacher.put(CLIENT_CACHE_KEY, HClient.UNITY); } else if (rd_nitro.isSelected()) { @@ -311,6 +323,8 @@ public class ConnectionController extends SubForm { switch (client) { case FLASH: return rd_flash.isSelected(); + case SHOCKWAVE: + return rd_origins.isSelected(); case UNITY: return rd_unity.isSelected(); case NITRO: @@ -333,6 +347,7 @@ public class ConnectionController extends SubForm { 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_origins.textProperty().bind(new TranslatableString("%s", "tab.connection.client.origins")); 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")); diff --git a/G-Earth/src/main/resources/gearth/ui/subforms/connection/Connection.fxml b/G-Earth/src/main/resources/gearth/ui/subforms/connection/Connection.fxml index 90b708b..18bf0a4 100644 --- a/G-Earth/src/main/resources/gearth/ui/subforms/connection/Connection.fxml +++ b/G-Earth/src/main/resources/gearth/ui/subforms/connection/Connection.fxml @@ -5,7 +5,7 @@ - + @@ -128,10 +128,10 @@ - - - - + + + + @@ -145,7 +145,7 @@ - + @@ -153,12 +153,17 @@ - + - + + + + + + diff --git a/G-Earth/src/main/resources/gearth/ui/translations/messages_en.properties b/G-Earth/src/main/resources/gearth/ui/translations/messages_en.properties index 9b4c080..d6c1923 100644 --- a/G-Earth/src/main/resources/gearth/ui/translations/messages_en.properties +++ b/G-Earth/src/main/resources/gearth/ui/translations/messages_en.properties @@ -5,7 +5,8 @@ ### Tab - Connection tab.connection=Connection tab.connection.client=Client type: -tab.connection.client.flash=Flash / Air +tab.connection.client.flash=Flash +tab.connection.client.origins=Origins tab.connection.client.unity=Unity tab.connection.client.nitro=Nitro tab.connection.version=Hotel version: