Please note: This document is incomplete and can contain errors. We are expanding on it so please bear with us.
Network messages are send and receive commands built into Exile. These commands allow Exile to transfer data across the network to either the server or client. These functions use remoteExec but they are more secure and harder to hack.
Starting in Exile version 0.9.6, these commands were opened up for coders and server owners to use without overriding an Exile function only on the client side, meaning that the server can send a network message to the client but not the other way around. Until the functionality is added to the server side, a coder can add this functionality via an override. This wiki will cover both ways of how network messages work, from the client and server side, but the functionality is almost the same.
CfgNetworkMessages
This class is what controls how a network message is defined. The files ExileClient_system_network_dispatchIncomingMessage.sqf and a custom ExileServer_system_network_dispatchIncomingMessage.sqf will look in the mission config file (IE. config.cpp or description.ext) for the class CfgNetworkMessages and will check the module and parameters against what is being sent. But fist, let's look at what an example network message class looks like. For these examples, we will be using a default Exile network message.
// Declare the class, the message name is purchaseVehicleRequest class purchaseVehicleRequest { // Declare the module, this is part of the file name module = "system_trading"; // Declare the parameters, these are the typeNames of the fields in the package parameters[] = {"STRING","STRING"}; }; class purchaseVehicleResponse { module = "system_trading"; parameters[] = {"SCALAR","STRING","STRING"}; };
It's a bit of information so let's dissect the message purchaseVehicleRequest.
class purchaseVehicleRequest {};
The declaration of the function. This name will be the ending part of the function name. This name must match the requesting function name or an error be be logged to the RPT and the message will be disregarded. A good practice with naming classes to end the class with the word "Request" if this message is expecting a "Response" back. In this case, it's good to name the responding network message to end with "Response".
module = "system_trading";
The "category" of the function. This is a good way of organizing code, allowing for easier flow. This module is used as part of the function call.
parameters[] = {"STRING","STRING"};
The typeNames of our data we are passing through the command. These MUST match the data being passed or an error will be logged to the RPT and the message will be disregarded.
"STRING" is not the only typeName that can be used, below are examples of others:
"SCALAR" // 1337
"BOOL" // true/false
"ARRAY" // [1,2,3,4,5]
For a list of every Network Message used by Exile, check the spoiler.
class CfgNetworkMessages { class startSessionRequest { module = "system_session"; parameters[] = {"STRING"}; }; class startSessionResponse { module = "system_session"; parameters[] = {"STRING"}; }; class updateSessionRequest { module = "system_session"; parameters[] = {"STRING"}; }; class switchMoveRequest { module = "object_player"; parameters[] = {"STRING","STRING"}; }; class updateStatsRequest { module = "object_player"; parameters[] = {"STRING","STRING","SCALAR","SCALAR"}; }; class showFragRequest { module = "gui"; parameters[] = {"ARRAY"}; }; class hasPlayerRequest { module = "object_player"; parameters[] = {}; }; class hasPlayerResponse { module = "object_player"; parameters[] = {"BOOL"}; }; class createPlayerRequest { module = "object_player"; parameters[] = {"STRING"}; }; class createPlayerResponse { module = "object_player"; parameters[] = {"OBJECT","STRING","STRING","STRING","SCALAR","SCALAR","SCALAR","SCALAR","SCALAR","SCALAR","STRING","SCALAR"}; }; class loadPlayerRequest { module = "object_player"; parameters[] = {}; }; class loadPlayerResponse { module = "object_player"; parameters[] = {"STRING","STRING","STRING","SCALAR","SCALAR","SCALAR","SCALAR","SCALAR","STRING","SCALAR","SCALAR"}; }; class updatePlayerIncapacitatedRequest { module = "object_player"; parameters[] = {"BOOL"}; }; class savePlayerRequest { module = "object_player"; parameters[] = {"SCALAR","SCALAR","SCALAR","SCALAR","SCALAR"}; }; class setPlayerMoneyRequest { module = "object_player"; parameters[] = {"SCLAR"}; }; class chopTreeRequest { module = "object_tree"; parameters[] = {"STRING"}; }; class chopBushRequest { module = "object_bush"; parameters[] = {"STRING"}; }; class systemChatRequest { module = "gui"; parameters[] = {"STRING"}; }; class advancedHintRequest { module = "gui"; parameters[] = {"STRING"}; }; class standardHintRequest { module = "gui"; parameters[] = {"STRING"}; }; class notificationRequest { module = "gui_notification"; parameters[] = {"STRING","ARRAY"}; }; class dynamicTextRequest { module = "gui"; parameters[] = {"STRING","SCALAR","SCALAR","STRING"}; }; class resetPlayerRequest { module = "object_player"; parameters[] = {}; }; class buildConstructionRequest { module = "object_construction"; parameters[] = {"STRING","ARRAY"}; }; class payTerritoryProtectionMoneyRequest { module = "system_territory"; parameters[] = {"STRING","SCALAR"}; }; class payTerritoryProtectionMoneyResponse { module = "system_territory"; parameters[] = {"STRING","STRING"}; }; class buildTerritoryRequest { module = "object_construction"; parameters[] = {"STRING","ARRAY","STRING","STRING"}; }; class constructionResponse { module = "object_construction"; parameters[] = {"STRING"}; }; class swapConstructionRequest { module = "object_construction"; parameters[] = {"STRING","STRING","ARRAY"}; }; class deconstructConstructionRequest { module = "object_construction"; parameters[] = {"STRING"}; }; class moveConstructionRequest { module = "object_construction"; parameters[] = {"STRING"}; }; class constructionMoveResponse { module = "object_construction"; parameters[] = {"BOOL","STRING"}; }; class upgradeConstructionRequest { module = "object_construction"; parameters[] = {"OBJECT"}; }; class upgradeConstructionResponse { module = "object_construction"; parameters[] = {"OBJECT"}; }; class flipVehRequest { module = "object_vehicle"; parameters[] = {"STRING"}; }; class pushVehicleRequest { module = "object_vehicle"; parameters[] = {"STRING","SCALAR","SCALAR","STRING"}; }; class rotateVehicleRequest { module = "object_vehicle"; parameters[] = {"STRING","SCALAR"}; }; class lockVehicleRequest { module = "object_vehicle"; parameters[] = {"STRING","BOOL"}; }; class lockResponse { module = "object_vehicle"; parameters[] = {"STRING","BOOL","STRING","STRING","SCALAR"}; }; class spawnLootRequest { module = "system_lootManager"; parameters[] = {"ARRAY"}; }; class toggleFloodLightRequest { module = "object_floodLight"; parameters[] = {"STRING","SCALAR"}; }; class connectionTest { module = "object_player"; parameters[] = {"BOOL"}; }; class purchaseVehicleRequest { module = "system_trading"; parameters[] = {"STRING","STRING"}; }; class purchaseVehicleResponse { module = "system_trading"; parameters[] = {"SCALAR","STRING","STRING"}; }; class vehicleSaveRequest { module = "system_vehicleSaveQueue"; parameters[] = {"STRING"}; }; class purchaseVehicleSkinRequest { module = "system_trading"; parameters[] = {"STRING","ARRAY"}; }; class purchaseVehicleSkinResponse { module = "system_trading"; parameters[] = {"SCALAR","STRING"}; }; class endBambiStateRequest { module = "object_player"; parameters[] = {}; }; class purchaseItemRequest { module = "system_trading"; parameters[] = {"STRING","SCALAR","SCALAR","STRING"}; }; class purchaseItemResponse { module = "system_trading"; parameters[] = {"SCALAR","STRING","STRING","SCALAR","SCALAR","STRING"}; }; class sellItemRequest { module = "system_trading"; parameters[] = {"STRING","SCALAR","SCALAR","STRING"}; }; class sellItemResponse { module = "system_trading"; parameters[] = {"SCALAR","STRING","STRING","SCALAR","SCALAR","STRING","STRING"}; }; class hotwireLockRequest { module = "object_lock"; parameters[] = {"STRING"}; }; class lockToggle { module = "object_lock"; parameters[] = {"STRING","STRING","BOOL"}; }; class setPin { module = "object_lock"; parameters[] = {"STRING","STRING","STRING"}; }; class setPinResponse { module = "object_lock"; parameters[] = {"ARRAY","STRING","STRING"}; }; class packRequest { module = "object_container"; parameters[] = {"STRING","STRING"}; }; class setFuelRequest { module = "object_vehicle"; parameters[] = {"STRING","SCALAR"}; }; class registerClanRequest { module = "system_clan"; parameters[] = {"STRING"}; }; class registerClanResponse { module = "system_clan"; parameters[] = {"SCALAR","STRING","STRING"}; }; class inviteToPartyRequest { module = "system_party"; parameters[] = {"STRING"}; }; class joinPartyRequest { module = "system_party"; parameters[] = {"STRING"}; }; class kickFromPartyRequest { module = "system_party"; parameters[] = {"STRING"}; }; class announceTerritoryRequest { module = "system_territory"; parameters[] = {"STRING"}; }; class addToTerritoryRequest { module = "system_territory"; parameters[] = {"STRING","STRING"}; }; class removeFromTerritoryRequest { module = "system_territory"; parameters[] = {"STRING","STRING"}; }; class moderationTerritoryRequest { module = "system_territory"; parameters[] = {"STRING","STRING","BOOL"}; }; class sendMoneyRequest { module = "system_trading"; parameters[] = {"STRING","STRING"}; }; class moneySentRequest { module = "system_trading"; parameters[] = {"STRING","STRING"}; }; class moneyReceivedRequest { module = "system_trading"; parameters[] = {"STRING","STRING"}; }; class purchaseTerritory { module = "system_territory"; parameters[] = {}; }; class purchaseTerritoryResponse { module = "system_territory"; parameters[] = {"SCALAR"}; }; class requestTerritoryUpgradeDialog { module = "system_territory"; parameters[] = {"OBJECT"}; }; class addLockRequest { module = "object_construction"; parameters[] = {"OBJECT","STRING"}; }; class addLockResponse { module = "object_construction"; parameters[] = {"STRING"}; }; class territoryUpgradeDialogResponse { module = "gui_upgradeTerritoryDialog"; parameters[] = {"SCALAR"}; }; class territoryUpgradeRequest { module = "system_territory"; parameters[] = {"OBJECT"}; }; class territoryUpgradeResponse { module = "system_territory"; parameters[] = {"STRING","SCALAR","SCALAR","STRING"}; }; class deleteGroupPlz { module = "system"; parameters[] = {"GROUP"}; }; class wasteDumpRequest { module = "system_trading"; parameters[] = {"STRING","SCALAR"}; }; class wasteDumpResponse { module = "system_trading"; parameters[] = {"SCALAR","STRING","STRING"}; }; class beginTakeAllRequest { module = "object_player"; parameters[] = {"STRING"}; }; class beginTakeAllResponse { module = "object_player"; parameters[] = {"STRING"}; }; class endTakeAllRequest { module = "object_player"; parameters[] = {"STRING"}; }; class scanCodeLockRequest { module = "object_lock"; parameters[] = {"STRING"}; }; class scanCodeLockResponse { module = "object_lock"; parameters[] = {"STRING"}; }; class enableSimulationRequest { module = "system_simulationMonitor"; parameters[] = {"STRING"}; }; class attachSupplyBoxRequest { module = "object_supplyBox"; parameters[] = {"STRING"}; }; class detachSupplyBoxRequest { module = "object_supplyBox"; parameters[] = {"STRING"}; }; class installSupplyBoxRequest { module = "object_supplyBox"; parameters[] = {"STRING"}; }; class handcuffRequest { module = "object_handcuffs"; parameters[] = {"STRING"}; }; class handcuffResponse { module = "object_handcuffs"; parameters[] = {"STRING"}; }; class freeRequest { module = "object_handcuffs"; parameters[] = {"STRING"}; }; class freeResponse { module = "object_handcuffs"; parameters[] = {"STRING"}; }; class breakFreeRequest { module = "object_handcuffs"; parameters[] = {}; }; class breakFreeResponse { module = "object_handcuffs"; parameters[] = {"STRING"}; }; class updateMyPartyMarkerRequest { module = "system_party"; parameters[] = {"BOOL","ARRAY"}; }; class updatePartyMarkerRequest { module = "system_party"; parameters[] = {"STRING","BOOL","ARRAY"}; }; class resetCodeRequest { module = "object_vehicle"; parameters[] = {"STRING","STRING","STRING"}; }; class resetCodeResponse { module = "object_vehicle"; parameters[] = {"ARRAY","STRING","STRING"}; }; class rekeyVehicleRequest { module = "object_vehicle"; parameters[] = {"STRING","STRING"}; }; class rekeyVehicleDialogRequest { module = "object_vehicle"; parameters[] = {"STRING","SCALAR"}; }; class rekeyVehicleDialogResponse { module = "gui_vehicleRekeyDialog"; parameters[] = {"STRING","STRING","SCALAR"}; }; class resetCodeDialogRequest { module = "object_vehicle"; parameters[] = {"STRING"}; }; class resetCodeDialogResponse { module = "gui_vehicleRekeyDialog"; parameters[] = {"STRING","STRING"}; }; };
How these are used in ExileServer_system_network_dispatchIncomingMessage / ExileClient_system_network_dispatchIncomingMessage will be explained below.
SENDING FROM CLIENT TO SERVER
These examples will be using the network messages defined above.
Let's say a player went to the trader and purchased a vehicle. Traders are handled on the client but in order for a vehicle to be saved to the database, the server needs to process it, but how do we let the server know that a client ran a client side script to purchase a vehicle? We send a network message.
Below is an example of the purchaseVehicleRequest network message used above.
// Taken from ExileClient_gui_vehicleTraderDialog_event_onPurchaseButtonClick.sqf [ "purchaseVehicleRequest", // This is the class that is defined in CfgNetworkMessages // Package START [ _vehicleClass, // This is the classname of the vehicle purchased _pin // This is the pin set by the player when purchased ] // Package END ] call ExileClient_system_network_send; // Exile function to call to send a message to the server. // Same above command just in one line ["purchaseVehicleRequest", [_vehicleClass,_pin]] call ExileClient_system_network_send;
NOTE: The example will be using a variables named _vehicleClass and _pin. These variables will change based on what vehicle class is chosen and what pin is entered. For ease of understanding, this topic will use the variable names instead of what the data could be. Both of these are passed as STRINGS.
ExileClient_system_network_send.sqf
ExileClient_system_network_send will take this information, attach the player's sessionID, which is unique to the player's client, and remoteExec to the server. ExileServer_system_network_dispatchIncomingMessage will receive this message on the server side.
ExileServer_system_network_dispatchIncomingMessage.sqf
ExileServer_system_network_dispatchIncomingMessage will take in this package and perform multiple error checks and security checks to make sure the data is correct.
Checks include:
-
Payload is defined.
- IE, [_sessionID, "purchaseVehicleRequest", [_vehicleClass,_pin]]
- _sessionID is the player's sessionID that was attached in ExileClient_system_network_send.
- Payload is an array.
-
Payload includes exactly three fields.
- Keep in mind that _sessionID was attached, so the payload is now three fields long.
- Requesting sessionID matches and exists
-
Message name matches the class defined in CfgNetworkMessages.
- IE, "purchaseVehicleRequest"
-
Requested package parameters count matches the one defined in CfgNetworkMessages.
- IE, [_vehicleClass,_pin]
- The count of this array is 2, and the count of parameters for this message is 2
-
typeName's of the package matches CfgNetworkMessages.
- IE. [_vehicleClass,_pin]
- The type names of the information being passed is ["STRING","STRING"]
Once it's checked all of those, dispatchIncomingMessage will try to compile a function call using this information. This is where the class name and module comes in to play.
In raw terms, this is what it's doing.
ExileServer_<ModuleName>_network_<FunctionName>
Using the purchaseVehicleRequest, the final function call will be
ExileServer_system_trading_network_purchaseVehicleRequest
dispatchIncomingMessage will then call the function with the following parameters: [_sessionID,[_vehicleClass,_pin]]
This concludes how a message is sent from the client to the server, next is the other way around.
SENDING FROM SERVER TO CLIENT
Since ExileServer_system_trading_network_purchaseVehicleRequest was called, let's examine what the file is doing with the data.
ExileServer_system_trading_network_purchaseVehicleRequest
_sessionID = _this select 0; _parameters = _this select 1; _vehicleClass = _parameters select 0; _pinCode = _parameters select 1;
This is how to extract the data sent via a network message. On the server side, the player's sessionID will ALWAYS be the first part of the payload (_this select 0) and the "package" will be the second (_this select 1). From the "package" the data sent can be extracted, in this case [_vehicleClass,_pin]. The "package" will always be an array. The way this data is sent to this file looks like this: [_sessionID,[_vehicleClass, _pinCode]]. All messages sent to the server will follow this design, so the way the data extracted is exactly the same across the board.
This topic won't go into detail how this file works, but in the end it will be sending back the following:
[ _sessionID, // SessionID of the requesting player "purchaseVehicleResponse", // Name of the class defined in CfgNetworkMessages // Package START [ 0, netId _vehicleObject, str _playerMoney ] // Package END ] call ExileServer_system_network_send_to;
Since the command is getting ran on the server, ExileServer_system_network_send_to needs a destination to send the data to. This is why the command has the _sessionID attached to it. _sessionID is the SessionID that was tied to the player that sent the initial request. The rest of this command is the same as on the client, "purchaseVehicleResponse" is the destination, and the package containing the information sending back to the client.
ExileServer_system_network_send_to
This file processing the request just like the client side, but instead of attaching the sessionID to the message, it strips it from it.
ExileClient_system_network_dispatchIncomingMessage
Back to the client side, ExileClient_system_network_dispatchIncomingMessage receives the message. It preforms the similar checks like the server side one does. Once the checks are done, compiles the function call and sends our package to it. The final package sent to the file, which in the example is ExileClient_system_trading_network_purchaseVehicleResponse, will be [0,netId _vehicleObject,str _playerMoney].
ExileClient_system_trading_network_purchaseVehicleResponse
This is the final file that will receive the information. As stated above, the package is [0,netId _vehicleObject,str _playerMoney] so the file only has a few variables to extract from the data.
_responseCode = _this select 0; _vehicleNetID = _this select 1; _newPlayerMoneyString = _this select 2;
That's it!
Editor Notes:
- You can send a network message to any client from the server so long as the server has their player object or the players sessionID.
-
Exile sends all of the money amounts across the network as strings. When that string reaches it's destination, it's converted back to a number using the command parseNumber
- 4