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 byte[] buffer = new byte[0xffff];
public CircularBuffer<byte> incomingStream = new CircularBuffer<byte>(1024); public CircularBuffer<byte> incomingStream = new CircularBuffer<byte>(1024);
public BlockingCollection<BasePacket> sendPacketQueue = new BlockingCollection<BasePacket>(100); public BlockingCollection<BasePacket> sendPacketQueue = new BlockingCollection<BasePacket>(100);
public int lastPartialSize = 0;
//Instance Stuff //Instance Stuff
public uint currentUserId = 0; public uint currentUserId = 0;

View file

@ -146,7 +146,6 @@ namespace FFXIVClassic_Lobby_Server
cmd.Parameters.AddWithValue("@characterId", cid); cmd.Parameters.AddWithValue("@characterId", cid);
cmd.Parameters.AddWithValue("@baseId", 0xFFFFFFFF); cmd.Parameters.AddWithValue("@baseId", 0xFFFFFFFF);
cmd.Parameters.AddWithValue("@tribe", charaInfo.appearance.tribe);
cmd.Parameters.AddWithValue("@size", charaInfo.appearance.size); cmd.Parameters.AddWithValue("@size", charaInfo.appearance.size);
cmd.Parameters.AddWithValue("@voice", charaInfo.appearance.voice); cmd.Parameters.AddWithValue("@voice", charaInfo.appearance.voice);
cmd.Parameters.AddWithValue("@skinColor", charaInfo.appearance.skinColor); cmd.Parameters.AddWithValue("@skinColor", charaInfo.appearance.skinColor);
@ -360,7 +359,7 @@ namespace FFXIVClassic_Lobby_Server
try try
{ {
conn.Open(); 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) catch (MySqlException e)
{ {

View file

@ -15,60 +15,8 @@ namespace FFXIVClassic_Lobby_Server
{ {
class PacketProcessor class PacketProcessor
{ {
List<ClientConnection> mConnections;
Boolean isAlive = true;
public PacketProcessor(List<ClientConnection> connectionList) public void processPacket(ClientConnection client, BasePacket packet)
{
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)
{ {
if ((packet.header.packetSize == 0x288) && (packet.data[0x34] == 'T')) //Test Ticket Data 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); BasePacket.decryptPacket(client.blowfish, ref packet);
//packet.debugPrintPacket(); packet.debugPrintPacket();
List<SubPacket> subPackets = packet.getSubpackets(); List<SubPacket> subPackets = packet.getSubpackets();
foreach (SubPacket subpacket in subPackets) foreach (SubPacket subpacket in subPackets)
{ {
subpacket.debugPrintSubPacket(); subpacket.debugPrintSubPacket();
switch (subpacket.header.opcode)
if (subpacket.header.type == 3)
{ {
case 0x03: switch (subpacket.gameMessage.opcode)
ProcessGetCharacters(client, subpacket); {
break; case 0x03:
case 0x04: ProcessGetCharacters(client, subpacket);
ProcessSelectCharacter(client, subpacket); break;
break; case 0x04:
case 0x05: ProcessSelectCharacter(client, subpacket);
ProcessSessionAcknowledgement(client, subpacket); break;
break; case 0x05:
case 0x0B: ProcessSessionAcknowledgement(client, subpacket);
ProcessModifyCharacter(client, subpacket); break;
break; case 0x0B:
case 0x0F: ProcessModifyCharacter(client, subpacket);
break;
case 0x0F:
//Mod Retainers //Mod Retainers
default: default:
Log.debug(String.Format("Unknown command 0x{0:X} received.", subpacket.header.opcode)); Log.debug(String.Format("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode));
break; break;
}
} }
} }
} }

View file

@ -35,7 +35,7 @@ namespace FFXIVClassic_Lobby_Server
startServer = false; startServer = false;
//Test DB Connection //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))) 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 try

View file

@ -7,19 +7,19 @@ using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using FFXIVClassic_Lobby_Server.common; using FFXIVClassic_Lobby_Server.common;
using FFXIVClassic_Lobby_Server.packets;
namespace FFXIVClassic_Lobby_Server namespace FFXIVClassic_Lobby_Server
{ {
class Server class Server
{ {
public const int FFXIV_LOBBY_PORT = 54994; public const int FFXIV_LOBBY_PORT = 54994;
public const int BUFFER_SIZE = 0x400; public const int BUFFER_SIZE = 0xFFFF;
public const int BACKLOG = 100; public const int BACKLOG = 100;
private Socket mServerSocket; private Socket mServerSocket;
private List<ClientConnection> mConnectionList = new List<ClientConnection>(); private List<ClientConnection> mConnectionList = new List<ClientConnection>();
private PacketProcessor mProcessor; private PacketProcessor mProcessor;
private Thread mProcessorThread;
#region Socket Handling #region Socket Handling
public bool startServer() 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.WriteLine("{0}:{1}", (mServerSocket.LocalEndPoint as IPEndPoint).Address, (mServerSocket.LocalEndPoint as IPEndPoint).Port);
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
mProcessor = new PacketProcessor(mConnectionList); mProcessor = new PacketProcessor();
mProcessorThread = new Thread(new ThreadStart(mProcessor.update));
mProcessorThread.Start();
return true; return true;
} }
@ -110,20 +109,51 @@ namespace FFXIVClassic_Lobby_Server
private void receiveCallback(IAsyncResult result) private void receiveCallback(IAsyncResult result)
{ {
ClientConnection conn = (ClientConnection)result.AsyncState; ClientConnection conn = (ClientConnection)result.AsyncState;
try try
{ {
int bytesRead = conn.socket.EndReceive(result); int bytesRead = conn.socket.EndReceive(result);
if (bytesRead > 0)
{
conn.processIncoming(bytesRead);
//Queue the next receive bytesRead += conn.lastPartialSize;
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), conn);
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 else
{ {
Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId)); Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId));
conn.socket.Close();
lock (mConnectionList) lock (mConnectionList)
{ {
mConnectionList.Remove(conn); mConnectionList.Remove(conn);
@ -134,8 +164,8 @@ namespace FFXIVClassic_Lobby_Server
{ {
if (conn.socket != null) if (conn.socket != null)
{ {
Log.conn(String.Format("Connection @ {0} has disconnected.", conn.currentUserId == 0 ? "Unknown User " : "User " + conn.currentUserId)); Log.conn(String.Format("{0} has disconnected.", conn.currentUserId == 0 ? conn.getAddress() : "User " + conn.currentUserId));
conn.socket.Close();
lock (mConnectionList) lock (mConnectionList)
{ {
mConnectionList.Remove(conn); 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 #endregion
} }

View file

@ -24,7 +24,7 @@ namespace FFXIVClassic_Lobby_Server.common
public static string ByteArrayToHex(byte[] bytes) public static string ByteArrayToHex(byte[] bytes)
{ {
var lookup32 = _lookup32; 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; int numNewLines = 0;
for (int i = 0; i < bytes.Length; i++) 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) + 1] = (char)(val >> 16);
result[(3 * i) + (17 * numNewLines) + 2] = ' '; 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++; numNewLines++;
} }
} }
return new string(result); return new string(result);
} }
/*
/// <summary> public static UInt32 UnixTimeStampUTC()
/// 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)
{ {
var obj = Activator.CreateInstance(typeof(T)); UInt32 unixTimeStamp;
var fields = obj.GetType().GetFields(); 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) uint k = (uint)BitConverter.ToInt32(data, dataIndex);
{ k = ((k >> 24) & 0xff) | // move byte 3 to byte 0
continue; ((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; k *= m;
var fieldType = field.FieldType; 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 //Chara Info
public byte tribe = 0;
public byte size = 0; public byte size = 0;
public byte voice = 0; public byte voice = 0;
public ushort skinColor = 0; public ushort skinColor = 0;

View file

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

View file

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

View file

@ -10,24 +10,29 @@ using System.IO;
namespace FFXIVClassic_Lobby_Server.packets namespace FFXIVClassic_Lobby_Server.packets
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct BasePacketHeader public struct BasePacketHeader
{ {
public byte isAuthenticated; public byte isAuthenticated;
public byte isEncrypted; public byte isEncrypted;
public ushort reserved; public ushort connectionType;
public ushort packetSize; public ushort packetSize;
public ushort numSubpackets; public ushort numSubpackets;
public uint unknown1; //Id? public ulong timestamp; //Miliseconds
public uint unknown2; //Usually 0x13B
} }
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 const int BASEPACKET_SIZE = 0x10;
public BasePacketHeader header; public BasePacketHeader header;
public byte[] data; public byte[] data;
//Loads a sniffed packet from a file
public unsafe BasePacket(String path) public unsafe BasePacket(String path)
{ {
byte[] bytes = File.ReadAllBytes(path); byte[] bytes = File.ReadAllBytes(path);
@ -45,10 +50,16 @@ namespace FFXIVClassic_Lobby_Server.packets
int packetSize = header.packetSize; int packetSize = header.packetSize;
data = new byte[packetSize - BASEPACKET_SIZE]; if (packetSize - BASEPACKET_SIZE != 0)
Array.Copy(bytes, BASEPACKET_SIZE, data, 0, packetSize - BASEPACKET_SIZE); {
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) public unsafe BasePacket(byte[] bytes)
{ {
if (bytes.Length < BASEPACKET_SIZE) if (bytes.Length < BASEPACKET_SIZE)
@ -141,6 +152,52 @@ namespace FFXIVClassic_Lobby_Server.packets
return outBytes; 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 #region Utility Functions
public static BasePacket createPacket(List<SubPacket> subpackets, bool isAuthed, bool isEncrypted) public static BasePacket createPacket(List<SubPacket> subpackets, bool isAuthed, bool isEncrypted)
{ {
@ -148,16 +205,17 @@ namespace FFXIVClassic_Lobby_Server.packets
BasePacketHeader header = new BasePacketHeader(); BasePacketHeader header = new BasePacketHeader();
byte[] data = null; byte[] data = null;
header.isAuthenticated = isAuthed?(byte)1:(byte)0; header.isAuthenticated = isAuthed ? (byte)1 : (byte)0;
header.isEncrypted = isEncrypted?(byte)1:(byte)0; header.isEncrypted = isEncrypted ? (byte)1 : (byte)0;
header.numSubpackets = (ushort)subpackets.Count; header.numSubpackets = (ushort)subpackets.Count;
header.packetSize = BASEPACKET_SIZE; header.packetSize = BASEPACKET_SIZE;
header.timestamp = Utils.MilisUnixTimeStampUTC();
//Get packet size //Get packet size
foreach (SubPacket subpacket in subpackets) foreach (SubPacket subpacket in subpackets)
header.packetSize += subpacket.header.subpacketSize; header.packetSize += subpacket.header.subpacketSize;
data = new byte[header.packetSize-0x10]; data = new byte[header.packetSize - 0x10];
//Add Subpackets //Add Subpackets
int offset = 0; int offset = 0;
@ -184,6 +242,7 @@ namespace FFXIVClassic_Lobby_Server.packets
header.isEncrypted = isEncrypted ? (byte)1 : (byte)0; header.isEncrypted = isEncrypted ? (byte)1 : (byte)0;
header.numSubpackets = (ushort)1; header.numSubpackets = (ushort)1;
header.packetSize = BASEPACKET_SIZE; header.packetSize = BASEPACKET_SIZE;
header.timestamp = Utils.MilisUnixTimeStampUTC();
//Get packet size //Get packet size
header.packetSize += subpacket.header.subpacketSize; header.packetSize += subpacket.header.subpacketSize;
@ -200,13 +259,35 @@ namespace FFXIVClassic_Lobby_Server.packets
return packet; 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) public static unsafe void encryptPacket(Blowfish blowfish, BasePacket packet)
{ {
byte[] data = packet.data; byte[] data = packet.data;
int size = packet.header.packetSize; int size = packet.header.packetSize;
int offset = 0; int offset = 0;
while (offset < data.Length) { while (offset < data.Length)
{
if (data.Length < offset + SubPacket.SUBPACKET_SIZE) if (data.Length < offset + SubPacket.SUBPACKET_SIZE)
throw new OverflowException("Packet Error: Subpacket was too small"); throw new OverflowException("Packet Error: Subpacket was too small");
@ -219,7 +300,7 @@ namespace FFXIVClassic_Lobby_Server.packets
if (data.Length < offset + header.subpacketSize) if (data.Length < offset + header.subpacketSize)
throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data"); 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; offset += header.subpacketSize;
} }
@ -246,7 +327,7 @@ namespace FFXIVClassic_Lobby_Server.packets
if (data.Length < offset + header.subpacketSize) if (data.Length < offset + header.subpacketSize)
throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data"); 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; 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(chara.name.PadRight(0x20, '\0'))); //Name
binWriter.Write(Encoding.ASCII.GetBytes(worldname.PadRight(0xE, '\0'))); //World Name binWriter.Write(Encoding.ASCII.GetBytes(worldname.PadRight(0xE, '\0'))); //World Name
CharaInfo info = JsonConvert.DeserializeObject<CharaInfo>(chara.charaInfo); binWriter.Write(CharaInfo.buildForCharaList(chara, appearance)); //Appearance Data
binWriter.Write(info.buildForCharaList(chara, appearance)); //Appearance Data
//binWriter.Write(CharaInfo.debug()); //Appearance Data //binWriter.Write(CharaInfo.debug()); //Appearance Data
characterCount++; characterCount++;

View file

@ -12,24 +12,31 @@ namespace FFXIVClassic_Lobby_Server.packets
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct SubPacketHeader public struct SubPacketHeader
{ {
public ushort subpacketSize; public ushort subpacketSize;
public ushort unknown0; //Always 0x03 public ushort type;
public uint sourceId; public uint sourceId;
public uint targetId; public uint targetId;
public uint unknown1; public uint unknown1;
public ushort unknown4; //Always 0x14 }
public ushort opcode;
public uint unknown5; [StructLayout(LayoutKind.Sequential)]
public uint timestamp; public struct GameMessageHeader
public uint unknown6; {
public ushort unknown4; //Always 0x14
public ushort opcode;
public uint unknown5;
public uint timestamp;
public uint unknown6;
} }
public class SubPacket 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 SubPacketHeader header;
public byte[] data; public GameMessageHeader gameMessage;
public byte[] data;
public unsafe SubPacket(byte[] bytes, ref int offset) 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)); 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) if (bytes.Length < offset + header.subpacketSize)
throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data"); throw new OverflowException("Packet Error: Subpacket size didn't equal subpacket data");
data = new byte[header.subpacketSize - SUBPACKET_SIZE]; if (header.type == 0x3)
Array.Copy(bytes, offset + SUBPACKET_SIZE, data, 0, data.Length); {
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; offset += header.subpacketSize;
} }
@ -53,22 +76,34 @@ namespace FFXIVClassic_Lobby_Server.packets
public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data) public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data)
{ {
this.header = new SubPacketHeader(); this.header = new SubPacketHeader();
header.opcode = opcode; this.gameMessage = new GameMessageHeader();
gameMessage.opcode = opcode;
header.sourceId = sourceId; header.sourceId = sourceId;
header.targetId = targetId; header.targetId = targetId;
UInt32 unixTimestamp = (UInt32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; gameMessage.timestamp = Utils.UnixTimeStampUTC();
header.timestamp = unixTimestamp;
header.unknown0 = 0x03; header.type = 0x03;
header.unknown1 = 0x00; header.unknown1 = 0x00;
header.unknown4 = 0x14; gameMessage.unknown4 = 0x14;
header.unknown5 = 0x00; gameMessage.unknown5 = 0x00;
header.unknown6 = 0x00; gameMessage.unknown6 = 0x00;
this.data = data; 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() public byte[] getHeaderBytes()
@ -83,11 +118,27 @@ namespace FFXIVClassic_Lobby_Server.packets
return arr; 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() public byte[] getBytes()
{ {
byte[] outBytes = new byte[header.subpacketSize]; byte[] outBytes = new byte[header.subpacketSize];
Array.Copy(getHeaderBytes(), 0, outBytes, 0, SUBPACKET_SIZE); 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; return outBytes;
} }
@ -95,8 +146,12 @@ namespace FFXIVClassic_Lobby_Server.packets
{ {
#if DEBUG #if DEBUG
Console.BackgroundColor = ConsoleColor.DarkRed; 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())); Console.WriteLine("{0}", Utils.ByteArrayToHex(getHeaderBytes()));
if (header.type == 0x03)
Console.WriteLine("{0}", Utils.ByteArrayToHex(getGameMessageBytes()));
Console.BackgroundColor = ConsoleColor.DarkMagenta; Console.BackgroundColor = ConsoleColor.DarkMagenta;
Console.WriteLine("{0}", Utils.ByteArrayToHex(data)); Console.WriteLine("{0}", Utils.ByteArrayToHex(data));
Console.BackgroundColor = ConsoleColor.Black; Console.BackgroundColor = ConsoleColor.Black;