Building a LoL Rank fetcher for Livestreams!

As you guys may or may not know, I do live stream LoL (a.k.a League of Legends for the uninitiated) every now and then, and in some very old live streams, I used to have a little box telling people my Rank. Back then, I used a PHP-script that I wrote in order to get the ranks. This, however, meant that I had to update my summoner name (I used a GET parameter, so it wasn't a big deal) when I was going to play on a smurf.

Initial Research

Today, I found something very interesting, but before we dive in any deeper, I want you to know that writing this post so detailed cost me a lot of effort since I can't go as fast as I normally go. So please if you really enjoyed this, make a donation for my efforts. Now let's get into it! I went with my usual stuff, checking which ports were listening on my PC (to search for malware) and I noticed there was a listener being opened by the LoL client (just the launcher, not the game). So, I fired up my browser (jepp, that was my first guess) and entered the URL with the port http://localhost:50864, and what did ya know? Now, I only got back an empty response (The Handshake was a success, but there was no response from the server)... So I thought? maybe the site is running on HTTPS? So I changed the URL to https://localhost:50864, aaaaand... Now that I got a 403 error, I needed credentials... But what credentials would I possibly need? So thinking it might be some pre-configured thing, I decided to look into my LoL installation folder. Not too soon, I immediately saw a file called lockfile. What was interesting about it was that it wasn't the normal 0kb size (which means the file is empty) but was 1kb in size. This meant that there was something in it. So, I opened the file and saw this (I blurted out something that resembles a password): Is this the information I need? After a bit of searching, I came by this code that showed me how to build the credential string. So it was quite obvious to me what the stuff on the lockfile meant:
Process Name : Process ID : Port : Password : https  
So now that I have that, I can try to create something with it! First, I created a repo on GitLab in order to easily show you what I exactly have been doing in my code. Feel free to fork my code if you'd like to. I then created my main script lol-wmr.py. I could now work on the first step in building my program:

Reading the Lockfile

Reading the lockfile now will save me a lot of issues later, as this file will contain all the dynamically generated data that I need. This saves me from having to keep track of this myself in case of me restarting the client or my PC or whatever. So first, I defined a function that will read the lockfile for me. To do so, I used Python's built-in file reader like so. I only need to read the file, so I added the r flag to it. I then added a print statement to see whether it read the file properly, which it didn't. Obviously, I didn't call the function yet, so I added that. This time, it worked just fine. However, I realized not everybody has the client installed in the same directory, so I added a variable to easily change that if needed. Now that I have all of that, I could work on getting all the individual values. To do so, I had to tokenize the string. Python (and most other languages) has a nice function for that, so obviously, I went with that. Obviously, I used the : as a delimiter. This is the code that I ended up with, which worked just fine. So the final step for this was return the data to the the call and remove the function call. Now that we have all the data we can get out of the lockfile, we can start building the function to connect to the client api.

Connect to the client API

In order to connect to the client api, I defined a function called getConnection. We will use this function to build the basic connection parameters. I have decided to do this using urllib3, as that seems to be the most used on StackOverflow (lmao). So I added an import for that into my script. Next, I added an urllib3 poolmanager, after which I added a call to the getConnection function. I now passed the results of the readLockfile function into the getConnection function so I could access its data. Now that I have that data, I can start building the URL. It was really simple and this is what I ended up with, nothing major. I could now work on getting the right headers. The way I did this is not how I would normally do this, but for the sake of ease for you guys, I decided to do it this way anyways. I added the first 2 basic headers: Content-Type and Accept. Now how I know that it uses the application/json? Well, I don't, they are just pure guesses at this moment. I now realized that I do not need the url here, but somewhere else, so, I removed the creation of the url out of that function for now and moved some bits and pieces around. At this point, I was ready to try to connect to the api. So I added a return statement to getConnection (I forgot that) and build the rest of the code to connect to the api. and... I could easily fix that, so I did and tried again... So there was nothing coming back, which is great! This actually means that what I build so far is sorta working. So now I just dumped the data we got from the request. We got back a 403, which is just what I expected since we didn't pass any authentication yet. So let's do that next!

Authentication to the Client API

Looking back at the IcuRequest.js, we can see we need to pass a basic authentication token into the header. This token is made by taking the username riot and password (the one in our lockfile) and encoding it using base64. This should be super simple, so let's get that done! First, I need to import the bas64 library. I then defined a function that will create the token for us. It takes the password variable so we don't have to pass the entire data object to it. I ended up with this small code, which does the job just fine I guess :) That means I can now pass it to the getConnection function. I added a token parameter to the function to which I can pass this token. I then proceeded to add the Authorization header. However, I got an error when I tried to run it... So, I decoded the bytes and tried again... So, I got a 403 again... what could I be doing wrong? After about an hour of searching with my friend Spike2147, I finally got that golden eureka moment:

Using `https://localhost` was forbidden, but using `https://127.0.0.1` wasn't.

And when thinking about it, it does kinda make sense. All I got not is a juicy 404 error (which I can easily fix) <3 Now that I've got that out of the way, I can work on getting the next thing done:

Getting the Summoner

So now that I got the authentication to work, I needed to get my Summoner information. Luckily, this was not very difficult. After a bit of Wiresharking (of which I did not have a screenshot T_T), I found that the endpoint that I need was /lol-summoner/v1/current-summoner. So, I added that in and found some useful data: For those to lazy to read that, here is the pretty JSON format:
{
    "accountId": 200334556,
    "displayName": "KaasInEenHoedje",
    "internalName": "kaasineenhoedje",
    "lastSeasonHighestRank": "SILVER",
    "percentCompleteForNextLevel": 75,
    "profileIconId": 744,
    "puuid": "6ad889b3-f049-50db-b5d2-85bae7bba184",
    "rerollPoints": {
        "currentPoints": 500,
        "maxRolls": 2,
        "numberOfRolls": 2,
        "pointsCostToRoll": 250,
        "pointsToReroll": 0
    },,
    "summonerId": 40868576,
    "summonerLevel": 36,
    "xpSinceLastLevel": 2168,
    "xpUntilNextLevel": 2880
},  
I now created a function to easily decode the JSON so that we could get our summonerId from the object. After I finished that, I went and created a function that would get our summonerId. And while this is no major thing, having it in a function can make it easier in the long run. After adding and removing some code, I could now work to the next step.

Getting the Rank

Now that we have our summonerId, we can hook that into the Riot Games API to get the current stats directly from their servers. So, I added a variable for the API Key (well, I did this a big before actually ending up here, sorry) and then proceeded to read the Riot Games Documentation. I quickly came by this page, at which I could easily test the endpoints. That was just what I needed! So after seeing what request headers I needed, I could continue on my script. so, I created a function called getRank, which took the summonerId as a parameter, adds some headers (of which the API key is one, the most important one even!), sends off a new request and decodes the JSON.
[
    {
        "queueType": "RANKED_FLEX_SR",
        "hotStreak": false,
        "wins": 5,
        "veteran": false,
        "losses": 6,
        "playerOrTeamId": "40868576",
        "leagueName": "Karthus's Urfriders",
        "playerOrTeamName": "KaasInEenHoedje",
        "inactive": false,
        "rank": "III",
        "freshBlood": false,
        "leagueId": "cd16b4c0-5459-11e8-bfae-c81f66dacb22",
        "tier": "BRONZE",
        "leaguePoints": 0
    },,
    {
        "queueType": "RANKED_SOLO_5x5",
        "hotStreak": false,
        "wins": 14,
        "veteran": false,
        "losses": 27,
        "playerOrTeamId": "40868576",
        "leagueName": "Malzahar's Outriders",
        "playerOrTeamName": "KaasInEenHoedje",
        "inactive": false,
        "rank": "II",
        "freshBlood": false,
        "leagueId": "82742be0-05ae-11e8-b5a5-c81f66dd0e0d",
        "tier": "SILVER",
        "leaguePoints": 0
    },
]  
Now that I had all of that, I was only interested in the RANKED_SOLO_5x5 Queue (as that's what I mostly play on stream anyway). I now added a little for loop that will check if the queueType is RANKED_SOLO_5x5 and if so, returns that entire object.  
 
At this point, the research part of this project is over. All that's left to do is to build a wrapper around it. Since I'll be using this for streams, I probably will decide on using a HTTP server for that, but I'll see about that. I hope you guys enjoyed this little research project of mine, if you did, please leave a donation, this project was quite a bit of effort for me so it'll be nice to see something in return :) But for now, [g33kout]

Comments


Leave a comment


Please login to leave comment!