Skip to content
ValooFX edited this page Mar 13, 2015 · 32 revisions

Packets 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.

Clone this wiki locally