mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-14 11:31:30 +00:00
622 lines
23 KiB
C#
622 lines
23 KiB
C#
using Ionic.Zlib;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace StreamParser.Common.Daybreak
|
|
{
|
|
public class Connection : IConnection
|
|
{
|
|
private class EncodeType
|
|
{
|
|
public const int None = 0;
|
|
public const int Compression = 1;
|
|
public const int XOR = 4;
|
|
}
|
|
|
|
private enum SequenceOrder
|
|
{
|
|
Past,
|
|
Current,
|
|
Future
|
|
}
|
|
|
|
private readonly IParser _owner;
|
|
private readonly IPAddress _srcAddr;
|
|
private readonly int _srcPort;
|
|
private readonly IPAddress _dstAddr;
|
|
private readonly int _dstPort;
|
|
private readonly Util.Crc32 _crc_generator = new Util.Crc32();
|
|
private readonly Guid _id = Guid.NewGuid();
|
|
private uint _connect_code = 0;
|
|
private int _encode_key = 0;
|
|
private int _crc_bytes = 0;
|
|
private int[] _encode_pass = new int[2] { 0, 0 };
|
|
private ConnectionStream[] _client_streams = new ConnectionStream[4] {
|
|
new ConnectionStream(),
|
|
new ConnectionStream(),
|
|
new ConnectionStream(),
|
|
new ConnectionStream()
|
|
};
|
|
private ConnectionStream[] _server_streams = new ConnectionStream[4] {
|
|
new ConnectionStream(),
|
|
new ConnectionStream(),
|
|
new ConnectionStream(),
|
|
new ConnectionStream()
|
|
};
|
|
|
|
public IConnection.OnPacketRecvHandler OnPacketRecv { get; set; }
|
|
|
|
public IPAddress ClientAddress => _srcAddr;
|
|
public int ClientPort => _srcPort;
|
|
public IPAddress ServerAddress => _dstAddr;
|
|
public int ServerPort => _dstPort;
|
|
public Guid Id => _id;
|
|
|
|
public ConnectionType ConnectionType {
|
|
get
|
|
{
|
|
//World servers used to be coded to always be 9000 but live started using dynamic ports
|
|
//I've seen from 9000 to 9008 on live
|
|
if (_dstPort >= 9000 && _dstPort <= 9010)
|
|
{
|
|
return ConnectionType.World;
|
|
}
|
|
else if (_encode_pass[0] == EncodeType.None && _encode_pass[1] == EncodeType.None)
|
|
{
|
|
return ConnectionType.Login;
|
|
}
|
|
else if (_encode_pass[0] == EncodeType.XOR && _encode_pass[1] == EncodeType.None)
|
|
{
|
|
return ConnectionType.Chat;
|
|
}
|
|
else if (_encode_pass[0] == EncodeType.Compression && _encode_pass[1] == EncodeType.None)
|
|
{
|
|
return ConnectionType.Zone;
|
|
}
|
|
|
|
return ConnectionType.Unknown;
|
|
}
|
|
}
|
|
|
|
public Connection(IParser owner, IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort)
|
|
{
|
|
_owner = owner;
|
|
_srcAddr = srcAddr;
|
|
_srcPort = srcPort;
|
|
_dstAddr = dstAddr;
|
|
_dstPort = dstPort;
|
|
}
|
|
|
|
public void ProcessPacket(IPAddress srcAddr, int srcPort, DateTime packetTime, ReadOnlySpan<byte> data)
|
|
{
|
|
if (data.Length < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var opcode = data[1];
|
|
if (data[0] == 0 && (opcode == Opcode.KeepAlive || opcode == Opcode.OutboundPing))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (PacketCanBeDecoded(data))
|
|
{
|
|
if (!ValidateCRC(data))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_encode_pass[0] == EncodeType.None && _encode_pass[1] == EncodeType.None)
|
|
{
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime, data.Slice(0, data.Length - _crc_bytes));
|
|
}
|
|
else
|
|
{
|
|
//unfortunately we can't avoid a copy here
|
|
var temp = data.Slice(0, data.Length - _crc_bytes).ToArray();
|
|
for (int i = 1; i >= 0; --i)
|
|
{
|
|
switch(_encode_pass[i])
|
|
{
|
|
case EncodeType.Compression:
|
|
if(temp[0] == 0)
|
|
{
|
|
temp = Decompress(temp, 2, temp.Length - 2);
|
|
}
|
|
else
|
|
{
|
|
temp = Decompress(temp, 1, temp.Length - 1);
|
|
}
|
|
break;
|
|
case EncodeType.XOR:
|
|
if (temp[0] == 0)
|
|
{
|
|
temp = Decode(temp, 2, temp.Length - 2);
|
|
}
|
|
else
|
|
{
|
|
temp = Decode(temp, 1, temp.Length - 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime, temp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime, data);
|
|
}
|
|
}
|
|
|
|
private void ProcessDecodedPacket(IPAddress srcAddr, int srcPort, DateTime packetTime, ReadOnlySpan<byte> data)
|
|
{
|
|
if (data.Length < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (data[0] == 0)
|
|
{
|
|
if (data.Length < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var opcode = data[1];
|
|
switch (opcode)
|
|
{
|
|
case Opcode.SessionResponse:
|
|
if (_connect_code == 0)
|
|
{
|
|
//if(data.Length != 21)
|
|
//{
|
|
// return;
|
|
//}
|
|
|
|
_connect_code = BitConverter.ToUInt32(data.Slice(2, 4));
|
|
_encode_key = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data.Slice(6, 4)));
|
|
_crc_bytes = data[10];
|
|
_encode_pass[0] = data[11];
|
|
_encode_pass[1] = data[12];
|
|
_owner.OnNewConnection?.Invoke(this, packetTime);
|
|
}
|
|
break;
|
|
case Opcode.SessionDisconnect:
|
|
if(_connect_code != 0)
|
|
{
|
|
_connect_code = 0;
|
|
_encode_key = 0;
|
|
_crc_bytes = 0;
|
|
_encode_pass[0] = 0;
|
|
_encode_pass[1] = 0;
|
|
_owner.OnLostConnection?.Invoke(this, packetTime);
|
|
}
|
|
break;
|
|
case Opcode.Combined:
|
|
{
|
|
int current = 2;
|
|
int end = data.Length;
|
|
while (current < end)
|
|
{
|
|
byte subpacket_length = data[current];
|
|
current += 1;
|
|
|
|
if (end < current + subpacket_length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var subpacket = data.Slice(current, subpacket_length);
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime, subpacket);
|
|
current += subpacket_length;
|
|
}
|
|
}
|
|
break;
|
|
case Opcode.AppCombined:
|
|
{
|
|
int current = 2;
|
|
int end = data.Length;
|
|
while (current < end)
|
|
{
|
|
int subpacket_length = 0;
|
|
if (data[current] == 0xff)
|
|
{
|
|
if (end < current + 3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (data[current + 1] == 0xff && data[current + 2] == 0xff)
|
|
{
|
|
if (end < current + 7)
|
|
{
|
|
return;
|
|
}
|
|
|
|
subpacket_length =
|
|
((data[current + 3]) << 24) |
|
|
((data[current + 4]) << 16) |
|
|
((data[current + 5]) << 8) |
|
|
(data[current + 6]);
|
|
|
|
current += 7;
|
|
}
|
|
else
|
|
{
|
|
subpacket_length =
|
|
((data[current + 1]) << 8) |
|
|
(data[current + 2]);
|
|
|
|
current += 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
subpacket_length = data[current];
|
|
current += 1;
|
|
}
|
|
|
|
var subpacket = data.Slice(current, subpacket_length);
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime, subpacket);
|
|
current += subpacket_length;
|
|
}
|
|
}
|
|
break;
|
|
case Opcode.Packet:
|
|
case Opcode.Packet2:
|
|
case Opcode.Packet3:
|
|
case Opcode.Packet4:
|
|
{
|
|
var stream_id = opcode - Opcode.Packet;
|
|
var stream = FindStream(srcAddr, srcPort, stream_id);
|
|
var sequence = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(2, 2)));
|
|
var order = CompareSequence(stream.Sequence, sequence);
|
|
if (order == SequenceOrder.Future)
|
|
{
|
|
if (!stream.PacketQueue.ContainsKey(sequence))
|
|
{
|
|
stream.PacketQueue.Add(sequence, new ConnectionStream.QueuedPacket
|
|
{
|
|
Data = data.ToArray(),
|
|
PacketTime = packetTime
|
|
});
|
|
}
|
|
}
|
|
else if (order == SequenceOrder.Current)
|
|
{
|
|
if (stream.PacketQueue.ContainsKey(sequence))
|
|
{
|
|
stream.PacketQueue.Remove(sequence);
|
|
}
|
|
|
|
stream.Sequence++;
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime, data.Slice(4));
|
|
ProcessQueue(srcAddr, srcPort, stream_id);
|
|
}
|
|
}
|
|
break;
|
|
case Opcode.Fragment:
|
|
case Opcode.Fragment2:
|
|
case Opcode.Fragment3:
|
|
case Opcode.Fragment4:
|
|
{
|
|
var stream_id = opcode - Opcode.Fragment;
|
|
var stream = FindStream(srcAddr, srcPort, stream_id);
|
|
var sequence = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(2, 2)));
|
|
var order = CompareSequence(stream.Sequence, sequence);
|
|
if (order == SequenceOrder.Future)
|
|
{
|
|
if (!stream.PacketQueue.ContainsKey(sequence))
|
|
{
|
|
stream.PacketQueue.Add(sequence, new ConnectionStream.QueuedPacket
|
|
{
|
|
Data = data.ToArray(),
|
|
PacketTime = packetTime
|
|
});
|
|
}
|
|
}
|
|
else if (order == SequenceOrder.Current)
|
|
{
|
|
if (stream.PacketQueue.ContainsKey(sequence))
|
|
{
|
|
stream.PacketQueue.Remove(sequence);
|
|
}
|
|
|
|
stream.Sequence++;
|
|
|
|
if (stream.TotalFragmentedBytes == 0)
|
|
{
|
|
stream.TotalFragmentedBytes = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data.Slice(4, 4)));
|
|
stream.CurrentFragmentedBytes = (uint)(data.Length - 8);
|
|
|
|
if(stream.FragmentBuffer == null || stream.FragmentBuffer.Length < (stream.TotalFragmentedBytes + 512))
|
|
{
|
|
stream.FragmentBuffer = new byte[stream.TotalFragmentedBytes + 512];
|
|
}
|
|
|
|
var target = stream.FragmentBuffer.AsSpan();
|
|
data.Slice(8).CopyTo(target);
|
|
} else
|
|
{
|
|
var target = stream.FragmentBuffer.AsSpan((int)stream.CurrentFragmentedBytes);
|
|
data.Slice(4).CopyTo(target);
|
|
|
|
stream.CurrentFragmentedBytes += (uint)(data.Length - 4);
|
|
|
|
if (stream.CurrentFragmentedBytes >= stream.TotalFragmentedBytes)
|
|
{
|
|
ProcessDecodedPacket(srcAddr, srcPort, packetTime,
|
|
stream.FragmentBuffer.AsSpan(0, (int)stream.TotalFragmentedBytes));
|
|
stream.CurrentFragmentedBytes = 0;
|
|
stream.TotalFragmentedBytes = 0;
|
|
}
|
|
}
|
|
|
|
ProcessQueue(srcAddr, srcPort, stream_id);
|
|
}
|
|
|
|
}
|
|
break;
|
|
case Opcode.Padding:
|
|
OnPacketRecv?.Invoke(this, GetDirection(srcAddr, srcPort), packetTime, data.Slice(1));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else
|
|
{
|
|
OnPacketRecv?.Invoke(this, GetDirection(srcAddr, srcPort), packetTime, data);
|
|
}
|
|
}
|
|
|
|
private void ProcessQueue(IPAddress srcAddr, int srcPort, int stream_id)
|
|
{
|
|
var stream = FindStream(srcAddr, srcPort, stream_id);
|
|
var sequence = stream.Sequence;
|
|
|
|
//try to get the current sequence in the queue, if it exists then process it
|
|
ConnectionStream.QueuedPacket value;
|
|
if(stream.PacketQueue.TryGetValue(sequence, out value))
|
|
{
|
|
ProcessDecodedPacket(srcAddr, srcPort, value.PacketTime, value.Data);
|
|
}
|
|
}
|
|
|
|
public bool Match(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort)
|
|
{
|
|
var p1 = _srcAddr.Equals(srcAddr) && _srcPort == srcPort && _dstAddr.Equals(dstAddr) && _dstPort == dstPort;
|
|
var p2 = _srcAddr.Equals(dstAddr) && _srcPort == dstPort && _dstAddr.Equals(srcAddr) && _dstPort == srcPort;
|
|
return p1 || p2;
|
|
}
|
|
|
|
private SequenceOrder CompareSequence(ushort expected, ushort actual)
|
|
{
|
|
int diff = (int)actual - (int)expected;
|
|
|
|
if (diff == 0)
|
|
{
|
|
return SequenceOrder.Current;
|
|
}
|
|
|
|
if (diff > 0)
|
|
{
|
|
if (diff > 10000)
|
|
{
|
|
return SequenceOrder.Past;
|
|
}
|
|
|
|
return SequenceOrder.Future;
|
|
}
|
|
|
|
if (diff < -10000)
|
|
{
|
|
return SequenceOrder.Future;
|
|
}
|
|
|
|
return SequenceOrder.Past;
|
|
}
|
|
|
|
private bool PacketCanBeDecoded(ReadOnlySpan<byte> p)
|
|
{
|
|
if (p.Length < 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (p[0] != 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var opcode = p[1];
|
|
|
|
if (opcode == Opcode.SessionRequest || opcode == Opcode.SessionResponse || opcode == Opcode.OutOfSession)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool ValidateCRC(ReadOnlySpan<byte> p)
|
|
{
|
|
try
|
|
{
|
|
if (_crc_bytes == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int actual = 0;
|
|
int calculated = _crc_generator.Calculate(p.Slice(0, p.Length - _crc_bytes), _encode_key);
|
|
switch (_crc_bytes)
|
|
{
|
|
case 2:
|
|
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(p.Slice(p.Length - 2, 2))) & 0xFFFF;
|
|
calculated = calculated & 0xFFFF;
|
|
break;
|
|
case 4:
|
|
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(p.Slice(p.Length - 4, 4)));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return actual == calculated;
|
|
} catch(Exception ex)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private byte[] Decompress(byte[] p, int offset, int length)
|
|
{
|
|
if (length < 2)
|
|
{
|
|
return p;
|
|
}
|
|
|
|
Span<byte> header = p.AsSpan(0, offset);
|
|
byte flag = p[offset];
|
|
Span<byte> payload = p.AsSpan(offset + 1, length - 1);
|
|
|
|
if (flag == 0x5a)
|
|
{
|
|
var pl = payload.ToArray();
|
|
var inflated = Inflate(payload.ToArray());
|
|
byte[] ret = new byte[offset + inflated.Length];
|
|
Array.Copy(p, 0, ret, 0, offset);
|
|
Array.Copy(inflated, 0, ret, offset, inflated.Length);
|
|
return ret;
|
|
}
|
|
else if (flag == 0xa5)
|
|
{
|
|
byte[] ret = new byte[offset + length - 1];
|
|
Array.Copy(p, 0, ret, 0, offset);
|
|
Array.Copy(p, offset + 1, ret, offset, length - 1);
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
return p;
|
|
}
|
|
}
|
|
|
|
private byte[] Inflate(byte[] p)
|
|
{
|
|
try
|
|
{
|
|
using (var out_stream = new MemoryStream())
|
|
{
|
|
using (var in_stream = new MemoryStream(p))
|
|
{
|
|
var buffer = new byte[512];
|
|
using (var zs = new ZlibStream(in_stream, CompressionMode.Decompress))
|
|
{
|
|
int r = 0;
|
|
do
|
|
{
|
|
r = zs.Read(buffer, 0, 512);
|
|
out_stream.Write(buffer, 0, r);
|
|
} while (r == 512);
|
|
}
|
|
}
|
|
|
|
var ret = out_stream.ToArray();
|
|
return ret;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private byte[] Decode(byte[] p, int offset, int length)
|
|
{
|
|
int key = _encode_key;
|
|
Span<byte> buffer = p.AsSpan(offset, length);
|
|
int i = 0;
|
|
for (i = 0; i + 4 <= length; i += 4)
|
|
{
|
|
int pt = BitConverter.ToInt32(buffer.Slice(i)) ^ key;
|
|
key = BitConverter.ToInt32(buffer.Slice(i));
|
|
|
|
if(BitConverter.TryWriteBytes(buffer.Slice(i), pt) == false)
|
|
{
|
|
throw new Exception("Error writing bytes back in decode.");
|
|
}
|
|
}
|
|
|
|
byte kc = (byte)(key & 0xFF);
|
|
for (; i < length; i++)
|
|
{
|
|
buffer[i] = (byte)(buffer[i] ^ kc);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
private Direction GetDirection(IPAddress srcAddr, int srcPort)
|
|
{
|
|
if(srcAddr.Equals(_srcAddr) && srcPort == _srcPort)
|
|
{
|
|
return Direction.ClientToServer;
|
|
} else
|
|
{
|
|
return Direction.ServerToClient;
|
|
}
|
|
}
|
|
|
|
private ConnectionStream FindStream(IPAddress srcAddr, int srcPort, int index)
|
|
{
|
|
if (index < 0 || index > 3)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var dir = GetDirection(srcAddr, srcPort);
|
|
|
|
if(dir == Direction.ClientToServer)
|
|
{
|
|
return _client_streams[index];
|
|
} else
|
|
{
|
|
return _server_streams[index];
|
|
}
|
|
}
|
|
|
|
private class ConnectionStream
|
|
{
|
|
public class QueuedPacket
|
|
{
|
|
public byte[] Data { get; set; }
|
|
public DateTime PacketTime { get; set; }
|
|
}
|
|
|
|
public ConnectionStream()
|
|
{
|
|
Sequence = 0;
|
|
CurrentFragmentedBytes = 0;
|
|
TotalFragmentedBytes = 0;
|
|
FragmentBuffer = null;
|
|
PacketQueue = new Dictionary<ushort, QueuedPacket>();
|
|
}
|
|
|
|
public ushort Sequence { get; set; }
|
|
public uint CurrentFragmentedBytes { get; set; }
|
|
public uint TotalFragmentedBytes { get; set; }
|
|
public byte[] FragmentBuffer { get; set; }
|
|
public Dictionary<ushort, QueuedPacket> PacketQueue { get; set; }
|
|
}
|
|
}
|
|
}
|