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=