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).

This commit is contained in:
Filip Maj 2015-09-13 11:30:33 -04:00
parent aadca3968d
commit b717f6aeb1
7 changed files with 402 additions and 100 deletions

View file

@ -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<Character>("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<String> 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)))

View file

@ -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<World> serverList = Database.getServers();
WorldListPacket worldlistPacket = new WorldListPacket(2, serverList);
WorldListPacket worldlistPacket = new WorldListPacket(0, serverList);
List<SubPacket> subPackets = worldlistPacket.buildPackets();
BasePacket basePacket = BasePacket.createPacket(subPackets, true, false);
@ -309,7 +332,7 @@ namespace FFXIVClassic_Lobby_Server
{
List<String> names = Database.getReservedNames(client.currentUserId);
ImportListPacket importListPacket = new ImportListPacket(2, names);
ImportListPacket importListPacket = new ImportListPacket(0, names);
List<SubPacket> subPackets = importListPacket.buildPackets();
BasePacket basePacket = BasePacket.createPacket(subPackets, true, false);
BasePacket.encryptPacket(client.blowfish, basePacket);
@ -320,7 +343,7 @@ namespace FFXIVClassic_Lobby_Server
{
List<Retainer> retainers = Database.getRetainers(client.currentUserId);
RetainerListPacket retainerListPacket = new RetainerListPacket(2, retainers);
RetainerListPacket retainerListPacket = new RetainerListPacket(0, retainers);
List<SubPacket> subPackets = retainerListPacket.buildPackets();
BasePacket basePacket = BasePacket.createPacket(subPackets, true, false);
BasePacket.encryptPacket(client.blowfish, basePacket);
@ -331,7 +354,10 @@ namespace FFXIVClassic_Lobby_Server
{
List<Character> 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<SubPacket> subPackets = characterlistPacket.buildPackets();
BasePacket basePacket = BasePacket.createPacket(subPackets, true, false);
BasePacket.encryptPacket(client.blowfish, basePacket);

View file

@ -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;
}
}
}
}

View file

@ -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)
{

View file

@ -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<Character> characterList;
public CharacterListPacket(ulong sequence, List<Character> characterList, ushort maxChars)
public CharacterListPacket(ulong sequence, List<Character> characterList)
{
this.sequence = sequence;
this.characterList = characterList;
this.maxChars = maxChars;
}
public List<SubPacket> buildPackets()
{
List<SubPacket> subPackets = new List<SubPacket>();
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<CharaInfo>(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();