После прочтения постов о Settlers и конкретно Пишем утилиту для Settlers Online, возникло желание несколько оптимизировать описанный там процесс, т.к. подход меня несколько смутил, и, заодно, рассмотреть аспект amf формата в C#. Предположим, что выбрана задача написать бота для этой игры.
Для начала нам необходимо разобраться с этапами логина.
Для начала определимся с инструментами:
1. Charles — отладочный прокси, поддерживающий формат amf сообщейний (триал раздражает долгим наг-скрином, раз в пол-часа выключается).
2. Fiddler — тоже прокси, намного лучше справляющийся с https траффиком (бесплатный).
3. Что-то для просмотра самой флешки, тут на выбор:
— AS3 Sorcerer — быстрый, легкий, хорошо справляется с протекторами флеша (есть триал, иногда отображающий наг-скрин с веселыми картинками).
— связка SWF Decrypt (бесплатный) + любой декомпилятор флеша, например http://www.sothink.com/ (триальная версия имеет урезанный функционал, но нам хватит).
4. FluorineFx — open source framework, поддерживающий amf0 и amf3 форматы.
Вооружившись всем необходимым, можно приступать к разбору этапов логина игры:
1. Непосредственно логин, используя https к https://www.diesiedleronline.de/de/api/user/login
2. Получение ключа сессии и пароля на чат-сервере, используя «Big Brother» сервис http://bb.diesiedleronline.de/
3. Первые пакеты уже к основному гейтвею игры.
Начнем по-порядку, для того, чтобы посмотреть что же шлется при логине, запустим Fiddler, разрешим в настройках вскрывать https-траффик и залогинимся в игру Найдем нужный пакет и посмотрим формат запроса
Расписывать как делаются элементарные вещи, вроде http-запросов я не буду, предполагая, что Вам это известно. Единственное замечение, что нам нужно обработать момент с верификацией сертификата, для этого достаточно добавить такой код:
Таким же образом подглядываем что же уходит к Большому Брату. Получаем адрес гейтвея, ключи чат-сервера.
А вот дальше начинается самое интересное, уходящие из клиента пакеты замечательно видны, а вот ответы сервера не парсятся. Для этого нам и пригодится возможность подсмотреть оригинальный код флешки, но об этом чуть позже.
Игра написана с использованием Flex'a, поэтому использует amf3 формат сообщений. Для коннекта к серверу нам нужен адрес гейтвея (получается у ББ), имя функции, которая дергается на сервере, непосредственно параметры.
Итак, запускаем игру, открываем Charles и смотрим формат уходящих пакетов
Выходит, что на сервер уходит объект dServerCall, содержащий в dataObject'e дополнительные параметры и имеющий целочисленный type.
ОК, все просто, быстро набрасываем класс и запинаемся на весьма элементарной вещи…
FluorineFx — замечательная библиотека устраивает нас всем, кроме элементарного — маппинга (сопоставления) имен классов и их полей. Прийдется ее немного пропатчить, для этого заведем собственный атрибут
С помощью него мы будем сопоставлять имена объектов и их свойств в нашем боте и на сервере.
Продебажив библиотеку можно понять, что нам нужно вставить проверку наличия атрибута в IO.AMFWriter'e в методах WriteAMF3Object (помните, что мы используем флекс, поэтому только amf3 формат) и GetMember. Получается достаточно просто
где
Аналогичным образом правим AMFReader. Конечно, можно и, собственно, нужно настроить элементарное кеширование найденных свойств, но описывать этот процесс я не буду, ибо там все просто.
Кроме того, для того, чтобы при десериализации данных наши классы были видимы для создания инстансов, необходимо «зарегестрировать» их в кеше Fluorine. Для этого ищем класс ObjectFactory и добавляем метод
и при старте нашего приложения регистрируем все наши классы
Кстати, AmfObjectName.DefaultPrefix заведен просто для того, чтобы во всех классах не писать одно и тоже в атрибутах:)
Ну и теперь пришла пора описать наш класс:
Все готово для первой отправки данных и разбора что же собственно заставляет Charles не справляться с парсингом. Открываем пример из библиотеки, делаем, запускаем и… получаем в ответ только исключение. Не зря, видимо, Чарли падал. ОК, но зато у нас есть имя виновника: dUniqueID. Судя по исключению не удается обработать IExternalizable элемент. Идем в Гугл и находим, что это определенный формат, позволяющий пользователю самому сериализовывать объект. Вот и первая проблема. Не зная формата объекта и очередности записи его полей ничего нам не светит. Поэтому нам и пригодится возможность подсмотреть что же там сделали разработчики. Открываем Sorcerer, натравливаем на флешку, ищем нужный файл и видим нужные нам данные
Отлично, осталось реализовать это, воспользовавшись интерфейсом, заботливо предоставленным разработчиками Fluorine:
Осталось по аналогии оформить необходимые нам классы, побороть ошибку и, наконец-то, первые данные получены:
Понятно, что это самое начало, но основные проблемы всегда бывают на старте:)
Если будут желающие, то буду продолжать писать бота и выкладывать результаты.
Для начала нам необходимо разобраться с этапами логина.
Для начала определимся с инструментами:
1. Charles — отладочный прокси, поддерживающий формат amf сообщейний (триал раздражает долгим наг-скрином, раз в пол-часа выключается).
2. Fiddler — тоже прокси, намного лучше справляющийся с https траффиком (бесплатный).
3. Что-то для просмотра самой флешки, тут на выбор:
— AS3 Sorcerer — быстрый, легкий, хорошо справляется с протекторами флеша (есть триал, иногда отображающий наг-скрин с веселыми картинками).
— связка SWF Decrypt (бесплатный) + любой декомпилятор флеша, например http://www.sothink.com/ (триальная версия имеет урезанный функционал, но нам хватит).
4. FluorineFx — open source framework, поддерживающий amf0 и amf3 форматы.
Вооружившись всем необходимым, можно приступать к разбору этапов логина игры:
1. Непосредственно логин, используя https к https://www.diesiedleronline.de/de/api/user/login
2. Получение ключа сессии и пароля на чат-сервере, используя «Big Brother» сервис http://bb.diesiedleronline.de/
3. Первые пакеты уже к основному гейтвею игры.
Начнем по-порядку, для того, чтобы посмотреть что же шлется при логине, запустим Fiddler, разрешим в настройках вскрывать https-траффик и залогинимся в игру Найдем нужный пакет и посмотрим формат запроса
Расписывать как делаются элементарные вещи, вроде http-запросов я не буду, предполагая, что Вам это известно. Единственное замечение, что нам нужно обработать момент с верификацией сертификата, для этого достаточно добавить такой код:
-
- System.Net.ServicePointManager.ServerCertificateValidationCallback
- = delegate(object sender,
- X509Certificate certificate,
- X509Chain chain,
- SslPolicyErrors sslPolicyErrors)
- {
- return true;
- };
-
Таким же образом подглядываем что же уходит к Большому Брату. Получаем адрес гейтвея, ключи чат-сервера.
А вот дальше начинается самое интересное, уходящие из клиента пакеты замечательно видны, а вот ответы сервера не парсятся. Для этого нам и пригодится возможность подсмотреть оригинальный код флешки, но об этом чуть позже.
FluorineFx
Игра написана с использованием Flex'a, поэтому использует amf3 формат сообщений. Для коннекта к серверу нам нужен адрес гейтвея (получается у ББ), имя функции, которая дергается на сервере, непосредственно параметры.
Итак, запускаем игру, открываем Charles и смотрим формат уходящих пакетов
Выходит, что на сервер уходит объект dServerCall, содержащий в dataObject'e дополнительные параметры и имеющий целочисленный type.
ОК, все просто, быстро набрасываем класс и запинаемся на весьма элементарной вещи…
FluorineFx — замечательная библиотека устраивает нас всем, кроме элементарного — маппинга (сопоставления) имен классов и их полей. Прийдется ее немного пропатчить, для этого заведем собственный атрибут
-
- [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
- public class AmfObjectName : System.Attribute
- {
- public const string DefaultPrefix = "defaultGame.Communication.VO.";
-
- public string Name { get; set; }
-
- public AmfObjectName(string name)
- {
- Name = name;
- }
- }
-
С помощью него мы будем сопоставлять имена объектов и их свойств в нашем боте и на сервере.
Продебажив библиотеку можно понять, что нам нужно вставить проверку наличия атрибута в IO.AMFWriter'e в методах WriteAMF3Object (помните, что мы используем флекс, поэтому только amf3 формат) и GetMember. Получается достаточно просто
-
- if (IsClassAttributed(type))
- {
- propertyInfo = FindProperty(type, member.Name);
- }
-
где
-
- public static bool IsClassAttributed(Type type)
- {
- var res = type.GetCustomAttributes(typeof(AmfObjectName), false);
- return null != res && res.Length > 0;
- }
-
- public static PropertyInfo FindProperty(Type type, string amfObjName)
- {
- foreach (var prop in type.GetProperties())
- {
- var attrs = prop.GetCustomAttributes(typeof(AmfObjectName), false);
- if (null == attrs || 0 == attrs.Length || ((AmfObjectName)attrs[0]).Name != amfObjName)
- {
- continue;
- }
-
- return prop;
- }
-
- return null;
- }
-
Аналогичным образом правим AMFReader. Конечно, можно и, собственно, нужно настроить элементарное кеширование найденных свойств, но описывать этот процесс я не буду, ибо там все просто.
Кроме того, для того, чтобы при десериализации данных наши классы были видимы для создания инстансов, необходимо «зарегестрировать» их в кеше Fluorine. Для этого ищем класс ObjectFactory и добавляем метод
-
- public static void AddToLocate(Type type, string mapName)
- {
- _typeCache.Add(mapName, type);
- }
-
и при старте нашего приложения регистрируем все наши классы
-
- foreach(var type in Assembly.GetExecutingAssembly().GetTypes())
- {
- var attrs = type.GetCustomAttributes(typeof(AmfObjectName), false);
- if (null == attrs || 0 == attrs.Length)
- {
- continue;
- }
-
- ObjectFactory.AddToLocate(type, AmfObjectName.DefaultPrefix + ((AmfObjectName)attrs[0]).Name);
- }
-
Кстати, AmfObjectName.DefaultPrefix заведен просто для того, чтобы во всех классах не писать одно и тоже в атрибутах:)
Ну и теперь пришла пора описать наш класс:
-
- namespace SettlersControl.Objects
- {
- [AmfObjectName("dServerCall")]
- public class SettlerRequest
- {
- public static int PlayerZoneId = 0;
-
- [AmfObjectName("type")]
- public int Type { get; set; }
- [AmfObjectName("zoneID")]
- public int ZoneId { get; set; }
- [AmfObjectName("dataObject")]
- public object DataObject { get; set; }
-
- public SettlerRequest(SettlerMessages message, object dataObject)
- {
- Type = (int)message;
- ZoneID = PlayerZoneId;
- DataObject = dataObject;
- }
- }
- }
-
Все готово для первой отправки данных и разбора что же собственно заставляет Charles не справляться с парсингом. Открываем пример из библиотеки, делаем, запускаем и… получаем в ответ только исключение. Не зря, видимо, Чарли падал. ОК, но зато у нас есть имя виновника: dUniqueID. Судя по исключению не удается обработать IExternalizable элемент. Идем в Гугл и находим, что это определенный формат, позволяющий пользователю самому сериализовывать объект. Вот и первая проблема. Не зная формата объекта и очередности записи его полей ничего нам не светит. Поэтому нам и пригодится возможность подсмотреть что же там сделали разработчики. Открываем Sorcerer, натравливаем на флешку, ищем нужный файл и видим нужные нам данные
Отлично, осталось реализовать это, воспользовавшись интерфейсом, заботливо предоставленным разработчиками Fluorine:
-
- [AmfObjectName("dUniqueID")]
- public class SettlerUniqueId : IExternalizable
- {
- [AmfObjectName("uniqueID1")]
- public int UniqueID1 {get; set; }
- [AmfObjectName("uniqueID2")]
- public int UniqueID2 {get; set; }
-
- public void ReadExternal(IDataInput input)
- {
- UniqueID1 = input.ReadInt();
- UniqueID2 = input.ReadInt();
- }
-
- public void WriteExternal(IDataOutput output)
- {
- output.WriteInt(UniqueID1);
- output.WriteInt(UniqueID2);
- }
- }
-
Осталось по аналогии оформить необходимые нам классы, побороть ошибку и, наконец-то, первые данные получены:
Понятно, что это самое начало, но основные проблемы всегда бывают на старте:)
Если будут желающие, то буду продолжать писать бота и выкладывать результаты.