diff --git a/FFXIVClassic_Lobby_Server/ClientConnection.cs b/FFXIVClassic_Lobby_Server/ClientConnection.cs index e326b3e1..771dd3df 100644 --- a/FFXIVClassic_Lobby_Server/ClientConnection.cs +++ b/FFXIVClassic_Lobby_Server/ClientConnection.cs @@ -26,6 +26,7 @@ namespace FFXIVClassic_Lobby_Server //Instance Stuff public uint currentUserId = 0; public uint currentAccount; + public string currentSessionToken; //Chara Creation public string newCharaName; diff --git a/FFXIVClassic_Lobby_Server/Database.cs b/FFXIVClassic_Lobby_Server/Database.cs index b3d7fe43..7e6f76de 100644 --- a/FFXIVClassic_Lobby_Server/Database.cs +++ b/FFXIVClassic_Lobby_Server/Database.cs @@ -7,10 +7,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using FFXIVClassic_Lobby_Server.common; namespace FFXIVClassic_Lobby_Server { - //charState: 0 - Reserved, 1 - Deleted, 2 - Inactive, 3 - Active + //charState: 0 - Reserved, 1 - Inactive, 2 - Active class Database { @@ -37,7 +38,7 @@ namespace FFXIVClassic_Lobby_Server finally { conn.Dispose(); - } + } } return id; } @@ -93,6 +94,8 @@ namespace FFXIVClassic_Lobby_Server { conn.Dispose(); } + + Log.database(String.Format("CID={0} created on 'characters' table.", cid)); } return alreadyExists; @@ -107,7 +110,7 @@ namespace FFXIVClassic_Lobby_Server conn.Open(); MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; - cmd.CommandText = "UPDATE characters SET state=3, charaInfo=@encodedInfo WHERE userId=@userId AND id=@cid"; + cmd.CommandText = "UPDATE characters SET state=2, charaInfo=@encodedInfo WHERE userId=@userId AND id=@cid"; cmd.Prepare(); cmd.Parameters.AddWithValue("@userId", accountId); @@ -125,6 +128,8 @@ namespace FFXIVClassic_Lobby_Server { conn.Dispose(); } + + Log.database(String.Format("CID={0} state updated to active(2).", cid)); } } @@ -167,6 +172,8 @@ namespace FFXIVClassic_Lobby_Server conn.Dispose(); } + Log.database(String.Format("CID={0} name updated to \"{1}\".", characterId, newName)); + return false; } } @@ -196,6 +203,8 @@ namespace FFXIVClassic_Lobby_Server conn.Dispose(); } } + + Log.database(String.Format("CID={0} deleted.", characterId)); } public static List getServers() diff --git a/FFXIVClassic_Lobby_Server/FFXIVClassic_Lobby_Server.csproj b/FFXIVClassic_Lobby_Server/FFXIVClassic_Lobby_Server.csproj index e30d33c2..524b1b38 100644 --- a/FFXIVClassic_Lobby_Server/FFXIVClassic_Lobby_Server.csproj +++ b/FFXIVClassic_Lobby_Server/FFXIVClassic_Lobby_Server.csproj @@ -58,6 +58,7 @@ + @@ -74,9 +75,11 @@ + + diff --git a/FFXIVClassic_Lobby_Server/PacketProcessor.cs b/FFXIVClassic_Lobby_Server/PacketProcessor.cs index 6ed7b0ed..33066905 100644 --- a/FFXIVClassic_Lobby_Server/PacketProcessor.cs +++ b/FFXIVClassic_Lobby_Server/PacketProcessor.cs @@ -84,8 +84,8 @@ namespace FFXIVClassic_Lobby_Server List subPackets = packet.getSubpackets(); foreach (SubPacket subpacket in subPackets) - { - + { + subpacket.debugPrintSubPacket(); switch (subpacket.header.opcode) { case 0x03: @@ -127,27 +127,39 @@ namespace FFXIVClassic_Lobby_Server private void ProcessSessionAcknowledgement(ClientConnection client, SubPacket packet) { - PacketStructs.SessionPacket sessionPacket = PacketStructs.toSessionStruct(packet.data); - String sessionId = sessionPacket.session; + PacketStructs.SessionPacket sessionPacket = PacketStructs.toSessionStruct(packet.data); String clientVersion = sessionPacket.version; - Log.info(String.Format("Got acknowledgment for secure session.")); - Log.info(String.Format("SESSION ID: {0}", sessionId)); + Log.info(String.Format("Got acknowledgment for secure session.")); Log.info(String.Format("CLIENT VERSION: {0}", clientVersion)); - uint userId = Database.getUserIdFromSession(sessionId); + uint userId = Database.getUserIdFromSession(sessionPacket.session); client.currentUserId = userId; + client.currentSessionToken = sessionPacket.session; ; if (userId == 0) { - //client.disconnect(); - Log.info(String.Format("Invalid session, kicking...")); + ErrorPacket errorPacket = new ErrorPacket(sessionPacket.sequence, 0, 0, 13001, "Your session has expired, please login again."); + SubPacket subpacket = errorPacket.buildPacket(); + BasePacket errorBasePacket = BasePacket.createPacket(subpacket, true, false); + BasePacket.encryptPacket(client.blowfish, errorBasePacket); + client.queuePacket(errorBasePacket); + + Log.info(String.Format("Invalid session, kicking...")); + return; } Log.info(String.Format("USER ID: {0}", userId)); - BasePacket outgoingPacket = new BasePacket("./packets/loginAck.bin"); - BasePacket.encryptPacket(client.blowfish, outgoingPacket); - client.queuePacket(outgoingPacket); + + List accountList = new List(); + Account defaultAccount = new Account(); + defaultAccount.id = 1; + defaultAccount.name = "FINAL FANTASY XIV"; + accountList.Add(defaultAccount); + AccountListPacket listPacket = new AccountListPacket(1, accountList); + BasePacket basePacket = BasePacket.createPacket(listPacket.buildPackets(), true, false); + BasePacket.encryptPacket(client.blowfish, basePacket); + client.queuePacket(basePacket); } private void ProcessGetCharacters(ClientConnection client, SubPacket packet) @@ -163,33 +175,29 @@ namespace FFXIVClassic_Lobby_Server private void ProcessSelectCharacter(ClientConnection client, SubPacket packet) { - uint characterId = 0; - using (BinaryReader binReader = new BinaryReader(new MemoryStream(packet.data))) + FFXIVClassic_Lobby_Server.packets.PacketStructs.SelectCharRequestPacket selectCharRequest = PacketStructs.toSelectCharRequestStruct(packet.data); + + Log.info(String.Format("{0} => Select character id {1}", client.currentUserId == 0 ? client.getAddress() : "User " + client.currentUserId, selectCharRequest.characterId)); + + Character chara = Database.getCharacter(client.currentUserId, selectCharRequest.characterId); + World world = null; + + if (chara != null) + world = Database.getServer(chara.serverId); + + if (world == null) { - binReader.BaseStream.Seek(0x8, SeekOrigin.Begin); - characterId = binReader.ReadUInt32(); - binReader.Close(); + ErrorPacket errorPacket = new ErrorPacket(selectCharRequest.sequence, 0, 0, 13001, "World does not exist or is inactive."); + SubPacket subpacket = errorPacket.buildPacket(); + BasePacket basePacket = BasePacket.createPacket(subpacket, true, false); + BasePacket.encryptPacket(client.blowfish, basePacket); + client.queuePacket(basePacket); + return; } - Log.info(String.Format("{0} => Select character id {1}", client.currentUserId == 0 ? client.getAddress() : "User " + client.currentUserId, characterId)); - - String serverIp = "141.117.161.40"; - ushort port = 54992; - BitConverter.GetBytes(port); - BasePacket outgoingPacket = new BasePacket("./packets/selectChar.bin"); - - //Write Character ID and Server info - using (BinaryWriter binWriter = new BinaryWriter(new MemoryStream(outgoingPacket.data))) - { - binWriter.Seek(0x28, SeekOrigin.Begin); - binWriter.Write(characterId); - binWriter.Seek(0x78, SeekOrigin.Begin); - binWriter.Write(System.Text.Encoding.ASCII.GetBytes(serverIp)); - binWriter.Seek(0x76, SeekOrigin.Begin); - binWriter.Write(port); - binWriter.Close(); - } + SelectCharacterConfirmPacket connectCharacter = new SelectCharacterConfirmPacket(selectCharRequest.sequence, selectCharRequest.characterId, client.currentSessionToken, world.address, world.port, selectCharRequest.ticket); + BasePacket outgoingPacket = BasePacket.createPacket(connectCharacter.buildPackets(), true, false); BasePacket.encryptPacket(client.blowfish, outgoingPacket); client.queuePacket(outgoingPacket); } diff --git a/FFXIVClassic_Lobby_Server/common/Blowfish.cs b/FFXIVClassic_Lobby_Server/common/Blowfish.cs index 366ba5e1..25eab806 100644 --- a/FFXIVClassic_Lobby_Server/common/Blowfish.cs +++ b/FFXIVClassic_Lobby_Server/common/Blowfish.cs @@ -8,11 +8,11 @@ namespace FFXIVClassic_Lobby_Server.common public class Blowfish { - [DllImport("../../../Debug/Blowfish.dll", CallingConvention = CallingConvention.Cdecl)] + [DllImport("./Blowfish.dll", CallingConvention = CallingConvention.Cdecl)] private static extern short initializeBlowfish(byte[] key, short keySize, uint[] P, uint[,] S); - [DllImport("../../../Debug/Blowfish.dll", CallingConvention = CallingConvention.Cdecl)] + [DllImport("./Blowfish.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void blowfish_encipher(ref int xl, ref int xr, uint[] P); - [DllImport("../../../Debug/Blowfish.dll", CallingConvention = CallingConvention.Cdecl)] + [DllImport("./Blowfish.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void blowfish_decipher(ref int xl, ref int xr, uint[] P); private uint[] P = new uint[16+2]; diff --git a/FFXIVClassic_Lobby_Server/dataobjects/Account.cs b/FFXIVClassic_Lobby_Server/dataobjects/Account.cs new file mode 100644 index 00000000..437b85ab --- /dev/null +++ b/FFXIVClassic_Lobby_Server/dataobjects/Account.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Lobby_Server.dataobjects +{ + class Account + { + public UInt32 id; + public string name; + } +} diff --git a/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs b/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs index 5609d829..765794c3 100644 --- a/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs +++ b/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs @@ -141,6 +141,14 @@ namespace FFXIVClassic_Lobby_Server.dataobjects { byte[] data; + mainHand = 79707136; + offHand = 32509954; + headGear = 43008; + bodyGear = 43008; + legsGear = 43008; + handsGear = 43008; + feetGear = 43008; + using (MemoryStream stream = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(stream)) @@ -240,8 +248,6 @@ namespace FFXIVClassic_Lobby_Server.dataobjects } data = stream.GetBuffer(); - - File.WriteAllBytes("./packets/out.bin",data); } return Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_'); diff --git a/FFXIVClassic_Lobby_Server/packets/AccountListPacket.cs b/FFXIVClassic_Lobby_Server/packets/AccountListPacket.cs new file mode 100644 index 00000000..743ace70 --- /dev/null +++ b/FFXIVClassic_Lobby_Server/packets/AccountListPacket.cs @@ -0,0 +1,99 @@ +using FFXIVClassic_Lobby_Server.dataobjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Lobby_Server.packets +{ + class AccountListPacket + { + public const ushort OPCODE = 0x0C; + public const ushort MAXPERPACKET = 8; + + private UInt64 sequence; + private List accountList; + + public AccountListPacket(UInt64 sequence, List accountList) + { + this.sequence = sequence; + this.accountList = accountList; + } + + public List buildPackets() + { + List subPackets = new List(); + + int accountCount = 0; + int totalCount = 0; + + MemoryStream memStream = null; + BinaryWriter binWriter = null; + + foreach (Account account in accountList) + { + if (totalCount == 0 || accountCount % MAXPERPACKET == 0) + { + memStream = new MemoryStream(0x280); + binWriter = new BinaryWriter(memStream); + + //Write List Info + binWriter.Write((UInt64)sequence); + byte listTracker = (byte)((MAXPERPACKET * 2) * subPackets.Count); + binWriter.Write(accountList.Count - totalCount <= MAXPERPACKET ? (byte)(listTracker + 1) : (byte)(listTracker)); + binWriter.Write(accountList.Count - totalCount <= MAXPERPACKET ? (UInt32)(accountList.Count - totalCount) : (UInt32)MAXPERPACKET); + binWriter.Write((byte)0); + binWriter.Write((UInt16)0); + } + + //Write Entries + binWriter.Write((UInt32)account.id); + binWriter.Write((UInt32)0); + binWriter.Write(Encoding.ASCII.GetBytes(account.name.PadRight(0x40, '\0'))); + + accountCount++; + totalCount++; + + //Send this chunk of world list + if (accountCount >= MAXPERPACKET) + { + byte[] data = memStream.GetBuffer(); + binWriter.Dispose(); + memStream.Dispose(); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + subPackets.Add(subpacket); + accountCount = 0; + } + + } + + //If there is anything left that was missed or the list is empty + if (accountCount > 0 || accountList.Count == 0) + { + if (accountList.Count == 0) + { + memStream = new MemoryStream(0x210); + binWriter = new BinaryWriter(memStream); + + //Write Empty List Info + binWriter.Write((UInt64)sequence); + byte listTracker = (byte)((MAXPERPACKET * 2) * subPackets.Count); + binWriter.Write(accountList.Count - totalCount <= MAXPERPACKET ? (byte)(listTracker + 1) : (byte)(listTracker)); + binWriter.Write((UInt32)0); + binWriter.Write((byte)0); + binWriter.Write((UInt16)0); + } + + byte[] data = memStream.GetBuffer(); + binWriter.Dispose(); + memStream.Dispose(); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + subPackets.Add(subpacket); + } + + return subPackets; + } + } +} diff --git a/FFXIVClassic_Lobby_Server/packets/PacketStructs.cs b/FFXIVClassic_Lobby_Server/packets/PacketStructs.cs index 76e31f4a..683954ca 100644 --- a/FFXIVClassic_Lobby_Server/packets/PacketStructs.cs +++ b/FFXIVClassic_Lobby_Server/packets/PacketStructs.cs @@ -13,13 +13,13 @@ namespace FFXIVClassic_Lobby_Server.packets public unsafe struct SessionPacket { [FieldOffset(0)] - public UInt64 sequence; + public UInt64 sequence; + [FieldOffset(0x10)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x40)] + public String session; [FieldOffset(0x50)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] - public String version; - [FieldOffset(0x70)] - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] - public String session; + public String version; } [StructLayout(LayoutKind.Sequential)] @@ -37,38 +37,13 @@ namespace FFXIVClassic_Lobby_Server.packets public String characterInfoEncoded; } - //Response Packets [StructLayout(LayoutKind.Sequential)] - public unsafe struct ReserveCharaResponse + public unsafe struct SelectCharRequestPacket { public UInt64 sequence; - public uint errorCode; - public uint statusCode; - public uint errorId; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x2BB)] - public String errorMessage; - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct MakeCharaResponse - { - public UInt64 sequence; - public uint errorCode; - public uint statusCode; - public uint errorId; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x2BB)] - public String errorMessage; - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct DeleteCharaResponse - { - public UInt64 sequence; - public uint errorCode; - public uint statusCode; - public uint errorId; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x2BB)] - public String errorMessage; + public uint characterId; + public uint unknownId; + public UInt64 ticket; } public static unsafe CharacterRequestPacket toCharacterRequestStruct(byte[] bytes) @@ -87,6 +62,14 @@ namespace FFXIVClassic_Lobby_Server.packets } } + public static unsafe SelectCharRequestPacket toSelectCharRequestStruct(byte[] bytes) + { + fixed (byte* pdata = &bytes[0]) + { + return (SelectCharRequestPacket)Marshal.PtrToStructure(new IntPtr(pdata), typeof(SelectCharRequestPacket)); + } + } + public static byte[] StructureToByteArray(object obj) { int len = Marshal.SizeOf(obj); diff --git a/FFXIVClassic_Lobby_Server/packets/SelectCharacterConfirmPacket.cs b/FFXIVClassic_Lobby_Server/packets/SelectCharacterConfirmPacket.cs new file mode 100644 index 00000000..5d00437f --- /dev/null +++ b/FFXIVClassic_Lobby_Server/packets/SelectCharacterConfirmPacket.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Lobby_Server.packets +{ + class SelectCharacterConfirmPacket + { + public const ushort OPCODE = 0x0F; + + private UInt64 sequence; + private UInt32 characterId; + private string sessionToken; + private string worldIp; + private UInt16 worldPort; + private UInt64 selectCharTicket; + + public SelectCharacterConfirmPacket(UInt64 sequence, UInt32 characterId, string sessionToken, string worldIp, ushort worldPort, UInt64 selectCharTicket) + { + this.sequence = sequence; + this.characterId = characterId; + this.sessionToken = sessionToken; + this.worldIp = worldIp; + this.worldPort = worldPort; + this.selectCharTicket = selectCharTicket; + } + + public List buildPackets() + { + List subPackets = new List(); + + byte[] data; + + using (MemoryStream memStream = new MemoryStream(0x98)) + { + using (BinaryWriter binWriter = new BinaryWriter(memStream)) + { + binWriter.Write((UInt64)sequence); + binWriter.Write((UInt32)characterId); //ActorId + binWriter.Write((UInt32)characterId); //CharacterId + binWriter.Write((UInt32)0); + binWriter.Write(Encoding.ASCII.GetBytes(sessionToken.PadRight(0x42, '\0'))); //Session Token + binWriter.Write((UInt16)worldPort); //World Port + binWriter.Write(Encoding.ASCII.GetBytes(worldIp.PadRight(0x38, '\0'))); //World Hostname/IP + binWriter.Write((UInt64)selectCharTicket); //Ticket or Handshake of somekind + } + data = memStream.GetBuffer(); + } + + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + subPackets.Add(subpacket); + + return subPackets; + } + } +}