Converted the forwarder from java to kotlin

This commit is contained in:
Josh Larson
2019-10-08 19:30:02 -04:00
parent ae2b56c4b3
commit 66f8c60f77
96 changed files with 2917 additions and 3754 deletions

View File

@@ -1,18 +1,26 @@
plugins {
id 'java'
id "org.javamodularity.moduleplugin"
id 'org.jetbrains.kotlin.jvm'
}
sourceCompatibility = 11
targetCompatibility = 11
sourceCompatibility = 12
repositories {
mavenLocal()
jcenter()
}
dependencies {
compile project(':pswgcommon')
compile project(':client-holocore')
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: '1.3.50'
testCompile 'junit:junit:4.12'
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "12"
}
}

View File

@@ -1,16 +0,0 @@
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 {
}

View File

@@ -1,227 +0,0 @@
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.IntentManager.IntentSpeedStatistics;
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.Comparator;
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.registerForIntent(ClientConnectedIntent.class, "Forwarder#handleClientConnectedIntent", cci -> connected.set(true));
intentManager.registerForIntent(ClientDisconnectedIntent.class, "Forwarder#handleClientDisconnectedIntent", 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);
List<IntentSpeedStatistics> intentTimes = intentManager.getSpeedRecorder();
intentTimes.sort(Comparator.comparingLong(IntentSpeedStatistics::getTotalTime).reversed());
Log.i(" Intent Times: [%d]", intentTimes.size());
Log.i(" %-30s%-60s%-40s%-10s%-20s", "Intent", "Receiver Class", "Receiver Method", "Count", "Time");
for (IntentSpeedStatistics record : intentTimes) {
String receiverName = record.getKey().toString();
if (receiverName.indexOf('$') != -1)
receiverName = receiverName.substring(0, receiverName.indexOf('$'));
receiverName = receiverName.replace("com.projectswg.forwarder.services.", "");
String intentName = record.getIntent().getSimpleName();
String recordCount = Long.toString(record.getCount());
String recordTime = String.format("%.6fms", record.getTotalTime() / 1E6);
String [] receiverSplit = receiverName.split("#", 2);
Log.i(" %-30s%-60s%-40s%-10s%-20s", intentName, receiverSplit[0], receiverSplit[1], recordCount, recordTime);
}
new StopForwarderIntent().broadcast(intentManager);
Manager.stop(managers);
}
intentManager.close(false, 1000);
primary.setIntentManager(null);
}
public ForwarderData getData() {
return data;
}
public static class ForwarderData {
private InetSocketAddress address = null;
private boolean verifyServer = true;
private String username = null;
private String password = null;
private int loginPort = 0;
private int zonePort = 0;
private int pingPort = 0;
private int outboundTunerMaxSend = 100;
private int outboundTunerInterval = 20;
private ForwarderData() { }
public InetSocketAddress getAddress() {
return address;
}
public boolean isVerifyServer() {
return verifyServer;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public int getLoginPort() {
return loginPort;
}
public int getZonePort() {
return zonePort;
}
public int getPingPort() {
return pingPort;
}
public int getOutboundTunerMaxSend() {
return outboundTunerMaxSend;
}
public int getOutboundTunerInterval() {
return outboundTunerInterval;
}
public void setAddress(InetSocketAddress address) {
this.address = address;
}
public void setVerifyServer(boolean verifyServer) {
this.verifyServer = verifyServer;
}
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;
}
public void setPingPort(int pingPort) {
this.pingPort = pingPort;
}
public void setOutboundTunerMaxSend(int outboundTunerMaxSend) {
this.outboundTunerMaxSend = outboundTunerMaxSend;
}
public void setOutboundTunerInterval(int outboundTunerInterval) {
this.outboundTunerInterval = outboundTunerInterval;
}
}
}

View File

@@ -1,11 +0,0 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
public class ClientConnectedIntent extends Intent {
public ClientConnectedIntent() {
}
}

View File

@@ -1,11 +0,0 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
public class ClientDisconnectedIntent extends Intent {
public ClientDisconnectedIntent() {
}
}

View File

@@ -1,19 +0,0 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
import org.jetbrains.annotations.NotNull;
public class DataPacketInboundIntent extends Intent {
private final byte [] data;
public DataPacketInboundIntent(@NotNull byte [] data) {
this.data = data;
}
@NotNull
public byte [] getData() {
return data;
}
}

View File

@@ -1,19 +0,0 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
import org.jetbrains.annotations.NotNull;
public class DataPacketOutboundIntent extends Intent {
private final byte [] data;
public DataPacketOutboundIntent(@NotNull byte [] data) {
this.data = data;
}
@NotNull
public byte [] getData() {
return data;
}
}

View File

@@ -1,17 +0,0 @@
package com.projectswg.forwarder.intents.client;
import me.joshlarson.jlcommon.control.Intent;
public class SendPongIntent extends Intent {
private final byte [] data;
public SendPongIntent(byte [] data) {
this.data = data;
}
public byte [] getData() {
return data;
}
}

View File

@@ -1,28 +0,0 @@
package com.projectswg.forwarder.intents.client;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import com.projectswg.forwarder.resources.networking.packets.Packet;
import me.joshlarson.jlcommon.control.Intent;
import org.jetbrains.annotations.NotNull;
public class SonyPacketInboundIntent extends Intent {
private final ProtocolStack stack;
private final Packet packet;
public SonyPacketInboundIntent(@NotNull ProtocolStack stack, @NotNull Packet packet) {
this.stack = stack;
this.packet = packet;
}
@NotNull
public ProtocolStack getStack() {
return stack;
}
@NotNull
public Packet getPacket() {
return packet;
}
}

View File

@@ -1,40 +0,0 @@
/***********************************************************************************
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
* *
* This file is part of the ProjectSWG Launcher. *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.intents.client;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import me.joshlarson.jlcommon.control.Intent;
import org.jetbrains.annotations.Nullable;
public class StackCreatedIntent extends Intent {
private final ProtocolStack stack;
public StackCreatedIntent(ProtocolStack stack) {
this.stack = stack;
}
@Nullable
public ProtocolStack getStack() {
return stack;
}
}

View File

@@ -1,40 +0,0 @@
/***********************************************************************************
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
* *
* This file is part of the ProjectSWG Launcher. *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.intents.client;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack;
import me.joshlarson.jlcommon.control.Intent;
import org.jetbrains.annotations.Nullable;
public class StackDestroyedIntent extends Intent {
private final ProtocolStack stack;
public StackDestroyedIntent(ProtocolStack stack) {
this.stack = stack;
}
@Nullable
public ProtocolStack getStack() {
return stack;
}
}

View File

@@ -1,24 +0,0 @@
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;
}
}

View File

@@ -1,21 +0,0 @@
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();
}
}

View File

@@ -1,11 +0,0 @@
package com.projectswg.forwarder.intents.control;
import me.joshlarson.jlcommon.control.Intent;
public class StopForwarderIntent extends Intent {
public StopForwarderIntent() {
}
}

View File

@@ -1,31 +0,0 @@
/***********************************************************************************
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
* *
* This file is part of the ProjectSWG Launcher. *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.forwarder.intents.server;
import me.joshlarson.jlcommon.control.Intent;
public class RequestServerConnectionIntent extends Intent {
public RequestServerConnectionIntent() {
}
}

View File

@@ -1,11 +0,0 @@
package com.projectswg.forwarder.intents.server;
import me.joshlarson.jlcommon.control.Intent;
public class ServerConnectedIntent extends Intent {
public ServerConnectedIntent() {
}
}

View File

@@ -1,11 +0,0 @@
package com.projectswg.forwarder.intents.server;
import me.joshlarson.jlcommon.control.Intent;
public class ServerDisconnectedIntent extends Intent {
public ServerDisconnectedIntent() {
}
}

View File

@@ -1,7 +0,0 @@
package com.projectswg.forwarder.resources.networking;
public enum ClientServer {
LOGIN,
ZONE,
PING
}

View File

@@ -1,66 +0,0 @@
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 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.getPingPort());
}
return cluster.encode().array();
}
}

View File

@@ -1,47 +0,0 @@
package com.projectswg.forwarder.resources.networking.data;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
import java.util.ArrayList;
import java.util.List;
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);
NetBuffer payload = NetBuffer.wrap(frag.getPayload());
int length = payload.getNetInt();
if ((int) ((length+4.0) / 489) > fragmentedBuffer.size())
return null; // Doesn't have the minimum number of required packets
return processFragmentedReady(length);
}
private byte [] processFragmentedReady(int size) {
byte [] combined = new byte[size];
int index = 0;
while (index < combined.length) {
byte [] payload = fragmentedBuffer.remove(0).getPayload();
int header = (index == 0) ? 4 : 0;
System.arraycopy(payload, header, combined, index, payload.length - header);
index += payload.length - header;
}
return combined;
}
}

View File

@@ -1,82 +0,0 @@
package com.projectswg.forwarder.resources.networking.data;
import com.projectswg.forwarder.resources.networking.data.ProtocolStack.ConnectionStream;
import com.projectswg.forwarder.resources.networking.packets.DataChannel;
import com.projectswg.forwarder.resources.networking.packets.Fragmented;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class Packager {
private final AtomicInteger size;
private final List<byte[]> dataChannel;
private final BlockingQueue<byte[]> outboundRaw;
private final ConnectionStream<SequencedOutbound> outboundPackaged;
public Packager(BlockingQueue<byte[]> outboundRaw, ConnectionStream<SequencedOutbound> outboundPackaged, ProtocolStack stack) {
this.size = new AtomicInteger(8);
this.dataChannel = new ArrayList<>();
this.outboundRaw = outboundRaw;
this.outboundPackaged = outboundPackaged;
}
public void handle(int maxPackaged) {
byte [] packet;
int packetSize;
while (outboundPackaged.size() < maxPackaged) {
packet = outboundRaw.poll();
if (packet == null)
break;
packetSize = getPacketLength(packet);
if (size.get() + packetSize >= 16384) // max data channel size
sendDataChannel();
if (packetSize < 16384) { // if overflowed, must go into fragmented
addToDataChannel(packet, packetSize);
} else {
sendFragmented(packet);
}
}
sendDataChannel();
}
private void addToDataChannel(byte [] packet, int packetSize) {
dataChannel.add(packet);
size.getAndAdd(packetSize);
}
private void sendDataChannel() {
if (dataChannel.isEmpty())
return;
outboundPackaged.addOrdered(new SequencedOutbound(new DataChannel(dataChannel)));
reset();
}
private void sendFragmented(byte [] packet) {
byte[][] frags = Fragmented.split(packet);
for (byte [] frag : frags) {
outboundPackaged.addOrdered(new SequencedOutbound(new Fragmented((short) 0, frag)));
}
}
private void reset() {
dataChannel.clear();
size.set(8);
}
private static int getPacketLength(byte [] data) {
int len = data.length;
if (len >= 255)
return len + 3;
return len + 1;
}
}

View File

@@ -1,218 +0,0 @@
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 me.joshlarson.jlcommon.log.Log;
import org.jetbrains.annotations.NotNull;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.BiConsumer;
public class ProtocolStack {
private final FragmentedProcessor fragmentedProcessor;
private final InetSocketAddress source;
private final BiConsumer<InetSocketAddress, byte[]> sender;
private final ClientServer server;
private final BlockingQueue<byte []> outboundRaw;
private final ConnectionStream<SequencedPacket> inbound;
private final ConnectionStream<SequencedOutbound> outbound;
private final Packager packager;
private InetSocketAddress pingSource;
private int connectionId;
public ProtocolStack(InetSocketAddress source, ClientServer server, BiConsumer<InetSocketAddress, byte[]> sender) {
this.fragmentedProcessor = new FragmentedProcessor();
this.source = source;
this.sender = sender;
this.server = server;
this.outboundRaw = new LinkedBlockingQueue<>();
this.inbound = new ConnectionStream<>();
this.outbound = new ConnectionStream<>();
this.packager = new Packager(outboundRaw, outbound, this);
this.connectionId = 0;
}
public void send(Packet packet) {
Log.t("Sending %s", 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 inbound.getSequence();
}
public short getTxSequence() {
return outbound.getSequence();
}
public void setPingSource(InetSocketAddress source) {
this.pingSource = source;
}
public void setConnectionId(int connectionId) {
this.connectionId = connectionId;
}
public SequencedStatus addIncoming(@NotNull SequencedPacket packet) {
return inbound.addUnordered(packet);
}
public SequencedPacket getNextIncoming() {
return inbound.poll();
}
public byte [] addFragmented(Fragmented frag) {
return fragmentedProcessor.addFragmented(frag);
}
public void addOutbound(@NotNull byte [] data) {
try {
outboundRaw.put(data);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public short getFirstUnacknowledgedOutbound() {
SequencedOutbound out = outbound.peek();
if (out == null)
return -1;
return out.getSequence();
}
public void clearAcknowledgedOutbound(short sequence) {
outbound.removeOrdered(sequence);
}
public synchronized void fillOutboundPackagedBuffer(int maxPackaged) {
packager.handle(maxPackaged);
}
public synchronized int fillOutboundBuffer(SequencedOutbound [] buffer) {
return outbound.fillBuffer(buffer);
}
@Override
public String toString() {
return String.format("ProtocolStack[server=%s, source=%s, connectionId=%d]", server, source, connectionId);
}
public static class ConnectionStream<T extends SequencedPacket> {
private final PriorityQueue<T> sequenced;
private final PriorityQueue<T> queued;
private long sequence;
public ConnectionStream() {
this.sequenced = new PriorityQueue<>();
this.queued = new PriorityQueue<>();
}
public short getSequence() {
return (short) sequence;
}
public synchronized SequencedStatus addUnordered(@NotNull T packet) {
if (SequencedPacket.compare(getSequence(), packet.getSequence()) > 0) {
T peek = peek();
return peek != null && peek.getSequence() == getSequence() ? SequencedStatus.READY : SequencedStatus.STALE;
}
if (packet.getSequence() == getSequence()) {
sequenced.add(packet);
sequence++;
// Add queued OOO packets
T queue;
while ((queue = queued.peek()) != null && queue.getSequence() == getSequence()) {
sequenced.add(queued.poll());
sequence++;
}
return SequencedStatus.READY;
} else {
queued.add(packet);
return SequencedStatus.OUT_OF_ORDER;
}
}
public synchronized void addOrdered(@NotNull T packet) {
packet.setSequence(getSequence());
addUnordered(packet);
}
public synchronized void removeOrdered(short sequence) {
T packet;
List<Short> sequencesRemoved = new ArrayList<>();
while ((packet = sequenced.peek()) != null && SequencedPacket.compare(sequence, packet.getSequence()) >= 0) {
T removed = sequenced.poll();
assert packet == removed;
sequencesRemoved.add(packet.getSequence());
}
Log.t("Removed acknowledged: %s", sequencesRemoved);
}
public synchronized T peek() {
return sequenced.peek();
}
public synchronized T poll() {
return sequenced.poll();
}
public synchronized int fillBuffer(T [] buffer) {
int n = 0;
for (T packet : sequenced) {
if (n >= buffer.length)
break;
buffer[n++] = packet;
}
return n;
}
public int size() {
return sequenced.size();
}
}
public enum SequencedStatus {
READY,
OUT_OF_ORDER,
STALE
}
}

View File

@@ -1,47 +0,0 @@
package com.projectswg.forwarder.resources.networking.data;
import com.projectswg.forwarder.resources.networking.packets.SequencedPacket;
import java.nio.ByteBuffer;
public class SequencedOutbound implements SequencedPacket {
private final SequencedPacket packet;
private byte [] data;
private boolean sent;
public SequencedOutbound(SequencedPacket packet) {
this.packet = packet;
this.data = packet.encode().array();
this.sent = false;
}
@Override
public short getSequence() {
return packet.getSequence();
}
public byte[] getData() {
return data;
}
public boolean isSent() {
return sent;
}
@Override
public ByteBuffer encode() {
return packet.encode();
}
@Override
public void setSequence(short sequence) {
this.packet.setSequence(sequence);
this.data = packet.encode().array();
}
public void setSent(boolean sent) {
this.sent = sent;
}
}

View File

@@ -1,70 +0,0 @@
/***********************************************************************************
* 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; }
@Override
public String toString() {
return String.format("Acknowledge[%d]", sequence);
}
}

View File

@@ -1,111 +0,0 @@
/***********************************************************************************
* 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; }
@Override
public String toString() {
return String.format("ClientNetworkStatusUpdate[tick=%d, lastUpdate=%d, avgUpdate=%d, shortestUpdate=%d, longestUpdate=%d, lastServerUpdate=%d, sent=%d, recv=%d]", clientTickCount, lastUpdate, avgUpdate, shortUpdate, longUpdate, lastServerUpdate, packetSent, packetRecv);
}
}

View File

@@ -1,201 +0,0 @@
/***********************************************************************************
* 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 com.projectswg.common.network.NetBuffer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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(List<byte[]> content) {
this.content = new ArrayList<>(content);
this.channel = Channel.DATA_CHANNEL_A;
this.sequence = 0;
this.multiPacket = (short) (content.isEmpty() ? 0 : 0x19);
}
public DataChannel(ByteBuffer data) {
this();
decode(data);
}
public DataChannel(byte[][] packets) {
this();
content.addAll(Arrays.asList(packets));
}
public DataChannel(byte [] packet) {
this();
content.add(packet);
}
@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;
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() {
NetBuffer 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);
}
return data.getBuffer();
}
public void addPacket(byte[] packet) {
content.add(packet);
}
public void clearPackets() {
content.clear();
}
public int getLength() {
int length = 6;
for (byte[] packet : content) {
int addLength = packet.length;
length += 1 + addLength + ((addLength >= 0xFF) ? 2 : 0);
}
return length;
}
@Override
public boolean equals(Object o) {
return o instanceof DataChannel && sequence == ((DataChannel) o).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;
}
}
@Override
public String toString() {
return String.format("DataChannel[seq=%d, packets=%d]", sequence, content.size());
}
}

View File

@@ -1,108 +0,0 @@
/***********************************************************************************
* 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;
}
}
@Override
public String toString() {
return String.format("Disconnect[id=%d, reason=%s]", connectionId, reason);
}
}

View File

@@ -1,124 +0,0 @@
/***********************************************************************************
* 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 com.projectswg.common.network.NetBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Fragmented extends Packet implements SequencedPacket {
private short sequence;
private byte [] payload;
public Fragmented() {
this.sequence = 0;
this.payload = null;
}
public Fragmented(ByteBuffer data) {
decode(data);
}
public Fragmented(short sequence, byte [] payload) {
this.sequence = sequence;
this.payload = payload;
}
@Override
public void decode(ByteBuffer bb) {
super.decode(bb);
NetBuffer data = NetBuffer.wrap(bb);
short type = data.getNetShort();
assert (type - 0x0D) < 4;
this.sequence = data.getNetShort();
this.payload = data.getArray(data.remaining());
}
@Override
public ByteBuffer encode() {
NetBuffer data = NetBuffer.allocate(4 + payload.length);
data.addNetShort(0x0D);
data.addNetShort(sequence);
data.addRawArray(payload);
return data.getBuffer();
}
@Override
public boolean equals(Object o) {
return o instanceof Fragmented && sequence == ((Fragmented) o).sequence;
}
@Override
public int hashCode() {
return sequence;
}
@Override
public String toString() {
return String.format("Fragmented[seq=%d, len=%d]", sequence, payload.length);
}
@Override
public short getSequence() {
return sequence;
}
public byte[] getPayload() {
return payload;
}
@Override
public void setSequence(short sequence) {
this.sequence = sequence;
}
public void setPayload(byte[] payload) {
this.payload = payload;
}
public static byte [][] split(byte [] data) {
int offset = 0;
int packetCount = (int) Math.ceil((data.length+4)/16377.0); // 489
byte [][] packets = new byte[packetCount][];
for (int i = 0; i < packetCount; i++) {
int header = (i == 0) ? 4 : 0;
ByteBuffer segment = ByteBuffer.allocate(Math.min(data.length-offset-header, 16377)).order(ByteOrder.BIG_ENDIAN);
if (i == 0)
segment.putInt(data.length);
int segmentLength = segment.remaining();
segment.put(data, offset, segmentLength);
offset += segmentLength;
packets[i] = segment.array();
}
return packets;
}
}

View File

@@ -1,30 +0,0 @@
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;
}
@Override
public String toString() {
return String.format("KeepAlive[]");
}
}

View File

@@ -1,120 +0,0 @@
/***********************************************************************************
* 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 com.projectswg.common.network.NetBuffer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
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;
}
@Override
public String toString() {
return String.format("MultiPacket[packets=%d]", content.size());
}
}

View File

@@ -1,67 +0,0 @@
/***********************************************************************************
* 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; }
@Override
public String toString() {
return String.format("OutOfOrder[%d]", sequence);
}
}

View File

@@ -1,256 +0,0 @@
/***********************************************************************************
* 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;
}
}

View File

@@ -1,34 +0,0 @@
package com.projectswg.forwarder.resources.networking.packets;
import com.projectswg.common.utilities.ByteUtilities;
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;
}
@Override
public String toString() {
return "PingPacket[payload=" + ByteUtilities.getHexString(payload) + "]";
}
}

View File

@@ -1,41 +0,0 @@
/***********************************************************************************
* 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;
}
}

View File

@@ -1,34 +0,0 @@
package com.projectswg.forwarder.resources.networking.packets;
import org.jetbrains.annotations.NotNull;
import java.nio.ByteBuffer;
public interface SequencedPacket extends Comparable<SequencedPacket> {
void setSequence(short sequence);
short getSequence();
ByteBuffer encode();
default int compareTo(@NotNull SequencedPacket p) {
return compare(this, p);
}
static int compare(@NotNull SequencedPacket a, @NotNull SequencedPacket b) {
return compare(a.getSequence(), b.getSequence());
}
static int compare(short a, short b) {
int aSeq = a & 0xFFFF;
int bSeq = b & 0xFFFF;
if (aSeq == bSeq)
return 0;
int diff = bSeq - aSeq;
if (diff <= 0)
diff += 0x10000;
return diff < 30000 ? -1 : 1;
}
}

View File

@@ -1,25 +0,0 @@
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;
}
}

View File

@@ -1,25 +0,0 @@
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;
}
}

View File

@@ -1,100 +0,0 @@
/***********************************************************************************
* 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; }
@Override
public String toString() {
return String.format("ServerNetworkStatusUpdate[ticks=%d, syncStamp=%d, clientSent=%d, clientRecv=%d, serverSent=%d, serverRecv=%d]", clientTickCount, serverSyncStampLong, clientPacketsSent, clientPacketsRecv, serverPacketsSent, serverPacketsRecv);
}
}

View File

@@ -1,80 +0,0 @@
/***********************************************************************************
* 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; }
@Override
public String toString() {
return String.format("SessionRequest[connectionId=%d, crcLength=%d, udpSize=%d]", connectionId, crcLength, udpSize);
}
}

View File

@@ -1,101 +0,0 @@
/***********************************************************************************
* 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; }
@Override
public String toString() {
return String.format("SessionResponse[connectionId=%d, crcSeed=%d, crcLength=%d, encryptionFlag=%d, xorLength=%d, udpSize=%d]", connectionId, crcSeed, crcLength, encryptionFlag, xorLength, udpSize);
}
}

View File

@@ -1,68 +0,0 @@
package com.projectswg.forwarder.resources.recording;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
public class PacketRecorder implements AutoCloseable, Closeable {
private static final byte VERSION = 3;
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.writeBoolean(server);
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();
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", ZoneId.systemDefault().getId());
systemStrings.put("time.current_time", Instant.now().toString());
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();
}
}
}

View File

@@ -1,14 +0,0 @@
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 {
}

View File

@@ -1,104 +0,0 @@
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.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;
public class ClientInboundDataService extends Service {
private final IntentMultiplexer multiplexer;
private final IntentChain intentChain;
public ClientInboundDataService() {
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
this.intentChain = new IntentChain();
}
@IntentHandler
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
multiplexer.call(spii.getStack(), spii.getPacket());
}
@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) {
switch (stack.addIncoming(data)) {
case READY:
readAvailablePackets(stack);
break;
case OUT_OF_ORDER:
Log.d("Data/Inbound Received Data Out of Order %d", data.getSequence());
for (short seq = stack.getRxSequence(); seq < data.getSequence(); seq++)
stack.send(new OutOfOrder(seq));
break;
default:
break;
}
}
@Multiplexer
private void handleFragmented(ProtocolStack stack, Fragmented frag) {
switch (stack.addIncoming(frag)) {
case READY:
readAvailablePackets(stack);
break;
case OUT_OF_ORDER:
Log.d("Data/Inbound Received Frag Out of Order %d", frag.getSequence());
for (short seq = stack.getRxSequence(); seq < frag.getSequence(); seq++)
stack.send(new OutOfOrder(seq));
break;
default:
break;
}
}
private void readAvailablePackets(ProtocolStack stack) {
short highestSequence = -1;
boolean updatedSequence = false;
SequencedPacket packet = stack.getNextIncoming();
while (packet != null) {
Log.t("Data/Inbound Received: %s", packet);
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);
}
highestSequence = packet.getSequence();
packet = stack.getNextIncoming();
updatedSequence = true;
}
if (updatedSequence)
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("Data/Inbound Received Data: %s", type);
intentChain.broadcastAfter(getIntentManager(), new DataPacketInboundIntent(data));
}
}

View File

@@ -1,184 +0,0 @@
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.*;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.resources.networking.ClientServer;
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.BasicScheduledThread;
import me.joshlarson.jlcommon.concurrency.BasicThread;
import me.joshlarson.jlcommon.concurrency.Delay;
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.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ClientOutboundDataService extends Service {
private final SequencedOutbound [] outboundBuffer;
private final IntentMultiplexer multiplexer;
private final Set<ProtocolStack> activeStacks;
private final BasicThread sendThread;
private final BasicScheduledThread heartbeatThread;
public ClientOutboundDataService() {
this.outboundBuffer = new SequencedOutbound[4096];
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
this.activeStacks = ConcurrentHashMap.newKeySet();
this.sendThread = new BasicThread("outbound-sender", this::persistentSend);
this.heartbeatThread = new BasicScheduledThread("heartbeat", this::heartbeat);
}
@Override
public boolean terminate() {
if (sendThread.isExecuting())
sendThread.stop(true);
if (heartbeatThread.isRunning())
heartbeatThread.stop();
return sendThread.awaitTermination(1000) && heartbeatThread.awaitTermination(1000);
}
@IntentHandler
private void handleStartForwarderIntent(StartForwarderIntent sfi) {
}
@IntentHandler
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
multiplexer.call(spii.getStack(), spii.getPacket());
}
@IntentHandler
private void handleClientConnectedIntent(ClientConnectedIntent cci) {
if (sendThread.isExecuting())
return;
sendThread.start();
heartbeatThread.startWithFixedRate(0, 500);
}
@IntentHandler
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
if (!sendThread.isExecuting())
return;
sendThread.stop(true);
heartbeatThread.stop();
sendThread.awaitTermination(1000);
heartbeatThread.awaitTermination(1000);
}
@IntentHandler
private void handleStackCreatedIntent(StackCreatedIntent sci) {
activeStacks.add(sci.getStack());
}
@IntentHandler
private void handleStackDestroyedIntent(StackDestroyedIntent sdi) {
activeStacks.remove(sdi.getStack());
}
@IntentHandler
private void handleDataPacketOutboundIntent(DataPacketOutboundIntent dpoi) {
PacketType type = PacketType.fromCrc(ByteBuffer.wrap(dpoi.getData()).order(ByteOrder.LITTLE_ENDIAN).getInt(2));
ClientServer filterServer = ClientServer.ZONE;
if (type != null) {
switch (type) {
case ERROR_MESSAGE:
case SERVER_ID:
case SERVER_NOW_EPOCH_TIME:
filterServer = null;
break;
case LOGIN_CLUSTER_STATUS:
case LOGIN_CLIENT_TOKEN:
case LOGIN_INCORRECT_CLIENT_ID:
case LOGIN_ENUM_CLUSTER:
case ENUMERATE_CHARACTER_ID:
case CHARACTER_CREATION_DISABLED:
case DELETE_CHARACTER_REQUEST:
case DELETE_CHARACTER_RESPONSE:
filterServer = ClientServer.LOGIN;
break;
case HEART_BEAT_MESSAGE: {
HeartBeat heartbeat = new HeartBeat();
heartbeat.decode(NetBuffer.wrap(dpoi.getData()));
if (heartbeat.getPayload().length > 0) {
new SendPongIntent(heartbeat.getPayload()).broadcast(getIntentManager());
return;
}
break;
}
}
}
final ClientServer finalFilterServer = filterServer;
ProtocolStack stack = activeStacks.stream().filter(s -> s.getServer() == finalFilterServer).findFirst().orElse(null);
if (stack == null) {
Log.d("Data/Outbound Sending %s [len=%d] to %s", type, dpoi.getData().length, activeStacks);
for (ProtocolStack active : activeStacks) {
active.addOutbound(dpoi.getData());
}
} else {
Log.d("Data/Outbound Sending %s [len=%d] to %s", type, dpoi.getData().length, stack);
stack.addOutbound(dpoi.getData());
}
}
@Multiplexer
private void handleAcknowledgement(ProtocolStack stack, Acknowledge ack) {
Log.t("Data/Outbound Client Acknowledged: %d. Min Sequence: %d", ack.getSequence(), stack.getFirstUnacknowledgedOutbound());
stack.clearAcknowledgedOutbound(ack.getSequence());
}
@Multiplexer
private void handleOutOfOrder(ProtocolStack stack, OutOfOrder ooo) {
Log.t("Data/Outbound Out of Order: %d. Min Sequence: %d", ooo.getSequence(), stack.getFirstUnacknowledgedOutbound());
}
private void persistentSend() {
Log.d("Data/Outbound Starting Persistent Send");
int iteration = 0;
while (!Delay.isInterrupted()) {
flushPackaged(iteration % 20 == 0);
iteration++;
Delay.sleepMilli(50);
}
Log.d("Data/Outbound Stopping Persistent Send");
}
private void heartbeat() {
for (ProtocolStack stack : activeStacks) {
stack.send(new HeartBeat().encode().array());
}
}
private void flushPackaged(boolean overrideSent) {
for (ProtocolStack stack : activeStacks) {
stack.fillOutboundPackagedBuffer(outboundBuffer.length);
int count = stack.fillOutboundBuffer(outboundBuffer);
int sent = 0;
for (int i = 0; i < count; i++) {
SequencedOutbound out = outboundBuffer[i];
if (overrideSent || !out.isSent()) {
stack.send(out.getData());
out.setSent(true);
sent++;
}
}
if (sent > 0)
Log.t("Data/Outbound Sent %d - %d [%d]", outboundBuffer[0].getSequence(), outboundBuffer[count-1].getSequence(), count);
else if (count > 0)
Log.t("Data/Outbound Waiting to send %d - %d [count=%d]", outboundBuffer[0].getSequence(), outboundBuffer[count-1].getSequence(), count);
}
}
}

View File

@@ -1,46 +0,0 @@
package com.projectswg.forwarder.services.client;
import com.projectswg.forwarder.intents.client.SonyPacketInboundIntent;
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;
public class ClientProtocolService extends Service {
private static final int GALACTIC_BASE_TIME = 1323043200;
private final IntentMultiplexer multiplexer;
public ClientProtocolService() {
this.multiplexer = new IntentMultiplexer(this, ProtocolStack.class, Packet.class);
}
@IntentHandler
private void handleSonyPacketInboundIntent(SonyPacketInboundIntent spii) {
multiplexer.call(spii.getStack(), spii.getPacket());
}
@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());
}
}

View File

@@ -1,340 +0,0 @@
package com.projectswg.forwarder.services.client;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import com.projectswg.forwarder.intents.client.*;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
import com.projectswg.forwarder.intents.server.RequestServerConnectionIntent;
import com.projectswg.forwarder.intents.server.ServerConnectedIntent;
import com.projectswg.forwarder.intents.server.ServerDisconnectedIntent;
import com.projectswg.forwarder.resources.networking.ClientServer;
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.network.UDPServer;
import me.joshlarson.jlcommon.utilities.ByteUtilities;
import java.net.*;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class ClientServerService extends Service {
private final IntentChain intentChain;
private final Map<ClientServer, ProtocolStack> stacks;
private final AtomicBoolean serverConnection;
private final AtomicReference<SessionRequest> cachedSessionRequest;
private final AtomicReference<InetSocketAddress> lastPing;
private ForwarderData data;
private UDPServer loginServer;
private UDPServer zoneServer;
private UDPServer pingServer;
public ClientServerService() {
this.intentChain = new IntentChain();
this.stacks = new EnumMap<>(ClientServer.class);
this.serverConnection = new AtomicBoolean(false);
this.cachedSessionRequest = new AtomicReference<>(null);
this.lastPing = new AtomicReference<>(null);
this.data = null;
this.loginServer = null;
this.zoneServer = null;
this.pingServer = null;
}
@Override
public boolean isOperational() {
if (data == null)
return true;
if (loginServer == null || !loginServer.isRunning())
return false;
if (zoneServer == null || !zoneServer.isRunning())
return false;
return pingServer != null && pingServer.isRunning();
}
@Override
public boolean stop() {
closeConnection(ClientServer.LOGIN);
closeConnection(ClientServer.ZONE);
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("Initializing ping udp server...");
pingServer = new UDPServer(new InetSocketAddress(InetAddress.getLoopbackAddress(), data.getPingPort()), 496, this::onPingPacket);
Log.t("Binding to login server...");
loginServer.bind(this::customizeUdpServer);
Log.t("Binding to zone server...");
zoneServer.bind(this::customizeUdpServer);
Log.t("Binding to ping server...");
pingServer.bind(this::customizeUdpServer);
data.setLoginPort(loginServer.getPort());
data.setZonePort(zoneServer.getPort());
data.setPingPort(pingServer.getPort());
Log.i("Initialized login (%d), zone (%d), and ping (%d) servers", loginServer.getPort(), zoneServer.getPort(), pingServer.getPort());
} catch (SocketException e) {
Log.a(e);
if (loginServer != null)
loginServer.close();
if (zoneServer != null)
zoneServer.close();
if (pingServer != null)
pingServer.close();
loginServer = null;
zoneServer = null;
pingServer = 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();
Log.t("Closing the ping udp server...");
if (pingServer != null)
pingServer.close();
loginServer = null;
zoneServer = null;
pingServer = null;
Log.i("Closed the login, zone, and ping udp servers");
}
@IntentHandler
private void handleServerConnectedIntent(ServerConnectedIntent sci) {
serverConnection.set(true);
SessionRequest request = this.cachedSessionRequest.getAndSet(null);
if (request != null) {
byte [] data = request.encode().array();
onLoginPacket(new DatagramPacket(data, data.length, new InetSocketAddress(request.getAddress(), request.getPort())));
}
}
@IntentHandler
private void handleServerDisconnectedIntent(ServerDisconnectedIntent sdi) {
serverConnection.set(false);
closeConnection(ClientServer.LOGIN);
closeConnection(ClientServer.ZONE);
closeConnection(ClientServer.PING);
}
@IntentHandler
private void handleSendPongIntent(SendPongIntent spi) {
InetSocketAddress lastPing = this.lastPing.get();
if (lastPing != null)
send(lastPing, ClientServer.PING, spi.getData());
}
private void customizeUdpServer(DatagramSocket socket) {
try {
socket.setReuseAddress(false);
socket.setTrafficClass(0x10);
socket.setBroadcast(false);
socket.setReceiveBufferSize(64 * 1024);
socket.setSendBufferSize(64 * 1024);
} 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 onPingPacket(DatagramPacket packet) {
InetSocketAddress source = (InetSocketAddress) packet.getSocketAddress();
lastPing.set(source);
ProtocolStack stack = stacks.get(ClientServer.ZONE);
PingPacket pingPacket = new PingPacket(packet.getData());
pingPacket.setAddress(source.getAddress());
pingPacket.setPort(source.getPort());
if (stack != null)
intentChain.broadcastAfter(getIntentManager(), new SonyPacketInboundIntent(stack, pingPacket));
}
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;
case PING:
pingServer.send(addr, data);
break;
}
}
private void process(InetSocketAddress source, ClientServer server, byte [] data) {
Packet parsed;
try {
parsed = (server == ClientServer.PING) ? new PingPacket(data) : parse(data);
if (parsed == null)
return;
} catch (BufferUnderflowException e) {
Log.w("Failed to parse packet: %s", ByteUtilities.getHexString(data));
return;
}
parsed.setAddress(source.getAddress());
parsed.setPort(source.getPort());
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 = process(source, server, parsed);
if (stack == null) {
Log.t("[%s]@%s DROPPED %s", source, server, parsed);
return;
}
Log.t("[%s]@%s sent: %s", source, server, parsed);
intentChain.broadcastAfter(getIntentManager(), new SonyPacketInboundIntent(stack, parsed));
}
private ProtocolStack process(InetSocketAddress source, ClientServer server, Packet parsed) {
Log.t("Process [%b] %s", serverConnection.get(), parsed);
if (!serverConnection.get()) {
if (parsed instanceof SessionRequest) {
cachedSessionRequest.set((SessionRequest) parsed);
intentChain.broadcastAfter(getIntentManager(), new RequestServerConnectionIntent());
}
for (ClientServer serverType : ClientServer.values())
closeConnection(serverType);
return null;
}
if (parsed instanceof SessionRequest)
return onSessionRequest(source, server, (SessionRequest) parsed);
if (parsed instanceof Disconnect)
return onDisconnect(source, server, (Disconnect) parsed);
ProtocolStack stack = stacks.get(server);
if (stack == null)
return null;
if (parsed instanceof PingPacket) {
stack.setPingSource(source);
} else if (!stack.getSource().equals(source) || stack.getServer() != server) {
return null;
}
return stack;
}
private ProtocolStack onSessionRequest(InetSocketAddress source, ClientServer server, SessionRequest request) {
ProtocolStack current = stacks.get(server);
if (current != null && request.getConnectionId() != current.getConnectionId()) {
closeConnection(server);
return null;
}
ProtocolStack stack = new ProtocolStack(source, server, (remote, data) -> send(remote, server, data));
stack.setConnectionId(request.getConnectionId());
openConnection(server, stack);
return stack;
}
private ProtocolStack onDisconnect(InetSocketAddress source, ClientServer server, Disconnect disconnect) {
// Sets the current stack to null if it matches the Disconnect packet
ProtocolStack stack = stacks.get(server);
if (stack != null && stack.getSource().equals(source) && stack.getConnectionId() == disconnect.getConnectionId()) {
closeConnection(server);
} else {
send(source, server, new Disconnect(disconnect.getConnectionId(), DisconnectReason.APPLICATION).encode().array());
}
return stack;
}
private void openConnection(ClientServer server, ProtocolStack stack) {
closeConnection(server);
if (server == ClientServer.LOGIN) {
closeConnection(ClientServer.ZONE);
intentChain.broadcastAfter(getIntentManager(), new ClientConnectedIntent());
}
stacks.put(server, stack);
stack.send(new SessionResponse(stack.getConnectionId(), 0, (byte) 0, (byte) 0, (byte) 0, 496));
intentChain.broadcastAfter(getIntentManager(), new StackCreatedIntent(stack));
}
private void closeConnection(ClientServer server) {
ProtocolStack stack = stacks.remove(server);
if (stack == null)
return;
stack.send(new Disconnect(stack.getConnectionId(), DisconnectReason.APPLICATION));
intentChain.broadcastAfter(getIntentManager(), new StackDestroyedIntent(stack));
if (stacks.isEmpty())
intentChain.broadcastAfter(getIntentManager(), new ClientDisconnectedIntent());
}
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 >= 6)
return new RawSWGPacket(rawData);
Log.w("Unknown SOE packet: %d %s", opcode, ByteUtilities.getHexString(data.array()));
return null;
}
}
}

View File

@@ -1,12 +0,0 @@
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 {
}

View File

@@ -1,138 +0,0 @@
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.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 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 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");
}
}
}

View File

@@ -1,79 +0,0 @@
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());
}
}

View File

@@ -1,11 +0,0 @@
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 {
}

View File

@@ -1,120 +0,0 @@
package com.projectswg.forwarder.services.server;
import com.projectswg.holocore.client.HolocoreSocket;
import com.projectswg.holocore.client.RawPacket;
import com.projectswg.forwarder.Forwarder.ForwarderData;
import com.projectswg.forwarder.intents.client.ClientDisconnectedIntent;
import com.projectswg.forwarder.intents.client.DataPacketInboundIntent;
import com.projectswg.forwarder.intents.client.DataPacketOutboundIntent;
import com.projectswg.forwarder.intents.control.StartForwarderIntent;
import com.projectswg.forwarder.intents.control.StopForwarderIntent;
import com.projectswg.forwarder.intents.server.RequestServerConnectionIntent;
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.control.IntentChain;
import me.joshlarson.jlcommon.control.IntentHandler;
import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
public class ServerConnectionService extends Service {
private static final int CONNECT_TIMEOUT = 5000;
private final IntentChain intentChain;
private final BasicThread thread;
private HolocoreSocket holocore;
private NetInterceptor interceptor;
private ForwarderData data;
public ServerConnectionService() {
this.intentChain = new IntentChain();
this.thread = new BasicThread("server-connection", this::primaryConnectionLoop);
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 handleRequestServerConnectionIntent(RequestServerConnectionIntent rsci) {
HolocoreSocket holocore = this.holocore;
if (holocore != null)
return; // It's trying to connect - give it a little more time
if (stopRunningLoop())
thread.start();
}
@IntentHandler
private void handleClientDisconnectedIntent(ClientDisconnectedIntent cdi) {
stopRunningLoop();
}
@IntentHandler
private void handleDataPacketInboundIntent(DataPacketInboundIntent dpii) {
HolocoreSocket holocore = this.holocore;
if (holocore != null)
holocore.send(interceptor.interceptClient(dpii.getData()));
}
private boolean stopRunningLoop() {
if (!thread.isExecuting())
return true;
thread.stop(true);
return thread.awaitTermination(1000);
}
private void primaryConnectionLoop() {
boolean didConnect = false;
try (HolocoreSocket holocore = new HolocoreSocket(data.getAddress().getAddress(), data.getAddress().getPort(), data.isVerifyServer())) {
this.holocore = holocore;
Log.t("Attempting to connect to server at %s", holocore.getRemoteAddress());
if (!holocore.connect(CONNECT_TIMEOUT)) {
Log.w("Failed to connect to server!");
return;
}
Log.i("Successfully connected to server at %s", holocore.getRemoteAddress());
didConnect = true;
intentChain.broadcastAfter(getIntentManager(), new ServerConnectedIntent());
while (holocore.isConnected()) {
RawPacket inbound = holocore.receive();
if (inbound == null) {
if (holocore.isConnected())
Log.w("Server connection interrupted");
else
Log.w("Server closed connection!");
return;
}
intentChain.broadcastAfter(getIntentManager(), new DataPacketOutboundIntent(interceptor.interceptServer(inbound.getData())));
}
} catch (Throwable t) {
Log.w("Caught unknown exception in server connection! %s: %s", t.getClass().getName(), t.getMessage());
Log.w(t);
} finally {
Log.i("Disconnected from server.");
if (didConnect)
intentChain.broadcastAfter(getIntentManager(), new ServerDisconnectedIntent());
this.holocore = null;
}
}
}

View File

@@ -2,10 +2,13 @@ module com.projectswg.forwarder {
requires com.projectswg.common;
requires com.projectswg.holocore.client;
requires org.jetbrains.annotations;
requires me.joshlarson.jlcommon;
requires java.management;
requires kotlin.stdlib;
exports com.projectswg.forwarder;
exports com.projectswg.forwarder.intents;
opens com.projectswg.forwarder.services.client to me.joshlarson.jlcommon;
opens com.projectswg.forwarder.services.crash to me.joshlarson.jlcommon;

View File

@@ -0,0 +1,14 @@
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
])
class ConnectionManager : Manager()

View File

@@ -0,0 +1,142 @@
package com.projectswg.forwarder
import com.projectswg.forwarder.intents.*
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.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.ReentrantLock
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class Forwarder {
val data: ForwarderData = ForwarderData()
val intentManager: IntentManager = IntentManager(false, Runtime.getRuntime().availableProcessors()*2)
private val connected: AtomicBoolean = AtomicBoolean(false)
fun readClientOutput(input: InputStream): File? {
val output = ByteArrayOutputStream()
try {
input.transferTo(output)
} catch (e: IOException) {
Log.w("IOException while reading client output")
Log.w(e)
}
return onClientClosed(output.toString())
}
private fun onClientClosed(clientOutput: String): File? {
if (!connected.get())
return null
val output: File
try {
output = Files.createTempFile("HolocoreCrashLog", ".zip").toFile()
} catch (e: IOException) {
Log.e("Failed to write crash log! Could not create temp file.")
return null
}
try {
ZipOutputStream(BufferedOutputStream(FileOutputStream(output))).use { zip ->
run {
val data = clientOutput.toByteArray(StandardCharsets.UTF_8)
val entry = ZipEntry("output.txt")
entry.time = System.currentTimeMillis()
entry.size = data.size.toLong()
entry.method = ZipOutputStream.DEFLATED
zip.putNextEntry(entry)
zip.write(data)
zip.closeEntry()
}
val cci = ClientCrashedIntent(zip, ReentrantLock())
cci.broadcast(intentManager)
val startSleep = System.nanoTime()
while (!cci.isComplete && System.nanoTime() - startSleep < 1E9)
Delay.sleepMilli(10)
return output
}
} catch (e: IOException) {
Log.e("Failed to write crash log! %s: %s", e.javaClass.name, e.message)
return null
}
}
fun run() {
intentManager.registerForIntent(ClientConnectedIntent::class.java, "Forwarder#handleClientConnectedIntent") { connected.set(true) }
intentManager.registerForIntent(ClientDisconnectedIntent::class.java, "Forwarder#handleClientDisconnectedIntent") { connected.set(false) }
val primary = ConnectionManager()
primary.setIntentManager(intentManager)
val managers = listOf<Manager>(primary)
Manager.start(managers)
StartForwarderIntent(data).broadcast(intentManager)
Manager.run(managers, 100)
val intentTimes = intentManager.speedRecorder.sortedByDescending { it.totalTime }
Log.i(" Intent Times: [%d]", intentTimes.size)
Log.i(" %-30s%-60s%-40s%-10s%-20s", "Intent", "Receiver Class", "Receiver Method", "Count", "Time")
for (record in intentTimes) {
var receiverName = record.key.toString()
if (receiverName.indexOf('$') != -1)
receiverName = receiverName.substring(0, receiverName.indexOf('$'))
receiverName = receiverName.replace("com.projectswg.forwarder.services.", "")
val intentName = record.intent.simpleName
val recordCount = record.count.toString()
val recordTime = String.format("%.6fms", record.totalTime / 1E6)
val receiverSplit = receiverName.split("#".toRegex(), 2).toTypedArray()
if (receiverSplit.size >= 2)
Log.i(" %-30s%-60s%-40s%-10s%-20s", intentName, receiverSplit[0], receiverSplit[1], recordCount, recordTime)
else
Log.i(" %-30s%-100s%-10s%-20s", intentName, receiverSplit[0], recordCount, recordTime)
}
StopForwarderIntent().broadcast(intentManager)
Manager.stop(managers)
intentManager.close(false, 1000)
primary.setIntentManager(null)
}
class ForwarderData internal constructor() {
var address: InetSocketAddress? = null
var isVerifyServer = true
var isEncryptionEnabled = true
var username: String? = null
var password: String? = null
var loginPort = 0
var zonePort = 0
var pingPort = 0
var outboundTunerMaxSend = 100
var outboundTunerInterval = 20
var crashed: Boolean = false
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
SafeMain.main("") { mainRunnable() }
}
private fun mainRunnable() {
Log.addWrapper(ConsoleLogWrapper(LogLevel.TRACE))
val forwarder = Forwarder()
forwarder.data.address = InetSocketAddress(44463)
forwarder.run()
ThreadUtilities.printActiveThreads()
}
}
}

View File

@@ -0,0 +1,14 @@
package com.projectswg.forwarder.intents
import com.projectswg.forwarder.resources.networking.data.ProtocolStack
import com.projectswg.forwarder.resources.networking.packets.Packet
import me.joshlarson.jlcommon.control.Intent
class ClientConnectedIntent: Intent()
class ClientDisconnectedIntent: Intent()
class DataPacketInboundIntent(val data: ByteArray): Intent()
class DataPacketOutboundIntent(val data: ByteArray): Intent()
class SendPongIntent(val data: ByteArray): Intent()
class SonyPacketInboundIntent(val stack: ProtocolStack, val packet: Packet): Intent()
class StackCreatedIntent(val stack: ProtocolStack): Intent()
class StackDestroyedIntent(val stack: ProtocolStack): Intent()

View File

@@ -0,0 +1,10 @@
package com.projectswg.forwarder.intents
import com.projectswg.forwarder.Forwarder
import me.joshlarson.jlcommon.control.Intent
import java.util.concurrent.locks.ReentrantLock
import java.util.zip.ZipOutputStream
class StartForwarderIntent(val data: Forwarder.ForwarderData): Intent()
class StopForwarderIntent: Intent()
class ClientCrashedIntent(val outputStream: ZipOutputStream, val fileMutex: ReentrantLock = ReentrantLock()): Intent()

View File

@@ -0,0 +1,7 @@
package com.projectswg.forwarder.intents
import me.joshlarson.jlcommon.control.Intent
class RequestServerConnectionIntent: Intent()
class ServerConnectedIntent: Intent()
class ServerDisconnectedIntent: Intent()

View File

@@ -0,0 +1,7 @@
package com.projectswg.forwarder.resources.networking
enum class ClientServer {
LOGIN,
ZONE,
PING
}

View File

@@ -0,0 +1,54 @@
package com.projectswg.forwarder.resources.networking
import com.projectswg.common.network.NetBuffer
import com.projectswg.common.network.packets.PacketType
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginRequestPacket
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 com.projectswg.holocore.client.HolocoreSocket
import java.nio.ByteBuffer
import java.nio.ByteOrder
class NetInterceptor(private val data: ForwarderData) {
fun interceptClient(holocore: HolocoreSocket, data: ByteArray) {
if (data.size < 6) {
holocore.send(data)
return
}
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
when (PacketType.fromCrc(bb.getInt(2))) {
PacketType.LOGIN_CLIENT_ID -> {
val loginClientId = LoginClientId(NetBuffer.wrap(bb))
if (loginClientId.username == this.data.username && loginClientId.password.isEmpty())
loginClientId.password = this.data.password
holocore.send(HoloLoginRequestPacket(loginClientId.username, loginClientId.password).encode().array())
}
else -> holocore.send(data)
}
}
fun interceptServer(data: ByteArray): ByteArray {
if (data.size < 6)
return data
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
when (PacketType.fromCrc(bb.getInt(2))) {
PacketType.LOGIN_CLUSTER_STATUS -> return getServerList(NetBuffer.wrap(bb))
else -> return data
}
}
private fun getServerList(data: NetBuffer): ByteArray {
val cluster = LoginClusterStatus()
cluster.decode(data)
for (g in cluster.galaxies) {
g.address = "127.0.0.1"
g.zonePort = this.data.zonePort
g.pingPort = this.data.pingPort
}
return cluster.encode().array()
}
}

View File

@@ -0,0 +1,38 @@
package com.projectswg.forwarder.resources.networking.data
import com.projectswg.common.network.NetBuffer
import com.projectswg.forwarder.resources.networking.packets.Fragmented
import java.util.*
class FragmentedProcessor {
private val fragmentedBuffer: MutableList<Fragmented> = ArrayList()
fun reset() {
fragmentedBuffer.clear()
}
fun addFragmented(frag: Fragmented): ByteArray? {
fragmentedBuffer.add(frag)
val firstFrag = fragmentedBuffer[0]
val payload = NetBuffer.wrap(firstFrag.payload)
val length = payload.netInt
return if (((length + 4.0) / 489).toInt() > fragmentedBuffer.size) null else processFragmentedReady(length) // Doesn't have the minimum number of required packets
}
private fun processFragmentedReady(size: Int): ByteArray {
val combined = ByteArray(size)
var index = 0
while (index < combined.size) {
val payload = fragmentedBuffer.removeAt(0).payload
val header = if (index == 0) 4 else 0
System.arraycopy(payload, header, combined, index, payload.size - header)
index += payload.size - header
}
return combined
}
}

View File

@@ -0,0 +1,68 @@
package com.projectswg.forwarder.resources.networking.data
import com.projectswg.forwarder.resources.networking.data.ProtocolStack.ConnectionStream
import com.projectswg.forwarder.resources.networking.packets.DataChannel
import com.projectswg.forwarder.resources.networking.packets.Fragmented
import java.util.*
import java.util.concurrent.BlockingQueue
import java.util.concurrent.atomic.AtomicInteger
class Packager(private val outboundRaw: BlockingQueue<ByteArray>, private val outboundPackaged: ConnectionStream<SequencedOutbound>) {
private val size: AtomicInteger = AtomicInteger(8)
private val dataChannel: MutableList<ByteArray> = ArrayList()
fun handle(maxPackaged: Int) {
var packet: ByteArray?
var packetSize: Int
while (outboundPackaged.size() < maxPackaged) {
packet = outboundRaw.poll()
if (packet == null)
break
packetSize = getPacketLength(packet)
if (size.get() + packetSize >= 16384) // max data channel size
sendDataChannel()
if (packetSize < 16384) { // if overflowed, must go into fragmented
addToDataChannel(packet, packetSize)
} else {
sendFragmented(packet)
}
}
sendDataChannel()
}
private fun addToDataChannel(packet: ByteArray, packetSize: Int) {
dataChannel.add(packet)
size.getAndAdd(packetSize)
}
private fun sendDataChannel() {
if (dataChannel.isEmpty())
return
outboundPackaged.addOrdered(SequencedOutbound(DataChannel(dataChannel)))
reset()
}
private fun sendFragmented(packet: ByteArray) {
val frags = Fragmented.split(packet)
for (frag in frags) {
outboundPackaged.addOrdered(SequencedOutbound(Fragmented(0.toShort(), frag)))
}
}
private fun reset() {
dataChannel.clear()
size.set(8)
}
private fun getPacketLength(data: ByteArray): Int {
val len = data.size
return if (len >= 255) len + 3 else len + 1
}
}

View File

@@ -0,0 +1,193 @@
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 me.joshlarson.jlcommon.log.Log
import java.net.InetSocketAddress
import java.util.*
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue
class ProtocolStack(val source: InetSocketAddress, val server: ClientServer, private val sendPacket: (InetSocketAddress, ByteArray) -> Unit) {
private val fragmentedProcessor: FragmentedProcessor = FragmentedProcessor()
private val outboundRaw: BlockingQueue<ByteArray> = LinkedBlockingQueue()
private val inbound: ConnectionStream<SequencedPacket> = ConnectionStream()
private val outbound: ConnectionStream<SequencedOutbound> = ConnectionStream()
private val packager: Packager = Packager(outboundRaw, outbound)
private var pingSource: InetSocketAddress? = null
var connectionId: Int = 0
val rxSourceSequence: Long
get() = inbound.sourceSequence
val rxSequence: Short
get() = inbound.getSequence()
val txSourceSequence: Long
get() = outbound.sourceSequence
val txSequence: Short
get() = outbound.getSequence()
val outboundPending: Int
get() = outboundRaw.size
fun send(packet: Packet) {
Log.t("Sending %s", packet)
send(packet.encode().array())
}
fun send(data: ByteArray) {
sendPacket(source, data)
}
fun sendPing(data: ByteArray) {
val pingSource = this.pingSource
if (pingSource != null)
sendPacket(pingSource, data)
}
fun getNextIncoming(): SequencedPacket? = inbound.poll()
fun getFirstUnacknowledgedOutbound(): Short {
val out = outbound.peek() ?: return -1
return out.sequence
}
fun setPingSource(source: InetSocketAddress) {
this.pingSource = source
}
fun addIncoming(packet: SequencedPacket): SequencedStatus {
return inbound.addUnordered(packet)
}
fun addFragmented(frag: Fragmented): ByteArray? {
return fragmentedProcessor.addFragmented(frag)
}
fun addOutbound(data: ByteArray) {
try {
outboundRaw.put(data)
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
}
fun clearAcknowledgedOutbound(sequence: Short) {
outbound.removeOrdered(sequence)
}
@Synchronized
fun fillOutboundPackagedBuffer(maxPackaged: Int) {
packager.handle(maxPackaged)
}
@Synchronized
fun fillOutboundBuffer(buffer: Array<SequencedOutbound?>): Int {
return outbound.fillBuffer(buffer)
}
override fun toString(): String {
return String.format("ProtocolStack[server=%s, source=%s, connectionId=%d]", server, source, connectionId)
}
class ConnectionStream<T : SequencedPacket> {
private val sequenced: PriorityQueue<T> = PriorityQueue()
private val queued: PriorityQueue<T> = PriorityQueue()
var sourceSequence: Long = 0
private set
fun getSequence(): Short {
return sourceSequence.toShort()
}
@Synchronized
fun addUnordered(packet: T): SequencedStatus {
if (SequencedPacket.compare(getSequence(), packet.sequence) > 0) {
val peek = peek()
return if (peek != null && peek.sequence == getSequence()) SequencedStatus.READY else SequencedStatus.STALE
}
if (packet.sequence == getSequence()) {
sequenced.add(packet)
sourceSequence++
// Add queued OOO packets
while (true) {
val queue = queued.peek()
if (queue == null || queue.sequence != getSequence())
break
sequenced.add(queued.poll())
sourceSequence++
}
return SequencedStatus.READY
} else {
queued.add(packet)
return SequencedStatus.OUT_OF_ORDER
}
}
@Synchronized
fun addOrdered(packet: T) {
packet.sequence = getSequence()
addUnordered(packet)
}
@Synchronized
fun removeOrdered(sequence: Short) {
val sequencesRemoved = ArrayList<Short>()
while (true) {
val packet = sequenced.peek()
if (packet == null || SequencedPacket.compare(sequence, packet.sequence) < 0)
break
val removed = sequenced.poll()
assert(packet === removed)
sequencesRemoved.add(packet.sequence)
}
Log.t("Removed acknowledged: %s", sequencesRemoved)
}
@Synchronized
fun peek(): T? {
return sequenced.peek()
}
@Synchronized
fun poll(): T? {
return sequenced.poll()
}
@Synchronized
fun fillBuffer(buffer: Array<T?>): Int {
var n = 0
for (packet in sequenced) {
if (n >= buffer.size)
break
buffer[n++] = packet
}
return n
}
fun size(): Int {
return sequenced.size
}
}
enum class SequencedStatus {
READY,
OUT_OF_ORDER,
STALE
}
}

View File

@@ -0,0 +1,23 @@
package com.projectswg.forwarder.resources.networking.data
import com.projectswg.common.network.NetBuffer
import com.projectswg.forwarder.resources.networking.packets.SequencedPacket
class SequencedOutbound(private val packet: SequencedPacket) : SequencedPacket {
var data: ByteArray = packet.encode().array()
private set
var isSent: Boolean = false
override var sequence: Short
get() = packet.sequence
set(sequence) {
this.packet.sequence = sequence
this.data = packet.encode().array()
}
override fun encode(): NetBuffer {
return packet.encode()
}
}

View File

@@ -1,103 +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[] { };
}
}
/***********************************************************************************
* 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[] { };
}
}

View File

@@ -0,0 +1,62 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class Acknowledge : Packet {
var sequence: Short = 0
constructor()
constructor(data: NetBuffer) {
decode(data)
}
constructor(sequence: Short) {
this.sequence = sequence
}
override fun decode(data: NetBuffer) {
assert(data.array().size == 4)
data.position(2)
sequence = data.netShort
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(4)
data.addNetShort(21)
data.addNetShort(sequence.toInt())
return data
}
override fun toString(): String {
return String.format("Acknowledge[%d]", sequence)
}
}

View File

@@ -0,0 +1,89 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class ClientNetworkStatusUpdate : Packet {
var tick: Int = 0
var lastUpdate: Int = 0
var averageUpdate: Int = 0
var shortestUpdate: Int = 0
var longestUpdate: Int = 0
var lastServerUpdate: Int = 0
var sent: Long = 0
var recv: Long = 0
constructor()
constructor(data: NetBuffer) {
decode(data)
}
constructor(clientTickCount: Int, lastUpdate: Int, avgUpdate: Int, shortUpdate: Int, longUpdate: Int, lastServerUpdate: Int, packetsSent: Long, packetsRecv: Long) {
this.tick = clientTickCount
this.lastUpdate = lastUpdate
this.averageUpdate = avgUpdate
this.shortestUpdate = shortUpdate
this.longestUpdate = longUpdate
this.lastServerUpdate = lastServerUpdate
this.sent = packetsSent
this.recv = packetsRecv
}
override fun decode(data: NetBuffer) {
data.netShort // 0x07
tick = data.netShort.toInt()
lastUpdate = data.netInt
averageUpdate = data.netInt
shortestUpdate = data.netInt
longestUpdate = data.netInt
lastServerUpdate = data.netInt
sent = data.netLong
recv = data.netLong
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(40)
data.addNetShort(7)
data.addNetShort(tick)
data.addNetInt(lastUpdate)
data.addNetInt(averageUpdate)
data.addNetInt(shortestUpdate)
data.addNetInt(longestUpdate)
data.addNetInt(lastServerUpdate)
data.addNetLong(sent)
data.addNetLong(recv)
return data
}
override fun toString(): String {
return String.format("ClientNetworkStatusUpdate[tick=%d, lastUpdate=%d, avgUpdate=%d, shortestUpdate=%d, longestUpdate=%d, lastServerUpdate=%d, sent=%d, recv=%d]", tick, lastUpdate, averageUpdate, shortestUpdate, longestUpdate, lastServerUpdate, sent, recv)
}
}

View File

@@ -0,0 +1,157 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
import java.util.*
class DataChannel : Packet, SequencedPacket {
private val content: MutableList<ByteArray>
private var multiPacket: Short = 0
override var sequence: Short = 0
var channel: Channel
val length: Int
get() {
var length = 6
for (packet in content) {
val addLength = packet.size
length += 1 + addLength + if (addLength >= 0xFF) 2 else 0
}
return length
}
val packets: List<ByteArray>
get() = content
val packetCount: Int
get() = content.size
constructor() {
this.content = ArrayList()
this.channel = Channel.DATA_CHANNEL_A
this.sequence = 0
this.multiPacket = 0
}
constructor(content: List<ByteArray>) {
this.content = ArrayList(content)
this.channel = Channel.DATA_CHANNEL_A
this.sequence = 0
this.multiPacket = (if (content.isEmpty()) 0 else 0x19).toShort()
}
constructor(data: NetBuffer) : this() {
decode(data)
}
constructor(packets: Array<ByteArray>) : this() {
content.addAll(listOf(*packets))
}
constructor(packet: ByteArray) : this() {
content.add(packet)
}
override fun decode(data: NetBuffer) {
super.decode(data)
when (opcode) {
9 -> channel = Channel.DATA_CHANNEL_A
10 -> channel = Channel.DATA_CHANNEL_B
11 -> channel = Channel.DATA_CHANNEL_C
12 -> channel = Channel.DATA_CHANNEL_D
else -> return
}
data.position(2)
sequence = data.netShort
multiPacket = data.netShort
if (multiPacket.toInt() == 0x19) {
var length: Int
while (data.remaining() > 1) {
length = data.byte.toInt() and 0xFF
if (length == 0xFF)
length = data.netShort.toInt()
if (length > data.remaining()) {
data.position(data.position() - 1)
return
}
content.add(data.getArray(length))
}
} else {
data.seek(-2)
content.add(data.getArray(data.remaining()))
}
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(length)
data.addNetShort(channel.opcode)
data.addNetShort(sequence.toInt())
data.addNetShort(0x19)
for (pData in content) {
if (pData.size >= 0xFF) {
data.addByte(0xFF)
data.addNetShort(pData.size)
} else {
data.addByte(pData.size)
}
data.addRawArray(pData)
}
return data
}
fun addPacket(packet: ByteArray) {
content.add(packet)
}
fun clearPackets() {
content.clear()
}
override fun equals(other: Any?): Boolean {
return other is DataChannel && sequence == other.sequence
}
override fun hashCode(): Int {
return sequence.toInt()
}
enum class Channel(val opcode: Int) {
DATA_CHANNEL_A(9),
DATA_CHANNEL_B(10),
DATA_CHANNEL_C(11),
DATA_CHANNEL_D(12)
}
override fun toString(): String {
return String.format("DataChannel[seq=%d, packets=%d]", sequence, content.size)
}
}

View File

@@ -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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class Disconnect : Packet {
var connectionId: Int = 0
private set
var reason: DisconnectReason = DisconnectReason.NONE
private set
constructor(connectionId: Int, reason: DisconnectReason) {
this.connectionId = connectionId
this.reason = reason
}
constructor(data: NetBuffer) {
this.decode(data)
}
override fun decode(data: NetBuffer) {
data.position(2)
connectionId = data.netInt
reason = getReason(data.netShort.toInt())
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(8)
data.addNetShort(5)
data.addNetInt(connectionId)
data.addNetShort(reason.reason.toInt())
return data
}
private fun getReason(reason: Int): DisconnectReason {
for (dr in DisconnectReason.values())
if (dr.reason.toInt() == reason)
return dr
return DisconnectReason.NONE
}
enum class DisconnectReason(reason: Int) {
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);
val reason: Short = reason.toShort()
}
override fun toString(): String {
return String.format("Disconnect[id=%d, reason=%s]", connectionId, reason)
}
}

View File

@@ -0,0 +1,100 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.ceil
import kotlin.math.min
class Fragmented : Packet, SequencedPacket {
override var sequence: Short = 0
lateinit var payload: ByteArray
constructor(data: NetBuffer) {
decode(data)
}
constructor(sequence: Short, payload: ByteArray) {
this.sequence = sequence
this.payload = payload
}
override fun decode(data: NetBuffer) {
super.decode(data)
val type = data.netShort
assert(type - 0x0D < 4)
this.sequence = data.netShort
this.payload = data.getArray(data.remaining())
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(4 + payload.size)
data.addNetShort(0x0D)
data.addNetShort(sequence.toInt())
data.addRawArray(payload)
return data
}
override fun equals(other: Any?): Boolean {
return other is Fragmented && sequence == other.sequence
}
override fun hashCode(): Int {
return sequence.toInt()
}
override fun toString(): String {
return String.format("Fragmented[seq=%d, len=%d]", sequence, payload.size)
}
companion object {
fun split(data: ByteArray): Array<ByteArray> {
var offset = 0
val packetCount = ceil((data.size + 4) / 16377.0).toInt() // 489
val packets = ArrayList<ByteArray>(packetCount)
for (i in 0 until packetCount) {
val header = if (i == 0) 4 else 0
val segment = ByteBuffer.allocate(min(data.size - offset - header, 16377)).order(ByteOrder.BIG_ENDIAN)
if (i == 0)
segment.putInt(data.size)
val segmentLength = segment.remaining()
segment.put(data, offset, segmentLength)
offset += segmentLength
packets[i] = segment.array()
}
return packets.toTypedArray()
}
}
}

View File

@@ -0,0 +1,27 @@
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class KeepAlive : Packet {
constructor()
constructor(data: NetBuffer) {
decode(data)
}
override fun decode(data: NetBuffer) {
data.position(2)
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(2)
data.addNetShort(0x06)
return data
}
override fun toString(): String {
return "KeepAlive[]"
}
}

View File

@@ -0,0 +1,98 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
import java.util.*
class MultiPacket @JvmOverloads constructor(private val content: MutableList<ByteArray> = ArrayList()) : Packet() {
val length: Int
get() {
var length = 2
for (packet in content) {
length += packet.size + 1
if (packet.size >= 255)
length += 2
}
return length
}
val packets: List<ByteArray>
get() = content
constructor(data: NetBuffer) : this(ArrayList<ByteArray>()) {
decode(data)
}
override fun decode(data: NetBuffer) {
data.position(2)
var pLength = getNextPacketLength(data)
while (data.remaining() >= pLength && pLength > 0) {
content.add(data.getArray(pLength))
pLength = getNextPacketLength(data)
}
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(length)
data.addNetShort(3)
for (packet in content) {
if (packet.size >= 255) {
data.addByte(255)
data.addShort(packet.size)
} else {
data.addByte(packet.size)
}
data.addRawArray(packet)
}
return data
}
fun addPacket(packet: ByteArray) {
content.add(packet)
}
fun clearPackets() {
content.clear()
}
private fun getNextPacketLength(data: NetBuffer): Int {
if (data.remaining() < 1)
return 0
val length = data.byte.toInt() and 0xFF
return if (length == 255) {
if (data.remaining() < 2) 0 else data.short.toInt() and 0xFFFF
} else length
}
override fun toString(): String {
return String.format("MultiPacket[packets=%d]", content.size)
}
}

View File

@@ -0,0 +1,62 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class OutOfOrder : Packet {
var sequence: Short = 0
constructor()
constructor(sequence: Short) {
this.sequence = sequence
}
constructor(data: NetBuffer) {
decode(data)
}
override fun decode(data: NetBuffer) {
data.position(2)
sequence = data.netShort
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(4)
data.addNetShort(0x11)
data.addNetShort(sequence.toInt())
return data
}
override fun toString(): String {
return "OutOfOrder[$sequence]"
}
}

View File

@@ -0,0 +1,46 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
import java.net.InetAddress
abstract class Packet {
var address: InetAddress? = null
var port: Int = 0
var opcode: Int = 0
open fun decode(data: NetBuffer) {
opcode = data.netShort.toInt()
data.position(0)
}
abstract fun encode(): NetBuffer
}

View File

@@ -0,0 +1,20 @@
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
import com.projectswg.common.utilities.ByteUtilities
class PingPacket(var payload: ByteArray) : Packet() {
override fun decode(data: NetBuffer) {
this.payload = data.array()
}
override fun encode(): NetBuffer {
return NetBuffer.wrap(payload)
}
override fun toString(): String {
return "PingPacket[payload=" + ByteUtilities.getHexString(payload) + "]"
}
}

View File

@@ -0,0 +1,36 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class RawSWGPacket(val rawData: ByteArray) : Packet() {
override fun encode(): NetBuffer = NetBuffer.wrap(rawData)
}

View File

@@ -0,0 +1,35 @@
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
interface SequencedPacket : Comparable<SequencedPacket> {
var sequence: Short
fun encode(): NetBuffer
override fun compareTo(other: SequencedPacket): Int {
return compare(this, other)
}
companion object {
fun compare(a: SequencedPacket, b: SequencedPacket): Int {
return compare(a.sequence, b.sequence)
}
fun compare(a: Short, b: Short): Int {
val aSeq = a.toInt() and 0xFFFF
val bSeq = b.toInt() and 0xFFFF
if (aSeq == bSeq)
return 0
var diff = bSeq - aSeq
if (diff <= 0)
diff += 0x10000
return if (diff < 30000) -1 else 1
}
}
}

View File

@@ -0,0 +1,23 @@
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class SeriousErrorAcknowledge : Packet {
constructor()
constructor(data: NetBuffer) {
decode(data)
}
override fun decode(data: NetBuffer) {
data.position(2)
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(2)
data.addNetShort( 0x1D)
return data
}
}

View File

@@ -0,0 +1,22 @@
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class SeriousErrorAcknowledgeReply : Packet {
constructor()
constructor(data: NetBuffer) {
decode(data)
}
override fun decode(data: NetBuffer) {
data.position(2)
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(2)
data.addNetShort(0x1E)
return data
}
}

View File

@@ -0,0 +1,83 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class ServerNetworkStatusUpdate : Packet {
var clientTickCount: Short = 0
var serverSyncStampLong = 0
var clientPacketsSent: Long = 0
var clientPacketsRecv: Long = 0
var serverPacketsSent: Long = 0
var serverPacketsRecv: Long = 0
constructor()
constructor(data: NetBuffer) {
decode(data)
}
constructor(clientTickCount: Int, serverSyncStamp: Int, clientSent: Long, clientRecv: Long, serverSent: Long, serverRecv: Long) {
this.clientTickCount = clientTickCount.toShort()
this.serverSyncStampLong = serverSyncStamp
this.clientPacketsSent = clientSent
this.clientPacketsRecv = clientRecv
this.serverPacketsSent = serverSent
this.serverPacketsRecv = serverRecv
}
override fun decode(data: NetBuffer) {
data.position(2)
clientTickCount = data.netShort
serverSyncStampLong = data.netInt
clientPacketsSent = data.netLong
clientPacketsRecv = data.netLong
serverPacketsSent = data.netLong
serverPacketsRecv = data.netLong
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(40)
data.addNetShort(8)
data.addNetShort(clientTickCount.toInt())
data.addNetInt(serverSyncStampLong)
data.addNetLong(clientPacketsSent)
data.addNetLong(clientPacketsRecv)
data.addNetLong(serverPacketsSent)
data.addNetLong(serverPacketsRecv)
return data
}
override fun toString(): String {
return String.format("ServerNetworkStatusUpdate[ticks=%d, syncStamp=%d, clientSent=%d, clientRecv=%d, serverSent=%d, serverRecv=%d]", clientTickCount, serverSyncStampLong, clientPacketsSent, clientPacketsRecv, serverPacketsSent, serverPacketsRecv)
}
}

View File

@@ -0,0 +1,75 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class SessionRequest : Packet {
var crcLength: Int = 0
private set
var connectionId: Int = 0
private set
var udpSize: Int = 0
private set
constructor()
constructor(data: NetBuffer) {
decode(data)
}
constructor(crcLength: Int, connectionId: Int, udpSize: Int) {
this.crcLength = crcLength
this.connectionId = connectionId
this.udpSize = udpSize
}
override fun decode(data: NetBuffer) {
super.decode(data)
data.position(2)
crcLength = data.netInt
connectionId = data.netInt
udpSize = data.netInt
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(14)
data.addNetShort(1)
data.addNetInt(crcLength)
data.addNetInt(connectionId)
data.addNetInt(udpSize)
return data
}
override fun toString(): String {
return String.format("SessionRequest[connectionId=%d, crcLength=%d, udpSize=%d]", connectionId, crcLength, udpSize)
}
}

View File

@@ -0,0 +1,83 @@
/***********************************************************************************
* 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:></http:>//www.gnu.org/licenses/>. *
* *
*/
package com.projectswg.forwarder.resources.networking.packets
import com.projectswg.common.network.NetBuffer
class SessionResponse : Packet {
var connectionId: Int = 0
var crcSeed: Int = 0
var crcLength: Byte = 0
var encryptionFlag: Byte = 0
var xorLength: Byte = 0
var udpSize: Int = 0
constructor()
constructor(data: NetBuffer) {
decode(data)
}
constructor(connectionId: Int, crcSeed: Int, crcLength: Byte, encryptionFlag: Byte, xorLength: Byte, udpSize: Int) {
this.connectionId = connectionId
this.crcSeed = crcSeed
this.crcLength = crcLength
this.encryptionFlag = encryptionFlag
this.xorLength = xorLength
this.udpSize = udpSize
}
override fun decode(data: NetBuffer) {
super.decode(data)
data.position(2)
connectionId = data.netInt
crcSeed = data.netInt
crcLength = data.byte
encryptionFlag = data.byte
xorLength = data.byte
udpSize = data.netInt
}
override fun encode(): NetBuffer {
val data = NetBuffer.allocate(17)
data.addNetShort(2)
data.addNetInt(connectionId)
data.addNetInt(crcSeed)
data.addByte(crcLength.toInt())
data.addByte(encryptionFlag.toInt())
data.addByte(xorLength.toInt())
data.addNetInt(udpSize)
return data
}
override fun toString(): String {
return String.format("SessionResponse[connectionId=%d, crcSeed=%d, crcLength=%d, encryptionFlag=%d, xorLength=%d, udpSize=%d]", connectionId, crcSeed, crcLength, encryptionFlag, xorLength, udpSize)
}
}

View File

@@ -0,0 +1,73 @@
package com.projectswg.forwarder.resources.recording
import java.io.*
import java.lang.management.ManagementFactory
import java.time.Instant
import java.time.ZoneId
import java.util.*
class PacketRecorder @Throws(FileNotFoundException::class)
constructor(file: File) : AutoCloseable, Closeable {
private val dataOut: DataOutputStream = DataOutputStream(FileOutputStream(file))
init {
writeHeader()
}
@Throws(IOException::class)
override fun close() {
dataOut.close()
}
fun record(server: Boolean, data: ByteArray) {
synchronized(dataOut) {
try {
dataOut.writeBoolean(server)
dataOut.writeLong(System.currentTimeMillis())
dataOut.writeShort(data.size)
dataOut.write(data)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun writeHeader() {
try {
dataOut.writeByte(VERSION.toInt())
writeSystemHeader()
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun writeSystemHeader() {
val os = ManagementFactory.getOperatingSystemMXBean()
val systemStrings = TreeMap<String, String>()
systemStrings["os.arch"] = os.arch
systemStrings["os.details"] = os.name + ":" + os.version
systemStrings["os.processor_count"] = os.availableProcessors.toString()
systemStrings["java.version"] = System.getProperty("java.version")
systemStrings["java.vendor"] = System.getProperty("java.vendor")
systemStrings["time.time_zone"] = ZoneId.systemDefault().id
systemStrings["time.current_time"] = Instant.now().toString()
try {
dataOut.writeByte(systemStrings.size) // Count of strings
for ((key, value) in systemStrings)
dataOut.writeUTF("$key=$value")
} catch (e: IOException) {
e.printStackTrace()
}
}
companion object {
private const val VERSION: Byte = 3
}
}

View File

@@ -0,0 +1,12 @@
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
])
class ClientConnectionManager : Manager()

View File

@@ -0,0 +1,92 @@
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.DataPacketInboundIntent
import com.projectswg.forwarder.intents.SonyPacketInboundIntent
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
class ClientInboundDataService : Service() {
private val multiplexer: IntentMultiplexer = IntentMultiplexer(this, ProtocolStack::class.java, Packet::class.java)
private val intentChain: IntentChain = IntentChain()
@IntentHandler
private fun handleSonyPacketInboundIntent(spii: SonyPacketInboundIntent) {
multiplexer.call(spii.stack, spii.packet)
}
@Multiplexer
private fun handlePingPacket(stack: ProtocolStack, ping: PingPacket) {
onData(HeartBeat(ping.payload).encode().array())
}
@Multiplexer
private fun handleRawSwgPacket(stack: ProtocolStack, data: RawSWGPacket) {
onData(data.rawData)
}
@Multiplexer
private fun handleDataChannel(stack: ProtocolStack, data: DataChannel) {
when (stack.addIncoming(data)) {
ProtocolStack.SequencedStatus.READY -> readAvailablePackets(stack)
ProtocolStack.SequencedStatus.OUT_OF_ORDER -> {
Log.d("Data/Inbound Received Data Out of Order %d", data.sequence)
for (seq in stack.rxSequence until data.sequence)
stack.send(OutOfOrder(seq.toShort()))
}
else -> { }
}
}
@Multiplexer
private fun handleFragmented(stack: ProtocolStack, frag: Fragmented) {
when (stack.addIncoming(frag)) {
ProtocolStack.SequencedStatus.READY -> readAvailablePackets(stack)
ProtocolStack.SequencedStatus.OUT_OF_ORDER -> {
Log.d("Data/Inbound Received Frag Out of Order %d", frag.sequence)
for (seq in stack.rxSequence until frag.sequence)
stack.send(OutOfOrder(seq.toShort()))
}
else -> { }
}
}
private fun readAvailablePackets(stack: ProtocolStack) {
var highestSequence: Short = -1
var updatedSequence = false
var packet = stack.getNextIncoming()
while (packet != null) {
Log.t("Data/Inbound Received: %s", packet)
if (packet is DataChannel) {
for (data in packet.packets)
onData(data)
} else if (packet is Fragmented) {
val data = stack.addFragmented((packet as Fragmented?)!!)
if (data != null)
onData(data)
}
highestSequence = packet.sequence
packet = stack.getNextIncoming()
updatedSequence = true
}
if (updatedSequence)
stack.send(Acknowledge(highestSequence))
}
private fun onData(data: ByteArray) {
val type = PacketType.fromCrc(ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getInt(2))
Log.d("Data/Inbound Received Data: %s", type)
intentChain.broadcastAfter(intentManager, DataPacketInboundIntent(data))
}
}

View File

@@ -0,0 +1,202 @@
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.*
import com.projectswg.forwarder.resources.networking.ClientServer
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.BasicScheduledThread
import me.joshlarson.jlcommon.concurrency.BasicThread
import me.joshlarson.jlcommon.concurrency.Delay
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.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class ClientOutboundDataService : Service() {
private val outboundBuffer: Array<SequencedOutbound?> = arrayOfNulls(4096)
private val multiplexer: IntentMultiplexer = IntentMultiplexer(this, ProtocolStack::class.java, Packet::class.java)
private val activeStacks: MutableSet<ProtocolStack> = ConcurrentHashMap.newKeySet()
private val sendThread: BasicThread = BasicThread("outbound-sender", Runnable { this.persistentSend() })
private val heartbeatThread: BasicScheduledThread = BasicScheduledThread("heartbeat", Runnable { this.heartbeat() })
private val zoningIn: AtomicBoolean = AtomicBoolean(false)
private val packetNotifyLock: ReentrantLock = ReentrantLock()
private val packetNotify: Condition = packetNotifyLock.newCondition()
override fun terminate(): Boolean {
if (sendThread.isExecuting)
sendThread.stop(true)
if (heartbeatThread.isRunning)
heartbeatThread.stop()
return sendThread.awaitTermination(1000) && heartbeatThread.awaitTermination(1000)
}
@IntentHandler
private fun handleStartForwarderIntent(sfi: StartForwarderIntent) {
}
@IntentHandler
private fun handleSonyPacketInboundIntent(spii: SonyPacketInboundIntent) {
multiplexer.call(spii.stack, spii.packet)
}
@IntentHandler
private fun handleClientConnectedIntent(cci: ClientConnectedIntent) {
if (sendThread.isExecuting)
return
sendThread.start()
heartbeatThread.startWithFixedRate(0, 500)
}
@IntentHandler
private fun handleClientDisconnectedIntent(cdi: ClientDisconnectedIntent) {
if (!sendThread.isExecuting)
return
sendThread.stop(true)
heartbeatThread.stop()
sendThread.awaitTermination(1000)
heartbeatThread.awaitTermination(1000)
}
@IntentHandler
private fun handleStackCreatedIntent(sci: StackCreatedIntent) {
activeStacks.add(sci.stack)
}
@IntentHandler
private fun handleStackDestroyedIntent(sdi: StackDestroyedIntent) {
activeStacks.remove(sdi.stack)
}
@IntentHandler
private fun handleDataPacketOutboundIntent(dpoi: DataPacketOutboundIntent) {
val type = PacketType.fromCrc(ByteBuffer.wrap(dpoi.data).order(ByteOrder.LITTLE_ENDIAN).getInt(2))
var filterServer: ClientServer? = ClientServer.ZONE
if (type != null) {
when (type) {
PacketType.ERROR_MESSAGE,
PacketType.SERVER_ID,
PacketType.SERVER_NOW_EPOCH_TIME -> filterServer = null
PacketType.CMD_START_SCENE -> zoningIn.set(true)
PacketType.CMD_SCENE_READY -> zoningIn.set(false)
PacketType.LOGIN_CLUSTER_STATUS,
PacketType.LOGIN_CLIENT_TOKEN,
PacketType.LOGIN_INCORRECT_CLIENT_ID,
PacketType.LOGIN_ENUM_CLUSTER,
PacketType.ENUMERATE_CHARACTER_ID,
PacketType.CHARACTER_CREATION_DISABLED,
PacketType.DELETE_CHARACTER_REQUEST,
PacketType.DELETE_CHARACTER_RESPONSE -> filterServer = ClientServer.LOGIN
PacketType.HEART_BEAT_MESSAGE -> {
val heartbeat = HeartBeat()
heartbeat.decode(NetBuffer.wrap(dpoi.data))
if (heartbeat.payload.isNotEmpty()) {
SendPongIntent(heartbeat.payload).broadcast(intentManager)
return
}
}
else -> {}
}
}
val finalFilterServer = filterServer
val stack = activeStacks.stream().filter { s -> s.server == finalFilterServer }.findFirst().orElse(null)
if (stack == null) {
Log.d("Data/Outbound Sending %s [len=%d] to %s", type, dpoi.data.size, activeStacks)
for (active in activeStacks) {
active.addOutbound(dpoi.data)
}
} else {
Log.d("Data/Outbound Sending %s [len=%d] to %s", type, dpoi.data.size, stack)
stack.addOutbound(dpoi.data)
}
packetNotifyLock.withLock { packetNotify.signal() }
}
@Multiplexer
private fun handleAcknowledgement(stack: ProtocolStack, ack: Acknowledge) {
Log.t("Data/Outbound Client Acknowledged: %d. Min Sequence: %d", ack.sequence, stack.getFirstUnacknowledgedOutbound())
stack.clearAcknowledgedOutbound(ack.sequence)
}
@Multiplexer
private fun handleOutOfOrder(stack: ProtocolStack, ooo: OutOfOrder) {
Log.t("Data/Outbound Out of Order: %d. Min Sequence: %d", ooo.sequence, stack.getFirstUnacknowledgedOutbound())
}
private fun persistentSend() {
Log.d("Data/Outbound Starting Persistent Send")
var iteration = 0
while (!Delay.isInterrupted()) {
flushPackaged(iteration % 20 == 0)
iteration++
if (zoningIn.get()) {
Delay.sleepMilli(50)
} else {
packetNotifyLock.withLock {
while (!hasPendingPacket() && !Delay.isInterrupted()) {
try {
packetNotify.await(1, TimeUnit.SECONDS)
} catch (e: InterruptedException) {
break
}
}
}
}
}
Log.d("Data/Outbound Stopping Persistent Send")
}
private fun hasPendingPacket(): Boolean {
for (stack in activeStacks) {
if (stack.outboundPending > 0)
return true
}
return false
}
private fun heartbeat() {
for (stack in activeStacks) {
stack.send(HeartBeat().encode().array())
}
}
private fun flushPackaged(overrideSent: Boolean) {
for (stack in activeStacks) {
stack.fillOutboundPackagedBuffer(outboundBuffer.size)
val count = stack.fillOutboundBuffer(outboundBuffer)
var sent = 0
for (i in 0 until count) {
val out = outboundBuffer[i] ?: continue
if (overrideSent || !out.isSent) {
stack.send(out.data)
out.isSent = true
sent++
}
}
if (sent > 0)
Log.t("Data/Outbound Sent ${outboundBuffer[0]?.sequence} - ${outboundBuffer[count - 1]?.sequence} [$count]")
else if (count > 0)
Log.t("Data/Outbound Waiting to send ${outboundBuffer[0]?.sequence} - ${outboundBuffer[count - 1]?.sequence} [$count]")
}
}
}

View File

@@ -0,0 +1,46 @@
package com.projectswg.forwarder.services.client
import com.projectswg.forwarder.intents.SonyPacketInboundIntent
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
class ClientProtocolService : Service() {
private val multiplexer: IntentMultiplexer = IntentMultiplexer(this, ProtocolStack::class.java, Packet::class.java)
@IntentHandler
private fun handleSonyPacketInboundIntent(spii: SonyPacketInboundIntent) {
multiplexer.call(spii.stack, spii.packet)
}
@Multiplexer
private fun handleClientNetworkStatus(stack: ProtocolStack, update: ClientNetworkStatusUpdate) {
val serverNet = ServerNetworkStatusUpdate()
serverNet.clientTickCount = update.tick.toShort()
serverNet.serverSyncStampLong = (System.currentTimeMillis() - GALACTIC_BASE_TIME).toInt()
serverNet.clientPacketsSent = update.sent
serverNet.clientPacketsRecv = update.recv
serverNet.serverPacketsSent = stack.txSourceSequence
serverNet.serverPacketsRecv = stack.rxSourceSequence
stack.send(serverNet)
}
@Multiplexer
private fun handleKeepAlive(stack: ProtocolStack, keepAlive: KeepAlive) {
stack.send(KeepAlive())
}
companion object {
private const val GALACTIC_BASE_TIME = 1323043200
}
}

View File

@@ -0,0 +1,300 @@
package com.projectswg.forwarder.services.client
import com.projectswg.common.network.NetBuffer
import com.projectswg.forwarder.Forwarder.ForwarderData
import com.projectswg.forwarder.intents.*
import com.projectswg.forwarder.resources.networking.ClientServer
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.network.UDPServer
import me.joshlarson.jlcommon.utilities.ByteUtilities
import java.net.*
import java.nio.BufferUnderflowException
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import java.util.function.Consumer
class ClientServerService : Service() {
private val intentChain: IntentChain = IntentChain()
private val stacks: MutableMap<ClientServer, ProtocolStack> = EnumMap(ClientServer::class.java)
private val serverConnection: AtomicBoolean = AtomicBoolean(false)
private val cachedSessionRequest: AtomicReference<SessionRequest> = AtomicReference<SessionRequest>(null)
private val lastPing: AtomicReference<InetSocketAddress> = AtomicReference<InetSocketAddress>(null)
private var data: ForwarderData? = null
private var loginServer: UDPServer? = null
private var zoneServer: UDPServer? = null
private var pingServer: UDPServer? = null
override fun isOperational(): Boolean {
if (data == null)
return true
if (loginServer?.isRunning != true)
return false
if (zoneServer?.isRunning != true)
return false
return pingServer?.isRunning == true
}
override fun stop(): Boolean {
closeConnection(ClientServer.LOGIN)
closeConnection(ClientServer.ZONE)
return true
}
@IntentHandler
private fun handleStartForwarderIntent(sfi: StartForwarderIntent) {
var loginServer: UDPServer? = null
var zoneServer: UDPServer? = null
var pingServer: UDPServer? = null
try {
val data = sfi.data
Log.t("Initializing login udp server...")
loginServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.loginPort), 16384, Consumer<DatagramPacket> { this.onLoginPacket(it) })
Log.t("Initializing zone udp server...")
zoneServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.zonePort), 16384, Consumer<DatagramPacket> { this.onZonePacket(it) })
Log.t("Initializing ping udp server...")
pingServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.pingPort), 16384, Consumer<DatagramPacket> { this.onPingPacket(it) })
Log.t("Binding to login server...")
loginServer.bind { this.customizeUdpServer(it) }
Log.t("Binding to zone server...")
zoneServer.bind { this.customizeUdpServer(it) }
Log.t("Binding to ping server...")
pingServer.bind { this.customizeUdpServer(it) }
this.loginServer = loginServer
this.zoneServer = zoneServer
this.pingServer = pingServer
data.loginPort = loginServer.port
data.zonePort = zoneServer.port
data.pingPort = pingServer.port
Log.i("Initialized login (%d), zone (%d), and ping (%d) servers", loginServer.port, zoneServer.port, pingServer.port)
} catch (e: SocketException) {
Log.a(e)
loginServer?.close()
zoneServer?.close()
pingServer?.close()
}
data = sfi.data
}
@IntentHandler
private fun handleStopForwarderIntent(sfi: StopForwarderIntent) {
Log.t("Closing the login udp server...")
loginServer?.close()
loginServer = null
Log.t("Closing the zone udp server...")
zoneServer?.close()
zoneServer = null
Log.t("Closing the ping udp server...")
pingServer?.close()
pingServer = null
Log.i("Closed the login, zone, and ping udp servers")
}
@IntentHandler
private fun handleServerConnectedIntent(sci: ServerConnectedIntent) {
serverConnection.set(true)
val request = this.cachedSessionRequest.getAndSet(null)
if (request != null) {
val data = request.encode().array()
onLoginPacket(DatagramPacket(data, data.size, InetSocketAddress(request.address, request.port)))
}
}
@IntentHandler
private fun handleServerDisconnectedIntent(sdi: ServerDisconnectedIntent) {
serverConnection.set(false)
closeConnection(ClientServer.LOGIN)
closeConnection(ClientServer.ZONE)
closeConnection(ClientServer.PING)
}
@IntentHandler
private fun handleSendPongIntent(spi: SendPongIntent) {
val lastPing = this.lastPing.get()
if (lastPing != null)
send(lastPing, ClientServer.PING, spi.data)
}
private fun customizeUdpServer(socket: DatagramSocket) {
try {
socket.reuseAddress = false
socket.trafficClass = 0x10
socket.broadcast = false
socket.receiveBufferSize = 64 * 1024
socket.sendBufferSize = 64 * 1024
} catch (e: SocketException) {
Log.w(e)
}
}
private fun onLoginPacket(packet: DatagramPacket) {
process(packet.socketAddress as InetSocketAddress, ClientServer.LOGIN, packet.data)
}
private fun onZonePacket(packet: DatagramPacket) {
process(packet.socketAddress as InetSocketAddress, ClientServer.ZONE, packet.data)
}
private fun onPingPacket(packet: DatagramPacket) {
val source = packet.socketAddress as InetSocketAddress
lastPing.set(source)
val stack = stacks[ClientServer.ZONE]
val pingPacket = PingPacket(packet.data)
pingPacket.address = source.address
pingPacket.port = source.port
if (stack != null)
intentChain.broadcastAfter(intentManager, SonyPacketInboundIntent(stack, pingPacket))
}
private fun send(addr: InetSocketAddress, server: ClientServer, data: ByteArray) {
when (server) {
ClientServer.LOGIN -> loginServer?.send(addr, data)
ClientServer.ZONE -> zoneServer?.send(addr, data)
ClientServer.PING -> pingServer?.send(addr, data)
}
}
private fun process(source: InetSocketAddress, server: ClientServer, data: ByteArray) {
val parsed: Packet?
try {
parsed = if (server === ClientServer.PING) PingPacket(data) else parse(data)
if (parsed == null)
return
} catch (e: BufferUnderflowException) {
Log.w("Failed to parse packet: %s", ByteUtilities.getHexString(data))
return
}
parsed.address = source.address
parsed.port = source.port
if (parsed is MultiPacket) {
for (child in parsed.packets) {
process(source, server, child)
}
} else {
broadcast(source, server, parsed)
}
}
private fun broadcast(source: InetSocketAddress, server: ClientServer, parsed: Packet) {
val stack = process(source, server, parsed)
if (stack == null) {
Log.t("[%s]@%s DROPPED %s", source, server, parsed)
return
}
Log.t("[%s]@%s sent: %s", source, server, parsed)
intentChain.broadcastAfter(intentManager, SonyPacketInboundIntent(stack, parsed))
}
private fun process(source: InetSocketAddress, server: ClientServer, parsed: Packet): ProtocolStack? {
Log.t("Process [%b] %s", serverConnection.get(), parsed)
if (!serverConnection.get()) {
if (parsed is SessionRequest) {
cachedSessionRequest.set(parsed)
intentChain.broadcastAfter(intentManager, RequestServerConnectionIntent())
}
for (serverType in ClientServer.values())
closeConnection(serverType)
return null
}
if (parsed is SessionRequest)
return onSessionRequest(source, server, parsed)
if (parsed is Disconnect)
return onDisconnect(source, server, parsed)
val stack = stacks[server] ?: return null
if (parsed is PingPacket) {
stack.setPingSource(source)
} else if (stack.source != source || stack.server !== server) {
return null
}
return stack
}
private fun onSessionRequest(source: InetSocketAddress, server: ClientServer, request: SessionRequest): ProtocolStack? {
val current = stacks[server]
if (current != null && request.connectionId != current.connectionId) {
closeConnection(server)
return null
}
val stack = ProtocolStack(source, server) { remote, data -> send(remote, server, data) }
stack.connectionId = request.connectionId
openConnection(server, stack)
return stack
}
private fun onDisconnect(source: InetSocketAddress, server: ClientServer, disconnect: Disconnect): ProtocolStack? {
// Sets the current stack to null if it matches the Disconnect packet
val stack = stacks[server]
if (stack != null && stack.source == source && stack.connectionId == disconnect.connectionId) {
closeConnection(server)
} else {
send(source, server, Disconnect(disconnect.connectionId, DisconnectReason.APPLICATION).encode().array())
}
return stack
}
private fun openConnection(server: ClientServer, stack: ProtocolStack) {
closeConnection(server)
if (server === ClientServer.LOGIN) {
closeConnection(ClientServer.ZONE)
intentChain.broadcastAfter(intentManager, ClientConnectedIntent())
}
stacks[server] = stack
stack.send(SessionResponse(stack.connectionId, 0, 0.toByte(), 0.toByte(), 0.toByte(), 16384))
intentChain.broadcastAfter(intentManager, StackCreatedIntent(stack))
}
private fun closeConnection(server: ClientServer) {
val stack = stacks.remove(server) ?: return
stack.send(Disconnect(stack.connectionId, DisconnectReason.APPLICATION))
intentChain.broadcastAfter(intentManager, StackDestroyedIntent(stack))
if (stacks.isEmpty())
intentChain.broadcastAfter(intentManager, ClientDisconnectedIntent())
}
private fun parse(rawData: ByteArray): Packet? {
if (rawData.size < 4)
return null
val data = NetBuffer.wrap(rawData)
val opcode = data.netShort.toInt()
data.position(0)
when (opcode) {
0x01 -> return SessionRequest(data)
0x03 -> return MultiPacket(data)
0x05 -> return Disconnect(data)
0x06 -> return KeepAlive(data)
0x07 -> return ClientNetworkStatusUpdate(data)
0x09, 0x0A, 0x0B, 0x0C -> return DataChannel(data)
0x0D, 0x0E, 0x0F, 0x10 -> return Fragmented(data)
0x11, 0x12, 0x13, 0x14 -> return OutOfOrder(data)
0x15, 0x16, 0x17, 0x18 -> return Acknowledge(data)
else -> {
if (rawData.size >= 6)
return RawSWGPacket(rawData)
Log.w("Unknown SOE packet: %d %s", opcode, ByteUtilities.getHexString(data.array()))
return null
}
}
}
}

View File

@@ -0,0 +1,10 @@
package com.projectswg.forwarder.services.crash
import me.joshlarson.jlcommon.control.Manager
import me.joshlarson.jlcommon.control.ManagerStructure
@ManagerStructure(children = [
PacketRecordingService::class,
IntentRecordingService::class
])
class CrashManager : Manager()

View File

@@ -0,0 +1,127 @@
package com.projectswg.forwarder.services.crash
import com.projectswg.forwarder.Forwarder.ForwarderData
import com.projectswg.forwarder.intents.*
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
class IntentRecordingService : Service() {
private val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yy HH:mm:ss.SSS zzz").withZone(ZoneId.systemDefault())
private var logPath: Path? = null
private var logWriter: FileWriter? = null
private var data: ForwarderData? = null
override fun initialize(): Boolean {
try {
logPath = Files.createTempFile("HolocoreIntents", ".txt")
logWriter = FileWriter(logPath!!.toFile())
} catch (e: IOException) {
Log.a(e)
return false
}
return true
}
override fun terminate(): Boolean {
try {
if (logWriter != null)
logWriter!!.close()
if (logPath != null)
return logPath!!.toFile().delete()
} catch (e: IOException) {
Log.w(e)
return false
}
return true
}
@IntentHandler
private fun handleClientConnectedIntent(cci: ClientConnectedIntent) {
val data = this.data
if (data == null)
log(cci, "")
else
log(cci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.address, data.username, data.loginPort, data.zonePort)
}
@IntentHandler
private fun handleClientDisconnectedIntent(cdi: ClientDisconnectedIntent) {
log(cdi, "")
}
@IntentHandler
private fun handleStartForwarderIntent(sfi: StartForwarderIntent) {
this.data = sfi.data
}
@IntentHandler
private fun handleStopForwarderIntent(sfi: StopForwarderIntent) {
val data = this.data
if (data == null)
log(sfi, "")
else
log(sfi, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.address, data.username, data.loginPort, data.zonePort)
}
@IntentHandler
private fun handleServerConnectedIntent(sci: ServerConnectedIntent) {
val data = this.data
if (data == null)
log(sci, "")
else
log(sci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.address, data.username, data.loginPort, data.zonePort)
}
@IntentHandler
private fun handleServerDisconnectedIntent(sdi: ServerDisconnectedIntent) {
log(sdi, "")
}
@IntentHandler
private fun handleClientCrashedIntent(cci: ClientCrashedIntent) {
log(cci, "")
try {
logWriter!!.flush()
val data = Files.readAllBytes(logPath!!)
val entry = ZipEntry("log.txt")
entry.time = System.currentTimeMillis()
entry.size = data.size.toLong()
entry.method = ZipOutputStream.DEFLATED
synchronized(cci.fileMutex) {
cci.outputStream.putNextEntry(entry)
cci.outputStream.write(data)
cci.outputStream.closeEntry()
}
} catch (e: IOException) {
Log.w("Failed to write intent data to crash log - IOException")
Log.w(e)
}
}
@Synchronized
private fun log(i: Intent, message: String, vararg args: Any?) {
try {
logWriter!!.write(dateTimeFormatter.format(Instant.now()) + ": " + i.javaClass.simpleName + ' '.toString() + String.format(message, *args) + '\r'.toString() + '\n'.toString())
} catch (e: IOException) {
Log.e("Failed to write to intent log")
}
}
}

View File

@@ -0,0 +1,76 @@
package com.projectswg.forwarder.services.crash
import com.projectswg.forwarder.intents.ClientCrashedIntent
import com.projectswg.forwarder.intents.DataPacketInboundIntent
import com.projectswg.forwarder.intents.DataPacketOutboundIntent
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
class PacketRecordingService : Service() {
private var recorderPath: Path? = null
private var recorder: PacketRecorder? = null
override fun initialize(): Boolean {
try {
recorderPath = Files.createTempFile("HolocorePackets", ".hcap")
recorder = PacketRecorder(recorderPath!!.toFile())
} catch (e: IOException) {
Log.a(e)
return false
}
return true
}
override fun terminate(): Boolean {
try {
if (recorder != null)
recorder!!.close()
return recorderPath!!.toFile().delete()
} catch (e: IOException) {
Log.w(e)
return false
}
}
@IntentHandler
private fun handleClientCrashedIntent(cci: ClientCrashedIntent) {
try {
val data = Files.readAllBytes(recorderPath!!)
val entry = ZipEntry("packet_log.hcap")
entry.time = System.currentTimeMillis()
entry.size = data.size.toLong()
entry.method = ZipOutputStream.DEFLATED
synchronized(cci.fileMutex) {
cci.outputStream.putNextEntry(entry)
cci.outputStream.write(data)
cci.outputStream.closeEntry()
}
} catch (e: IOException) {
Log.w("Failed to write packet data to crash log - IOException")
Log.w(e)
}
}
@IntentHandler
private fun handleDataPacketInboundIntent(dpii: DataPacketInboundIntent) {
recorder?.record(false, dpii.data)
}
@IntentHandler
private fun handleDataPacketOutboundIntent(dpoi: DataPacketOutboundIntent) {
recorder?.record(true, dpoi.data)
}
}

View File

@@ -0,0 +1,7 @@
package com.projectswg.forwarder.services.server
import me.joshlarson.jlcommon.control.Manager
import me.joshlarson.jlcommon.control.ManagerStructure
@ManagerStructure(children = [ServerConnectionService::class])
class ServerConnectionManager : Manager()

View File

@@ -0,0 +1,172 @@
package com.projectswg.forwarder.services.server
import com.projectswg.common.network.NetBuffer
import com.projectswg.common.network.packets.PacketType
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginResponsePacket
import com.projectswg.common.network.packets.swg.login.*
import com.projectswg.forwarder.Forwarder.ForwarderData
import com.projectswg.forwarder.intents.*
import com.projectswg.forwarder.resources.networking.NetInterceptor
import com.projectswg.holocore.client.HolocoreSocket
import me.joshlarson.jlcommon.concurrency.BasicThread
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.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
class ServerConnectionService : Service() {
private val intentChain: IntentChain = IntentChain()
private val thread: BasicThread = BasicThread("server-connection", Runnable { this.primaryConnectionLoop() })
private lateinit var interceptor: NetInterceptor // set by StartForwarderIntent
private lateinit var data: ForwarderData // set by StartForwarderIntent
private var holocore: HolocoreSocket? = null
override fun stop(): Boolean {
return stopRunningLoop(HoloConnectionStopped.ConnectionStoppedReason.APPLICATION)
}
@IntentHandler
private fun handleStartForwarderIntent(sfi: StartForwarderIntent) {
interceptor = NetInterceptor(sfi.data)
data = sfi.data
}
@IntentHandler
private fun handleStopForwarderIntent(sfi: StopForwarderIntent) {
stopRunningLoop(if (data.crashed) HoloConnectionStopped.ConnectionStoppedReason.CRASH else HoloConnectionStopped.ConnectionStoppedReason.APPLICATION)
}
@IntentHandler
private fun handleRequestServerConnectionIntent(rsci: RequestServerConnectionIntent) {
val holocore = this.holocore
if (holocore != null)
return // It's trying to connect - give it a little more time
if (stopRunningLoop(HoloConnectionStopped.ConnectionStoppedReason.NEW_CONNECTION))
thread.start()
}
@IntentHandler
private fun handleClientDisconnectedIntent(cdi: ClientDisconnectedIntent) {
stopRunningLoop(HoloConnectionStopped.ConnectionStoppedReason.APPLICATION)
}
@IntentHandler
private fun handleDataPacketInboundIntent(dpii: DataPacketInboundIntent) {
val holocore = this.holocore ?: return
val data = dpii.data
if (data.size < 6) {
return // not a valid packet
}
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
when (PacketType.fromCrc(bb.getInt(2))) {
PacketType.LOGIN_CLIENT_ID -> {
val loginClientId = LoginClientId(NetBuffer.wrap(bb))
if (loginClientId.username == this.data.username && loginClientId.password.isEmpty())
loginClientId.password = this.data.password
holocore.send(loginClientId.encode().array())
}
else -> holocore.send(data)
}
}
private fun stopRunningLoop(reason: HoloConnectionStopped.ConnectionStoppedReason): Boolean {
if (!thread.isExecuting)
return true
thread.stop(true)
Log.d("Terminating connection with the server. Reason: $reason")
holocore?.send(HoloConnectionStopped(reason).encode().array())
holocore?.close()
return thread.awaitTermination(1000)
}
private fun primaryConnectionLoop() {
var didConnect = false
try {
val address = data.address ?: return
HolocoreSocket(address.address, address.port, data.isVerifyServer, data.isEncryptionEnabled).use { holocore ->
this.holocore = holocore
Log.t("Attempting to connect to server at %s", holocore.remoteAddress)
try {
holocore.connect(CONNECT_TIMEOUT)
} catch (e: IOException) {
Log.e("Failed to connect to server")
return
}
didConnect = true
intentChain.broadcastAfter(intentManager, ServerConnectedIntent())
Log.i("Successfully connected to server at %s", holocore.remoteAddress)
while (holocore.isConnected) {
if (primaryConnectionLoopReceive(holocore))
continue // More packets to receive
if (holocore.isConnected)
Log.w("Server connection interrupted")
else
Log.w("Server closed connection!")
return
}
}
} catch (t: Throwable) {
Log.w("Caught unknown exception in server connection! %s: %s", t.javaClass.name, t.message)
Log.w(t)
} finally {
Log.i("Disconnected from server.")
if (didConnect)
intentChain.broadcastAfter(intentManager, ServerDisconnectedIntent())
this.holocore = null
}
}
private fun primaryConnectionLoopReceive(holocore: HolocoreSocket): Boolean {
val inbound = holocore.receive() ?: return false
val data = inbound.data
if (data.size < 6)
return true
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
when (PacketType.fromCrc(bb.getInt(2))) {
PacketType.LOGIN_CLUSTER_STATUS -> {
val cluster = LoginClusterStatus(NetBuffer.wrap(data))
for (g in cluster.galaxies) {
g.address = "127.0.0.1"
g.zonePort = this.data.zonePort
g.pingPort = this.data.pingPort
}
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(cluster.encode().array()))
}
PacketType.HOLO_LOGIN_RESPONSE -> {
val response = HoloLoginResponsePacket(NetBuffer.wrap(data))
for (g in response.galaxies) {
g.address = "127.0.0.1"
g.zonePort = this.data.zonePort
g.pingPort = this.data.pingPort
}
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginClientToken(ByteArray(24), 0, "").encode().array()))
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(CharacterCreationDisabled().encode().array()))
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginEnumCluster(response.galaxies, 2).encode().array()))
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(EnumerateCharacterId(response.characters).encode().array()))
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginClusterStatus(response.galaxies).encode().array()))
}
else -> {
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(interceptor.interceptServer(inbound.data)))
}
}
return true
}
companion object {
private const val CONNECT_TIMEOUT = 5000
}
}