Revamped Forwarder

This commit is contained in:
Josh Larson
2018-05-17 10:23:22 -05:00
parent 70695be0fe
commit d064989ef7
78 changed files with 1899 additions and 2980 deletions
@@ -0,0 +1,16 @@
package com.projectswg.forwarder;
import com.projectswg.forwarder.services.client.ClientConnectionManager;
import com.projectswg.forwarder.services.crash.CrashManager;
import com.projectswg.forwarder.services.server.ServerConnectionManager;
import me.joshlarson.jlcommon.control.Manager;
import me.joshlarson.jlcommon.control.ManagerStructure;
@ManagerStructure(children = {
ClientConnectionManager.class,
ServerConnectionManager.class,
CrashManager.class
})
public class ConnectionManager extends Manager {
}
@@ -0,0 +1,175 @@
package com.projectswg.forwarder;
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
import com.projectswg.forwarder.intents.control.ClientCrashedIntent;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
import me.joshlarson.jlcommon.concurrency.Delay;
import me.joshlarson.jlcommon.control.IntentManager;
import me.joshlarson.jlcommon.control.Manager;
import me.joshlarson.jlcommon.control.SafeMain;
import me.joshlarson.jlcommon.log.Log;
import me.joshlarson.jlcommon.log.Log.LogLevel;
import me.joshlarson.jlcommon.log.log_wrapper.ConsoleLogWrapper;
import me.joshlarson.jlcommon.utilities.ThreadUtilities;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Forwarder {
private final ForwarderData data;
private final IntentManager intentManager;
private final AtomicBoolean connected;
public Forwarder() {
this.data = new ForwarderData();
this.intentManager = new IntentManager(false, Runtime.getRuntime().availableProcessors());
this.connected = new AtomicBoolean(false);
}
public static void main(String [] args) {
SafeMain.main("forwarder", Forwarder::mainRunnable);
}
private static void mainRunnable() {
Log.addWrapper(new ConsoleLogWrapper(LogLevel.TRACE));
Forwarder forwarder = new Forwarder();
forwarder.getData().setAddress(new InetSocketAddress(44463));
forwarder.run();
ThreadUtilities.printActiveThreads();
}
public File readClientOutput(InputStream is) {
StringBuilder output = new StringBuilder();
byte [] buffer = new byte[2048];
int n;
try {
while ((n = is.read(buffer)) > 0) {
output.append(new String(buffer, 0, n, StandardCharsets.UTF_8));
}
} catch (IOException e) {
Log.w("IOException while reading client output");
Log.w(e);
}
return onClientClosed(output.toString());
}
private File onClientClosed(String clientOutput) {
if (!connected.get())
return null;
File output;
try {
output = Files.createTempFile("HolocoreCrashLog", ".zip").toFile();
} catch (IOException e) {
Log.e("Failed to write crash log! Could not create temp file.");
return null;
}
try (ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output)))) {
{
byte [] data = clientOutput.getBytes(StandardCharsets.UTF_8);
ZipEntry entry = new ZipEntry("output.txt");
entry.setTime(System.currentTimeMillis());
entry.setSize(data.length);
entry.setMethod(ZipOutputStream.DEFLATED);
zip.putNextEntry(entry);
zip.write(data);
zip.closeEntry();
}
ClientCrashedIntent cci = new ClientCrashedIntent(zip);
cci.broadcast(intentManager);
long startSleep = System.nanoTime();
while (!cci.isComplete() && System.nanoTime() - startSleep < 1E9)
Delay.sleepMilli(10);
return output;
} catch (IOException e) {
Log.e("Failed to write crash log! %s: %s", e.getClass().getName(), e.getMessage());
return null;
}
}
public void run() {
intentManager.initialize();
intentManager.registerForIntent(ClientConnectedIntent.class, cci -> connected.set(true));
intentManager.registerForIntent(ClientDisconnectedIntent.class, cdi -> connected.set(false));
ConnectionManager primary = new ConnectionManager();
{
primary.setIntentManager(intentManager);
List<Manager> managers = Collections.singletonList(primary);
Manager.start(managers);
new StartForwarderIntent(data).broadcast(intentManager);
Manager.run(managers, 100);
new StopForwarderIntent().broadcast(intentManager);
Manager.stop(managers);
}
intentManager.terminate(false);
primary.setIntentManager(null);
}
public ForwarderData getData() {
return data;
}
public static class ForwarderData {
private InetSocketAddress address = null;
private String username = null;
private String password = null;
private int loginPort = 0;
private int zonePort = 0;
private ForwarderData() { }
public InetSocketAddress getAddress() {
return address;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public int getLoginPort() {
return loginPort;
}
public int getZonePort() {
return zonePort;
}
public void setAddress(InetSocketAddress address) {
this.address = address;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setLoginPort(int loginPort) {
this.loginPort = loginPort;
}
public void setZonePort(int zonePort) {
this.zonePort = zonePort;
}
}
}
@@ -0,0 +1,11 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
public class ClientConnectedIntent extends Intent {
public ClientConnectedIntent() {
}
}
@@ -0,0 +1,11 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
public class ClientDisconnectedIntent extends Intent {
public ClientDisconnectedIntent() {
}
}
@@ -0,0 +1,20 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
import javax.annotation.Nonnull;
public class DataPacketInboundIntent extends Intent {
private final byte [] data;
public DataPacketInboundIntent(@Nonnull byte [] data) {
this.data = data;
}
@Nonnull
public byte [] getData() {
return data;
}
}
@@ -0,0 +1,20 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
import javax.annotation.Nonnull;
public class DataPacketOutboundIntent extends Intent {
private final byte [] data;
public DataPacketOutboundIntent(@Nonnull byte [] data) {
this.data = data;
}
@Nonnull
public byte [] getData() {
return data;
}
}
@@ -0,0 +1,21 @@
package com.projectswg.forwarder.intents.client;
import com.projectswg.forwarder.resources.networking.packets.Packet;
import me.joshlarson.jlcommon.control.Intent;
import javax.annotation.Nonnull;
public class SonyPacketInboundIntent extends Intent {
private final Packet packet;
public SonyPacketInboundIntent(@Nonnull Packet packet) {
this.packet = packet;
}
@Nonnull
public Packet getPacket() {
return packet;
}
}
@@ -0,0 +1,21 @@
package com.projectswg.forwarder.intents.client;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import me.joshlarson.jlcommon.control.Intent;
import javax.annotation.CheckForNull;
public class UpdateStackIntent extends Intent {
private final ProtocolStack stack;
public UpdateStackIntent(ProtocolStack stack) {
this.stack = stack;
}
@CheckForNull
public ProtocolStack getStack() {
return stack;
}
}
@@ -0,0 +1,24 @@
package com.projectswg.forwarder.intents.control;
import me.joshlarson.jlcommon.control.Intent;
import java.util.zip.ZipOutputStream;
public class ClientCrashedIntent extends Intent {
private final ZipOutputStream outputStream;
private final Object fileMutex;
public ClientCrashedIntent(ZipOutputStream outputStream) {
this.outputStream = outputStream;
this.fileMutex = new Object();
}
public ZipOutputStream getOutputStream() {
return outputStream;
}
public Object getFileMutex() {
return fileMutex;
}
}
@@ -0,0 +1,21 @@
package com.projectswg.forwarder.intents.control;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import me.joshlarson.jlcommon.control.Intent;
public class StartForwarderIntent extends Intent {
private final ForwarderData data;
public StartForwarderIntent(ForwarderData data) {
this.data = data;
}
public ForwarderData getData() {
return data;
}
public static void broadcast(ForwarderData data) {
new StartForwarderIntent(data).broadcast();
}
}
@@ -0,0 +1,11 @@
package com.projectswg.forwarder.intents.control;
import me.joshlarson.jlcommon.control.Intent;
public class StopForwarderIntent extends Intent {
public StopForwarderIntent() {
}
}
@@ -0,0 +1,11 @@
package com.projectswg.forwarder.intents.server;
import me.joshlarson.jlcommon.control.Intent;
public class ServerConnectedIntent extends Intent {
public ServerConnectedIntent() {
}
}
@@ -0,0 +1,11 @@
package com.projectswg.forwarder.intents.server;
import me.joshlarson.jlcommon.control.Intent;
public class ServerDisconnectedIntent extends Intent {
public ServerDisconnectedIntent() {
}
}
@@ -0,0 +1,59 @@
package com.projectswg.forwarder.resources.client.state;
import me.joshlarson.jlcommon.log.Log;
public class OutboundDataTuner {
private long start;
private int maxSend;
private int outOfOrders;
public OutboundDataTuner() {
reset();
}
public int getMaxSend() {
return maxSend;
}
public void markStart() {
reset();
start = System.nanoTime();
}
public void markEnd() {
long time = System.nanoTime() - start;
Log.d("Max Send=%d Time to Zone=%.0fms OOO=%d", maxSend, time/1E6, outOfOrders);
reset();
}
public void markOOO() {
outOfOrders++;
maxSend -= 2;
capMaxSend();
}
public void markAck() {
maxSend += 2;
capMaxSend();
}
public void markLag() {
maxSend -= 50;
capMaxSend();
}
private void reset() {
start = 0;
outOfOrders = 0;
maxSend = 200;
}
private void capMaxSend() {
if (maxSend < 100)
maxSend = 100;
else if (maxSend > 5000)
maxSend = 5000;
}
}
@@ -0,0 +1,6 @@
package com.projectswg.forwarder.resources.networking;
public enum ClientServer {
LOGIN,
ZONE
}
@@ -0,0 +1,68 @@
package com.projectswg.forwarder.resources.networking;
import com.projectswg.common.data.encodables.galaxy.Galaxy;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.packets.PacketType;
import com.projectswg.common.network.packets.swg.login.LoginClientId;
import com.projectswg.common.network.packets.swg.login.LoginClusterStatus;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import me.joshlarson.jlcommon.log.Log;
import me.joshlarson.jlcommon.utilities.ByteUtilities;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class NetInterceptor {
private final ForwarderData data;
public NetInterceptor(ForwarderData data) {
this.data = data;
}
public byte[] interceptClient(byte[] data) {
if (data.length < 6)
return data;
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
PacketType type = PacketType.fromCrc(bb.getInt(2));
switch (type) {
case LOGIN_CLIENT_ID:
return setAutoLogin(NetBuffer.wrap(bb));
default:
return data;
}
}
public byte[] interceptServer(byte[] data) {
if (data.length < 6)
return data;
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
PacketType type = PacketType.fromCrc(bb.getInt(2));
switch (type) {
case LOGIN_CLUSTER_STATUS:
return getServerList(NetBuffer.wrap(bb));
default:
return data;
}
}
private byte[] setAutoLogin(NetBuffer data) {
LoginClientId id = new LoginClientId(data);
if (!id.getUsername().equals(this.data.getUsername()) || !id.getPassword().isEmpty())
return data.array();
id.setPassword(this.data.getPassword());
return id.encode().array();
}
private byte[] getServerList(NetBuffer data) {
LoginClusterStatus cluster = new LoginClusterStatus();
cluster.decode(data);
for (Galaxy g : cluster.getGalaxies()) {
g.setAddress("127.0.0.1");
g.setZonePort(this.data.getZonePort());
g.setPingPort(this.data.getZonePort());
}
return cluster.encode().array();
}
}
@@ -0,0 +1,169 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking;
import me.joshlarson.jlcommon.concurrency.Delay;
import me.joshlarson.jlcommon.log.Log;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.net.*;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* This class represents a UDP server that listens for packets and
* will call the callback when it receives one
*/
public class UDPServer {
private final byte [] dataBuffer;
private final AtomicBoolean running;
private final InetSocketAddress bindAddr;
private final Consumer<DatagramPacket> callback;
private DatagramSocket socket;
private Thread thread;
public UDPServer(@Nonnull InetSocketAddress bindAddr, @Nonnull Consumer<DatagramPacket> callback) {
this(bindAddr, 1024, callback);
}
public UDPServer(@Nonnull InetSocketAddress bindAddr, int packetSize, @Nonnull Consumer<DatagramPacket> callback) {
this.dataBuffer = new byte[packetSize];
this.running = new AtomicBoolean(false);
this.bindAddr = bindAddr;
this.callback = callback;
}
public void bind() throws SocketException {
bind(null);
}
public void bind(Consumer<DatagramSocket> customizationCallback) throws SocketException {
assert socket == null : "binding twice";
socket = new DatagramSocket(bindAddr);
if (customizationCallback != null)
customizationCallback.accept(socket);
start();
}
public void close() {
assert socket != null : "socket already closed";
stop();
socket.close();
socket = null;
}
public int getPort() {
int port = socket.getLocalPort();
while (port == 0) {
port = socket.getLocalPort();
if (!Delay.sleepMilli(5))
break;
}
return port;
}
public boolean isRunning() {
return running.get();
}
public boolean send(DatagramPacket packet) {
try {
socket.send(packet);
return true;
} catch (IOException e) {
String msg = e.getMessage();
if (msg == null || !msg.toLowerCase(Locale.US).contains("socket closed")) {
Log.e(e);
close();
}
}
return false;
}
public boolean send(int port, InetAddress addr, byte [] data) {
return send(new DatagramPacket(data, data.length, addr, port));
}
public boolean send(int port, String addr, byte [] data) {
try {
return send(port, InetAddress.getByName(addr), data);
} catch (UnknownHostException e) {
Log.e(e);
}
return false;
}
public boolean send(InetSocketAddress addr, byte [] data) {
return send(new DatagramPacket(data, data.length, addr));
}
private void start() {
running.set(true);
thread = new Thread(this::run);
thread.setName("UDPServer Port#" + getPort());
thread.start();
}
private void stop() {
running.set(false);
thread.interrupt();
}
private void run() {
try {
while (running.get()) {
DatagramPacket packet = new DatagramPacket(dataBuffer, dataBuffer.length);
try {
socket.receive(packet);
if (packet.getLength() > 0) {
byte [] buffer = new byte[packet.getLength()];
System.arraycopy(packet.getData(), 0, buffer, 0, packet.getLength());
packet.setData(buffer);
callback.accept(packet);
}
} catch (IOException e) {
String msg = e.getMessage();
if (msg == null || !msg.toLowerCase(Locale.US).contains("socket closed")) {
Log.e(e);
close();
}
packet.setLength(0);
}
}
} catch (Exception e) {
Log.e(e);
} finally {
running.set(false);
}
}
}
@@ -0,0 +1,50 @@
package com.projectswg.forwarder.resources.networking.data;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
import com.projectswg.forwarder.resources.networking.packets.Packet;
public class FragmentedProcessor {
private final List<Fragmented> fragmentedBuffer;
public FragmentedProcessor() {
this.fragmentedBuffer = new ArrayList<>();
}
public void reset() {
fragmentedBuffer.clear();
}
public byte [] addFragmented(Fragmented frag) {
fragmentedBuffer.add(frag);
frag = fragmentedBuffer.get(0);
ByteBuffer data = frag.getPacketData();
data.position(4);
int size = Packet.getNetInt(data);
int index = data.remaining();
for (int i = 1; i < fragmentedBuffer.size() && index < size; i++)
index += fragmentedBuffer.get(i).getPacketData().limit()-4;
if (index == size)
return processFragmentedReady(size);
return null;
}
private byte [] processFragmentedReady(int size) {
byte [] combined = new byte[size];
int index = 0;
while (index < combined.length) {
ByteBuffer packet = fragmentedBuffer.get(0).getPacketData();
packet.position(index == 0 ? 8 : 4);
int len = packet.remaining();
packet.get(combined, index, len);
index += len;
fragmentedBuffer.remove(0);
}
return combined;
}
}
@@ -0,0 +1,82 @@
package com.projectswg.forwarder.resources.networking.data;
import com.projectswg.forwarder.resources.networking.packets.DataChannel;
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
public class Packager {
private final AtomicInteger size;
private final DataChannel channel;
private final Queue<byte[]> outboundRaw;
private final Queue<SequencedOutbound> outboundPackaged;
private final ProtocolStack stack;
public Packager(Queue<byte[]> outboundRaw, Queue<SequencedOutbound> outboundPackaged, ProtocolStack stack) {
this.size = new AtomicInteger(8);
this.channel = new DataChannel();
this.outboundRaw = outboundRaw;
this.outboundPackaged = outboundPackaged;
this.stack = stack;
}
public void handle(int maxPackaged) {
byte [] packet;
int packetSize;
while (!outboundRaw.isEmpty() && outboundPackaged.size() < maxPackaged) {
packet = outboundRaw.poll();
if (packet == null)
break;
packetSize = getPacketLength(packet);
if (size.get() + packetSize >= 496) // overflowed previous packet
sendDataChannel();
if (packetSize < 496) {
addToDataChannel(packet, packetSize);
} else {
sendFragmented(packet);
}
}
sendDataChannel();
}
private void addToDataChannel(byte [] packet, int packetSize) {
channel.addPacket(packet);
size.getAndAdd(packetSize);
}
private void sendDataChannel() {
if (channel.getPacketCount() == 0)
return;
channel.setSequence(stack.getAndIncrementTxSequence());
outboundPackaged.add(new SequencedOutbound(channel.getSequence(), channel.encode().array()));
reset();
}
private void sendFragmented(byte [] packet) {
Fragmented[] frags = Fragmented.encode(ByteBuffer.wrap(packet), stack.getTxSequence());
stack.getAndIncrementTxSequence(frags.length);
for (Fragmented frag : frags) {
outboundPackaged.add(new SequencedOutbound(frag.getSequence(), frag.encode().array()));
}
}
private void reset() {
channel.clearPackets();
size.set(8);
}
private static int getPacketLength(byte [] data) {
int len = data.length;
if (len >= 255)
return len + 3;
return len + 1;
}
}
@@ -0,0 +1,169 @@
package com.projectswg.forwarder.resources.networking.data;
import com.projectswg.forwarder.resources.networking.ClientServer;
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
import com.projectswg.forwarder.resources.networking.packets.Packet;
import com.projectswg.forwarder.resources.networking.packets.SequencedPacket;
import javax.annotation.Nonnull;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.function.BiConsumer;
public class ProtocolStack {
private final PriorityQueue<SequencedPacket> sequenced;
private final FragmentedProcessor fragmentedProcessor;
private final InetSocketAddress source;
private final BiConsumer<InetSocketAddress, byte[]> sender;
private final ClientServer server;
private final Queue<byte []> outboundRaw;
private final Queue<SequencedOutbound> outboundPackaged;
private final Packager packager;
private final Object txMutex;
private InetSocketAddress pingSource;
private int connectionId;
private short rxSequence;
private short txSequence;
private boolean txOverflow;
public ProtocolStack(InetSocketAddress source, ClientServer server, BiConsumer<InetSocketAddress, byte[]> sender) {
this.sequenced = new PriorityQueue<>();
this.fragmentedProcessor = new FragmentedProcessor();
this.source = source;
this.sender = sender;
this.server = server;
this.outboundRaw = new LinkedList<>();
this.outboundPackaged = new LinkedList<>();
this.packager = new Packager(outboundRaw, outboundPackaged, this);
this.txMutex = new Object();
this.connectionId = 0;
this.rxSequence = 0;
this.txSequence = 0;
this.txOverflow = false;
}
public void send(Packet packet) {
send(packet.encode().array());
}
public void send(byte [] data) {
sender.accept(source, data);
}
public void sendPing(byte [] data) {
InetSocketAddress pingSource = this.pingSource;
if (pingSource != null)
sender.accept(pingSource, data);
}
public InetSocketAddress getSource() {
return source;
}
public ClientServer getServer() {
return server;
}
public int getConnectionId() {
return connectionId;
}
public short getRxSequence() {
return rxSequence;
}
public short getTxSequence() {
return txSequence;
}
public void setPingSource(InetSocketAddress source) {
this.pingSource = source;
}
public void setConnectionId(int connectionId) {
this.connectionId = connectionId;
}
public short getAndIncrementTxSequence() {
return getAndIncrementTxSequence(1);
}
public short getAndIncrementTxSequence(int amount) {
synchronized (txMutex) {
short prev = this.txSequence;
short next = prev;
next += amount;
if (prev > next)
txOverflow = true;
this.txSequence = next;
return prev;
}
}
public boolean addIncoming(@Nonnull SequencedPacket packet) {
synchronized (sequenced) {
if (packet.getSequence() < rxSequence)
return true;
// If it already exists in here, don't add it again
for (SequencedPacket seq : sequenced) {
if (seq.getSequence() == packet.getSequence())
return true;
}
sequenced.add(packet);
packet = sequenced.peek();
assert packet != null : "the world is on fire";
return packet.getSequence() == rxSequence;
}
}
public SequencedPacket getNextIncoming() {
synchronized (sequenced) {
SequencedPacket peek = sequenced.peek();
if (peek == null || peek.getSequence() != rxSequence)
return null;
rxSequence++;
return sequenced.poll();
}
}
public byte [] addFragmented(Fragmented frag) {
return fragmentedProcessor.addFragmented(frag);
}
public void addOutbound(@Nonnull byte [] data) {
outboundRaw.offer(data);
}
public short getFirstUnacknowledgedOutbound() {
SequencedOutbound out = outboundPackaged.peek();
if (out == null)
return -1;
return out.getSequence();
}
public void clearAcknowledgedOutbound(short sequence) {
synchronized (txMutex) {
if (txOverflow && sequence <= txSequence) {
outboundPackaged.removeIf(out -> out.getSequence() > txSequence);
txOverflow = false;
}
SequencedOutbound out = outboundPackaged.peek();
while (out != null && out.getSequence() <= sequence) {
outboundPackaged.poll();
out = outboundPackaged.peek();
}
}
}
public void fillOutboundPackagedBuffer(int maxPackaged) {
packager.handle(maxPackaged);
}
public Collection<SequencedOutbound> getOutboundPackagedBuffer() {
return Collections.unmodifiableCollection(outboundPackaged);
}
}
@@ -0,0 +1,31 @@
package com.projectswg.forwarder.resources.networking.data;
public class SequencedOutbound {
private final short sequence;
private final byte [] data;
private boolean sent;
public SequencedOutbound(short sequence, byte [] data) {
this.sequence = sequence;
this.data = data;
this.sent = false;
}
public short getSequence() {
return sequence;
}
public byte[] getData() {
return data;
}
public boolean isSent() {
return sent;
}
public void setSent(boolean sent) {
this.sent = sent;
}
}
@@ -0,0 +1,35 @@
package com.projectswg.forwarder.resources.networking.encryption;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4SafeDecompressor;
public class Compression {
private static final LZ4Compressor COMPRESSOR = LZ4Factory.safeInstance().highCompressor();
private static final LZ4SafeDecompressor DECOMPRESSOR = LZ4Factory.safeInstance().safeDecompressor();
public static byte [] compress(byte [] data) {
int maxCompressedLength = COMPRESSOR.maxCompressedLength(data.length);
byte[] compressed = new byte[maxCompressedLength];
int length = COMPRESSOR.compress(data, compressed);
byte [] ret = new byte[length];
System.arraycopy(compressed, 0, ret, 0, length);
return ret;
}
public static byte [] decompress(byte [] data) {
return decompress(data, data.length * 10);
}
public static byte [] decompress(byte [] data, int bufferSize) {
byte [] restored = new byte[bufferSize];
int length = DECOMPRESSOR.decompress(data, restored);
if (length == bufferSize)
return restored;
byte [] ret = new byte[length];
System.arraycopy(restored, 0, ret, 0, length);
return ret;
}
}
@@ -0,0 +1,164 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.encryption;
import me.joshlarson.jlcommon.log.Log;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class Encryption {
public static String md5(String plaintext) {
return MD5.digest(plaintext);
}
public static byte [] encode(byte [] input, int crc) {
try {
return assembleMessage(input, crc);
} catch (Throwable t) {
Log.e(t);
return new byte[0];
}
}
public static byte [] decode(byte [] input, int crc) {
try {
return disassemble(input, crc);
} catch (Throwable t) {
t.printStackTrace();
return new byte[0];
}
}
private static byte [] disassemble(byte [] data, int crcSeed) {
return decompress(encrypt(EncryptionCRC.validate(data, crcSeed), crcSeed, false));
}
private static byte [] assembleMessage(byte [] data, int crc) {
if (data.length < 2)
return data;
return EncryptionCRC.append(encrypt(compress(data), crc, true), crc);
}
public static byte [] encrypt(byte [] data, int iCrc, boolean encrypt) {
if (data.length < 2)
return data;
byte [] encrypted = new byte[data.length];
int start = 2;
encrypted[0] = data[0];
if (data[0] != 0)
start = 1;
else
encrypted[1] = data[1];
if (data.length < 6) {
for (int i = start; i < data.length; i++)
encrypted[i] = (byte) (data[i] ^ iCrc);
return encrypted;
}
int lastBytes = start+(int)((data.length-start)/4)*4;
byte [] crc = new byte[] {(byte)(iCrc), (byte)(iCrc >>> 8),(byte)(iCrc >>> 16), (byte)(iCrc >>> 24)};
for (int i = start; i < lastBytes; i++) {
encrypted[i] = (byte) (data[i] ^ crc[(i-start)%4]);
crc[(i-start)%4] = encrypt ? encrypted[i] : data[i];
}
if (lastBytes >= 4) { // Avoids an Index Out of Bounds Exception
for (int i = lastBytes; i < data.length; i++) { // Remaining 0-3 bytes that won't fit w/ the crc
byte lastCrc = encrypt ? encrypted[lastBytes-4] : data[lastBytes-4];
encrypted[i] = (byte) (data[i] ^ lastCrc);
}
}
return encrypted;
}
public static byte [] compress(byte [] data) {
if (data.length >= 200) {
byte [] result = new byte[512];
Deflater compressor = new Deflater();
compressor.setInput(data, 2, data.length - 2);
compressor.finish();
int length = 0;
try {
length = compressor.deflate(result);
} finally {
compressor.end();
}
if (length < data.length) {
ByteBuffer bb = ByteBuffer.allocate(length+3);
bb.put(data[0]).put(data[1]);
bb.put(result, 0, length);
bb.put((byte) 1);
return bb.array();
}
}
return ByteBuffer.allocate(data.length+1).put(data).put((byte) 0).array();
}
public static byte [] decompress(byte [] data) {
if (data == null || data.length == 0)
return data;
else if (data[data.length - 1] == 0)
return ByteBuffer.allocate(data.length - 1).put(data, 0, data.length - 1).array();
else if (data[data.length - 1] != 1)
return new byte[0];
int startingIndex = data[0] > 0 ? 1 : 2;
byte [] result = new byte[512];
int length = inflate(startingIndex, data, result);
if (length == -1)
return data;
byte [] ret = new byte[length+startingIndex];
ret[0] = data[0];
if (startingIndex > 1)
System.arraycopy(data, 1, ret, 1, startingIndex-1);
System.arraycopy(result, 0, ret, startingIndex, length);
return ret;
}
private static int inflate(int startingIndex, byte [] data, byte [] result) {
if (data == null || data.length - startingIndex < 0)
return -1;
Inflater decompressor = new Inflater();
decompressor.setInput(data, startingIndex, data.length - startingIndex);
try {
int length = decompressor.inflate(result);
return length;
} catch (DataFormatException e) {
Log.e("Failed to decompress packet. "+e.getClass().getSimpleName()+" Message: " + e.getMessage());
return -1;
} finally {
decompressor.end();
}
}
}
@@ -0,0 +1,103 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.encryption;
import java.nio.ByteBuffer;
class EncryptionCRC {
private static int [] crcTable = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
public static short generate(byte [] data, int crcSeed) {
return generate(data, 0, data.length, crcSeed);
}
public static short generate(byte [] data, int off, int len, int crcSeed) {
int crc = crcTable[(~crcSeed) & 0xFF];
int index;
if (data.length <= 4) return 0;
crc ^= 0x00FFFFFF;
for (int i = 1; i < 4; i++, crc = ((crc >> 8) & 0x00FFFFFF) ^ crcTable[index & 0xFF])
index = (crcSeed >> (8 * i) ^ crc);
for (int i = off; i < off+len; i++, crc = ((crc >> 8) & 0x00FFFFFF) ^ crcTable[index & 0xFF])
index = (data[i]) ^ crc;
return (short)~crc;
}
private static boolean isValid(byte[] data, int crcSeed) {
try {
short gen = generate(data, 0, data.length-2, crcSeed);
short crc = (short)((data[data.length-2] << 8) + (data[data.length-1] & 0xff));
return crc == gen;
} catch (ArrayIndexOutOfBoundsException e) {
}
return false;
}
public static byte[] append(byte[] data, int crcSeed) {
return ByteBuffer.allocate(data.length + 2).put(data).putShort(generate(data, crcSeed)).array();
}
public static byte[] validate(byte[] data, int crcSeed) {
if (isValid(data, crcSeed))
return ByteBuffer.allocate(data.length - 2).put(data, 0, data.length-2).array();
return new byte[] { };
}
}
@@ -0,0 +1,58 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.encryption;
import me.joshlarson.jlcommon.log.Log;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5 {
public static String digest(String text) {
String result = text;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(text.getBytes("iso-8859-1"), 0, text.length());
result = convertToHex(md.digest());
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
Log.e(e);
}
return result;
}
private static String convertToHex(byte[] b) {
StringBuilder result = new StringBuilder(32);
for (int i = 0; i < b.length; i++) {
result.append(Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 ));
}
return result.toString();
}
}
@@ -0,0 +1,65 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class Acknowledge extends Packet {
private short sequence;
public Acknowledge() {
sequence = 0;
}
public Acknowledge(ByteBuffer data) {
decode(data);
}
public Acknowledge(short sequence) {
this.sequence = sequence;
}
public void decode(ByteBuffer data) {
assert data.array().length == 4;
data.position(2);
sequence = getNetShort(data);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(4);
addNetShort(data, 21);
addNetShort(data, sequence);
return data;
}
public void setSequence(short sequence) { this.sequence = sequence; }
public short getSequence() { return sequence; }
}
@@ -0,0 +1,105 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class ClientNetworkStatusUpdate extends Packet {
private int clientTickCount;
private int lastUpdate;
private int avgUpdate;
private int shortUpdate;
private int longUpdate;
private int lastServerUpdate;
private long packetSent;
private long packetRecv;
public ClientNetworkStatusUpdate() {
}
public ClientNetworkStatusUpdate(ByteBuffer data) {
decode(data);
}
public ClientNetworkStatusUpdate(int clientTickCount, int lastUpdate, int avgUpdate, int shortUpdate, int longUpdate, int lastServerUpdate, long packetsSent, long packetsRecv) {
this.clientTickCount = clientTickCount;
this.lastUpdate = lastUpdate;
this.avgUpdate = avgUpdate;
this.shortUpdate = shortUpdate;
this.longUpdate = longUpdate;
this.lastServerUpdate = lastServerUpdate;
this.packetSent = packetsSent;
this.packetRecv = packetsRecv;
}
public void decode(ByteBuffer data) {
getNetShort(data); // 0x07
clientTickCount = getNetShort(data);
lastUpdate = getNetInt(data);
avgUpdate = getNetInt(data);
shortUpdate = getNetInt(data);
longUpdate = getNetInt(data);
lastServerUpdate = getNetInt(data);
packetSent = getNetLong(data);
packetRecv = getNetLong(data);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(40);
addNetShort(data, 7);
addNetShort(data, clientTickCount);
addNetInt( data, lastUpdate);
addNetInt( data, avgUpdate);
addNetInt( data, shortUpdate);
addNetInt( data, longUpdate);
addNetInt( data, lastServerUpdate);
addNetLong( data, packetSent);
addNetLong( data, packetRecv);
return data;
}
public int getTick() { return clientTickCount; }
public int getLastUpdate() { return lastUpdate; }
public int getAverageUpdate() { return avgUpdate; }
public int getShortestUpdate() { return shortUpdate; }
public int getLongestUpdate() { return longUpdate; }
public int getLastServerUpdate() { return lastServerUpdate; }
public long getSent() { return packetSent; }
public long getRecv() { return packetRecv; }
public void setTick(int tick) { this.clientTickCount = tick; }
public void setLastUpdate(int last) { this.lastUpdate = last; }
public void setAverageUpdate(int avg) { this.avgUpdate = avg; }
public void setShortestUpdate(int shortest) { this.shortUpdate = shortest; }
public void setLongestUpdate(int longest) { this.longUpdate = longest; }
public void setLastServerUpdate(int last) { this.lastServerUpdate = last; }
public void setPacketsSent(long sent) { this.packetSent = sent; }
public void setPacketsRecv(long recv) { this.packetRecv = recv; }
}
@@ -0,0 +1,216 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import com.projectswg.common.network.NetBuffer;
public class DataChannel extends Packet implements SequencedPacket {
private final List<byte[]> content;
private Channel channel;
private short sequence;
private short multiPacket;
public DataChannel() {
this.content = new ArrayList<>();
this.channel = Channel.DATA_CHANNEL_A;
this.sequence = 0;
this.multiPacket = 0;
}
public DataChannel(ByteBuffer data) {
this();
decode(data);
}
public DataChannel(byte[][] packets) {
this();
for (byte[] p : packets) {
content.add(p);
}
}
@Override
public void decode(ByteBuffer data) {
super.decode(data);
switch (getOpcode()) {
case 9: channel = Channel.DATA_CHANNEL_A; break;
case 10: channel = Channel.DATA_CHANNEL_B; break;
case 11: channel = Channel.DATA_CHANNEL_C; break;
case 12: channel = Channel.DATA_CHANNEL_D; break;
default: return;
}
data.position(2);
sequence = getNetShort(data);
multiPacket = getNetShort(data);
if (multiPacket == 0x19) {
int length = 0;
while (data.remaining() > 1) {
length = data.get() & 0xFF;
if (length == 0xFF)
length = getNetShort(data);
if (length > data.remaining()) {
data.position(data.position() - 1);
return;
}
byte[] pData = new byte[length];
data.get(pData);
content.add(pData);
}
} else {
data.position(data.position() - 2);
byte[] pData = new byte[data.remaining()];
data.get(pData);
content.add(pData);
}
}
@Override
public ByteBuffer encode() {
return encode(this.sequence);
}
public ByteBuffer encode(int sequence) {
this.sequence = (short) sequence;
NetBuffer data;
if (content.size() == 1) {
byte[] pData = content.get(0);
data = NetBuffer.allocate(4 + pData.length);
data.addNetShort(channel.getOpcode());
data.addNetShort(sequence);
data.addRawArray(pData);
} else if (content.size() > 1) {
data = NetBuffer.allocate(getLength());
data.addNetShort(channel.getOpcode());
data.addNetShort(sequence);
data.addNetShort(0x19);
for (byte[] pData : content) {
if (pData.length >= 0xFF) {
data.addByte(0xFF);
data.addNetShort(pData.length);
} else {
data.addByte(pData.length);
}
data.addRawArray(pData);
}
} else {
data = NetBuffer.allocate(0);
}
return data.getBuffer();
}
public void addPacket(byte[] packet) {
content.add(packet);
}
public void clearPackets() {
content.clear();
}
public int getLength() {
if (content.size() == 1) {
return 4 + content.get(0).length;
} else {
int length = 6;
for (byte[] packet : content) {
int addLength = packet.length;
length += 1 + addLength + ((addLength >= 0xFF) ? 2 : 0);
}
return length;
}
}
@Override
public int compareTo(SequencedPacket p) {
if (sequence < p.getSequence())
return -1;
if (sequence == p.getSequence())
return 0;
return 1;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DataChannel))
return false;
return ((DataChannel) o).sequence == sequence;
}
@Override
public int hashCode() {
return sequence;
}
public void setSequence(short sequence) {
this.sequence = sequence;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
@Override
public short getSequence() {
return sequence;
}
public Channel getChannel() {
return channel;
}
public List<byte[]> getPackets() {
return content;
}
public int getPacketCount() {
return content.size();
}
public enum Channel {
DATA_CHANNEL_A(9),
DATA_CHANNEL_B(10),
DATA_CHANNEL_C(11),
DATA_CHANNEL_D(12);
private final int opcode;
Channel(int opcode) {
this.opcode = opcode;
}
public int getOpcode() {
return opcode;
}
}
}
@@ -0,0 +1,102 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class Disconnect extends Packet {
private int connectionId;
private DisconnectReason reason;
public Disconnect() {
connectionId = 0;
reason = DisconnectReason.NONE;
}
public Disconnect(int connectionId, DisconnectReason reason) {
this.connectionId = connectionId;
this.reason = reason;
}
public Disconnect(ByteBuffer data){
this.decode(data);
}
public void decode(ByteBuffer data) {
data.position(2);
connectionId = getNetInt(data);
reason = getReason(getNetShort(data));
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(8);
addNetShort(data, 5);
addNetInt(data, connectionId);
addNetShort(data, reason.getReason());
return data;
}
public int getConnectionId() { return connectionId; }
public DisconnectReason getReason() { return reason; }
private DisconnectReason getReason(int reason) {
for (DisconnectReason dr : DisconnectReason.values())
if (dr.getReason() == reason)
return dr;
return DisconnectReason.NONE;
}
public enum DisconnectReason {
NONE (0x00),
ICMP_ERROR (0x01),
TIMEOUT (0x02),
OTHER_SIDE_TERMINATED (0x03),
MANAGER_DELETED (0x04),
CONNECT_FAIL (0x05),
APPLICATION (0x06),
UNREACHABLE_CONNECTION (0x07),
UNACKNOWLEDGED_TIMEOUT (0x08),
NEW_CONNECTION_ATTEMPT (0x09),
CONNECTION_REFUSED (0x0A),
MUTUAL_CONNETION_ERROR (0x0B),
CONNETING_TO_SELF (0x0C),
RELIABLE_OVERFLOW (0x0D),
COUNT (0x0E);
private short reason;
DisconnectReason(int reason) {
this.reason = (short) reason;
}
public short getReason() {
return reason;
}
}
}
@@ -0,0 +1,136 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class Fragmented extends Packet implements SequencedPacket {
private short sequence;
private int length;
private ByteBuffer packet;
private ByteBuffer data;
public Fragmented() {
this.sequence = 0;
this.length = 0;
this.packet = null;
this.data = null;
}
public Fragmented(ByteBuffer data) {
decode(data);
length = -1;
}
public Fragmented(ByteBuffer data, int sequence) {
this.sequence = (short) sequence;
decode(data);
length = -1;
}
public void setPacket(ByteBuffer packet) {
this.packet = packet;
}
public void decode(ByteBuffer data) {
data.position(2);
sequence = getNetShort(data);
this.data = data;
}
public ByteBuffer encode() {
return data;
}
public Fragmented [] encode(int startSequence) {
packet.position(0);
int ord = 0;
Fragmented [] packets = new Fragmented[(int) Math.ceil((packet.remaining()+4)/489.0)];
while (packet.remaining() > 0) {
packets[ord] = createSegment(startSequence++, ord++, packet);
}
return packets;
}
@Override
public int compareTo(SequencedPacket p) {
if (sequence < p.getSequence())
return -1;
if (sequence == p.getSequence())
return 0;
return 1;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Fragmented))
return super.equals(o);
return ((Fragmented) o).sequence == sequence;
}
@Override
public int hashCode() {
return sequence;
}
public ByteBuffer getPacketData() { return data; }
public short getSequence() { return sequence; }
public int getDatLength() { return length; }
public static final Fragmented [] encode(ByteBuffer data, int startSequence) {
data.position(0);
int ord = 0;
Fragmented [] packets = new Fragmented[(int) Math.ceil((data.remaining()+4)/489.0)];
while (data.remaining() > 0) {
packets[ord] = createSegment(startSequence++, ord++, data);
}
return packets;
}
private static final Fragmented createSegment(int startSequence, int ord, ByteBuffer packet) {
int header = (ord == 0) ? 8 : 4;
ByteBuffer data = ByteBuffer.allocate(Math.min(packet.remaining()+header, 493));
addNetShort(data, 0x0D);
addNetShort(data, startSequence);
if (ord == 0)
addNetInt(data, packet.remaining());
int len = data.remaining();
data.put(packet.array(), packet.position(), len);
packet.position(packet.position() + len);
byte [] pData = new byte[data.array().length-header];
System.arraycopy(data.array(), header, pData, 0, pData.length);
Fragmented f = new Fragmented(data, startSequence);
f.packet = ByteBuffer.wrap(pData);
return f;
}
}
@@ -0,0 +1,25 @@
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class KeepAlive extends Packet {
public KeepAlive() {
}
public KeepAlive(ByteBuffer data) {
decode(data);
}
public void decode(ByteBuffer data) {
data.position(2);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(2);
addNetShort(data, 0x06);
return data;
}
}
@@ -0,0 +1,115 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import com.projectswg.common.network.NetBuffer;
public class MultiPacket extends Packet {
private final List <byte []> content;
public MultiPacket() {
this(new ArrayList<>());
}
public MultiPacket(ByteBuffer data) {
this(new ArrayList<>());
decode(data);
}
public MultiPacket(List <byte []> packets) {
this.content = packets;
}
@Override
public void decode(ByteBuffer data) {
data.position(2);
int pLength = getNextPacketLength(data);
while (data.remaining() >= pLength && pLength > 0) {
byte [] pData = new byte[pLength];
data.get(pData);
content.add(pData);
pLength = getNextPacketLength(data);
}
}
@Override
public ByteBuffer encode() {
NetBuffer data = NetBuffer.allocate(getLength());
data.addNetShort(3);
for (byte [] packet : content) {
if (packet.length >= 255) {
data.addByte(255);
data.addShort(packet.length);
} else {
data.addByte(packet.length);
}
data.addRawArray(packet);
}
return data.getBuffer();
}
public int getLength() {
int length = 2;
for (byte [] packet : content) {
length += packet.length + 1;
if (packet.length >= 255)
length += 2;
}
return length;
}
public void addPacket(byte [] packet) {
content.add(packet);
}
public void clearPackets() {
content.clear();
}
public List <byte []> getPackets() {
return content;
}
private int getNextPacketLength(ByteBuffer data) {
if (data.remaining() < 1)
return 0;
int length = data.get() & 0xFF;
if (length == 255) {
if (data.remaining() < 2)
return 0;
return data.getShort() & 0xFFFF;
}
return length;
}
}
@@ -0,0 +1,61 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class OutOfOrder extends Packet {
private short sequence;
public OutOfOrder() {
}
public OutOfOrder(short sequence) {
this.sequence = sequence;
}
public OutOfOrder(ByteBuffer data) {
decode(data);
}
public void decode(ByteBuffer data) {
data.position(2);
sequence = getNetShort(data);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(4);
addNetShort(data, 0x11);
addNetShort(data, sequence);
return data;
}
public short getSequence() { return sequence; }
}
@@ -0,0 +1,256 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.List;
public class Packet {
public static final Charset ascii = Charset.forName("UTF-8");
public static final Charset unicode = Charset.forName("UTF-16LE");
private InetAddress address;
private ByteBuffer data;
private int port = 0;
private int opcode;
public Packet() {
data = ByteBuffer.allocate(2);
}
public Packet(ByteBuffer data) {
decode(data);
}
public void setAddress(InetAddress address) {
this.address = address;
}
public InetAddress getAddress() {
return address;
}
public void setPort(int port) {
this.port = port;
}
public int getPort() {
return port;
}
public void setOpcode(int opcode) {
this.opcode = opcode;
}
public int getOpcode() {
return opcode;
}
public static void addList(ByteBuffer bb, List<byte[]> list) {
addInt(bb, list.size());
for (byte[] bytes : list) {
addData(bb, bytes);
}
}
public static void addBoolean(ByteBuffer bb, boolean b) {
bb.put(b ? (byte) 1 : (byte) 0);
}
public static void addAscii(ByteBuffer bb, String s) {
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putShort((short) s.length());
bb.put(s.getBytes(ascii));
}
public static void addUnicode(ByteBuffer bb, String s) {
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(s.length());
bb.put(s.getBytes(unicode));
}
public static void addLong(ByteBuffer bb, long l) {
bb.order(ByteOrder.LITTLE_ENDIAN).putLong(l);
}
public static void addInt(ByteBuffer bb, int i) {
bb.order(ByteOrder.LITTLE_ENDIAN).putInt(i);
}
public static void addFloat(ByteBuffer bb, float f) {
bb.putFloat(f);
}
public static void addShort(ByteBuffer bb, int i) {
bb.order(ByteOrder.LITTLE_ENDIAN).putShort((short) i);
}
public static void addNetLong(ByteBuffer bb, long l) {
bb.order(ByteOrder.BIG_ENDIAN).putLong(l);
}
public static void addNetInt(ByteBuffer bb, int i) {
bb.order(ByteOrder.BIG_ENDIAN).putInt(i);
}
public static void addNetShort(ByteBuffer bb, int i) {
bb.order(ByteOrder.BIG_ENDIAN).putShort((short) i);
}
public static void addByte(ByteBuffer bb, int b) {
bb.put((byte) b);
}
public static void addData(ByteBuffer bb, byte[] data) {
bb.put(data);
}
public static void addArray(ByteBuffer bb, byte[] data) {
bb.order(ByteOrder.LITTLE_ENDIAN);
addShort(bb, data.length);
addData(bb, data);
}
public static void addArrayList(ByteBuffer bb, byte[] b) {
addShort(bb, b.length);
bb.put(b);
}
public static boolean getBoolean(ByteBuffer bb) {
return getByte(bb) == 1;
}
public static String getAscii(ByteBuffer bb) {
bb.order(ByteOrder.LITTLE_ENDIAN);
short length = bb.getShort();
if (length > bb.remaining())
return "";
byte [] str = new byte[length];
bb.get(str);
return new String(str, ascii);
}
public static String getUnicode(ByteBuffer bb) {
bb.order(ByteOrder.LITTLE_ENDIAN);
int length = bb.getInt() * 2;
if (length > bb.remaining())
return "";
byte [] str = new byte[length];
bb.get(str);
return new String(str, unicode);
}
public static byte getByte(ByteBuffer bb) {
return bb.get();
}
public static short getShort(ByteBuffer bb) {
return bb.order(ByteOrder.LITTLE_ENDIAN).getShort();
}
public static int getInt(ByteBuffer bb) {
return bb.order(ByteOrder.LITTLE_ENDIAN).getInt();
}
public static float getFloat(ByteBuffer bb) {
return bb.getFloat();
}
public static long getLong(ByteBuffer bb) {
return bb.order(ByteOrder.LITTLE_ENDIAN).getLong();
}
public static short getNetShort(ByteBuffer bb) {
return bb.order(ByteOrder.BIG_ENDIAN).getShort();
}
public static int getNetInt(ByteBuffer bb) {
return bb.order(ByteOrder.BIG_ENDIAN).getInt();
}
public static long getNetLong(ByteBuffer bb) {
return bb.order(ByteOrder.BIG_ENDIAN).getLong();
}
public static byte [] getArray(ByteBuffer bb) {
byte [] data = new byte[getShort(bb)];
bb.get(data);
return data;
}
public static byte [] getArray(ByteBuffer bb, int length) {
byte [] data = new byte[length];
bb.get(data);
return data;
}
public static int [] getIntArray(ByteBuffer bb) {
bb.order(ByteOrder.LITTLE_ENDIAN);
int [] ints = new int[bb.getInt()];
for (int i = 0; i < ints.length; i++)
ints[i] = bb.getInt();
return ints;
}
public static int [] getIntArray(ByteBuffer bb, int size) {
bb.order(ByteOrder.LITTLE_ENDIAN);
int [] ints = new int[size];
for (int i = 0; i < ints.length; i++)
ints[i] = bb.getInt();
return ints;
}
public static boolean[] getBooleanArray(ByteBuffer bb) {
bb.order(ByteOrder.LITTLE_ENDIAN);
boolean[] booleans = new boolean[bb.getInt()];
for(int i = 0; i < booleans.length; i++)
booleans[i] = getBoolean(bb);
return booleans;
}
public void decode(ByteBuffer data) {
data.position(0);
this.data = data;
opcode = getNetShort(data);
data.position(0);
}
public ByteBuffer getData() {
return data;
}
public ByteBuffer encode() {
return data;
}
}
@@ -0,0 +1,27 @@
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class PingPacket extends Packet {
private byte [] payload;
public PingPacket(byte [] payload) {
this.payload = payload;
}
@Override
public void decode(ByteBuffer data) {
this.payload = data.array();
}
@Override
public ByteBuffer encode() {
return ByteBuffer.wrap(payload);
}
public byte [] getPayload() {
return payload;
}
}
@@ -0,0 +1,41 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
public class RawSWGPacket extends Packet {
private byte [] data;
public RawSWGPacket(byte [] data) {
this.data = data;
}
public byte[] getRawData() {
return data;
}
}
@@ -0,0 +1,5 @@
package com.projectswg.forwarder.resources.networking.packets;
public interface SequencedPacket extends Comparable<SequencedPacket> {
short getSequence();
}
@@ -0,0 +1,25 @@
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class SeriousErrorAcknowledge extends Packet {
public SeriousErrorAcknowledge() {
}
public SeriousErrorAcknowledge(ByteBuffer data) {
decode(data);
}
public void decode(ByteBuffer data) {
data.position(2);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(2);
addNetShort(data, 0x1D);
return data;
}
}
@@ -0,0 +1,25 @@
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class SeriousErrorAcknowledgeReply extends Packet {
public SeriousErrorAcknowledgeReply() {
}
public SeriousErrorAcknowledgeReply(ByteBuffer data) {
decode(data);
}
public void decode(ByteBuffer data) {
data.position(2);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(2);
addNetShort(data, 0x1E);
return data;
}
}
@@ -0,0 +1,94 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
public class ServerNetworkStatusUpdate extends Packet {
private short clientTickCount = 0;
private int serverSyncStampLong = 0;
private long clientPacketsSent = 0;
private long clientPacketsRecv = 0;
private long serverPacketsSent = 0;
private long serverPacketsRecv = 0;
public ServerNetworkStatusUpdate() {
}
public ServerNetworkStatusUpdate(ByteBuffer data) {
decode(data);
}
public ServerNetworkStatusUpdate(int clientTickCount, int serverSyncStamp, long clientSent, long clientRecv, long serverSent, long serverRecv) {
this.clientTickCount = (short) clientTickCount;
this.serverSyncStampLong = serverSyncStamp;
this.clientPacketsSent = clientSent;
this.clientPacketsRecv = clientRecv;
this.serverPacketsSent = serverSent;
this.serverPacketsRecv = serverRecv;
}
public void decode(ByteBuffer data) {
data.position(2);
clientTickCount = getNetShort(data);
serverSyncStampLong = getNetInt(data);
clientPacketsSent = getNetLong(data);
clientPacketsRecv = getNetLong(data);
serverPacketsSent = getNetLong(data);
serverPacketsRecv = getNetLong(data);
}
public ByteBuffer encode() {
ByteBuffer data = ByteBuffer.allocate(40);
addNetShort(data, 8);
addNetShort(data, clientTickCount);
addNetInt( data, serverSyncStampLong);
addNetLong( data, clientPacketsSent);
addNetLong( data, clientPacketsRecv);
addNetLong( data, serverPacketsSent);
addNetLong( data, serverPacketsRecv);
return data;
}
public short getClientTickCount() { return clientTickCount; }
public int getServerSyncStampLong() { return serverSyncStampLong; }
public long getClientPacketsSent() { return clientPacketsSent; }
public long getClientPacketsRecv() { return clientPacketsRecv; }
public long getServerPacketsSent() { return serverPacketsSent; }
public long getServerPacketsRecv() { return serverPacketsRecv; }
public void setClientTickCount(short tick) { this.clientTickCount = tick; }
public void setServerSyncStampLong(int sync) { this.serverSyncStampLong = sync; }
public void setClientPacketsSent(long sent) { this.clientPacketsSent = sent; }
public void setClientPacketsRecv(long recv) { this.clientPacketsRecv = recv; }
public void setServerPacketsSent(long sent) { this.serverPacketsSent = sent; }
public void setServerPacketsRecv(long recv) { this.serverPacketsRecv = recv; }
}
@@ -0,0 +1,74 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class SessionRequest extends Packet {
private int crcLength;
private int connectionId;
private int udpSize;
public SessionRequest() {
}
public SessionRequest(ByteBuffer data) {
decode(data);
}
public SessionRequest(int crcLength, int connectionId, int udpSize) {
this.crcLength = crcLength;
this.connectionId = connectionId;
this.udpSize = udpSize;
}
public void decode(ByteBuffer packet) {
super.decode(packet);
packet.position(2);
crcLength = getNetInt(packet);
connectionId = getNetInt(packet);
udpSize = getNetInt(packet);
}
public ByteBuffer encode() {
ByteBuffer bb = ByteBuffer.allocate(14).order(ByteOrder.BIG_ENDIAN);
addNetShort(bb, 1);
addNetInt(bb, crcLength);
addNetInt(bb, connectionId);
addNetInt(bb, udpSize);
return bb;
}
public int getCrcLength() { return crcLength; }
public int getConnectionId() { return connectionId; }
public int getUdpSize() { return udpSize; }
}
@@ -0,0 +1,95 @@
/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.resources.networking.packets;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class SessionResponse extends Packet {
private int connectionId;
private int crcSeed;
private byte crcLength;
private byte encryptionFlag;
private byte xorLength;
private int udpSize;
public SessionResponse() {
}
public SessionResponse(ByteBuffer data) {
decode(data);
}
public SessionResponse(int connectionId, int crcSeed, byte crcLength, byte encryptionFlag, byte xorLength, int udpSize) {
this.connectionId = connectionId;
this.crcSeed = crcSeed;
this.crcLength = crcLength;
this.encryptionFlag = encryptionFlag;
this.xorLength = xorLength;
this.udpSize = udpSize;
}
public void decode(ByteBuffer data) {
super.decode(data);
data.position(2);
connectionId = getNetInt(data);
crcSeed = getNetInt(data);
crcLength = data.get();
encryptionFlag = data.get();
xorLength = data.get();
udpSize = getNetInt(data);
}
public ByteBuffer encode() {
ByteBuffer bb = ByteBuffer.allocate(17).order(ByteOrder.BIG_ENDIAN);
addNetShort(bb, 2);
addNetInt( bb, connectionId);
addNetInt( bb, crcSeed);
addByte( bb, crcLength);
addByte( bb, encryptionFlag);
addByte( bb, xorLength);
addNetInt( bb, udpSize);
return bb;
}
public int getConnectionId() { return connectionId; }
public int getCrcSeed() { return crcSeed; }
public byte getCrcLength() { return crcLength; }
public short getEncryptionFlag() { return encryptionFlag; }
public byte getXorLength() { return xorLength; }
public int getUdpSize() { return udpSize; }
public void setConnectionId(int id) { this.connectionId = id; }
public void setCrcSeed(int crc) { this.crcSeed = crc; }
public void setCrcLength(int length) { this.crcLength = (byte) length; }
public void setEncryptionFlag(short flag) { this.encryptionFlag = (byte) flag; }
public void setXorLength(byte xorLength) { this.xorLength = xorLength; }
public void setUdpSize(int size) { this.udpSize = size; }
}
@@ -0,0 +1,69 @@
package com.projectswg.forwarder.resources.recording;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import java.util.TreeMap;
public class PacketRecorder implements AutoCloseable, Closeable {
private static final byte VERSION = 2;
private final DataOutputStream dataOut;
public PacketRecorder(File file) throws FileNotFoundException {
dataOut = new DataOutputStream(new FileOutputStream(file));
writeHeader();
}
public void close() throws IOException {
dataOut.close();
}
public void record(boolean server, byte [] data) {
synchronized (dataOut) {
try {
dataOut.writeByte(server?1:0);
dataOut.writeLong(System.currentTimeMillis());
dataOut.writeShort(data.length);
dataOut.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void writeHeader() {
try {
dataOut.writeByte(VERSION);
writeSystemHeader();
} catch (IOException e) {
e.printStackTrace();
}
}
private void writeSystemHeader() {
OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
TimeZone tz = TimeZone.getDefault();
Map<String, String> systemStrings = new TreeMap<>();
systemStrings.put("os.arch", os.getArch());
systemStrings.put("os.details", os.getName()+":"+os.getVersion());
systemStrings.put("os.processor_count", Integer.toString(os.getAvailableProcessors()));
systemStrings.put("java.version", System.getProperty("java.version"));
systemStrings.put("java.vendor", System.getProperty("java.vendor"));
systemStrings.put("time.time_zone", tz.getID()+":"+tz.getDisplayName());
systemStrings.put("time.current_time", Long.toString(System.currentTimeMillis()));
try {
dataOut.writeByte(systemStrings.size()); // Count of strings
for (Entry<String, String> e : systemStrings.entrySet())
dataOut.writeUTF(e.getKey() + "=" + e.getValue());
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,14 @@
package com.projectswg.forwarder.services.client;
import me.joshlarson.jlcommon.control.Manager;
import me.joshlarson.jlcommon.control.ManagerStructure;
@ManagerStructure(children = {
ClientInboundDataService.class,
ClientOutboundDataService.class,
ClientProtocolService.class,
ClientServerService.class
})
public class ClientConnectionManager extends Manager {
}
@@ -0,0 +1,105 @@
package com.projectswg.forwarder.services.client;
import com.projectswg.common.network.packets.PacketType;
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import com.projectswg.forwarder.resources.networking.packets.*;
import me.joshlarson.jlcommon.control.IntentChain;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.IntentMultiplexer;
import me.joshlarson.jlcommon.control.IntentMultiplexer.Multiplexer;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.atomic.AtomicReference;
public class ClientInboundDataService extends Service {
private final IntentMultiplexer multiplexer;
private final AtomicReference<ProtocolStack> stack;
private final IntentChain intentChain;
public ClientInboundDataService() {
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
this.stack = new AtomicReference<>(null);
this.intentChain = new IntentChain();
}
@IntentHandler
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
ProtocolStack stack = this.stack.get();
assert stack != null : "stack is null";
multiplexer.call(stack, spii.getPacket());
}
@IntentHandler
private void handleUpdateStackIntent(UpdateStackIntent sci) {
stack.set(sci.getStack());
}
@Multiplexer
private void handlePingPacket(ProtocolStack stack, PingPacket ping) {
onData(new HeartBeat(ping.getPayload()).encode().array());
}
@Multiplexer
private void handleRawSwgPacket(ProtocolStack stack, RawSWGPacket data) {
onData(data.getRawData());
}
@Multiplexer
private void handleDataChannel(ProtocolStack stack, DataChannel data) {
if (stack.addIncoming(data)) {
readAvailablePackets(stack);
} else {
Log.d("Inbound Out of Order %d (data)", data.getSequence());
for (short seq = stack.getRxSequence(); seq < data.getSequence(); seq++)
stack.send(new OutOfOrder(seq));
}
}
@Multiplexer
private void handleFragmented(ProtocolStack stack, Fragmented frag) {
if (stack.addIncoming(frag)) {
readAvailablePackets(stack);
} else {
Log.d("Inbound Out of Order %d (frag)", frag.getSequence());
for (short seq = stack.getRxSequence(); seq < frag.getSequence(); seq++)
stack.send(new OutOfOrder(seq));
}
}
private void readAvailablePackets(ProtocolStack stack) {
short highestSequence = -1;
SequencedPacket packet = stack.getNextIncoming();
while (packet != null) {
if (packet instanceof DataChannel) {
for (byte [] data : ((DataChannel) packet).getPackets())
onData(data);
} else if (packet instanceof Fragmented) {
byte [] data = stack.addFragmented((Fragmented) packet);
if (data != null)
onData(data);
}
Log.t("Data Inbound: %s", packet);
highestSequence = packet.getSequence();
packet = stack.getNextIncoming();
}
if (highestSequence != -1) {
Log.t("Inbound Acknowledge %d", highestSequence);
stack.send(new Acknowledge(highestSequence));
}
}
private void onData(byte [] data) {
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
Log.d("Incoming Data: %s", type);
intentChain.broadcastAfter(getIntentManager(), new DataPacketInboundIntent(data));
}
}
@@ -0,0 +1,178 @@
package com.projectswg.forwarder.services.client;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.packets.PacketType;
import com.projectswg.common.network.packets.swg.zone.HeartBeat;
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
import com.projectswg.forwarder.resources.client.state.OutboundDataTuner;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import com.projectswg.forwarder.resources.networking.data.SequencedOutbound;
import com.projectswg.forwarder.resources.networking.packets.Acknowledge;
import com.projectswg.forwarder.resources.networking.packets.OutOfOrder;
import com.projectswg.forwarder.resources.networking.packets.Packet;
import me.joshlarson.jlcommon.concurrency.Delay;
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.IntentMultiplexer;
import me.joshlarson.jlcommon.control.IntentMultiplexer.Multiplexer;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.atomic.AtomicReference;
public class ClientOutboundDataService extends Service {
private static final int TIMER_DELAY = 20;
private final IntentMultiplexer multiplexer;
private final AtomicReference<ProtocolStack> stack;
private final ScheduledThreadPool timerThread;
private final OutboundDataTuner tuner;
private final Object outboundMutex;
public ClientOutboundDataService() {
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
this.stack = new AtomicReference<>(null);
this.timerThread = new ScheduledThreadPool(2, 5, "outbound-sender-%d");
this.tuner = new OutboundDataTuner();
this.outboundMutex = new Object();
}
@IntentHandler
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
ProtocolStack stack = this.stack.get();
assert stack != null : "stack is null";
multiplexer.call(stack, spii.getPacket());
}
@IntentHandler
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
if (timerThread.isRunning())
return;
timerThread.start();
timerThread.executeWithFixedDelay(TIMER_DELAY, TIMER_DELAY, this::timerCallback);
timerThread.executeWithFixedRate(0, 5000, this::clearSentBit);
}
@IntentHandler
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
if (!timerThread.isRunning())
return;
timerThread.stop();
timerThread.awaitTermination(500);
}
@IntentHandler
private void handleUpdateStackIntent(UpdateStackIntent sci) {
stack.set(sci.getStack());
}
@IntentHandler
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
ProtocolStack stack = this.stack.get();
if (stack == null)
return;
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(dpii.getData()).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
switch (type) {
case LAG_REQUEST:
tuner.markLag();
break;
}
}
@IntentHandler
private void handleDataPacketOutboundIntent(DataPacketOutboundIntent dpoi) {
ProtocolStack stack = this.stack.get();
if (stack == null)
return;
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(dpoi.getData()).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
switch (type) {
case CMD_START_SCENE:
tuner.markStart();
break;
case CMD_SCENE_READY:
tuner.markEnd();
break;
case HEART_BEAT_MESSAGE: {
HeartBeat heartbeat = new HeartBeat();
heartbeat.decode(NetBuffer.wrap(dpoi.getData()));
if (heartbeat.getPayload().length > 0) {
stack.sendPing(heartbeat.getPayload());
return;
}
break;
}
}
synchronized (outboundMutex) {
stack.addOutbound(dpoi.getData());
}
}
@Multiplexer
private void handleAcknowledgement(ProtocolStack stack, Acknowledge ack) {
Log.t("Acknowledged: %d. Min Sequence: %d", ack.getSequence(), stack.getFirstUnacknowledgedOutbound());
tuner.markAck();
synchronized (outboundMutex) {
stack.clearAcknowledgedOutbound(ack.getSequence());
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
outbound.setSent(false);
}
}
}
@Multiplexer
private void handleOutOfOrder(ProtocolStack stack, OutOfOrder ooo) {
tuner.markOOO();
synchronized (outboundMutex) {
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
if (outbound.getSequence() > ooo.getSequence())
break;
outbound.setSent(false);
}
}
}
private void clearSentBit() {
ProtocolStack stack = this.stack.get();
if (stack == null)
return;
synchronized (outboundMutex) {
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
outbound.setSent(false);
}
}
}
private void timerCallback() {
ProtocolStack stack = this.stack.get();
if (stack == null)
return;
int maxSend = tuner.getMaxSend();
synchronized (outboundMutex) {
stack.fillOutboundPackagedBuffer(maxSend);
int sent = 0;
int runStart = Integer.MIN_VALUE;
int runEnd = 0;
for (SequencedOutbound outbound : stack.getOutboundPackagedBuffer()) {
runEnd = outbound.getSequence();
if (runStart == Integer.MIN_VALUE) {
runStart = runEnd;
}
stack.send(outbound.getData());
Delay.sleepMicro(50);
if (sent++ >= maxSend)
break;
}
if (runStart != Integer.MIN_VALUE)
Log.t("Sending to %s: %d - %d", stack.getSource(), runStart, runEnd);
}
}
}
@@ -0,0 +1,58 @@
package com.projectswg.forwarder.services.client;
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import com.projectswg.forwarder.resources.networking.packets.ClientNetworkStatusUpdate;
import com.projectswg.forwarder.resources.networking.packets.KeepAlive;
import com.projectswg.forwarder.resources.networking.packets.Packet;
import com.projectswg.forwarder.resources.networking.packets.ServerNetworkStatusUpdate;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.IntentMultiplexer;
import me.joshlarson.jlcommon.control.IntentMultiplexer.Multiplexer;
import me.joshlarson.jlcommon.control.Service;
import java.util.concurrent.atomic.AtomicReference;
public class ClientProtocolService extends Service {
private static final int GALACTIC_BASE_TIME = 1323043200;
private final AtomicReference<ProtocolStack> stack;
private final IntentMultiplexer multiplexer;
public ClientProtocolService() {
this.stack = new AtomicReference<>(null);
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
}
@IntentHandler
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
ProtocolStack stack = this.stack.get();
assert stack != null : "stack is null";
multiplexer.call(stack, spii.getPacket());
}
@IntentHandler
private void handleUpdateStackIntent(UpdateStackIntent sci) {
stack.set(sci.getStack());
}
@Multiplexer
private void handleClientNetworkStatus(ProtocolStack stack, ClientNetworkStatusUpdate update) {
ServerNetworkStatusUpdate serverNet = new ServerNetworkStatusUpdate();
serverNet.setClientTickCount((short) update.getTick());
serverNet.setServerSyncStampLong((int) (System.currentTimeMillis()-GALACTIC_BASE_TIME));
serverNet.setClientPacketsSent(update.getSent());
serverNet.setClientPacketsRecv(update.getRecv());
serverNet.setServerPacketsSent(stack.getTxSequence());
serverNet.setServerPacketsRecv(stack.getRxSequence());
stack.send(serverNet);
}
@Multiplexer
private void handleKeepAlive(ProtocolStack stack, KeepAlive keepAlive) {
stack.send(new KeepAlive());
}
}
@@ -0,0 +1,212 @@
package com.projectswg.forwarder.services.client;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
import com.projectswg.forwarder.resources.networking.ClientServer;
import com.projectswg.forwarder.resources.networking.UDPServer;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import com.projectswg.forwarder.resources.networking.packets.*;
import com.projectswg.forwarder.resources.networking.packets.Disconnect.DisconnectReason;
import me.joshlarson.jlcommon.control.IntentChain;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import me.joshlarson.jlcommon.utilities.ByteUtilities;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicReference;
public class ClientServerService extends Service {
private final IntentChain intentChain;
private final AtomicReference<ProtocolStack> stack;
private ForwarderData data;
private UDPServer loginServer;
private UDPServer zoneServer;
public ClientServerService() {
this.intentChain = new IntentChain();
this.stack = new AtomicReference<>(null);
this.data = null;
this.loginServer = null;
this.zoneServer = null;
}
@Override
public boolean isOperational() {
return data == null || (loginServer != null && zoneServer != null && loginServer.isRunning() && zoneServer.isRunning());
}
@Override
public boolean stop() {
setStack(null);
return true;
}
@IntentHandler
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
try {
ForwarderData data = sfi.getData();
Log.t("Initializing login udp server...");
loginServer = new UDPServer(new InetSocketAddress(InetAddress.getLoopbackAddress(), data.getLoginPort()), 496, this::onLoginPacket);
Log.t("Initializing zone udp server...");
zoneServer = new UDPServer(new InetSocketAddress(InetAddress.getLoopbackAddress(), data.getZonePort()), 496, this::onZonePacket);
Log.t("Binding to login server...");
loginServer.bind(this::customizeUdpServer);
Log.t("Binding to zone server...");
zoneServer.bind(this::customizeUdpServer);
data.setLoginPort(loginServer.getPort());
data.setZonePort(zoneServer.getPort());
Log.i("Initialized login (%d) and zone servers (%d)", loginServer.getPort(), zoneServer.getPort());
} catch (SocketException e) {
Log.a(e);
if (loginServer != null)
loginServer.close();
if (zoneServer != null)
zoneServer.close();
loginServer = null;
zoneServer = null;
}
data = sfi.getData();
}
@IntentHandler
private void handleStopForwarderIntent(StopForwarderIntent sfi) {
Log.t("Closing the login udp server...");
if (loginServer != null)
loginServer.close();
Log.t("Closing the zone udp server...");
if (zoneServer != null)
zoneServer.close();
loginServer = null;
zoneServer = null;
Log.i("Closed the login and zone udp servers");
}
private void customizeUdpServer(DatagramSocket socket) {
try {
socket.setReuseAddress(false);
socket.setTrafficClass(0x02 | 0x04 | 0x08 | 0x10);
socket.setBroadcast(false);
socket.setReceiveBufferSize(496 * 2048);
socket.setSendBufferSize(496 * 2048);
} catch (SocketException e) {
Log.w(e);
}
}
private void onLoginPacket(DatagramPacket packet) {
process((InetSocketAddress) packet.getSocketAddress(), ClientServer.LOGIN, packet.getData());
}
private void onZonePacket(DatagramPacket packet) {
process((InetSocketAddress) packet.getSocketAddress(), ClientServer.ZONE, packet.getData());
}
private void send(InetSocketAddress addr, ClientServer server, byte [] data) {
switch (server) {
case LOGIN:
loginServer.send(addr, data);
break;
case ZONE:
zoneServer.send(addr, data);
break;
}
}
private void process(InetSocketAddress source, ClientServer server, byte [] data) {
Packet parsed = parse(data);
if (parsed == null)
return;
if (parsed instanceof MultiPacket) {
for (byte [] child : ((MultiPacket) parsed).getPackets()) {
process(source, server, child);
}
} else {
broadcast(source, server, parsed);
}
}
private void broadcast(InetSocketAddress source, ClientServer server, Packet parsed) {
ProtocolStack stack = this.stack.get();
if (parsed instanceof SessionRequest) {
stack = new ProtocolStack(source, server, (remote, data) -> send(remote, server, data));
stack.setConnectionId(((SessionRequest) parsed).getConnectionId());
setStack(stack);
stack.send(new SessionResponse(((SessionRequest) parsed).getConnectionId(), 0, (byte) 0, (byte) 0, (byte) 0, 496));
}
if (stack != null && parsed instanceof PingPacket) {
stack.setPingSource(source);
} else if (stack == null || !stack.getSource().equals(source) || stack.getServer() != server) {
Log.t("[%s]@%s DROPPED %s", source, server, parsed);
return;
}
Log.t("[%s]@%s sent: %s", source, server, parsed);
intentChain.broadcastAfter(getIntentManager(), new SonyPacketInboundIntent(parsed));
if (parsed instanceof Disconnect) {
setStack(null);
}
}
private void setStack(ProtocolStack stack) {
ProtocolStack oldStack = this.stack.getAndSet(stack);
if (oldStack != null) {
oldStack.send(new Disconnect(oldStack.getConnectionId(), DisconnectReason.MANAGER_DELETED));
if (stack == null)
intentChain.broadcastAfter(getIntentManager(), new ClientDisconnectedIntent());
}
if (stack != null && stack.getServer() == ClientServer.LOGIN) {
intentChain.broadcastAfter(getIntentManager(), new ClientConnectedIntent());
}
intentChain.broadcastAfter(getIntentManager(), new UpdateStackIntent(stack));
}
private static Packet parse(byte [] rawData) {
if (rawData.length < 4)
return null;
ByteBuffer data = ByteBuffer.wrap(rawData);
short opcode = data.getShort(0);
switch (opcode) {
case 0x01: return new SessionRequest(data);
case 0x03: return new MultiPacket(data);
case 0x05: return new Disconnect(data);
case 0x06: return new KeepAlive(data);
case 0x07: return new ClientNetworkStatusUpdate(data);
case 0x09:
case 0x0A:
case 0x0B:
case 0x0C: return new DataChannel(data);
case 0x0D:
case 0x0E:
case 0x0F:
case 0x10: return new Fragmented(data);
case 0x11:
case 0x12:
case 0x13:
case 0x14: return new OutOfOrder(data);
case 0x15:
case 0x16:
case 0x17:
case 0x18: return new Acknowledge(data);
default:
if (rawData.length == 4)
return new PingPacket(rawData);
if (rawData.length >= 6)
return new RawSWGPacket(rawData);
Log.w("Unknown SOE packet: %d %s", opcode, ByteUtilities.getHexString(data.array()));
return null;
}
}
}
@@ -0,0 +1,12 @@
package com.projectswg.forwarder.services.crash;
import me.joshlarson.jlcommon.control.Manager;
import me.joshlarson.jlcommon.control.ManagerStructure;
@ManagerStructure(children = {
PacketRecordingService.class,
IntentRecordingService.class
})
public class CrashManager extends Manager {
}
@@ -0,0 +1,149 @@
package com.projectswg.forwarder.services.crash;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
import com.projectswg.forwarder.intents.client.UpdateStackIntent;
import com.projectswg.forwarder.intents.control.ClientCrashedIntent;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
import com.projectswg.forwarder.intents.server.ServerConnectedIntent;
import com.projectswg.forwarder.intents.server.ServerDisconnectedIntent;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import me.joshlarson.jlcommon.control.Intent;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class IntentRecordingService extends Service {
private final DateTimeFormatter dateTimeFormatter;
private Path logPath;
private FileWriter logWriter;
private ForwarderData data;
public IntentRecordingService() {
this.dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yy HH:mm:ss.SSS zzz").withZone(ZoneId.systemDefault());
this.logPath = null;
this.logWriter = null;
this.data = null;
}
@Override
public boolean initialize() {
try {
logPath = Files.createTempFile("HolocoreIntents", ".txt");
logWriter = new FileWriter(logPath.toFile());
} catch (IOException e) {
Log.a(e);
return false;
}
return true;
}
@Override
public boolean terminate() {
try {
if (logWriter != null)
logWriter.close();
if (logPath != null)
return logPath.toFile().delete();
} catch (IOException e) {
Log.w(e);
return false;
}
return true;
}
@IntentHandler
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
ForwarderData data = this.data;
if (data == null)
log(cci, "");
else
log(cci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.getAddress(), data.getUsername(), data.getLoginPort(), data.getZonePort());
}
@IntentHandler
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
log(cdi, "");
}
@IntentHandler
private void handleUpdateStackIntent(UpdateStackIntent usi) {
ProtocolStack stack = usi.getStack();
if (stack == null)
log(usi, "Stack='null'");
else
log(usi, "Server='%s' Source='%s' ConnectionId='%d'", stack.getServer(), stack.getSource(), stack.getConnectionId());
}
@IntentHandler
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
this.data = sfi.getData();
}
@IntentHandler
private void handleStopForwarderIntent(StopForwarderIntent sfi) {
ForwarderData data = this.data;
if (data == null)
log(sfi, "");
else
log(sfi, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.getAddress(), data.getUsername(), data.getLoginPort(), data.getZonePort());
}
@IntentHandler
private void handleServerConnectedIntent(ServerConnectedIntent sci) {
ForwarderData data = this.data;
if (data == null)
log(sci, "");
else
log(sci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.getAddress(), data.getUsername(), data.getLoginPort(), data.getZonePort());
}
@IntentHandler
private void handleServerDisconnectedIntent(ServerDisconnectedIntent sdi) {
log(sdi, "");
}
@IntentHandler
private void handleClientCrashedIntent(ClientCrashedIntent cci) {
log(cci, "");
try {
logWriter.flush();
byte[] data = Files.readAllBytes(logPath);
ZipEntry entry = new ZipEntry("log.txt");
entry.setTime(System.currentTimeMillis());
entry.setSize(data.length);
entry.setMethod(ZipOutputStream.DEFLATED);
synchronized (cci.getFileMutex()) {
cci.getOutputStream().putNextEntry(entry);
cci.getOutputStream().write(data);
cci.getOutputStream().closeEntry();
}
} catch (IOException e) {
Log.w("Failed to write intent data to crash log - IOException");
Log.w(e);
}
}
private synchronized void log(Intent i, String message, Object ... args) {
try {
logWriter.write(dateTimeFormatter.format(Instant.now()) + ": " + i.getClass().getSimpleName() + ' ' + String.format(message, args) + '\r' + '\n');
} catch (IOException e) {
Log.e("Failed to write to intent log");
}
}
}
@@ -0,0 +1,79 @@
package com.projectswg.forwarder.services.crash;
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
import com.projectswg.forwarder.intents.control.ClientCrashedIntent;
import com.projectswg.forwarder.resources.recording.PacketRecorder;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class PacketRecordingService extends Service {
private Path recorderPath;
private PacketRecorder recorder;
public PacketRecordingService() {
this.recorder = null;
}
@Override
public boolean initialize() {
try {
recorderPath = Files.createTempFile("HolocorePackets", ".hcap");
recorder = new PacketRecorder(recorderPath.toFile());
} catch (IOException e) {
Log.a(e);
return false;
}
return true;
}
@Override
public boolean terminate() {
try {
if (recorder != null)
recorder.close();
return recorderPath.toFile().delete();
} catch (IOException e) {
Log.w(e);
return false;
}
}
@IntentHandler
private void handleClientCrashedIntent(ClientCrashedIntent cci) {
try {
byte[] data = Files.readAllBytes(recorderPath);
ZipEntry entry = new ZipEntry("packet_log.hcap");
entry.setTime(System.currentTimeMillis());
entry.setSize(data.length);
entry.setMethod(ZipOutputStream.DEFLATED);
synchronized (cci.getFileMutex()) {
cci.getOutputStream().putNextEntry(entry);
cci.getOutputStream().write(data);
cci.getOutputStream().closeEntry();
}
} catch (IOException e) {
Log.w("Failed to write packet data to crash log - IOException");
Log.w(e);
}
}
@IntentHandler
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
recorder.record(false, dpii.getData());
}
@IntentHandler
private void handleDataPacketOutboundIntent(DataPacketOutboundIntent dpoi) {
recorder.record(true, dpoi.getData());
}
}
@@ -0,0 +1,11 @@
package com.projectswg.forwarder.services.server;
import me.joshlarson.jlcommon.control.Manager;
import me.joshlarson.jlcommon.control.ManagerStructure;
@ManagerStructure(children = {
ServerConnectionService.class
})
public class ServerConnectionManager extends Manager {
}
@@ -0,0 +1,129 @@
package com.projectswg.forwarder.services.server;
import com.projectswg.connection.HolocoreSocket;
import com.projectswg.connection.ServerConnectionChangedReason;
import com.projectswg.connection.packets.RawPacket;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
import com.projectswg.forwarder.intents.client.ClientConnectedIntent;
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
import com.projectswg.forwarder.intents.server.ServerConnectedIntent;
import com.projectswg.forwarder.intents.server.ServerDisconnectedIntent;
import com.projectswg.forwarder.resources.networking.NetInterceptor;
import me.joshlarson.jlcommon.concurrency.BasicThread;
import me.joshlarson.jlcommon.concurrency.Delay;
import me.joshlarson.jlcommon.control.IntentChain;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.util.concurrent.atomic.AtomicBoolean;
public class ServerConnectionService extends Service {
private final IntentChain intentChain;
private final AtomicBoolean running;
private final BasicThread thread;
private HolocoreSocket holocore;
private NetInterceptor interceptor;
private ForwarderData data;
public ServerConnectionService() {
this.intentChain = new IntentChain();
this.running = new AtomicBoolean(false);
this.thread = new BasicThread("server-connection", this::runningLoop);
this.holocore = null;
this.interceptor = null;
this.data = null;
}
@Override
public boolean stop() {
return stopRunningLoop();
}
@IntentHandler
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
interceptor = new NetInterceptor(sfi.getData());
data = sfi.getData();
}
@IntentHandler
private void handleStopForwarderIntent(StopForwarderIntent sfi) {
stopRunningLoop();
}
@IntentHandler
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
stopRunningLoop();
thread.start();
}
@IntentHandler
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
stopRunningLoop();
}
@IntentHandler
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
if (running.get())
holocore.send(interceptor.interceptClient(dpii.getData()));
}
private boolean stopRunningLoop() {
if (running.getAndSet(false)) {
thread.stop(true);
return thread.awaitTermination(500);
}
return true;
}
private void runningLoop() {
holocore = new HolocoreSocket(data.getAddress().getAddress(), data.getAddress().getPort());
running.set(true);
while (running.get()) {
try {
connectedLoop();
} catch (Throwable t) {
Log.w(t);
Log.i("Disconnected from server. Sleeping 3 seconds");
holocore.disconnect(ServerConnectionChangedReason.UNKNOWN);
Delay.sleepMilli(3000);
}
}
Log.t("Destroying holocore connection");
holocore.terminate();
holocore = null;
}
private void connectedLoop() {
Log.t("Attempting to connect to server at %s", holocore.getRemoteAddress());
if (!holocore.connect(5000)) {
Log.t("Failed to connect to server. Sleeping 3 seconds");
Delay.sleepMilli(3000);
return;
}
intentChain.broadcastAfter(getIntentManager(), new ServerConnectedIntent());
Log.i("Successfully connected to server at %s", holocore.getRemoteAddress());
while (holocore.isConnected()) {
if (!running.get()) {
holocore.disconnect(ServerConnectionChangedReason.CLIENT_DISCONNECT);
break;
}
RawPacket inbound = holocore.receive();
if (inbound == null) {
holocore.disconnect(ServerConnectionChangedReason.SOCKET_CLOSED);
break;
}
intentChain.broadcastAfter(getIntentManager(), new DataPacketOutboundIntent(interceptor.interceptServer(inbound.getData())));
}
intentChain.broadcastAfter(getIntentManager(), new ServerDisconnectedIntent());
Log.i("Disconnected from server");
}
}