Shockwave progress

This commit is contained in:
UnfamiliarLegacy 2024-06-21 07:09:32 +02:00
parent 7964aaabbb
commit ab263a6cfd
27 changed files with 705 additions and 94 deletions

View File

@ -0,0 +1,38 @@
package gearth.encoding;
/**
* Kepler Copyright (C) 2018 Quackster
* <a href="https://github.com/Quackster/Kepler">Kepler</a>
*/
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;
}
}

View File

@ -0,0 +1,63 @@
package gearth.encoding;
/**
* Kepler Copyright (C) 2018 Quackster
* <a href="https://github.com/Quackster/Kepler">Kepler</a>
*/
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;
}
}

View File

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

View File

@ -1,7 +1,11 @@
package gearth.protocol;
import gearth.protocol.format.shockwave.ShockMessage;
public interface TrafficListener {
void onCapture(HMessage message);
void onCapture(ShockMessage message);
}

View File

@ -3,5 +3,6 @@ package gearth.protocol.connection;
public enum HClient {
UNITY,
FLASH,
NITRO
NITRO,
SHOCKWAVE
}

View File

@ -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<Object> 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<Object> additionalCachedHotels = Cacher.getList(HOTELS_CACHE_KEY);
if (additionalCachedHotels == null) {
additionalCachedHotels = new ArrayList<>();
@ -129,11 +132,14 @@ public class ProxyProviderFactory {
else {
List<String> potentialHost = new ArrayList<>();
potentialHost.add(domain+":"+port);
return provide(potentialHost);
return provide(client, potentialHost);
}
}
private ProxyProvider provide(List<String> potentialHosts) {
return new NormalFlashProxyProvider(proxySetter, stateSetter, hConnection, potentialHosts, socksConfig.useSocks() && !socksConfig.onlyUseIfNeeded());
private ProxyProvider provide(HClient client, List<String> 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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
package gearth.protocol.format.shockwave;
public class ShockPacketIn {
}

View File

@ -0,0 +1,4 @@
package gearth.protocol.format.shockwave;
public class ShockPacketOut {
}

View File

@ -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<String> 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<String> potentialHosts;
private volatile boolean hostRedirected = false;
private volatile List<HProxy> potentialProxies = new ArrayList<>();
@ -35,28 +47,31 @@ public class NormalFlashProxyProvider extends FlashProxyProvider {
private boolean useSocks;
public NormalFlashProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection, List<String> potentialHosts, boolean useSocks) {
super(proxySetter, stateSetter, hConnection);
public ConnectionInterceptor(HClient client, HStateSetter stateSetter, HConnection hConnection, ConnectionInterceptorCallbacks callbacks, List<String> 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<String> 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<String> 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<String> 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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<HPacket> all = new ArrayList<>();
return all.toArray(new ShockPacket[all.size()]);
}
}

View File

@ -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<HMessage.Direction, Integer> 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<HMessage.Direction, Integer> 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() {

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane alignment="CENTER" 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.connection.ConnectionController">
<GridPane alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="258.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gearth.ui.subforms.connection.ConnectionController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
@ -128,10 +128,10 @@
<GridPane fx:id="grd_clientSelection" prefHeight="26.0" prefWidth="286.0" style="-fx-border-color: #888888; -fx-border-width: 0 0 1 0;">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="149.0" minWidth="10.0" prefWidth="76.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="149.0" minWidth="10.0" prefWidth="25.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="177.0" minWidth="66.0" prefWidth="90.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="133.0" minWidth="8.0" prefWidth="70.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="242.0" minWidth="10.0" prefWidth="70.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="177.0" minWidth="44.0" prefWidth="57.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="133.0" minWidth="8.0" prefWidth="65.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="242.0" minWidth="10.0" prefWidth="54.0" />
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="20.0" prefHeight="34.0" vgrow="SOMETIMES" />
@ -145,7 +145,7 @@
<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">
<RadioButton fx:id="rd_unity" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" prefHeight="22.0" prefWidth="54.0" text="Unity" GridPane.columnIndex="3" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
<toggleGroup>
<ToggleGroup fx:id="tgl_clientMode" />
</toggleGroup>
@ -153,12 +153,17 @@
<Insets />
</GridPane.margin>
</RadioButton>
<RadioButton fx:id="rd_flash" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" prefHeight="35.0" prefWidth="111.0" selected="true" text="Flash / Air" toggleGroup="$tgl_clientMode" GridPane.columnIndex="2">
<RadioButton fx:id="rd_flash" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" prefHeight="35.0" prefWidth="54.0" selected="true" text="Flash" toggleGroup="$tgl_clientMode" GridPane.columnIndex="1">
<GridPane.margin>
<Insets />
</GridPane.margin>
</RadioButton>
<RadioButton fx:id="rd_nitro" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" prefHeight="22.0" prefWidth="54.0" text="Nitro" toggleGroup="$tgl_clientMode" GridPane.columnIndex="4">
<RadioButton fx:id="rd_origins" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" prefHeight="22.0" prefWidth="71.0" text="Origins" toggleGroup="$tgl_clientMode" GridPane.columnIndex="2">
<GridPane.margin>
<Insets />
</GridPane.margin>
</RadioButton>
<RadioButton fx:id="rd_nitro" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" prefHeight="22.0" prefWidth="74.0" text="Nitro" toggleGroup="$tgl_clientMode" GridPane.columnIndex="4">
<GridPane.margin>
<Insets />
</GridPane.margin>

View File

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