This is an unofficial documentation of the Virtual Society Server-Client Protocol (VSCP). Most of the content written here are hypotheses from manual packet decoding, therefore it might not be accurate. The interpretations provided here should be considered entirely subjective.
Currently, it is also incomplete as some parts of the protocol lack any viable explaination. If you feel like something could use a better interpretation or you have a hypothesis on how something works, feel free to make a suggestion or a PR.
The protocol follows a client-server model and uses TCP for sending data between the server and its clients.
The data that it sends is sometimes a mix of binary and "human readable" data, therefore thoughout this documentation, both hexadecimal and string representations are provided where applicable. The strings might include C-like escape sequences.
A packet layout is described using a table, split into sections (rows) that follows one another in the exact order of the actual packet itself.
Example:
Section | Size | Type |
---|---|---|
Number of cats | 4 | uint32 |
Number of dogs | 2 | uint16 |
Some data | 12 | data |
Some text | ~ | string |
All of the section sizes provided here are in bytes. Sections with ~
as the size have a varying size.
data
: Raw (arbitrary) dataint8
,int16
,int32
: n-bit signed integeruint8
,uint16
,uint32
: n-bit unsigned integerstring
: Null-terminated string. Does not have a determined size.int32float
: Special type used to store floating point values as 32-bit big-endian signed integers. The float value would be multiplied by 65536 and then casted to a 32-bit signed integer.- The original float value is calculated by dividing the integer by 65536.
All other integer types are in little-endian byte order.
Throughout the documentation, you'll find mentions of a user's "aura". This is used to refer to the Aura system used by the server.
In a nutshell: Each user will have a defined radius where they can be seen; that is their aura. If a user's aura overlaps another user's, then they can "see" each other.
This system is referenced from: Benford, S., & Fahlén, L. (1993). A spatial model of interaction in large virtual environments. In ECSCW 1993: Proceedings of the Third European Conference on Computer Supported Cooperative Work. Kluwer Academic Publishers, Dordrecht, The Netherlands.
The Aura system has influence on what data the client will accept from other users. If a client was sent any message that makes use of the broadcast ID (which also identifies their Aura) from another user that can't be "seen" by it, it will actively ignore the message. The server should also not send any message that are not within their aura.
To establish a connection to the server, the client first performs a TCP 3-way handshake with the server. If that succeeds, it sends the hello
packet to the server:
Hex | String |
---|---|
0x68 0x65 0x6c 0x6c 0x6f 0x01 0x01 |
hello\x01\x01 |
The server then replies with a matching hello
packet, followed by the connection ID:
Section | Size | Type |
---|---|---|
Header | 13 | data |
Connection ID | 1 | uint8 |
The header data is as follows:
Hex | String |
---|---|
0x68 0x65 0x6c 0x6c 0x6f 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 |
hello\0\0\0\0\0\0\0\0 |
The connection ID is generated by the server and could be used to uniquely identify clients. The original server software supposedly generated this ID by simply incrementing a counter and using the value until it hits 255, then go back to 0.
After receiving the hello
packet from the server, the client should immediately follow up with a message packet containing a CMSG_NEW_USER
section.
After sending the hello
packet, the client and the server can now talk to each other through structured message packets. A packet may contain multiple message sections placed one after another.
A section will always start with the following data:
Section | Size | Type |
---|---|---|
Section type | 4 | uint32 |
The section type defines how the message section should be interpreted.
Section sizes varies between different section types:
- For General Message sections: 17 + n bytes (let n be the content size, see below)
- For Position Update sections: 27 bytes
Section | Size | Type |
---|---|---|
Section type | 4 | uint32 |
ID 1 | 4 | uint32 |
ID 2 | 4 | uint32 |
Opcode | 4 | uint32 |
Content size (n) | 1 | uint8 |
Content | n | data |
- Section type = 0
ID 1 and 2's values are set depending on the type of opcode used and does not necessarily correlate to the current client's IDs.
For client messages: ID 1 is usually set to the connection ID, while ID 2 is usually set to the client ID (see SMSG_CLIENT_ID
)
For server messages: ID values varies between each message type and content, check each of them for more details.
It is not important to distinguish between ID 1 and ID 2 (or between the connection ID and the client ID) as long as the server has enough information to identify a client and set unique IDs for each message.
The opcode describes how the content should be interpreted by the receiver.
- Known opcodes:
CMSG_NEW_USER = 0,
SMSG_CLIENT_ID = 1,
SMSG_USER_JOINED = 2,
SMSG_USER_LEFT = 3,
SMSG_BROADCAST_ID = 4,
MSG_COMMON = 6,
CMSG_STATE_CHANGE = 7,
SMSG_UNNAMED_1 = 8,
SMSG_USER_COUNT = 11
Opcodes prefixed with MSG are used by both the client and the server, while CMSG and SMSG are used exclusively by the client and the server respectively. UNNAMED
opcodes are opcodes that have no exact meaning bound to them (yet)
The following subsections describe the data expected in the content section of the message, depending on the opcode:
The first thing that the client sends after receiving the server's hello
reply is the CMSG_NEW_USER
message, which identifies the user.
Section | Size | Type |
---|---|---|
Username | ~ | string |
Avatar path | ~ | string |
The avatar path is a relative path based on where the avatar is located in the current world's directory, which could be a local or a remote directory (served over HTTP and loaded by Netscape). Example: avtwrl/01cat.wrl
After receiving the CMSG_NEW_USER
message, the server will proceed to broadcast SMSG_USER_COUNT
to all clients connected to it. The server will also send SMSG_CLIENT_ID
, SMSG_UNNAMED_1
, SMSG_USER_JOINED
and SMSG_BROADCAST_ID
messages back to the current client.
Used to send the client ID. It can be identical to the connection ID.
Section | Size | Type |
---|---|---|
Unknown | 3 | data |
Client ID | 1 | uint8 |
The unknown bytes are always set to zeroes.
In the original server software, the client ID (which is generated after CMSG_NEW_USER
was sent) might differ from the connection ID (which is generated when the socket was first opened). However, it is fine to use the same ID for both.
Used to announce that someone has joined a user's aura.
Section | Size | Type |
---|---|---|
ID 1 Type | 2 | uint16 |
Broadcast ID 1 | 2 | uint16 |
ID 2 Type | 2 | uint16 |
Broadcast ID 2 | 2 | uint16 |
Avatar path | ~ | string |
Username | ~ | string |
Broadcast ID 1 is always set to the broadcast ID of the user that joined the Aura. It is unknown what the second broadcast ID is used for, as it differs between users to users, although setting it to the same ID will work just fine. Both broadcast ID types must be set to 0.
Used to announce that someone has left a user's aura. This is also sent to other users within their aura when a user leaves the server.
Section | Size | Type |
---|---|---|
ID type | 2 | uint16 |
Broadcast ID | 2 | uint16 |
Used to send the client's broadcast ID, which will be used for messages that are bound to a specific user (position updates, chat, state changes, etc.)
Section | Size | Type |
---|---|---|
ID type | 2 | uint16 |
Broadcast ID | 2 | uint16 |
Type must always be 0. 0xFFFF
is a reserved type. The broadcast ID seems to be a randomly generated value from 1 to 0xFFFF
(0 is reserved for "no broadcast ID"). Could also be used to uniquely identify a client.
As the name implies, this is the most common opcode and is used for a number of things.
Section | Size | Type |
---|---|---|
ID type | 2 | uint16 |
Broadcast ID | 2 | uint16 |
Type | 4 | uint32 |
Subtype | 1 | uint8 |
Content | ~ | data |
The type defines what the content contains. Known types:
APPL_SPECIFIC = 0x10270000,
CHAT_SEND = 0x09000000,
NAME_CHANGE = 0x0D000000,
AVATAR_CHANGE = 0x0E000000,
TRANSFORM_UPDATE = 0x02000000,
CHARACTER_UPDATE = 0x0C000000,
VOICE_STATE = 0x12000000,
UNNAMED_1 = 0x10000000,
PRIVATE_CHAT = 0x0F000000
Possible subtype values are 0, 1, 2 and 3. They define what to do with the message.
- If subtype is 0 or 1, the message should be broadcasted to other clients.
- If subtype is 2 or 3, the message should be sent back to the client with the broadcast ID specified in the message. Usually, this is the ID of the same user, however some content types might have a different one (such as
PRIVATE_CHAT
)
Each type usually has a specific subtype. Note that when broadcasting messages to other clients, ID 1 and ID 2 must be set to those of the client being sent to.
The following subsections describe the content data of each type:
- Subtype = 2 (other values are possible, but deprecated)
Section | Size | Type |
---|---|---|
Unknown | 1 | uint8 |
Method name | ~ | string |
Argument | ~ | string |
ID type | 2 | uint16 |
Broadcast ID | 2 | uint16 |
Used to invoke the specified script function. Sent by calling the vs.Vscp.sendApplSpecificMsgWithDist
Java API.
The ID type must be set to 0xFFFF
and broadcast ID must be set to 0xF1D8
(unknown purpose/unconfirmed). This type of message could also be sent by the client without having a broadcast ID.
- Subtype = 0
Used to send chat messages.
Section | Size | Type |
---|---|---|
Message | ~ | string |
- Subtype = 1
Used to announce a name change.
Section | Size | Type |
---|---|---|
New name | ~ | string |
- Subtype = 1
Used to announce an avatar change.
Section | Size | Type |
---|---|---|
New avatar | ~ | string |
- Subtype = 1
Used to update the character's transformation matrix and position. The transformation matrix is a 3x3 matrix with 9 int32float values.
Section | Size | Type |
---|---|---|
Matrix value 1 | 4 | int32float |
... | ||
Matrix value 9 | 4 | int32float |
X | 4 | int32float |
Y | 4 | int32float |
Z | 4 | int32float |
Note: Due to how the data is laid out, it's safe to assume that the character's position (or translation) is not contained within the transformation matrix.
- Subtype = 1
Used to update the character data. This is also a companion to CMSG_STATE_CHANGE
, since it can't actually broadcast the user's state to other players.
Section | Size | Type |
---|---|---|
Data | ~ | string |
The data will look something like this: sleep:0 1:000000000000:58:0:
- The first part (sleep:0) tells whether the character is sleeping or not (1 for true, 0 for false)
- The second part contains the character data, each part separated by a colon:
- The first value (1) is the number of the avatar.
- Next is the body parts's color and scale:
- Each body part has two values, the first value is the color, the second value is the scale.
- The amount of values depends on how much body parts the user's avatar has.
- After that is the minutes spent using that particular avatar (58)
- The final value is user's medal, which is awarded if they have spent enough time using that avatar (none = 0, happy = 1, lucky = 2, lovely = 3).
Used to update the voice chat state.
Section | Size | Type |
---|---|---|
Unknown | 4 | uint32 |
Unknown | 1 | uint8 |
State | 1 | uint8 |
Both unknown values always seem to be set to 1.
A state value of 2 seems to stand for Disabled, while 3 stands for Enabled.
Unknown purpose. Sent by Browser v2.0 alpha 2 or later upon connecting to the server, a bit after sending the CMSG_NEW_USER
packet. Probably used for voice chat?
Used to initiate and send private chat messages to other users.
Section | Size | Type |
---|---|---|
ID type | 2 | uint16 |
Broadcast ID | 2 | uint16 |
Message | ~ | string |
Note that the broadcast ID in the header of the message is for the user that the message is being sent to, while the broadcast ID here is for the user that sent the message.
The message may contain special values that starts with %%
to communicate with the client application itself. Possible values:
%%REQ
: Used to initiate a request to chat with another user. Also used as a keep-alive message which is sent periodically.%%RINGING
: Let the sender know that the end user is being prompted for the chat request.%%ACCEPT
: Accept the chat request.%%REJECT
: Reject the chat request.%%OK
: Used to reply to a keep-alive%%REQ
message.%%END
: Used by either clients to end the chat session.
Used to announce a player state change.
Section | Size | Type |
---|---|---|
State | 1 | uint8 |
Known state values:
0x00
: NOT_CONNECTED0x01
: CONNECTING0x02
: CONNECTED0x03
: DISCONNECTING0x04
: ACTIVE0x05
: SLEEP
Active and Sleep can be activated manually by the user. The Disconnecting state is triggered when the Browser is closing itself and will terminate the connection shortly. Other states are used internally by the client and are not sent to the server.
If the state is Active or Sleep, the client will follow up with another packet containing a MSG_COMMON
section to send the sleep state.
Unknown purpose.
Section | Size | Type |
---|---|---|
Value | 1 | uint8 |
Value is always set to 1.
Used to announce the number of users in the server.
Section | Size | Type |
---|---|---|
Unknown | 4 | uint32 |
User count | 1 | uint8 |
The unknown value is always set to 1.
Used to update the character's position.
Section | Size | Type |
---|---|---|
Section type | 4 | uint32 |
ID 1 | 4 | uint32 |
ID 2 | 1 | uint8 |
ID type | 2 | uint16 |
Broadcast ID | 2 | uint16 |
X | 4 | int32float |
Y | 4 | int32float |
Z | 4 | int32float |
Unknown | 2 | uint16 |
- Section type = 2
ID 1 and ID 2 are always set to the connection ID and the client ID of the user, repectively. ID type must always be 0.
The unknown value is always set to 0x100
.
The server should broadcast this message to other users that are within the aura of the current user. ID 1 and ID 2 of the broadcasted message must be set to those of the user being sent to.
The server can also send a Position Update message to update any client's own character position.
By design, the protocol is limited to 256 concurrent users. This is because, the hello
packet that the server sends only uses an uint8 to send the connection ID; effectively limiting its range from 0 to 255, thus limiting it to 256 possible values. However, it is still entirely possible for a server to handle more than that, as TCP doesn't limit the amount of active connections, though it is impractical to do so. This is where WLS comes into use.
This documentation was made possible by the help of these folks:
- barra: Provided information for the character update data.
- Twig: Discovered how the character position and rotation values are encoded.
This documentation is licensed under CC BY-SA 4.0