Протокол Lineage II
1. Общие сведения
Каждый пакет состоит из размера пакета(2 байта), типа пакета(1 байт) и блока параметров(переменная длина). В дополнение к этому, в пакетах сервера
авторизации, в конце добавляется контрольная сумма и дополняется нулями так, чтобы размер пакета был кратен 8-ми байтам. Контрольная сумма может
быть рассчитана следующей функцией:
unsigned long checksum( unsigned char *packet, int count )
{
long chksum = 0L;
for( int i = 0; i < count; i += 4 ) chksum ^= *((unsigned long *)&raw
);
return chksum;
};
Протокол lineage использует 6 разных типов данных:
char – может принимать значение от -128 до 127. Имеет длину 1 байт
short – может принимать значение от -32768 до 32767. Имеет длину 2 байта
int – может принимать значение от -2147483648 до 2147483647. Имеет длину 4 байта
int64 – может принимать значение от -9223372036854775808 до 9223372036854775807. Имеет длину 8 байт.
float – может принимать значение от 2.22507e-308 до 1.79769e+308. Имеет длину 8 байт
string – текстовая строка в юникоде(UTF8). Каждая буква представлена двумя байтами, первый байтом код буквы, а второй –
номер кодовой таблицы. Индикатором конца строки служит символ с кодом 0.
Примечание: Для тех кто незнаком с принципом хранения данных в памяти ПК уточню, байты идут в обратном порядке. Т.е. если нам нужно записать
в пакет число 10 представленное типом int, то мы должны:
1) перевести его в шестнадцатеричную систему счисления – получим 00 00 00 0a
2) обратить порядок байт в слове – получаем 0a 00 00 00
Пакеты сервера авторизации шифруются по алгоритму Blowfish. Стандартный ключ по умолчанию в 4-х хрониках:
5F 3B 35 2E 5D 39 34 2D 33 31 3D 3D 2D 25 78 54 21 5E 5B 24. К концу ключа прибавляется символ с кодом 0. В Interlude тип шифрования был изменен - пакет
InitLoginRequest дополнительно
шифруется по алгоритму RSA. Ключ состоит из следующих частей: B = 1024, E = 65537, N = передается в пакете Init. Вместе эти 3 части составляют целый RSA
ключ. Байты N в пакете зашифрованы функцией:
void scrambleMod( char *n )
{
typedef unsigned char byte;
int i;
for( i=0; i<4; i++ ) {
byte temp = n[0x00 + i];
n[0x00 + i] = n[0x4d + i];
n[0x4d + i] = temp;
};
// step 2 xor first 0x40 bytes with last 0x40 bytes
for( i=0; i<0x40; i++ ) {
n = (byte)(n ^ n[0x40 + i]);
};
// step 3 xor bytes 0x0d-0x10 with bytes 0x34-0x38
for( i=0; i<4; i++ ) {
n[0x0d + i] = (byte)(n[0x0d + i] ^ n[0x34 + i]);
};
// step 4 xor last 0x40 bytes with first 0x40 bytes
for( i=0; i<0x40; i++ ) {
n[0x40 + i] = (byte)(n[0x40 + i] ^ n);
};
};
Для расшифровки можно воспользоваться следующей функцией:
void unscrambleMod( char *n )
{
typedef unsigned char byte;
int i;
// step 4 xor last 0x40 bytes with first 0x40 bytes
for( i=0; i<0x40; i++ ) {
n[0x40 + i] = (byte)(n[0x40 + i] ^ n);
};
// step 3 xor bytes 0x0d-0x10 with bytes 0x34-0x38
for( i=0; i<4; i++ ) {
n[0x0d + i] = (byte)(n[0x0d + i] ^ n[0x34 + i]);
};
// step 2 xor first 0x40 bytes with last 0x40 bytes
for( i=0; i<0x40; i++ ) {
n = (byte)(n ^ n[0x40 + i]);
};
for( i=0; i<4; i++ ) {
byte temp = n[0x00 + i];
n[0x00 + i] = n[0x4d + i];
n[0x4d + i] = temp;
};
};
Также есть сервера использующие старый протокол авторизации(ревизия 785a) который не шифрует пакет Init, а остальные шифрует Blowfish
ключом длинной 21 байт. При этом пакет LoginRequest шифруется только по алгоритму Blowfish, без дополнительного шифрования RSA.
Для шифрования пакетов гейм сервера используется алгоритм XOR. Ключ XOR генерируется случайно и передается клиенту в пакете CryptInit. Функции
шифрования и дешифрации приведены ниже:
/* Декодирует данные */
void decrypt( unsigned char *data, unsigned int len, unsigned char *Key )
{
int temp = 0;
for( unsigned int i = 0; i < len; ++i ) {
int temp2 = data & 0xff;
data = (temp2 ^ (Key[i & 15] & 0xff) ^ temp);
temp = temp2;
};
int old = Key[8] & 0xff;
old |= (Key[9] << 0x08) & 0xff00;
old |= (Key[10] << 0x10) & 0xff0000;
old |= (Key[11] << 0x18) & 0xff000000;
old += len;
Key[8] = old &0xff;
Key[9] = (old >> 0x08) & 0xff;
Key[10] = (old >> 0x10) & 0xff;
Key[11] = (old >> 0x18) & 0xff;
};
/* Кодирует данные */
void encrypt( unsigned char *data, unsigned int len, unsigned char *Key )
{
int temp = 0;
for( unsigned int i = 0; i < len; i+ {
int temp2 = data & 0xff;
data = (temp2 ^ (Key[i & 15] & 0xff) ^ temp);
temp = data;
};
int old = Key[8] & 0xff;
old |= (Key[9] << 0x08) & 0xff00;
old |= (Key[10] << 0x10) & 0xff0000;
old |= (Key[11] << 0x18) & 0xff000000;
old += len;
Key[8] = old &0xff;
Key[9] = (old >> 0x08) & 0xff;
Key[10] = (old >> 0x10) & 0xff;
Key[11] = (old >> 0x18) & 0xff;
};
С каждым кодированным/декодированным пакетом ключ изменяется на длину пакета, поэтому нужно использовать два отдельных экземпляра ключа – один для
шифрования исходящих пакетов, второй для расшифровки входящих.
Все пакеты шифруются начиная с 3-го байта, т.е. размер пакета никогда не шифруется.
Порядок авторизации на логин сервере
Во первых сразу замечу что существует две ревизии протокола использующиеся на данный момент(может и больше но мне о них не известно) - c621 и 785a.
Отличие их в том что в c621 используется дополнительное шифрование и авторизация GameGuard. Пакеты Init и RequestAuthLogin также отличаются.
Определить версию можно по размеру пакета Init, для ревизии 785a он составляет 11 байт, для c621 – 170.
1. Сразу после установки соединения сервер отправляет клиенту пакет Init
2. В ответ на него клиент отправляет пакет RequestGGAuth(в ревизии протокола 785a этот пакет не высылается)
3. Сервер отвечает на него пакетом GGAuth (в ревизии протокола 785a этот пакет не высылается)
4. Если сервер ответил, что авторизация прошла успешно, то клиент высылает пакет RequestAuthLogin, содержащий логин и пароль.
5. Проверка логина и пароля, в случае неудачи, сервер высылает пакет LoginFail содержащий причину неудачи, инатче высылается пакет LoginOk, содержащий
session key #1.
6. Далее клиент запрашивает список серверов пакетом RequestServerList
7. В ответ на этот пакет сервер высылает клиенту ServerList, в нем содержиться список серверов и их IP адреса с номерами портов.
8. После выбора game-сервера, и нажатия на ОК, клиент отсылает пакет RequestServerLogin
9. Сервер авторизации выполняет проверки на максимальное количество игроков, доступность сервера и т.п., если все проверки пройдены, то высылает пакет
PlayOk,содержащий session key #2, этот ключь генерируется из текущего времени системы в мс, номера сокета и еще всякой лабуды. После этого клиент
отключается от логин сервера и подключается к гейм серверу.
Порядок авторизации на гейм сервере
1. После установки соединения, клиент высылает пакет ProtocolVersion, содержащий версию протокола.
2. Сервер высылает пакет CryptInit, содержащий XOR ключ которым будут шифроваться все следующие пакеты.
3. Клиент высылает пакет AuthLogin выбраному серверу, содержащий session key #1, session key #2 и логин. При несовпадении с теми ключами и логином,
что храняться на сервере авторизации клиент отключается.
4. Сервер высылает пакет CharList, содержащий список всех чаров на аккаунте.
5. Тут идет процесс создания/удаления и выбора чара, после того как чар выбран и нажата кнопка Start, клиент отправляет пакет CharacterSelected
6. Клиент отправляет 2 пакета - RequestQuestList и RequestExManorList
7. Сервер высылает пакет ExSendManorList
8. Сервер высылает пакет QuestList
9. Клиент отправляет пакет EnterWorld
10. Сервер отправляет пакет UserInfo, который также служит сигналом окончания загрузки.
11. Все мы в игре. Сервер с периодичностью 60 секунд высылает пакет NetPingRequest, на который клиент должен ответить пакетом NetPing
В описании пакетов будут встречаться ObjectID и ItemID, ItemID это идентификатор типа предмета, например у авадон робы он 2406. А ObjectID это
уникальный идентификатор самого предмета в игре. Например у двух чаров есть авадон роба, ItemID робы у каждого из них будет одинаковый - 2406, тогда
как ObjectID будет уникальным.
00 - RequestAuthLogin
02 - RequestServerLogin
05 - RequestServerList
07 - RequestGGAuth
00 - Init
01 - LoginFail
02 - AccountKicked
03 - LoginOk
04 - ServerList
06 - PlayFail
07 - PlayOk
0B - GGAuth
RequestAuthLogin
29 DD 95 4E // \
77 C3 9C FC // | хз что
97 AD B6 20 // |
07 BD E0 F7 // /
XX XX XX XX ... // 16 байт blowfish ключа которым шифруются все последующие пакеты
00
XX XX XX XX // контрольная сумма пакета
Пакет для Interlude содержит дополнительные 4 байта:
XX XX XX XX // XOR ключ которым зашифрован пакет
RequestAuthLogin, в случае успешной проверки логина и пароля.
Формат:
03
XX XX XX XX // SessionKey1 первая часть
XX XX XX XX // SessionKey1 вторая часть
00 00 00 00
00 00 00 00
EA 03 00 00
00 00 00 00
00 00 00 00
02 00 00 00
XX XX XX XX ... // массив из 16 байт, назначение неизвестно
00 - CryptInit
01 - MoveToLocation
04 - UserInfo
0E - StatusUpdate
13 - CharList
14 - AuthLoginFail
19 - CharCreateOk
1A - CharCreateFail
23 - CharDeleteOk
24 - CharDeleteFail
25 - ActionFailed
2F - ChangeWaitType
38 - TeleportToLocation
3E - ChangeMoveType
7E - LogoutOK
80 - QuestList
D3 - NetPingRequest
AF - ServerSocketClose
E1 - ChairSit
FE:1B - ExSendManorList
Logout
Формат:
7E
NetPing, клиент отключаеться.
Формат:
D3
XX XX XX XX // идентификатор пинга. Вроде как генерируется случайно
00 - ProtocolVersion
01 - MoveBackwardToLocation
02 - Say
03 - EnterWorld
04 - Action
08 - AuthRequest
09 - Logout
0A - AttackRequest
0B - CharacterCreate
0C - CharacterDelete
0D - CharacterSelected
0F - RequestItemList
11 - RequestUnEquipItem
12 - RequestDropItem
14 - UseItem
15 - TradeRequest
16 - AddTradeItem
17 - TradeDone
1B - RequestSocialAction
1C - ChangeMoveType // устарел. Теперь юзается 'RequestActionUse'
1D - ChangeWaitType // устарел. Теперь юзается 'RequestActionUse'
1E - RequestSellItem
1F - RequestBuyItem
21 - RequestBypassToServer
24 - RequestJoinPledge
25 - RequestAnswerJoinPledge
26 - RequestWithdrawalPledge
27 - RequestOustPledgeMember
29 - RequestJoinParty
2A - RequestAnswerJoinParty
2B - RequestWithDrawalParty
2C - RequestOustPartyMember
2F - RequestMagicSkillUse
30 - Appearing
33 - RequestShortCutReg
35 - RequestShortCutDel
37 - RequestTargetCanceld
38 - Say2
3С - RequestPledgeMemberList
3F - RequestSkillList
40 - AnswerTradeRequest
45 - RequestActionUse
46 - RequestRestart
48 - ValidatePosition
4A - StartRotating
4B - FinishRotating
4D - RequestStartPledgeWar
4F - RequestStopPledgeWar
55 - RequestGiveNickName
58 - RequestEnchantItem
59 - RequestDestroyItem
5E - RequestFriendInvite
5F - RequestAnswerFriendInvite
60 - RequestFriendList
61 - RequestFriendDel
62 - CharacterRestore
63 - RequestQuestList
64 - RequestQuestAbort
66 - RequestPledgeInfo
68 - RequestPledgeCrest
6A - RequestRide
6B - RequestAquireSkillInfo
6C - RequestAquireSkill
6D - RequestRestartPoint
6E - RequestGMCommand
6F - RequestPartyMatchConfig
70 - RequestPartyMatchList
71 - RequestPartyMatchDetail
72 - RequestCrystallizeItem
77 - SetPrivateStoreMsgSell
81 - RequestGmList
82 - RequestJoinAlly
83 - RequestAnswerJoinAlly
84 - AllyLeave
85 - AllyDismiss
88 - RequestAllyCrest
89 - RequestChangePetName
8A - RequestPetUseItem
8B - RequestGiveItemToPet
8C - RequestGetItemFromPet
8E - RequestAllyInfo
8F - RequestPetGetItem
94 - SetPrivateStoreMsgBuy
98 - RequestStartAllianceWar
9А - RequestStopAllianceWar
A0 - RequestBlock
A2 - RequestSiegeAttackerList
A4 - RequestJoinSiege
A8 - NetPing
AС - RequestRecipeBookOpen
B9 - RequestEvaluate
BA - RequestHennaList
BB - RequestHennaItemInfo
BС - RequestHennaEquip
C1 - RequestMakeMacro
C2 - RequestDeleteMacro
CF - RequestAutoSoulShot
D0:06 - RequestExEnchantSkillInfo
D0:07 - RequestExEnchantSkill
D0:08 - RequestExManorList
D0:10 - RequestExPledgeCrestLarge
D0:11 - RequestExSetPledgeCrestLarge
EE - RequestChangePartyLeader
RequestRestartPoint и ValidatePosition
Формат:
30
RequestNetPing
Формат:
A8
XX XX XX XX // Это число берется из пакета RequestNetPing, отправляемого сервером
XX XX XX XX // Пинг
RequestRecipeBookOpen
Назначение: открыть книгу рецептов
Формат:
AС
RequestEvaluate
Назначение: запрос на рекомендацию игрока
Формат:
B9
XX XX XX XX // ID цели
RequestHennaList
Назначение: запросить список доступных татуировок
Формат:
BA
XX XX XX XX // неизвестно
RequestHennaItemInfo
Назначение: получить информацию о татуировке
Формат:
BB
XX XX XX XX // ID татуировки
RequestHennaEquip
Назначение: запрос на нанесение татуировки
Формат:
BС
XX XX XX XX // ID татуировки которую нужно нанести
RequestMakeMacro
Назначение: запрос на создание макроса
Формат:
С1
XX XX XX XX // ID макроса
XX XX XX XX 00 00 // строка содержащая имя макроса
XX XX XX XX 00 00 // строка с описанием макроса
XX XX XX XX 00 00 // строка с текстом на иконке
XX // ID иконки
XX // количество строк
// <<<<< Следующий блок повторяется столько раз, сколько строк в макросе.
XX // строка
XX // тип
XX // ID скилла
XX // ID ярлыка на панели
XX XX XX XX 00 00 // имя комманды
// конец повторяющегося блока
RequestDeleteMacro
Назначение: запрос на удаление макроса
Формат:
C2
XX XX XX XX // ID макроса
RequestAutoSoulShot
Назначение: включает/выключает использование AutoSS
Формат:
CF
XX XX XX XX // идентификатор итема
XX XX XX XX // 1 - включить : 0 - выключить
RequestExEnchantSkillInfo
Назначение: запросить информацию о заточке скила
Формат:
D0
06
00
XX XX XX XX // ID скила
XX XX XX XX // уровень скила
RequestExEnchantSkill
Назначение: запросить заточку скила
Формат:
D0
07
00
XX XX XX XX // ID скила
XX XX XX XX // уровень скила
RequestExManorList
Назначение: запросить заточку скила
Формат:
D0
08
00
RequestExPledgeCrestLarge
Назначение: запросить данные изображения большой иконки клана(те что размещаються на вещах клана типа щитов) клана
Формат:
D0
10
XX XX XX XX // ID иконки
RequestExSetPledgeCrestLarge
Назначение: отправить данные изображения большой иконки клана(те что размещаються на вещах клана типа щитов) на сервер
Формат:
D0
11
XX XX XX XX // размер данных
// <<<<< Следующий блок повторяется столько раз, сколько байт в данных изображения
XX // данные изображения
// конец повторяющегося блока
RequestChangePartyLeader
Назначение: передает лидерство в пати
Формат:
EE
XX XX XX XX 00 00 // строка с именем чара, которому передается лидерство