PDA

Просмотр полной версии : [Руководство] PW Library OOG


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 });
}

Компилируем.

Библиотеку и исходный код примера можно скачать в прикреплениях к этому сообщению.

VeTaL_UA
14.08.2013, 05:07
Ооооооочень сомневаюсь в вашем авторстве.

Desmond Hume
14.08.2013, 07:21
VeTaL_UA, могу предоставить исходные коды (в пм, естественно;а можете и сами декомплировать), если есть какие-то сомнения.
Я, конечно, особенно код не комментировал, но тем не менее могу и подробно рассказать, что и для чего, а также предоставить прототип пакетов движения (на данный момент есть некоторая проблема с 0x07 (MoveStop) (Client -> Server), связанная с углом поворота чара).

VeTaL_UA
14.08.2013, 16:25
Если вы и правда автор, то прошу прощения и снимаю шляпу. Просто видел ну очень похожую библиотеку другого автора ;)

Desmond Hume
14.08.2013, 18:19
VeTaL_UA, да ничего страшного, ошибаться может каждый.
Собственно, на немного новой библиотеки (эта более стабильна, хотя стоит поправить маленькую ошибку с куинтом) у меня стоит OOG бот, который копает рес на одной точке. Палится ужасно, правда...
Если где–то и есть похожая библиотека с нормально реализованными пакетами движения, хотелось бы получить ссылку на неё, или же, если у кого-то хватит мысли, алгоритм (на словах) вычисления угла поворота чара. Пока удается поворачивать его лишь в четырех направлениях (С, Ю, З, В).

nitrotek
14.08.2013, 18:41
Напиши чему равны значения: С, Ю, З, В. Есть одно предположение.

Desmond Hume
14.08.2013, 19:20
С — 40 HEX (64 DEC)
Ю — С0 HEX (192 DEC)
З — 80 HEX (128 DEC)
В — 00 HEX (0 DEC)

nitrotek
14.08.2013, 19:40
Ну вот как я и думал, простая математика.
Максимальное значение байта: 255. Значит необходимо составить биекцию Y = F(X) такой чтобы Y было множество: {0..256}, а X {0..360}.

Биекция:
Y = (256 / 360) * X

Обратная функция:
X = Round(Y * 360 / 256)

Иначе говоря в PW не 360 градусов, а всего 256. Т.е каждый PW градус это эквивалент 1,40625 градусам.

Desmond Hume
14.08.2013, 19:49
nitrotek, была идея совсем не из этой песни, но сама идея бредова и сложна в реализации.

BritishColonist
14.08.2013, 22:20
Иначе говоря в PW не 360 градусов, а всего 256.
Да уж, и система координат у них XZY.
PW такой PW..

Не знаю, как обстоят в этом плане дела с ООГ, но если можно получить структуру игрока, то в её начале есть косинус и синус угла поворота игрока (смещения раньше были +0xC для синуса и +0x2C для косинуса, думаю, сейчас ничего не изменилось).

Desmond Hume
14.08.2013, 22:36
BritishColonist, в пакетах позиции данных фиксированы (иначе в редких случаях), смещения тут не требуются. Позиция отсылается сервером в пакете 0x08 (PlayerPos) (Server -> Client), длина её составляет 12 байт (по 4 байта на каждую из мировых осей).

BritishColonist
15.08.2013, 00:09
в пакетах позиции данных фиксированы (иначе в редких случаях), смещения тут не требуются
Благодарю за пояснения, но я давал смещения не для пакетов (лолшто?), а для структуры игрока.
Ну, на всякий случай. Если в ООГ можно получить структуру, то можно будет узнать и угол поворота персонажа.

VeTaL_UA
15.08.2013, 02:00
BritishColonist, не в ту оперу залез ;)

Envy12
21.08.2013, 22:26
Как я понял, этой либой можно пользоваться только в dotNet?

Desmond Hume
22.08.2013, 20:28
Envy12, да.

Ilyialat
07.09.2013, 16:57
А толку, у меня тоже есть своя библиотека с полностью расписанными данными, все поля закомментированны, но геодату так и не одолел + для неё алгоритм писать надо. А без движения толку мало. Я, конечно, реализовывал запись движения, т.е. бот бежал за мной координата в координату, записывал в файл каждое обновление моей локации, но отойти ты от начала пути максимум на несколько метров сможешь, а потом по небу побежишь и откатит. Нужно геодату парсить.

Desmond Hume
10.09.2013, 19:33
Ilyialat, ну поздравляю, что у тебя есть. Можешь дальше фапать на неё. Смысл было писать, что у тебя есть? /kidding

Res5
16.09.2013, 05:24
Впихните в нее юз кота) XD

whoami
16.09.2013, 23:48
Лучше расскажите, что там с протоколом поменялось. А то после LoginAnnonce приходит сразу 0x05 с кодом ошибки 3 :(

N00bSa1b0t
16.09.2013, 23:57
Вроде только C46 поменялся,в конце добавился нулевой байт.

Моим программам эта правка помогла)

whoami
17.09.2013, 00:03
Такое ощущение, что там что-то с авторизацией поменялось. Раньше был такой код для C03:

private void ProcessServerInfo(byte[] data)
{
byte[] key = new byte[data[2]];
Array.Copy(data, 3, key, 0, key.Length);
ServerVersion = string.Format("{0}.{1}.{2}.{3}", data[4 + key.Length], data[5 + key.Length], data[6 + key.Length], data[7 + key.Length]);

List<byte> Send = new List<byte>();

byte[] loginbt = Encoding.GetEncoding(1251).GetBytes(AccountName);
byte[] logwithpass = Encoding.ASCII.GetBytes(AccountName + AccountPassword);
MD5 md5 = MD5.Create();
hash = new HMACMD5(md5.ComputeHash(logwithpass)).ComputeHash( key);

Send.Add((byte)loginbt.Length);
Send.AddRange(loginbt);
Send.Add((byte)hash.Length);
Send.AddRange(hash);
Send.AddRange(new byte[] { 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF} );

Send.InsertRange(0, new byte[] { 0x03, (byte)Send.Count });
try
{
socket.BeginSend(Send.ToArray(), 0, Send.Count, SocketFlags.None, OnSend, socket);
}
catch { }
}


теперь в ответ приходит серверная ошибка (S05, code=0x03)

N00bSa1b0t
17.09.2013, 00:08
Насколько "раньше"? О_о
Я C03 не трогал вообще.
Он выглядит так
[Field] public StringALen Login;
[Field] public byte PassHashLen;
[Field] public byte[] PassHash;
[Field] public byte[] Unknown = new byte[]
{
0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF
};

Стоп.

последние 6 байт.
у тебя 0000FFFFFFFF
у меня 0004FFFFFFFF

whoami
17.09.2013, 00:18
Сделал как у тебя - не помогло =( Может адреса серверов поменялись?

N00bSa1b0t
17.09.2013, 00:21
я по имени хоста подключаюсь всегда..причем тут они?)

whoami
17.09.2013, 00:24
Да я прям даже не знаю. Беда какая-то :( обновиться не могу - на serverlist.txt ругается, бота завести, чтобы хоть чаты почитать - тоже :(

Короче, разобрался: проблема в переполненности серваков. Они такой ошибкой реагируют. А в C03 прокатывают и 0000FFFFFFFF
и 0004FFFFFFFF

Archnael
22.01.2018, 08:15
Have download sources?

seergeeyy
29.04.2018, 21:49
Можно вернуть ссылки на файлы?

qqsda
14.04.2019, 20:35
Как кодировать трафик RC4? Ключи извесны