From b717f6aeb1213cd5929a94c10bb61aeb6f7a94c9 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 13 Sep 2015 11:30:33 -0400 Subject: [PATCH] Character deletes now delete the character from the DB instead of just changing the state. DB can now get single character. Character list is sent properly as per how 1.0 did it (only 1 'NEW' entry if available). Character info is now loaded from the new character packet and stored. It is also loaded for each character, encoded, and displayed (still testing). --- FFXIVClassic_Lobby_Server/Database.cs | 47 +++- FFXIVClassic_Lobby_Server/PacketProcessor.cs | 66 +++-- .../dataobjects/CharaInfo.cs | 240 ++++++++++++++++-- .../dataobjects/Character.cs | 8 +- .../packets/CharacterListPacket.cs | 53 ++-- research/encodedCharaInfo.txt | 50 ++-- research/encodedCharaMakeInfo.txt | 38 +++ 7 files changed, 402 insertions(+), 100 deletions(-) create mode 100644 research/encodedCharaMakeInfo.txt diff --git a/FFXIVClassic_Lobby_Server/Database.cs b/FFXIVClassic_Lobby_Server/Database.cs index 3ffebe1b..b3d7fe43 100644 --- a/FFXIVClassic_Lobby_Server/Database.cs +++ b/FFXIVClassic_Lobby_Server/Database.cs @@ -128,17 +128,31 @@ namespace FFXIVClassic_Lobby_Server } } - public static void renameCharacter(uint characterId, String newName) + public static bool renameCharacter(uint userId, uint characterId, uint serverId, String newName) { 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(); - MySqlCommand cmd = new MySqlCommand(); + + //Check if exists + MySqlCommand cmd = new MySqlCommand("SELECT * FROM characters WHERE name=@name AND serverId=@serverId", conn); + cmd.Parameters.AddWithValue("@serverId", serverId); + cmd.Parameters.AddWithValue("@name", newName); + using (MySqlDataReader Reader = cmd.ExecuteReader()) + { + if (Reader.HasRows) + { + return true; + } + } + + cmd = new MySqlCommand(); cmd.Connection = conn; - cmd.CommandText = "UPDATE characters SET name=@name WHERE id=@cid"; + cmd.CommandText = "UPDATE characters SET name=@name, doRename=0 WHERE id=@cid AND userId=@uid"; cmd.Prepare(); + cmd.Parameters.AddWithValue("@uid", userId); cmd.Parameters.AddWithValue("@cid", characterId); cmd.Parameters.AddWithValue("@name", newName); cmd.ExecuteNonQuery(); @@ -152,6 +166,8 @@ namespace FFXIVClassic_Lobby_Server { conn.Dispose(); } + + return false; } } @@ -164,7 +180,7 @@ namespace FFXIVClassic_Lobby_Server conn.Open(); MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; - cmd.CommandText = "UPDATE characters SET state=1 WHERE id=@cid AND name=@name"; + cmd.CommandText = "DELETE FROM characters WHERE id=@cid AND name=@name"; cmd.Prepare(); cmd.Parameters.AddWithValue("@cid", characterId); cmd.Parameters.AddWithValue("@name", name); @@ -244,6 +260,29 @@ namespace FFXIVClassic_Lobby_Server } } + + public static Character getCharacter(uint userId, uint charId) + { + using (var 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))) + { + Character chara = null; + try + { + conn.Open(); + chara = conn.Query("SELECT * FROM characters WHERE id=@CharaId and userId=@UserId", new { UserId = userId, CharaId = charId }).SingleOrDefault(); + } + catch (MySqlException e) + { + } + finally + { + conn.Dispose(); + } + + return chara; + } + } + public static List getReservedNames(uint userId) { using (var 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))) diff --git a/FFXIVClassic_Lobby_Server/PacketProcessor.cs b/FFXIVClassic_Lobby_Server/PacketProcessor.cs index 3a7155ba..d94f1471 100644 --- a/FFXIVClassic_Lobby_Server/PacketProcessor.cs +++ b/FFXIVClassic_Lobby_Server/PacketProcessor.cs @@ -157,13 +157,11 @@ namespace FFXIVClassic_Lobby_Server sendWorldList(client, packet); sendImportList(client, packet); sendRetainerList(client, packet); - //sendCharacterList(client, packet); - - //BasePacket outgoingPacket = new BasePacket("./packets/getCharsPacket.bin"); - BasePacket outgoingPacket = new BasePacket("./packets/getChars_GOOD.bin"); + sendCharacterList(client, packet); + /*BasePacket outgoingPacket = new BasePacket("./packets/getChars_GOOD.bin"); + outgoingPacket.debugPrintPacket(); BasePacket.encryptPacket(client.blowfish, outgoingPacket); - client.queuePacket(outgoingPacket); - + client.queuePacket(outgoingPacket);*/ } @@ -211,9 +209,18 @@ namespace FFXIVClassic_Lobby_Server 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) @@ -231,11 +238,13 @@ namespace FFXIVClassic_Lobby_Server return; } + bool alreadyTaken; + switch (code) { case 0x01://Reserve - var alreadyTaken = Database.reserveCharacter(client.currentUserId, slot, worldId, name, out pid, out cid); + alreadyTaken = Database.reserveCharacter(client.currentUserId, slot, worldId, name, out pid, out cid); if (alreadyTaken) { @@ -257,31 +266,45 @@ namespace FFXIVClassic_Lobby_Server client.newCharaName = name; } - Log.info(String.Format("User {0} => Character reserved \"{1}\"", client.currentUserId, charaReq.characterName)); + Log.info(String.Format("User {0} => Character reserved \"{1}\"", client.currentUserId, name)); break; case 0x02://Make - CharaInfo info = new CharaInfo(); + CharaInfo info = CharaInfo.getFromNewCharRequest(charaReq.characterInfoEncoded); Database.makeCharacter(client.currentUserId, client.newCharaCid, info); pid = 1; cid = client.newCharaCid; - name = client.newCharaName; + name = client.newCharaName; - Log.info(String.Format("User {0} => Character created \"{1}\"", client.currentUserId, charaReq.characterName)); + Log.info(String.Format("User {0} => Character created \"{1}\"", client.currentUserId, name)); break; case 0x03://Rename - - Log.info(String.Format("User {0} => Character renamed \"{1}\"", client.currentUserId, charaReq.characterName)); + + 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); + + Log.info(String.Format("User {0} => Error; name taken: \"{1}\"", client.currentUserId, charaReq.characterName)); + return; + } + + Log.info(String.Format("User {0} => Character renamed \"{1}\"", client.currentUserId, name)); break; case 0x04://Delete Database.deleteCharacter(charaReq.characterId, charaReq.characterName); - - Log.info(String.Format("User {0} => Character deleted \"{1}\"", client.currentUserId, charaReq.characterName)); + + Log.info(String.Format("User {0} => Character deleted \"{1}\"", client.currentUserId, name)); break; case 0x06://Rename Retainer - Log.info(String.Format("User {0} => Retainer renamed \"{1}\"", client.currentUserId, charaReq.characterName)); + Log.info(String.Format("User {0} => Retainer renamed \"{1}\"", client.currentUserId, name)); break; } @@ -296,7 +319,7 @@ namespace FFXIVClassic_Lobby_Server private void sendWorldList(ClientConnection client, SubPacket packet) { List serverList = Database.getServers(); - WorldListPacket worldlistPacket = new WorldListPacket(2, serverList); + WorldListPacket worldlistPacket = new WorldListPacket(0, serverList); List subPackets = worldlistPacket.buildPackets(); BasePacket basePacket = BasePacket.createPacket(subPackets, true, false); @@ -309,7 +332,7 @@ namespace FFXIVClassic_Lobby_Server { List names = Database.getReservedNames(client.currentUserId); - ImportListPacket importListPacket = new ImportListPacket(2, names); + ImportListPacket importListPacket = new ImportListPacket(0, names); List subPackets = importListPacket.buildPackets(); BasePacket basePacket = BasePacket.createPacket(subPackets, true, false); BasePacket.encryptPacket(client.blowfish, basePacket); @@ -320,7 +343,7 @@ namespace FFXIVClassic_Lobby_Server { List retainers = Database.getRetainers(client.currentUserId); - RetainerListPacket retainerListPacket = new RetainerListPacket(2, retainers); + RetainerListPacket retainerListPacket = new RetainerListPacket(0, retainers); List subPackets = retainerListPacket.buildPackets(); BasePacket basePacket = BasePacket.createPacket(subPackets, true, false); BasePacket.encryptPacket(client.blowfish, basePacket); @@ -331,7 +354,10 @@ namespace FFXIVClassic_Lobby_Server { List characterList = Database.getCharacters(client.currentUserId); - CharacterListPacket characterlistPacket = new CharacterListPacket(2, characterList, 2); + if (characterList.Count > 8) + 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); diff --git a/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs b/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs index 3c96dc18..49194557 100644 --- a/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs +++ b/FFXIVClassic_Lobby_Server/dataobjects/CharaInfo.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using FFXIVClassic_Lobby_Server.common; +using System.IO; namespace FFXIVClassic_Lobby_Server.dataobjects { @@ -11,30 +13,33 @@ namespace FFXIVClassic_Lobby_Server.dataobjects public uint tribe = 0; public uint size = 0; public uint voice = 0; - public uint skinColor = 0; + public ushort skinColor = 0; - public uint hairStyle = 0; - public uint hairColor = 0; - public uint eyeColor = 0; + public ushort hairStyle = 0; + public ushort hairColor = 0; + public ushort hairHighlightColor = 0; + public ushort eyeColor = 0; + public ushort characteristicsColor = 0; public uint faceType = 0; - public uint faceBrow = 0; - public uint faceEye = 0; - public uint faceIris = 0; + public uint faceEyebrow = 0; + public uint faceEyeShape = 0; + public uint faceEyeSize = 0; public uint faceNose = 0; public uint faceMouth = 0; - public uint faceJaw = 0; - public uint faceCheek = 0; - public uint faceOption1 = 0; - public uint faceOption2 = 0; + public uint faceFeatures = 0; + public uint characteristics = 0; + public uint ears = 0; public uint guardian = 0; public uint birthMonth = 0; public uint birthDay = 0; + public uint currentClass = 0; + public uint currentJob = 0; public uint allegiance = 0; - public uint weapon1 = 0; - public uint weapon2 = 0; + public uint mainHand = 0; + public uint offHand = 0; public uint headGear = 0; public uint bodyGear = 0; @@ -46,12 +51,213 @@ namespace FFXIVClassic_Lobby_Server.dataobjects public uint leftEarGear = 0; public uint rightFingerGear = 0; public uint leftFingerGear = 0; - - public byte[] toBytes() + + public uint currentLevel = 1; + + public static CharaInfo getFromNewCharRequest(String encoded) { - byte[] bytes = new byte[0x120]; - return bytes; + byte[] data = Convert.FromBase64String(encoded.Replace('-', '+').Replace('_', '/')); + CharaInfo info = new CharaInfo(); + + using (MemoryStream stream = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + uint version = reader.ReadUInt32(); + uint unknown1 = reader.ReadUInt32(); + info.tribe = reader.ReadByte(); + info.size = reader.ReadByte(); + info.hairStyle = reader.ReadUInt16(); + info.hairHighlightColor = reader.ReadUInt16(); + info.faceType = reader.ReadByte(); + info.characteristics = reader.ReadByte(); + info.characteristicsColor = reader.ReadByte(); + + reader.ReadUInt32(); + + info.faceEyebrow = reader.ReadByte(); + info.faceEyeSize = reader.ReadByte(); + info.faceEyeShape = reader.ReadByte(); + info.faceNose = reader.ReadByte(); + info.faceFeatures = reader.ReadByte(); + info.faceMouth = reader.ReadByte(); + info.ears = reader.ReadByte(); + info.hairColor = reader.ReadUInt16(); + + reader.ReadUInt32(); + + info.skinColor = reader.ReadUInt16(); + info.eyeColor = reader.ReadUInt16(); + + info.voice = reader.ReadByte(); + info.guardian = reader.ReadByte(); + info.birthMonth = reader.ReadByte(); + info.birthDay = reader.ReadByte(); + info.currentClass = reader.ReadUInt16(); + + reader.ReadUInt32(); + reader.ReadUInt32(); + reader.ReadUInt32(); + + reader.BaseStream.Seek(0x10, SeekOrigin.Current); + + info.allegiance = reader.ReadByte(); + + } + } + + + + return info; } + public String buildForCharaList(Character chara) + { + byte[] data; + + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + string location1 = "prv0Inn01"; + string location2 = "defaultTerritory"; + + writer.Write((UInt32)0x000004c0); + writer.Write((UInt32)0x232327ea); + writer.Write((UInt32)System.Text.Encoding.UTF8.GetBytes(chara.name).Length); + writer.Write(System.Text.Encoding.UTF8.GetBytes(chara.name)); + writer.Write((UInt32)0x1c); + writer.Write((UInt32)0x04); + writer.Write((UInt32)getTribeModel()); + writer.Write((UInt32)size); + uint colorVal = skinColor | (uint)(hairColor << 10) | (uint)(eyeColor << 20); + writer.Write((UInt32)colorVal); + writer.Write((UInt32)0x14d00100); //FACE, Figure this out! + uint hairVal = hairHighlightColor | (uint)(hairStyle << 10) | (uint)(characteristicsColor << 20); + writer.Write((UInt32)hairVal); + writer.Write((UInt32)voice); + writer.Write((UInt32)mainHand); + writer.Write((UInt32)offHand); + + writer.Write((UInt32)0); + writer.Write((UInt32)0); + writer.Write((UInt32)0); + writer.Write((UInt32)0); + writer.Write((UInt32)0); + + writer.Write((UInt32)headGear); + writer.Write((UInt32)bodyGear); + writer.Write((UInt32)legsGear); + writer.Write((UInt32)handsGear); + writer.Write((UInt32)feetGear); + writer.Write((UInt32)waistGear); + + writer.Write((UInt32)0); + + writer.Write((UInt32)rightEarGear); + writer.Write((UInt32)leftEarGear); + + writer.Write((UInt32)0); + writer.Write((UInt32)0); + + writer.Write((UInt32)rightFingerGear); + writer.Write((UInt32)leftFingerGear); + + for (int i = 0; i < 0xC; i++) + writer.Write((byte)0); + + writer.Write((UInt32)1); + writer.Write((UInt32)1); + + writer.Write((byte)currentClass); + writer.Write((UInt16)currentLevel); + writer.Write((byte)currentJob); + writer.Write((UInt16)1); + writer.Write((byte)tribe); + + writer.Write((UInt32)System.Text.Encoding.UTF8.GetBytes(location1).Length); + writer.Write(System.Text.Encoding.UTF8.GetBytes(location1)); + writer.Write((UInt32)System.Text.Encoding.UTF8.GetBytes(location2).Length); + writer.Write(System.Text.Encoding.UTF8.GetBytes(location2)); + + writer.Write((byte)guardian); + writer.Write((byte)birthMonth); + writer.Write((byte)birthDay); + + writer.Write((UInt32)4); + writer.Write((UInt32)4); + + writer.BaseStream.Seek(0x10, SeekOrigin.Current); + + writer.Write((UInt32)allegiance); + writer.Write((UInt32)allegiance); + } + + data = stream.GetBuffer(); + + File.WriteAllBytes("./packets/out.bin",data); + } + + return Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_'); + } + + public static String debug() + { + byte[] bytes = File.ReadAllBytes("./packets/charaInfo.bin"); + + Console.WriteLine(Utils.ByteArrayToHex(bytes)); + + return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_'); + } + + public UInt32 getTribeModel() + { + switch (tribe) + { + //Hyur Midlander Male + case 0: + default: + return 1; + + //Hyur Midlander Female + case 1: + case 2: + return 2; + + //Elezen Male + case 4: + case 6: + return 3; + + //Elezen Female + case 5: + case 7: + return 4; + + //Lalafell Male + case 8: + case 10: + return 5; + + //Lalafell Female + case 9: + case 11: + return 6; + + //Miqo'te Female + case 12: + case 13: + return 8; + + //Roegadyn Male + case 14: + case 15: + return 7; + + //Hyur Highlander Male + case 3: + return 9; + } + } } } diff --git a/FFXIVClassic_Lobby_Server/dataobjects/Character.cs b/FFXIVClassic_Lobby_Server/dataobjects/Character.cs index f9f2bc06..764220e1 100644 --- a/FFXIVClassic_Lobby_Server/dataobjects/Character.cs +++ b/FFXIVClassic_Lobby_Server/dataobjects/Character.cs @@ -20,13 +20,7 @@ namespace FFXIVClassic_Lobby_Server public bool doRename; public uint currentZoneId; - public static String characterToEncoded(CharaInfo chara) - { - String charaInfo = System.Convert.ToBase64String(chara.toBytes()); - charaInfo.Replace("+", "-"); - charaInfo.Replace("/", "_"); - return charaInfo; - } + public static CharaInfo EncodedToCharacter(String charaInfo) { diff --git a/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs b/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs index c75f5c61..6cbf1d50 100644 --- a/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs +++ b/FFXIVClassic_Lobby_Server/packets/CharacterListPacket.cs @@ -1,4 +1,5 @@ using FFXIVClassic_Lobby_Server.dataobjects; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -14,20 +15,20 @@ namespace FFXIVClassic_Lobby_Server.packets public const ushort MAXPERPACKET = 2; private ulong sequence; - private ushort maxChars; private List characterList; - public CharacterListPacket(ulong sequence, List characterList, ushort maxChars) + public CharacterListPacket(ulong sequence, List characterList) { this.sequence = sequence; this.characterList = characterList; - this.maxChars = maxChars; } public List buildPackets() { List subPackets = new List(); + int numCharacters = characterList.Count >= 8 ? 8 : characterList.Count + 1; + int characterCount = 0; int totalCount = 0; @@ -44,9 +45,8 @@ namespace FFXIVClassic_Lobby_Server.packets //Write List Info binWriter.Write((UInt64)sequence); byte listTracker = (byte)((MAXPERPACKET * 2) * subPackets.Count); - binWriter.Write(characterList.Count - totalCount <= MAXPERPACKET ? (byte)(listTracker + 1) : (byte)(listTracker)); - //binWriter.Write((byte)1); - binWriter.Write(maxChars - totalCount <= MAXPERPACKET ? (UInt32)(maxChars - totalCount) : (UInt32)MAXPERPACKET); + binWriter.Write(numCharacters - totalCount <= MAXPERPACKET ? (byte)(listTracker + 1) : (byte)(listTracker)); + binWriter.Write(numCharacters - totalCount <= MAXPERPACKET ? (UInt32)(numCharacters - totalCount) : (UInt32)MAXPERPACKET); binWriter.Write((byte)0); binWriter.Write((UInt16)0); } @@ -68,13 +68,16 @@ namespace FFXIVClassic_Lobby_Server.packets options |= 0x02; if (chara.isLegacy) options |= 0x08; - + binWriter.Write((byte)options); //Options (0x01: Service Account not active, 0x72: Change Chara Name) binWriter.Write((ushort)0); binWriter.Write((uint)0xF4); //Logged out zone binWriter.Write(Encoding.ASCII.GetBytes(chara.name.PadRight(0x20, '\0'))); //Name binWriter.Write(Encoding.ASCII.GetBytes(worldname.PadRight(0xE, '\0'))); //World Name - binWriter.Write("wAQAAOonIyMNAAAAV3Jlbml4IFdyb25nABwAAAAEAAAAAwAAAAMAAAA_8OADAAHQFAAEAAABAAAAABTQCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGEgAAAAMQAAQCQAAMAsAACKVAAAAPgCAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAkAwAAAAAAAAAAANvb1M05AQAABBoAAAEABqoiIuIKAAAAcHJ2MElubjAxABEAAABkZWZhdWx0VGVycml0b3J5AAwJAhcABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAA="); //Appearance Data + + CharaInfo info = JsonConvert.DeserializeObject(chara.charaInfo); + //binWriter.Write(info.buildForCharaList(chara)); //Appearance Data + binWriter.Write(CharaInfo.debug()); //Appearance Data characterCount++; totalCount++; @@ -90,21 +93,24 @@ namespace FFXIVClassic_Lobby_Server.packets characterCount = 0; } + //Incase DB came back with more than max + if (totalCount >= 8) + break; } - //Keep creating empty slots until done max characters - while (maxChars - totalCount > 0) + //Add a 'NEW' slot if there is space + if (characterList.Count < 8) { if (characterCount % MAXPERPACKET == 0) { - memStream = new MemoryStream(0x3D0); + memStream = new MemoryStream(0x3B0); binWriter = new BinaryWriter(memStream); //Write List Info binWriter.Write((UInt64)sequence); - binWriter.Write(maxChars - totalCount <= MAXPERPACKET ? (byte)(maxChars + 1) : (byte)0); - //binWriter.Write((byte)1); - binWriter.Write(maxChars - totalCount <= MAXPERPACKET ? (UInt32)(maxChars - totalCount) : (UInt32)MAXPERPACKET); + byte listTracker = (byte)((MAXPERPACKET * 2) * subPackets.Count); + binWriter.Write(numCharacters - totalCount <= MAXPERPACKET ? (byte)(listTracker + 1) : (byte)(listTracker)); + binWriter.Write(numCharacters - totalCount <= MAXPERPACKET ? (UInt32)(numCharacters-totalCount) : (UInt32)MAXPERPACKET); binWriter.Write((byte)0); binWriter.Write((UInt16)0); } @@ -115,7 +121,6 @@ namespace FFXIVClassic_Lobby_Server.packets binWriter.Write((uint)0); //??? binWriter.Write((uint)0); //Character Id binWriter.Write((byte)(totalCount)); //Slot - binWriter.Write((byte)0); //Options (0x01: Service Account not active, 0x72: Change Chara Name) binWriter.Write((ushort)0); binWriter.Write((uint)0); //Logged out zone @@ -136,22 +141,8 @@ namespace FFXIVClassic_Lobby_Server.packets } //If there is anything left that was missed or the list is empty - if (characterCount > 0 || maxChars == 0) - { - if (maxChars == 0) - { - memStream = new MemoryStream(0x3D0); - binWriter = new BinaryWriter(memStream); - - //Write Empty List Info - binWriter.Write((UInt64)sequence); - byte listTracker = (byte)((MAXPERPACKET * 2) * subPackets.Count); - binWriter.Write(characterList.Count - totalCount <= MAXPERPACKET ? (byte)(listTracker + 1) : (byte)(listTracker)); - binWriter.Write((UInt32)0); - binWriter.Write((byte)0); - binWriter.Write((UInt16)0); - } - + if (characterCount > 0 || numCharacters == 0) + { byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); diff --git a/research/encodedCharaInfo.txt b/research/encodedCharaInfo.txt index e15526e6..b9d49618 100644 --- a/research/encodedCharaInfo.txt +++ b/research/encodedCharaInfo.txt @@ -3,18 +3,18 @@ 0x000: Int32; 0x004: Int32; -0x008:Name Size Int32; +0x008:Name Size Int32; 0x00C:Name String; Variable size, but in file the name "Wrenix Wrong" is 0xD in size -0x019: Int32; -0x01D: Int32; -0x021:Tribe Model Int32; +0x019:Size? Offset? Int32; Must be 0x1c or is crashes.... +0x01D:Unknown Int32; +0x021:Tribe Model Int32; 0x025:Size Int32; -0x029:Colors Info Int32; -0x02D:Face Info Int32; -0x031:Hair Model Int32; +0x029:Colors Info Int32; +0x02D:Face Info Int32; +0x031:Hair Style + Highlight Color Int32; 0x035:Voice Int32; -0x039:MainHand Int32; -0x03D:OffHand Int32; +0x039:MainHand Int32; +0x03D:OffHand Int32; 0x041: Int32; 0x045: Int32; 0x049: Int32; @@ -27,29 +27,37 @@ 0x065:Feet Int32; 0x069:Waist Int32; 0x06D: Int32; -0x071:Right Ear Int32; -0x075:Left Ear Int32; +0x071:Right Ear Int32; +0x075:Left Ear Int32; 0x079: Int32; 0x07D: Int32; -0x081:Right Ring Int32; -0x085:Left Ring Int32; +0x081:Right Ring Int32; +0x085:Left Ring Int32; +====Zeros/Unknown==== + +0x091:ID????? Int32; +0x095:Unknown (Must be > 0x00) Int32; +0x099:Class Byte; +0x09A:Level Short; +0x09C:Job Byte; +0x09D:Unknown Short; 0x09F:Tribe Byte; 0x0A0: Int32; -0x0A4:Location Str Size Int32; -0x0A8:Location String String; Variable size, but in file it is prv0Inn01\0, 0x0A in size. +0x0A4:Location Str Size Int32; +0x0A8:Location String String; Variable size, but in file it is prv0Inn01\0, 0x0A in size. -0x0B2:Territory Str Size Int32; -0x0B6:Territory Str? String; Variable size, but in file it is defaultTerritory\0, 0x11 in size. +0x0B2:Territory Str Size Int32; +0x0B6:Territory Str? String; Variable size, but in file it is defaultTerritory\0, 0x11 in size. -0x0C7:Guardian Byte; -0x0C8:Birth Month Byte; -0x0C9:Birth Day Byte; +0x0C7:Guardian Byte; +0x0C8:Birth Month Byte; +0x0C9:Birth Day Byte; 0x0CA: Short; 0x0CC: Int32; 0x0D0: Int32; 0x0E4: Byte; -0x0E8:Allegiance Byte; \ No newline at end of file +0x0E8:Allegiance Byte; \ No newline at end of file diff --git a/research/encodedCharaMakeInfo.txt b/research/encodedCharaMakeInfo.txt new file mode 100644 index 00000000..1b9c6766 --- /dev/null +++ b/research/encodedCharaMakeInfo.txt @@ -0,0 +1,38 @@ +===Encoded CharaMake Info=== By Ioncannon +-Based on chara info array in Seventh Umbral + +0x00: Unknown... Version? Int32; +0x04: Unknown - Weird #1 Int32; +0x08: Tribe +0x09: Size +0x0A: Hair Style Short +0x0C: Highlight Hair Color Short; +0x0E: Face +0x0F: Characteristics +0x10: Characteristics Color Short; +0x12: Unknown - Weird #2 Int32; +0x15: Eyebrows +0x16: Eye Size +0x17: Eye Shape +0x18: Nose +0x19: Feature +0x1A: Mouth +0x1B: Ears +0x1C: Hair Color Short; +0x1E: Unknown - Weird #3 Int32; +0x22: Skin Color Short +0x24: Eye Color Short; +0x26: Voice +0x27: Guardian +0x28: Month +0x29: Day +0x2A: Start Class Short; +0x2C: Unknown Int32; +0x30: Unknown Int32; +0x34: Unknown Int32; + +0x38: 0x10 bytes of 0s + +0x48: Start Nation + +0x49: 0xC bytes of 0s \ No newline at end of file