Build a Desktop Client for Splatoon 2’s SplatNet in Electron

Mathew Chan
6 min readSep 12, 2020

Access SplatNet on Your Desktop without mitmproxy or manually modifying browser cookies.

Unofficial Desktop Client for SplatNet 2

You can download the Desktop Client here https://github.com/mathewthe2/splatnet-desktop/releases

This will be the file structure for this app.

splatnet-desktop/
├── package.json
├── main.js
├── views/
│ ├── loading.html
│ ├── logging-in.html
├── login.js
├── nso.js
├── stores.js

When the user first opens the application, they will be directed to a Nintendo Login Page by the logic in login.js. Once they log in and connect their account, they will see a logging-in message in logging-in.html while nso.js retrieves the tokens and cookies required to access SplatNet. Upon closing and reopening the application, SplatNet will be loaded using the tokens stored in the system through stores.js and the loading.html will be displayed during the loading process.

Only the iksm token is required to access SplatNet, however, it expires every two days so the session token is used to refresh the iksm token.

Step 1: Setting up Electron

If this is your first time developing with Electron, you should first go through the following tutorial.

We can set up our electron app with 4 commands by cloning the quick start repo.

git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install
npm start

If everything is installed correctly, you should see the the application pop up.

We need extra node packages. Run the following commands.

npm install --save base64url
npm install --save request request-promise-native promise-memoize
npm install --save electron-store

Step 2: Redirecting to Nintendo Login Portal

Instead of showing Hello World, we would like to show the contents of the Nintendo Login Portal. Create a new file called login.js and copy the following code.

generateRandom, calculateChallenge, and generateAuthenticationParams are helper functions that create the values required in the query parameters of the Nintendo authentication link. setMainWindow is the function that is exported back to main.js so login.js could load our Nintendo url on the window first launched through main.js.

Back to main.js, we can replace the line mainWindow.loadFile('index.html')with the following:

login.setMainWindow(mainWindow);login.openNSOLogin();

In the beginning of main.js we would also need to import login.js

const login = require('./login');

Now you can remove index.html and run the command npm start.

Nintendo’s login page should be redirected upon launch.

Step 3: Getting Nintendo’s Session Token and SplatNet’s iksm Token

After the user logins to their account, they would need to press Select this account to integrate their account with the app. This “redirects” the browser to a link that contains the Session Token Code which we would use to obtain our Session Token.

To let login.js know that the BrowserWindow has been redirected to that link, we would need to register a HTTP Protocol. Since we already know that the redirected link begins with the string npf71b963c1b7b6d119, we can code the following:

protocol.registerHttpProtocol(  'npf71b963c1b7b6d119', 
(request, callback) => {
mainWindow.loadFile('./views/logging-in.html') const url = request.url; const params = {}; url .split('#')[1] .split('&') .forEach(str => { const splitStr = str.split('='); params[splitStr[0]] = splitStr[1]; }); session_token_code = params.session_token_code
session_verifier = authParams.codeVerifier
// Get our Session Token with session_token_code & session_verifier // Get our SplatNet Iksm Token // Set our cookies with the correct Iksm Token and redirect to SplatNet);

Once we detect the url has been changed to npf71b963c1b7b6d119, we show our logging in message through loading mainWindow with logging-in.html. Then we parse the redirect uri to get the session token code. Now we will have to use our session token code and session verifier to get our session token. First let’s create a folder called views and in that folder create logging-in.html.

Then we have to create nso.js that converts our session token code to Nintendo’s session token.

nso.js receives the session token code and session verifier in the function getSplatnetSession, makes several requests to Nintendo’s servers, gets a hash value from elifessler’s server which is passed to a request to flapg’s server, and finally returns the session token. Check the API documentation page by frozenpandaman if you want to know how the session token and cookie are acquired. At the time of this writing, the userAgentVersion for sending requests to Nintendo’s servers is 1.8.0 but you may have to update it when Nintendo updates their servers. The function checkIksmValid makes a sample request to see if the iksm token works and the function refresh_iksm gets a new iksm token (that expires after 2 days) from the session token and stores it in our application’s data store.

This data store persists our tokens. Let’s create the file stores.js.

And let’s not forget to call login.js’s HTTP Protocol Registration function in main.js and replace login.openNSOLogin() with login.openSplatNet()so we can route our first-time versus second-time visiting logic in our openSplatNet function.

function createWindow () { ...  login.setMainWindow(mainWindow);  login.registerSplatnetHandler();  login.openSplatNet();}

Step 4: Loading SplatNet with Iksm Token

Back in login.js, we have to import nso.js and use getSplatnetSession to retrieve the session token and SplatNet’s iksm token. With the iksm token, we can finally load SplatNet to our BrowserWindow.

function openSplatNet () {  iksm = userDataStore.get('iksmCookie')  if (iksm) {    mainWindow.loadFile('./views/loading.html')    nso.checkIksmValid(iksm, mainWindow.webContents.session)    .then(isValid=>{      if (isValid) {        mainWindow.loadURL(`${splatnetUrl}/home`)      } else {        const session_token = userDataStore.get('sessionToken');        if (session_token) {          nso.refresh_iksm(session_token)          .then(iksm =>{            nso
.setIksmToken(iksm, mainWindow.webContents.session)
.then(()=> mainWindow.loadURL(`${splatnetUrl}/home`)) }) .catch(err => { console.log('Error refreshing iksm with session token:', err) openNSOLogin(); }) } else { // No cookies or tokens openNSOLogin(); } } }) } else { // First Time Login openNSOLogin() }}

We first load loading.html to our BrowserWindow, then we check if the Iksm token is valid. If it has expired, we renew the token with the session token. If no tokens are present, we load the Nintendo login page again. Once the iksm is acquired, we load it as a cookie in BrowserWindow through the function setIksmToken in nso.js.

async function setIksmToken(cookieValue, ses) {  // set cookies for BrowserWindow  ses.clearStorageData([], (data) => {})  const cookie = { url: splatnetUrl, name: 'iksm_session', value: cookieValue }  await ses.cookies.set(cookie);  ..}

This is the entire code for login.js.

npm start to see the changes. Once you login you should see SplatNet loaded in the window.

The language of SplatNet is determined by your Splatoon 2 version

Step 5: Packaging the App

npm install electron-packager --save-dev
electron-packager ./ all

On OSX, we can compile the App to DMG with electron-installer-dmg. For Win10, we can compile the exe and the folder to one setup.exe with InnoSetup.

You can find the complete source code here.

Acknowledgements

SquidTracks: Some of the code in this application has been modified from SquidTracks, specifically cookie jars and protocol registrations. I also helped them recently fix their login issue, which prompted me to start this project.

--

--