1
Fork 0
mirror of https://bitbucket.org/Ioncannon/project-meteor-server.git synced 2025-04-21 20:27:47 +00:00

Ported over the network code from the map server. A lot of backedlogged changes are here as well.

This commit is contained in:
Filip Maj 2016-02-16 23:35:21 -05:00
parent af4a0d5546
commit 8c73f6e926
12 changed files with 443 additions and 184 deletions

View file

@ -22,6 +22,7 @@ namespace FFXIVClassic_Lobby_Server
public byte[] buffer = new byte[0xffff];
public CircularBuffer<byte> incomingStream = new CircularBuffer<byte>(1024);
public BlockingCollection<BasePacket> sendPacketQueue = new BlockingCollection<BasePacket>(100);
public int lastPartialSize = 0;
//Instance Stuff
public uint currentUserId = 0;

View file

@ -146,7 +146,6 @@ namespace FFXIVClassic_Lobby_Server
cmd.Parameters.AddWithValue("@characterId", cid);
cmd.Parameters.AddWithValue("@baseId", 0xFFFFFFFF);
cmd.Parameters.AddWithValue("@tribe", charaInfo.appearance.tribe);
cmd.Parameters.AddWithValue("@size", charaInfo.appearance.size);
cmd.Parameters.AddWithValue("@voice", charaInfo.appearance.voice);
cmd.Parameters.AddWithValue("@skinColor", charaInfo.appearance.skinColor);
@ -360,7 +359,7 @@ namespace FFXIVClassic_Lobby_Server
try
{
conn.Open();
appearance = conn.Query<Appearance>("SELECT * FROM characters_appearance WHERE id=@CharaId", new { CharaId = charaId }).SingleOrDefault();
appearance = conn.Query<Appearance>("SELECT * FROM characters_appearance WHERE characterId=@CharaId", new { CharaId = charaId }).SingleOrDefault();
}
catch (MySqlException e)
{

View file

@ -15,60 +15,8 @@ namespace FFXIVClassic_Lobby_Server
{
class PacketProcessor
{
List<ClientConnection> mConnections;
Boolean isAlive = true;
public PacketProcessor(List<ClientConnection> connectionList)
{
mConnections = connectionList;
}
public void update()
{
Console.WriteLine("Packet processing thread has started");
while (isAlive)
{
lock (mConnections)
{
foreach (ClientConnection client in mConnections)
{
//Receive packets
while (true)
{
if (client.incomingStream.Size < BasePacket.BASEPACKET_SIZE)
break;
try {
if (client.incomingStream.Size < BasePacket.BASEPACKET_SIZE)
break;
BasePacketHeader header = BasePacket.getHeader(client.incomingStream.Peek(BasePacket.BASEPACKET_SIZE));
if (client.incomingStream.Size < header.packetSize)
break;
BasePacket packet = new BasePacket(client.incomingStream.Get(header.packetSize));
processPacket(client, packet);
}
catch(OverflowException)
{ break; }
}
//Send packets
while (client.sendPacketQueue.Count != 0)
client.flushQueuedSendPackets();
}
}
//Don't waste CPU if isn't needed
if (mConnections.Count == 0)
Thread.Sleep(2000);
else
Thread.Sleep(100);
}
}
private void processPacket(ClientConnection client, BasePacket packet)
public void processPacket(ClientConnection client, BasePacket packet)
{
if ((packet.header.packetSize == 0x288) && (packet.data[0x34] == 'T')) //Test Ticket Data
@ -80,31 +28,35 @@ namespace FFXIVClassic_Lobby_Server
BasePacket.decryptPacket(client.blowfish, ref packet);
//packet.debugPrintPacket();
packet.debugPrintPacket();
List<SubPacket> subPackets = packet.getSubpackets();
foreach (SubPacket subpacket in subPackets)
{
subpacket.debugPrintSubPacket();
switch (subpacket.header.opcode)
if (subpacket.header.type == 3)
{
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:
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:
Log.debug(String.Format("Unknown command 0x{0:X} received.", subpacket.header.opcode));
break;
default:
Log.debug(String.Format("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode));
break;
}
}
}
}

View file

@ -35,7 +35,7 @@ namespace FFXIVClassic_Lobby_Server
startServer = false;
//Test DB Connection
Console.Write("Testing DB connection... ");
Console.Write("Testing DB connection to \"{0}\"... ", ConfigConstants.DATABASE_HOST);
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

View file

@ -7,19 +7,19 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using System.Threading;
using FFXIVClassic_Lobby_Server.common;
using FFXIVClassic_Lobby_Server.packets;
namespace FFXIVClassic_Lobby_Server
{
class Server
{
public const int FFXIV_LOBBY_PORT = 54994;
public const int BUFFER_SIZE = 0x400;
public const int BUFFER_SIZE = 0xFFFF;
public const int BACKLOG = 100;
private Socket mServerSocket;
private List<ClientConnection> mConnectionList = new List<ClientConnection>();
private PacketProcessor mProcessor;
private Thread mProcessorThread;
#region Socket Handling
public bool startServer()
@ -56,9 +56,8 @@ namespace FFXIVClassic_Lobby_Server
Console.WriteLine("{0}:{1}", (mServerSocket.LocalEndPoint as IPEndPoint).Address, (mServerSocket.LocalEndPoint as IPEndPoint).Port);
Console.ForegroundColor = ConsoleColor.Gray;
mProcessor = new PacketProcessor(mConnectionList);
mProcessorThread = new Thread(new ThreadStart(mProcessor.update));
mProcessorThread.Start();
mProcessor = new PacketProcessor();
return true;
}
@ -110,20 +109,51 @@ namespace FFXIVClassic_Lobby_Server
private void receiveCallback(IAsyncResult result)
{
ClientConnection conn = (ClientConnection)result.AsyncState;
try
{
int bytesRead = conn.socket.EndReceive(result);
if (bytesRead > 0)
{
conn.processIncoming(bytesRead);
//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), conn);
bytesRead += conn.lastPartialSize;
if (bytesRead >= 0)
{
int offset = 0;
//Build packets until can no longer or out of data
while (true)
{
BasePacket basePacket = buildPacket(ref offset, conn.buffer, bytesRead);
//If can't build packet, break, else process another
if (basePacket == null)
break;
else
mProcessor.processPacket(conn, basePacket);
}
//Not all bytes consumed, transfer leftover to beginning
if (offset < bytesRead)
Array.Copy(conn.buffer, offset, conn.buffer, 0, bytesRead - offset);
Array.Clear(conn.buffer, bytesRead - offset, conn.buffer.Length - (bytesRead - offset));
conn.lastPartialSize = bytesRead - offset;
//Build any queued subpackets into basepackets and send
conn.flushQueuedSendPackets();
if (offset < bytesRead)
//Need offset since not all bytes consumed
conn.socket.BeginReceive(conn.buffer, bytesRead - offset, conn.buffer.Length - (bytesRead - offset), SocketFlags.None, new AsyncCallback(receiveCallback), conn);
else
//All bytes consumed, full buffer available
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), conn);
}
else
{
Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId));
conn.socket.Close();
lock (mConnectionList)
{
mConnectionList.Remove(conn);
@ -134,8 +164,8 @@ namespace FFXIVClassic_Lobby_Server
{
if (conn.socket != null)
{
Log.conn(String.Format("Connection @ {0} has disconnected.", conn.currentUserId == 0 ? "Unknown User " : "User " + conn.currentUserId));
conn.socket.Close();
Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId));
lock (mConnectionList)
{
mConnectionList.Remove(conn);
@ -144,6 +174,41 @@ namespace FFXIVClassic_Lobby_Server
}
}
/// <summary>
/// Builds a packet from the incoming buffer + offset. If a packet can be built, it is returned else null.
/// </summary>
/// <param name="offset">Current offset in buffer.</param>
/// <param name="buffer">Incoming buffer.</param>
/// <returns>Returns either a BasePacket or null if not enough data.</returns>
public BasePacket buildPacket(ref int offset, byte[] buffer, int bytesRead)
{
BasePacket newPacket = null;
//Too small to even get length
if (bytesRead <= offset)
return null;
ushort packetSize = BitConverter.ToUInt16(buffer, offset);
//Too small to whole packet
if (bytesRead < offset + packetSize)
return null;
if (buffer.Length < offset + packetSize)
return null;
try
{
newPacket = new BasePacket(buffer, ref offset);
}
catch (OverflowException)
{
return null;
}
return newPacket;
}
#endregion
}

View file

@ -24,7 +24,7 @@ namespace FFXIVClassic_Lobby_Server.common
public static string ByteArrayToHex(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[(bytes.Length * 3) + ((bytes.Length/16) < 1 ? 1 : (bytes.Length/16)*3) + bytes.Length+60];
var result = new char[(bytes.Length * 3) + ((bytes.Length / 16) < 1 ? 1 : (bytes.Length / 16) * 3) + bytes.Length + 60];
int numNewLines = 0;
for (int i = 0; i < bytes.Length; i++)
{
@ -33,45 +33,151 @@ namespace FFXIVClassic_Lobby_Server.common
result[(3 * i) + (17 * numNewLines) + 1] = (char)(val >> 16);
result[(3 * i) + (17 * numNewLines) + 2] = ' ';
result[(numNewLines * (3*16+17)) + (3 * 16) + (i % 16)] = (char)bytes[i] >= 32 && (char)bytes[i] <= 126 ? (char)bytes[i] : '.';
result[(numNewLines * (3 * 16 + 17)) + (3 * 16) + (i % 16)] = (char)bytes[i] >= 32 && (char)bytes[i] <= 126 ? (char)bytes[i] : '.';
if (i != bytes.Length - 1 && bytes.Length > 16 && i != 0 && (i+1) % 16 == 0)
if (i != bytes.Length - 1 && bytes.Length > 16 && i != 0 && (i + 1) % 16 == 0)
{
result[(numNewLines * (3*16+17)) + (3 * 16) + (16)] = '\n';
result[(numNewLines * (3 * 16 + 17)) + (3 * 16) + (16)] = '\n';
numNewLines++;
}
}
return new string(result);
}
/*
/// <summary>
/// Grabs an instance of a class directly from the MySqlDataReader instead of manually building from GetString() etc.
/// </summary>
/// <typeparam name="T">Type of object to return</typeparam>
/// <param name="reader">Reader instance</param>
/// <returns>new T</returns>
public static T GetSQLObject<T>(this MySql.Data.MySqlClient.MySqlDataReader reader)
public static UInt32 UnixTimeStampUTC()
{
var obj = Activator.CreateInstance(typeof(T));
var fields = obj.GetType().GetFields();
UInt32 unixTimeStamp;
DateTime currentTime = DateTime.Now;
DateTime zuluTime = currentTime.ToUniversalTime();
DateTime unixEpoch = new DateTime(1970, 1, 1);
unixTimeStamp = (UInt32)(zuluTime.Subtract(unixEpoch)).TotalSeconds;
foreach (var field in obj.GetType().GetFields())
return unixTimeStamp;
}
public static UInt64 MilisUnixTimeStampUTC()
{
UInt64 unixTimeStamp;
DateTime currentTime = DateTime.Now;
DateTime zuluTime = currentTime.ToUniversalTime();
DateTime unixEpoch = new DateTime(1970, 1, 1);
unixTimeStamp = (UInt64)(zuluTime.Subtract(unixEpoch)).TotalMilliseconds;
return unixTimeStamp;
}
public static ulong swapEndian(ulong input)
{
return ((0x00000000000000FF) & (input >> 56) |
(0x000000000000FF00) & (input >> 40) |
(0x0000000000FF0000) & (input >> 24) |
(0x00000000FF000000) & (input >> 8) |
(0x000000FF00000000) & (input << 8) |
(0x0000FF0000000000) & (input << 24) |
(0x00FF000000000000) & (input << 40) |
(0xFF00000000000000) & (input << 56));
}
public static uint swapEndian(uint input)
{
return ((input >> 24) & 0xff) |
((input << 8) & 0xff0000) |
((input >> 8) & 0xff00) |
((input << 24) & 0xff000000);
}
public static int swapEndian(int input)
{
uint inputAsUint = (uint)input;
input = (int)
(((inputAsUint >> 24) & 0xff) |
((inputAsUint << 8) & 0xff0000) |
((inputAsUint >> 8) & 0xff00) |
((inputAsUint << 24) & 0xff000000));
return input;
}
public static uint MurmurHash2(string key, uint seed)
{
// 'm' and 'r' are mixing constants generated offline.
// They're not really 'magic', they just happen to work well.
byte[] data = Encoding.ASCII.GetBytes(key);
const uint m = 0x5bd1e995;
const int r = 24;
int len = key.Length;
int dataIndex = len - 4;
// Initialize the hash to a 'random' value
uint h = seed ^ (uint)len;
// Mix 4 bytes at a time into the hash
while (len >= 4)
{
var attrs = field.GetCustomAttributes(typeof(System.Runtime.Serialization.DataMemberAttribute), true) as System.Runtime.Serialization.DataMemberAttribute[];
h *= m;
if (attrs.Length == 0)
{
continue;
}
uint k = (uint)BitConverter.ToInt32(data, dataIndex);
k = ((k >> 24) & 0xff) | // move byte 3 to byte 0
((k << 8) & 0xff0000) | // move byte 1 to byte 2
((k >> 8) & 0xff00) | // move byte 2 to byte 1
((k << 24) & 0xff000000); // byte 0 to byte 3
var fieldID = attrs[0].Name;
var fieldType = field.FieldType;
k *= m;
k ^= k >> r;
k *= m;
field.SetValue(obj, reader.GetValue(reader.GetOrdinal(fieldID)));
h ^= k;
dataIndex -= 4;
len -= 4;
}
return (T)obj;
}*/
// Handle the last few bytes of the input array
switch (len)
{
case 3:
h ^= (uint)data[0] << 16; goto case 2;
case 2:
h ^= (uint)data[len - 2] << 8; goto case 1;
case 1:
h ^= data[len - 1];
h *= m;
break;
};
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
public static byte[] ConvertBoolArrayToBinaryStream(bool[] array)
{
byte[] data = new byte[(array.Length / 8) + (array.Length % 8 != 0 ? 1 : 0)];
int dataCounter = 0;
for (int i = 0; i < array.Length; i += 8)
{
for (int bitCount = 0; bitCount < 8; bitCount++)
{
if (i + bitCount >= array.Length)
break;
data[dataCounter] = (byte)(((array[i + bitCount] ? 1 : 0) << 7 - bitCount) | data[dataCounter]);
}
dataCounter++;
}
return data;
}
}
}

View file

@ -10,7 +10,6 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
{
////////////
//Chara Info
public byte tribe = 0;
public byte size = 0;
public byte voice = 0;
public ushort skinColor = 0;

View file

@ -44,6 +44,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
public uint currentClass = 0;
public uint currentJob = 0;
public uint allegiance = 0;
public uint tribe = 0;
public uint currentLevel = 1;
@ -60,7 +61,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
{
uint version = reader.ReadUInt32();
uint unknown1 = reader.ReadUInt32();
appearance.tribe = reader.ReadByte();
info.tribe = reader.ReadByte();
appearance.size = reader.ReadByte();
appearance.hairStyle = reader.ReadUInt16();
appearance.hairHighlightColor = reader.ReadUInt16();
@ -106,7 +107,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
return info;
}
public String buildForCharaList(Character chara, Appearance appearance)
public static String buildForCharaList(Character chara, Appearance appearance)
{
byte[] data;
@ -137,7 +138,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
writer.Write(System.Text.Encoding.UTF8.GetBytes(chara.name + '\0'));
writer.Write((UInt32)0x1c);
writer.Write((UInt32)0x04);
writer.Write((UInt32)getTribeModel(appearance.tribe));
writer.Write((UInt32)getTribeModel(chara.tribe));
writer.Write((UInt32)appearance.size);
uint colorVal = appearance.skinColor | (uint)(appearance.hairColor << 10) | (uint)(appearance.eyeColor << 20);
writer.Write((UInt32)colorVal);
@ -185,7 +186,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
writer.Write((UInt16)chara.currentLevel);
writer.Write((byte)chara.currentJob);
writer.Write((UInt16)1);
writer.Write((byte)appearance.tribe);
writer.Write((byte)chara.tribe);
writer.Write((UInt32)0xe22222aa);
@ -204,8 +205,8 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
writer.BaseStream.Seek(0x10, SeekOrigin.Current);
writer.Write((UInt32)chara.allegiance);
writer.Write((UInt32)chara.allegiance);
writer.Write((UInt32)chara.initialTown);
writer.Write((UInt32)chara.initialTown);
}
data = stream.GetBuffer();
@ -223,7 +224,7 @@ namespace FFXIVClassic_Lobby_Server.dataobjects
return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_');
}
public UInt32 getTribeModel(byte tribe)
public static UInt32 getTribeModel(byte tribe)
{
switch (tribe)
{

View file

@ -20,12 +20,13 @@ namespace FFXIVClassic_Lobby_Server
public bool doRename;
public uint currentZoneId;
public uint guardian = 0;
public uint birthMonth = 0;
public uint birthDay = 0;
public uint currentClass = 0;
public byte guardian = 0;
public byte birthMonth = 0;
public byte birthDay = 0;
public uint currentClass = 3;
public uint currentJob = 0;
public uint allegiance = 0;
public byte initialTown = 0;
public byte tribe = 0;
public uint currentLevel = 1;

View file

@ -10,24 +10,29 @@ using System.IO;
namespace FFXIVClassic_Lobby_Server.packets
{
[StructLayout(LayoutKind.Sequential)]
public struct BasePacketHeader
{
public byte isAuthenticated;
public byte isEncrypted;
public ushort reserved;
public ushort packetSize;
public ushort numSubpackets;
public uint unknown1; //Id?
public uint unknown2; //Usually 0x13B
public byte isAuthenticated;
public byte isEncrypted;
public ushort connectionType;
public ushort packetSize;
public ushort numSubpackets;
public ulong timestamp; //Miliseconds
}
public class BasePacket{
public class BasePacket
{
public const int TYPE_ZONE = 1;
public const int TYPE_CHAT = 2;
public const int BASEPACKET_SIZE = 0x10;
public BasePacketHeader header;
public byte[] data;
public byte[] data;
//Loads a sniffed packet from a file
public unsafe BasePacket(String path)
{
byte[] bytes = File.ReadAllBytes(path);
@ -45,10 +50,16 @@ namespace FFXIVClassic_Lobby_Server.packets
int packetSize = header.packetSize;
data = new byte[packetSize - BASEPACKET_SIZE];
Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE);
if (packetSize - BASEPACKET_SIZE != 0)
{
data = new byte[packetSize - BASEPACKET_SIZE];
Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE);
}
else
data = new byte[0];
}
//Loads a sniffed packet from a byte array
public unsafe BasePacket(byte[] bytes)
{
if (bytes.Length < BASEPACKET_SIZE)
@ -141,6 +152,52 @@ namespace FFXIVClassic_Lobby_Server.packets
return outBytes;
}
//Replaces all instances of the sniffed actorID with the given one
public void replaceActorID(uint actorID)
{
using (MemoryStream mem = new MemoryStream(data))
{
using (BinaryWriter binWriter = new BinaryWriter(mem))
{
using (BinaryReader binreader = new BinaryReader(mem))
{
while (binreader.BaseStream.Position + 4 < data.Length)
{
uint read = binreader.ReadUInt32();
if (read == 0x029B2941 || read == 0x02977DC7 || read == 0x0297D2C8 || read == 0x0230d573 || read == 0x23317df || read == 0x23344a3 || read == 0x1730bdb) //Original ID
{
binWriter.BaseStream.Seek(binreader.BaseStream.Position - 0x4, SeekOrigin.Begin);
binWriter.Write(actorID);
}
}
}
}
}
}
//Replaces all instances of the sniffed actorID with the given one
public void replaceActorID(uint fromActorID, uint actorID)
{
using (MemoryStream mem = new MemoryStream(data))
{
using (BinaryWriter binWriter = new BinaryWriter(mem))
{
using (BinaryReader binreader = new BinaryReader(mem))
{
while (binreader.BaseStream.Position + 4 < data.Length)
{
uint read = binreader.ReadUInt32();
if (read == fromActorID) //Original ID
{
binWriter.BaseStream.Seek(binreader.BaseStream.Position - 0x4, SeekOrigin.Begin);
binWriter.Write(actorID);
}
}
}
}
}
}
#region Utility Functions
public static BasePacket createPacket(List<SubPacket> subpackets, bool isAuthed, bool isEncrypted)
{
@ -148,16 +205,17 @@ namespace FFXIVClassic_Lobby_Server.packets
BasePacketHeader header = new BasePacketHeader();
byte[] data = null;
header.isAuthenticated = isAuthed?(byte)1:(byte)0;
header.isEncrypted = isEncrypted?(byte)1:(byte)0;
header.isAuthenticated = isAuthed ? (byte)1 : (byte)0;
header.isEncrypted = isEncrypted ? (byte)1 : (byte)0;
header.numSubpackets = (ushort)subpackets.Count;
header.packetSize = BASEPACKET_SIZE;
header.timestamp = Utils.MilisUnixTimeStampUTC();
//Get packet size
foreach (SubPacket subpacket in subpackets)
header.packetSize += subpacket.header.subpacketSize;
data = new byte[header.packetSize-0x10];
data = new byte[header.packetSize - 0x10];
//Add Subpackets
int offset = 0;
@ -184,6 +242,7 @@ namespace FFXIVClassic_Lobby_Server.packets
header.isEncrypted = isEncrypted ? (byte)1 : (byte)0;
header.numSubpackets = (ushort)1;
header.packetSize = BASEPACKET_SIZE;
header.timestamp = Utils.MilisUnixTimeStampUTC();
//Get packet size
header.packetSize += subpacket.header.subpacketSize;
@ -200,13 +259,35 @@ namespace FFXIVClassic_Lobby_Server.packets
return packet;
}
public static BasePacket createPacket(byte[] data, bool isAuthed, bool isEncrypted)
{
Debug.Assert(data != null);
//Create Header
BasePacketHeader header = new BasePacketHeader();
header.isAuthenticated = isAuthed ? (byte)1 : (byte)0;
header.isEncrypted = isEncrypted ? (byte)1 : (byte)0;
header.numSubpackets = (ushort)1;
header.packetSize = BASEPACKET_SIZE;
header.timestamp = Utils.MilisUnixTimeStampUTC();
//Get packet size
header.packetSize += (ushort)data.Length;
BasePacket packet = new BasePacket(header, data);
return packet;
}
public static unsafe void encryptPacket(Blowfish blowfish, BasePacket packet)
{
byte[] data = packet.data;
int size = packet.header.packetSize;
int offset = 0;
while (offset < data.Length) {
while (offset < data.Length)
{
if (data.Length < offset + SubPacket.SUBPACKET_SIZE)
throw new OverflowException("Packet Error: Subpacket was too small");
@ -219,7 +300,7 @@ namespace FFXIVClassic_Lobby_Server.packets
if (data.Length < offset + header.subpacketSize)
throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data");
blowfish.Encipher(data, offset + 0x10, header.subpacketSize-0x10);
blowfish.Encipher(data, offset + 0x10, header.subpacketSize - 0x10);
offset += header.subpacketSize;
}
@ -246,7 +327,7 @@ namespace FFXIVClassic_Lobby_Server.packets
if (data.Length < offset + header.subpacketSize)
throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data");
blowfish.Decipher(data, offset + 0x10, header.subpacketSize-0x10);
blowfish.Decipher(data, offset + 0x10, header.subpacketSize - 0x10);
offset += header.subpacketSize;
}

View file

@ -77,8 +77,7 @@ namespace FFXIVClassic_Lobby_Server.packets
binWriter.Write(Encoding.ASCII.GetBytes(chara.name.PadRight(0x20, '\0'))); //Name
binWriter.Write(Encoding.ASCII.GetBytes(worldname.PadRight(0xE, '\0'))); //World Name
CharaInfo info = JsonConvert.DeserializeObject<CharaInfo>(chara.charaInfo);
binWriter.Write(info.buildForCharaList(chara, appearance)); //Appearance Data
binWriter.Write(CharaInfo.buildForCharaList(chara, appearance)); //Appearance Data
//binWriter.Write(CharaInfo.debug()); //Appearance Data
characterCount++;

View file

@ -12,24 +12,31 @@ namespace FFXIVClassic_Lobby_Server.packets
[StructLayout(LayoutKind.Sequential)]
public struct SubPacketHeader
{
public ushort subpacketSize;
public ushort unknown0; //Always 0x03
public uint sourceId;
public uint targetId;
public uint unknown1;
public ushort unknown4; //Always 0x14
public ushort opcode;
public uint unknown5;
public uint timestamp;
public uint unknown6;
public ushort subpacketSize;
public ushort type;
public uint sourceId;
public uint targetId;
public uint unknown1;
}
[StructLayout(LayoutKind.Sequential)]
public struct GameMessageHeader
{
public ushort unknown4; //Always 0x14
public ushort opcode;
public uint unknown5;
public uint timestamp;
public uint unknown6;
}
public class SubPacket
{
public const int SUBPACKET_SIZE = 0x20;
public const int SUBPACKET_SIZE = 0x10;
public const int GAMEMESSAGE_SIZE = 0x10;
public SubPacketHeader header;
public byte[] data;
public SubPacketHeader header;
public GameMessageHeader gameMessage;
public byte[] data;
public unsafe SubPacket(byte[] bytes, ref int offset)
{
@ -41,11 +48,27 @@ namespace FFXIVClassic_Lobby_Server.packets
header = (SubPacketHeader)Marshal.PtrToStructure(new IntPtr(pdata), typeof(SubPacketHeader));
}
if (header.type == 0x3)
{
fixed (byte* pdata = &bytes[offset + SUBPACKET_SIZE])
{
gameMessage = (GameMessageHeader)Marshal.PtrToStructure(new IntPtr(pdata), typeof(GameMessageHeader));
}
}
if (bytes.Length < offset + header.subpacketSize)
throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data");
data = new byte[header.subpacketSize - SUBPACKET_SIZE];
Array.Copy(bytes, offset + SUBPACKET_SIZE, data, 0, data.Length);
if (header.type == 0x3)
{
data = new byte[header.subpacketSize - SUBPACKET_SIZE - GAMEMESSAGE_SIZE];
Array.Copy(bytes, offset + SUBPACKET_SIZE + GAMEMESSAGE_SIZE, data, 0, data.Length);
}
else
{
data = new byte[header.subpacketSize - SUBPACKET_SIZE];
Array.Copy(bytes, offset + SUBPACKET_SIZE, data, 0, data.Length);
}
offset += header.subpacketSize;
}
@ -53,22 +76,34 @@ namespace FFXIVClassic_Lobby_Server.packets
public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data)
{
this.header = new SubPacketHeader();
header.opcode = opcode;
this.gameMessage = new GameMessageHeader();
gameMessage.opcode = opcode;
header.sourceId = sourceId;
header.targetId = targetId;
UInt32 unixTimestamp = (UInt32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
header.timestamp = unixTimestamp;
gameMessage.timestamp = Utils.UnixTimeStampUTC();
header.unknown0 = 0x03;
header.type = 0x03;
header.unknown1 = 0x00;
header.unknown4 = 0x14;
header.unknown5 = 0x00;
header.unknown6 = 0x00;
gameMessage.unknown4 = 0x14;
gameMessage.unknown5 = 0x00;
gameMessage.unknown6 = 0x00;
this.data = data;
header.subpacketSize = (ushort)(0x20 + data.Length);
header.subpacketSize = (ushort)(SUBPACKET_SIZE + GAMEMESSAGE_SIZE + data.Length);
}
public SubPacket(SubPacket original, uint newTargetId)
{
this.header = new SubPacketHeader();
this.gameMessage = original.gameMessage;
header.subpacketSize = original.header.subpacketSize;
header.type = original.header.type;
header.sourceId = original.header.sourceId;
header.targetId = newTargetId;
data = original.data;
}
public byte[] getHeaderBytes()
@ -83,11 +118,27 @@ namespace FFXIVClassic_Lobby_Server.packets
return arr;
}
public byte[] getGameMessageBytes()
{
int size = Marshal.SizeOf(gameMessage);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(gameMessage, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
public byte[] getBytes()
{
byte[] outBytes = new byte[header.subpacketSize];
Array.Copy(getHeaderBytes(), 0, outBytes, 0, SUBPACKET_SIZE);
Array.Copy(data, 0, outBytes, SUBPACKET_SIZE, data.Length);
if (header.type == 0x3)
Array.Copy(getGameMessageBytes(), 0, outBytes, SUBPACKET_SIZE, GAMEMESSAGE_SIZE);
Array.Copy(data, 0, outBytes, SUBPACKET_SIZE + (header.type == 0x3 ? GAMEMESSAGE_SIZE : 0), data.Length);
return outBytes;
}
@ -95,8 +146,12 @@ namespace FFXIVClassic_Lobby_Server.packets
{
#if DEBUG
Console.BackgroundColor = ConsoleColor.DarkRed;
Console.WriteLine("Size: 0x{0:X}, Opcode: 0x{1:X}", header.subpacketSize, header.opcode);
Console.WriteLine("Size: 0x{0:X}", header.subpacketSize);
if (header.type == 0x03)
Console.WriteLine("Opcode: 0x{0:X}", gameMessage.opcode);
Console.WriteLine("{0}", Utils.ByteArrayToHex(getHeaderBytes()));
if (header.type == 0x03)
Console.WriteLine("{0}", Utils.ByteArrayToHex(getGameMessageBytes()));
Console.BackgroundColor = ConsoleColor.DarkMagenta;
Console.WriteLine("{0}", Utils.ByteArrayToHex(data));
Console.BackgroundColor = ConsoleColor.Black;