Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pretendo Support #28

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
87a1589
Updated code to use the latest NintendoClients version (+ a few other…
Preloading Apr 15, 2024
cc7c79e
Update creation script and add Migration script to support multiple n…
Preloading Apr 15, 2024
b467277
left my note to added friends oops
Preloading Apr 15, 2024
e1e85c4
realizing that my ideas are stupid, and changing it from a flag to a …
Preloading Apr 16, 2024
c39d125
Add args to choose between nintendo & pretendo
Preloading Apr 16, 2024
c05e2fd
changes to the migration script & small change to backend
Preloading Apr 16, 2024
8c70cb4
Hotfix: Fix SQL being odd
Preloading Apr 16, 2024
fd3ab39
Add broken pretendo backend support, waiting for help on a datetime b…
Preloading Apr 18, 2024
1da19ca
Add pretendo support to the backend uptime counter
Preloading Apr 18, 2024
1ae808a
fix homepage, roster, and active
Preloading Apr 18, 2024
232d317
fix consoles page
Preloading Apr 18, 2024
fa60f94
Created the basic "select the network" page
Preloading Apr 20, 2024
5fd14c3
Hotfix: correct and implement some feedback
Preloading Apr 20, 2024
397a4a7
Mostly finish up registering UI and site backend (actually registerin…
Preloading Apr 20, 2024
8ae5da0
You can now add consoles from the menu + maybe fixing the backend for…
Preloading Apr 20, 2024
6f2987e
try to work around missing function on pretendo, currently hangs at r…
Preloading Apr 20, 2024
d6e7fac
Add small fixes allowing selecting and deleting consoles
Preloading Apr 22, 2024
b8e44b9
Well, the most inelegent way of fixing pretendo support (i guess)
Preloading Apr 27, 2024
628c5ec
Add proper pretendo redirection to the new users and active users (ma…
Preloading Apr 27, 2024
107bb05
Fix console profile redirect
Preloading Apr 27, 2024
681ffff
Make the try profile button work with pretendo
Preloading Apr 27, 2024
db89c6f
Finish sidebar, Fix index forgetting about pretendo, and another thing
Preloading Apr 27, 2024
beb800f
Done web backend!
Preloading Apr 27, 2024
9145e04
Fix mobile support for the Select Network page
Preloading Apr 27, 2024
998c089
Update template.private.py to be accurate with my private file
Preloading Apr 27, 2024
ec164ee
Make discord.py not break and rename some variables in server.py
Preloading Apr 27, 2024
f4e569f
Possibly finish off discord.py (open to adding something saying what …
Preloading Apr 27, 2024
6f26c3c
Show what network on the user's page
Preloading Apr 28, 2024
f2f9c7a
Add a sign of what network you are on, on the discord rpc
Preloading Apr 28, 2024
12bb21e
Fix API commands to be compatible with before
Preloading Apr 28, 2024
bea621d
Remove debug print line
Preloading Apr 28, 2024
1b70062
Uncomment temporary commenting on getPresence (thanks phoenix)
Preloading Apr 28, 2024
51d10c7
Fix mistake where i mixed == with != somehow
Preloading Apr 28, 2024
4918ffb
[Cleanup] Rework __main__: Add cleaner command line & Remove unneeded…
HotaruBlaze Apr 28, 2024
25da282
Merge pull request #1 from HotaruBlaze/Preloading/main
Preloading Apr 28, 2024
8325f52
Fix @HotaruBlaze (Phoenix)'s code
Preloading Apr 28, 2024
5a96a68
Hot... Change?: Add the ability to use -n and --network instead of ju…
Preloading Apr 28, 2024
0eee7ab
Move NetworkIDsToName and other functions to new file + change it to …
Preloading May 3, 2024
788ed5d
Resolve some SQL syntax errors (thanks @spotlightishere)
Preloading May 3, 2024
909fbeb
Add default to some values in CREATE.sql
Preloading May 3, 2024
045b32e
this is actually horrifying, but it fixes pretendo finally.
Preloading May 12, 2024
a209e53
Add a quick note regarding my hacky fix.
Preloading May 12, 2024
40bfcc8
Fix the downloadable client's API, and missing variables
Preloading May 19, 2024
431529e
Update copyright & requirements.txt
Preloading Jun 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions api/networks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from enum import IntEnum
from api.public import nintendoBotFC, pretendoBotFC

# Selectable networks
class NetworkIDsToName(IntEnum):
nintendo = 0
pretendo = 1

def getBotFriendCodeFromNetworkId(network:int):
match network:
case 0:
return nintendoBotFC
case 1:
return pretendoBotFC

def nameToNetworkId(network:int):
if network == None:
network = 0
else:
try:
network = NetworkIDsToName[network].value
except:
network = 0
return network
3 changes: 2 additions & 1 deletion api/public.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
botFC = str(233790548638).zfill(12) # FC == Friendcode
pretendoBotFC = str(220707964911).zfill(12) # FC == Friendcode
nintendoBotFC = str(439944935945).zfill(12) # FC == Friendcode
14 changes: 9 additions & 5 deletions api/template.private.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# Created by Deltaion Lee (MCMi460) on Github
# Created by Deltaion Lee (MCMi460) & Preloading on Github
# This is my private file, but with all of the dangerous stuff removed
# You can use it as a template for yours

SERIAL_NUMBER:str = "" # Serial number on console minus the last digit
MAC_ADDRESS:str = "" # Console MAC address (see WiFi settings), all lowercase with no colons
DEVICE_CERT:bytes = bytes.fromhex("") # Unique console certificate. Get from sniffing traffic
DEVICE_CERT:bytes = bytes.fromhex("") # Unique console certificate. Get from sniffing traffic or from the LocalSeedFriendCode file
DEVICE_NAME:str = "" # Doesn't matter

# 3DS does NOT send NEX credentials over NASC
# They are generated once when the account is created and stored on the device
# Homebrew like https://github.com/Stary2001/nex-dissector/tree/master/get_3ds_pid_password
# can be used to dump the PID and password
PID:int = 0
PID_HMAC:str = "" # Sniff console traffic or dump from friends title save (bytes 66-84)
# You must redump these keys for each network.
NINTENDO_PID:int = 0
NINTENDO_NEX_PASSWORD:str = ""

NEX_PASSWORD:str = ""
PRETENDO_PID:int = 0
PRETENDO_NEX_PASSWORD:str = ""

PID_HMAC:str = "" # Sniff console traffic or dump from friends title save (bytes 66-84) somewhere im the file

REGION:int = 1 # USA
LANGUAGE:int = 1 # English
Expand Down
10 changes: 5 additions & 5 deletions api/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ def getPath(path):

return os.path.join(root, path)

def startDBTime(time):
def startDBTime(time, network):
with sqlite3.connect('sqlite/fcLibrary.db') as con:
cursor = con.cursor()
cursor.execute('DELETE FROM config')
cursor.execute('INSERT INTO config (BACKEND_UPTIME) VALUES (%s)' % (time,))
con.commit()
cursor = con.cursor()
cursor.execute('DELETE FROM config WHERE network=' + str(network)) # doing this isn't the most intelegent of ideas but oh well (i hope you don't need to ever add another config :D)
cursor.execute('INSERT INTO config (BACKEND_UPTIME, NETWORK) VALUES (%s, %s)' % (time, network,))
con.commit()

try:
terminalSize = os.get_terminal_size(0).columns - 2
Expand Down
2 changes: 1 addition & 1 deletion client/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def assignVariables(self):

self.loginButton.clicked.connect(self.changeState)

self.botFCLabel.setText('-'.join(botFC[i:i+4] for i in range(0, len(botFC), 4)))
self.botFCLabel.setText('-'.join(nintendoBotFC[i:i+4] for i in range(0, len(nintendoBotFC), 4)))

def stylize(self):
self.underLyingButton.click()
Expand Down
6 changes: 3 additions & 3 deletions client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
## The below contains 3dsrpc.com-specific information
## You will have to provide your own 'bot' FC if you are planning
## on running your own front and backend.
convertFriendCodeToPrincipalId(botFC) # A quick verification check
convertFriendCodeToPrincipalId(nintendoBotFC) # A quick verification check

_REGION = typing.Literal['ALL', 'US', 'JP', 'GB', 'KR', 'TW']
path = getAppPath()
Expand Down Expand Up @@ -132,7 +132,7 @@ def fetch(self):
APIExcept(r)
if r['Exception']:
if 'not recognized' in r['Exception']['Error']:
print('%sRemember, the bot\'s friend code is as follows:\n%s%s' % (Color.YELLOW, '-'.join(botFC[i:i+4] for i in range(0, len(botFC), 4)), Color.DEFAULT))
print('%sRemember, the bot\'s friend code is as follows:\n%s%s' % (Color.YELLOW, '-'.join(nintendoBotFC[i:i+4] for i in range(0, len(nintendoBotFC), 4)), Color.DEFAULT))
raise APIException(r['Exception'])
return r

Expand Down Expand Up @@ -215,7 +215,7 @@ def main():
else:
raise Exception()
except:
print('%sPlease take this time to add the bot\'s FC to your target 3DS\' friends list.\n%sBot FC: %s%s' % (Color.YELLOW, Color.DEFAULT, Color.BLUE, '-'.join(botFC[i:i+4] for i in range(0, 12, 4))))
print('%sPlease take this time to add the bot\'s FC to your target 3DS\' friends list.\n%sBot FC: %s%s' % (Color.YELLOW, Color.DEFAULT, Color.BLUE, '-'.join(nintendoBotFC[i:i+4] for i in range(0, 12, 4))))
input('%s[Press enter to continue]%s' % (Color.GREEN, Color.DEFAULT))
friendCode = input('Please enter your 3DS\' friend code\n> %s' % Color.PURPLE)
config = {}
Expand Down
94 changes: 66 additions & 28 deletions server/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
from nintendo import nasc
from nintendo.nex import backend, friends, settings, streams
from nintendo.nex import common
import anyio, time, sqlite3, sys, traceback
from enum import Enum
import anyio, time, sqlite3, sys, traceback, argparse
sys.path.append('../')
from api.private import SERIAL_NUMBER, MAC_ADDRESS, DEVICE_CERT, DEVICE_NAME, REGION, LANGUAGE, PID, PID_HMAC, NEX_PASSWORD
from api.private import SERIAL_NUMBER, MAC_ADDRESS, DEVICE_CERT, DEVICE_NAME, REGION, LANGUAGE, NINTENDO_PID, PRETENDO_PID, PID_HMAC, NINTENDO_NEX_PASSWORD, PRETENDO_NEX_PASSWORD
from api import *
from api.love2 import *
from api.networks import NetworkIDsToName

import logging
logging.basicConfig(level=logging.INFO)
Expand All @@ -17,16 +19,16 @@
since = 0
quicker = 6
begun = time.time()
startDBTime(begun)

network:int = 0

async def main():
while True:
time.sleep(1)
print('Grabbing new friends...')
with sqlite3.connect('sqlite/fcLibrary.db') as con:
cursor = con.cursor()

cursor.execute('SELECT friendCode, lastAccessed FROM friends')
cursor.execute('SELECT friendCode, lastAccessed FROM ' + NetworkIDsToName(network).name + "_friends")
result = cursor.fetchall()
if not result:
continue
Expand All @@ -39,15 +41,34 @@ async def main():

try:
client = nasc.NASCClient()
client.set_title(0x0004013000003202, 20)
client.set_device(SERIAL_NUMBER, MAC_ADDRESS, DEVICE_CERT, DEVICE_NAME)
client.set_title(0x0004013000003202, 20) # to be honest, this should be seperate between networks, so if one eg, gets banned, you still get to keep the friend code for the other network, but i'm lazy.
client.set_locale(REGION, LANGUAGE)
client.set_user(PID, PID_HMAC)

if network == 0: # Nintendo Network
client.set_url("nasc.nintendowifi.net")
PID = NINTENDO_PID
NEX_PASSWORD = NINTENDO_NEX_PASSWORD

elif network == 1:
client.set_url("nasc.pretendo.cc")
client.context.set_authority(None)
PID = PRETENDO_PID
NEX_PASSWORD = PRETENDO_NEX_PASSWORD

else:
raise Exception(NetworkIDsToName(network).name + " is not a valid network")

client.set_device(SERIAL_NUMBER, MAC_ADDRESS, DEVICE_CERT, DEVICE_NAME)
client.set_user(PID , PID_HMAC)

response = await client.login(0x3200)

s = settings.load('friends')
s.configure("ridfebb9", 20000)

if network == 1: # If the app starts randomly hanging on Pretendo, try removing this, and the next line.
s["prudp.ping_timeout"] = 100000000000 # oh my god this is horrifying, but it makes it works, so who am i to care
Comment on lines +69 to +70

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We made a lot of stability changes and bug fixes a while ago, after this PR was made. This hack can likely be removed, and if the issue still persists then you should open an issue about it on our end. This is obviously bad behavior on our end if it persists

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there! A bit ago, I tested, and the program does work without these lines. (yay, thank you!) However, the PR has technically been merged (see 8751376), so I can't modify the PR without causing problems (probably). Why this isn't closed, I do not know. I should probably close it. I'll make sure to try to get the owner of the repo to remove the workaround. Thanks tho!


async with backend.connect(s, response.host, response.port) as be:
async with be.login(str(PID), NEX_PASSWORD) as client:
friends_client = friends.FriendsClientV1(client)
Expand All @@ -64,50 +85,56 @@ async def main():
removables = await friends_client.get_all_friends()
for friend in removables:
time.sleep(delay / quicker)
await friends_client.remove_friend_by_principal_id(friend.unk1)
await friends_client.remove_friend_by_principal_id(friend.pid)
print('Removed %s friends' % str(len(removables)))

removeList = []
cleanUp = []
time.sleep(delay)
await friends_client.add_friend_by_principal_ids(0, rotation)
if network == 1:
for friend_pid in rotation:
time.sleep(delay / quicker)
await friends_client.add_friend_by_principal_id(0, friend_pid) # the add_friend_by_principal_ids hasn't been implemented yet on pretendo, so this is a fix for now.
else:
time.sleep(delay)
await friends_client.add_friend_by_principal_ids(0, rotation)


time.sleep(delay)
t = await friends_client.get_all_friends()
if len(t) < len(rotation):
for ID in rotation:
if ID not in [ f.unk1 for f in t ]:
if ID not in [ f.pid for f in t ]:
removeList.append(ID)
x = t
t = []
for t1 in x:
if t1.unk1 in rotation:
if t1.pid in rotation:
t.append(t1)
else:
cleanUp.append(t1.unk1)
cleanUp.append(t1.pid)

for remover in removeList:
cursor.execute('DELETE FROM friends WHERE friendCode = ?', (str(convertPrincipalIdtoFriendCode(remover)).zfill(12),))
cursor.execute('DELETE FROM discordFriends WHERE friendCode = ?', (str(convertPrincipalIdtoFriendCode(remover)).zfill(12),))
cursor.execute('DELETE FROM ' + NetworkIDsToName(network).name + "_friends" + ' WHERE friendCode = ?', (str(convertPrincipalIdtoFriendCode(remover)).zfill(12),))
cursor.execute('DELETE FROM discordFriends WHERE friendCode = ? AND network = ?', (str(convertPrincipalIdtoFriendCode(remover)).zfill(12), str(network)))
con.commit()

if len(t) > 0:
time.sleep(delay)
f = await friends_client.get_friend_presence([ e.unk1 for e in t ])
f = await friends_client.get_friend_presence([ e.pid for e in t ])
users = []
for game in f:
# game.unk == principalId
users.append(game.unk)
users.append(game.pid)
#print(game.__dict__)
#print(game.presence.__dict__)
#print(game.presence.game_key.__dict__)
gameDescription = game.presence.game_mode_description
if not gameDescription: gameDescription = ''
joinable = bool(game.presence.join_availability_flag)

cursor.execute('UPDATE friends SET online = ?, titleID = ?, updID = ?, joinable = ?, gameDescription = ?, lastOnline = ? WHERE friendCode = ?', (True, game.presence.game_key.title_id, game.presence.game_key.title_version, joinable, gameDescription, time.time(), str(convertPrincipalIdtoFriendCode(users[-1])).zfill(12)))
cursor.execute('UPDATE ' + NetworkIDsToName(network).name + "_friends" + ' SET online = ?, titleID = ?, updID = ?, joinable = ?, gameDescription = ?, lastOnline = ? WHERE friendCode = ?', (True, game.presence.game_key.title_id, game.presence.game_key.title_version, joinable, gameDescription, time.time(), str(convertPrincipalIdtoFriendCode(users[-1])).zfill(12)))
for user in [ h for h in rotation if not h in users ]:
cursor.execute('UPDATE friends SET online = ?, titleID = ?, updID = ? WHERE friendCode = ?', (False, 0, 0, str(convertPrincipalIdtoFriendCode(user)).zfill(12)))
cursor.execute('UPDATE ' + NetworkIDsToName(network).name + "_friends" + ' SET online = ?, titleID = ?, updID = ? WHERE friendCode = ?', (False, 0, 0, str(convertPrincipalIdtoFriendCode(user)).zfill(12)))

con.commit()

Expand All @@ -120,16 +147,16 @@ async def main():
for ti in t:
work = False
for l in list_:
if l[0] == ti.unk1 and time.time() - l[1] <= 600:
if l[0] == ti.pid and time.time() - l[1] <= 600:
work = True
if not work:
continue

time.sleep(delay)

ti.unk2 = 0 # A cursed (but operable) 'hack'
ti.friend_code = 0 # A cursed (but operable) 'hack'
try:
j1 = await friends_client.get_friend_persistent_info([ti.unk1,])
j1 = await friends_client.get_friend_persistent_info([ti.pid,])
except:
continue
comment = j1[0].message
Expand All @@ -139,7 +166,7 @@ async def main():
if not comment.endswith(' '):
# Get user's mii + username from mii
m = await friends_client.get_friend_mii([ti,])
username = m[0].mii.unk1
username = m[0].mii.name
mii_data = m[0].mii.mii_data
obj = MiiData()
obj.decode(obj.convert(io.BytesIO(mii_data)))
Expand All @@ -149,7 +176,7 @@ async def main():
jeuFavori = j1[0].game_key.title_id
else:
comment = ''
cursor.execute('UPDATE friends SET username = ?, message = ?, mii = ?, jeuFavori = ? WHERE friendCode = ?', (username, comment, face, jeuFavori, str(convertPrincipalIdtoFriendCode(ti.unk1)).zfill(12)))
cursor.execute('UPDATE ' + NetworkIDsToName(network).name + "_friends" + ' SET username = ?, message = ?, mii = ?, jeuFavori = ? WHERE friendCode = ?', (username, comment, face, jeuFavori, str(convertPrincipalIdtoFriendCode(ti.pid)).zfill(12)))
con.commit()

for friend in rotation + cleanUp:
Expand All @@ -159,10 +186,21 @@ async def main():
print('An error occurred!\n%s' % e)
print(traceback.format_exc())
time.sleep(2)

if __name__ == '__main__':
try:
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--network', choices=[member.name.lower() for member in NetworkIDsToName], required=True)
args = parser.parse_args()

network = NetworkIDsToName[args.network.lower()].value
if network == NetworkIDsToName.pretendo.value:
# Pretendo shouldn't care about a delay, like maybe nintendo? Since this was here just to prevent spamming Nintendo, we don't need it for pretendo. It will also make it faster.
# Maybe later it should just not have a delay for all networks?
delay, quicker = 0, 1
startDBTime(begun, network)
anyio.run(main)
except (KeyboardInterrupt, Exception) as e:
startDBTime(0)
print(e)
if network is not None:
startDBTime(0, network)
print(e)
Loading