mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-15 21:18:22 +00:00
Initial commit from laurion branch
This commit is contained in:
@@ -0,0 +1,621 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StreamParser.Common.Daybreak
|
||||
{
|
||||
public ref struct GamePacket
|
||||
{
|
||||
private readonly ReadOnlySpan<byte> _data;
|
||||
|
||||
public GamePacket(ReadOnlySpan<byte> data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return ToString(16);
|
||||
}
|
||||
|
||||
public readonly string ToString(int columns)
|
||||
{
|
||||
int rows = _data.Length / columns;
|
||||
if (_data.Length % columns != 0)
|
||||
{
|
||||
rows += 1;
|
||||
}
|
||||
|
||||
int expected = (10 + columns * 4) * rows;
|
||||
var sb = new StringBuilder(expected);
|
||||
|
||||
for(var i = 0; i < rows; ++i)
|
||||
{
|
||||
sb.AppendFormat("{0} |", (i * columns).ToString("X5"));
|
||||
|
||||
for(var j = 0; j < columns; ++j)
|
||||
{
|
||||
var index = (i * 16) + j;
|
||||
if (index >= _data.Length)
|
||||
{
|
||||
sb.Append(" ");
|
||||
} else
|
||||
{
|
||||
var c = _data[index];
|
||||
sb.AppendFormat("{0,3}", c.ToString("X2"));
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(" | ");
|
||||
|
||||
for (var j = 0; j < columns; ++j)
|
||||
{
|
||||
var index = (i * 16) + j;
|
||||
if (index >= _data.Length)
|
||||
{
|
||||
sb.Append(" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
var c = _data[index];
|
||||
var ch = (char)c;
|
||||
if (char.IsLetterOrDigit(ch) || char.IsPunctuation(ch) || char.IsSymbol(ch) || (ch == ' '))
|
||||
{
|
||||
sb.Append(ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public readonly string ToModelString(int max_taken, bool hex)
|
||||
{
|
||||
int expected = Math.Min(_data.Length, max_taken) * (hex ? 2 : 1);
|
||||
if(expected <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(expected);
|
||||
|
||||
for(var i = 0; i < Math.Min(_data.Length, max_taken); ++i)
|
||||
{
|
||||
var c = _data[i];
|
||||
if(hex)
|
||||
{
|
||||
sb.Append(c.ToString("X2"));
|
||||
} else
|
||||
{
|
||||
var ch = (char)c;
|
||||
if (char.IsLetterOrDigit(ch) || char.IsPunctuation(ch) || char.IsSymbol(ch) || (ch == ' '))
|
||||
{
|
||||
sb.Append(ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StreamParser.Common.Daybreak
|
||||
{
|
||||
public enum Direction
|
||||
{
|
||||
ClientToServer,
|
||||
ServerToClient
|
||||
}
|
||||
|
||||
public enum ConnectionType
|
||||
{
|
||||
Unknown,
|
||||
Login,
|
||||
World,
|
||||
Chat,
|
||||
Zone
|
||||
}
|
||||
|
||||
public interface IConnection
|
||||
{
|
||||
delegate void OnPacketRecvHandler(Connection connection, Direction direction, DateTime packetTime, ReadOnlySpan<byte> data);
|
||||
OnPacketRecvHandler OnPacketRecv { get; set; }
|
||||
|
||||
void ProcessPacket(IPAddress srcAddr, int srcPort, DateTime packetTime, ReadOnlySpan<byte> data);
|
||||
bool Match(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort);
|
||||
|
||||
ConnectionType ConnectionType { get; }
|
||||
IPAddress ClientAddress { get; }
|
||||
int ClientPort { get; }
|
||||
IPAddress ServerAddress { get; }
|
||||
int ServerPort { get; }
|
||||
Guid Id { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StreamParser.Common.Daybreak
|
||||
{
|
||||
public interface IParser
|
||||
{
|
||||
delegate void ConnectionHandler(IConnection connection, DateTime connectionTime);
|
||||
ConnectionHandler OnNewConnection { get; set; }
|
||||
ConnectionHandler OnLostConnection { get; set; }
|
||||
void Parse(string filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StreamParser.Common.Daybreak
|
||||
{
|
||||
public class Opcode
|
||||
{
|
||||
public const byte Padding = 0;
|
||||
public const byte SessionRequest = 1;
|
||||
public const byte SessionResponse = 2;
|
||||
public const byte Combined = 3;
|
||||
public const byte SessionDisconnect = 5;
|
||||
public const byte KeepAlive = 6;
|
||||
public const byte SessionStatRequest = 7;
|
||||
public const byte SessionStatResponse = 8;
|
||||
public const byte Packet = 9;
|
||||
public const byte Packet2 = 10;
|
||||
public const byte Packet3 = 11;
|
||||
public const byte Packet4 = 12;
|
||||
public const byte Fragment = 13;
|
||||
public const byte Fragment2 = 14;
|
||||
public const byte Fragment3 = 15;
|
||||
public const byte Fragment4 = 16;
|
||||
public const byte OutOfOrderAck = 17;
|
||||
public const byte OutOfOrderAck2 = 18;
|
||||
public const byte OutOfOrderAck3 = 19;
|
||||
public const byte OutOfOrderAck4 = 20;
|
||||
public const byte Ack = 21;
|
||||
public const byte Ack2 = 22;
|
||||
public const byte Ack3 = 23;
|
||||
public const byte Ack4 = 22;
|
||||
public const byte AppCombined = 25;
|
||||
public const byte OutboundPing = 28;
|
||||
public const byte OutOfSession = 29;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharpPcap;
|
||||
using SharpPcap.LibPcap;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StreamParser.Common.Daybreak
|
||||
{
|
||||
public class Parser : IParser
|
||||
{
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
private readonly ILogger<Parser> _logger;
|
||||
private readonly List<IConnection> _connections = new List<IConnection>();
|
||||
|
||||
public IParser.ConnectionHandler OnNewConnection { get; set; }
|
||||
public IParser.ConnectionHandler OnLostConnection { get; set; }
|
||||
|
||||
public Parser(ILogger<Parser> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Parse(string filename)
|
||||
{
|
||||
ICaptureDevice device = null;
|
||||
try
|
||||
{
|
||||
device = new CaptureFileReaderDevice(filename);
|
||||
device.Open();
|
||||
|
||||
device.OnPacketArrival += new PacketArrivalEventHandler(OnPacketCapture);
|
||||
|
||||
device.Capture();
|
||||
device.Close();
|
||||
} catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error reading device capture.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPacketCapture(object sender, PacketCapture capture)
|
||||
{
|
||||
var raw = capture.GetPacket();
|
||||
if (raw.LinkLayerType == PacketDotNet.LinkLayers.Ethernet || raw.LinkLayerType == PacketDotNet.LinkLayers.Null)
|
||||
{
|
||||
var packet = PacketDotNet.Packet.ParsePacket(raw.LinkLayerType, raw.Data);
|
||||
var ipPacket = packet.Extract<PacketDotNet.IPv4Packet>();
|
||||
var udpPacket = packet.Extract<PacketDotNet.UdpPacket>();
|
||||
|
||||
if (ipPacket != null && udpPacket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessPacket(ipPacket.SourceAddress,
|
||||
udpPacket.SourcePort,
|
||||
ipPacket.DestinationAddress,
|
||||
udpPacket.DestinationPort,
|
||||
raw.Timeval.Date,
|
||||
udpPacket.PayloadData);
|
||||
} catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing packet");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPacket(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort, DateTime packetTime, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if(data.Length < 2)
|
||||
{
|
||||
_logger.LogTrace("Tossing packet, {0} was less than minimum packet size", data.Length);
|
||||
return;
|
||||
}
|
||||
|
||||
var c = FindConnection(srcAddr, srcPort, dstAddr, dstPort);
|
||||
|
||||
if(c != null)
|
||||
{
|
||||
c.ProcessPacket(srcAddr, srcPort, packetTime, data);
|
||||
}
|
||||
else if (data[0] == 0 && data[1] == Opcode.SessionRequest)
|
||||
{
|
||||
if(data.Length != 24)
|
||||
{
|
||||
_logger.LogTrace("Tossing packet, {0} was not the right size for a SessionRequest", data.Length);
|
||||
return;
|
||||
}
|
||||
|
||||
c = new Connection(this, srcAddr, srcPort, dstAddr, dstPort);
|
||||
_connections.Add(c);
|
||||
c.ProcessPacket(srcAddr, srcPort, packetTime, data);
|
||||
}
|
||||
}
|
||||
|
||||
private IConnection FindConnection(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort)
|
||||
{
|
||||
foreach (var c in _connections)
|
||||
{
|
||||
if (c.Match(srcAddr, srcPort, dstAddr, dstPort))
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user