Knight OnLine Paket Yapısı

Tengri

Moderatör
Merhabalar arkadaşlar,

Knight OnLine geliştirme ile alakalı eğitim konularımızdan ilki ile geliştirme alt forumumuza başlangıç veriyoruz. Geliştirme sürecine katkıda bulunabilmek veya şahsi olarak geliştirme yapabilmek için, oyunun sistemleri ile aşina olmalı ve her detayına hakim olmalısınız. Geliştirmeyi hedeflediğiniz program bir sunucu programı olduğu için, multithreading, network altyapısı, yapay zeka, veritabanı ilişkileri gibi birçok konu ile haşır neşir olmanız gerekecek. Bebek adımlarınızdan ilkini beraber atalım ve Paket nedir, ne işe yarar hep beraber bakalım.



Paket, knight online sunucusu ve istemcisi arasındaki veri alışverişinde kullanılan veri parçalarına verilen addır. Oyun, MMORPG türünde bir oyun olduğu için, oyunun kalbi sunucu programında yatmaktadır. Yani, oyun içerisinde yaptığınız bütün işlemler sizin bilgisayarınız tarafından değil, sunucu bilgisayarı tarafından hesaplanmakta, işlenmekte ve çevrenizdeki oyunculara ve size bu verinin aktarılması sağlanmaktadır. Aklınıza gelebilecek her türlü aktivite(oyuna giriş, sunucu seçimi, karakter seçimi, karakter yaratma, büyü kullanma, envanterde eşya konumu değiştirme ve binlercesi), istemci tarafından bir 'istek' haline getirilerek sunucuya iletiliir. Bu işleme basit bir açıdan yaklaşalım ve oyuna giriş sistemini inceleyelim.

Öncelikle, oyuna girişin gerçekleşebilmesi için sunucunun istemciden iki bilgi edinmesi gerekir. Birincisi, kullanıcı adınız, ikincisi ise parolanız. Bu iki bilgi, bir işlem kodu(opcode) ile birleştirilerek, aşağıdakine benzer bir formda sunucuya iletilir. İşlem kodu, sunucunun ve istemcinin paketin hangi işlem için gönderildiğini anlamasına yarar. Yani, her işlemin (oyuna giriş, karakter seçme vs.) kendine özgü eşsiz bir kodu vardır. Oyuna giriş için bu kod 0xF3'tür. İşlem kodlarının tam listesine shared\packets.h dosyasını açarak erişebilirsiniz.

AA551E00F30800627A6B7274686D7A0E0052534F454D503052554A3630383000000055AA

Yukarıdaki paket, verinin ağ üzerinde iletilen halidir. Verimize, knight online protokolünün gereksinimi olan HEADER, SIZE ve TAIL verileri eklenmiş durumda. Şimdi bu paketi ayıklayalım, yani içerisindeki asıl veriyi okuyalım.

paket.jpg

AA551E00F30800627A6B7274686D7A0E0052534F454D503052554A3630383055AA
İlk başta AA55 olarak gördüğümüz kısım, bütün paketler için sabit olup adı paket başlığı yani HEADER'dir. 2 byte uzunluğundadır. Binary olarak 1010101001010101 değerine tekavül eder. Sunucu tarafında parity check için kullanılır.

AA551E00F30800627A6B7274686D7A0E0052534F454D503052554A3630383055AA

Paket başlığından sonra gelen 2 byte, paketin içerisindeki verinin byte cinsinden uzunluğunu belirtir. Buradaki 2 byte, unsigned short olarak, little endian byte sırası ile okunmalıdır. 1E00 değerini, decimale çevirdiğimizde paketimizin 30 byte uzunluğunda olduğunu öğreniyoruz.

AA551E00F30800627A6B7274686D7A0E0052534F454D503052554A3630383055AA

Paket boyutu kısmından sonra gelen (paket boyutu uzunluğu) 30 byte, asıl gönderdiğimiz veriden oluşuyor. Bu kısımda tahmin edebileceğiniz gibi opcode, kullanıcı adı ve şifre verileri bulunmakta. Veri kısmını biraz daha incelersek;
F3 ->İşlem kodu (WIZ_LOGIN)
0800627A6B7274686D7A -> 8 karakter uzunluğunda double-byte string (kullanıcı adı)
0E0052534F454D503052554A36303830 -> 14 karakter uzunluğunda double-byte string (parola)

0800627A6B7274686D7A kalın gösterilen kısım, kullanıcı adının hexadecimal string halidir. yani Convert Hexadecimal To String Online gibi bir dönüştürme aracı kullanarak, orjinal halini görebiliriz. Bu login isteğinde kullanılan kullanıcı adı : 'bzkrthmz' imiş.

Aynı şekilde, parolayı da görüntüleyebiliriz. Parola ise 'RSOEMP0RUJ6080' olarak görüntüleniyor fakat bu aslında asıl parola değil. Knight online istemcisi, v1453 sürümünden itibaren parolaları MD5 benzeri bir hash algoritması ile dönüştürerek sunucuya iletmekte, ve gördüğümüz de tam olarak bu 'hash'.

Veriyi incelememiz bittiğine göre, son kalan kısmı da inceleyebiliriz.

AA551E00F30800627A6B7274686D7A0E0052534F454D503052554A3630383055AA

Bu kısım paket sonu, yani tail olarak adlandırılan kısımdır. 2 byte uzunluğundadır. 0101010110101010 binary değerine tekavül eder. Parity check ve birleşik halde gelen paketleri birbirinden ayırmak için ayraç görevi görür.

Paketleri sunucu tarafında oluşturmak için, 'Packet' sınıfını kullanıyoruz. Packet sınıfı, göndereceğimiz veriyi depolamak için kullanılan sınıftır. İçerisinde pakete eklenen verinin byte olarak depolandığı bir vektör, ve pakete veri ekleyebilmek için overload edilmiş stream operatörleri ve fonksiyonlar bulunur.

Örnek olarak, bir paket oluşturalım.

Packet test(WIZ_LOGIN);
test test Send(&test);

Yukarıda WIZ_LOGIN işlem kodu ile bir paket oluşturup, bu pakete sırasıyla kullanıcı adı ve şifre değerlerini ekledik. Bu paket gönderilirken şu şekilde görünecek;

AA551500F30800746573745f6163630800746573745f70776455AA

Bu paketi işleyecek program, tıpkı bizim yaptığımız gibi bu paketi programatik olarak ayıklayacak ve içerisinden kullanıcı adı ve şifreyi çıkartarak veritabanında sorgulayacak, veritabanından dönen sonuca göre isteği yapan programa cevabı iletecektir. (giriş başarılı, hesap yok vs.)

Şimdi, hep beraber topluluk projemizde kullanılan 'Packet' sınıfını inceleyelim ve hangi fonksiyon ne işe yarıyor hep beraber görelim.
1708620457276.gif


Kod:
class Packet : public ByteBuffer
{
public:
/*....*/


  • Görüldüğü üzere, Packet sınıfı ByteBuffer sınıfından kalıtım ile özelliklerini alıyor. Dolayısıyla asıl incelememiz gereken sınıf ByteBuffer.
C++:
#pragma once

#include

class ByteBuffer
{
public:
const static size_t DEFAULT_SIZE = 32;
bool m_doubleByte;

ByteBuffer(): _rpos(0), _wpos(0), m_doubleByte(true) { _storage.reserve(DEFAULT_SIZE); }
ByteBuffer(size_t res): m_doubleByte(true), _rpos(0), _wpos(0) { _storage.reserve(res ByteBuffer(const ByteBuffer &buf): _rpos(buf._rpos), _wpos(buf._wpos), _storage(buf._storage) { }
virtual ~ByteBuffer() {}

void clear()
{
_storage.clear();
_rpos = _wpos = 0;
}
// append, append şeklinde yazma yapabilmek için template fonksiyon
template void append(T value) { append((uint8 *)&value, sizeof(value)); }
template void put(size_t pos,T value) { put(pos,(uint8 *)&value, sizeof(value)); }



// stream like operators for storing data
ByteBuffer &operator((char)value); return *this; }

// İşaretsiz tamsayı eklemek için stream operatörleri
/*
örnek :
Packet test;
test */
ByteBuffer &operator (value); return *this; }
ByteBuffer &operator(swap16(value)); return *this; }
ByteBuffer &operator(swap32(value)); return *this; }
ByteBuffer &operator(swap64(value)); return *this; }
// İşaretli tamsayı eklemek için stream operatörleri
/*
örnek :
Packet test;
test */
ByteBuffer &operator (value); return *this; }
ByteBuffer &operator(swap16(value)); return *this; }
ByteBuffer &operator(swap32(value)); return *this; }
ByteBuffer &operator(swap64(value)); return *this; }
ByteBuffer &operator(swapfloat(value)); return *this; }
ByteBuffer &operator(swapdouble(value)); return *this; }

ByteBuffer &operator {
if (value.wpos())
append(value.contents(), value.wpos());
return *this;
}

// İşaretsiz tamsayı çıkartmak (okumak) için stream operatörleri
/*
örnek:
uint16 x;
Packet test;
test test >> x;// paketten uint16 oku
// x = 5
*/
ByteBuffer &operator>>(bool &value) { value = read() > 0 ? true : false; return *this; }
// unsigned
ByteBuffer &operator>>(uint8 &value) { value = read(); return *this; }
ByteBuffer &operator>>(uint16 &value) { value = swap16(read()); return *this; }
ByteBuffer &operator>>(uint32 &value) { value = swap32(read()); return *this; }
ByteBuffer &operator>>(uint64 &value) { value = swap64(read()); return *this; }
// İşaretli tamsayı çıkartmak (okumak) için stream operatörleri
/*
örnek:
int8 x;
Packet test;
test test >> x;// paketten int8 oku
// x = -9
*/
ByteBuffer &operator>>(int8 &value) { value = read(); return *this; }
ByteBuffer &operator>>(int16 &value) { value = swap16(read()); return *this; }
ByteBuffer &operator>>(int32 &value) { value = swap32(read()); return *this; }
ByteBuffer &operator>>(int64 &value) { value = swap64(read()); return *this; }
ByteBuffer &operator>>(float &value) { value = swapfloat(read()); return *this; }
ByteBuffer &operator>>(double &value) { value = swapdouble(read()); return *this; }

// Hacky KO string flag - either it's a single byte length, or a double byte.
void SByte() { m_doubleByte = false; }
void DByte() { m_doubleByte = true; }

ByteBuffer &operator ByteBuffer &operator
ByteBuffer &operator {
uint16 len = (uint16)strlen(str);
if (m_doubleByte)
append((uint8*)&len, 2);
else
append((uint8*)&len, 1);
append((uint8 *)str, len);
return *this;
}
ByteBuffer &operator // std::string eklemek için stream operatörü
ByteBuffer &operator>>(std::string& value)
{
uint16 len;
value.clear();
if (m_doubleByte)
len = read();
else
len = read();


if (_rpos + len {
/* preallocate memory */
value.reserve(len);

for (uint16 i = 0; i value.push_back(read());
}
return *this;
}

uint8 operator[](size_t pos) { return read(pos); }

INLINE size_t rpos() { return _rpos; };
INLINE size_t rpos(size_t rpos) { return _rpos = rpos; };
INLINE size_t wpos() { return _wpos; };
INLINE size_t wpos(size_t wpos) { return _wpos = wpos; };
// read, read şeklinde okuma yapabilmek için template fonksiyon
template T read()
{
T r = read(_rpos);
_rpos += sizeof(T);
return r;
};

template T read(size_t pos) const
{
//ASSERT(pos + sizeof(T) if (pos + sizeof(T) > size())
return (T)0;
return *((T*)&_storage[pos]);
};
/* char buf[50]; şeklinde tanımladığımız buffera veri okuyabilmek için;
Packet test;
/* ... */
/* test.read(buf,50); // test paketinden, buf'a 50 byte veri oku
*/
void read(void *dest, size_t len)
{
if (_rpos + len memcpy(dest, &_storage[_rpos], len);
else // throw error();
memset(dest, 0, len);
_rpos += len;
};

const uint8 *contents() const { return &_storage[0]; };
INLINE size_t size() const { return _storage.size(); };

// one should never use resize
void resize(size_t newsize)
{
_storage.resize(newsize);
_rpos = 0;
_wpos = size();
};

void reserve(size_t ressize) { if (ressize > size()) _storage.reserve(ressize); };

// append to the end of buffer
void append(const std::string& str) { append((uint8 *)str.c_str(),str.size() + 1); }
void append(const char *src, size_t cnt) { return append(reinterpret_cast(src), cnt); }
void append(const void *src, size_t cnt)
{
if (!cnt)
return;

// 10MB is far more than you'll ever need.
ASSERT(size()
if (_storage.size() _storage.resize(_wpos + cnt);

memcpy(&_storage[_wpos], src, cnt);
_wpos += cnt;
}

void append(const ByteBuffer& buffer) { if (buffer.size() > 0) append(buffer.contents(), buffer.size()); }
void append(const ByteBuffer& buffer, size_t len)
{
ASSERT(buffer._rpos + len append(buffer.contents() + buffer._rpos, len);
}

void put(size_t pos, const void *src, size_t cnt)
{
ASSERT(pos + cnt memcpy(&_storage[pos], src, cnt);
}

protected:
// okuma ve yazma konumları,
// bu değişkenler her okuma ve yazma işleminde güncellenerek
// okuma ve yazma konumlarını saklarlar.
//örneğin;
// Packet test; // _rpos = 0, _wpos = 0;
// test // test // uint8 x;
// test >> x; // _rpos = 1; _wpos = 12;
// uint64 q;
// test >> q; // _rpos = 9; _wpos = 12;
size_t _rpos, _wpos;
/* verinin depolandığı vektör */
std::vector _storage;
};
 
T.C.K 20.ci Madde ve 5651 Sayılı Kanun'un 4.cü maddesinin (2).ci fıkrasına göre TÜM ÜYELERİMİZ Yaptıkları paylaşımlardan kendileri sorumludur!
Geri
Üst Alt