using FFXIVClassic.Common; using FFXIVClassic_Lobby_Server.dataobjects; using FFXIVClassic_Lobby_Server.packets; using FFXIVClassic_Lobby_Server.packets.receive; using FFXIVClassic_Lobby_Server.utils; using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; namespace FFXIVClassic_Lobby_Server { class PacketProcessor { public void ProcessPacket(ClientConnection client, BasePacket packet) { if ((packet.header.packetSize == 0x288) && (packet.data[0x34] == 'T')) //Test Ticket Data { packet.DebugPrintPacket(); //Crypto handshake ProcessStartSession(client, packet); return; } BasePacket.DecryptPacket(client.blowfish, ref packet); packet.DebugPrintPacket(); List subPackets = packet.GetSubpackets(); foreach (SubPacket subpacket in subPackets) { subpacket.DebugPrintSubPacket(); if (subpacket.header.type == 3) { switch (subpacket.gameMessage.opcode) { case 0x03: ProcessGetCharacters(client, subpacket); break; case 0x04: ProcessSelectCharacter(client, subpacket); break; case 0x05: ProcessSessionAcknowledgement(client, subpacket); break; case 0x0B: ProcessModifyCharacter(client, subpacket); break; case 0x0F: //Mod Retainers default: Program.Log.Debug("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode); break; } } } } private void ProcessStartSession(ClientConnection client, BasePacket packet) { SecurityHandshakePacket securityHandshake = new SecurityHandshakePacket(packet.data); byte[] blowfishKey = GenerateKey(securityHandshake.ticketPhrase, securityHandshake.clientNumber); client.blowfish = new Blowfish(blowfishKey); Program.Log.Info("SecCNum: 0x{0:X}", securityHandshake.clientNumber); //Respond with acknowledgment BasePacket outgoingPacket = new BasePacket(HardCoded_Packets.g_secureConnectionAcknowledgment); BasePacket.EncryptPacket(client.blowfish, outgoingPacket); client.QueuePacket(outgoingPacket); } private void ProcessSessionAcknowledgement(ClientConnection client, SubPacket packet) { packet.DebugPrintSubPacket(); SessionPacket sessionPacket = new SessionPacket(packet.data); String clientVersion = sessionPacket.version; Program.Log.Info("Got acknowledgment for secure session."); Program.Log.Info("CLIENT VERSION: {0}", clientVersion); uint userId = Database.GetUserIdFromSession(sessionPacket.session); client.currentUserId = userId; client.currentSessionToken = sessionPacket.session; ; if (userId == 0) { ErrorPacket errorPacket = new ErrorPacket(sessionPacket.sequence, 0, 0, 13001, "Your session has expired, please login again."); SubPacket subpacket = errorPacket.BuildPacket(); subpacket.SetTargetId(0xe0006868); BasePacket errorBasePacket = BasePacket.CreatePacket(subpacket, true, false); BasePacket.EncryptPacket(client.blowfish, errorBasePacket); client.QueuePacket(errorBasePacket); Program.Log.Info("Invalid session, kicking..."); return; } Program.Log.Info("USER ID: {0}", userId); 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) { Program.Log.Info("{0} => Get characters", client.currentUserId == 0 ? client.GetAddress() : "User " + client.currentUserId); SendWorldList(client, packet); SendImportList(client, packet); SendRetainerList(client, packet); SendCharacterList(client, packet); } private void ProcessSelectCharacter(ClientConnection client, SubPacket packet) { SelectCharacterPacket selectCharRequest = new SelectCharacterPacket(packet.data); Program.Log.Info("{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) { 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; } 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); } private void ProcessModifyCharacter(ClientConnection client, SubPacket packet) { CharacterModifyPacket charaReq = new CharacterModifyPacket(packet.data); var slot = charaReq.slot; var name = charaReq.characterName; var worldId = charaReq.worldId; uint pid = 0, cid = 0; //Get world from new char instance if (worldId == 0) worldId = client.newCharaWorldId; //Check if this character exists, Get world from there if (worldId == 0 && charaReq.characterId != 0) { Character chara = Database.GetCharacter(client.currentUserId, charaReq.characterId); if (chara != null) worldId = chara.serverId; } string worldName = null; World world = Database.GetServer(worldId); if (world != null) worldName = world.name; if (worldName == null) { ErrorPacket errorPacket = new ErrorPacket(charaReq.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); Program.Log.Info("User {0} => Error; invalid server id: \"{1}\"", client.currentUserId, worldId); return; } bool alreadyTaken; switch (charaReq.command) { case 0x01://Reserve alreadyTaken = Database.ReserveCharacter(client.currentUserId, slot, worldId, name, out pid, out cid); if (alreadyTaken) { ErrorPacket errorPacket = new ErrorPacket(charaReq.sequence, 1003, 0, 13005, ""); //BDB - Chara Name Used, //1003 - Bad Word SubPacket subpacket = errorPacket.BuildPacket(); BasePacket basePacket = BasePacket.CreatePacket(subpacket, true, false); BasePacket.EncryptPacket(client.blowfish, basePacket); client.QueuePacket(basePacket); Program.Log.Info("User {0} => Error; name taken: \"{1}\"", client.currentUserId, charaReq.characterName); return; } else { pid = 0; client.newCharaCid = cid; client.newCharaSlot = slot; client.newCharaWorldId = worldId; client.newCharaName = name; } Program.Log.Info("User {0} => Character reserved \"{1}\"", client.currentUserId, name); break; case 0x02://Make CharaInfo info = CharaInfo.GetFromNewCharRequest(charaReq.characterInfoEncoded); //Set Initial Appearance (items will be loaded in by map server) uint[] classAppearance = CharacterCreatorUtils.GetEquipmentForClass(info.currentClass); info.weapon1 = classAppearance[0]; info.weapon2 = classAppearance[1]; info.head = classAppearance[7]; if (classAppearance[8] != 0) info.body = classAppearance[8]; else info.body = CharacterCreatorUtils.GetUndershirtForTribe(info.tribe); info.legs = classAppearance[9]; info.hands = classAppearance[10]; info.feet = classAppearance[11]; info.belt = classAppearance[12]; //Set Initial Position switch (info.initialTown) { case 1: //ocn0Battle02 (Limsa) info.zoneId = 193; info.x = 0.016f; info.y = 10.35f; info.z = -36.91f; info.rot = 0.025f; break; case 2: //fst0Battle03 (Gridania) info.zoneId = 166; info.x = 369.5434f; info.y = 4.21f; info.z = -706.1074f; info.rot = -1.26721f; break; case 3: //wil0Battle01 (Ul'dah) info.zoneId = 184; info.x = 5.364327f; info.y = 196.0f; info.z = 133.6561f; info.rot = -2.849384f; break; } Database.MakeCharacter(client.currentUserId, client.newCharaCid, info); pid = 1; cid = client.newCharaCid; name = client.newCharaName; Program.Log.Info("User {0} => Character Created \"{1}\"", client.currentUserId, name); break; case 0x03://Rename alreadyTaken = Database.RenameCharacter(client.currentUserId, charaReq.characterId, worldId, charaReq.characterName); if (alreadyTaken) { ErrorPacket errorPacket = new ErrorPacket(charaReq.sequence, 1003, 0, 13005, ""); //BDB - Chara Name Used, //1003 - Bad Word SubPacket subpacket = errorPacket.BuildPacket(); BasePacket basePacket = BasePacket.CreatePacket(subpacket, true, false); BasePacket.EncryptPacket(client.blowfish, basePacket); client.QueuePacket(basePacket); Program.Log.Info("User {0} => Error; name taken: \"{1}\"", client.currentUserId, charaReq.characterName); return; } Program.Log.Info("User {0} => Character renamed \"{1}\"", client.currentUserId, name); break; case 0x04://Delete Database.DeleteCharacter(charaReq.characterId, charaReq.characterName); Program.Log.Info("User {0} => Character deleted \"{1}\"", client.currentUserId, name); break; case 0x06://Rename Retainer Program.Log.Info("User {0} => Retainer renamed \"{1}\"", client.currentUserId, name); break; } CharaCreatorPacket charaCreator = new CharaCreatorPacket(charaReq.sequence, charaReq.command, pid, cid, 1, name, worldName); BasePacket charaCreatorPacket = BasePacket.CreatePacket(charaCreator.BuildPacket(), true, false); BasePacket.EncryptPacket(client.blowfish, charaCreatorPacket); client.QueuePacket(charaCreatorPacket); } private void SendWorldList(ClientConnection client, SubPacket packet) { List serverList = Database.GetServers(); WorldListPacket worldlistPacket = new WorldListPacket(0, serverList); List subPackets = worldlistPacket.BuildPackets(); BasePacket basePacket = BasePacket.CreatePacket(subPackets, true, false); BasePacket.EncryptPacket(client.blowfish, basePacket); client.QueuePacket(basePacket); } private void SendImportList(ClientConnection client, SubPacket packet) { List names = Database.GetReservedNames(client.currentUserId); ImportListPacket importListPacket = new ImportListPacket(0, names); List subPackets = importListPacket.BuildPackets(); BasePacket basePacket = BasePacket.CreatePacket(subPackets, true, false); BasePacket.EncryptPacket(client.blowfish, basePacket); client.QueuePacket(basePacket); } private void SendRetainerList(ClientConnection client, SubPacket packet) { List retainers = Database.GetRetainers(client.currentUserId); RetainerListPacket retainerListPacket = new RetainerListPacket(0, retainers); List subPackets = retainerListPacket.BuildPackets(); BasePacket basePacket = BasePacket.CreatePacket(subPackets, true, false); BasePacket.EncryptPacket(client.blowfish, basePacket); client.QueuePacket(basePacket); } private void SendCharacterList(ClientConnection client, SubPacket packet) { List characterList = Database.GetCharacters(client.currentUserId); if (characterList.Count > 8) Program.Log.Error("Warning, got more than 8 characters. List truncated, check DB for issues."); CharacterListPacket characterlistPacket = new CharacterListPacket(0, characterList); List subPackets = characterlistPacket.BuildPackets(); BasePacket basePacket = BasePacket.CreatePacket(subPackets, true, false); BasePacket.EncryptPacket(client.blowfish, basePacket); client.QueuePacket(basePacket); } private byte[] GenerateKey(string ticketPhrase, uint clientNumber) { byte[] key; using (MemoryStream memStream = new MemoryStream(0x2C)) { using (BinaryWriter binWriter = new BinaryWriter(memStream)) { binWriter.Write((Byte)0x78); binWriter.Write((Byte)0x56); binWriter.Write((Byte)0x34); binWriter.Write((Byte)0x12); binWriter.Write((UInt32)clientNumber); binWriter.Write((Byte)0xE8); binWriter.Write((Byte)0x03); binWriter.Write((Byte)0x00); binWriter.Write((Byte)0x00); binWriter.Write(Encoding.ASCII.GetBytes(ticketPhrase), 0, Encoding.ASCII.GetByteCount(ticketPhrase) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(ticketPhrase)); } byte[] nonMD5edKey = memStream.GetBuffer(); using (MD5 md5Hash = MD5.Create()) { key = md5Hash.ComputeHash(nonMD5edKey); } } return key; } } }