-
Notifications
You must be signed in to change notification settings - Fork 0
Packet Layout
Every packet has this header:
struct PacketHeader (32 bits)
{
WORD PacketID;
WORD PacketSize;
} If the PacketSize attribute does not match the actual packet size, the client will not trigger the ReadMessage or will trigger an error. If the client wants to read more data than you give to him, it will sayin the logs: "PACKET_NAME READ BUFFER OVERFLOW".
Note: For what I've seen so far, the client does not check the size in its read algorithm to verify if its is consistent with the type of packet you send.
The client will just try to read everything it has to read and if you send not enough data, triggers the BUFFER OVERFLOW error seen above. This behavior is due to the d_mmos.dll::d_message::read (const void* outData, uint dataSize) method which check the message size everytime you ask it to read a value in the packet. If the size alreday read + the size of the data you ask to read is greater than the message size: error.
Note: The following sizes does not take the packet's header into account, because the size contained in the packet's header do the same.
struct CONNECT (4 bytes)
{
struct PacketHeader header;
DWORD unknownDword; //statuscode?
};
struct DISCONNECT (various size)
{
struct PacketHeader header;
DWORD unknownDword; //statuscode?
DWORD reasonNumChars;//num chars in the reason string
char [reasonNumChars*2] reason;//in UTF16-LE
};
struct C2L_USER_LOGIN (various size)
{
struct PacketHeader header;
DWORD clientVersion;//svn build version
DWORD loginNumCharacters;
BYTE[loginNumCharacters*2] loginString;//in UTF-16LE
DWORD passwordNumCharacters;
BYTE[passwordNumCharacters * 2] passwordString;//in UTF16-LE
};
struct L2C_USER_LOGIN_ACK (2 DWORDS = 8 bytes)
{
struct PacketHeader header;
DWORD zeroDword;//unknown data, its value does nothing
DWORD statusCode;
}; statusCode possible values:
- 0: Login OK
- 1: Wrong client version
- 2: Bad login or password
- 3: Bad login or password (yes, the same)
- 6: Failed to connect to spellborn network
- 8: This account has been banned
- 9: Account temporarily suspended, contact support
- 10: This account is waiting for email confirmation
- 11: Error during authentication, please try again
struct C2L_QUERY_UNIVERSE_LIST (empty)
{
struct PacketHeader header;
};
struct Universe (various size)
{
DWORD universeID;
DWORD universeNameNumChars;
BYTE[universeNameNumChars*2] universeName; //UTF16-LE
DWORD universeLanguageNumChars;
BYTE[universeLanguageNumChars*2] universeLanguage; //UTF16-LE
DWORD universeTypeNumChars;
BYTE[universeTypeNumChars*2] universeType; //UTF16-LE
DWORD universePopulationNumChars;
BYTE[universePopulationNumChars*2] universePopulation; //UTF16-LE
};
struct L2C_QUERY_UNIVERSE_LIST_ACK (various size)
{
struct PacketHeader header;
DWORD unknownDWORD; // MUST BE 0 to work, integrity check?
DWORD universesNumber; //number of universes available
struct Universe[universesNumber] universesList; // universesNumber times the attributes in the struct Universe
};
struct C2L_UNIVERSE_SELECTED (size = 1 DWORD)
{
struct PacketHeader header;
DWORD universeID; //id of the selected universe
};
struct L2C_UNIVERSE_SELECTED_ACK (size = 1 DWORD)
{
struct PacketHeader header;
DWORD unknownDword#1; //MUST BE 0
DWORD unknownDword#2;
DWORD universePackageNameNumChars;
BYTE[universePackageNameNumChars*2] universePackageName;
DWORD tkey;//to investigate
DWORD gameServerIP;
WORD gameServerPort;
};Note: universePackageName is the name of the file in data/universes with the SBU sufix removed from its name (I am not speeking of the extension but the sufix), and without the extension.
For now it's Complete_Universe with length 17.
The tkey may be for "transport key" seems to be (not sure) a security allowing the game world to detect if a client did the login part before connecting to it. I think it works this way: the Login Server generate a tkey for a given player when it tries to login, send this key to the game world (by storing it in a commune DB or whatever) and to the client through this packet. When the client log into the game world, it sends back the key, the game world check if the key is correct and allow connection or not.
struct C2S_TRAVEL_CONNECT (size = 1 DWORD)
{
struct PacketHeader header;
DWORD tkey; //the tkey value the login server has send in the L2C_UNIVERSE_SELECTED_ACK packet
};
struct S2C_WORLD_PRE_LOGIN (size = 2 DWORD)
{
struct PacketHeader header;
DWORD unknownDWord;//MUST BE 0
DWORD worldId; //must be 1 for character selection
};Note: the worldId is the ID identifying the map to load. 1 is CharacterSelection.sbw, and then beginning to 100 (PT_Hawksmouth) are the other IDs. See the wiki page world IDs and spawn locations to load other maps.
struct C2S_WORLD_PRE_LOGIN_ACK (size = 1 DWORD)
{
struct PacketHeader header;
DWORD unknownDWord;// = 0; to investigate
};
See the struct page for structs indicated in the following packet.
//Local struct for S2C_CS_LOGIN
struct FameMap
{
DWORD characterID;//Key
DWORD characterFame;//Value
}
struct S2C_CS_LOGIN (various size)
{
struct PacketHeader header;
DWORD numCharacters;
struct SD_BASE_CHARACTER_INFO[numCharacters] charactersInfo;
DWORD fameMapSize;//=should be equal to numCharacters
struct FameMap[fameMapSize];
};
struct C2S_CS_CREATE_CHARACTER (various size)
{
struct PacketHeader header;
DWORD lod0size;
byte[lod0size] lod0;
DWORD lod1size;
byte[lod1size] lod1;
DWORD lod2size;
byte[lod2size] lod2;
DWORD lod3size;
byte[lod3size] lod3;
DWORD charNameNumChars;
BYTE[charNameNumChars*2] characterName;
DWORD classID;
DWORD fixedSkill1ID;//Hack/slash/shoot
DWORD fixedSkill2ID;//Hack/slash/shoot
DWORD fixedSkill3ID;//Hack/slash/shoot
DWORD customSkill1ID;//choosen by player
DWORD customSkill2ID;//choosen by player
DWORD unknwownDword;//=41 it changes when the character has a shield (=43)
}; Note: Lod0 Lod1 Lod2 Lod3 are packed structures with variables of different bit lengths, they always start with a dword size in bytes, the bytes should be read in reverse order in order to keep the order of the bits correct (i.e. where a variable crosses a byte boundary the lower bits of the var will be in a byte before the byte containing the upper bits, so reversing the order will allow you to use shift left on the whole structure to extract the variables).
The layouts for Lod0-3 are listed below with variables appearing in the order they should be read (i.e. in LOD0, Voice ID appears first and is held in the 13th byte).
/*LOD0 Size = 13
* 8-bits Voice ID
* 8-bits unused
* 8-bits unused
* 4-bits unused (Fourth tattoo in code=> head tattoo?)
* 4-bits Right Arm Tattoo (0 - 5)
* 4-bits Left Arm Tattoo (0 - 5)
* 4-bits Chest Tattoo (0 - 5)
* 8-bits Right Gauntlet Colour 1
* 8-bits Right Gauntlet Colour 2
* 8-bits Left Gauntlet Colour 1
* 8-bits Left Gauntlet Colour 2
* 8-bits Right Glove Colour 1
* 8-bits Right Glove Colour 2
* 8-bits Left Glove Colour 1
* 8-bits Left Glove Colour 2
*/
/*LOD1 Size = 20
* 8-bits Shin Right Colour 1
* 8-bits Shin Right Colour 2
* 8-bits Shin Left Colour 1
* 8-bits Shin Left Colour 2
* 8-bits Thigh Right Colour 1
* 8-bits Thigh Right Colour 2
* 8-bits Thigh Left Colour 1
* 8-bits Thigh Left Colour 2
* 8-bits Belt Colour 1
* 8-bits Belt Colour 2
* 8-bits Right Shoulder Colour 1
* 8-bits Right Shoulder Colour 2
* 8-bits Left Shoulder Colour 1
* 8-bits Left Shoulder Colour 2
* 8-bits Helmet Colour 1
* 8-bits Helmet Colour 2
* 8-bits Shoes Colour 1
* 8-bits Shoes Colour 2
* 8-bits Pants Colour 1
* 8-bits Pants Colour 2
*/
/*LOD2 Size = 15
* 8-bits Unused
* 4-bits Unused
* 8-bits Ranged Weapon ID //Polymo: 6 bits
* 6-bits Shield ID //Polymo: 8 bits
* 8-bits Melee Weapon ID
* 6-bits Shin Right
* 6-bits Shin Left
* 6-bits Thigh Right
* 6-bits Thigh Left
* 6-bits Belt
* 6-bits Gauntlet Right
* 6-bits Gauntlet Left
* 6-bits Shoulder Right
* 6-bits Shoulder Left
* 6-bits Helmet
* 6-bits Shoes //Polymo: 7 bits
* 8-bits Pants //Polymo: 7 bits
* 6-bits Glove right
* 6-bits Glove left
*/
/*LOD3 Size = 10 Bytes
* 1-Bit Unused
* 8-bits Chest Colour 1
* 8-bits Chest Colour 2
* 6-bits Chest Armour
* 8-bits Torso Colour 1
* 8-bits Torso Colour 2
* 8-bits Torso
* 8-bits Hair Colour
* 6-bits Hair Style
* 8-bits Body Colour
* 1-bit DisplayLogo
* 6-bits Head Type
* 2-bits Body Type
* 1-bit Male = 0 Female = 1
* 1-bit Human = 0 Daevi = 1
*/struct S2C_CS_CREATE_CHARACTER_ACK (various size)
{
struct PacketHeader header;
DWORD status;//0 = OK
struct SD_BASE_CHARACTER_INFO characterInfo;//Filled with values from previous packet, reformatted
};
struct C2L_CS_SELECT_CHARACTER (size = 4 bytes)
{
struct PacketHeader header;
DWORD characterId;
};
struct S2C_CS_SELECT_CHARACTER_ACK (size = 4 bytes)
{
struct PacketHeader header;
int errorCode; // only 2 has a special value
};
struct S2C_CS_DELETE_CHARACTER_ACK(size = 8 bytes)
{
struct PacketHeader header;
int statusCode; //0= success, 1=NotOwningCharacter, 2=WrongPassword
int characterID;
}Note: The S2C_CS_SELECT_CHARACTER_ACK packet is send only if there is an error. If there is no error, you can directly send a world prelogin then a world login packet.
struct C2S_GAME_PLAYERPAWN_CL2SV_UPDATEMOVEMENT (size = 29)
{
struct PacketHeader header;
DWORD Unknown1;
struct FVector position;
struct FVector direction;
byte FrameID;//start at 0 and increase each packet until 255, then restart at 0 etc
}
struct C2S_GAME_PLAYERPAWN_CL2SV_UPDATEMOVEMENTWITHPYSICS (size = 30)
{
struct PacketHeader header;
DWORD Unknown1;
struct FVector position;
struct FVector direction;
byte Physics;
byte FrameID;//start at 0 and increase each packet until 255, then restart at 0 etc
}Note: The FrameID in these two last messages are synchronized. Meaning that the same player sending first a UpdateMovement with a FrameID of 1, if it then send a UpdateMovementWithPhysics, the FrameID will be of 2.