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