Skip to content
Giorgio edited this page Jul 17, 2022 · 6 revisions

Riot Games use an XMPP server with a custom authentication mechanism to broadcast the presence of all their games, as well as for friend requests and chat messages.

Prerequisites

To authenticate with the XMPP server, you will need two tokens, an RSO token and a PAS token.

RSO

The methods of obtaining an RSO token are documented here.

The plugin uses GET Cookie Reauth which lets it get a new token using the player's cookies. This avoids the hassle of two-factor authentication and Google sign in, while also allowing us not store the user's password in plain text.

PAS

The PAS token can be obtained from the following endpoint:

GET https://riot-geo.pas.si.riotgames.com/pas/v1/service/chat
Headers:
 Authorization: Bearer <RSO token>

Getting the XMPP server's address

The address of the XMPP server depends on the region of the account. To get the region code for your account, decode your PAS token and get the affinity.

To get the list of XMPP servers, use the clientconfig endpoint:

GET https://clientconfig.rpg.riotgames.com/api/v1/config/player?os=windows&region=EUW&app=Riot%20Client&version=39.0.0.3949982&patchline=KeystoneFoundationLiveWin
Headers:
 x-riot-entitlements-jwt: <entitlements token>
 authorization: Bearer <RSO token>

You will need to get an entitlements token.

The XMPP server addresses are unlikely to change, which is why I hardcoded them in my plugin.

Connecting to the XMPP server

The connection happens using a TCP stream encrypted using TLS over port 5223.

Once connected, send these messages one by one, waiting for riot servers to respond between each one:

<?xml version="1.0"?><stream:stream to="${XMPPRegion}.pvp.net" version="1.0" xmlns:stream="http://etherx.jabber.org/streams">
<auth mechanism="X-Riot-RSO-PAS" xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><rso_token>${RSO}</rso_token><pas_token>${PAS}</pas_token></auth>
<?xml version="1.0"?><stream:stream to="${XMPPRegion}.pvp.net" version="1.0" xmlns:stream="http://etherx.jabber.org/streams">
<iq id="_xmpp_bind1" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"></bind></iq>
<iq id="_xmpp_session1" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>

Make sure to replace ${RSO} and ${PAS} with your tokens, and ${XMPPRegion} with your XMPP region.
Note your XMPP region is not the same as your account region, although the map of "account regions -> XMPP regions" is obtained using the clientconfig endpoint described above.

Once you send these messages, you should be authenticated and ready to ask for the friends list and presences.

NOTE: Riot likes to split up large messages into two or more chunks somewhat arbitrarily. Make sure you have got the complete XML message before you start processing it.

Setting the entitlements token

According to ev3nvy, You can set the entitlements token using the following request:

<iq id="xmpp_entitlements_0" type="set"><entitlements xmlns="urn:riotgames:entitlements"><token xmlns="">${entitlements token}</token></entitlements></iq>

It isn't needed for sending and receiving presences. It might be needed for other XMPP operations such as friend requests or chat messages, but I haven't tested those.

Getting the friends list

To ask for the friends list, send the following:

<iq type="get" id="2"><query xmlns="jabber:iq:riotgames:roster" last_state="true" /></iq>

Getting your friend's presences

Once you send any presence, you are asking for the list of friend presences as well as asking to receive all future presence updates. Simply sending <presence/> is enough.

You can also use this to spoof your presence to your friends.

Presence format

Here is what a Valorant presence should look like:

<presence from='puuid@eu1.pvp.net/RC-123456789' to='puuid@eu1.pvp.net/987654321' id='presence_69'>
    <games>
        <keystone>
            <st>chat</st>
            <s.t>1611172871420</s.t>
            <m>custom status go brrrr</m>
            <s.p>keystone</s.p>
        </keystone>
        <valorant>
            <st>chat</st>
            <s.t>1611172871069</s.t>
            <s.d/>
            <s.l/>
            <m/>
            <s.a/>
            <p>ewoJImlzVmFsaWQiOiBmYWxzZSwKCSJ3aGF0IjogInRoaXMgaXMganVzdCBzYW1wbGUgZGF0YSBkdWRlIDopIiwKfQ==</p>
            <s.p>valorant</s.p>
        </valorant>
    </games>
    <show>chat</show>
    <status/>
</presence>

There are two "games" being played, keystone and valorant. Keystone is simply the fact that the user is able to receive chat messages, so it can be ignored.

<s.t> contains the timestamp the presence was last updated. If a Riot client or game receives multiple presences for the same game, it displays the one with the most recent timestamp.

Note: If a friend has downloaded the "Riot Mobile" app (formerly League+), they will always return a keystone presence regardless of whether they are online or not.

Valorant presences

You're probably best off reading my code for how to interpret the data, but here's the simple english version anyway.

The presence is sent as a base64 encoded JSON object. To see all possible values, check out the bottom of this page.

There are three main sessionLoopStates a presence can be in: MENUS for the menu, PREGAME for agent select and INGAME for when your friend is suffering in game.

Stateless variables

Some data is the same regardless of what state the friend is in:

  • competitiveTier is the current rank: 0 is for unranked, 3 for Iron 1, 4 for Iron 2... up to 27 for Radiant
  • matchMap is the interal game asset path to the current map
    • For example, /Game/Maps/Triad/Triad is the path for Haven. Use Valorant-API to get the actual name
    • The exception is for the range, where the map is simply "Range"
    • If the player is in the menus setting up a custom game, matchMap is set to the map they selected. It's an empty string otherwise.
  • queueId is the ID of the current gamemode. For custom games, it's an empty string for some reason
  • partyVersion is the timestamp at which the party was last changed
  • Use Valorant-API to get useful data out of playerCardId, playerTitleId and preferredLevelBorderId

Menu

partyState is the main thing to look at:

  • "DEFAULT" when sitting in the lobby
  • "MATCHMAKING" when in the queue (you may find queueEntryTime useful)
  • "MATCHMADE_GAME_STARTING" for the MATCH FOUND screen
  • "CUSTOM_GAME_SETUP" for when setting up a custom game

Pregame

Not much to say about this one. Then again, there's not much to do in agent select other than pick an agent.

In Game

If the party leader is still in the loading screen, partyOwnerMatchCurrentTeam will be an empty string.

You can get the score using partyOwnerMatchScoreAllyTeam and partyOwnerMatchScoreEnemyTeam.

If the player is in a custom game, customGameTeam can be "TeamOne", "TeamTwo" or "TeamSpectate".

League of Legends & TFT

League of Legends and Teamfight Tactics presences are also sent over the same XMPP connection.

I don't play League or TFT so I don't have much knowledge on what most variables mean. You can probably have a look at my code which I made using a combination of educated guessing and asking friends.

Oh, and Wild Rift is also sent over XMPP. No data other than "this guy opened the app" is sent though.

Finally, for the (very few remaining) players of Runeterra... sorry :P

Acknowledgements

  • molenzwiebel's Deceive source code, which helped me tremendously in figuring out Riot's XMPP, and even how XMPP works in the first place. That man is either a genius, or works for Riot Games
  • The Valorant App Developers Discord Server, with a special thanks to Hamper and his vast knowledge of everything Valorant and helping me get the plugin to a usable state
  • techchrism's Valorant API Docs on how Riot's auth works

Resources

ev3nvy is making a javascript library for Riot's XMPP, go check it out.

Otherwise, there are two ways of reading the XMPP communication between the game and Riot's servers:

  • For Valorant, you can use riot-xmpp-mitm made by Burak
  • Clone the Deceive repo and run it using Visual Studio, it should output the XMPP traffic to the debug console

Since you're here, check out this writeup on the state of the in-game API.