diff --git a/build.gradle b/build.gradle index b3b9ca6..6abbd7a 100644 --- a/build.gradle +++ b/build.gradle @@ -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" + } +} diff --git a/client-holocore b/client-holocore index a655e1a..1fb38ec 160000 --- a/client-holocore +++ b/client-holocore @@ -1 +1 @@ -Subproject commit a655e1a47655f3165472449cc42a92ac12a5981f +Subproject commit 1fb38ecf37d9b79b782dda87dec65e277c532a61 diff --git a/pswgcommon b/pswgcommon index 81bb85c..9c041be 160000 --- a/pswgcommon +++ b/pswgcommon @@ -1 +1 @@ -Subproject commit 81bb85c1ee17f3ee9653588498dd55cf7ab6994d +Subproject commit 9c041be3efd1fd4cfab5e19a54e7992f16be99d2 diff --git a/src/main/java/com/projectswg/forwarder/ConnectionManager.java b/src/main/java/com/projectswg/forwarder/ConnectionManager.java deleted file mode 100644 index 970e27c..0000000 --- a/src/main/java/com/projectswg/forwarder/ConnectionManager.java +++ /dev/null @@ -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 { - -} diff --git a/src/main/java/com/projectswg/forwarder/Forwarder.java b/src/main/java/com/projectswg/forwarder/Forwarder.java deleted file mode 100644 index 1e0e319..0000000 --- a/src/main/java/com/projectswg/forwarder/Forwarder.java +++ /dev/null @@ -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 managers = Collections.singletonList(primary); - Manager.start(managers); - new StartForwarderIntent(data).broadcast(intentManager); - Manager.run(managers, 100); - List 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; - } - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/ClientConnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/ClientConnectedIntent.java deleted file mode 100644 index a44f4c9..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/ClientConnectedIntent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.projectswg.forwarder.intents.client; - -import me.joshlarson.jlcommon.control.Intent; - -public class ClientConnectedIntent extends Intent { - - public ClientConnectedIntent() { - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/ClientDisconnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/ClientDisconnectedIntent.java deleted file mode 100644 index 7c5037d..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/ClientDisconnectedIntent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.projectswg.forwarder.intents.client; - -import me.joshlarson.jlcommon.control.Intent; - -public class ClientDisconnectedIntent extends Intent { - - public ClientDisconnectedIntent() { - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/DataPacketInboundIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/DataPacketInboundIntent.java deleted file mode 100644 index 990b8fe..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/DataPacketInboundIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/DataPacketOutboundIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/DataPacketOutboundIntent.java deleted file mode 100644 index ce8acb9..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/DataPacketOutboundIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/SendPongIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/SendPongIntent.java deleted file mode 100644 index e8d42c8..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/SendPongIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/SonyPacketInboundIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/SonyPacketInboundIntent.java deleted file mode 100644 index 8ff1456..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/SonyPacketInboundIntent.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/StackCreatedIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/StackCreatedIntent.java deleted file mode 100644 index b287491..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/StackCreatedIntent.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/client/StackDestroyedIntent.java b/src/main/java/com/projectswg/forwarder/intents/client/StackDestroyedIntent.java deleted file mode 100644 index ed894af..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/client/StackDestroyedIntent.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/control/ClientCrashedIntent.java b/src/main/java/com/projectswg/forwarder/intents/control/ClientCrashedIntent.java deleted file mode 100644 index af55df9..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/control/ClientCrashedIntent.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/com/projectswg/forwarder/intents/control/StartForwarderIntent.java b/src/main/java/com/projectswg/forwarder/intents/control/StartForwarderIntent.java deleted file mode 100644 index 6baff8c..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/control/StartForwarderIntent.java +++ /dev/null @@ -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(); - } -} diff --git a/src/main/java/com/projectswg/forwarder/intents/control/StopForwarderIntent.java b/src/main/java/com/projectswg/forwarder/intents/control/StopForwarderIntent.java deleted file mode 100644 index 4f713a5..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/control/StopForwarderIntent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.projectswg.forwarder.intents.control; - -import me.joshlarson.jlcommon.control.Intent; - -public class StopForwarderIntent extends Intent { - - public StopForwarderIntent() { - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/server/RequestServerConnectionIntent.java b/src/main/java/com/projectswg/forwarder/intents/server/RequestServerConnectionIntent.java deleted file mode 100644 index 7480873..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/server/RequestServerConnectionIntent.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -package com.projectswg.forwarder.intents.server; - -import me.joshlarson.jlcommon.control.Intent; - -public class RequestServerConnectionIntent extends Intent { - - public RequestServerConnectionIntent() { - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/server/ServerConnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/server/ServerConnectedIntent.java deleted file mode 100644 index eb18bfd..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/server/ServerConnectedIntent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.projectswg.forwarder.intents.server; - -import me.joshlarson.jlcommon.control.Intent; - -public class ServerConnectedIntent extends Intent { - - public ServerConnectedIntent() { - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/intents/server/ServerDisconnectedIntent.java b/src/main/java/com/projectswg/forwarder/intents/server/ServerDisconnectedIntent.java deleted file mode 100644 index 80bfcb5..0000000 --- a/src/main/java/com/projectswg/forwarder/intents/server/ServerDisconnectedIntent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.projectswg.forwarder.intents.server; - -import me.joshlarson.jlcommon.control.Intent; - -public class ServerDisconnectedIntent extends Intent { - - public ServerDisconnectedIntent() { - - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/ClientServer.java b/src/main/java/com/projectswg/forwarder/resources/networking/ClientServer.java deleted file mode 100644 index 0b81aba..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/ClientServer.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.projectswg.forwarder.resources.networking; - -public enum ClientServer { - LOGIN, - ZONE, - PING -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/NetInterceptor.java b/src/main/java/com/projectswg/forwarder/resources/networking/NetInterceptor.java deleted file mode 100644 index d821382..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/NetInterceptor.java +++ /dev/null @@ -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(); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.java deleted file mode 100644 index c6839e0..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.java +++ /dev/null @@ -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 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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/Packager.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/Packager.java deleted file mode 100644 index 346ba3e..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/data/Packager.java +++ /dev/null @@ -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 dataChannel; - private final BlockingQueue outboundRaw; - private final ConnectionStream outboundPackaged; - - public Packager(BlockingQueue outboundRaw, ConnectionStream 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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/ProtocolStack.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/ProtocolStack.java deleted file mode 100644 index 94691f5..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/data/ProtocolStack.java +++ /dev/null @@ -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 sender; - private final ClientServer server; - private final BlockingQueue outboundRaw; - private final ConnectionStream inbound; - private final ConnectionStream outbound; - private final Packager packager; - - private InetSocketAddress pingSource; - private int connectionId; - - public ProtocolStack(InetSocketAddress source, ClientServer server, BiConsumer 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 { - - private final PriorityQueue sequenced; - private final PriorityQueue 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 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 - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.java b/src/main/java/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.java deleted file mode 100644 index 2a5d067..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Acknowledge.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Acknowledge.java deleted file mode 100644 index b71e060..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Acknowledge.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.java deleted file mode 100644 index a6ae217..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/DataChannel.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/DataChannel.java deleted file mode 100644 index 3f8d341..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/DataChannel.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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 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 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 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()); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Disconnect.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Disconnect.java deleted file mode 100644 index 0f1665b..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Disconnect.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Fragmented.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Fragmented.java deleted file mode 100644 index 7d9507a..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Fragmented.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/KeepAlive.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/KeepAlive.java deleted file mode 100644 index 61ef7a2..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/KeepAlive.java +++ /dev/null @@ -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[]"); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/MultiPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/MultiPacket.java deleted file mode 100644 index ab6d48f..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/MultiPacket.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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 content; - - public MultiPacket() { - this(new ArrayList<>()); - } - - public MultiPacket(ByteBuffer data) { - this(new ArrayList<>()); - decode(data); - } - - public MultiPacket(List 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 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()); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.java deleted file mode 100644 index 1a46d36..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Packet.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/Packet.java deleted file mode 100644 index e7bc9e3..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/Packet.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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 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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/PingPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/PingPacket.java deleted file mode 100644 index d875c62..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/PingPacket.java +++ /dev/null @@ -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) + "]"; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.java deleted file mode 100644 index a048dcf..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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; - } -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.java deleted file mode 100644 index 688b782..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.java +++ /dev/null @@ -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 { - 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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.java deleted file mode 100644 index 1201373..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.java deleted file mode 100644 index 5eea8d1..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.java deleted file mode 100644 index 4ef692e..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionRequest.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionRequest.java deleted file mode 100644 index 1217797..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionRequest.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionResponse.java b/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionResponse.java deleted file mode 100644 index 4a13f87..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/networking/packets/SessionResponse.java +++ /dev/null @@ -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 . * -* * -***********************************************************************************/ -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); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/resources/recording/PacketRecorder.java b/src/main/java/com/projectswg/forwarder/resources/recording/PacketRecorder.java deleted file mode 100644 index b2dbc1f..0000000 --- a/src/main/java/com/projectswg/forwarder/resources/recording/PacketRecorder.java +++ /dev/null @@ -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 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 e : systemStrings.entrySet()) - dataOut.writeUTF(e.getKey() + "=" + e.getValue()); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientConnectionManager.java b/src/main/java/com/projectswg/forwarder/services/client/ClientConnectionManager.java deleted file mode 100644 index a4b25b3..0000000 --- a/src/main/java/com/projectswg/forwarder/services/client/ClientConnectionManager.java +++ /dev/null @@ -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 { - -} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientInboundDataService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientInboundDataService.java deleted file mode 100644 index 9a26fdc..0000000 --- a/src/main/java/com/projectswg/forwarder/services/client/ClientInboundDataService.java +++ /dev/null @@ -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)); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientOutboundDataService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientOutboundDataService.java deleted file mode 100644 index 9040f09..0000000 --- a/src/main/java/com/projectswg/forwarder/services/client/ClientOutboundDataService.java +++ /dev/null @@ -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 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); - } - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientProtocolService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientProtocolService.java deleted file mode 100644 index ad077a3..0000000 --- a/src/main/java/com/projectswg/forwarder/services/client/ClientProtocolService.java +++ /dev/null @@ -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()); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/client/ClientServerService.java b/src/main/java/com/projectswg/forwarder/services/client/ClientServerService.java deleted file mode 100644 index 548dbb1..0000000 --- a/src/main/java/com/projectswg/forwarder/services/client/ClientServerService.java +++ /dev/null @@ -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 stacks; - private final AtomicBoolean serverConnection; - private final AtomicReference cachedSessionRequest; - private final AtomicReference 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; - } - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/crash/CrashManager.java b/src/main/java/com/projectswg/forwarder/services/crash/CrashManager.java deleted file mode 100644 index 135ee49..0000000 --- a/src/main/java/com/projectswg/forwarder/services/crash/CrashManager.java +++ /dev/null @@ -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 { - -} diff --git a/src/main/java/com/projectswg/forwarder/services/crash/IntentRecordingService.java b/src/main/java/com/projectswg/forwarder/services/crash/IntentRecordingService.java deleted file mode 100644 index ede798e..0000000 --- a/src/main/java/com/projectswg/forwarder/services/crash/IntentRecordingService.java +++ /dev/null @@ -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"); - } - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/crash/PacketRecordingService.java b/src/main/java/com/projectswg/forwarder/services/crash/PacketRecordingService.java deleted file mode 100644 index fd75337..0000000 --- a/src/main/java/com/projectswg/forwarder/services/crash/PacketRecordingService.java +++ /dev/null @@ -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()); - } - -} diff --git a/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionManager.java b/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionManager.java deleted file mode 100644 index d65d447..0000000 --- a/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionManager.java +++ /dev/null @@ -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 { - -} diff --git a/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionService.java b/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionService.java deleted file mode 100644 index 965cab4..0000000 --- a/src/main/java/com/projectswg/forwarder/services/server/ServerConnectionService.java +++ /dev/null @@ -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; - } - } - -} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ce576ef..976fc49 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -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; diff --git a/src/main/kotlin/com/projectswg/forwarder/ConnectionManager.kt b/src/main/kotlin/com/projectswg/forwarder/ConnectionManager.kt new file mode 100644 index 0000000..1c10eb6 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/ConnectionManager.kt @@ -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() diff --git a/src/main/kotlin/com/projectswg/forwarder/Forwarder.kt b/src/main/kotlin/com/projectswg/forwarder/Forwarder.kt new file mode 100644 index 0000000..4a23839 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/Forwarder.kt @@ -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(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) { + SafeMain.main("") { mainRunnable() } + } + + private fun mainRunnable() { + Log.addWrapper(ConsoleLogWrapper(LogLevel.TRACE)) + val forwarder = Forwarder() + forwarder.data.address = InetSocketAddress(44463) + forwarder.run() + ThreadUtilities.printActiveThreads() + } + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/intents/ClientIntents.kt b/src/main/kotlin/com/projectswg/forwarder/intents/ClientIntents.kt new file mode 100644 index 0000000..b345f9e --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/intents/ClientIntents.kt @@ -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() diff --git a/src/main/kotlin/com/projectswg/forwarder/intents/ControlIntents.kt b/src/main/kotlin/com/projectswg/forwarder/intents/ControlIntents.kt new file mode 100644 index 0000000..465ec50 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/intents/ControlIntents.kt @@ -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() diff --git a/src/main/kotlin/com/projectswg/forwarder/intents/ServerIntents.kt b/src/main/kotlin/com/projectswg/forwarder/intents/ServerIntents.kt new file mode 100644 index 0000000..28d9386 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/intents/ServerIntents.kt @@ -0,0 +1,7 @@ +package com.projectswg.forwarder.intents + +import me.joshlarson.jlcommon.control.Intent + +class RequestServerConnectionIntent: Intent() +class ServerConnectedIntent: Intent() +class ServerDisconnectedIntent: Intent() diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/ClientServer.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/ClientServer.kt new file mode 100644 index 0000000..4315a1d --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/ClientServer.kt @@ -0,0 +1,7 @@ +package com.projectswg.forwarder.resources.networking + +enum class ClientServer { + LOGIN, + ZONE, + PING +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/NetInterceptor.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/NetInterceptor.kt new file mode 100644 index 0000000..ebb5afe --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/NetInterceptor.kt @@ -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() + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.kt new file mode 100644 index 0000000..747323d --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/FragmentedProcessor.kt @@ -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 = 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 + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/Packager.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/Packager.kt new file mode 100644 index 0000000..a979d9e --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/Packager.kt @@ -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, private val outboundPackaged: ConnectionStream) { + + private val size: AtomicInteger = AtomicInteger(8) + private val dataChannel: MutableList = 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 + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/ProtocolStack.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/ProtocolStack.kt new file mode 100644 index 0000000..bd30838 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/ProtocolStack.kt @@ -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 = LinkedBlockingQueue() + private val inbound: ConnectionStream = ConnectionStream() + private val outbound: ConnectionStream = 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): Int { + return outbound.fillBuffer(buffer) + } + + override fun toString(): String { + return String.format("ProtocolStack[server=%s, source=%s, connectionId=%d]", server, source, connectionId) + } + + class ConnectionStream { + + private val sequenced: PriorityQueue = PriorityQueue() + private val queued: PriorityQueue = 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() + 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): 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 + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.kt new file mode 100644 index 0000000..c239b6d --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/data/SequencedOutbound.kt @@ -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() + } + +} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/encryption/Encryption.java b/src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/Encryption.java similarity index 100% rename from src/main/java/com/projectswg/forwarder/resources/networking/encryption/Encryption.java rename to src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/Encryption.java diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java b/src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java similarity index 98% rename from src/main/java/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java rename to src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java index 445397b..cbe9f63 100644 --- a/src/main/java/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/EncryptionCRC.java @@ -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 . * -* * -***********************************************************************************/ -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 . * +* * +***********************************************************************************/ +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[] { }; + } + +} diff --git a/src/main/java/com/projectswg/forwarder/resources/networking/encryption/MD5.java b/src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/MD5.java similarity index 100% rename from src/main/java/com/projectswg/forwarder/resources/networking/encryption/MD5.java rename to src/main/kotlin/com/projectswg/forwarder/resources/networking/encryption/MD5.java diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Acknowledge.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Acknowledge.kt new file mode 100644 index 0000000..cbb517e --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Acknowledge.kt @@ -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 //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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.kt new file mode 100644 index 0000000..b693095 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/ClientNetworkStatusUpdate.kt @@ -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 //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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/DataChannel.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/DataChannel.kt new file mode 100644 index 0000000..8becf3a --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/DataChannel.kt @@ -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 //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 + 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 + 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) { + 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) : 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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Disconnect.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Disconnect.kt new file mode 100644 index 0000000..783695f --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Disconnect.kt @@ -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 //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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Fragmented.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Fragmented.kt new file mode 100644 index 0000000..0a76bb2 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Fragmented.kt @@ -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 //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 { + var offset = 0 + val packetCount = ceil((data.size + 4) / 16377.0).toInt() // 489 + val packets = ArrayList(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() + } + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/KeepAlive.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/KeepAlive.kt new file mode 100644 index 0000000..0fb8c8a --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/KeepAlive.kt @@ -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[]" + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/MultiPacket.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/MultiPacket.kt new file mode 100644 index 0000000..711b28f --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/MultiPacket.kt @@ -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 //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 = 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 + get() = content + + constructor(data: NetBuffer) : this(ArrayList()) { + 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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.kt new file mode 100644 index 0000000..980c7b0 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/OutOfOrder.kt @@ -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 //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]" + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Packet.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Packet.kt new file mode 100644 index 0000000..66778e9 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/Packet.kt @@ -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 //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 + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/PingPacket.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/PingPacket.kt new file mode 100644 index 0000000..18289b0 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/PingPacket.kt @@ -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) + "]" + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.kt new file mode 100644 index 0000000..7716728 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/RawSWGPacket.kt @@ -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 //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) + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.kt new file mode 100644 index 0000000..f59d6b4 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SequencedPacket.kt @@ -0,0 +1,35 @@ +package com.projectswg.forwarder.resources.networking.packets + +import com.projectswg.common.network.NetBuffer + +interface SequencedPacket : Comparable { + + 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 + } + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.kt new file mode 100644 index 0000000..1c46a3f --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledge.kt @@ -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 + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.kt new file mode 100644 index 0000000..2ab3a94 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SeriousErrorAcknowledgeReply.kt @@ -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 + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.kt new file mode 100644 index 0000000..4a49a9f --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/ServerNetworkStatusUpdate.kt @@ -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 //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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SessionRequest.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SessionRequest.kt new file mode 100644 index 0000000..1fb6031 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SessionRequest.kt @@ -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 //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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SessionResponse.kt b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SessionResponse.kt new file mode 100644 index 0000000..888e63b --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/networking/packets/SessionResponse.kt @@ -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 //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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/resources/recording/PacketRecorder.kt b/src/main/kotlin/com/projectswg/forwarder/resources/recording/PacketRecorder.kt new file mode 100644 index 0000000..c6572eb --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/resources/recording/PacketRecorder.kt @@ -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() + 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 + + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/client/ClientConnectionManager.kt b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientConnectionManager.kt new file mode 100644 index 0000000..5293ec8 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientConnectionManager.kt @@ -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() diff --git a/src/main/kotlin/com/projectswg/forwarder/services/client/ClientInboundDataService.kt b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientInboundDataService.kt new file mode 100644 index 0000000..d27462e --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientInboundDataService.kt @@ -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)) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/client/ClientOutboundDataService.kt b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientOutboundDataService.kt new file mode 100644 index 0000000..a88b2e5 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientOutboundDataService.kt @@ -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 = arrayOfNulls(4096) + private val multiplexer: IntentMultiplexer = IntentMultiplexer(this, ProtocolStack::class.java, Packet::class.java) + private val activeStacks: MutableSet = 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]") + } + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/client/ClientProtocolService.kt b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientProtocolService.kt new file mode 100644 index 0000000..19f6a7e --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientProtocolService.kt @@ -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 + + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/client/ClientServerService.kt b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientServerService.kt new file mode 100644 index 0000000..c07884d --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/client/ClientServerService.kt @@ -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 = EnumMap(ClientServer::class.java) + private val serverConnection: AtomicBoolean = AtomicBoolean(false) + private val cachedSessionRequest: AtomicReference = AtomicReference(null) + private val lastPing: AtomicReference = AtomicReference(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 { this.onLoginPacket(it) }) + Log.t("Initializing zone udp server...") + zoneServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.zonePort), 16384, Consumer { this.onZonePacket(it) }) + Log.t("Initializing ping udp server...") + pingServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.pingPort), 16384, Consumer { 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 + } + } + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/crash/CrashManager.kt b/src/main/kotlin/com/projectswg/forwarder/services/crash/CrashManager.kt new file mode 100644 index 0000000..5648f7a --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/crash/CrashManager.kt @@ -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() diff --git a/src/main/kotlin/com/projectswg/forwarder/services/crash/IntentRecordingService.kt b/src/main/kotlin/com/projectswg/forwarder/services/crash/IntentRecordingService.kt new file mode 100644 index 0000000..1c5201e --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/crash/IntentRecordingService.kt @@ -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") + } + + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/crash/PacketRecordingService.kt b/src/main/kotlin/com/projectswg/forwarder/services/crash/PacketRecordingService.kt new file mode 100644 index 0000000..2d1bedd --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/crash/PacketRecordingService.kt @@ -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) + } + +} diff --git a/src/main/kotlin/com/projectswg/forwarder/services/server/ServerConnectionManager.kt b/src/main/kotlin/com/projectswg/forwarder/services/server/ServerConnectionManager.kt new file mode 100644 index 0000000..b6a0fb9 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/server/ServerConnectionManager.kt @@ -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() diff --git a/src/main/kotlin/com/projectswg/forwarder/services/server/ServerConnectionService.kt b/src/main/kotlin/com/projectswg/forwarder/services/server/ServerConnectionService.kt new file mode 100644 index 0000000..3ca4cc2 --- /dev/null +++ b/src/main/kotlin/com/projectswg/forwarder/services/server/ServerConnectionService.kt @@ -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 + + } + +}