commit 7f4330e534c2ef12e993e73d1396c58fcbd7eda6 Author: Obique Date: Mon Aug 14 00:34:27 2017 -0500 Initial Commit diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..2ebdd55 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6d2bb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle/ +.settings/ +bin/ +build/ +doc/ diff --git a/.project b/.project new file mode 100644 index 0000000..d3fd460 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + Client Holocore + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/ClientHolocore.jar b/ClientHolocore.jar new file mode 100644 index 0000000..2304986 Binary files /dev/null and b/ClientHolocore.jar differ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..0772d1e --- /dev/null +++ b/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'java' + +sourceSets { + main { + java { + srcDirs = ['src'] + } + resources { + srcDirs = ['src'] + } + } +} + +dependencies { + compile fileTree(dir: 'lib', include: ['*.jar']) +} + +jar { + from sourceSets.main.allSource + archiveName = "ClientHolocore.jar" +} diff --git a/lib/lz4-1.3.0.jar b/lib/lz4-1.3.0.jar new file mode 100644 index 0000000..aaf641c Binary files /dev/null and b/lib/lz4-1.3.0.jar differ diff --git a/src/com/projectswg/connection/HolocoreProtocol.java b/src/com/projectswg/connection/HolocoreProtocol.java new file mode 100644 index 0000000..de3ae84 --- /dev/null +++ b/src/com/projectswg/connection/HolocoreProtocol.java @@ -0,0 +1,92 @@ +package com.projectswg.connection; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.projectswg.connection.common.Compression; +import com.projectswg.connection.common.NetBufferStream; + +class HolocoreProtocol { + + private static final byte [] EMPTY_PACKET = new byte[0]; + + private final NetBufferStream inboundStream; + + public HolocoreProtocol() { + this.inboundStream = new NetBufferStream(); + } + + public void reset() { + inboundStream.reset(); + } + + public ByteBuffer assemble(byte [] raw) { + int decompressedLength = raw.length; + boolean compressed = raw.length >= 16; + if (compressed) { + byte [] compressedData = Compression.compress(raw); + if (compressedData.length >= raw.length) + compressed = false; + else + raw = compressedData; + } + ByteBuffer data = ByteBuffer.allocate(raw.length + 5).order(ByteOrder.LITTLE_ENDIAN); + data.put(createBitmask(compressed, true)); + data.putShort((short) raw.length); + data.putShort((short) decompressedLength); + data.put(raw); + data.flip(); + return data; + } + + public boolean addToBuffer(ByteBuffer data) { + synchronized (inboundStream) { + inboundStream.write(data); + inboundStream.mark(); + try { + if (inboundStream.remaining() < 5) + return false; + inboundStream.getByte(); + short messageLength = inboundStream.getShort(); + inboundStream.getShort(); + if (inboundStream.remaining() < messageLength) { + inboundStream.rewind(); + return false; + } + return true; + } finally { + inboundStream.rewind(); + } + } + } + + public byte [] disassemble() { + synchronized (inboundStream) { + inboundStream.mark(); + if (inboundStream.remaining() < 5) { + inboundStream.rewind(); + return EMPTY_PACKET; + } + byte bitmask = inboundStream.getByte(); + short messageLength = inboundStream.getShort(); + short decompressedLength = inboundStream.getShort(); + if (inboundStream.remaining() < messageLength) { + inboundStream.rewind(); + return EMPTY_PACKET; + } + byte [] message = inboundStream.getArray(messageLength); + if ((bitmask & 1) != 0) // Compressed + message = Compression.decompress(message, decompressedLength); + inboundStream.compact(); + return message; + } + } + + private byte createBitmask(boolean compressed, boolean swg) { + byte bitfield = 0; + bitfield |= (compressed?1:0) << 0; + bitfield |= (swg?1:0) << 1; + return bitfield; + } + +} diff --git a/src/com/projectswg/connection/HolocoreSocket.java b/src/com/projectswg/connection/HolocoreSocket.java new file mode 100644 index 0000000..b4ec928 --- /dev/null +++ b/src/com/projectswg/connection/HolocoreSocket.java @@ -0,0 +1,335 @@ +package com.projectswg.connection; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.SocketChannel; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicReference; + +import com.projectswg.connection.UDPServer.UDPPacket; +import com.projectswg.connection.common.NetBuffer; +import com.projectswg.connection.packets.RawPacket; +import com.projectswg.connection.packets.holo.HoloConnectionStarted; +import com.projectswg.connection.packets.holo.HoloConnectionStopped; +import com.projectswg.connection.packets.holo.HoloConnectionStopped.ConnectionStoppedReason; +import com.projectswg.connection.packets.holo.HoloSetProtocolVersion; + +public class HolocoreSocket { + + private static final String PROTOCOL = "2016-04-13"; + + private final Object socketMutex; + private final ByteBuffer buffer; + private final SWGProtocol swgProtocol; + private final AtomicReference status; + private final UDPServer udpServer; + private SocketChannel socket; + private StatusChangedCallback callback; + private InetAddress addr; + private int port; + + public HolocoreSocket(InetAddress addr, int port) { + this.socketMutex = new Object(); + this.buffer = ByteBuffer.allocateDirect(128*1024); + this.swgProtocol = new SWGProtocol(); + this.socket = null; + this.status = new AtomicReference<>(ServerConnectionStatus.DISCONNECTED); + UDPServer udpServer = null; + try { + udpServer = new UDPServer(0); + } catch (SocketException e) { + e.printStackTrace(); + } + this.udpServer = udpServer; + this.callback = null; + this.addr = addr; + this.port = port; + } + + /** + * Shuts down any miscellaneous resources--such as the query UDP server + */ + public void terminate() { + if (udpServer != null) + udpServer.close(); + } + + /** + * Sets a callback for when the status of the server socket changes + * @param callback the callback + */ + public void setStatusChangedCallback(StatusChangedCallback callback) { + this.callback = callback; + } + + /** + * Sets the remote address this socket will attempt to connect to + * @param addr the destination address + * @param port the destination port + */ + public void setRemoteAddress(InetAddress addr, int port) { + this.addr = addr; + this.port = port; + } + + /** + * Returns the remote address this socket is pointing to + * @return the remote address as an InetSocketAddress + */ + public InetSocketAddress getRemoteAddress() { + return new InetSocketAddress(addr, port); + } + + /** + * Gets the current connection state of the socket + * @return the connection state + */ + public ServerConnectionStatus getConnectionState() { + return status.get(); + } + + /** + * Returns whether or not this socket is disconnected + * @return TRUE if disconnected, FALSE otherwise + */ + public boolean isDisconnected() { + return status.get() == ServerConnectionStatus.DISCONNECTED; + } + + /** + * Returns whether or not this socket is connecting + * @return TRUE if connecting, FALSE otherwise + */ + public boolean isConnecting() { + return status.get() == ServerConnectionStatus.CONNECTING; + } + + /** + * Returns whether or not this socket is connected + * @return TRUE if connected, FALSE otherwise + */ + public boolean isConnected() { + return status.get() == ServerConnectionStatus.CONNECTED; + } + + /** + * Retrieves the server status via a UDP query, with the default timeout of 2000ms + * @return the server status as a string + */ + public String getServerStatus() { + return getServerStatus(2000); + } + + /** + * Retrives the server status via a UDP query, with the specified timeout + * @param timeout the timeout in milliseconds + * @return the server status as a string + */ + public String getServerStatus(long timeout) { + udpServer.send(port, addr, new byte[]{1}); + udpServer.waitForPacket(timeout); + UDPPacket packet = udpServer.receive(); + if (packet == null) + return "OFFLINE"; + NetBuffer data = NetBuffer.wrap(packet.getData()); + data.getByte(); + return data.getAscii(); + } + + /** + * Attempts to connect to the remote server. This call is a blocking function that will not + * return until it has either successfully connected or has failed. It starts by initializing a + * TCP connection, then initializes the Holocore connection, then returns. + * @param timeout the timeout for the connect call + * @return TRUE if successful and connected, FALSE on error + */ + public boolean connect(int timeout) { + synchronized (socketMutex) { + if (!isDisconnected()) + throw new IllegalStateException("Socket must be disconnected when attempting to connect!"); + try { + swgProtocol.reset(); + socket = SocketChannel.open(); + updateStatus(ServerConnectionStatus.CONNECTING, ServerConnectionChangedReason.NONE); + socket.socket().setKeepAlive(true); + socket.socket().setPerformancePreferences(0, 1, 2); + socket.socket().setTrafficClass(0x10); // Low Delay bit + socket.configureBlocking(true); + socket.connect(new InetSocketAddress(addr, port)); + if (!socket.finishConnect()) + return false; + waitForConnect(timeout); + return true; + } catch (IOException e) { + if (e instanceof AsynchronousCloseException) { + disconnect(ServerConnectionChangedReason.SOCKET_CLOSED); + } else if (e instanceof SocketTimeoutException) { + disconnect(ServerConnectionChangedReason.CONNECT_TIMEOUT); + } else if (e.getMessage() == null) { + disconnect(ServerConnectionChangedReason.UNKNOWN); + } else { + disconnect(getReason(e.getMessage())); + } + return false; + } + } + } + + /** + * Attempts to disconnect from the server with the specified reason. Before this socket is + * closed, it will send a HoloConnectionStopped packet to notify the remote server. + * @param reason the reason for disconnecting + * @return TRUE if successfully disconnected, FALSE on error + */ + public boolean disconnect(ServerConnectionChangedReason reason) { + synchronized (socketMutex) { + if (isDisconnected()) + return true; + if (socket == null) + throw new NullPointerException("Socket cannot be null when disconnecting"); + updateStatus(ServerConnectionStatus.DISCONNECTED, reason); + try { + if (socket.isOpen()) + socket.write(swgProtocol.assemble(new HoloConnectionStopped(ConnectionStoppedReason.APPLICATION).encode().array())); + socket.close(); + socket = null; + return true; + } catch (IOException e) { + return false; + } + } + } + + /** + * Attempts to send a byte array to the remote server. This method blocks until it has + * completely sent or has failed. + * @param raw the byte array to send + * @return TRUE on success, FALSE on failure + */ + public boolean send(byte [] raw) { + return sendRaw(swgProtocol.assemble(raw)); + } + + /** + * Attempts to receive a packet from the remote server. This method blocks until a packet is + * recieved or has failed. + * @return the RawPacket containing the CRC of the SWG message and the raw data array, or NULL + * on error + */ + public RawPacket receive() { + RawPacket packet = null; + do { + packet = swgProtocol.disassemble(); + if (packet != null) + return packet; + readRaw(buffer); + swgProtocol.addToBuffer(buffer); + } while (!isDisconnected()); + return null; + } + + private boolean sendRaw(ByteBuffer data) { + if (isDisconnected()) + return false; + try { + synchronized (socketMutex) { + while (data.hasRemaining()) + socket.write(data); + } + return !data.hasRemaining(); + } catch (IOException e) { + disconnect(ServerConnectionChangedReason.OTHER_SIDE_TERMINATED); + } + return false; + } + + private boolean readRaw(ByteBuffer data) { + try { + data.position(0); + data.limit(data.capacity()); + int n = socket.read(data); + if (n < 0) { + disconnect(ServerConnectionChangedReason.OTHER_SIDE_TERMINATED); + } else { + data.flip(); + return true; + } + } catch (Exception e) { + if (e instanceof AsynchronousCloseException) { + disconnect(ServerConnectionChangedReason.SOCKET_CLOSED); + } else if (e.getMessage() != null) { + disconnect(getReason(e.getMessage())); + } else { + disconnect(ServerConnectionChangedReason.UNKNOWN); + } + } + return false; + } + + private void waitForConnect(int timeout) throws SocketException { + send(new HoloSetProtocolVersion(PROTOCOL).encode().array()); + socket.socket().setSoTimeout(timeout); + try { + while (isConnecting()) { + RawPacket packet = receive(); + if (packet == null) + continue; + handlePacket(packet.getCrc(), packet.getData()); + } + if (isConnected()) + send(new HoloConnectionStarted().encode().array()); + } finally { + socket.socket().setSoTimeout(0); + } + } + + private void handlePacket(int crc, byte [] raw) { + if (crc == HoloConnectionStarted.CRC) { + updateStatus(ServerConnectionStatus.CONNECTED, ServerConnectionChangedReason.NONE); + } else if (crc == HoloConnectionStopped.CRC) { + HoloConnectionStopped packet = new HoloConnectionStopped(); + packet.decode(ByteBuffer.wrap(raw)); + switch (packet.getReason()) { + case INVALID_PROTOCOL: + disconnect(ServerConnectionChangedReason.INVALID_PROTOCOL); + break; + default: + disconnect(ServerConnectionChangedReason.NONE); + break; + } + } + } + + private void updateStatus(ServerConnectionStatus status, ServerConnectionChangedReason reason) { + ServerConnectionStatus old = this.status.getAndSet(status); + if (old != status && callback != null) + callback.onConnectionStatusChanged(old, status, reason); + } + + private ServerConnectionChangedReason getReason(String message) { + message = message.toLowerCase(Locale.US); + if (message.contains("broken pipe")) + return ServerConnectionChangedReason.BROKEN_PIPE; + if (message.contains("connection reset")) + return ServerConnectionChangedReason.CONNECTION_RESET; + if (message.contains("connection refused")) + return ServerConnectionChangedReason.CONNECTION_REFUSED; + if (message.contains("address in use")) + return ServerConnectionChangedReason.ADDR_IN_USE; + if (message.contains("socket closed")) + return ServerConnectionChangedReason.SOCKET_CLOSED; + if (message.contains("no route to host")) + return ServerConnectionChangedReason.NO_ROUTE_TO_HOST; + return ServerConnectionChangedReason.UNKNOWN; + } + + public interface StatusChangedCallback { + void onConnectionStatusChanged(ServerConnectionStatus oldStatus, ServerConnectionStatus newStatus, ServerConnectionChangedReason reason); + } + +} diff --git a/src/com/projectswg/connection/SWGProtocol.java b/src/com/projectswg/connection/SWGProtocol.java new file mode 100644 index 0000000..0078951 --- /dev/null +++ b/src/com/projectswg/connection/SWGProtocol.java @@ -0,0 +1,36 @@ +package com.projectswg.connection; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.projectswg.connection.packets.RawPacket; + +class SWGProtocol { + + private final HolocoreProtocol holocore; + + public SWGProtocol() { + holocore = new HolocoreProtocol(); + } + + public void reset() { + holocore.reset(); + } + + public ByteBuffer assemble(byte [] packet) { + return holocore.assemble(packet); + } + + public boolean addToBuffer(ByteBuffer network) { + return holocore.addToBuffer(network); + } + + public RawPacket disassemble() { + byte [] packet = holocore.disassemble(); + if (packet.length < 6) + return null; + ByteBuffer data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN); + return new RawPacket(data.getInt(2), packet); + } + +} diff --git a/src/com/projectswg/connection/ServerConnectionChangedReason.java b/src/com/projectswg/connection/ServerConnectionChangedReason.java new file mode 100644 index 0000000..9633b19 --- /dev/null +++ b/src/com/projectswg/connection/ServerConnectionChangedReason.java @@ -0,0 +1,16 @@ +package com.projectswg.connection; + +public enum ServerConnectionChangedReason { + NONE, + CLIENT_DISCONNECT, + SOCKET_CLOSED, + CONNECT_TIMEOUT, + INVALID_PROTOCOL, + BROKEN_PIPE, + CONNECTION_RESET, + CONNECTION_REFUSED, + ADDR_IN_USE, + NO_ROUTE_TO_HOST, + OTHER_SIDE_TERMINATED, + UNKNOWN +} diff --git a/src/com/projectswg/connection/ServerConnectionStatus.java b/src/com/projectswg/connection/ServerConnectionStatus.java new file mode 100644 index 0000000..bf7377b --- /dev/null +++ b/src/com/projectswg/connection/ServerConnectionStatus.java @@ -0,0 +1,7 @@ +package com.projectswg.connection; + +public enum ServerConnectionStatus { + CONNECTING, + CONNECTED, + DISCONNECTED +} diff --git a/src/com/projectswg/connection/UDPServer.java b/src/com/projectswg/connection/UDPServer.java new file mode 100644 index 0000000..2ec06d9 --- /dev/null +++ b/src/com/projectswg/connection/UDPServer.java @@ -0,0 +1,277 @@ +/*********************************************************************************** +* 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.connection; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * This class represents a UDP server that listens for packets and + * will call the callback when it receives one + */ +class UDPServer { + + private Object waitingForPacket = new Object(); + + private final DatagramSocket socket; + private final UDPUpdater updater; + private final Queue inbound; + private final int packetSize; + private UDPCallback callback; + private int port; + + public UDPServer(int port) throws SocketException { + this(port, 1024); + } + + public UDPServer(int port, int packetSize) throws SocketException { + this(null, port, packetSize); + } + + public UDPServer(InetAddress bindAddr, int port, int packetSize) throws SocketException { + this.callback = null; + this.packetSize = packetSize; + inbound = new LinkedBlockingQueue(); + if (port > 0) { + if (bindAddr == null) + socket = new DatagramSocket(port); + else + socket = new DatagramSocket(port, bindAddr); + } else + socket = new DatagramSocket(); + this.port = socket.getLocalPort(); + updater = new UDPUpdater(); + updater.start(); + } + + public void close() { + if (updater != null) + updater.stop(); + if (socket != null) + socket.close(); + } + + public UDPPacket receive() { + return inbound.poll(); + } + + public int packetCount() { + return inbound.size(); + } + + public int getPort() { + return port; + } + + public boolean isRunning() { + return updater != null && updater.isRunning(); + } + + public void waitForPacket() { + synchronized (waitingForPacket) { + try { + while (inbound.isEmpty()) { + waitingForPacket.wait(); + } + } catch (InterruptedException e) { + + } + } + } + + public boolean waitForPacket(long timeout) { + long start = System.nanoTime(); + synchronized (waitingForPacket) { + try { + while (inbound.isEmpty()) { + long waitTime = (long) (timeout - (System.nanoTime() - start)/1E6 + 0.5); + if (waitTime <= 0) + return false; + waitingForPacket.wait(waitTime); + } + return true; + } catch (InterruptedException e) { + + } + } + return false; + } + + public boolean send(int port, InetAddress addr, byte [] data) { + try { + socket.send(new DatagramPacket(data, data.length, addr, port)); + return true; + } catch (IOException e) { + String msg = e.getMessage(); + if (msg != null && msg.startsWith("Socket") && msg.endsWith("closed")) + return false; + else + e.printStackTrace(); + return false; + } + } + + public boolean send(int port, String addr, byte [] data) { + try { + return send(port, InetAddress.getByName(addr), data); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + return false; + } + + public boolean send(InetSocketAddress addr, byte [] data) { + return send(addr.getPort(), addr.getAddress(), data); + } + + public void setCallback(UDPCallback callback) { + this.callback = callback; + } + + public void removeCallback() { + callback = null; + } + + public interface UDPCallback { + public void onReceivedPacket(UDPPacket packet); + } + + public static class UDPPacket { + private final byte [] data; + private final InetAddress addr; + private final int port; + + public UDPPacket(InetAddress addr, int port, byte [] data) { + this.data = data; + this.addr = addr; + this.port = port; + } + + public InetAddress getAddress() { + return addr; + } + + public int getPort() { + return port; + } + + public byte [] getData() { + return data; + } + + public int getLength() { + return data.length; + } + } + + private class UDPUpdater implements Runnable { + + private final Thread thread; + private final byte [] dataBuffer; + private boolean running; + + public UDPUpdater() { + thread = new Thread(this); + thread.setName("UDPServer Port#" + port); + dataBuffer = new byte[packetSize]; + } + + public boolean isRunning() { + return running; + } + + public void start() { + running = true; + thread.start(); + } + + public void stop() { + running = false; + thread.interrupt(); + } + + public void run() { + try { + while (running) { + loop(); + } + } catch (Exception e) { + e.printStackTrace(); + } + running = false; + } + + private void loop() { + DatagramPacket packet = receivePacket(); + if (packet.getLength() <= 0) + return; + UDPPacket udpPacket = generatePacket(packet); + if (callback != null) + callback.onReceivedPacket(udpPacket); + else + inbound.add(udpPacket); + notifyPacketReceived(); + } + + private void notifyPacketReceived() { + synchronized (waitingForPacket) { + waitingForPacket.notifyAll(); + } + } + + private DatagramPacket receivePacket() { + DatagramPacket packet = new DatagramPacket(dataBuffer, dataBuffer.length); + try { + socket.receive(packet); + } catch (IOException e) { + if (e.getMessage() != null && (e.getMessage().contains("socket closed") || e.getMessage().contains("Socket closed"))) + running = false; + else + e.printStackTrace(); + packet.setLength(0); + } + return packet; + } + + private UDPPacket generatePacket(DatagramPacket packet) { + byte [] data = new byte[packet.getLength()]; + System.arraycopy(packet.getData(), 0, data, 0, packet.getLength()); + UDPPacket udpPacket = new UDPPacket(packet.getAddress(), packet.getPort(), data); + return udpPacket; + } + + } + +} diff --git a/src/com/projectswg/connection/common/CRC.java b/src/com/projectswg/connection/common/CRC.java new file mode 100644 index 0000000..3ba8d83 --- /dev/null +++ b/src/com/projectswg/connection/common/CRC.java @@ -0,0 +1,181 @@ +/*********************************************************************************** +* 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.connection.common; + +import java.nio.charset.StandardCharsets; + +public class CRC { + + private static final int CRC_TABLE[] = { + 0x0000000, + 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, + 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, + 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, + 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, + 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, + 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, + 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, + 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, + 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, + 0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE, + 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, + 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, + 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, + 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, + 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, + 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, + 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, + 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, + 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, + 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, + 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, + 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, + 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, + 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F, + 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, + 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, + 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, + 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, + 0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629, + 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, + 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, + 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, + 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, + 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, + 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, + 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, + 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, + 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, + 0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, + 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21, + 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, + 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087, + 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, + 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, + 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, + 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, + 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, + 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09, + 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, + 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, + 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4, + }; + + private static final int[] CRC_ZIP_TABLE = { + 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 int getCrc(String input) { + byte [] bytes = input.getBytes(StandardCharsets.UTF_8); + int crc = 0xffffffff; + + for (int i = 0; i < bytes.length; ++i) { + crc = CRC_TABLE[(int) (bytes[i] ^ (crc >> 24)) & 0x000000FF] ^ (crc << 8); + } + return (int) ~crc; + } + + public static int memcrc(String input) { + byte [] bytes = input.getBytes(StandardCharsets.UTF_8); + + int crc = 0xffffffff; + for (int i = 0; i < bytes.length; ++i) { + crc = CRC_TABLE[(int) (bytes[i] ^ (crc >> 24)) & 0x000000FF] ^ (crc << 8); + } + + return (int) ~crc; + } + + public static int memcrc(byte [] src_buffer, int offset, int length, int seed) { + int index; + int newCRC = 0; + + newCRC = CRC_ZIP_TABLE[(~seed) & 0xFF]; + newCRC ^= 0x00FFFFFF; + index = (seed >> 8) ^ newCRC; + newCRC = (newCRC >> 8) & 0x00FFFFFF; + newCRC ^= CRC_ZIP_TABLE[index & 0xFF]; + index = (seed >> 16) ^ newCRC; + newCRC = (newCRC >> 8) & 0x00FFFFFF; + newCRC ^= CRC_ZIP_TABLE[index & 0xFF]; + index = (seed >> 24) ^ newCRC; + newCRC = (newCRC >> 8) & 0x00FFFFFF; + newCRC ^= CRC_ZIP_TABLE[index & 0xFF]; + + for (int i = offset; i < length; i++) { + index = (src_buffer[i]) ^ newCRC; + newCRC = (newCRC >> 8) & 0x00FFFFFF; + newCRC ^= CRC_ZIP_TABLE[index & 0xFF]; + } + + return ~newCRC; + } + +} diff --git a/src/com/projectswg/connection/common/Compression.java b/src/com/projectswg/connection/common/Compression.java new file mode 100644 index 0000000..3a4c1b8 --- /dev/null +++ b/src/com/projectswg/connection/common/Compression.java @@ -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 . * + * * + ***********************************************************************************/ +package com.projectswg.connection.common; + +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4SafeDecompressor; + +public class Compression { + + private static final LZ4Compressor COMPRESSOR = LZ4Factory.safeInstance().highCompressor(); + private static final LZ4SafeDecompressor DECOMPRESSOR = LZ4Factory.safeInstance().safeDecompressor(); + + public static byte [] compress(byte [] data) { + int maxCompressedLength = COMPRESSOR.maxCompressedLength(data.length); + byte[] compressed = new byte[maxCompressedLength]; + int length = COMPRESSOR.compress(data, compressed); + byte [] ret = new byte[length]; + System.arraycopy(compressed, 0, ret, 0, length); + return ret; + } + + public static byte [] decompress(byte [] data) { + return decompress(data, data.length * 10); + } + + public static byte [] decompress(byte [] data, int bufferSize) { + byte [] restored = new byte[bufferSize]; + int length = DECOMPRESSOR.decompress(data, restored); + if (length == bufferSize) + return restored; + byte [] ret = new byte[length]; + System.arraycopy(restored, 0, ret, 0, length); + return ret; + } + +} diff --git a/src/com/projectswg/connection/common/NetBuffer.java b/src/com/projectswg/connection/common/NetBuffer.java new file mode 100644 index 0000000..1dd0322 --- /dev/null +++ b/src/com/projectswg/connection/common/NetBuffer.java @@ -0,0 +1,237 @@ +/************************************************************************************ + * 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.connection.common; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + + +public class NetBuffer { + + public static final Charset ASCII = Charset.forName("UTF-8"); + public static final Charset UNICODE = Charset.forName("UTF-16LE"); + + private final ByteBuffer data; + private final int size; + + private NetBuffer(ByteBuffer data) { + this.data = data; + this.size = data.array().length; + } + + public static final NetBuffer allocate(int size) { + return new NetBuffer(ByteBuffer.allocate(size)); + } + + public static final NetBuffer wrap(byte [] data) { + return new NetBuffer(ByteBuffer.wrap(data)); + } + + public static final NetBuffer wrap(ByteBuffer data) { + return new NetBuffer(data); + } + + public int remaining() { + return data.remaining(); + } + + public int position() { + return data.position(); + } + + public void position(int position) { + data.position(position); + } + + public void seek(int relative) { + data.position(data.position()+relative); + } + + public ByteBuffer getBuffer() { + return data; + } + + public void addBoolean(boolean b) { + data.put(b ? (byte)1 : (byte)0); + } + + public void addAscii(String s) { + data.order(ByteOrder.LITTLE_ENDIAN); + data.putShort((short)s.length()); + data.put(s.getBytes(ASCII)); + } + + public void addAscii(char [] s) { + data.order(ByteOrder.LITTLE_ENDIAN); + data.putShort((short)s.length); + ByteBuffer bb = ASCII.encode(CharBuffer.wrap(s)); + byte [] bData = new byte[bb.limit()]; + bb.get(bData); + data.put(bData); + } + + public void addUnicode(String s) { + data.order(ByteOrder.LITTLE_ENDIAN); + data.putInt(s.length()); + data.put(s.getBytes(UNICODE)); + } + + public void addLong(long l) { + data.order(ByteOrder.LITTLE_ENDIAN).putLong(l); + } + + public void addInt(int i) { + data.order(ByteOrder.LITTLE_ENDIAN).putInt(i); + } + + public void addFloat(float f) { + data.putFloat(f); + } + + public void addShort(int i) { + data.order(ByteOrder.LITTLE_ENDIAN).putShort((short)i); + } + + public void addNetLong(long l) { + data.order(ByteOrder.BIG_ENDIAN).putLong(l); + } + + public void addNetInt(int i) { + data.order(ByteOrder.BIG_ENDIAN).putInt(i); + } + + public void addNetShort(int i) { + data.order(ByteOrder.BIG_ENDIAN).putShort((short)i); + } + + public void addByte(int b) { + data.put((byte)b); + } + + public void addArray(byte [] b) { + addShort(b.length); + data.put(b); + } + + public void addRawArray(byte [] b) { + data.put(b); + } + + public boolean getBoolean() { + return getByte() == 1 ? true : false; + } + + public String getAscii() { + data.order(ByteOrder.LITTLE_ENDIAN); + short length = data.getShort(); + if (length > data.remaining()) + return ""; + byte [] str = new byte[length]; + data.get(str); + return new String(str, ASCII); + } + + public String getUnicode() { + data.order(ByteOrder.LITTLE_ENDIAN); + int length = data.getInt() * 2; + if (length > data.remaining()) + return ""; + byte [] str = new byte[length]; + data.get(str); + return new String(str, UNICODE); + } + + public byte getByte() { + return data.get(); + } + + public short getShort() { + return data.order(ByteOrder.LITTLE_ENDIAN).getShort(); + } + + public int getInt() { + return data.order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + public float getFloat() { + return data.getFloat(); + } + + public long getLong() { + return data.order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + + public short getNetShort() { + return data.order(ByteOrder.BIG_ENDIAN).getShort(); + } + + public int getNetInt() { + return data.order(ByteOrder.BIG_ENDIAN).getInt(); + } + + public long getNetLong() { + return data.order(ByteOrder.BIG_ENDIAN).getLong(); + } + + public byte [] getArray() { + byte [] bData = new byte[getShort()]; + data.get(bData); + return bData; + } + + public byte [] getArray(int size) { + byte [] bData = new byte[size]; + data.get(bData); + return bData; + } + + public byte [] array() { + return data.array(); + } + + public int size() { + return size; + } + + public byte [] copyArray() { + return copyArray(0, size); + } + + public byte [] copyArray(int offset, int length) { + if (length < 0) + throw new IllegalArgumentException("Length cannot be less than 0!"); + if (offset+length > size) + throw new IllegalArgumentException("Length extends past the end of the array!"); + byte [] ret = new byte[length]; + System.arraycopy(array(), offset, ret, 0, length); + return ret; + } + +} \ No newline at end of file diff --git a/src/com/projectswg/connection/common/NetBufferStream.java b/src/com/projectswg/connection/common/NetBufferStream.java new file mode 100644 index 0000000..b3b400a --- /dev/null +++ b/src/com/projectswg/connection/common/NetBufferStream.java @@ -0,0 +1,435 @@ +/************************************************************************************ + * 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.connection.common; + +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class NetBufferStream extends OutputStream { + + private final Object expansionMutex; + private final Object bufferMutex; + private NetBuffer buffer; + private int capacity; + private int size; + private int mark; + + public NetBufferStream() { + this(1024); + } + + public NetBufferStream(int size) { + if (size <= 0) + throw new NegativeArraySizeException("Size cannot be less than or equal to 0!"); + this.expansionMutex = new Object(); + this.bufferMutex = new Object(); + this.buffer = NetBuffer.allocate(size); + this.capacity = size; + this.size = 0; + this.mark = 0; + } + + @Override + public void close() { + reset(); + } + + @Override + public void flush() { + + } + + /** + * Sets the mark to the buffer's current position + */ + public void mark() { + synchronized (bufferMutex) { + mark = buffer.position(); + } + } + + /** + * Rewinds the buffer to the previously set mark + */ + public void rewind() { + synchronized (bufferMutex) { + buffer.position(mark); + mark = 0; + } + } + + /** + * Resets the buffer to the default capacity and clears all data + */ + public void reset() { + synchronized (expansionMutex) { + synchronized (bufferMutex) { + buffer = NetBuffer.allocate(1024); + capacity = 1024; + size = 0; + mark = 0; + } + } + } + + @Override + public void write(int b) { + ensureCapacity(size + 1); + synchronized (bufferMutex) { + buffer.array()[size] = (byte) b; + size++; + } + } + + public void write(byte [] data) { + write(data, 0, data.length); + } + + public void write(byte [] data, int offset, int length) { + ensureCapacity(size + length); + synchronized (bufferMutex) { + System.arraycopy(data, offset, buffer.array(), size, length); + size += length; + } + } + + public void write(ByteBuffer data) { + ensureCapacity(size + data.remaining()); + synchronized (bufferMutex) { + while (data.hasRemaining()) { + buffer.array()[size++] = data.get(); + } + } + } + + /** + * Moves all data from the buffer's current position to position 0. This + * method also adjusts the mark to be pointing to the same data + */ + public void compact() { + synchronized (bufferMutex) { + byte [] data = buffer.array(); + for (int i = buffer.position(), j = 0; i < size; ++i, ++j) { + data[j] = data[i]; + } + size -= buffer.position(); + mark -= buffer.position(); + buffer.position(0); + } + } + + public int remaining() { + synchronized (bufferMutex) { + return size - buffer.position(); + } + } + + public boolean hasRemaining() { + return remaining() > 0; + } + + public int position() { + synchronized (bufferMutex) { + return buffer.position(); + } + } + + public void position(int position) { + synchronized (bufferMutex) { + buffer.position(position); + } + } + + public void seek(int relative) { + synchronized (bufferMutex) { + buffer.seek(relative); + } + } + + public ByteBuffer getBuffer() { + synchronized (bufferMutex) { + return buffer.getBuffer(); + } + } + + public boolean getBoolean() { + synchronized (bufferMutex) { + return buffer.getBoolean(); + } + } + + public String getAscii() { + synchronized (bufferMutex) { + return buffer.getAscii(); + } + } + + public String getUnicode() { + synchronized (bufferMutex) { + return buffer.getUnicode(); + } + } + + public byte getByte() { + synchronized (bufferMutex) { + return buffer.getByte(); + } + } + + public short getShort() { + synchronized (bufferMutex) { + return buffer.getShort(); + } + } + + public int getInt() { + synchronized (bufferMutex) { + return buffer.getInt(); + } + } + + public float getFloat() { + synchronized (bufferMutex) { + return buffer.getFloat(); + } + } + + public long getLong() { + synchronized (bufferMutex) { + return buffer.getLong(); + } + } + + public short getNetShort() { + synchronized (bufferMutex) { + return buffer.getNetShort(); + } + } + + public int getNetInt() { + synchronized (bufferMutex) { + return buffer.getNetInt(); + } + } + + public long getNetLong() { + synchronized (bufferMutex) { + return buffer.getNetLong(); + } + } + + public byte[] getArray() { + synchronized (bufferMutex) { + return buffer.getArray(); + } + } + + public byte[] getArray(int size) { + synchronized (bufferMutex) { + return buffer.getArray(size); + } + } + + public void getList(NumericalIterator ni) { + int size = getInt(); + for (int i = 0; i < size; i++) + ni.onItem(i); + } + + public void addBoolean(boolean b) { + ensureCapacity(size+1); + synchronized (bufferMutex) { + buffer.addBoolean(b); + size++; + } + } + + public void addAscii(String s) { + ensureCapacity(size+2+s.length()); + synchronized (bufferMutex) { + buffer.addAscii(s); + size += 2 + s.length(); + } + } + + public void addAscii(char[] s) { + ensureCapacity(size+2+s.length); + synchronized (bufferMutex) { + buffer.addAscii(s); + size += 2 + s.length; + } + } + + public void addUnicode(String s) { + ensureCapacity(size+4+s.length()*2); + synchronized (bufferMutex) { + buffer.addUnicode(s); + size += 4 + s.length()*2; + } + } + + public void addLong(long l) { + ensureCapacity(size+8); + synchronized (bufferMutex) { + buffer.addLong(l); + size += 8; + } + } + + public void addInt(int i) { + ensureCapacity(size+4); + synchronized (bufferMutex) { + buffer.addInt(i); + size += 4; + } + } + + public void addFloat(float f) { + ensureCapacity(size+4); + synchronized (bufferMutex) { + buffer.addFloat(f); + size += 4; + } + } + + public void addShort(int i) { + ensureCapacity(size+2); + synchronized (bufferMutex) { + buffer.addShort(i); + size += 2; + } + } + + public void addNetLong(long l) { + ensureCapacity(size+8); + synchronized (bufferMutex) { + buffer.addNetLong(l); + size += 8; + } + } + + public void addNetInt(int i) { + ensureCapacity(size+4); + synchronized (bufferMutex) { + buffer.addNetInt(i); + size += 4; + } + } + + public void addNetShort(int i) { + ensureCapacity(size+2); + synchronized (bufferMutex) { + buffer.addNetShort(i); + size += 2; + } + } + + public void addByte(int b) { + ensureCapacity(size+1); + synchronized (bufferMutex) { + buffer.addByte(b); + size++; + } + } + + public void addArray(byte[] b) { + ensureCapacity(size+2+b.length); + synchronized (bufferMutex) { + buffer.addArray(b); + size += 2 + b.length; + } + } + + public void addList(List l, ListIterable r) { + addInt(l.size()); + for (T t : l) + r.onItem(t); + } + + public void addList(Set s, ListIterable r) { + addInt(s.size()); + for (T t : s) + r.onItem(t); + } + + public void addMap(Map m, MapIterable r) { + addInt(m.size()); + for (Entry e : m.entrySet()) + r.onItem(e); + } + + public void addRawArray(byte[] b) { + write(b); + } + + public byte [] array() { + synchronized (bufferMutex) { + return buffer.array(); + } + } + + public int size() { + return size; + } + + public int capacity() { + return capacity; + } + + private void ensureCapacity(int size) { + if (size <= capacity) + return; + synchronized (expansionMutex) { + while (size > capacity) + capacity <<= 2; + synchronized (bufferMutex) { + NetBuffer buf = NetBuffer.allocate(capacity); + System.arraycopy(buffer.array(), 0, buf.array(), 0, this.size); + buf.position(buffer.position()); + this.buffer = buf; + } + } + } + + public static interface ListIterable { + void onItem(T item); + } + + public static interface MapIterable { + void onItem(Entry item); + } + + public static interface NumericalIterator { + void onItem(int index); + } + +} diff --git a/src/com/projectswg/connection/packets/RawPacket.java b/src/com/projectswg/connection/packets/RawPacket.java new file mode 100644 index 0000000..c0a3cb6 --- /dev/null +++ b/src/com/projectswg/connection/packets/RawPacket.java @@ -0,0 +1,21 @@ +package com.projectswg.connection.packets; + +public class RawPacket { + + private final int crc; + private final byte[] data; + + public RawPacket(int crc, byte[] data) { + this.crc = crc; + this.data = data; + } + + public int getCrc() { + return crc; + } + + public byte[] getData() { + return data; + } + +} diff --git a/src/com/projectswg/connection/packets/holo/HoloConnectionStarted.java b/src/com/projectswg/connection/packets/holo/HoloConnectionStarted.java new file mode 100644 index 0000000..30dc726 --- /dev/null +++ b/src/com/projectswg/connection/packets/holo/HoloConnectionStarted.java @@ -0,0 +1,55 @@ +/************************************************************************************ + * 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.connection.packets.holo; + +import java.nio.ByteBuffer; + +import com.projectswg.connection.common.NetBuffer; + +public class HoloConnectionStarted extends HoloPacket { + + public static final int CRC = com.projectswg.connection.common.CRC.getCrc("HoloConnectionStarted"); + + public HoloConnectionStarted() { + + } + + @Override + public void decode(ByteBuffer data) { + + } + + @Override + public ByteBuffer encode() { + NetBuffer data = NetBuffer.allocate(6); + data.addShort(1); + data.addInt(CRC); + return data.getBuffer(); + } + +} diff --git a/src/com/projectswg/connection/packets/holo/HoloConnectionStopped.java b/src/com/projectswg/connection/packets/holo/HoloConnectionStopped.java new file mode 100644 index 0000000..280c771 --- /dev/null +++ b/src/com/projectswg/connection/packets/holo/HoloConnectionStopped.java @@ -0,0 +1,85 @@ +/************************************************************************************ + * 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.connection.packets.holo; + +import java.nio.ByteBuffer; + +import com.projectswg.connection.common.NetBuffer; + +public class HoloConnectionStopped extends HoloPacket { + + public static final int CRC = com.projectswg.connection.common.CRC.getCrc("HoloConnectionStopped"); + + private ConnectionStoppedReason reason; + + public HoloConnectionStopped() { + this(ConnectionStoppedReason.UNKNOWN); + } + + public HoloConnectionStopped(ConnectionStoppedReason reason) { + this.reason = reason; + } + + @Override + public void decode(ByteBuffer data) { + NetBuffer buf = NetBuffer.wrap(data); + buf.position(6); + try { + reason = ConnectionStoppedReason.valueOf(buf.getAscii()); + } catch (IllegalArgumentException e) { + reason = ConnectionStoppedReason.UNKNOWN; + } + } + + @Override + public ByteBuffer encode() { + NetBuffer data = NetBuffer.allocate(8+reason.name().length()); + data.addShort(2); + data.addInt(CRC); + data.addAscii(reason.name()); + return data.getBuffer(); + } + + public void setReason(ConnectionStoppedReason reason) { + this.reason = reason; + } + + public ConnectionStoppedReason getReason() { + return reason; + } + + public static enum ConnectionStoppedReason { + APPLICATION, + INVALID_PROTOCOL, + OTHER_SIDE_TERMINATED, + NETWORK, + SERVER_ERROR, + UNKNOWN + } + +} diff --git a/src/com/projectswg/connection/packets/holo/HoloPacket.java b/src/com/projectswg/connection/packets/holo/HoloPacket.java new file mode 100644 index 0000000..17333e6 --- /dev/null +++ b/src/com/projectswg/connection/packets/holo/HoloPacket.java @@ -0,0 +1,37 @@ +/************************************************************************************ + * 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.connection.packets.holo; + +import java.nio.ByteBuffer; + +public abstract class HoloPacket { + + public abstract void decode(ByteBuffer data); + public abstract ByteBuffer encode(); + +} diff --git a/src/com/projectswg/connection/packets/holo/HoloSetProtocolVersion.java b/src/com/projectswg/connection/packets/holo/HoloSetProtocolVersion.java new file mode 100644 index 0000000..dcbbcbe --- /dev/null +++ b/src/com/projectswg/connection/packets/holo/HoloSetProtocolVersion.java @@ -0,0 +1,72 @@ +/************************************************************************************ + * 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.connection.packets.holo; + +import java.nio.ByteBuffer; + +import com.projectswg.connection.common.NetBuffer; + +public class HoloSetProtocolVersion extends HoloPacket { + + public static final int CRC = com.projectswg.connection.common.CRC.getCrc("HoloSetProtocolVersion"); + + private String protocol; + + public HoloSetProtocolVersion() { + this(""); + } + + public HoloSetProtocolVersion(String protocol) { + this.protocol = protocol; + } + + @Override + public void decode(ByteBuffer data) { + NetBuffer buf = NetBuffer.wrap(data); + buf.position(6); + protocol = buf.getAscii(); + } + + @Override + public ByteBuffer encode() { + NetBuffer data = NetBuffer.allocate(8 + protocol.length()); + data.addShort(2); + data.addInt(CRC); + data.addAscii(protocol); + return data.getBuffer(); + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + +}