diff --git a/FFXIVClassic Map Server/packets/BasePacket.cs b/FFXIVClassic Common Class Lib/BasePacket.cs similarity index 85% rename from FFXIVClassic Map Server/packets/BasePacket.cs rename to FFXIVClassic Common Class Lib/BasePacket.cs index d44e35d3..9f9a4c4c 100644 --- a/FFXIVClassic Map Server/packets/BasePacket.cs +++ b/FFXIVClassic Common Class Lib/BasePacket.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; -using FFXIVClassic.Common; using NLog; using NLog.Targets; +using Ionic.Zlib; -namespace FFXIVClassic_Map_Server.packets +namespace FFXIVClassic.Common { [StructLayout(LayoutKind.Sequential)] public struct BasePacketHeader @@ -295,6 +295,41 @@ namespace FFXIVClassic_Map_Server.packets return packet; } + /// + /// 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 static BasePacket CreatePacket(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; + } + public static unsafe void EncryptPacket(Blowfish blowfish, BasePacket packet) { var data = packet.data; @@ -347,6 +382,28 @@ namespace FFXIVClassic_Map_Server.packets } } + public static unsafe void DecompressPacket(ref BasePacket packet) + { + using (var compressedStream = new MemoryStream(packet.data)) + using (var zipStream = new ZlibStream(compressedStream, Ionic.Zlib.CompressionMode.Decompress)) + using (var resultStream = new MemoryStream()) + { + zipStream.CopyTo(resultStream); + packet.data = resultStream.ToArray(); + } + } + + public static unsafe void CompressPacket(ref BasePacket packet) + { + using (var compressedStream = new MemoryStream(packet.data)) + using (var zipStream = new ZlibStream(compressedStream, Ionic.Zlib.CompressionMode.Compress)) + using (var resultStream = new MemoryStream()) + { + zipStream.CopyTo(resultStream); + packet.data = resultStream.ToArray(); + } + } + #endregion } diff --git a/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj b/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj index e4f4e80c..a08e253f 100644 --- a/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj +++ b/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj @@ -38,6 +38,9 @@ false + + ..\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll + ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll True @@ -56,12 +59,14 @@ + + diff --git a/FFXIVClassic Lobby Server/packets/SubPacket.cs b/FFXIVClassic Common Class Lib/SubPacket.cs similarity index 69% rename from FFXIVClassic Lobby Server/packets/SubPacket.cs rename to FFXIVClassic Common Class Lib/SubPacket.cs index d684a087..38394e29 100644 --- a/FFXIVClassic Lobby Server/packets/SubPacket.cs +++ b/FFXIVClassic Common Class Lib/SubPacket.cs @@ -4,7 +4,7 @@ using FFXIVClassic.Common; using NLog; using NLog.Targets; -namespace FFXIVClassic_Lobby_Server.packets +namespace FFXIVClassic.Common { [StructLayout(LayoutKind.Sequential)] public struct SubPacketHeader @@ -72,26 +72,38 @@ namespace FFXIVClassic_Lobby_Server.packets offset += header.subpacketSize; } - public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data) + public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data) : this(true, opcode, sourceId, targetId, data) { } + + public SubPacket(bool isGameMessage, ushort opcode, uint sourceId, uint targetId, byte[] data) { header = new SubPacketHeader(); - gameMessage = new GameMessageHeader(); - gameMessage.opcode = opcode; + if (isGameMessage) + { + gameMessage = new GameMessageHeader(); + gameMessage.opcode = opcode; + gameMessage.timestamp = Utils.UnixTimeStampUTC(); + gameMessage.unknown4 = 0x14; + gameMessage.unknown5 = 0x00; + gameMessage.unknown6 = 0x00; + } + header.sourceId = sourceId; header.targetId = targetId; - gameMessage.timestamp = Utils.UnixTimeStampUTC(); + if (isGameMessage) + header.type = 0x03; + else + header.type = opcode; - header.type = 0x03; header.unknown1 = 0x00; - gameMessage.unknown4 = 0x14; - gameMessage.unknown5 = 0x00; - gameMessage.unknown6 = 0x00; this.data = data; - header.subpacketSize = (ushort) (SUBPACKET_SIZE + GAMEMESSAGE_SIZE + data.Length); + header.subpacketSize = (ushort) (SUBPACKET_SIZE + data.Length); + + if (isGameMessage) + header.subpacketSize += GAMEMESSAGE_SIZE; } public SubPacket(SubPacket original, uint newTargetId) @@ -141,6 +153,41 @@ namespace FFXIVClassic_Lobby_Server.packets return outBytes; } + /// + /// 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 static SubPacket CreatePacket(ref int offset, byte[] buffer, int bytesRead) + { + SubPacket 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 SubPacket(buffer, ref offset); + } + catch (OverflowException) + { + return null; + } + + return newPacket; + } + public void DebugPrintSubPacket() { #if DEBUG @@ -157,7 +204,10 @@ namespace FFXIVClassic_Lobby_Server.packets logger.ColorDebug(Utils.ByteArrayToHex(data, SUBPACKET_SIZE + GAMEMESSAGE_SIZE), ConsoleOutputColor.DarkMagenta); } + else + logger.ColorDebug(Utils.ByteArrayToHex(data, SUBPACKET_SIZE), + ConsoleOutputColor.DarkMagenta); #endif - } + } } } \ No newline at end of file diff --git a/FFXIVClassic Common Class Lib/packages.config b/FFXIVClassic Common Class Lib/packages.config index 84d14a9e..5436126a 100644 --- a/FFXIVClassic Common Class Lib/packages.config +++ b/FFXIVClassic Common Class Lib/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/FFXIVClassic Lobby Server/ClientConnection.cs b/FFXIVClassic Lobby Server/ClientConnection.cs index 02747492..1bc1208b 100644 --- a/FFXIVClassic Lobby Server/ClientConnection.cs +++ b/FFXIVClassic Lobby Server/ClientConnection.cs @@ -1,6 +1,5 @@ using System; using System.Net.Sockets; -using FFXIVClassic_Lobby_Server.packets; using FFXIVClassic.Common; using System.Collections.Concurrent; using Cyotek.Collections.Generic; diff --git a/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj b/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj index e8d78846..a8c4fab3 100644 --- a/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj +++ b/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj @@ -91,7 +91,6 @@ - @@ -101,7 +100,6 @@ - diff --git a/FFXIVClassic Lobby Server/NLog.config b/FFXIVClassic Lobby Server/NLog.config index 2384b15b..4463f999 100644 --- a/FFXIVClassic Lobby Server/NLog.config +++ b/FFXIVClassic Lobby Server/NLog.config @@ -38,13 +38,13 @@ diff --git a/FFXIVClassic Lobby Server/Server.cs b/FFXIVClassic Lobby Server/Server.cs index e596ec26..93cd8639 100644 --- a/FFXIVClassic Lobby Server/Server.cs +++ b/FFXIVClassic Lobby Server/Server.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; -using FFXIVClassic_Lobby_Server.packets; + using FFXIVClassic.Common; using NLog; diff --git a/FFXIVClassic Lobby Server/packets/BasePacket.cs b/FFXIVClassic Lobby Server/packets/BasePacket.cs deleted file mode 100644 index 075ec2b6..00000000 --- a/FFXIVClassic Lobby Server/packets/BasePacket.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using FFXIVClassic.Common; -using NLog; -using NLog.Targets; - -namespace FFXIVClassic_Lobby_Server.packets -{ - [StructLayout(LayoutKind.Sequential)] - public struct BasePacketHeader - { - public byte isAuthenticated; - public byte isEncrypted; - public ushort connectionType; - public ushort packetSize; - public ushort numSubpackets; - public ulong timestamp; //Miliseconds - } - - public class BasePacket - { - public const int TYPE_ZONE = 1; - public const int TYPE_CHAT = 2; - public const int BASEPACKET_SIZE = 0x10; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - public byte[] data; - - public BasePacketHeader header; - - //Loads a sniffed packet from a file - public unsafe BasePacket(string path) - { - var bytes = File.ReadAllBytes(path); - - if (bytes.Length < BASEPACKET_SIZE) - throw new OverflowException("Packet Error: Packet was too small"); - - fixed (byte* pdata = &bytes[0]) - { - header = (BasePacketHeader) Marshal.PtrToStructure(new IntPtr(pdata), typeof(BasePacketHeader)); - } - - if (bytes.Length < header.packetSize) - throw new OverflowException("Packet Error: Packet size didn't equal given size"); - - int packetSize = header.packetSize; - - 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) - throw new OverflowException("Packet Error: Packet was too small"); - - fixed (byte* pdata = &bytes[0]) - { - header = (BasePacketHeader) Marshal.PtrToStructure(new IntPtr(pdata), typeof(BasePacketHeader)); - } - - if (bytes.Length < header.packetSize) - throw new OverflowException("Packet Error: Packet size didn't equal given size"); - - int packetSize = header.packetSize; - - data = new byte[packetSize - BASEPACKET_SIZE]; - Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); - } - - public unsafe BasePacket(byte[] bytes, ref int offset) - { - if (bytes.Length < offset + BASEPACKET_SIZE) - throw new OverflowException("Packet Error: Packet was too small"); - - fixed (byte* pdata = &bytes[offset]) - { - header = (BasePacketHeader) Marshal.PtrToStructure(new IntPtr(pdata), typeof(BasePacketHeader)); - } - - int packetSize = header.packetSize; - - if (bytes.Length < offset + header.packetSize) - 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); - - offset += packetSize; - } - - public BasePacket(BasePacketHeader header, byte[] data) - { - this.header = header; - this.data = data; - } - - public List GetSubpackets() - { - var subpackets = new List(header.numSubpackets); - - var offset = 0; - - while (offset < data.Length) - subpackets.Add(new SubPacket(data, ref offset)); - - return subpackets; - } - - public static unsafe BasePacketHeader GetHeader(byte[] bytes) - { - BasePacketHeader header; - if (bytes.Length < BASEPACKET_SIZE) - throw new OverflowException("Packet Error: Packet was too small"); - - fixed (byte* pdata = &bytes[0]) - { - header = (BasePacketHeader) Marshal.PtrToStructure(new IntPtr(pdata), typeof(BasePacketHeader)); - } - - return header; - } - - public byte[] GetHeaderBytes() - { - var size = Marshal.SizeOf(header); - var arr = new byte[size]; - - var ptr = Marshal.AllocHGlobal(size); - Marshal.StructureToPtr(header, ptr, true); - Marshal.Copy(ptr, arr, 0, size); - Marshal.FreeHGlobal(ptr); - return arr; - } - - public byte[] GetPacketBytes() - { - var outBytes = new byte[header.packetSize]; - Array.Copy(GetHeaderBytes(), 0, outBytes, 0, BASEPACKET_SIZE); - Array.Copy(data, 0, outBytes, BASEPACKET_SIZE, data.Length); - return outBytes; - } - - //Replaces all instances of the sniffed actorID with the given one - public void ReplaceActorID(uint actorID) - { - using (var mem = new MemoryStream(data)) - { - using (var binWriter = new BinaryWriter(mem)) - { - using (var binreader = new BinaryReader(mem)) - { - while (binreader.BaseStream.Position + 4 < data.Length) - { - var 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 (var mem = new MemoryStream(data)) - { - using (var binWriter = new BinaryWriter(mem)) - { - using (var binreader = new BinaryReader(mem)) - { - while (binreader.BaseStream.Position + 4 < data.Length) - { - var read = binreader.ReadUInt32(); - if (read == fromActorID) //Original ID - { - binWriter.BaseStream.Seek(binreader.BaseStream.Position - 0x4, SeekOrigin.Begin); - binWriter.Write(actorID); - } - } - } - } - } - } - - public void DebugPrintPacket() - { -#if DEBUG - logger.ColorDebug( - string.Format("IsAuth:{0} Size:0x{1:X}, NumSubpackets:{2}{3}{4}", - header.isAuthenticated, header.packetSize, header.numSubpackets, - Environment.NewLine, Utils.ByteArrayToHex(GetHeaderBytes())), ConsoleOutputColor.DarkYellow); - - foreach (var sub in GetSubpackets()) - { - sub.DebugPrintSubPacket(); - } -#endif - } - - #region Utility Functions - - public static BasePacket CreatePacket(List subpackets, bool isAuthed, bool isEncrypted) - { - //Create Header - var header = new BasePacketHeader(); - byte[] data = null; - - 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 (var subpacket in subpackets) - header.packetSize += subpacket.header.subpacketSize; - - data = new byte[header.packetSize - 0x10]; - - //Add Subpackets - var offset = 0; - foreach (var subpacket in subpackets) - { - var subpacketData = subpacket.GetBytes(); - Array.Copy(subpacketData, 0, data, offset, subpacketData.Length); - offset += (ushort) subpacketData.Length; - } - - Debug.Assert(data != null && offset == data.Length && header.packetSize == 0x10 + offset); - - var packet = new BasePacket(header, data); - return packet; - } - - public static BasePacket CreatePacket(SubPacket subpacket, bool isAuthed, bool isEncrypted) - { - //Create Header - var header = new BasePacketHeader(); - byte[] data = null; - - header.isAuthenticated = isAuthed ? (byte) 1 : (byte) 0; - header.isEncrypted = isEncrypted ? (byte) 1 : (byte) 0; - header.numSubpackets = 1; - header.packetSize = BASEPACKET_SIZE; - header.timestamp = Utils.MilisUnixTimeStampUTC(); - - //Get packet size - header.packetSize += subpacket.header.subpacketSize; - - data = new byte[header.packetSize - 0x10]; - - //Add Subpackets - var subpacketData = subpacket.GetBytes(); - Array.Copy(subpacketData, 0, data, 0, subpacketData.Length); - - Debug.Assert(data != null); - - var packet = new BasePacket(header, data); - return packet; - } - - public static BasePacket CreatePacket(byte[] data, bool isAuthed, bool isEncrypted) - { - Debug.Assert(data != null); - - //Create Header - var header = new BasePacketHeader(); - - header.isAuthenticated = isAuthed ? (byte) 1 : (byte) 0; - header.isEncrypted = isEncrypted ? (byte) 1 : (byte) 0; - header.numSubpackets = 1; - header.packetSize = BASEPACKET_SIZE; - header.timestamp = Utils.MilisUnixTimeStampUTC(); - - //Get packet size - header.packetSize += (ushort) data.Length; - - var packet = new BasePacket(header, data); - return packet; - } - - public static unsafe void EncryptPacket(Blowfish blowfish, BasePacket packet) - { - var data = packet.data; - int size = packet.header.packetSize; - - var offset = 0; - while (offset < data.Length) - { - if (data.Length < offset + SubPacket.SUBPACKET_SIZE) - throw new OverflowException("Packet Error: Subpacket was too small"); - - SubPacketHeader header; - fixed (byte* pdata = &data[offset]) - { - header = (SubPacketHeader) Marshal.PtrToStructure(new IntPtr(pdata), typeof(SubPacketHeader)); - } - - 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); - - offset += header.subpacketSize; - } - } - - public static unsafe void DecryptPacket(Blowfish blowfish, ref BasePacket packet) - { - var data = packet.data; - int size = packet.header.packetSize; - - var offset = 0; - while (offset < data.Length) - { - if (data.Length < offset + SubPacket.SUBPACKET_SIZE) - throw new OverflowException("Packet Error: Subpacket was too small"); - - SubPacketHeader header; - fixed (byte* pdata = &data[offset]) - { - header = (SubPacketHeader) Marshal.PtrToStructure(new IntPtr(pdata), typeof(SubPacketHeader)); - } - - 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); - - offset += header.subpacketSize; - } - } - - #endregion - } - - public static class LoggerExtensions - { - public static void ColorDebug(this Logger logger, string message, ConsoleOutputColor color) - { - var logEvent = new LogEventInfo(LogLevel.Debug, logger.Name, message); - logEvent.Properties["color"] = (int) color; - logger.Log(logEvent); - } - } -} \ No newline at end of file diff --git a/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs b/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs index 9bedc3d3..a6c69341 100644 --- a/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs @@ -1,4 +1,5 @@ -using FFXIVClassic_Lobby_Server.dataobjects; +using FFXIVClassic.Common; +using FFXIVClassic_Lobby_Server.dataobjects; using System; using System.Collections.Generic; using System.IO; diff --git a/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs b/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs index d5b9289b..5825c7be 100644 --- a/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs @@ -1,4 +1,5 @@ -using System; +using FFXIVClassic.Common; +using System; using System.IO; using System.Text; diff --git a/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs b/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs index 2ad1b0b9..42db777f 100644 --- a/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs @@ -1,4 +1,5 @@ -using FFXIVClassic_Lobby_Server.dataobjects; +using FFXIVClassic.Common; +using FFXIVClassic_Lobby_Server.dataobjects; using System; using System.Collections.Generic; using System.IO; diff --git a/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs b/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs index 0e707e62..5578ee52 100644 --- a/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs @@ -1,4 +1,5 @@ -using System; +using FFXIVClassic.Common; +using System; using System.IO; using System.Text; diff --git a/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs b/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs index 80bce85c..ac389071 100644 --- a/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs @@ -1,4 +1,5 @@ -using System; +using FFXIVClassic.Common; +using System; using System.Collections.Generic; using System.IO; using System.Text; diff --git a/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs b/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs index c12c245b..c9a6377d 100644 --- a/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs @@ -1,4 +1,5 @@ -using System; +using FFXIVClassic.Common; +using System; using System.Collections.Generic; using System.IO; using System.Text; diff --git a/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs b/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs index 352c11e3..6b57da3f 100644 --- a/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs @@ -1,4 +1,5 @@ -using System; +using FFXIVClassic.Common; +using System; using System.Collections.Generic; using System.IO; using System.Text; diff --git a/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs b/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs index 06866bfc..0e939700 100644 --- a/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs @@ -1,4 +1,5 @@ -using FFXIVClassic_Lobby_Server.dataobjects; +using FFXIVClassic.Common; +using FFXIVClassic_Lobby_Server.dataobjects; using System; using System.Collections.Generic; using System.IO; diff --git a/FFXIVClassic Map Server/CommandProcessor.cs b/FFXIVClassic Map Server/CommandProcessor.cs index 50985884..16792d81 100644 --- a/FFXIVClassic Map Server/CommandProcessor.cs +++ b/FFXIVClassic Map Server/CommandProcessor.cs @@ -1,40 +1,25 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; -using System.Threading; using FFXIVClassic.Common; using FFXIVClassic_Map_Server.dataobjects; -using FFXIVClassic_Map_Server.packets; + using System.IO; using FFXIVClassic_Map_Server.packets.send.actor; -using FFXIVClassic_Map_Server; using FFXIVClassic_Map_Server.packets.send; -using FFXIVClassic_Map_Server.dataobjects.chara; -using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.lua; -using FFXIVClassic_Map_Server.actors.chara.player; -using FFXIVClassic_Map_Server.Properties; +using FFXIVClassic_Map_Server.Actors; namespace FFXIVClassic_Map_Server { class CommandProcessor { - private Dictionary mConnectedPlayerList; private static Dictionary gamedataItems = Server.GetGamedataItems(); // For the moment, this is the only predefined item // TODO: make a list/enum in the future so that items can be given by name, instead of by id const UInt32 ITEM_GIL = 1000001; - - public CommandProcessor(Dictionary playerList) - { - mConnectedPlayerList = playerList; - } - + public void ChangeProperty(uint id, uint value, string target) { SetActorPropetyPacket ChangeProperty = new SetActorPropetyPacket(target); @@ -43,9 +28,11 @@ namespace FFXIVClassic_Map_Server ChangeProperty.AddInt(id, value); ChangeProperty.AddTarget(); - foreach (KeyValuePair entry in mConnectedPlayerList) + Dictionary sessionList = Server.GetServer().GetSessionList(); + + foreach (KeyValuePair entry in sessionList) { - SubPacket ChangePropertyPacket = ChangeProperty.BuildPacket((entry.Value.actorID), (entry.Value.actorID)); + SubPacket ChangePropertyPacket = ChangeProperty.BuildPacket((entry.Value.id), (entry.Value.id)); BasePacket packet = BasePacket.CreatePacket(ChangePropertyPacket, true, false); packet.DebugPrintPacket(); @@ -60,13 +47,13 @@ namespace FFXIVClassic_Map_Server /// /// /// - private void SendMessage(ConnectedPlayer client, String message) + private void SendMessage(Session session, String message) { - if (client != null) - client.GetActor().QueuePacket(SendMessagePacket.BuildPacket(client.actorID, client.actorID, SendMessagePacket.MESSAGE_TYPE_GENERAL_INFO, "", message)); + if (session != null) + session.GetActor().QueuePacket(SendMessagePacket.BuildPacket(session.id, session.id, SendMessagePacket.MESSAGE_TYPE_GENERAL_INFO, "", message)); } - internal bool DoCommand(string input, ConnectedPlayer client) + internal bool DoCommand(string input, Session session) { if (!input.Any() || input.Equals("")) return false; @@ -88,7 +75,9 @@ namespace FFXIVClassic_Map_Server if (cmd.Any()) { // if client isnt null, take player to be the player actor - var player = client?.GetActor(); + Player player = null; + if (session != null) + player = session.GetActor(); if (cmd.Equals("help")) { @@ -125,11 +114,11 @@ namespace FFXIVClassic_Map_Server if (split[0].Equals("reloaditems")) { Program.Log.Info(String.Format("Got request to reload item gamedata")); - SendMessage(client, "Reloading Item Gamedata..."); + SendMessage(session, "Reloading Item Gamedata..."); gamedataItems.Clear(); gamedataItems = Database.GetItemGamedata(); Program.Log.Info(String.Format("Loaded {0} items.", gamedataItems.Count)); - SendMessage(client, String.Format("Loaded {0} items.", gamedataItems.Count)); + SendMessage(session, String.Format("Loaded {0} items.", gamedataItems.Count)); return true; } #endregion diff --git a/FFXIVClassic Map Server/ConfigConstants.cs b/FFXIVClassic Map Server/ConfigConstants.cs index cf4006e6..c3ea8d54 100644 --- a/FFXIVClassic Map Server/ConfigConstants.cs +++ b/FFXIVClassic Map Server/ConfigConstants.cs @@ -30,7 +30,7 @@ namespace FFXIVClassic_Map_Server INIFile configIni = new INIFile("./map_config.ini"); ConfigConstants.OPTIONS_BINDIP = configIni.GetValue("General", "server_ip", "127.0.0.1"); - ConfigConstants.OPTIONS_PORT = configIni.GetValue("General", "server_port", "54992"); + ConfigConstants.OPTIONS_PORT = configIni.GetValue("General", "server_port", "1989"); ConfigConstants.OPTIONS_TIMESTAMP = configIni.GetValue("General", "showtimestamp", "true").ToLower().Equals("true"); ConfigConstants.DATABASE_WORLDID = UInt32.Parse(configIni.GetValue("Database", "worldid", "0")); diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index 823e8b13..0bb01eb3 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using FFXIVClassic.Common; using FFXIVClassic_Map_Server.utils; -using FFXIVClassic_Map_Server.packets; + using FFXIVClassic_Map_Server.packets.send.player; using FFXIVClassic_Map_Server.dataobjects; using FFXIVClassic_Map_Server.Actors; @@ -258,6 +258,8 @@ namespace FFXIVClassic_Map_Server positionY = @y, positionZ = @z, rotation = @rot, + destinationZoneId = @destZone, + destinationSpawnType = @destSpawn, currentZoneId = @zoneId WHERE id = @charaId "; @@ -269,6 +271,8 @@ namespace FFXIVClassic_Map_Server cmd.Parameters.AddWithValue("@z", player.positionZ); cmd.Parameters.AddWithValue("@rot", player.rotation); cmd.Parameters.AddWithValue("@zoneId", player.zoneId); + cmd.Parameters.AddWithValue("@destZone", player.destinationZone); + cmd.Parameters.AddWithValue("@destSpawn", player.destinationSpawnType); cmd.ExecuteNonQuery(); } @@ -402,7 +406,9 @@ namespace FFXIVClassic_Map_Server tribe, restBonus, achievementPoints, - playTime + playTime, + destinationZoneId, + destinationSpawnType FROM characters WHERE id = @charId"; cmd = new MySqlCommand(query, conn); @@ -419,8 +425,7 @@ namespace FFXIVClassic_Map_Server player.oldRotation = player.rotation = reader.GetFloat(4); player.currentMainState = reader.GetUInt16(5); player.zoneId = reader.GetUInt32(6); - player.isZoning = true; - player.zone = Server.GetWorldManager().GetZone(player.zoneId); + player.isZoning = true; player.gcCurrent = reader.GetByte(7); player.gcRankLimsa = reader.GetByte(8); player.gcRankGridania = reader.GetByte(9); @@ -434,6 +439,13 @@ namespace FFXIVClassic_Map_Server player.playerWork.restBonusExpRate = reader.GetInt32(17); player.achievementPoints = reader.GetUInt32(18); player.playTime = reader.GetUInt32(19); + player.destinationZone = reader.GetUInt32("destinationZoneId"); + player.destinationSpawnType = reader.GetByte("destinationSpawnType"); + + if (player.destinationZone != 0) + player.zoneId = player.destinationZone; + + player.zone = Server.GetWorldManager().GetZone(player.zoneId); } } diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index 2bd335eb..c645651e 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -93,7 +93,7 @@ - + @@ -112,7 +112,7 @@ - + @@ -124,7 +124,6 @@ - @@ -258,9 +257,13 @@ - + + + + + @@ -286,6 +289,7 @@ Designer + diff --git a/FFXIVClassic Map Server/NLog.config b/FFXIVClassic Map Server/NLog.config index a59613ac..ad616d50 100644 --- a/FFXIVClassic Map Server/NLog.config +++ b/FFXIVClassic Map Server/NLog.config @@ -38,13 +38,13 @@ @@ -55,6 +55,7 @@ + + \ No newline at end of file diff --git a/FFXIVClassic World Server/NLog.config b/FFXIVClassic World Server/NLog.config new file mode 100644 index 00000000..e3e21f9c --- /dev/null +++ b/FFXIVClassic World Server/NLog.config @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FFXIVClassic World Server/NLog.xsd b/FFXIVClassic World Server/NLog.xsd new file mode 100644 index 00000000..31cd6c0a --- /dev/null +++ b/FFXIVClassic World Server/NLog.xsd @@ -0,0 +1,2601 @@ + + + + + + + + + + + + + + + Watch config file for changes and reload automatically. + + + + + Print internal NLog messages to the console. Default value is: false + + + + + Print internal NLog messages to the console error output. Default value is: false + + + + + Write internal NLog messages to the specified file. + + + + + Log level threshold for internal log messages. Default value is: Info. + + + + + Global log level threshold for application log messages. Messages below this level won't be logged.. + + + + + Pass NLog internal exceptions to the application. Default value is: false. + + + + + Write internal NLog messages to the the System.Diagnostics.Trace. Default value is: false + + + + + + + + + + + + + + Make all targets within this section asynchronous (Creates additional threads but the calling thread isn't blocked by any target writes). + + + + + + + + + + + + + + + + + Prefix for targets/layout renderers/filters/conditions loaded from this assembly. + + + + + Load NLog extensions from the specified file (*.dll) + + + + + Load NLog extensions from the specified assembly. Assembly name should be fully qualified. + + + + + + + + + + Name of the logger. May include '*' character which acts like a wildcard. Allowed forms are: *, Name, *Name, Name* and *Name* + + + + + Comma separated list of levels that this rule matches. + + + + + Minimum level that this rule matches. + + + + + Maximum level that this rule matches. + + + + + Level that this rule matches. + + + + + Comma separated list of target names. + + + + + Ignore further rules if this one matches. + + + + + Enable or disable logging rule. Disabled rules are ignored. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the file to be included. The name is relative to the name of the current config file. + + + + + Ignore any errors in the include file. + + + + + + + Variable name. + + + + + Variable value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to add <!-- --> comments around all written texts. + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events that should be processed in a batch by the lazy writer thread. + + + + + Action to be taken when the lazy writer thread request queue count exceeds the set limit. + + + + + Limit on the number of requests in the lazy writer thread request queue. + + + + + Time in milliseconds to sleep between batches. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events to be buffered. + + + + + Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. + + + + + Indicates whether to use sliding timeout. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + Maximum current connections. 0 = no maximum. + + + + + Network address. + + + + + Maximum queue size. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + NDC item separator. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include dictionary contents. + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout that should be use to calcuate the value for the parameter. + + + + + Viewer parameter name. + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to use default row highlighting rules. + + + + + The encoding for writing messages to the . + + + + + Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Condition that must be met in order to set the specified foreground and background color. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + Indicates whether to ignore case when comparing texts. + + + + + Regular expression to be matched. You must specify either text or regex. + + + + + Text to be matched. You must specify either text or regex. + + + + + Indicates whether to match whole words only. + + + + + Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + The encoding for writing messages to the . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. + + + + + Name of the connection string (as specified in <connectionStrings> configuration section. + + + + + Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. + + + + + Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. + + + + + Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. + + + + + Name of the database provider. + + + + + Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. + + + + + Indicates whether to keep the database connection open between the log events. + + + + + Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. + + + + + Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. + + + + + Text of the SQL command to be run on each log level. + + + + + Type of the SQL command to be run on each log level. + + + + + + + + + + + + + + + + + + + + + + + Type of the command. + + + + + Connection string to run the command against. If not provided, connection string from the target is used. + + + + + Indicates whether to ignore failures. + + + + + Command text. + + + + + + + + + + + + + + Layout that should be use to calcuate the value for the parameter. + + + + + Database parameter name. + + + + + Database parameter precision. + + + + + Database parameter scale. + + + + + Database parameter size. + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Layout that renders event Category. + + + + + Layout that renders event ID. + + + + + Name of the Event Log to write to. This can be System, Application or any user-defined name. + + + + + Name of the machine on which Event Log service is running. + + + + + Value to be used as the event Source. + + + + + Action to take if the message is larger than the option. + + + + + Optional entrytype. When not set, or when not convertable to then determined by + + + + + Message length limit to write to the Event Log. + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to return to the first target after any successful write. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + File encoding. + + + + + Line ending mode. + + + + + Way file archives are numbered. + + + + + Name of the file to be used for an archive. + + + + + Indicates whether to automatically archive log files every time the specified time passes. + + + + + Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot Create multiple archive files, if they should have the same name. Choose: + + + + + Maximum number of archive files that should be kept. + + + + + Indicates whether to compress archive files into the zip archive format. + + + + + Gets or set a value indicating whether a managed file stream is forced, instead of used the native implementation. + + + + + Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing Gets written when the filename is wrong. + + + + + Name of the file to write to. + + + + + Value specifying the date format to use when archiving files. + + + + + Indicates whether to archive old log file on startup. + + + + + Indicates whether to Create directories if they Do not exist. + + + + + Indicates whether to enable log file(s) to be deleted. + + + + + File attributes (Windows only). + + + + + Indicates whether to delete old log file on startup. + + + + + Indicates whether to replace file contents on each write instead of appending log message at the end. + + + + + Indicates whether concurrent writes to the log file by multiple processes on the same host. + + + + + Delay in milliseconds to wait before attempting to write to the file again. + + + + + Maximum number of log filenames that should be stored as existing. + + + + + Indicates whether concurrent writes to the log file by multiple processes on different network hosts. + + + + + Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). + + + + + Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. + + + + + Log file buffer size in bytes. + + + + + Indicates whether to automatically flush the file buffers after each log message. + + + + + Number of times the write is appended on the file before NLog discards the log message. + + + + + Indicates whether to keep log file open instead of opening and closing it on each logging event. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Condition expression. Log events who meet this condition will be forwarded to the wrapped target. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Windows Domain name to change context to. + + + + + Required impersonation level. + + + + + Type of the logon provider. + + + + + Logon Type. + + + + + User account password. + + + + + Indicates whether to revert to the credentials of the process instead of impersonating another user. + + + + + Username to change context to. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Endpoint address. + + + + + Name of the endpoint configuration in WCF configuration file. + + + + + Indicates whether to use a WCF service contract that is one way (fire and forGet) or two way (request-reply) + + + + + Client ID. + + + + + Indicates whether to include per-event properties in the payload sent to the server. + + + + + Indicates whether to use binary message encoding. + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Name of the parameter. + + + + + Type of the parameter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to send message as HTML instead of plain text. + + + + + Encoding to be used for sending e-mail. + + + + + Indicates whether to add new lines between log entries. + + + + + CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Mail message body (repeated for each log message send in one mail). + + + + + Mail subject. + + + + + Sender's email address (e.g. joe@domain.com). + + + + + Indicates whether NewLine characters in the body should be replaced with tags. + + + + + Priority used for sending mails. + + + + + Indicates the SMTP client timeout. + + + + + SMTP Server to be used for sending. + + + + + SMTP Authentication mode. + + + + + Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. + + + + + Port number that SMTP Server is listening on. + + + + + Indicates whether the default Settings from System.Net.MailSettings should be used. + + + + + Folder where applications save mail messages to be processed by the local SMTP server. + + + + + Specifies how outgoing email messages will be handled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used when writing text to the queue. + + + + + Indicates whether to use the XML format when serializing message. This will also disable creating queues. + + + + + Indicates whether to check if a queue exists before writing to it. + + + + + Indicates whether to Create the queue if it Doesn't exists. + + + + + Label to associate with each message. + + + + + Name of the queue to write to. + + + + + Indicates whether to use recoverable messages (with guaranteed delivery). + + + + + + + + + + + + + + + + + Name of the target. + + + + + Class name. + + + + + Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used. + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + Indicates whether to keep connection open whenever possible. + + + + + Maximum current connections. 0 = no maximum. + + + + + Maximum queue size. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + Maximum current connections. 0 = no maximum. + + + + + Network address. + + + + + Maximum queue size. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + NDC item separator. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include dictionary contents. + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to perform layout calculation. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether performance counter should be automatically Created. + + + + + Name of the performance counter category. + + + + + Counter help text. + + + + + Name of the performance counter. + + + + + Performance counter type. + + + + + The value by which to increment the counter. + + + + + Performance counter instance name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Default filter to be applied when no specific rule matches. + + + + + + + + + + + + + Condition to be tested. + + + + + Resulting filter to be applied when the condition matches. + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + Name of the target. + + + + + Number of times to repeat each log message. + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of retries that should be attempted on the wrapped target in case of a failure. + + + + + Time to wait between retries in milliseconds. + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + Name of the target. + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. + + + + + Encoding. + + + + + Web service method name. Only used with Soap. + + + + + Web service namespace. Only used with Soap. + + + + + Protocol to be used when calling web service. + + + + + Web service URL. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). + + + + + Column delimiter. + + + + + Quote Character. + + + + + Quoting mode. + + + + + Indicates whether CVS should include header. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout of the column. + + + + + Name of the column. + + + + + + + + + + + + + Option to suppress the extra spaces in the output json + + + + + + + + + + + + + + Determines wether or not this attribute will be Json encoded. + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + + + + + + + + + + + + + + + + + Layout text. + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Condition expression. + + + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FFXIVClassic World Server/PacketProcessor.cs b/FFXIVClassic World Server/PacketProcessor.cs new file mode 100644 index 00000000..34793e38 --- /dev/null +++ b/FFXIVClassic World Server/PacketProcessor.cs @@ -0,0 +1,145 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using FFXIVClassic_World_Server.Packets.Receive; +using FFXIVClassic_World_Server.Packets.Send; +using FFXIVClassic_World_Server.Packets.Send.Login; +using FFXIVClassic_World_Server.Packets.WorldPackets.Receive; +using FFXIVClassic_World_Server.Packets.WorldPackets.Send; +using System; +using System.Collections.Generic; +using System.IO; + +namespace FFXIVClassic_World_Server +{ + class PacketProcessor + { + /* + Session Creation: + + Get 0x1 from server + Send 0x7 + Send 0x2 + + Zone Change: + + Send 0x7 + Get 0x8 - Wait?? + Send 0x2 + */ + + + Server mServer; + + public PacketProcessor(Server server) + { + mServer = server; + } + + public void ProcessPacket(ClientConnection client, BasePacket packet) + { + if (packet.header.isCompressed == 0x01) + BasePacket.DecompressPacket(ref packet); + + List subPackets = packet.GetSubpackets(); + foreach (SubPacket subpacket in subPackets) + { + //Initial Connect Packet, Create session + if (subpacket.header.type == 0x01) + { + HelloPacket hello = new HelloPacket(packet.data); + + if (packet.header.connectionType == BasePacket.TYPE_ZONE) + { + mServer.AddSession(client, Session.Channel.ZONE, hello.sessionId); + mServer.GetWorldManager().DoLogin(mServer.GetSession(hello.sessionId)); + } + else if (packet.header.connectionType == BasePacket.TYPE_CHAT) + mServer.AddSession(client, Session.Channel.CHAT, hello.sessionId); + + client.QueuePacket(_0x7Packet.BuildPacket(0x0E016EE5), true, false); + client.QueuePacket(_0x2Packet.BuildPacket(hello.sessionId), true, false); + } + //Ping from World Server + else if (subpacket.header.type == 0x07) + { + SubPacket init = _0x8PingPacket.BuildPacket(client.owner.sessionId); + client.QueuePacket(BasePacket.CreatePacket(init, true, false)); + } + //Zoning Related + else if (subpacket.header.type == 0x08) + { + //Response, client's current [actorID][time] + //BasePacket init = Login0x7ResponsePacket.BuildPacket(BitConverter.ToUInt32(packet.data, 0x10), Utils.UnixTimeStampUTC(), 0x07); + //client.QueuePacket(init); + packet.DebugPrintPacket(); + } + //Game Message + else if (subpacket.header.type == 0x03) + { + //Send to the correct zone server + uint targetSession = subpacket.header.targetId; + + if (mServer.GetSession(targetSession).routing1 != null) + mServer.GetSession(targetSession).routing1.SendPacket(subpacket); + + if (mServer.GetSession(targetSession).routing2 != null) + mServer.GetSession(targetSession).routing2.SendPacket(subpacket); + } + //World Server Type + else if (subpacket.header.type >= 0x1000) + { + uint targetSession = subpacket.header.targetId; + Session session = mServer.GetSession(targetSession); + + switch (subpacket.header.type) + { + //Session Begin Confirm + case 0x1000: + SessionBeginConfirmPacket beginConfirmPacket = new SessionBeginConfirmPacket(packet.data); + + if (beginConfirmPacket.invalidPacket || beginConfirmPacket.errorCode == 0) + Program.Log.Error("Session {0} had a error beginning session.", beginConfirmPacket.sessionId); + + break; + //Session End Confirm + case 0x1001: + SessionEndConfirmPacket endConfirmPacket = new SessionEndConfirmPacket(packet.data); + + if (!endConfirmPacket.invalidPacket && endConfirmPacket.errorCode != 0) + { + //Check destination, if != 0, update route and start new session + if (endConfirmPacket.destinationZone != 0) + { + session.routing1 = Server.GetServer().GetWorldManager().GetZoneServer(endConfirmPacket.destinationZone); + session.routing1.SendSessionStart(session); + } + else + { + mServer.RemoveSession(Session.Channel.ZONE, endConfirmPacket.sessionId); + mServer.RemoveSession(Session.Channel.CHAT, endConfirmPacket.sessionId); + } + } + else + Program.Log.Error("Session {0} had an error ending session.", endConfirmPacket.sessionId); + + break; + //Zone Change Request + case 0x1002: + WorldRequestZoneChangePacket zoneChangePacket = new WorldRequestZoneChangePacket(packet.data); + + if (!zoneChangePacket.invalidPacket) + { + mServer.GetWorldManager().DoZoneServerChange(session, zoneChangePacket.destinationZoneId, "", zoneChangePacket.destinationSpawnType, zoneChangePacket.destinationX, zoneChangePacket.destinationY, zoneChangePacket.destinationZ, zoneChangePacket.destinationRot); + } + + break; + } + + } + else + packet.DebugPrintPacket(); + } + } + + } +} diff --git a/FFXIVClassic World Server/Packets/Receive/HelloPacket.cs b/FFXIVClassic World Server/Packets/Receive/HelloPacket.cs new file mode 100644 index 00000000..6f2992e5 --- /dev/null +++ b/FFXIVClassic World Server/Packets/Receive/HelloPacket.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.Receive +{ + class HelloPacket + { + public bool invalidPacket = false; + public uint sessionId; + + public HelloPacket(byte[] data) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryReader binReader = new BinaryReader(mem)) + { + try + { + byte[] readIn = new byte[12]; + binReader.BaseStream.Seek(0x14, SeekOrigin.Begin); + binReader.Read(readIn, 0, 12); + sessionId = UInt32.Parse(Encoding.ASCII.GetString(readIn)); + } + catch (Exception) + { + invalidPacket = true; + } + } + } + } + } +} diff --git a/FFXIVClassic World Server/Packets/Send/_0x2Packet.cs b/FFXIVClassic World Server/Packets/Send/_0x2Packet.cs new file mode 100644 index 00000000..fd8e623d --- /dev/null +++ b/FFXIVClassic World Server/Packets/Send/_0x2Packet.cs @@ -0,0 +1,48 @@ +using FFXIVClassic.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.Send +{ + class _0x2Packet + { + public const ushort OPCODE = 0x0002; + public const uint PACKET_SIZE = 0x38; + + public static SubPacket BuildPacket(uint actorID) + { + byte[] data = new byte[PACKET_SIZE]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + try + { + binWriter.Write((UInt32)actorID); + } + catch (Exception) + { } + } + } + + byte[] reply2Data = { + 0x6c, 0x00, 0x00, 0x00, 0xC8, 0xD6, 0xAF, 0x2B, 0x38, 0x2B, 0x5F, 0x26, 0xB8, 0x8D, 0xF0, 0x2B, + 0xC8, 0xFD, 0x85, 0xFE, 0xA8, 0x7C, 0x5B, 0x09, 0x38, 0x2B, 0x5F, 0x26, 0xC8, 0xD6, 0xAF, 0x2B, + 0xB8, 0x8D, 0xF0, 0x2B, 0x88, 0xAF, 0x5E, 0x26 + }; + + /* + 0x6c, 0x00, 0x00, 0x00, 0xC8, 0xD6, 0xAF, 0x2B, 0x38, 0x2B, 0x5F, 0x26, 0xB8, 0x8D, 0xF0, 0x2B, + 0xC8, 0xFD, 0x85, 0xFE, 0xA8, 0x7C, 0x5B, 0x09, 0x38, 0x2B, 0x5F, 0x26, 0xC8, 0xD6, 0xAF, 0x2B, + 0xB8, 0x8D, 0xF0, 0x2B, 0x88, 0xAF, 0x5E, 0x26 + */ + + return new SubPacket(false, OPCODE, 0, 0, reply2Data); + } + } +} diff --git a/FFXIVClassic World Server/Packets/Send/_0x7Packet.cs b/FFXIVClassic World Server/Packets/Send/_0x7Packet.cs new file mode 100644 index 00000000..f0854229 --- /dev/null +++ b/FFXIVClassic World Server/Packets/Send/_0x7Packet.cs @@ -0,0 +1,37 @@ +using FFXIVClassic.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.Send +{ + class _0x7Packet + { + public const ushort OPCODE = 0x0007; + public const uint PACKET_SIZE = 0x18; + + public static SubPacket BuildPacket(uint actorID) + { + byte[] data = new byte[PACKET_SIZE]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + try + { + binWriter.Write((UInt32)actorID); + binWriter.Write((UInt32)Utils.UnixTimeStampUTC()); + } + catch (Exception) + { } + } + } + + return new SubPacket(false, OPCODE, 0, 0, data); + } + } +} diff --git a/FFXIVClassic World Server/Packets/Send/_0x8PingPacket.cs b/FFXIVClassic World Server/Packets/Send/_0x8PingPacket.cs new file mode 100644 index 00000000..21061c7e --- /dev/null +++ b/FFXIVClassic World Server/Packets/Send/_0x8PingPacket.cs @@ -0,0 +1,33 @@ +using FFXIVClassic.Common; +using System; +using System.IO; + +namespace FFXIVClassic_World_Server.Packets.Send.Login +{ + class _0x8PingPacket + { + public const ushort OPCODE = 0x0008; + public const uint PACKET_SIZE = 0x18; + + public static SubPacket BuildPacket(uint actorID) + { + byte[] data = new byte[PACKET_SIZE]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + try + { + binWriter.Write((UInt32)actorID); + binWriter.Write((UInt32)Utils.UnixTimeStampUTC()); + } + catch (Exception) + {} + } + } + + return new SubPacket(false, OPCODE, 0, 0, data); + } + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Receive/SessionBeginConfirmPacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Receive/SessionBeginConfirmPacket.cs new file mode 100644 index 00000000..189865c6 --- /dev/null +++ b/FFXIVClassic World Server/Packets/WorldPackets/Receive/SessionBeginConfirmPacket.cs @@ -0,0 +1,38 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.WorldPackets.Receive +{ + class SessionBeginConfirmPacket + { + public bool invalidPacket = false; + public uint sessionId; + public ushort errorCode; + + public SessionBeginConfirmPacket(byte[] data) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryReader binReader = new BinaryReader(mem)) + { + try + { + sessionId = binReader.ReadUInt32(); + errorCode = binReader.ReadUInt16(); + } + catch (Exception) + { + invalidPacket = true; + } + } + } + } + + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Receive/SessionEndConfirmPacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Receive/SessionEndConfirmPacket.cs new file mode 100644 index 00000000..b0e22aec --- /dev/null +++ b/FFXIVClassic World Server/Packets/WorldPackets/Receive/SessionEndConfirmPacket.cs @@ -0,0 +1,39 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.WorldPackets.Receive +{ + class SessionEndConfirmPacket + { + public bool invalidPacket = false; + public uint sessionId; + public ushort errorCode; + public uint destinationZone; + + public SessionEndConfirmPacket(byte[] data) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryReader binReader = new BinaryReader(mem)) + { + try + { + sessionId = binReader.ReadUInt32(); + errorCode = binReader.ReadUInt16(); + destinationZone = binReader.ReadUInt32(); + } + catch (Exception) + { + invalidPacket = true; + } + } + } + } + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Receive/WorldRequestZoneChangePacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Receive/WorldRequestZoneChangePacket.cs new file mode 100644 index 00000000..faa2e4b4 --- /dev/null +++ b/FFXIVClassic World Server/Packets/WorldPackets/Receive/WorldRequestZoneChangePacket.cs @@ -0,0 +1,49 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.WorldPackets.Receive +{ + class WorldRequestZoneChangePacket + { + public uint sessionId; + public uint destinationZoneId; + public byte destinationSpawnType; + public float destinationX; + public float destinationY; + public float destinationZ; + public float destinationRot; + + public bool invalidPacket = false; + + public WorldRequestZoneChangePacket(byte[] data) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryReader binReader = new BinaryReader(mem)) + { + try + { + sessionId = binReader.ReadUInt32(); + destinationZoneId = binReader.ReadUInt32(); + destinationSpawnType = (byte)binReader.ReadUInt16(); + destinationX = binReader.ReadSingle(); + destinationY = binReader.ReadSingle(); + destinationZ = binReader.ReadSingle(); + destinationRot = binReader.ReadSingle(); + } + catch (Exception) + { + invalidPacket = true; + } + } + } + + } + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Send/ErrorPacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Send/ErrorPacket.cs new file mode 100644 index 00000000..2a7d4c4f --- /dev/null +++ b/FFXIVClassic World Server/Packets/WorldPackets/Send/ErrorPacket.cs @@ -0,0 +1,37 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.WorldPackets.Send +{ + class ErrorPacket + { + public const ushort OPCODE = 0x100A; + public const uint PACKET_SIZE = 0x24; + + public static SubPacket BuildPacket(Session session, uint errorCode) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + try + { + binWriter.Write((UInt32)errorCode); + } + catch (Exception) + { } + } + } + + return new SubPacket(true, OPCODE, 0, session.sessionId, data); + } + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Send/SessionBeginPacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Send/SessionBeginPacket.cs new file mode 100644 index 00000000..686aceeb --- /dev/null +++ b/FFXIVClassic World Server/Packets/WorldPackets/Send/SessionBeginPacket.cs @@ -0,0 +1,24 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.WorldPackets.Send +{ + class SessionBeginPacket + { + public const ushort OPCODE = 0x1000; + public const uint PACKET_SIZE = 0x24; + + public static SubPacket BuildPacket(Session session) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + return new SubPacket(true, OPCODE, 0, session.sessionId, data); + } + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Send/SessionEndPacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Send/SessionEndPacket.cs new file mode 100644 index 00000000..868f65bd --- /dev/null +++ b/FFXIVClassic World Server/Packets/WorldPackets/Send/SessionEndPacket.cs @@ -0,0 +1,63 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.WorldPackets.Send +{ + class SessionEndPacket + { + public const ushort OPCODE = 0x1001; + public const uint PACKET_SIZE = 0x38; + + public static SubPacket BuildPacket(Session session) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + try + { + binWriter.Write((UInt32)0); + } + catch (Exception) + { } + } + } + + return new SubPacket(true, OPCODE, 0, session.sessionId, data); + } + + public static SubPacket BuildPacket(Session session, uint destinationZoneId, string destinationPrivateArea, byte spawnType, float spawnX, float spawnY, float spawnZ, float spawnRotation) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + try + { + binWriter.Write((UInt32)destinationZoneId); + binWriter.Write((UInt16)spawnType); + binWriter.Write((Single)spawnX); + binWriter.Write((Single)spawnY); + binWriter.Write((Single)spawnZ); + binWriter.Write((Single)spawnRotation); + + } + catch (Exception) + { } + } + } + + return new SubPacket(true, OPCODE, 0, session.sessionId, data); + } + } +} diff --git a/FFXIVClassic World Server/Program.cs b/FFXIVClassic World Server/Program.cs new file mode 100644 index 00000000..931a51c8 --- /dev/null +++ b/FFXIVClassic World Server/Program.cs @@ -0,0 +1,78 @@ +using MySql.Data.MySqlClient; +using NLog; +using NLog.Fluent; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server +{ + class Program + { + public static Logger Log; + + static void Main(string[] args) + { + // set up logging + Log = LogManager.GetCurrentClassLogger(); +#if DEBUG + TextWriterTraceListener myWriter = new TextWriterTraceListener(System.Console.Out); + Debug.Listeners.Add(myWriter); + + if (System.Diagnostics.Debugger.IsAttached) + { + System.Threading.Thread.Sleep(15000); + } + +#endif + bool startServer = true; + + Log.Info("=================================="); + Log.Info("FFXIV Classic World Server"); + Log.Info("Version: 0.0.1"); + Log.Info("=================================="); + + //Load Config + if (!ConfigConstants.Load()) + startServer = false; + + //Test DB Connection + Log.Info("Testing DB connection... "); + 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 + { + conn.Open(); + conn.Close(); + + Log.Info("Connection ok."); + } + catch (MySqlException e) + { + Log.Error(e.ToString()); + startServer = false; + } + } + + //Start server if A-OK + if (startServer) + { + Server server = new Server(); + server.StartServer(); + + while (startServer) + { + String input = Console.ReadLine(); + Log.Info("[Console Input] " + input); + //cp.DoCommand(input, null); + } + } + + Program.Log.Info("Press any key to continue..."); + Console.ReadKey(); + } + } +} diff --git a/FFXIVClassic World Server/Properties/AssemblyInfo.cs b/FFXIVClassic World Server/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2bb7a8a3 --- /dev/null +++ b/FFXIVClassic World Server/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FFXIVClassic Proxy Server")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FFXIVClassic Proxy Server")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3067889d-8a50-40d6-9cd5-23aa8ea96f26")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/FFXIVClassic World Server/Server.cs b/FFXIVClassic World Server/Server.cs new file mode 100644 index 00000000..c010faef --- /dev/null +++ b/FFXIVClassic World Server/Server.cs @@ -0,0 +1,341 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using FFXIVClassic_World_Server.Packets.WorldPackets.Receive; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace FFXIVClassic_World_Server +{ + class Server + { + public const int FFXIV_MAP_PORT = 54992; + public const int BUFFER_SIZE = 0xFFFF; //Max basepacket size is 0xFFFF + public const int BACKLOG = 100; + public const int HEALTH_THREAD_SLEEP_TIME = 5; + + private static Server mSelf; + + private Socket mServerSocket; + + WorldManager mWorldManager; + PacketProcessor mPacketProcessor; + + private List mConnectionList = new List(); + private Dictionary mZoneSessionList = new Dictionary(); + private Dictionary mChatSessionList = new Dictionary(); + + public Server() + { + mSelf = this; + } + + public static Server GetServer() + { + return mSelf; + } + + public bool StartServer() + { + mPacketProcessor = new PacketProcessor(this); + mWorldManager = new WorldManager(this); + mWorldManager.LoadZoneServerList(); + mWorldManager.LoadZoneEntranceList(); + mWorldManager.ConnectToZoneServers(); + + IPEndPoint serverEndPoint = new System.Net.IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT)); + + try + { + mServerSocket = new System.Net.Sockets.Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + } + catch (Exception e) + { + throw new ApplicationException("Could not Create socket, check to make sure not duplicating port", e); + } + try + { + mServerSocket.Bind(serverEndPoint); + mServerSocket.Listen(BACKLOG); + } + catch (Exception e) + { + throw new ApplicationException("Error occured while binding socket, check inner exception", e); + } + try + { + mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + } + catch (Exception e) + { + throw new ApplicationException("Error occured starting listeners, check inner exception", e); + } + + Console.ForegroundColor = ConsoleColor.White; + Program.Log.Info("World Server accepting connections @ {0}:{1}", (mServerSocket.LocalEndPoint as IPEndPoint).Address, (mServerSocket.LocalEndPoint as IPEndPoint).Port); + Console.ForegroundColor = ConsoleColor.Gray; + + return true; + } + + public void AddSession(ClientConnection connection, Session.Channel type, uint id) + { + Session session = new Session(id, connection, type); + + switch (type) + { + case Session.Channel.ZONE: + if (!mZoneSessionList.ContainsKey(id)) + mZoneSessionList.Add(id, session); + break; + case Session.Channel.CHAT: + if (!mChatSessionList.ContainsKey(id)) + mChatSessionList.Add(id, session); + break; + } + } + + public void RemoveSession(Session.Channel type, uint id) + { + switch (type) + { + case Session.Channel.ZONE: + if (mZoneSessionList.ContainsKey(id)) + { + mZoneSessionList[id].clientConnection.Disconnect(); + mConnectionList.Remove(mZoneSessionList[id].clientConnection); + mZoneSessionList.Remove(id); + } + break; + case Session.Channel.CHAT: + if (mChatSessionList.ContainsKey(id)) + { + mChatSessionList[id].clientConnection.Disconnect(); + mConnectionList.Remove(mChatSessionList[id].clientConnection); + mChatSessionList.Remove(id); + } + break; + } + } + + public Session GetSession(uint targetSession, Session.Channel type = Session.Channel.ZONE) + { + switch (type) + { + case Session.Channel.ZONE: + if (mZoneSessionList.ContainsKey(targetSession)) + return mZoneSessionList[targetSession]; + break; + case Session.Channel.CHAT: + if (mChatSessionList.ContainsKey(targetSession)) + return mChatSessionList[targetSession]; + break; + } + + return null; + } + + public void OnReceiveSubPacketFromZone(ZoneServer zoneServer, SubPacket subpacket) + { + uint sessionId = subpacket.header.targetId; + + if (subpacket.gameMessage.opcode >= 0x1000) + { + subpacket.DebugPrintSubPacket(); + uint targetSession = subpacket.header.targetId; + Session session = GetSession(targetSession); + + switch (subpacket.gameMessage.opcode) + { + //Session Begin Confirm + case 0x1000: + SessionBeginConfirmPacket beginConfirmPacket = new SessionBeginConfirmPacket(subpacket.data); + + if (beginConfirmPacket.invalidPacket || beginConfirmPacket.errorCode == 0) + Program.Log.Error("Session {0} had a error beginning session.", beginConfirmPacket.sessionId); + + break; + //Session End Confirm + case 0x1001: + SessionEndConfirmPacket endConfirmPacket = new SessionEndConfirmPacket(subpacket.data); + + if (!endConfirmPacket.invalidPacket && endConfirmPacket.errorCode == 0) + { + //Check destination, if != 0, update route and start new session + if (endConfirmPacket.destinationZone != 0) + { + session.routing1 = Server.GetServer().GetWorldManager().GetZoneServer(endConfirmPacket.destinationZone); + session.routing1.SendSessionStart(session); + } + else + { + RemoveSession(Session.Channel.ZONE, endConfirmPacket.sessionId); + RemoveSession(Session.Channel.CHAT, endConfirmPacket.sessionId); + } + } + else + Program.Log.Error("Session {0} had an error ending session.", endConfirmPacket.sessionId); + + break; + //Zone Change Request + case 0x1002: + WorldRequestZoneChangePacket zoneChangePacket = new WorldRequestZoneChangePacket(subpacket.data); + + if (!zoneChangePacket.invalidPacket) + { + GetWorldManager().DoZoneServerChange(session, zoneChangePacket.destinationZoneId, "", zoneChangePacket.destinationSpawnType, zoneChangePacket.destinationX, zoneChangePacket.destinationY, zoneChangePacket.destinationZ, zoneChangePacket.destinationRot); + } + + break; + } + } + + if (mZoneSessionList.ContainsKey(sessionId)) + { + ClientConnection conn = mZoneSessionList[sessionId].clientConnection; + conn.QueuePacket(subpacket, true, false); + conn.FlushQueuedSendPackets(); + } + + } + + public WorldManager GetWorldManager() + { + return mWorldManager; + } + + #region Socket Handling + private void AcceptCallback(IAsyncResult result) + { + ClientConnection conn = null; + Socket socket = (System.Net.Sockets.Socket)result.AsyncState; + + try + { + conn = new ClientConnection(); + conn.socket = socket.EndAccept(result); + conn.buffer = new byte[BUFFER_SIZE]; + + lock (mConnectionList) + { + mConnectionList.Add(conn); + } + + Program.Log.Info("Connection {0}:{1} has connected.", (conn.socket.RemoteEndPoint as IPEndPoint).Address, (conn.socket.RemoteEndPoint as IPEndPoint).Port); + //Queue recieving of data from the connection + conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn); + //Queue the accept of the next incomming connection + mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + } + catch (SocketException) + { + if (conn != null) + { + + lock (mConnectionList) + { + mConnectionList.Remove(conn); + } + } + mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + } + catch (Exception) + { + if (conn != null) + { + lock (mConnectionList) + { + mConnectionList.Remove(conn); + } + } + mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + } + } + + /// + /// Receive Callback. Reads in incoming data, converting them to base packets. Base packets are sent to be parsed. If not enough data at the end to build a basepacket, move to the beginning and prepend. + /// + /// + private void ReceiveCallback(IAsyncResult result) + { + ClientConnection conn = (ClientConnection)result.AsyncState; + + //Check if disconnected + if ((conn.socket.Poll(1, SelectMode.SelectRead) && conn.socket.Available == 0)) + { + lock (mConnectionList) + { + mConnectionList.Remove(conn); + } + + return; + } + + try + { + int bytesRead = conn.socket.EndReceive(result); + + bytesRead += conn.lastPartialSize; + + if (bytesRead >= 0) + { + int offset = 0; + + //Build packets until can no longer or out of data + while (true) + { + BasePacket basePacket = BasePacket.CreatePacket(ref offset, conn.buffer, bytesRead); + + //If can't build packet, break, else process another + if (basePacket == null) + break; + else + { + mPacketProcessor.ProcessPacket(conn, basePacket); + } + + } + + //Not all bytes consumed, transfer leftover to beginning + if (offset < bytesRead) + Array.Copy(conn.buffer, offset, conn.buffer, 0, 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 + { + + lock (mConnectionList) + { + mConnectionList.Remove(conn); + } + } + } + catch (SocketException) + { + if (conn.socket != null) + { + + lock (mConnectionList) + { + mConnectionList.Remove(conn); + } + } + } + } + + #endregion + + } +} diff --git a/FFXIVClassic World Server/WorldMaster.cs b/FFXIVClassic World Server/WorldMaster.cs new file mode 100644 index 00000000..22ebd24a --- /dev/null +++ b/FFXIVClassic World Server/WorldMaster.cs @@ -0,0 +1,211 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects; +using FFXIVClassic_World_Server.Packets.WorldPackets.Send; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server +{ + class WorldManager + { + private Server mServer; + public Dictionary mZoneServerList; + private Dictionary zoneEntranceList; + + public WorldManager(Server server) + { + mServer = server; + } + + public void LoadZoneServerList() + { + mZoneServerList = new Dictionary(); + + 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 + { + conn.Open(); + + string query = @" + SELECT + id, + serverIp, + serverPort + FROM server_zones + WHERE serverIp IS NOT NULL"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + uint id = reader.GetUInt32(0); + string ip = reader.GetString(1); + int port = reader.GetInt32(2); + string address = ip + ":" + port; + + if (!mZoneServerList.ContainsKey(address)) + { + ZoneServer zone = new ZoneServer(ip, port, id); + mZoneServerList.Add(address, zone); + } + else + mZoneServerList[address].AddLoadedZone(id); + } + } + } + catch (MySqlException e) + { Console.WriteLine(e); } + finally + { + conn.Dispose(); + } + } + + } + + public void LoadZoneEntranceList() + { + zoneEntranceList = new Dictionary(); + int count = 0; + 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 + { + conn.Open(); + + string query = @" + SELECT + id, + zoneId, + spawnType, + spawnX, + spawnY, + spawnZ, + spawnRotation, + privateAreaName + FROM server_zones_spawnlocations"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + uint id = reader.GetUInt32(0); + string privArea = null; + + if (!reader.IsDBNull(7)) + privArea = reader.GetString(7); + + ZoneEntrance entance = new ZoneEntrance(reader.GetUInt32(1), privArea, reader.GetByte(2), reader.GetFloat(3), reader.GetFloat(4), reader.GetFloat(5), reader.GetFloat(6)); + zoneEntranceList[id] = entance; + count++; + } + } + } + catch (MySqlException e) + { Console.WriteLine(e); } + finally + { + conn.Dispose(); + } + } + + Program.Log.Info(String.Format("Loaded {0} zone spawn locations.", count)); + } + + public void ConnectToZoneServers() + { + Program.Log.Info("--------------------------"); + Program.Log.Info("Connecting to zone servers"); + Program.Log.Info("--------------------------"); + + foreach (ZoneServer zs in mZoneServerList.Values) + { + zs.Connect(); + } + } + + public ZoneServer GetZoneServer(uint zoneId) + { + foreach (ZoneServer zs in mZoneServerList.Values) + { + if (zs.ownedZoneIds.Contains(zoneId)) + return zs; + } + + return null; + } + + //Moves the actor to the new zone if exists. No packets are sent nor position changed. + public void DoSeamlessZoneServerChange(Session session, uint destinationZoneId) + { + + } + + //Moves actor to new zone, and sends packets to spawn at the given zone entrance + public void DoZoneServerChange(Session session, uint zoneEntrance) + { + if (!zoneEntranceList.ContainsKey(zoneEntrance)) + { + Program.Log.Error("Given zone entrance was not found: " + zoneEntrance); + return; + } + + ZoneEntrance ze = zoneEntranceList[zoneEntrance]; + DoZoneServerChange(session, ze.zoneId, ze.privateAreaName, ze.spawnType, ze.spawnX, ze.spawnY, ze.spawnZ, ze.spawnRotation); + } + + //Moves actor to new zone, and sends packets to spawn at the given coords. + public void DoZoneServerChange(Session session, uint destinationZoneId, string destinationPrivateArea, byte spawnType, float spawnX, float spawnY, float spawnZ, float spawnRotation) + { + ZoneServer zs = GetZoneServer(destinationZoneId); + if (zs.isConnected) + session.routing1.SendSessionEnd(session, destinationZoneId, destinationPrivateArea, spawnType, spawnX, spawnY, spawnZ, spawnRotation); + else if (zs.Connect()) + session.routing1.SendSessionEnd(session, destinationZoneId, destinationPrivateArea, spawnType, spawnX, spawnY, spawnZ, spawnRotation); + else + session.routing1.SendPacket(ErrorPacket.BuildPacket(session, 1)); + } + + //Login Zone In + public void DoLogin(Session session) + { + session.routing1 = GetZoneServer(Database.GetCurrentZoneForSession(session.sessionId)); + session.routing1.SendSessionStart(session); + } + + public class ZoneEntrance + { + public uint zoneId; + public string privateAreaName; + public byte spawnType; + public float spawnX; + public float spawnY; + public float spawnZ; + public float spawnRotation; + + public ZoneEntrance(uint zoneId, string privateAreaName, byte spawnType, float x, float y, float z, float rot) + { + this.zoneId = zoneId; + this.privateAreaName = privateAreaName; + this.spawnType = spawnType; + this.spawnX = x; + this.spawnY = y; + this.spawnZ = z; + this.spawnRotation = rot; + } + } + + } + +} diff --git a/FFXIVClassic World Server/packages.config b/FFXIVClassic World Server/packages.config new file mode 100644 index 00000000..6de55267 --- /dev/null +++ b/FFXIVClassic World Server/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/FFXIVClassic.sln b/FFXIVClassic.sln index 5cb0528a..b79d9e72 100644 --- a/FFXIVClassic.sln +++ b/FFXIVClassic.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFXIVClassic Map Server", "FFXIVClassic Map Server\FFXIVClassic Map Server.csproj", "{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}" ProjectSection(ProjectDependencies) = postProject @@ -15,6 +15,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFXIVClassic Lobby Server", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFXIVClassic Common Class Lib", "FFXIVClassic Common Class Lib\FFXIVClassic Common Class Lib.csproj", "{3A3D6626-C820-4C18-8C81-64811424F20E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFXIVClassic World Server", "FFXIVClassic World Server\FFXIVClassic World Server.csproj", "{3067889D-8A50-40D6-9CD5-23AA8EA96F26}" + ProjectSection(ProjectDependencies) = postProject + {3A3D6626-C820-4C18-8C81-64811424F20E} = {3A3D6626-C820-4C18-8C81-64811424F20E} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +38,10 @@ Global {3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A3D6626-C820-4C18-8C81-64811424F20E}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A3D6626-C820-4C18-8C81-64811424F20E}.Release|Any CPU.Build.0 = Release|Any CPU + {3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/data/world_config.ini b/data/world_config.ini new file mode 100644 index 00000000..2251736f --- /dev/null +++ b/data/world_config.ini @@ -0,0 +1,11 @@ +[General] +server_ip=127.0.0.1 +showtimestamp = true + +[Database] +worldid=1 +host=127.0.0.1 +port=3306 +database=ffxiv_server +username=root +password=