Desmond Hume
13.08.2013, 14:39
Введение
PW Library OOG — библиотека для работы с пакетами игры Perfect World, базирующаяся на новой технологии Out of Game (Вне игры), позволяя тем самым реализовать бота, который будет работать без клиента игры.
Язык разработки — C#, библиотека может использоваться на всех dotNet языках программирования.
Взаимодействие между библиотекой и целевой программой осуществляется посредством событий, вызываемых из библиотеки.
Структура
PWProtocol.cs — класс–инициализатор.
SocketsClient.cs — асинхронная реализация сокетов.
RC4.cs — шифрование/дешифрование методом RC4.
MPPCUnpacker.cs — распаковщик по алгоритму MPPC.
Packet.cs — инкапсулированная структура для сборки пакетов.
Пакеты
На данный момент реализованы следующие пакеты:
0x01 (ServerInfo) (Server -> Client)
0x02 (SMKey) (Server -> Client)
0x02 (CMKey) (Client -> Server)
0x03 (LogginAnnounce) (Client -> Server)
0x04 (OnlineAnnounce) (Server -> Client)
0x05 (ServerError) (Server -> Client)
0x46 (SelectRole) (Client -> Server)
0x47 (SelectRole_Re) (Server -> Client)
0x48 (EnterWorld) (Client -> Server)
0x52 (RoleList) (Client -> Server)
0x53 (RoleList_Re) (Server -> Client)
0x5A (KeepAlive) (Client -> Server)
0x60 (ReadPrivateMessage) (Server -> Client)
0x60 (SendPrivateMessage) (Client -> Server)
Этот список будет постепенно дополняться.
Использование
Добавляем ссылку на библиотеку, на форму кидаем кнопки два textBox, один listView и три Button.
После определения класс определяем класс для работы с протоколом и List<byte>, где будут храниться UID персонажей.
PWProtocol Session;
List<byte[]> chars = new List<byte[]>();
В обработчик первой кнопки пишем
Session = new PWProtocol();
Session.Login = textBox1.Text;
Session.Password = maskedTextBox1.Text;
Session.Disconnected += Session_Disconnected;
Session.AuthFailed += Session_AuthFailed;
Session.CharDataOnList += Session_CharDataOnList;
Session.CharDataOnListEnd += Session_CharDataOnListEnd;
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, false });
Session.Connect("link3.pwonline.ru", 29000);
В обработчик второй
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, false });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
Invoke(new ListViewEnabledDelegate(ListViewEnabledEnabled), new object[] { true });
listView1.Items.Clear();
Session.Disconnect();
И в обработчик третьей
Session.SelectRole(chars[listView1.SelectedIndices[0]], listView1.SelectedItems[0].Text);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
Invoke(new ListViewEnabledDelegate(ListViewEnabledEnabled), new object[] { false });
Вешаем обработчики событий
private void Session_AuthFailed(object sender, EventArgs e)
{
MessageBox.Show("Неправильный логин или пароль.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
}
private void Session_Disconnected(object sender, EventArgs e)
{
MessageBox.Show("Соединение с сервером было разорвано.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, false });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
Invoke(new ListViewEnabledDelegate(ListViewEnabledEnabled), new object[] { true });
listView1.Items.Clear();
}
private void Session_CharDataOnList(byte[] charid, byte gender, byte race, byte occupation, byte[] level, byte[] charname)
{
Invoke(new ListViewAddDelegate(ListViewAdd), new object[] { charid, gender, race, occupation, level, charname });
}
private void Session_CharDataOnListEnd(object sender, EventArgs e)
{
if (listView1.Items.Count == 0)
{
MessageBox.Show("Не найдено ни одного персонажа." + Environment.NewLine + "Проверьте присутствие персонажей на выбранном сервере." + Environment.NewLine + Environment.NewLine + "Соединение с сервером было закрыто.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, false });
Session.Disconnect();
}
}
А также методы и делегаты для доступа к элементам окна из других потоков через Invoke.
private delegate void ButtonEnabledDelegate(Button button, bool enabled);
private delegate void ListViewEnabledDelegate(bool enabled);
private void ButtonEnabled(Button _button, bool enabled)
{
_button.Enabled = enabled;
}
private void ListViewEnabledEnabled(bool enabled)
{
listView1.Enabled = enabled;
}
private delegate void ListViewAddDelegate(byte[] charid, byte gender, byte race, byte occupation, byte[] level, byte[] charname);
private void ListViewAdd(byte[] charid, byte gender, byte race, byte occupation, byte[] level, byte[] charname)
{
string level_string = Convert.ToInt32(String.Format("{0:X}", level[0]) + String.Format("{0:X}", level[1]) + String.Format("{0:X}", level[2]) + String.Format("{0:X}", level[3]), 16).ToString();
listView1.Items.Add(Encoding.Unicode.GetString(cha rname)).SubItems.AddRange(new string[] { Occupation(occupation), level_string });
chars.Add(charid);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, true });
}
private string Occupation(byte occupation)
{
string occupation_name = null;
switch (occupation)
{
case 0x00:
occupation_name = "Воин";
break;
case 0x01:
occupation_name = "Маг";
break;
case 0x02:
occupation_name = "Шаман";
break;
case 0x03:
occupation_name = "Друид";
break;
case 0x04:
occupation_name = "Оборотень";
break;
case 0x05:
occupation_name = "Убийца";
break;
case 0x06:
occupation_name = "Лучник";
break;
case 0x07:
occupation_name = "Жрец";
break;
case 0x08:
occupation_name = "Страж";
break;
case 0x09:
occupation_name = "Мистик";
break;
default:
occupation_name = "Неизвестно";
break;
}
return occupation_name;
}
В обработчик изменения индекса listView помещаем код
if (listView1.SelectedIndices.Count != 0)
{
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, true });
}
else
{
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
}
Компилируем.
Библиотеку и исходный код примера можно скачать в прикреплениях к этому сообщению.
PW Library OOG — библиотека для работы с пакетами игры Perfect World, базирующаяся на новой технологии Out of Game (Вне игры), позволяя тем самым реализовать бота, который будет работать без клиента игры.
Язык разработки — C#, библиотека может использоваться на всех dotNet языках программирования.
Взаимодействие между библиотекой и целевой программой осуществляется посредством событий, вызываемых из библиотеки.
Структура
PWProtocol.cs — класс–инициализатор.
SocketsClient.cs — асинхронная реализация сокетов.
RC4.cs — шифрование/дешифрование методом RC4.
MPPCUnpacker.cs — распаковщик по алгоритму MPPC.
Packet.cs — инкапсулированная структура для сборки пакетов.
Пакеты
На данный момент реализованы следующие пакеты:
0x01 (ServerInfo) (Server -> Client)
0x02 (SMKey) (Server -> Client)
0x02 (CMKey) (Client -> Server)
0x03 (LogginAnnounce) (Client -> Server)
0x04 (OnlineAnnounce) (Server -> Client)
0x05 (ServerError) (Server -> Client)
0x46 (SelectRole) (Client -> Server)
0x47 (SelectRole_Re) (Server -> Client)
0x48 (EnterWorld) (Client -> Server)
0x52 (RoleList) (Client -> Server)
0x53 (RoleList_Re) (Server -> Client)
0x5A (KeepAlive) (Client -> Server)
0x60 (ReadPrivateMessage) (Server -> Client)
0x60 (SendPrivateMessage) (Client -> Server)
Этот список будет постепенно дополняться.
Использование
Добавляем ссылку на библиотеку, на форму кидаем кнопки два textBox, один listView и три Button.
После определения класс определяем класс для работы с протоколом и List<byte>, где будут храниться UID персонажей.
PWProtocol Session;
List<byte[]> chars = new List<byte[]>();
В обработчик первой кнопки пишем
Session = new PWProtocol();
Session.Login = textBox1.Text;
Session.Password = maskedTextBox1.Text;
Session.Disconnected += Session_Disconnected;
Session.AuthFailed += Session_AuthFailed;
Session.CharDataOnList += Session_CharDataOnList;
Session.CharDataOnListEnd += Session_CharDataOnListEnd;
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, false });
Session.Connect("link3.pwonline.ru", 29000);
В обработчик второй
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, false });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
Invoke(new ListViewEnabledDelegate(ListViewEnabledEnabled), new object[] { true });
listView1.Items.Clear();
Session.Disconnect();
И в обработчик третьей
Session.SelectRole(chars[listView1.SelectedIndices[0]], listView1.SelectedItems[0].Text);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
Invoke(new ListViewEnabledDelegate(ListViewEnabledEnabled), new object[] { false });
Вешаем обработчики событий
private void Session_AuthFailed(object sender, EventArgs e)
{
MessageBox.Show("Неправильный логин или пароль.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
}
private void Session_Disconnected(object sender, EventArgs e)
{
MessageBox.Show("Соединение с сервером было разорвано.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, false });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
Invoke(new ListViewEnabledDelegate(ListViewEnabledEnabled), new object[] { true });
listView1.Items.Clear();
}
private void Session_CharDataOnList(byte[] charid, byte gender, byte race, byte occupation, byte[] level, byte[] charname)
{
Invoke(new ListViewAddDelegate(ListViewAdd), new object[] { charid, gender, race, occupation, level, charname });
}
private void Session_CharDataOnListEnd(object sender, EventArgs e)
{
if (listView1.Items.Count == 0)
{
MessageBox.Show("Не найдено ни одного персонажа." + Environment.NewLine + "Проверьте присутствие персонажей на выбранном сервере." + Environment.NewLine + Environment.NewLine + "Соединение с сервером было закрыто.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button1, true });
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, false });
Session.Disconnect();
}
}
А также методы и делегаты для доступа к элементам окна из других потоков через Invoke.
private delegate void ButtonEnabledDelegate(Button button, bool enabled);
private delegate void ListViewEnabledDelegate(bool enabled);
private void ButtonEnabled(Button _button, bool enabled)
{
_button.Enabled = enabled;
}
private void ListViewEnabledEnabled(bool enabled)
{
listView1.Enabled = enabled;
}
private delegate void ListViewAddDelegate(byte[] charid, byte gender, byte race, byte occupation, byte[] level, byte[] charname);
private void ListViewAdd(byte[] charid, byte gender, byte race, byte occupation, byte[] level, byte[] charname)
{
string level_string = Convert.ToInt32(String.Format("{0:X}", level[0]) + String.Format("{0:X}", level[1]) + String.Format("{0:X}", level[2]) + String.Format("{0:X}", level[3]), 16).ToString();
listView1.Items.Add(Encoding.Unicode.GetString(cha rname)).SubItems.AddRange(new string[] { Occupation(occupation), level_string });
chars.Add(charid);
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button2, true });
}
private string Occupation(byte occupation)
{
string occupation_name = null;
switch (occupation)
{
case 0x00:
occupation_name = "Воин";
break;
case 0x01:
occupation_name = "Маг";
break;
case 0x02:
occupation_name = "Шаман";
break;
case 0x03:
occupation_name = "Друид";
break;
case 0x04:
occupation_name = "Оборотень";
break;
case 0x05:
occupation_name = "Убийца";
break;
case 0x06:
occupation_name = "Лучник";
break;
case 0x07:
occupation_name = "Жрец";
break;
case 0x08:
occupation_name = "Страж";
break;
case 0x09:
occupation_name = "Мистик";
break;
default:
occupation_name = "Неизвестно";
break;
}
return occupation_name;
}
В обработчик изменения индекса listView помещаем код
if (listView1.SelectedIndices.Count != 0)
{
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, true });
}
else
{
Invoke(new ButtonEnabledDelegate(ButtonEnabled), new object[] { button3, false });
}
Компилируем.
Библиотеку и исходный код примера можно скачать в прикреплениях к этому сообщению.