PDA

Просмотр полной версии : Packet.cs - читаем пакет


FreePVP)))
21.04.2012, 19:51
Я очень долго хотел выложить что-нибудь интересное, но я некоторое время был заблокирован:)

Собственно кто-то решил изучить область оог, но у многих, как и у меня, пакет читался очень криво, т.е. код для чтения этого пакета был ужасен и в некоторых моментах становился не понятным/dgs

Мне пригляделся класс CPWPacket из наработок ворта, но насколько я помню - он был не полон.
В конце концов я написал свой класс, в который входят следующие методы:

GetType() // Возвращает тип текущего пакета
SetType(uint tp) // Задает тип пакету
GetLength() // возвращает длинну буфера
GetShift() // Возвращает значение указателя
GetBuf() // Возвращает весь буфер
ReadUString(bool dword(?)) // Читает длину текста(CUI/Dword) и возвращает сам текст
WriteUString(string text, bool dword(?)) // Записывает длину текста и сам текст в Unicode
ReadAString(bool dword(?)) // Читает текст в формате ANCII
WriteAString(string text, bool dword(?)) // Записывает длину и текст в формате ANCII
WriteData(byte[] buf, bool dword(?)) // записывает длину данных(CUI/Dword), а потом сами данные
ReadData(bool dword(?)) // Читает длину данных и возвращает эти данные
ReadCUInt32() // Возвращает значение CUI
WriteCUInt32(uint value) // Записывает значение CUI
ReadTime(bool swap(?)) // Читает дату и время(swap - флаг(перевернуты ли байт))
WriteFloat(float value, bool swap(?)) // Записывает значение Float(Single)
ReadFloat(bool swap(?)) // Читает значение Float(Single)
ReadInt32(bool swap(?)) // Читает значение Int32
WriteInt32(int value, bool swap(?)) // Записывает значение Int32
ReadDword(bool swap(?)) // Читает значение Dword
WriteDword(uint value, bool swap(?)) // Записывает значение Dword
ReadWord(bool swap(?)) // Читает значение Word
WriteWord(ushort value, bool swap(?)) // Записывает значение Word
WriteArray(byte[] buf, bool swap(?)) // Записывает массив байт в буфер
ReadArray(uint length, bool swap(?)) // Читает массив байт из буфера
WriteByte(byte bt(?)) // Записывает байт(если аргументов 0, то записывается просто нулевой байт)
ReadByte() // Читает 1 байт

Например нам надо прочитать пару байт, дворд и CUI:
Packet P;
byte[] bt = P.ReadArray(2);
uint dword = P.ReadDword();
uint cui = P.ReadCUInt32();



using System;
using System.Collections.Generic;
using System.Text;

namespace OOGLibrary
{
public class Packet
{
public Packet()
{
SetType(00);
}
public Packet(uint type)
{
SetType(type);
}
List<byte> buffer = new List<byte>();
uint shift = 0;
uint packtype = 0;

public uint GetType()
{ return packtype; }
public void SetType(uint tp)
{ packtype = tp; }
public uint GetLength()
{ return (uint)buffer.Count; }
public uint GetShift()
{ return shift; }
public byte[] GetBuf()
{ return buffer.ToArray(); }

public string ReadUString()
{
return Encoding.Unicode.GetString(ReadData());
}
public void WriteUString(string text)
{
WriteData(Encoding.Unicode.GetBytes(text));
}

public string ReadAString()
{
byte[] data = ReadData();
return Encoding.ASCII.GetString(data);
}
public void WriteAString(string text)
{
WriteData(Encoding.ASCII.GetBytes(text));
}

// dword/
public string ReadUString(bool dword)
{
return Encoding.Unicode.GetString(ReadData(dword));
}
public void WriteUString(string text, bool dword)
{
WriteData(Encoding.Unicode.GetBytes(text), dword);
}

public string ReadAString(bool dword)
{
return Encoding.ASCII.GetString(ReadData(dword));
}
public void WriteAString(string text, bool dword)
{
WriteData(Encoding.ASCII.GetBytes(text), dword);
}

// /dword

public void WriteData(byte[] buf)
{
WriteCUInt32((uint)buf.Length);
WriteArray(buf);
}
public byte[] ReadData()
{
uint length = ReadCUInt32();
return ReadArray(length);
}

public DateTime ReadTime()
{
return ReadTime(false);
}
public DateTime ReadTime(bool swap)
{
uint value = ReadDword(!swap);
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(value).AddHours(4);
}

public void WriteData(byte[] buf, bool dword)
{
if (!dword)
{
WriteData(buf);
return;
}

WriteDword((uint)buf.Length);
WriteArray(buf);
}
public byte[] ReadData(bool dword)
{
if(!dword)return ReadData();

uint length = ReadDword();
return ReadArray(length);
}

public uint ReadCUInt32()
{
byte code = ReadByte();
switch (code & 0xE0)
{
case 0xE0:
return BitConverter.ToUInt32(ReadArray(4, true), 0);
case 0xC0:
byte[] bt = ReadArray(3, true);
return BitConverter.ToUInt32(new byte[] { bt[2], bt[1], bt[0], code }, 0) & 0x1FFFFFFF;
case 0x80:
case 0xA0:
return (uint)(BitConverter.ToUInt16(new byte[] { ReadByte(), code }, 0) & 0x3FFF);
}
return (uint)code;
}

public byte[] WriteCUInt32(uint value)
{
if (value <= 0x7F)
{
WriteByte((byte)value);
return new byte[] { (byte)value };
}
if (value <= 0x3FFF)
{
byte[] bt = BitConverter.GetBytes((ushort)(value + 0x8000));
Array.Reverse(bt);
WriteArray(bt);
return bt;
}
if (value <= 0x1FFFFFFF)
{
byte[] bt = BitConverter.GetBytes((uint)(value + 0xC0000000));
Array.Reverse(bt);
WriteArray(bt);
return bt;
}
if (value <= 0xFFFFFFFF)
{
List<byte> bt = new List<byte>();
bt.Add(0xE0);
byte[] arrbt = BitConverter.GetBytes((uint)value);
Array.Reverse(arrbt);
bt.AddRange(arrbt);
WriteArray(bt.ToArray());
return bt.ToArray();
}
return new byte[0];
}

public void WriteFloat(float value, bool swap)
{
byte[] bt = BitConverter.GetBytes(value);
WriteArray(bt, swap);
}
public float ReadFloat(bool swap)
{
byte[] bt = ReadArray(4, swap);
return BitConverter.ToSingle(bt, 0);
}

public void WriteFloat(float value)
{
WriteFloat(value, false);
}
public float ReadFloat()
{
return ReadFloat(false);
}

public int ReadInt32()
{
return ReadInt32(false);
}
public int ReadInt32(bool swap)
{
return BitConverter.ToInt32(ReadArray(4, swap), 0);
}

public void WriteInt32(int value)
{
WriteInt32(value, false);
}
public void WriteInt32(int value, bool swap)
{
WriteArray(BitConverter.GetBytes(value), swap);
}

public void WriteDword(uint value)
{
WriteArray(BitConverter.GetBytes(value));
}
public void WriteDword(uint value, bool swap)
{
WriteArray(BitConverter.GetBytes(value), swap);
}



public void WriteWord(ushort value)
{
WriteArray(BitConverter.GetBytes(value));
}
public void WriteWord(ushort value, bool swap)
{
WriteArray(BitConverter.GetBytes(value), swap);
}

public uint ReadDword()
{
return BitConverter.ToUInt32(ReadArray(4),0);
}
public uint ReadDword(bool swap)
{
return BitConverter.ToUInt32(ReadArray(4, swap),0);
}

public ushort ReadWord()
{
return BitConverter.ToUInt16(ReadArray(2),0);
}
public ushort ReadWord(bool swap)
{
return BitConverter.ToUInt16(ReadArray(2, swap),0);
}

public void WriteArray(byte[] buf)
{ WriteArray(buf, false); }
public void WriteArray(byte[] buf, bool swap)
{
if (swap) Array.Reverse(buf);
for (int i = 0; i < buf.Length; i++)
WriteByte(buf[i]);
}

public byte[] ReadArray(uint length)
{ return ReadArray(length, false); }
public byte[] ReadArray(uint length, bool swap)
{
byte[] ret = new byte[length];
for (int i = 0; i < length; i++)
ret[i] = ReadByte();
if (swap) Array.Reverse(ret);
return ret;
}

public void WriteByte()
{
WriteByte(0x00);
}
public void WriteByte(byte bt)
{
buffer.Add(bt);
}
public byte ReadByte()
{
if (shift >= buffer.Count)
{
Console.WriteLine("shift >= buffer.Count");
return 0x00;
}
byte ret = buffer[(int)shift];
shift++;
return ret;
}
}
}

silk
21.04.2012, 21:13
а наша наука дошла до следующего

Обявлениее пакета:


class MoveStopImpl
{
public:
enum { ID = 0x0007 };

Coord3D pos;
byte orientation;
byte moveType;
WORD seqNum;
byte chkSum, qIdx;
WORD speed;

protected:
template<int mode> void format(Serializer<mode> & s)
{
s.fr(pos.x_).fr(pos.z_).fr(pos.y_)
.wr(speed).b(orientation).b(moveType)
.wr(seqNum).b(chkSum).b(qIdx);
}
};

typedef FragmentGiSpec<MoveStopImpl> FragmentGiMoveStop;


собственно, для описания пакета больше ничего не нужно. Весь остальной код работает чисто с этим классом, а раскладыванием пакетов занимаются две отдельные фабрики. Плюс объявление работает в обе стороны - т.е. пакет как и разбирается по этим правилам, так и может быть собран обратно в массив байт.
Магия С++ :)

Kitsune
21.04.2012, 22:01
собственно, для описания пакета больше ничего не нужно. Весь остальной код работает чисто с этим классом, а раскладыванием пакетов занимаются две отдельные фабрики. Плюс объявление работает в обе стороны - т.е. пакет как и разбирается по этим правилам, так и может быть собран обратно в массив байт.
Магия С++
Магия прямых рук ;)
Сами имеем схожую систему чтения / обработки пакетов.
Хочется обратить внимание тех, кто читает эту тему, что данный подход является чертовски правильным и ничего лучше пока никто не придумал :)

Не следует мешать в кучу пакет, как сущность и поток данных, который вычитываем. Разделение наглядно видно в приведенном во 2ом посте коде.

FreePVP)))
22.04.2012, 16:50
Интересно, но не очень понятно:)
Т.е.

Packet P;
byte[] bt = P.ReadArray(2);
uint dword = P.ReadDword();
uint cui = P.ReadCUInt32();

Можно записать как:

byte[] bt = new byte[2];
uint dword;
uint cui;
public void Read(Packet P)
{
P.ReadArray(bt).ReadDword(dword).ReadCUInt32(cui);
}

Я так понимаю?)

Kitsune
22.04.2012, 17:15
но не очень понятно
Видимо совсем не понятно /dgs

Пакет, это результат чтение данных из потока, а не средство для чтения данных.

Иными словами:

класс пакет1
{
тип1 свойство1 { get; set; }
тип2 свойство2 { get; set; }
тип3 свойство3 { get; set; }

конструктор {}

метод читать(поток птк)
{
свойство1 = птк.ЧитатьТип1();
свойство2 = птк.ЧитатьТип2();
свойство3 = птк.ЧитатьТип3();
}

метод писать(поток птк)
{
птк.Писать(свойство1);
птк.Писать(свойство2);
птк.Писать(свойство3);
}
}

FreePVP)))
22.04.2012, 18:05
Пакет, это результат чтение данных из потока, а не средство для чтения данных.
Я через отдельный метод записываю каждый полученный байт, а этот метод создает пакет(Packet)(записывает тип, длинну, а потом данные пакета), разделяет эти байт, и передает его через евент
Далее я уже читаю из этого Packet все нужные мне данные и создаю другой пакет
Иными словами:


public void WriteByte(byte bt)
{
try
{
if (!CheckCUInt32(bttype.ToArray()))
{
bttype.Add(bt);
return;
}
if (!CheckCUInt32(btlen.ToArray()))
{
btlen.Add(bt);
if (CheckCUInt32(btlen.ToArray()))
{
len = ReadCUInt32(btlen.ToArray());
if (len > 0) return;
}
else
return;
}
if (btbuf.Count < len) btbuf.Add(bt);
if (btbuf.Count == len)
{
uint type = ReadCUInt32(bttype.ToArray());
byte[] buf = btbuf.ToArray();
Packet P = new Packet();
P.SetType(type);
P.WriteArray(buf);

btbuf.Clear();
bttype.Clear();
btlen.Clear();
len = 0;

client.Read(P);
if(P.GetType() == 0x00)
{
Packet[] Packs = GetContainer(P);
for (int i = 0; i < Packs.Length; i++)
client.ReadContainer(Packs[i]);
}
}
}
catch { }
}


Добавлено через 3 часа 54 минуты
Видимо я правильно понял)
В примере Slik переменная s - класс пакета
Метод fr(FloatRead) на C# мог бы выглядеть вот так:
public Packet fr(out float value)
{
value = BitConverter.ToSingle(ReadArray(4));
return this;
}

Kitsune
23.04.2012, 11:49
В примере Slik переменная s - класс пакета
В примере silk'a

MoveStopImpl - класс пакета
Serializer<mode> & s - его реализация потока данных (скорее всего)

silk
24.04.2012, 00:25
ну как-то так, да. Более того, само описание фрагмента задает формат для содержимого фрагмента, а ответственность за форматирование контейнеров, 00-22, и прочих деталях находятся в других похожих местах. Более того, с помощью такого описания контролируется и необходимая длина данных для формирования фрагмента при помощи специального сериализатора, который только считает длину :)

Добавлено через 4 минуты
ну и потом, можно делать более веселые штуки, например такие

class PlayerPurchaseItemImpl
{
public:
enum { ID = 0x0048 };

struct Item
{
DWORD id;
DWORD unk;
WORD count;
WORD slot;
byte tradeSlot; // tradeSlot is a slot in market list of <22><A9>.
// Slots are not reordered after sell/buy.

template<int mode> void format(Serializer<mode> & s)
{
s.lr(id).lr(unk).wr(count).wr(slot).b(tradeSlot);
}
};

DWORD moneyLost;
DWORD unk;
BYTE unk2;

std::vector<struct Item> items;

protected:
template<int mode> void format(Serializer<mode> & s)
{
s.lr(moneyLost).lr(unk).b(unk2).arr(items, &Serializer<mode>::wr);
}
};

vogel
26.04.2012, 18:53
Пакет, это результат чтение данных из потока, а не средство для чтения данных.

Эммм... Вопросик Вам - а чего бы не инкапсулировать туда же логику обработки данного пакета ?
В противном случае не вижу профита плодить десятки классов на каждый пакет, учитывая, что нужно он один единственный раз - получить его из потока. Дальше его данные обрабатываются и пакет освобождается.
При таком раскладе пакет - как средство чтения и десериализации данныых - самое то. Вот как-то так :

procedure TCharacterController.HandleS00_105RoleLockInfo(pck : TSubPacket);
var
b : byte;
begin
// byte - lock status (0=disabled, 1=enabled)
b := pck.ReadByte();
if b = 1 then self._locked := true else self._locked := false;
// dword - lock end timestanmp
self._lockEndTimestamp := pck.ReadDword();
// dword - lock duration
self._lockDuration := pck.ReadDword();
end;


учитывая, что TSubPacket сделан по аналогии с классом автора.

silk
27.04.2012, 10:28
ну как минимум, потребность обработать один и тот же пакет в двух разных местах поставит вас в неудобное положение. :)

vogel
27.04.2012, 11:55
Для этого есть ивенты и их хэндлеры =)

FreePVP)))
27.04.2012, 20:17
Для этого есть ивенты и их хэндлеры =)
Я делал сначала что-то подобное
В итоге получилась куча методов в одну строку