Skip to content
2 changes: 2 additions & 0 deletions PresenceClient/PresenceClient-Py/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
IP = 'the ip of your device'
APPLICATION_ID = 'your the discord application id'
170 changes: 95 additions & 75 deletions PresenceClient/PresenceClient-Py/presence-client.py
Original file line number Diff line number Diff line change
@@ -1,140 +1,161 @@
import argparse
import sys
import json
import socket
import struct
import time
import re
import requests
import os
from pypresence import Presence
from dotenv import load_dotenv
import subprocess
import time

load_dotenv()

switch_ip = os.getenv('IP')
client_id = os.getenv('APPLICATION_ID')

rpc = Presence(str(client_id))

TCP_PORT = 0xCAFE
PACKETMAGIC = 0xFFAADD23

parser = argparse.ArgumentParser()
parser.add_argument('ip', help='The IP address of your device')
parser.add_argument('client_id', help='The Client ID of your Discord Rich Presence application')
parser.add_argument('--ignore-home-screen', dest='ignore_home_screen', action='store_true', help='Don\'t display the home screen. Defaults to false if missing this flag.')
parser.add_argument('--ignore-home-screen', dest='ignore_home_screen', action='store_true', help='Hide the home screen. Defaults to false if missing this flag.')
parser.add_argument('--ignore-tinfoil', dest='ignore_tinfoil', action='store_true', help='Hide the Tinfoil app. Defaults to false if missing this flag.')
parser.add_argument('--low-latancy', dest='latancy',action='store_true', help='Enable low-latancy mode. Defaults to false if missing this flag.')
parser.set_defaults(ignore_home_screen=False, ignore_tinfoil=False, latancy=False)
consoleargs = parser.parse_args()

questOverrides = None
switchOverrides = None

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
switch_server_address = (switch_ip, TCP_PORT)

if consoleargs.latancy == True:
latancy = 15
else:
latancy = 60

try:
questOverrides = json.loads(requests.get("https://raw.githubusercontent.com/Sun-Research-University/PresenceClient/master/Resource/QuestApplicationOverrides.json").text)
switchOverrides = json.loads(requests.get("https://raw.githubusercontent.com/Sun-Research-University/PresenceClient/master/Resource/SwitchApplicationOverrides.json").text)
except:
print('Failed to retrieve Override files')
exit()

#Defines a title packet
class Title:
def restart():
rpc.clear()
rpc.close()
sock.close()

command = ['python3', 'presence-client.py']

if consoleargs.latancy:
command.append('--low-latancy')
if consoleargs.ignore_tinfoil:
command.append('--ignore-tinfoil')
if consoleargs.ignore_home_screen:
command.append('--ignore-home-screen')

subprocess.Popen(command)
exit()

class Title:
def __init__(self, raw_data):
if len(raw_data) != 628:
restart()

unpacker = struct.Struct('2L612s')
enc_data = unpacker.unpack(raw_data)

self.magic = int(enc_data[0])
if int(enc_data[1]) == 0:
self.pid = int(enc_data[1])
self.name = 'Home Menu'
self.pid = int(enc_data[1])

if self.pid == 0:
self.name = 'Home Menu' if self.magic != PACKETMAGIC else 'Tinfoil'
else:
self.pid = int(enc_data[1])
self.name = enc_data[2].decode('utf-8', 'ignore').split('\x00')[0]
if int(enc_data[0]) == PACKETMAGIC:
if self.name in questOverrides:
if questOverrides[self.name]['CustomName'] != '':
self.name = questOverrides[self.name]['CustomName']
else:
if self.name in switchOverrides:
if switchOverrides[self.name]['CustomName'] != '':
self.name = switchOverrides[self.name]['CustomName']

overrides = questOverrides if self.magic == PACKETMAGIC else switchOverrides
if overrides is not None and self.name in overrides and overrides[self.name]['CustomName']:
self.name = overrides[self.name]['CustomName']

def main():
consoleargs = parser.parse_args()

switch_ip = consoleargs.ip
client_id = consoleargs.client_id

if not checkIP(switch_ip):
print('Invalid IP')
exit()

rpc = Presence(str(client_id))
try:
rpc.connect()
rpc.clear()
except:
print('Unable to start RPC!')

switch_server_address = (switch_ip, TCP_PORT)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
sock.connect(switch_server_address)
print('Successfully connected to %s' % switch_ip + ':' + str(TCP_PORT))
except:
print('Error connection to %s refused' % switch_ip + ':' + str(TCP_PORT))
exit()

while True:
try:
sock.connect(switch_server_address)
print(f'Successfully connected to {switch_ip}:{TCP_PORT}')
break
except socket.error:
print(f'Error connecting to {switch_ip}:{TCP_PORT}. Retrying in {latancy} seconds.')
time.sleep(latancy)
lastProgramName = ''
startTimer = 0

while True:
data = None
try:
sock.settimeout(15)
data = sock.recv(628)
except:
sock.settimeout(None)
except socket.timeout:
sock.settimeout(None)
sock.close()
restart()
except socket.error:
print('Could not connect to Server! Retrying...')
startTimer = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
time.sleep(latancy)
try:
sock.connect(switch_server_address)
print('Successfully reconnected to %s' %
repr(switch_server_address))
except:
print('Error reconnection to %s refused' %
repr(switch_server_address))
exit()
print('Successfully reconnected to %s' % repr(switch_server_address))
except socket.error:
print(f'Error reconnecting to {repr(switch_server_address)}. Retrying...')
time.sleep(latancy)
continue
def get_details(title, overrides):
if title.name in overrides:
orinfo = overrides[title.name]
largeimagekey = orinfo['CustomKey'] or iconFromPid(title.pid)
details = orinfo['CustomPrefix'] or 'Playing'
else:
largeimagekey = iconFromPid(title.pid) if int(title.pid) != PACKETMAGIC else title.name.lower().replace(' ', '')
details = 'Playing'
details += ' ' + title.name
return largeimagekey, details

title = Title(data)
if not hasattr(title, 'magic'):
restart()
continue

if title.magic == PACKETMAGIC:
if lastProgramName != title.name:
startTimer = int(time.time())
if consoleargs.ignore_home_screen and title.name == 'Home Menu':
rpc.clear()
elif consoleargs.ignore_tinfoil and title.name == 'Tinfoil':
rpc.clear()
else:
smallimagetext = ''
largeimagekey = ''
details = ''
largeimagetext = title.name
if int(title.pid) != PACKETMAGIC:
smallimagetext = 'SwitchPresence-Rewritten'
if title.name not in switchOverrides:
largeimagekey = iconFromPid(title.pid)
details = 'Playing ' + str(title.name)
else:
orinfo = switchOverrides[title.name]
largeimagekey = orinfo['CustomKey'] or iconFromPid(title.pid)
details = orinfo['CustomPrefix'] or 'Playing'
details += ' ' + title.name
else:
smallimagetext = 'QuestPresence'
if title.name not in questOverrides:
largeimagekey = title.name.lower().replace(' ', '')
details = 'Playing ' + title.name
else:
orinfo = questOverrides[title.name]
largeimagekey = orinfo['CustomKey'] or title.name.lower().replace(
' ', '')
details = orinfo['CustomPrefix'] or 'Playing'
details += ' ' + title.name
if not title.name:
title.name = ''
lastProgramName = title.name
smallimagetext = 'SwitchPresence-Rewritten' if int(title.pid) != PACKETMAGIC else 'QuestPresence'
overrides = switchOverrides if int(title.pid) != PACKETMAGIC else questOverrides
largeimagekey, details = get_details(title, overrides)
largeimagetext = title.name or ''
lastProgramName = title.name or ''
rpc.update(details=details, start=startTimer, large_image=largeimagekey,
large_text=largeimagetext, small_text=smallimagetext)
time.sleep(1)
else:
time.sleep(1)
rpc.clear()
rpc.close()
sock.close()
Expand All @@ -151,6 +172,5 @@ def checkIP(ip):
def iconFromPid(pid):
return '0' + str(hex(int(pid))).split('0x')[1]


if __name__ == '__main__':
main()