Amf and C # on the example of a bot for Settlers online
After reading the posts about Settlers and specifically Writing a utility for Settlers Online , there was a desire to somewhat optimize the process described there, since The approach confused me a little, and, at the same time, to consider the aspect of the amf format in C #. Suppose a task is chosen to write a bot for this game.
First we need to understand the steps of the login. To get started, let's decide on the tools:
1. Charles - a debugging proxy that supports the amf format of messages (the trial is annoying with a long nag-screen, it turns off every half-hour). 2. Fiddler is also a proxy, coping much better with https traffic (free). 3. Something to view the flash drive itself, here to choose: - AS3 Sorcerer - fast, easy, copes well with flash protectors (there is a trial, sometimes showing a nag-screen with funny pictures). - a bunch of SWF Decrypt (free) + any flash decompiler, for example http://www.sothink.com/ (the trial version has reduced functionality, but we have enough). 4. FluorineFx is an open source framework that supports amf0 and amf3 formats. ')
Armed with everything you need, you can proceed to the analysis of the game login steps:
1. Directly login using https to https://www.diesiedleronline.de/de/api/user/login 2. Getting the session key and password on the chat server using the “Big Brother” service http://bb.diesiedleronline.de/ 3. The first packages already to the main gateway of the game.
Let's start in order, in order to see what is sent at login, let's run Fiddler, allow opening https traffic in the settings and log in to the game. Find the right package and see the request format
I will not describe how elementary things are done, like http-requests, I will not assume that you know this. The only remark that we need to process the moment with the verification of the certificate, for this it is enough to add such code:
In the same way we pry what goes to Big Brother. We get the address of the gateway, the keys of the chat server.
But then the most interesting thing begins, the packages leaving the client are remarkably visible, but the server’s responses are not parsed. For this we can use the opportunity to see the original flash drive code, but more on that later.
FluorineFx
The game is written using Flex, so it uses amf3 message format. To connect to the server, we need the address of the gateway (obtained from the BB), the name of the function that is jerked on the server, the parameters themselves. So, start the game, open Charles and see the format of the outgoing packages. It turns out that the dServerCall object, which contains additional parameters in dataObject and has an integer type, goes to the server. OK, everything is simple, we quickly throw in a class and stammer at a very basic thing ... FluorineFx - a wonderful library suits us all, except for the elementary - mapping (mapping) of class names and their fields. I'll have to patch it a bit, for this we will get our own attribute
[ 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 ;
}
}
With it, we will match the names of objects and their properties in our bot and on the server. Having done this, the library can be understood that we need to insert an attribute presence check into IO.AMFWriter in the WriteAMF3Object methods (remember that we use flex, therefore only amf3 format) and GetMember. It turns out quite simple
if ( IsClassAttributed ( type ) )
{
propertyInfo = FindProperty ( type, member. Name ) ;
}
Where
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 )
Similarly, we rule AMFReader. Of course, you can and, in fact, need to adjust the elementary caching of the properties found, but I will not describe this process, because everything is simple there. In addition, in order for our classes to be deserialized to be visible to create instances, it is necessary to “register” them in the Fluorine cache. To do this, look for the ObjectFactory class and add the method
public static void AddToLocate ( Type type, string mapName )
{
_typeCache. Add ( mapName, type ) ;
}
and when starting our application we register all our classes
foreach ( var type in Assembly. GetExecutingAssembly ( ) . GetTypes ( ) )
By the way, AmfObjectName.DefaultPrefix is ​​wound up just to not write the same thing in attributes in all classes :)
Well, now it's time to describe our class:
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 ;
}
}
}
Everything is ready for the first data sending and parsing, which actually causes Charles not to cope with parsing. We open an example from the library, we do it, we launch it and ... we get only an exception in response. No wonder Charlie apparently fell. OK, but we have the name of the culprit: dUniqueID. Judging by the exception, the IExternalizable element cannot be processed. Go to Google and find that it is a specific format that allows the user to serialize the object itself. That is the first problem. Without knowing the format of the object and the order of recording its fields, nothing shines for us. Therefore, we can use the opportunity to see what the developers did there. Open the Sorcerer, set it on the USB flash drive, look for the file you need and see the data we need Great, it remains to implement this, using the interface carefully provided by the developers of 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 ) ;
}
}
It remains to do by analogy to arrange the classes we need, to overcome the error and, finally, the first data is obtained: It is clear that this is the very beginning, but the main problems are always at the start :) If you are willing, I will continue to write a bot and upload the results.