From 8c73f6e926202b4a8fca635b11a10448e37346a8 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Tue, 16 Feb 2016 23:35:21 -0500 Subject: [PATCH] Ported over the network code from the map server. A lot of backedlogged changes are here as well. --- FFXIVClassic_Lobby_Server/ClientConnection.cs | 1 + FFXIVClassic_Lobby_Server/Database.cs | 3 +- FFXIVClassic_Lobby_Server/PacketProcessor.cs | 96 +++-------- FFXIVClassic_Lobby_Server/Program.cs | 2 +- FFXIVClassic_Lobby_Server/Server.cs | 99 +++++++++-- FFXIVClassic_Lobby_Server/common/Utils.cs | 160 +++++++++++++++--- .../dataobjects/Appearance.cs | 1 - .../dataobjects/CharaInfo.cs | 15 +- .../dataobjects/Character.cs | 11 +- .../packets/BasePacket.cs | 131 +++++++++++--- .../packets/CharacterListPacket.cs | 3 +- .../packets/SubPacket.cs | 105 +++++++++--- 12 files changed, 443 insertions(+), 184 deletions(-) diff --git a/FFXIVClassic_Lobby_Server/ClientConnection.cs b/FFXIVClassic_Lobby_Server/ClientConnection.cs index 771dd3df..1a24974a 100644 --- a/FFXIVClassic_Lobby_Server/ClientConnection.cs +++ b/FFXIVClassic_Lobby_Server/ClientConnection.cs @@ -22,6 +22,7 @@ namespace FFXIVClassic_Lobby_Server public byte[] buffer = new byte[0xffff]; public CircularBuffer incomingStream = new CircularBuffer(1024); public BlockingCollection sendPacketQueue = new BlockingCollection(100); + public int lastPartialSize = 0; //Instance Stuff public uint currentUserId = 0; diff --git a/FFXIVClassic_Lobby_Server/Database.cs b/FFXIVClassic_Lobby_Server/Database.cs index e270dc15..9238039c 100644 --- a/FFXIVClassic_Lobby_Server/Database.cs +++ b/FFXIVClassic_Lobby_Server/Database.cs @@ -146,7 +146,6 @@ namespace FFXIVClassic_Lobby_Server cmd.Parameters.AddWithValue("@characterId", cid); cmd.Parameters.AddWithValue("@baseId", 0xFFFFFFFF); - cmd.Parameters.AddWithValue("@tribe", charaInfo.appearance.tribe); cmd.Parameters.AddWithValue("@size", charaInfo.appearance.size); cmd.Parameters.AddWithValue("@voice", charaInfo.appearance.voice); cmd.Parameters.AddWithValue("@skinColor", charaInfo.appearance.skinColor); @@ -360,7 +359,7 @@ namespace FFXIVClassic_Lobby_Server try { conn.Open(); - appearance = conn.Query("SELECT * FROM characters_appearance WHERE id=@CharaId", new { CharaId = charaId }).SingleOrDefault(); + appearance = conn.Query("SELECT * FROM characters_appearance WHERE characterId=@CharaId", new { CharaId = charaId }).SingleOrDefault(); } catch (MySqlException e) { diff --git a/FFXIVClassic_Lobby_Server/PacketProcessor.cs b/FFXIVClassic_Lobby_Server/PacketProcessor.cs index 33066905..87fe9a7b 100644 --- a/FFXIVClassic_Lobby_Server/PacketProcessor.cs +++ b/FFXIVClassic_Lobby_Server/PacketProcessor.cs @@ -15,96 +15,48 @@ namespace FFXIVClassic_Lobby_Server { class PacketProcessor { - List mConnections; - Boolean isAlive = true; - public PacketProcessor(List connectionList) + public void processPacket(ClientConnection client, BasePacket packet) { - mConnections = connectionList; - } - public void update() - { - Console.WriteLine("Packet processing thread has started"); - while (isAlive) - { - lock (mConnections) - { - foreach (ClientConnection client in mConnections) - { - //Receive packets - while (true) - { - if (client.incomingStream.Size < BasePacket.BASEPACKET_SIZE) - break; - - try { - if (client.incomingStream.Size < BasePacket.BASEPACKET_SIZE) - break; - BasePacketHeader header = BasePacket.getHeader(client.incomingStream.Peek(BasePacket.BASEPACKET_SIZE)); - - if (client.incomingStream.Size < header.packetSize) - break; - - BasePacket packet = new BasePacket(client.incomingStream.Get(header.packetSize)); - processPacket(client, packet); - - } - catch(OverflowException) - { break; } - } - - //Send packets - while (client.sendPacketQueue.Count != 0) - client.flushQueuedSendPackets(); - } - } - - //Don't waste CPU if isn't needed - if (mConnections.Count == 0) - Thread.Sleep(2000); - else - Thread.Sleep(100); - } - } - - private void processPacket(ClientConnection client, BasePacket packet) - { - if ((packet.header.packetSize == 0x288) && (packet.data[0x34] == 'T')) //Test Ticket Data { //Crypto handshake ProcessStartSession(client, packet); return; } - + BasePacket.decryptPacket(client.blowfish, ref packet); - //packet.debugPrintPacket(); + packet.debugPrintPacket(); List subPackets = packet.getSubpackets(); foreach (SubPacket subpacket in subPackets) { subpacket.debugPrintSubPacket(); - switch (subpacket.header.opcode) + + if (subpacket.header.type == 3) { - case 0x03: - ProcessGetCharacters(client, subpacket); - break; - case 0x04: - ProcessSelectCharacter(client, subpacket); - break; - case 0x05: - ProcessSessionAcknowledgement(client, subpacket); - break; - case 0x0B: - ProcessModifyCharacter(client, subpacket); - break; - case 0x0F: + switch (subpacket.gameMessage.opcode) + { + case 0x03: + ProcessGetCharacters(client, subpacket); + break; + case 0x04: + ProcessSelectCharacter(client, subpacket); + break; + case 0x05: + ProcessSessionAcknowledgement(client, subpacket); + break; + case 0x0B: + ProcessModifyCharacter(client, subpacket); + break; + case 0x0F: //Mod Retainers - default: - Log.debug(String.Format("Unknown command 0x{0:X} received.", subpacket.header.opcode)); - break; + default: + Log.debug(String.Format("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode)); + break; + } } } } diff --git a/FFXIVClassic_Lobby_Server/Program.cs b/FFXIVClassic_Lobby_Server/Program.cs index 3ccd16f9..af827314 100644 --- a/FFXIVClassic_Lobby_Server/Program.cs +++ b/FFXIVClassic_Lobby_Server/Program.cs @@ -35,7 +35,7 @@ namespace FFXIVClassic_Lobby_Server startServer = false; //Test DB Connection - Console.Write("Testing DB connection... "); + Console.Write("Testing DB connection to \"{0}\"... ", ConfigConstants.DATABASE_HOST); using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) { try diff --git a/FFXIVClassic_Lobby_Server/Server.cs b/FFXIVClassic_Lobby_Server/Server.cs index c3ec43f0..bb66de1f 100644 --- a/FFXIVClassic_Lobby_Server/Server.cs +++ b/FFXIVClassic_Lobby_Server/Server.cs @@ -7,19 +7,19 @@ using System.Net.Sockets; using System.Threading.Tasks; using System.Threading; using FFXIVClassic_Lobby_Server.common; +using FFXIVClassic_Lobby_Server.packets; namespace FFXIVClassic_Lobby_Server { class Server { public const int FFXIV_LOBBY_PORT = 54994; - public const int BUFFER_SIZE = 0x400; + public const int BUFFER_SIZE = 0xFFFF; public const int BACKLOG = 100; private Socket mServerSocket; private List mConnectionList = new List(); private PacketProcessor mProcessor; - private Thread mProcessorThread; #region Socket Handling public bool startServer() @@ -56,9 +56,8 @@ namespace FFXIVClassic_Lobby_Server Console.WriteLine("{0}:{1}", (mServerSocket.LocalEndPoint as IPEndPoint).Address, (mServerSocket.LocalEndPoint as IPEndPoint).Port); Console.ForegroundColor = ConsoleColor.Gray; - mProcessor = new PacketProcessor(mConnectionList); - mProcessorThread = new Thread(new ThreadStart(mProcessor.update)); - mProcessorThread.Start(); + mProcessor = new PacketProcessor(); + return true; } @@ -109,21 +108,52 @@ namespace FFXIVClassic_Lobby_Server private void receiveCallback(IAsyncResult result) { - ClientConnection conn = (ClientConnection)result.AsyncState; + ClientConnection conn = (ClientConnection)result.AsyncState; + try { int bytesRead = conn.socket.EndReceive(result); - if (bytesRead > 0) - { - conn.processIncoming(bytesRead); - //Queue the next receive - conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), conn); + bytesRead += conn.lastPartialSize; + + if (bytesRead >= 0) + { + int offset = 0; + + //Build packets until can no longer or out of data + while (true) + { + BasePacket basePacket = buildPacket(ref offset, conn.buffer, bytesRead); + + //If can't build packet, break, else process another + if (basePacket == null) + break; + else + mProcessor.processPacket(conn, basePacket); + } + + //Not all bytes consumed, transfer leftover to beginning + if (offset < bytesRead) + Array.Copy(conn.buffer, offset, conn.buffer, 0, bytesRead - offset); + + Array.Clear(conn.buffer, bytesRead - offset, conn.buffer.Length - (bytesRead - offset)); + + conn.lastPartialSize = bytesRead - offset; + + //Build any queued subpackets into basepackets and send + conn.flushQueuedSendPackets(); + + if (offset < bytesRead) + //Need offset since not all bytes consumed + conn.socket.BeginReceive(conn.buffer, bytesRead - offset, conn.buffer.Length - (bytesRead - offset), SocketFlags.None, new AsyncCallback(receiveCallback), conn); + else + //All bytes consumed, full buffer available + conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), conn); } else { Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId)); - conn.socket.Close(); + lock (mConnectionList) { mConnectionList.Remove(conn); @@ -131,18 +161,53 @@ namespace FFXIVClassic_Lobby_Server } } catch (SocketException) - { + { if (conn.socket != null) { - Log.conn(String.Format("Connection @ {0} has disconnected.", conn.currentUserId == 0 ? "Unknown User " : "User " + conn.currentUserId)); - conn.socket.Close(); + Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId)); + lock (mConnectionList) { mConnectionList.Remove(conn); - } + } } } - } + } + + /// + /// Builds a packet from the incoming buffer + offset. If a packet can be built, it is returned else null. + /// + /// Current offset in buffer. + /// Incoming buffer. + /// Returns either a BasePacket or null if not enough data. + public BasePacket buildPacket(ref int offset, byte[] buffer, int bytesRead) + { + BasePacket newPacket = null; + + //Too small to even get length + if (bytesRead <= offset) + return null; + + ushort packetSize = BitConverter.ToUInt16(buffer, offset); + + //Too small to whole packet + if (bytesRead < offset + packetSize) + return null; + + if (buffer.Length < offset + packetSize) + return null; + + try + { + newPacket = new BasePacket(buffer, ref offset); + } + catch (OverflowException) + { + return null; + } + + return newPacket; + } #endregion diff --git a/FFXIVClassic_Lobby_Server/common/Utils.cs b/FFXIVClassic_Lobby_Server/common/Utils.cs index 9f7df2ba..40830386 100644 --- a/FFXIVClassic_Lobby_Server/common/Utils.cs +++ b/FFXIVClassic_Lobby_Server/common/Utils.cs @@ -24,7 +24,7 @@ namespace FFXIVClassic_Lobby_Server.common public static string ByteArrayToHex(byte[] bytes) { var lookup32 = _lookup32; - var result = new char[(bytes.Length * 3) + ((bytes.Length/16) < 1 ? 1 : (bytes.Length/16)*3) + bytes.Length+60]; + var result = new char[(bytes.Length * 3) + ((bytes.Length / 16) < 1 ? 1 : (bytes.Length / 16) * 3) + bytes.Length + 60]; int numNewLines = 0; for (int i = 0; i < bytes.Length; i++) { @@ -33,45 +33,151 @@ namespace FFXIVClassic_Lobby_Server.common result[(3 * i) + (17 * numNewLines) + 1] = (char)(val >> 16); result[(3 * i) + (17 * numNewLines) + 2] = ' '; - result[(numNewLines * (3*16+17)) + (3 * 16) + (i % 16)] = (char)bytes[i] >= 32 && (char)bytes[i] <= 126 ? (char)bytes[i] : '.'; - - if (i != bytes.Length - 1 && bytes.Length > 16 && i != 0 && (i+1) % 16 == 0) + result[(numNewLines * (3 * 16 + 17)) + (3 * 16) + (i % 16)] = (char)bytes[i] >= 32 && (char)bytes[i] <= 126 ? (char)bytes[i] : '.'; + + if (i != bytes.Length - 1 && bytes.Length > 16 && i != 0 && (i + 1) % 16 == 0) { - result[(numNewLines * (3*16+17)) + (3 * 16) + (16)] = '\n'; + result[(numNewLines * (3 * 16 + 17)) + (3 * 16) + (16)] = '\n'; numNewLines++; } - + } return new string(result); } - /* - /// - /// Grabs an instance of a class directly from the MySqlDataReader instead of manually building from GetString() etc. - /// - /// Type of object to return - /// Reader instance - /// new T - public static T GetSQLObject(this MySql.Data.MySqlClient.MySqlDataReader reader) + + public static UInt32 UnixTimeStampUTC() { - var obj = Activator.CreateInstance(typeof(T)); - var fields = obj.GetType().GetFields(); + UInt32 unixTimeStamp; + DateTime currentTime = DateTime.Now; + DateTime zuluTime = currentTime.ToUniversalTime(); + DateTime unixEpoch = new DateTime(1970, 1, 1); + unixTimeStamp = (UInt32)(zuluTime.Subtract(unixEpoch)).TotalSeconds; - foreach (var field in obj.GetType().GetFields()) + return unixTimeStamp; + } + + public static UInt64 MilisUnixTimeStampUTC() + { + UInt64 unixTimeStamp; + DateTime currentTime = DateTime.Now; + DateTime zuluTime = currentTime.ToUniversalTime(); + DateTime unixEpoch = new DateTime(1970, 1, 1); + unixTimeStamp = (UInt64)(zuluTime.Subtract(unixEpoch)).TotalMilliseconds; + + return unixTimeStamp; + } + + public static ulong swapEndian(ulong input) + { + return ((0x00000000000000FF) & (input >> 56) | + (0x000000000000FF00) & (input >> 40) | + (0x0000000000FF0000) & (input >> 24) | + (0x00000000FF000000) & (input >> 8) | + (0x000000FF00000000) & (input << 8) | + (0x0000FF0000000000) & (input << 24) | + (0x00FF000000000000) & (input << 40) | + (0xFF00000000000000) & (input << 56)); + } + + public static uint swapEndian(uint input) + { + return ((input >> 24) & 0xff) | + ((input << 8) & 0xff0000) | + ((input >> 8) & 0xff00) | + ((input << 24) & 0xff000000); + } + + public static int swapEndian(int input) + { + uint inputAsUint = (uint)input; + + input = (int) + (((inputAsUint >> 24) & 0xff) | + ((inputAsUint << 8) & 0xff0000) | + ((inputAsUint >> 8) & 0xff00) | + ((inputAsUint << 24) & 0xff000000)); + + return input; + } + + public static uint MurmurHash2(string key, uint seed) + { + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + byte[] data = Encoding.ASCII.GetBytes(key); + const uint m = 0x5bd1e995; + const int r = 24; + int len = key.Length; + int dataIndex = len - 4; + + // Initialize the hash to a 'random' value + + uint h = seed ^ (uint)len; + + // Mix 4 bytes at a time into the hash + + + while (len >= 4) { - var attrs = field.GetCustomAttributes(typeof(System.Runtime.Serialization.DataMemberAttribute), true) as System.Runtime.Serialization.DataMemberAttribute[]; + h *= m; - if (attrs.Length == 0) - { - continue; - } + uint k = (uint)BitConverter.ToInt32(data, dataIndex); + k = ((k >> 24) & 0xff) | // move byte 3 to byte 0 + ((k << 8) & 0xff0000) | // move byte 1 to byte 2 + ((k >> 8) & 0xff00) | // move byte 2 to byte 1 + ((k << 24) & 0xff000000); // byte 0 to byte 3 - var fieldID = attrs[0].Name; - var fieldType = field.FieldType; + k *= m; + k ^= k >> r; + k *= m; - field.SetValue(obj, reader.GetValue(reader.GetOrdinal(fieldID))); + h ^= k; + + dataIndex -= 4; + len -= 4; } - return (T)obj; - }*/ + // Handle the last few bytes of the input array + switch (len) + { + case 3: + h ^= (uint)data[0] << 16; goto case 2; + case 2: + h ^= (uint)data[len - 2] << 8; goto case 1; + case 1: + h ^= data[len - 1]; + h *= m; + break; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } + + public static byte[] ConvertBoolArrayToBinaryStream(bool[] array) + { + byte[] data = new byte[(array.Length / 8) + (array.Length % 8 != 0 ? 1 : 0)]; + + int dataCounter = 0; + for (int i = 0; i < array.Length; i += 8) + { + for (int bitCount = 0; bitCount < 8; bitCount++) + { + if (i + bitCount >= array.Length) + break; + data[dataCounter] = (byte)(((array[i + bitCount] ? 1 : 0) << 7 - bitCount) | data[dataCounter]); + } + dataCounter++; + } + + return data; + } } } diff --git a/FFXIVClassic_Lobby_Server/dataobjects/Appearance.cs b/FFXIVClassic_Lobby_Server/dataobjects/Appearance.cs index 21b35960..f8cc0dd4 100644 --- a/FFXIVClassic_Lobby_Server/dataobjects/Appearance.cs +++ b/FFXIVClassic_Lobby_Server/dataobjects/Appearance.cs @@ -10,7 +10,6 @@ namespace FFXIVClassic_Lobby_Server.dataobjects { //////////// //Chara Info - public byte tribe = 0; public byte size = 0; public byte voice = 0; public ushort skinColor = 0; diff --git a/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs b/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs index c43c5a9a..5e7b6b86 100644 --- a/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs +++ b/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs @@ -44,6 +44,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects public uint currentClass = 0; public uint currentJob = 0; public uint allegiance = 0; + public uint tribe = 0; public uint currentLevel = 1; @@ -60,7 +61,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects { uint version = reader.ReadUInt32(); uint unknown1 = reader.ReadUInt32(); - appearance.tribe = reader.ReadByte(); + info.tribe = reader.ReadByte(); appearance.size = reader.ReadByte(); appearance.hairStyle = reader.ReadUInt16(); appearance.hairHighlightColor = reader.ReadUInt16(); @@ -106,7 +107,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects return info; } - public String buildForCharaList(Character chara, Appearance appearance) + public static String buildForCharaList(Character chara, Appearance appearance) { byte[] data; @@ -137,7 +138,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects writer.Write(System.Text.Encoding.UTF8.GetBytes(chara.name + '\0')); writer.Write((UInt32)0x1c); writer.Write((UInt32)0x04); - writer.Write((UInt32)getTribeModel(appearance.tribe)); + writer.Write((UInt32)getTribeModel(chara.tribe)); writer.Write((UInt32)appearance.size); uint colorVal = appearance.skinColor | (uint)(appearance.hairColor << 10) | (uint)(appearance.eyeColor << 20); writer.Write((UInt32)colorVal); @@ -185,7 +186,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects writer.Write((UInt16)chara.currentLevel); writer.Write((byte)chara.currentJob); writer.Write((UInt16)1); - writer.Write((byte)appearance.tribe); + writer.Write((byte)chara.tribe); writer.Write((UInt32)0xe22222aa); @@ -204,8 +205,8 @@ namespace FFXIVClassic_Lobby_Server.dataobjects writer.BaseStream.Seek(0x10, SeekOrigin.Current); - writer.Write((UInt32)chara.allegiance); - writer.Write((UInt32)chara.allegiance); + writer.Write((UInt32)chara.initialTown); + writer.Write((UInt32)chara.initialTown); } data = stream.GetBuffer(); @@ -223,7 +224,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_'); } - public UInt32 getTribeModel(byte tribe) + public static UInt32 getTribeModel(byte tribe) { switch (tribe) { diff --git a/FFXIVClassic_Lobby_Server/dataobjects/Character.cs b/FFXIVClassic_Lobby_Server/dataobjects/Character.cs index 91a18ad1..b379b400 100644 --- a/FFXIVClassic_Lobby_Server/dataobjects/Character.cs +++ b/FFXIVClassic_Lobby_Server/dataobjects/Character.cs @@ -20,12 +20,13 @@ namespace FFXIVClassic_Lobby_Server public bool doRename; public uint currentZoneId; - public uint guardian = 0; - public uint birthMonth = 0; - public uint birthDay = 0; - public uint currentClass = 0; + public byte guardian = 0; + public byte birthMonth = 0; + public byte birthDay = 0; + public uint currentClass = 3; public uint currentJob = 0; - public uint allegiance = 0; + public byte initialTown = 0; + public byte tribe = 0; public uint currentLevel = 1; diff --git a/FFXIVClassic_Lobby_Server/packets/BasePacket.cs b/FFXIVClassic_Lobby_Server/packets/BasePacket.cs index 4f448cc8..485c60cf 100644 --- a/FFXIVClassic_Lobby_Server/packets/BasePacket.cs +++ b/FFXIVClassic_Lobby_Server/packets/BasePacket.cs @@ -9,25 +9,30 @@ using FFXIVClassic_Lobby_Server.common; using System.IO; namespace FFXIVClassic_Lobby_Server.packets -{ +{ + [StructLayout(LayoutKind.Sequential)] public struct BasePacketHeader { - public byte isAuthenticated; - public byte isEncrypted; - public ushort reserved; - public ushort packetSize; - public ushort numSubpackets; - public uint unknown1; //Id? - public uint unknown2; //Usually 0x13B + public byte isAuthenticated; + public byte isEncrypted; + public ushort connectionType; + public ushort packetSize; + public ushort numSubpackets; + public ulong timestamp; //Miliseconds } - public class BasePacket{ + public class BasePacket + { + + public const int TYPE_ZONE = 1; + public const int TYPE_CHAT = 2; public const int BASEPACKET_SIZE = 0x10; public BasePacketHeader header; - public byte[] data; + public byte[] data; + //Loads a sniffed packet from a file public unsafe BasePacket(String path) { byte[] bytes = File.ReadAllBytes(path); @@ -45,10 +50,16 @@ namespace FFXIVClassic_Lobby_Server.packets int packetSize = header.packetSize; - data = new byte[packetSize - BASEPACKET_SIZE]; - Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); + if (packetSize - BASEPACKET_SIZE != 0) + { + data = new byte[packetSize - BASEPACKET_SIZE]; + Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); + } + else + data = new byte[0]; } + //Loads a sniffed packet from a byte array public unsafe BasePacket(byte[] bytes) { if (bytes.Length < BASEPACKET_SIZE) @@ -65,7 +76,7 @@ namespace FFXIVClassic_Lobby_Server.packets int packetSize = header.packetSize; data = new byte[packetSize - BASEPACKET_SIZE]; - Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); + Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); } public unsafe BasePacket(byte[] bytes, ref int offset) @@ -81,7 +92,7 @@ namespace FFXIVClassic_Lobby_Server.packets int packetSize = header.packetSize; if (bytes.Length < offset + header.packetSize) - throw new OverflowException("Packet Error: Packet size didn't equal given size"); + throw new OverflowException("Packet Error: Packet size didn't equal given size"); data = new byte[packetSize - BASEPACKET_SIZE]; Array.Copy(bytes, offset + BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); @@ -90,7 +101,7 @@ namespace FFXIVClassic_Lobby_Server.packets } public BasePacket(BasePacketHeader header, byte[] data) - { + { this.header = header; this.data = data; } @@ -102,7 +113,7 @@ namespace FFXIVClassic_Lobby_Server.packets int offset = 0; while (offset < data.Length) - subpackets.Add(new SubPacket(data, ref offset)); + subpackets.Add(new SubPacket(data, ref offset)); return subpackets; } @@ -141,6 +152,52 @@ namespace FFXIVClassic_Lobby_Server.packets return outBytes; } + //Replaces all instances of the sniffed actorID with the given one + public void replaceActorID(uint actorID) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + using (BinaryReader binreader = new BinaryReader(mem)) + { + while (binreader.BaseStream.Position + 4 < data.Length) + { + uint read = binreader.ReadUInt32(); + if (read == 0x029B2941 || read == 0x02977DC7 || read == 0x0297D2C8 || read == 0x0230d573 || read == 0x23317df || read == 0x23344a3 || read == 0x1730bdb) //Original ID + { + binWriter.BaseStream.Seek(binreader.BaseStream.Position - 0x4, SeekOrigin.Begin); + binWriter.Write(actorID); + } + } + } + } + } + } + + //Replaces all instances of the sniffed actorID with the given one + public void replaceActorID(uint fromActorID, uint actorID) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + using (BinaryReader binreader = new BinaryReader(mem)) + { + while (binreader.BaseStream.Position + 4 < data.Length) + { + uint read = binreader.ReadUInt32(); + if (read == fromActorID) //Original ID + { + binWriter.BaseStream.Seek(binreader.BaseStream.Position - 0x4, SeekOrigin.Begin); + binWriter.Write(actorID); + } + } + } + } + } + } + #region Utility Functions public static BasePacket createPacket(List subpackets, bool isAuthed, bool isEncrypted) { @@ -148,16 +205,17 @@ namespace FFXIVClassic_Lobby_Server.packets BasePacketHeader header = new BasePacketHeader(); byte[] data = null; - header.isAuthenticated = isAuthed?(byte)1:(byte)0; - header.isEncrypted = isEncrypted?(byte)1:(byte)0; + header.isAuthenticated = isAuthed ? (byte)1 : (byte)0; + header.isEncrypted = isEncrypted ? (byte)1 : (byte)0; header.numSubpackets = (ushort)subpackets.Count; header.packetSize = BASEPACKET_SIZE; + header.timestamp = Utils.MilisUnixTimeStampUTC(); //Get packet size foreach (SubPacket subpacket in subpackets) header.packetSize += subpacket.header.subpacketSize; - data = new byte[header.packetSize-0x10]; + data = new byte[header.packetSize - 0x10]; //Add Subpackets int offset = 0; @@ -184,6 +242,7 @@ namespace FFXIVClassic_Lobby_Server.packets header.isEncrypted = isEncrypted ? (byte)1 : (byte)0; header.numSubpackets = (ushort)1; header.packetSize = BASEPACKET_SIZE; + header.timestamp = Utils.MilisUnixTimeStampUTC(); //Get packet size header.packetSize += subpacket.header.subpacketSize; @@ -192,7 +251,7 @@ namespace FFXIVClassic_Lobby_Server.packets //Add Subpackets byte[] subpacketData = subpacket.getBytes(); - Array.Copy(subpacketData, 0, data, 0, subpacketData.Length); + Array.Copy(subpacketData, 0, data, 0, subpacketData.Length); Debug.Assert(data != null); @@ -200,13 +259,35 @@ namespace FFXIVClassic_Lobby_Server.packets return packet; } + public static BasePacket createPacket(byte[] data, bool isAuthed, bool isEncrypted) + { + + Debug.Assert(data != null); + + //Create Header + BasePacketHeader header = new BasePacketHeader(); + + header.isAuthenticated = isAuthed ? (byte)1 : (byte)0; + header.isEncrypted = isEncrypted ? (byte)1 : (byte)0; + header.numSubpackets = (ushort)1; + header.packetSize = BASEPACKET_SIZE; + header.timestamp = Utils.MilisUnixTimeStampUTC(); + + //Get packet size + header.packetSize += (ushort)data.Length; + + BasePacket packet = new BasePacket(header, data); + return packet; + } + public static unsafe void encryptPacket(Blowfish blowfish, BasePacket packet) { byte[] data = packet.data; int size = packet.header.packetSize; int offset = 0; - while (offset < data.Length) { + while (offset < data.Length) + { if (data.Length < offset + SubPacket.SUBPACKET_SIZE) throw new OverflowException("Packet Error: Subpacket was too small"); @@ -219,7 +300,7 @@ namespace FFXIVClassic_Lobby_Server.packets if (data.Length < offset + header.subpacketSize) throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data"); - blowfish.Encipher(data, offset + 0x10, header.subpacketSize-0x10); + blowfish.Encipher(data, offset + 0x10, header.subpacketSize - 0x10); offset += header.subpacketSize; } @@ -246,7 +327,7 @@ namespace FFXIVClassic_Lobby_Server.packets if (data.Length < offset + header.subpacketSize) throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data"); - blowfish.Decipher(data, offset + 0x10, header.subpacketSize-0x10); + blowfish.Decipher(data, offset + 0x10, header.subpacketSize - 0x10); offset += header.subpacketSize; } @@ -257,13 +338,13 @@ namespace FFXIVClassic_Lobby_Server.packets { #if DEBUG Console.BackgroundColor = ConsoleColor.DarkYellow; - Console.WriteLine("IsAuthed: {0}, IsEncrypted: {1}, Size: 0x{2:X}, Num Subpackets: {3}", header.isAuthenticated, header.isEncrypted, header.packetSize, header.numSubpackets); + Console.WriteLine("IsAuthed: {0}, IsEncrypted: {1}, Size: 0x{2:X}, Num Subpackets: {3}", header.isAuthenticated, header.isEncrypted, header.packetSize, header.numSubpackets); Console.WriteLine("{0}", Utils.ByteArrayToHex(getHeaderBytes())); foreach (SubPacket sub in getSubpackets()) sub.debugPrintSubPacket(); Console.BackgroundColor = ConsoleColor.Black; #endif } - + } } diff --git a/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs b/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs index 8919e702..f43c0c46 100644 --- a/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs +++ b/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs @@ -77,8 +77,7 @@ namespace FFXIVClassic_Lobby_Server.packets binWriter.Write(Encoding.ASCII.GetBytes(chara.name.PadRight(0x20, '\0'))); //Name binWriter.Write(Encoding.ASCII.GetBytes(worldname.PadRight(0xE, '\0'))); //World Name - CharaInfo info = JsonConvert.DeserializeObject(chara.charaInfo); - binWriter.Write(info.buildForCharaList(chara, appearance)); //Appearance Data + binWriter.Write(CharaInfo.buildForCharaList(chara, appearance)); //Appearance Data //binWriter.Write(CharaInfo.debug()); //Appearance Data characterCount++; diff --git a/FFXIVClassic_Lobby_Server/packets/SubPacket.cs b/FFXIVClassic_Lobby_Server/packets/SubPacket.cs index 390f7306..4941d174 100644 --- a/FFXIVClassic_Lobby_Server/packets/SubPacket.cs +++ b/FFXIVClassic_Lobby_Server/packets/SubPacket.cs @@ -12,24 +12,31 @@ namespace FFXIVClassic_Lobby_Server.packets [StructLayout(LayoutKind.Sequential)] public struct SubPacketHeader { - public ushort subpacketSize; - public ushort unknown0; //Always 0x03 - public uint sourceId; - public uint targetId; - public uint unknown1; - public ushort unknown4; //Always 0x14 - public ushort opcode; - public uint unknown5; - public uint timestamp; - public uint unknown6; + public ushort subpacketSize; + public ushort type; + public uint sourceId; + public uint targetId; + public uint unknown1; + } + + [StructLayout(LayoutKind.Sequential)] + public struct GameMessageHeader + { + public ushort unknown4; //Always 0x14 + public ushort opcode; + public uint unknown5; + public uint timestamp; + public uint unknown6; } public class SubPacket { - public const int SUBPACKET_SIZE = 0x20; + public const int SUBPACKET_SIZE = 0x10; + public const int GAMEMESSAGE_SIZE = 0x10; - public SubPacketHeader header; - public byte[] data; + public SubPacketHeader header; + public GameMessageHeader gameMessage; + public byte[] data; public unsafe SubPacket(byte[] bytes, ref int offset) { @@ -41,11 +48,27 @@ namespace FFXIVClassic_Lobby_Server.packets header = (SubPacketHeader)Marshal.PtrToStructure(new IntPtr(pdata), typeof(SubPacketHeader)); } + if (header.type == 0x3) + { + fixed (byte* pdata = &bytes[offset + SUBPACKET_SIZE]) + { + gameMessage = (GameMessageHeader)Marshal.PtrToStructure(new IntPtr(pdata), typeof(GameMessageHeader)); + } + } + if (bytes.Length < offset + header.subpacketSize) throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data"); - data = new byte[header.subpacketSize - SUBPACKET_SIZE]; - Array.Copy(bytes, offset + SUBPACKET_SIZE, data, 0, data.Length); + if (header.type == 0x3) + { + data = new byte[header.subpacketSize - SUBPACKET_SIZE - GAMEMESSAGE_SIZE]; + Array.Copy(bytes, offset + SUBPACKET_SIZE + GAMEMESSAGE_SIZE, data, 0, data.Length); + } + else + { + data = new byte[header.subpacketSize - SUBPACKET_SIZE]; + Array.Copy(bytes, offset + SUBPACKET_SIZE, data, 0, data.Length); + } offset += header.subpacketSize; } @@ -53,22 +76,34 @@ namespace FFXIVClassic_Lobby_Server.packets public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data) { this.header = new SubPacketHeader(); - header.opcode = opcode; + this.gameMessage = new GameMessageHeader(); + + gameMessage.opcode = opcode; header.sourceId = sourceId; header.targetId = targetId; - UInt32 unixTimestamp = (UInt32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; - header.timestamp = unixTimestamp; + gameMessage.timestamp = Utils.UnixTimeStampUTC(); - header.unknown0 = 0x03; + header.type = 0x03; header.unknown1 = 0x00; - header.unknown4 = 0x14; - header.unknown5 = 0x00; - header.unknown6 = 0x00; + gameMessage.unknown4 = 0x14; + gameMessage.unknown5 = 0x00; + gameMessage.unknown6 = 0x00; this.data = data; - header.subpacketSize = (ushort)(0x20 + data.Length); + header.subpacketSize = (ushort)(SUBPACKET_SIZE + GAMEMESSAGE_SIZE + data.Length); + } + + public SubPacket(SubPacket original, uint newTargetId) + { + this.header = new SubPacketHeader(); + this.gameMessage = original.gameMessage; + header.subpacketSize = original.header.subpacketSize; + header.type = original.header.type; + header.sourceId = original.header.sourceId; + header.targetId = newTargetId; + data = original.data; } public byte[] getHeaderBytes() @@ -83,11 +118,27 @@ namespace FFXIVClassic_Lobby_Server.packets return arr; } + public byte[] getGameMessageBytes() + { + int size = Marshal.SizeOf(gameMessage); + byte[] arr = new byte[size]; + + IntPtr ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(gameMessage, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + Marshal.FreeHGlobal(ptr); + return arr; + } + public byte[] getBytes() { byte[] outBytes = new byte[header.subpacketSize]; Array.Copy(getHeaderBytes(), 0, outBytes, 0, SUBPACKET_SIZE); - Array.Copy(data, 0, outBytes, SUBPACKET_SIZE, data.Length); + + if (header.type == 0x3) + Array.Copy(getGameMessageBytes(), 0, outBytes, SUBPACKET_SIZE, GAMEMESSAGE_SIZE); + + Array.Copy(data, 0, outBytes, SUBPACKET_SIZE + (header.type == 0x3 ? GAMEMESSAGE_SIZE : 0), data.Length); return outBytes; } @@ -95,8 +146,12 @@ namespace FFXIVClassic_Lobby_Server.packets { #if DEBUG Console.BackgroundColor = ConsoleColor.DarkRed; - Console.WriteLine("Size: 0x{0:X}, Opcode: 0x{1:X}", header.subpacketSize, header.opcode); + Console.WriteLine("Size: 0x{0:X}", header.subpacketSize); + if (header.type == 0x03) + Console.WriteLine("Opcode: 0x{0:X}", gameMessage.opcode); Console.WriteLine("{0}", Utils.ByteArrayToHex(getHeaderBytes())); + if (header.type == 0x03) + Console.WriteLine("{0}", Utils.ByteArrayToHex(getGameMessageBytes())); Console.BackgroundColor = ConsoleColor.DarkMagenta; Console.WriteLine("{0}", Utils.ByteArrayToHex(data)); Console.BackgroundColor = ConsoleColor.Black;