diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java new file mode 100644 index 0000000..d64a88f --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java @@ -0,0 +1,11 @@ +package gearth.protocol.connection.proxy.nitro; + +public final class NitroConstants { + + public static final int PORT_WEBSOCKET = 2096; + public static final int PORT_HTTP = 9090; + + public static final int WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 10; + public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768"; + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index 5ba1172..a3ac2ec 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -1,34 +1,52 @@ package gearth.protocol.connection.proxy.nitro; import gearth.protocol.HConnection; +import gearth.protocol.StateChangeListener; import gearth.protocol.connection.HProxySetter; import gearth.protocol.connection.HState; import gearth.protocol.connection.HStateSetter; 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 java.io.IOException; -public class NitroProxyProvider implements ProxyProvider { +public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener { private final HProxySetter proxySetter; private final HStateSetter stateSetter; - private final HConnection hConnection; - private final NitroHttpProxy nitroProxy; + private final HConnection connection; + private final NitroHttpProxy nitroHttpProxy; + private final NitroWebsocketProxy nitroWebsocketProxy; - public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection) { + private String originalWebsocketUrl; + + public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) { this.proxySetter = proxySetter; this.stateSetter = stateSetter; - this.hConnection = hConnection; - this.nitroProxy = new NitroHttpProxy(); + this.connection = connection; + this.nitroHttpProxy = new NitroHttpProxy(this); + this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this); + } + + public String getOriginalWebsocketUrl() { + return originalWebsocketUrl; } @Override public void start() throws IOException { - if (!nitroProxy.start()) { - System.out.println("Failed to start nitro proxy"); + connection.getStateObservable().addListener(this); - stateSetter.setState(HState.NOT_CONNECTED); + if (!nitroHttpProxy.start()) { + System.out.println("Failed to start nitro proxy"); + abort(); + return; + } + + if (!nitroWebsocketProxy.start()) { + System.out.println("Failed to start nitro websocket proxy"); + abort(); return; } @@ -41,13 +59,35 @@ public class NitroProxyProvider implements ProxyProvider { new Thread(() -> { try { - nitroProxy.stop(); + nitroHttpProxy.stop(); } catch (Exception e) { e.printStackTrace(); - } finally { - stateSetter.setState(HState.NOT_CONNECTED); } + + try { + nitroWebsocketProxy.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + + stateSetter.setState(HState.NOT_CONNECTED); + + connection.getStateObservable().removeListener(this); }).start(); } + @Override + public String replaceWebsocketServer(String configUrl, String websocketUrl) { + originalWebsocketUrl = websocketUrl; + return String.format("ws://127.0.0.1:%d", NitroConstants.PORT_WEBSOCKET); + } + + @Override + public void stateChanged(HState oldState, HState newState) { + if (oldState == HState.WAITING_FOR_CLIENT && newState == HState.CONNECTED) { + // Unregister but do not stop http proxy. + // We are not stopping the http proxy because some requests might still require it to be running. + nitroHttpProxy.pause(); + } + } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 13acc03..7e23a75 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -1,5 +1,6 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; import org.littleshoot.proxy.HttpProxyServer; @@ -12,10 +13,12 @@ public class NitroHttpProxy { private final Authority authority; private final NitroOsFunctions osFunctions; + private final NitroHttpProxyServerCallback serverCallback; private HttpProxyServer proxyServer = null; - public NitroHttpProxy() { + public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback) { + this.serverCallback = serverCallback; this.authority = new NitroAuthority(); this.osFunctions = NitroOsFunctionsFactory.create(); } @@ -28,7 +31,7 @@ public class NitroHttpProxy { * Register HTTP(s) proxy on the system. */ private boolean registerProxy() { - return this.osFunctions.registerSystemProxy("127.0.0.1", 9090); + return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.PORT_HTTP); } /** @@ -39,18 +42,11 @@ public class NitroHttpProxy { } public boolean start() { - - try { proxyServer = DefaultHttpProxyServer.bootstrap() - .withPort(9090) + .withPort(NitroConstants.PORT_HTTP) .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) - // TODO: Replace lambda with some class - .withFiltersSource(new NitroHttpProxyFilterSource((configUrl, websocketUrl) -> { - System.out.printf("Found %s at %s%n", websocketUrl, configUrl); - - return "wss://127.0.0.1:2096"; - })) + .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); if (!initializeCertificate()) { @@ -74,10 +70,14 @@ public class NitroHttpProxy { } } - public void stop() { + public void pause() { if (!unregisterProxy()) { System.out.println("Failed to unregister system proxy, please check manually"); } + } + + public void stop() { + pause(); if (proxyServer == null) { return; diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java new file mode 100644 index 0000000..42433da --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; + +public class NitroForwarder { + + private final HConnection connection; + private final NitroSession client; + private final NitroSession server; + + public NitroForwarder(HConnection connection, NitroSession client, NitroSession server) { + this.connection = connection; + this.client = client; + this.server = server; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java new file mode 100644 index 0000000..a6283e3 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -0,0 +1,56 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.packethandler.PacketHandler; +import gearth.services.extension_handler.ExtensionHandler; +import gearth.services.extension_handler.OnHMessageHandled; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class NitroPacketHandler extends PacketHandler { + + private final HMessage.Direction direction; + private final NitroSession session; + + protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) { + super(extensionHandler, trafficObservables); + this.direction = direction; + this.session = session; + } + + @Override + public boolean sendToStream(byte[] buffer) { + session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); + return true; + } + + @Override + public void act(byte[] buffer) throws IOException { + HMessage hMessage = new HMessage(new HPacket(deepCopy(buffer)), direction, currentIndex); + + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + notifyListeners(2, hMessage1); + + if (!hMessage1.isBlocked()) { + sendToStream(hMessage1.getPacket().toBytes()); + } + }; + + notifyListeners(0, hMessage); + notifyListeners(1, hMessage); + extensionHandler.handle(hMessage, afterExtensionIntercept); + + currentIndex++; + } + + public static byte[] deepCopy(byte[] org) { + if (org == null) + return null; + byte[] result = new byte[org.length]; + System.arraycopy(org, 0, result, 0, org.length); + return result; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java new file mode 100644 index 0000000..f85854e --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java @@ -0,0 +1,9 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import javax.websocket.Session; + +public interface NitroSession { + + Session getSession(); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java new file mode 100644 index 0000000..d1c8fa5 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -0,0 +1,78 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.connection.*; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; +import gearth.services.internal_extensions.uilogger.hexdumper.Hexdump; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +@ServerEndpoint(value = "/") +public class NitroWebsocketClient implements NitroSession { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + private final NitroWebsocketServer server; + private final NitroPacketHandler packetHandler; + + private Session activeSession = null; + private HProxy proxy = null; + + public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + this.server = new NitroWebsocketServer(connection, this); + this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); + } + + @OnOpen + public void onOpen(Session session) throws IOException { + activeSession = session; + activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + + server.connect(proxyProvider.getOriginalWebsocketUrl()); + + proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + proxy.verifyProxy( + this.server.getPacketHandler(), + this.packetHandler, + NitroConstants.WEBSOCKET_REVISION, + "HTML5" // TODO: What is its purpose? + ); + + proxySetter.setProxy(proxy); + stateSetter.setState(HState.CONNECTED); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + System.out.printf("onMessage (%d)%n", b.length); + System.out.println(session); + System.out.println(Hexdump.hexdump(b)); + + packetHandler.act(b); + } + + @OnClose + public void onClose(Session session) throws IOException { + activeSession = null; + } + + @OnError + public void onError(Session session, Throwable throwable) { + + } + + @Override + public Session getSession() { + return activeSession; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java new file mode 100644 index 0000000..d162347 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java @@ -0,0 +1,66 @@ +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.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; + +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +public class NitroWebsocketProxy { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + + private final Server server; + + public NitroWebsocketProxy(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + this.server = new Server(NitroConstants.PORT_WEBSOCKET); + } + + public boolean start() { + try { + final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + + final HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { context }); + + final ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); + wscontainer.addEndpoint(ServerEndpointConfig.Builder + .create(NitroWebsocketClient.class, "/") + .configurator(new NitroWebsocketServerConfigurator(proxySetter, stateSetter, connection, proxyProvider)) + .build()); + + server.setHandler(handlers); + server.start(); + + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java new file mode 100644 index 0000000..f7928df --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -0,0 +1,65 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.packethandler.PacketHandler; + +import javax.websocket.*; +import java.io.IOException; +import java.net.URI; + +@ClientEndpoint +public class NitroWebsocketServer implements NitroSession { + + private final PacketHandler packetHandler; + private Session activeSession = null; + + public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { + this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); + } + + public void connect(String websocketUrl) throws IOException { + try { + ContainerProvider.getWebSocketContainer().connectToServer(this, URI.create(websocketUrl)); + } catch (DeploymentException e) { + throw new IOException("Failed to deploy websocket client", e); + } + } + + @OnOpen + public void onOpen(Session Session) { + this.activeSession = Session; + this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + //System.out.printf("onMessage (%d)%n", b.length); + //System.out.println(session); + //System.out.println(Hexdump.hexdump(b)); + + packetHandler.act(b); + } + + @OnClose + public void onClose(Session userSession, CloseReason reason) { + //System.out.println("closing websocket"); + } + + @OnError + public void onError(Session session, Throwable throwable) { + //System.out.println("onError"); + //System.out.println(session); + //System.out.println(throwable); + } + + @Override + public Session getSession() { + return activeSession; + } + + public PacketHandler getPacketHandler() { + return packetHandler; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java new file mode 100644 index 0000000..8155f27 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java @@ -0,0 +1,28 @@ +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.NitroProxyProvider; + +import javax.websocket.server.ServerEndpointConfig; + +public class NitroWebsocketServerConfigurator extends ServerEndpointConfig.Configurator { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + + public NitroWebsocketServerConfigurator(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + } + + @Override + public T getEndpointInstance(Class endpointClass) { + return (T) new NitroWebsocketClient(proxySetter, stateSetter, connection, proxyProvider); + } +} 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 27904b4..af1fda8 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,8 +114,7 @@ public class PacketInfoManager { if (clientType == HClient.UNITY) { result.addAll(new GEarthUnityPacketInfoProvider(hotelversion).provide()); - } - else if (clientType == HClient.FLASH) { + } else if (clientType == HClient.FLASH || clientType == HClient.NITRO) { try { List providers = new ArrayList<>(); providers.add(new HarblePacketInfoProvider(hotelversion));