All checks were successful
Build and Test / puyopuyo (push) Successful in 3m26s
Build and Test / minecraft-wiiu (push) Successful in 3m57s
Build and Test / splatoon (push) Successful in 4m12s
Build and Test / wii-u-chat (push) Successful in 4m12s
Build and Test / splatoon-testfire (push) Successful in 4m20s
Build and Test / fast-racing-neo (push) Successful in 4m21s
Build and Test / mario-tennis (push) Successful in 4m21s
Build and Test / wii-sports-club (push) Successful in 4m21s
Build and Test / sonic-transformed (push) Successful in 4m27s
Build and Test / super-mario-maker (push) Successful in 4m44s
Build and Test / friends (push) Successful in 5m26s
1769 lines
58 KiB
Rust
1769 lines
58 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use std::io::{Cursor, Write};
|
|
use std::ops::Deref;
|
|
use std::process::id;
|
|
use std::sync::{Arc, atomic::AtomicU32};
|
|
use std::sync::{LazyLock, Weak};
|
|
use std::time::Duration;
|
|
use std::{env, mem};
|
|
|
|
use base64::{Engine as _, engine::general_purpose};
|
|
use bytemuck::{Pod, Zeroable, bytes_of};
|
|
use chrono::{NaiveDateTime, TimeZone, Utc};
|
|
use futures::StreamExt;
|
|
use hex::decode;
|
|
use hmac::Mac;
|
|
use log::info;
|
|
use macros::rmc_struct;
|
|
use rnex_core::rmc::protocols::account_management::{
|
|
AccountExtraInfo, AccountManagement, RawAccountManagement, RawAccountManagementInfo,
|
|
RemoteAccountManagement,
|
|
};
|
|
use rnex_core::rmc::protocols::friends_3ds::{
|
|
Friends3DS, RawFriends3DS, RawFriends3DSInfo, RemoteFriends3DS,
|
|
};
|
|
use rnex_core::rmc::protocols::friends_wiiu::{
|
|
FriendsWiiU, RawFriendsWiiU, RawFriendsWiiUInfo, RemoteFriendsWiiU,
|
|
};
|
|
use rnex_core::rmc::protocols::nintendo_notification::{
|
|
NintendoNotification, RawNintendoNotification, RawNintendoNotificationInfo,
|
|
RemoteNintendoNotification,
|
|
};
|
|
use rnex_core::rmc::protocols::secure::{RawSecure, RawSecureInfo, RemoteSecure, Secure};
|
|
use rnex_core::{
|
|
define_rmc_proto,
|
|
kerberos::KerberosDateTime,
|
|
nex::common::get_station_urls,
|
|
prudp::{socket_addr::PRUDPSockAddr, station_url::StationUrl},
|
|
rmc::{
|
|
protocols::friends_wiiu::{
|
|
BlacklistedPrincipal, Comment, FriendInfo, FriendRequest, NNAInfo, NintendoPresenceV2,
|
|
PersistentNotification, PrincipalPreference, PrincipalRequestBlockSetting,
|
|
},
|
|
response::ErrorCode,
|
|
structures::{any::Any, qresult::QResult},
|
|
},
|
|
};
|
|
use sqlx::query;
|
|
use std::sync::atomic::Ordering::Relaxed;
|
|
use tokio::spawn;
|
|
use tokio::sync::RwLock;
|
|
use tokio::time::sleep;
|
|
|
|
use rnex_core::rmc::protocols::friends_wiiu::{GameKey, MiiV2, PrincipalBasicInfo};
|
|
|
|
use rnex_core::PID;
|
|
|
|
use rnex_core::rmc::protocols::account_management::NintendoCreateAccountData;
|
|
use rnex_core::rmc::protocols::nintendo_notification::NintendoNotificationEvent;
|
|
use rnex_core::rmc::structures::RmcSerialize;
|
|
|
|
use rnex_core::rmc::structures::data::Data;
|
|
|
|
use crate::executables::common::get_db;
|
|
|
|
use crate::kerberos;
|
|
use crate::rmc::protocols::friends_3ds::{
|
|
FriendComment, FriendMii, FriendMiiList, FriendPersistentInfo, FriendPicture, FriendPresence,
|
|
FriendRelationship, Mii, MiiList, MyProfile, NintendoPresence, PlayedGame,
|
|
};
|
|
use crate::rmc::protocols::friends_wiiu::FriendRequestMessage;
|
|
use crate::rmc::protocols::nintendo_notification::NintendoNotificationEventGeneral;
|
|
use crate::rmc::response::ErrorCode::FPD_InvalidArgument;
|
|
use nex_account::grpc::ActCreateInfo;
|
|
use nex_account::grpc::nex_account_service_client::NexAccountServiceClient;
|
|
use nex_account::{derive_pid_hmac, grpc_client};
|
|
use rnex_core::rmc::structures::qbuffer::QBuffer;
|
|
|
|
define_rmc_proto!(
|
|
proto FriendsUser{
|
|
Secure,
|
|
FriendsWiiU,
|
|
Friends3DS
|
|
}
|
|
);
|
|
define_rmc_proto!(
|
|
proto FriendRemote{
|
|
NintendoNotification
|
|
}
|
|
);
|
|
define_rmc_proto!(
|
|
proto FriendsGuest{
|
|
Secure,
|
|
AccountManagement
|
|
}
|
|
);
|
|
|
|
static NEX_ACCOUNT_URL: LazyLock<String> =
|
|
LazyLock::new(|| env::var("NEX_ACCOUNT_ENDPOINT").expect("NEX_ACCOUNT_ENDPOINT not set"));
|
|
|
|
#[repr(C, packed)]
|
|
#[derive(Pod, Zeroable, Copy, Clone, Debug)]
|
|
pub struct NascToken {
|
|
pub pid: i32,
|
|
pub time: [u8; 14],
|
|
pub pwd_hash: [u8; 4],
|
|
}
|
|
|
|
#[rmc_struct(FriendsUser)]
|
|
pub struct FriendsUser {
|
|
pub fm: Arc<FriendsManager>,
|
|
pub addr: PRUDPSockAddr,
|
|
pub pid: PID,
|
|
pub friend_pids: RwLock<Vec<PID>>,
|
|
pub maybe_remote_friend: RwLock<HashMap<PID, Weak<FriendsUser>>>,
|
|
pub presence: RwLock<Option<NintendoPresenceV2>>,
|
|
pub this: Weak<FriendsUser>,
|
|
pub remote: RemoteFriendRemote,
|
|
}
|
|
|
|
#[rmc_struct(FriendsGuest)]
|
|
pub struct FriendsGuest {
|
|
pub fm: Arc<FriendsManager>,
|
|
pub addr: PRUDPSockAddr,
|
|
}
|
|
|
|
pub struct FriendsManager {
|
|
pub cid_counter: AtomicU32,
|
|
pub users: RwLock<HashMap<PID, Weak<FriendsUser>>>,
|
|
}
|
|
|
|
impl FriendsManager {
|
|
pub fn next_cid(&self) -> u32 {
|
|
self.cid_counter.fetch_add(1, Relaxed)
|
|
}
|
|
}
|
|
|
|
impl FriendsManager {
|
|
async fn denies_friend_requests(&self, pid: PID) -> Result<bool, ErrorCode> {
|
|
query!("select principal_preference_block_friend_requests from nintendo_network_accounts where pid = $1", pid)
|
|
.fetch_one(get_db())
|
|
.await
|
|
.map_err(|_| ErrorCode::FPD_InvalidAccount)
|
|
.map(|v| v.principal_preference_block_friend_requests)
|
|
}
|
|
}
|
|
|
|
// ALL of this is stubbed
|
|
impl Friends3DS for FriendsUser {
|
|
async fn update_profile(&self, profile: MyProfile) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_mii(&self, profile: Mii) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_mii_list(&self, profile: MiiList) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_played_games(&self, profile: Vec<PlayedGame>) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_preference(
|
|
&self,
|
|
show_online_status: bool,
|
|
show_current_title: bool,
|
|
block_friend_requests: bool,
|
|
) -> Result<(), ErrorCode> {
|
|
// stubbed
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_friend_mii(
|
|
&self,
|
|
friends: Vec<crate::rmc::protocols::friends_3ds::FriendInfo>,
|
|
) -> Result<Vec<FriendMii>, ErrorCode> {
|
|
// sorry for the copying pretendo but i don't have a mii on hand rn
|
|
let data: Vec<u8> = vec![
|
|
0x03, 0x00, 0x00, 0x40, 0xE9, 0x55, 0xA2, 0x09, 0xE7, 0xC7, 0x41, 0x82, 0xD9, 0x7D,
|
|
0x0B, 0x2D, 0x03, 0xB3, 0xB8, 0x8D, 0x27, 0xD9, 0x00, 0x00, 0x01, 0x40, 0x62, 0x00,
|
|
0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x12, 0x00, 0x81, 0x01, 0x04, 0x68, 0x43, 0x18,
|
|
0x20, 0x34, 0x46, 0x14, 0x81, 0x12, 0x17, 0x68, 0x0D, 0x00, 0x00, 0x29, 0x03, 0x52,
|
|
0x48, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x86,
|
|
];
|
|
|
|
let dummymii = FriendMii {
|
|
data: Data {},
|
|
pid: 69,
|
|
mii: Mii {
|
|
data: Data {},
|
|
name: "test".to_string(),
|
|
profanity: false,
|
|
char_set: 0,
|
|
mii_data: data,
|
|
},
|
|
modified_at: Default::default(),
|
|
};
|
|
|
|
Ok(vec![dummymii])
|
|
}
|
|
|
|
async fn get_friend_mii_list(
|
|
&self,
|
|
friends: Vec<crate::rmc::protocols::friends_3ds::FriendInfo>,
|
|
) -> Result<Vec<FriendMiiList>, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn is_active_game(
|
|
&self,
|
|
unk: Vec<u32>,
|
|
game_key: crate::rmc::protocols::friends_3ds::GameKey,
|
|
) -> Result<Vec<u32>, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn get_principal_id_by_local_friend_code(
|
|
&self,
|
|
unk1: u64,
|
|
unk2: Vec<u64>,
|
|
) -> Result<Vec<FriendRelationship>, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn get_friend_relationships(
|
|
&self,
|
|
unk2: Vec<u32>,
|
|
) -> Result<Vec<FriendRelationship>, ErrorCode> {
|
|
let dummy = FriendRelationship {
|
|
data: Data {},
|
|
pid: 69,
|
|
local_friend_code: 3268487429723707977,
|
|
relationship_type: 1,
|
|
};
|
|
|
|
Ok(vec![dummy])
|
|
}
|
|
|
|
async fn add_friend_by_pid(&self, unk: u64, pid: PID) -> Result<FriendRelationship, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn add_friend_by_lst_pid(
|
|
&self,
|
|
unk: u64,
|
|
pid: Vec<PID>,
|
|
) -> Result<Vec<FriendRelationship>, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn remove_friend_by_local_code(&self, local_code: u64) -> Result<(), ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn remove_friend_by_pid(&self, pid: PID) -> Result<(), ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn get_all_friends(&self) -> Result<Vec<FriendRelationship>, ErrorCode> {
|
|
let dummy = FriendRelationship {
|
|
data: Data {},
|
|
pid: 69,
|
|
local_friend_code: 3268487429723707977,
|
|
relationship_type: 1,
|
|
};
|
|
|
|
Ok(vec![dummy])
|
|
}
|
|
|
|
async fn update_blacklist(&self) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn sync_friend(
|
|
&self,
|
|
unk1: u64,
|
|
unk2: Vec<u32>,
|
|
unk3: Vec<u64>,
|
|
) -> Result<Vec<FriendRelationship>, ErrorCode> {
|
|
log::info!("params: {:?}, {:?}, {:?}", unk1, unk2, unk3);
|
|
|
|
let dummy = FriendRelationship {
|
|
data: Data {},
|
|
pid: 69,
|
|
local_friend_code: 3268487429723707977,
|
|
relationship_type: 1,
|
|
};
|
|
|
|
Ok(vec![dummy])
|
|
}
|
|
|
|
async fn update_presence(
|
|
&self,
|
|
nintendo_presence: NintendoPresence,
|
|
unk: bool,
|
|
) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_favorite_game_key(
|
|
&self,
|
|
game_key: rnex_core::rmc::protocols::friends_3ds::GameKey,
|
|
) -> Result<(), ErrorCode> {
|
|
log::info!("favorite game key: {:?}", game_key);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_comment(&self, comment: String) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_picture(&self, unk: u32, picture: Vec<u8>) -> Result<(), ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn get_friend_presence(&self, unk: Vec<u32>) -> Result<Vec<FriendPresence>, ErrorCode> {
|
|
log::info!("pids: {:?}", unk);
|
|
|
|
let presence = FriendPresence {
|
|
data: Data {},
|
|
pid: 69,
|
|
presence: NintendoPresence {
|
|
data: Data {},
|
|
changed_bit_flag: 0xFFFFFFFF,
|
|
game_key: rnex_core::rmc::protocols::friends_3ds::GameKey {
|
|
data: Data {},
|
|
title_id: 1125899907457280,
|
|
version: 2064,
|
|
},
|
|
game_mode_desctiption: "".to_string(),
|
|
join_availibility_flag: 0,
|
|
mm_system_type: 0,
|
|
join_game_id: 0,
|
|
join_game_mode: 0,
|
|
owner_pid: 0,
|
|
join_group_id: 0,
|
|
application_arg: vec![],
|
|
},
|
|
};
|
|
|
|
Ok(vec![presence])
|
|
}
|
|
|
|
async fn get_friend_comment(
|
|
&self,
|
|
unk: Vec<crate::rmc::protocols::friends_3ds::FriendInfo>,
|
|
) -> Result<Vec<FriendComment>, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn get_friend_picture(&self, unk: Vec<u32>) -> Result<Vec<FriendPicture>, ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
|
|
async fn get_friend_persistent_info(
|
|
&self,
|
|
unk: Vec<u32>,
|
|
) -> Result<Vec<FriendPersistentInfo>, ErrorCode> {
|
|
let dummypersistentinfo = FriendPersistentInfo {
|
|
data: Data {},
|
|
pid: 69,
|
|
region: 0,
|
|
country: 0,
|
|
area: 0,
|
|
language: 0,
|
|
platform: 0,
|
|
game_key: rnex_core::rmc::protocols::friends_3ds::GameKey {
|
|
data: Data {},
|
|
title_id: 1125899907457280,
|
|
version: 2064,
|
|
},
|
|
message: "yo whats up".to_string(),
|
|
msg_updated_at: KerberosDateTime::now(),
|
|
friended_at: KerberosDateTime::now(),
|
|
last_online: KerberosDateTime::now(),
|
|
};
|
|
|
|
Ok(vec![dummypersistentinfo])
|
|
}
|
|
|
|
async fn send_invitation(&self, unk: Vec<u32>) -> Result<(), ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
}
|
|
|
|
fn smoosh_to_i16(val1: u8, val2: u8) -> i16 {
|
|
bytemuck::cast(((val1 as u16) << 8) | val2 as u16)
|
|
}
|
|
fn unsmoosh_from_i16(val: i16) -> (u8, u8) {
|
|
let val: u16 = bytemuck::cast(val);
|
|
((val & 0xFF00 >> 8) as u8, (val & 0xFF) as u8)
|
|
}
|
|
macro_rules! basic_principal_from_record {
|
|
($record:expr) => {{
|
|
let (mii_unk1, mii_unk2) = unsmoosh_from_i16($record.mii_unks);
|
|
PrincipalBasicInfo {
|
|
data: Data {},
|
|
pid: $record.pid,
|
|
nnid: $record.nnid.clone(),
|
|
mii: MiiV2 {
|
|
data: Data {},
|
|
date_time: KerberosDateTime(bytemuck::cast($record.mii_unk_datetime)),
|
|
mii_data: $record.mii_ffl_data.clone(),
|
|
name: QBuffer($record.mii_name.clone()),
|
|
unk: mii_unk1,
|
|
unk2: mii_unk2,
|
|
},
|
|
unk: (bytemuck::cast::<_, u16>($record.principal_info_unk) & 0xFF) as u8,
|
|
}
|
|
}};
|
|
}
|
|
|
|
macro_rules! nna_info_from_record {
|
|
($record:expr) => {{
|
|
let (nna_unk1, nna_unk2) = unsmoosh_from_i16($record.nna_info_unks_2);
|
|
NNAInfo {
|
|
data: Data {},
|
|
principal_basic_info: basic_principal_from_record!($record),
|
|
unk: nna_unk1,
|
|
unk2: nna_unk2,
|
|
}
|
|
}};
|
|
}
|
|
|
|
macro_rules! game_key_from_record {
|
|
($record:expr) => {
|
|
GameKey {
|
|
data: Data {},
|
|
tid: $record.game_key_tid,
|
|
version: $record.game_key_version,
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! friend_request_from_record {
|
|
($record:expr) => {{
|
|
let (unk, unk2) = unsmoosh_from_i16($record.unks_1);
|
|
FriendRequest {
|
|
data: Data {},
|
|
basic_info: basic_principal_from_record!($record),
|
|
request_message: FriendRequestMessage {
|
|
data: Data {},
|
|
expires_on: KerberosDateTime::from_naive(
|
|
Utc.timestamp_opt(Utc::now().timestamp() + 2592000, 0)
|
|
.unwrap()
|
|
.naive_utc(),
|
|
),
|
|
friend_request_id: $record.id,
|
|
game_key: game_key_from_record!($record),
|
|
is_recieved: $record.is_recieved,
|
|
message: $record.message,
|
|
unk,
|
|
unk2,
|
|
unk3: $record.unk_2,
|
|
unk4: KerberosDateTime(bytemuck::cast($record.unk_3)),
|
|
},
|
|
sent_on: KerberosDateTime::from_naive($record.creation_time),
|
|
}
|
|
}};
|
|
}
|
|
|
|
impl FriendsWiiU for FriendsUser {
|
|
async fn update_and_get_all_information(
|
|
&self,
|
|
info: NNAInfo,
|
|
presence: NintendoPresenceV2,
|
|
birthday: KerberosDateTime,
|
|
) -> Result<
|
|
(
|
|
PrincipalPreference,
|
|
Comment,
|
|
Vec<FriendInfo>,
|
|
Vec<FriendRequest>,
|
|
Vec<FriendRequest>,
|
|
Vec<BlacklistedPrincipal>,
|
|
bool,
|
|
Vec<PersistentNotification>,
|
|
bool,
|
|
),
|
|
ErrorCode,
|
|
> {
|
|
if info.principal_basic_info.pid != self.pid {
|
|
return Err(ErrorCode::FPD_InvalidArgument);
|
|
}
|
|
|
|
*self.presence.write().await = Some(presence.clone());
|
|
|
|
let Ok(query) = query!("
|
|
insert into nintendo_network_accounts (
|
|
pid, nnid, mii_name, mii_unks, mii_ffl_data, mii_unk_datetime, principal_info_unk, nna_info_unks_2, birthday
|
|
) values (
|
|
$1, $2, $3, $4, $5, $6 , $7, $8, $9
|
|
)
|
|
on conflict (pid)
|
|
do update set
|
|
mii_name = excluded.mii_name,
|
|
mii_unks = excluded.mii_unks,
|
|
mii_ffl_data = excluded.mii_ffl_data,
|
|
mii_unk_datetime = excluded.mii_unk_datetime,
|
|
principal_info_unk = excluded.principal_info_unk,
|
|
nna_info_unks_2 = excluded.nna_info_unks_2,
|
|
birthday = excluded.birthday
|
|
returning comment_unk, comment_message, comment_lastchanged, principal_preference_show_online, principal_preference_show_currently_playing_title, principal_preference_block_friend_requests
|
|
",
|
|
self.pid,
|
|
info.principal_basic_info.nnid,
|
|
info.principal_basic_info.mii.name.0,
|
|
smoosh_to_i16(info.principal_basic_info.mii.unk, info.principal_basic_info.mii.unk2),
|
|
info.principal_basic_info.mii.mii_data,
|
|
bytemuck::cast::<_, i64>(info.principal_basic_info.mii.date_time.0),
|
|
info.principal_basic_info.unk as i16,
|
|
smoosh_to_i16(info.unk, info.unk2),
|
|
bytemuck::cast::<_, i64>(birthday)
|
|
)
|
|
.fetch_one(get_db())
|
|
.await else {
|
|
println!("psql failed(unable to update user)");
|
|
return Err(ErrorCode::Core_SystemError)
|
|
};
|
|
|
|
let Ok(outgoing_friend_requests) = query!(
|
|
"
|
|
select
|
|
*
|
|
from friend_requests
|
|
inner join nintendo_network_accounts on recipient = nintendo_network_accounts.pid
|
|
where sender = $1
|
|
",
|
|
self.pid
|
|
)
|
|
.fetch_all(get_db())
|
|
.await
|
|
.map(|v| {
|
|
v.into_iter()
|
|
.map(|v| friend_request_from_record!(v))
|
|
.collect::<Vec<_>>()
|
|
}) else {
|
|
println!("error whilest getting friend requests");
|
|
return Err(ErrorCode::Core_SystemError);
|
|
};
|
|
|
|
let Ok(incoming_friend_requests) = query!(
|
|
"
|
|
select
|
|
*
|
|
from friend_requests
|
|
inner join nintendo_network_accounts on sender = nintendo_network_accounts.pid
|
|
where recipient = $1
|
|
",
|
|
self.pid
|
|
)
|
|
.fetch_all(get_db())
|
|
.await
|
|
.map(|v| {
|
|
v.into_iter()
|
|
.map(|v| friend_request_from_record!(v))
|
|
.collect::<Vec<_>>()
|
|
}) else {
|
|
println!("error whilest getting friend requests");
|
|
return Err(ErrorCode::Core_SystemError);
|
|
};
|
|
|
|
let Ok(friends_raw) = query!(
|
|
"
|
|
select
|
|
nintendo_network_accounts.pid,
|
|
since,
|
|
nnid,
|
|
comment_message,
|
|
comment_unk,
|
|
comment_lastchanged,
|
|
nna_info_unks_2,
|
|
principal_info_unk,
|
|
mii_name,
|
|
mii_unks,
|
|
mii_ffl_data,
|
|
mii_unk_datetime,
|
|
last_online
|
|
from friendships_of_pid($1) as friendships
|
|
inner join nintendo_network_accounts on friendships.pid = nintendo_network_accounts.pid",
|
|
self.pid
|
|
)
|
|
.fetch_all(get_db())
|
|
.await
|
|
else {
|
|
println!("error whilest getting friends");
|
|
return Err(ErrorCode::Core_SystemError);
|
|
};
|
|
|
|
let mut friends = Vec::with_capacity(friends_raw.len());
|
|
|
|
for mut friend in friends_raw {
|
|
let Some(friend_since) = friend.since else {
|
|
println!("this should absolutely never happen(psql messed up somehow)");
|
|
return Err(ErrorCode::Core_SystemError);
|
|
};
|
|
|
|
let mut presence = NintendoPresenceV2::default();
|
|
|
|
let mut current_friends = self.friend_pids.write().await;
|
|
current_friends.push(friend.pid);
|
|
drop(current_friends);
|
|
|
|
let users = self.fm.users.read().await;
|
|
let user = users.get(&friend.pid).cloned();
|
|
drop(users);
|
|
if let Some(user) = user {
|
|
if let Some(user) = user.upgrade() {
|
|
let online_presence = user.presence.read().await;
|
|
if let Some(online_presence) = online_presence.as_ref() {
|
|
presence = online_presence.clone();
|
|
} else {
|
|
println!(
|
|
"internal server error, user is somehow online and in the friends manager yet has not set their presence yet..."
|
|
);
|
|
}
|
|
drop(online_presence);
|
|
let mut other_friend_remotes = user.maybe_remote_friend.write().await;
|
|
other_friend_remotes.insert(self.pid, self.this.clone());
|
|
drop(other_friend_remotes);
|
|
let mut remo_friends = self.maybe_remote_friend.write().await;
|
|
remo_friends.insert(friend.pid, Arc::downgrade(&user));
|
|
drop(remo_friends);
|
|
}
|
|
}
|
|
|
|
friends.push(FriendInfo {
|
|
data: Data {},
|
|
nna_info: nna_info_from_record!(friend),
|
|
presence,
|
|
comment: Comment {
|
|
data: Data {},
|
|
unk: (bytemuck::cast::<_, u16>(friend.comment_unk) & 0xFF) as u8,
|
|
message: friend.comment_message,
|
|
last_changed: KerberosDateTime::from_naive(friend.comment_lastchanged),
|
|
},
|
|
became_friends: KerberosDateTime::from_naive(friend_since),
|
|
last_online: KerberosDateTime::from_naive(friend.last_online),
|
|
unk: 0,
|
|
});
|
|
}
|
|
|
|
let Ok(denylist) = query!(
|
|
"
|
|
select
|
|
*
|
|
from denylist
|
|
inner join nintendo_network_accounts on other = nintendo_network_accounts.pid
|
|
where initiator = $1
|
|
",
|
|
self.pid
|
|
)
|
|
.fetch_all(get_db())
|
|
.await
|
|
.map(|v| {
|
|
v.into_iter()
|
|
.map(|v| BlacklistedPrincipal {
|
|
basic_info: basic_principal_from_record!(v),
|
|
since: KerberosDateTime::from_naive(v.since),
|
|
..Default::default()
|
|
})
|
|
.collect::<Vec<_>>()
|
|
}) else {
|
|
println!("error whilest getting friend requests");
|
|
return Err(ErrorCode::Core_SystemError);
|
|
};
|
|
|
|
self.fm
|
|
.users
|
|
.write()
|
|
.await
|
|
.insert(self.pid, self.this.clone());
|
|
|
|
<Self as FriendsWiiU>::update_presence(self, presence).await?;
|
|
dbg!(Ok((
|
|
PrincipalPreference {
|
|
data: Data {},
|
|
block_friend_request: query.principal_preference_block_friend_requests,
|
|
show_online: query.principal_preference_show_online,
|
|
show_playing_title: query.principal_preference_show_currently_playing_title,
|
|
},
|
|
Comment {
|
|
data: Data {},
|
|
last_changed: KerberosDateTime::from_naive(query.comment_lastchanged),
|
|
message: query.comment_message,
|
|
unk: (bytemuck::cast::<_, u16>(query.comment_unk) & 0xFF) as u8,
|
|
},
|
|
friends,
|
|
outgoing_friend_requests,
|
|
incoming_friend_requests,
|
|
// todo: blacklisted principals
|
|
denylist,
|
|
false,
|
|
// todo: persistent notifications
|
|
vec![],
|
|
false,
|
|
)))
|
|
}
|
|
|
|
async fn add_friend(&self, friend: PID) -> Result<(FriendRequest, FriendInfo), ErrorCode> {
|
|
self.add_friend_request(
|
|
friend,
|
|
0,
|
|
"".into(),
|
|
0,
|
|
"".into(),
|
|
GameKey::default(),
|
|
KerberosDateTime::now(),
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn add_friend_by_name(
|
|
&self,
|
|
name: String,
|
|
) -> Result<(FriendRequest, FriendInfo), ErrorCode> {
|
|
let Ok(pid) = query!(
|
|
"select pid from nintendo_network_accounts where nnid = $1",
|
|
name
|
|
)
|
|
.fetch_optional(get_db())
|
|
.await
|
|
else {
|
|
println!("db error when trying to look up nnid");
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
|
|
match pid {
|
|
Some(pid) => self.add_friend(pid.pid).await,
|
|
None => Err(ErrorCode::FPD_InvalidAccount),
|
|
}
|
|
}
|
|
|
|
async fn remove_friend(&self, friend: PID) -> Result<(), ErrorCode> {
|
|
let Ok(query) = query!(
|
|
"delete from friendships where (pid_a = $1 AND pid_b = $2) OR (pid_a = $2 AND pid_b = $1)",
|
|
self.pid,
|
|
friend
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let mut friends = self.friend_pids.write().await;
|
|
friends.retain(|v| *v != friend);
|
|
drop(friends);
|
|
let mut friends = self.maybe_remote_friend.write().await;
|
|
friends.remove(&friend);
|
|
drop(friends);
|
|
|
|
let users = self.fm.users.read().await;
|
|
if let Some(user) = users.get(&friend).and_then(|v| v.upgrade()) {
|
|
drop(users);
|
|
let mut friends = user.friend_pids.write().await;
|
|
friends.retain(|v| *v != self.pid);
|
|
drop(friends);
|
|
let mut friends = user.maybe_remote_friend.write().await;
|
|
friends.remove(&self.pid);
|
|
drop(friends);
|
|
|
|
user.remote
|
|
.process_nintendo_notification_event_1(NintendoNotificationEvent {
|
|
event_type: 26,
|
|
sender: self.pid,
|
|
data: Any::new(&NintendoNotificationEventGeneral {
|
|
param1: bytemuck::cast(self.pid),
|
|
..Default::default()
|
|
})
|
|
.expect("type error"),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
let mut friends = self.friend_pids.write().await;
|
|
friends.retain(|v| *v != friend);
|
|
drop(friends);
|
|
let mut friends = self.maybe_remote_friend.write().await;
|
|
friends.remove(&friend);
|
|
drop(friends);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn add_friend_request(
|
|
&self,
|
|
friend: PID,
|
|
mut unk1: u8,
|
|
message: String,
|
|
mut unk2: u8,
|
|
unk3: String,
|
|
game_key: GameKey,
|
|
unk4: KerberosDateTime,
|
|
) -> Result<(FriendRequest, FriendInfo), ErrorCode> {
|
|
unk1 = 0;
|
|
unk2 = 1;
|
|
|
|
if self.fm.denies_friend_requests(friend).await? {
|
|
return Err(ErrorCode::FPD_FriendRequestNotAllowed);
|
|
}
|
|
|
|
if friend == 99 {
|
|
query!(
|
|
"insert into friendships (pid_a, pid_b) values(99, $1)",
|
|
self.pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
.ok();
|
|
|
|
let Ok(v) = query!("select * from nintendo_network_accounts where pid = 99")
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
let Some(this) = self.this.upgrade() else {
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
|
|
let pid = self.pid;
|
|
|
|
spawn(async move {
|
|
sleep(Duration::from_secs(5)).await;
|
|
|
|
let mut friends = this.friend_pids.write().await;
|
|
friends.push(99);
|
|
drop(friends);
|
|
|
|
let Ok(r) = query!("select * from nintendo_network_accounts where pid = 99")
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
return;
|
|
};
|
|
let data = Any::new(&FriendInfo {
|
|
data: Data {},
|
|
nna_info: nna_info_from_record!(r),
|
|
became_friends: KerberosDateTime::now(),
|
|
comment: Comment {
|
|
data: Data {},
|
|
last_changed: KerberosDateTime::from_naive(r.comment_lastchanged),
|
|
message: r.comment_message,
|
|
unk: (bytemuck::cast::<_, u16>(r.comment_unk) & 0xFF) as u8,
|
|
},
|
|
last_online: KerberosDateTime::now(),
|
|
presence: NintendoPresenceV2::default(),
|
|
unk: 0,
|
|
})
|
|
.expect("type error");
|
|
this.remote
|
|
.process_nintendo_notification_event_1(NintendoNotificationEvent {
|
|
event_type: 30,
|
|
sender: pid,
|
|
data: data,
|
|
})
|
|
.await;
|
|
});
|
|
|
|
return Ok((
|
|
FriendRequest {
|
|
basic_info: basic_principal_from_record!(v),
|
|
request_message: FriendRequestMessage {
|
|
data: Data {},
|
|
friend_request_id: i64::MAX,
|
|
is_recieved: true,
|
|
unk: 0,
|
|
message: message,
|
|
unk2: 1,
|
|
unk3: "Dummy".into(),
|
|
game_key,
|
|
unk4: KerberosDateTime::now(),
|
|
expires_on: KerberosDateTime::now(),
|
|
},
|
|
data: Data {},
|
|
sent_on: KerberosDateTime::now(),
|
|
},
|
|
FriendInfo {
|
|
became_friends: KerberosDateTime::now(),
|
|
comment: Comment {
|
|
data: Data {},
|
|
unk: bytemuck::cast::<_, u16>(v.comment_unk & 0xFF) as u8,
|
|
message: v.comment_message,
|
|
last_changed: KerberosDateTime::from_naive(v.comment_lastchanged),
|
|
},
|
|
last_online: KerberosDateTime::now(),
|
|
nna_info: nna_info_from_record!(v),
|
|
..Default::default()
|
|
},
|
|
));
|
|
}
|
|
|
|
let Ok(q) = query!(
|
|
"select * from denylist where initiator = $1 and other = $2",
|
|
friend,
|
|
self.pid
|
|
)
|
|
.fetch_optional(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::Authentication_AccountLibraryError);
|
|
};
|
|
|
|
if q.is_some() {
|
|
return Err(ErrorCode::FPD_FriendRequestBlocked);
|
|
}
|
|
|
|
// check for too many friend requests both ways
|
|
let Ok(query) = query!(
|
|
"select count(recipient) from friend_requests where sender = $1",
|
|
self.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
println!("friend request count check failed to execute on database");
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
if query.count.is_none_or(|v| v >= 100) {
|
|
return Err(ErrorCode::FPD_RequestLimitExceed);
|
|
}
|
|
let Ok(query) = query!(
|
|
"select count(recipient) from friend_requests where recipient = $1",
|
|
friend
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
println!("friend request count check failed to execute on database");
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
if query.count.is_none_or(|v| v >= 100) {
|
|
return Err(ErrorCode::FPD_RequestLimitExceed);
|
|
}
|
|
|
|
let unks_1 = smoosh_to_i16(unk1, unk2);
|
|
let query = query!(
|
|
"with fr_base as (insert into friend_requests
|
|
(sender, recipient, message, unks_1, unk_2, unk_3, game_key_tid, game_key_version)
|
|
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
returning *)
|
|
select *
|
|
from fr_base
|
|
inner join nintendo_network_accounts
|
|
on recipient=pid
|
|
",
|
|
//inner join on recipient = pid
|
|
self.pid,
|
|
friend,
|
|
message,
|
|
unks_1,
|
|
unk3,
|
|
bytemuck::cast::<_, i64>(unk4.0),
|
|
game_key.tid,
|
|
game_key.version
|
|
)
|
|
.fetch_one(get_db())
|
|
.await;
|
|
|
|
let query = match query {
|
|
Ok(q) => q,
|
|
Err(e) => {
|
|
if let Some(e) = e.into_database_error() {
|
|
match e.kind() {
|
|
sqlx::error::ErrorKind::UniqueViolation => {
|
|
return Err(ErrorCode::FPD_AddFriendProhibited);
|
|
}
|
|
sqlx::error::ErrorKind::ForeignKeyViolation => {
|
|
return Err(ErrorCode::FPD_NotNetworkAccount);
|
|
}
|
|
sqlx::error::ErrorKind::CheckViolation => {
|
|
return Err(ErrorCode::FPD_FriendRequestNotAllowed);
|
|
}
|
|
_ => {
|
|
println!("unknown db error occurred");
|
|
return Err(ErrorCode::Core_Exception);
|
|
}
|
|
}
|
|
} else {
|
|
println!("unknown db error occurred");
|
|
return Err(ErrorCode::Core_Exception);
|
|
}
|
|
}
|
|
};
|
|
|
|
let fr = friend_request_from_record!(query);
|
|
|
|
let users = self.fm.users.read().await;
|
|
if let Some(user) = users.get(&friend).and_then(|v| v.upgrade()) {
|
|
let mut fr = fr.clone();
|
|
|
|
let Ok(query) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
self.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
println!("failed to acquire account info after adding friend");
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
fr.basic_info = basic_principal_from_record!(query);
|
|
|
|
user.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 27,
|
|
sender: self.pid,
|
|
data: Any::new(&fr).expect("type check failed"),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
dbg!(Ok((
|
|
fr,
|
|
FriendInfo {
|
|
presence: NintendoPresenceV2 {
|
|
game_key,
|
|
app_data: vec![0x00],
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
},
|
|
)))
|
|
}
|
|
|
|
async fn cancel_friend_request(&self, id: u64) -> Result<(), ErrorCode> {
|
|
let Ok(query) = query!(
|
|
"delete from friend_requests where id = $1 and sender = $2 returning recipient",
|
|
bytemuck::cast::<_, i64>(id),
|
|
self.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let users = self.fm.users.read().await;
|
|
if let Some(user) = users.get(&query.recipient).and_then(|v| v.upgrade()) {
|
|
drop(users);
|
|
|
|
user.remote
|
|
.process_nintendo_notification_event_1(NintendoNotificationEvent {
|
|
event_type: 26,
|
|
sender: self.pid,
|
|
data: Any::new(&NintendoNotificationEventGeneral {
|
|
param1: bytemuck::cast(self.pid),
|
|
..Default::default()
|
|
})
|
|
.expect("type error"),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn accept_friend_request(&self, id: u64) -> Result<FriendInfo, ErrorCode> {
|
|
let Ok(mut tx) = get_db().begin().await else {
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
let Ok(query) = query!(
|
|
"delete from friend_requests where id = $1 and recipient = $2 returning recipient, sender",
|
|
bytemuck::cast::<_, i64>(id),
|
|
self.pid
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let sender = query.sender;
|
|
|
|
match query!(
|
|
"insert into friendships (pid_a, pid_b) values ($1, $2)",
|
|
query.recipient,
|
|
query.sender
|
|
)
|
|
.execute(&mut *tx)
|
|
.await
|
|
{
|
|
Ok(_) => {}
|
|
Err(e) => return Err(ErrorCode::FPD_FriendAlreadyAdded),
|
|
};
|
|
|
|
let Ok(query) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
query.sender
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
else {
|
|
println!("failed to acquire account info after adding friend");
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let mut presence = NintendoPresenceV2::default();
|
|
|
|
let users = self.fm.users.read().await;
|
|
let user = users.get(&sender).cloned();
|
|
drop(users);
|
|
if let Some(user) = user {
|
|
if let Some(user) = user.upgrade() {
|
|
let mut friends = user.friend_pids.write().await;
|
|
friends.push(self.pid);
|
|
drop(friends);
|
|
let mut friends = user.maybe_remote_friend.write().await;
|
|
friends.insert(self.pid, self.this.clone());
|
|
drop(friends);
|
|
|
|
let Ok(r) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
self.pid
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
else {
|
|
println!("internal server error whilest getting nna info");
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
let data = Any::new(&FriendInfo {
|
|
data: Data {},
|
|
nna_info: nna_info_from_record!(r),
|
|
became_friends: KerberosDateTime::now(),
|
|
comment: Comment {
|
|
data: Data {},
|
|
last_changed: KerberosDateTime::from_naive(r.comment_lastchanged),
|
|
message: r.comment_message,
|
|
unk: (bytemuck::cast::<_, u16>(r.comment_unk) & 0xFF) as u8,
|
|
},
|
|
last_online: KerberosDateTime::now(),
|
|
presence: self.presence.read().await.clone().unwrap_or_default(),
|
|
unk: 0,
|
|
})
|
|
.expect("type error");
|
|
user.remote
|
|
.process_nintendo_notification_event_1(NintendoNotificationEvent {
|
|
event_type: 30,
|
|
sender: self.pid,
|
|
data: data,
|
|
})
|
|
.await;
|
|
let online_presence = user.presence.read().await;
|
|
if let Some(online_presence) = online_presence.as_ref() {
|
|
presence = online_presence.clone();
|
|
} else {
|
|
println!(
|
|
"internal server error, user is somehow online and in the friends manager yet has not set their presence yet..."
|
|
);
|
|
}
|
|
drop(online_presence);
|
|
}
|
|
}
|
|
|
|
tx.commit().await.ok();
|
|
|
|
Ok(FriendInfo {
|
|
data: Data {},
|
|
nna_info: nna_info_from_record!(query),
|
|
presence,
|
|
comment: Comment {
|
|
data: Data {},
|
|
last_changed: KerberosDateTime::from_naive(query.comment_lastchanged),
|
|
message: query.comment_message,
|
|
unk: (bytemuck::cast::<_, u16>(query.comment_unk) & 0xFF) as u8,
|
|
},
|
|
became_friends: KerberosDateTime::now(),
|
|
last_online: KerberosDateTime::from_naive(query.last_online),
|
|
unk: 0,
|
|
})
|
|
}
|
|
|
|
async fn delete_friend_request(&self, id: u64) -> Result<(), ErrorCode> {
|
|
let Ok(query) = query!(
|
|
"delete from friend_requests where id = $1 returning sender, recipient",
|
|
bytemuck::cast::<_, i64>(id),
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let other = if query.recipient == self.pid {
|
|
query.sender
|
|
} else if query.sender == self.pid {
|
|
query.recipient
|
|
} else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let users = self.fm.users.read().await;
|
|
if let Some(user) = users.get(&other).and_then(|v| v.upgrade()) {
|
|
drop(users);
|
|
|
|
user.remote
|
|
.process_nintendo_notification_event_1(NintendoNotificationEvent {
|
|
event_type: 26,
|
|
sender: self.pid,
|
|
data: Any::new(&NintendoNotificationEventGeneral {
|
|
param1: bytemuck::cast(self.pid),
|
|
..Default::default()
|
|
})
|
|
.expect("type error"),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn deny_friend_request(&self, id: u64) -> Result<BlacklistedPrincipal, ErrorCode> {
|
|
let Ok(query) = query!(
|
|
"delete from friend_requests where id = $1 and recipient = $2 returning sender, recipient",
|
|
bytemuck::cast::<_, i64>(id),
|
|
self.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let other = if query.recipient == self.pid {
|
|
query.sender
|
|
} else if query.sender == self.pid {
|
|
query.recipient
|
|
} else {
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
if let Err(e) = query!(
|
|
"insert into denylist(initiator, other) VALUES ($1, $2)",
|
|
self.pid,
|
|
query.sender
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
{
|
|
println!("{}", e);
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let users = self.fm.users.read().await;
|
|
if let Some(user) = users.get(&other).and_then(|v| v.upgrade()) {
|
|
drop(users);
|
|
|
|
user.remote
|
|
.process_nintendo_notification_event_1(NintendoNotificationEvent {
|
|
event_type: 26,
|
|
sender: self.pid,
|
|
data: Any::new(&NintendoNotificationEventGeneral {
|
|
param1: bytemuck::cast(self.pid),
|
|
param2: id,
|
|
..Default::default()
|
|
})
|
|
.expect("type error"),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
let Ok(user) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
query.sender
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
println!("attempt to get invalid user which is in friend request");
|
|
return Err(FPD_InvalidArgument);
|
|
};
|
|
|
|
Ok(BlacklistedPrincipal {
|
|
data: Data {},
|
|
basic_info: basic_principal_from_record!(user),
|
|
game_key: GameKey::default(),
|
|
since: KerberosDateTime::now(),
|
|
})
|
|
}
|
|
|
|
async fn mark_friend_requests_as_received(&self, ids: Vec<u64>) -> Result<(), ErrorCode> {
|
|
for id in ids {
|
|
query!(
|
|
"update friend_requests set is_recieved = true where id = $1",
|
|
bytemuck::cast::<_, i64>(id)
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
.ok();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn add_blacklist(
|
|
&self,
|
|
principal: BlacklistedPrincipal,
|
|
) -> Result<BlacklistedPrincipal, ErrorCode> {
|
|
if let Err(e) = query!(
|
|
"insert into denylist(initiator, other) VALUES ($1, $2)",
|
|
self.pid,
|
|
principal.basic_info.pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
{
|
|
println!("{}", e);
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
|
|
let Ok(user) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
principal.basic_info.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
println!("attempt to get invalid user which is in friend request");
|
|
return Err(FPD_InvalidArgument);
|
|
};
|
|
|
|
Ok(BlacklistedPrincipal {
|
|
data: Data {},
|
|
basic_info: basic_principal_from_record!(user),
|
|
game_key: GameKey::default(),
|
|
since: KerberosDateTime::now(),
|
|
})
|
|
}
|
|
|
|
async fn remove_blacklist(&self, id: PID) -> Result<(), ErrorCode> {
|
|
if let Err(e) = query!(
|
|
"delete from denylist where initiator = $1 and other = $2",
|
|
self.pid,
|
|
id
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
{
|
|
println!("{}", e);
|
|
return Err(ErrorCode::FPD_InvalidMessageID);
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_presence(&self, mut presence: NintendoPresenceV2) -> Result<(), ErrorCode> {
|
|
if !query!("select principal_preference_show_currently_playing_title from nintendo_network_accounts where pid = $1", self.pid).fetch_one(get_db()).await.map_err(|_| ErrorCode::FPD_InvalidAccount)?.principal_preference_show_currently_playing_title{
|
|
presence.game_server_id = 0;
|
|
presence.game_key = GameKey::default();
|
|
presence.app_data = vec![];
|
|
}
|
|
if !query!(
|
|
"select principal_preference_show_online from nintendo_network_accounts where pid = $1",
|
|
self.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
.map_err(|_| ErrorCode::FPD_InvalidAccount)?
|
|
.principal_preference_show_online
|
|
{
|
|
presence.is_online = false;
|
|
}
|
|
|
|
presence.is_online = true;
|
|
let data = Any::new(&presence).expect("type error");
|
|
let mut user_presence = self.presence.write().await;
|
|
*user_presence = Some(presence);
|
|
drop(user_presence);
|
|
|
|
let friends = self.maybe_remote_friend.read().await;
|
|
for friend in friends.iter().filter_map(|f| f.1.upgrade()) {
|
|
friend
|
|
.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 24,
|
|
sender: self.pid,
|
|
data: data.clone(),
|
|
})
|
|
.await;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn update_mii(&self, mii: MiiV2) -> Result<KerberosDateTime, ErrorCode> {
|
|
if let Err(e) = query!(
|
|
"
|
|
update nintendo_network_accounts
|
|
set mii_name = $1, mii_unks = $2, mii_ffl_data = $3, mii_unk_datetime = $4
|
|
where pid = $5",
|
|
mii.name.0,
|
|
smoosh_to_i16(mii.unk, mii.unk2),
|
|
mii.mii_data,
|
|
bytemuck::cast::<_, i64>(mii.date_time.0),
|
|
self.pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
{
|
|
println!("internal server error whilest updating mii: {}", e);
|
|
return Err(ErrorCode::Core_Exception);
|
|
}
|
|
|
|
let Ok(r) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
self.pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
println!("internal server error whilest getting nna info");
|
|
return Err(ErrorCode::Core_Exception);
|
|
};
|
|
|
|
let nna_info = nna_info_from_record!(r);
|
|
let data = Any::new(&nna_info).expect("type error");
|
|
|
|
let friends = self.maybe_remote_friend.read().await;
|
|
for friend in friends.iter().filter_map(|f| f.1.upgrade()) {
|
|
friend
|
|
.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 21,
|
|
sender: self.pid,
|
|
data: data.clone(),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
Ok(KerberosDateTime::now())
|
|
}
|
|
|
|
async fn update_comment(&self, comment: Comment) -> Result<KerberosDateTime, ErrorCode> {
|
|
if let Err(e) = query!(
|
|
"
|
|
update nintendo_network_accounts
|
|
set comment_unk = $1, comment_message = $2, comment_lastchanged = now()
|
|
where pid = $3",
|
|
comment.unk as i16,
|
|
comment.message.clone(),
|
|
self.pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
{
|
|
println!("internal server error whilest updating mii: {}", e);
|
|
return Err(ErrorCode::Core_Exception);
|
|
}
|
|
|
|
let data = Any::new(&NintendoNotificationEventGeneral {
|
|
param1: bytemuck::cast(self.pid),
|
|
str_param: comment.message,
|
|
..Default::default()
|
|
})
|
|
.expect("type error");
|
|
let friends = self.maybe_remote_friend.read().await;
|
|
for friend in friends.iter().filter_map(|f| f.1.upgrade()) {
|
|
friend
|
|
.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 21,
|
|
sender: self.pid,
|
|
data: data.clone(),
|
|
})
|
|
.await;
|
|
}
|
|
|
|
Ok(KerberosDateTime::now())
|
|
}
|
|
|
|
async fn update_preference(&self, preference: PrincipalPreference) -> Result<(), ErrorCode> {
|
|
let data = Any::new(&preference).expect("type error");
|
|
|
|
if let Err(e) = query!(
|
|
"
|
|
update nintendo_network_accounts
|
|
set
|
|
principal_preference_show_online = $1, principal_preference_show_currently_playing_title = $2, principal_preference_block_friend_requests = $3
|
|
where pid = $4",
|
|
preference.show_online,
|
|
preference.show_playing_title,
|
|
preference.block_friend_request,
|
|
self.pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
{
|
|
println!("internal server error whilest updating mii: {}", e);
|
|
return Err(ErrorCode::Core_Exception);
|
|
}
|
|
|
|
let mut presence = self.presence.write().await;
|
|
let Some(presence) = presence.as_mut() else {
|
|
return Err(ErrorCode::FPD_InvalidState);
|
|
};
|
|
|
|
if !preference.show_playing_title {
|
|
presence.game_server_id = 0;
|
|
presence.game_key = GameKey::default();
|
|
presence.app_data = vec![];
|
|
}
|
|
query!(
|
|
"update nintendo_network_accounts set last_online = now() where pid = $1",
|
|
self.pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
.ok();
|
|
let friends = self.maybe_remote_friend.read().await;
|
|
for friend in friends.iter().filter_map(|f| f.1.upgrade()) {
|
|
friend
|
|
.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 23,
|
|
sender: self.pid,
|
|
data: data.clone(),
|
|
})
|
|
.await;
|
|
if preference.show_online {
|
|
friend
|
|
.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 24,
|
|
sender: self.pid,
|
|
data: Any::new(presence).expect("type error"),
|
|
})
|
|
.await;
|
|
} else {
|
|
friend
|
|
.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 10,
|
|
sender: self.pid,
|
|
data: Any::new(&NintendoNotificationEventGeneral {
|
|
param3: KerberosDateTime::now().0,
|
|
..Default::default()
|
|
})
|
|
.expect("type error"),
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_basic_info(&self, pids: Vec<PID>) -> Result<Vec<PrincipalBasicInfo>, ErrorCode> {
|
|
let mut principal_infos = Vec::with_capacity(pids.len());
|
|
|
|
for pid in pids {
|
|
println!("looking up pid: {}", pid);
|
|
let Ok(user) = query!(
|
|
"select * from nintendo_network_accounts where pid = $1",
|
|
pid
|
|
)
|
|
.fetch_one(get_db())
|
|
.await
|
|
else {
|
|
return Err(ErrorCode::FPD_NotFriend);
|
|
};
|
|
|
|
principal_infos.push(basic_principal_from_record!(user));
|
|
}
|
|
|
|
Ok(principal_infos)
|
|
}
|
|
|
|
async fn delete_persistent_notification(
|
|
&self,
|
|
notifs: Vec<PersistentNotification>,
|
|
) -> Result<(), ErrorCode> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_setting_status(&self) -> Result<u8, ErrorCode> {
|
|
Ok(0)
|
|
}
|
|
|
|
async fn get_request_block_settings(
|
|
&self,
|
|
pids: Vec<PID>,
|
|
) -> Result<Vec<PrincipalRequestBlockSetting>, ErrorCode> {
|
|
let mut blocked: Vec<_> = Vec::with_capacity(pids.len());
|
|
|
|
for pid in pids {
|
|
let Ok(denylist) = query!(
|
|
"
|
|
select
|
|
since
|
|
from denylist
|
|
where initiator = $1 and other = $2
|
|
",
|
|
pid,
|
|
self.pid
|
|
)
|
|
.fetch_optional(get_db())
|
|
.await
|
|
else {
|
|
println!("error whilest getting friend requests");
|
|
return Err(ErrorCode::Core_SystemError);
|
|
};
|
|
|
|
if denylist.is_some() {
|
|
blocked.push(PrincipalRequestBlockSetting {
|
|
data: Data {},
|
|
pid,
|
|
blocked: true,
|
|
});
|
|
} else {
|
|
blocked.push(PrincipalRequestBlockSetting {
|
|
data: Data {},
|
|
pid,
|
|
blocked: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(blocked)
|
|
}
|
|
}
|
|
|
|
type HMacMd5 = hmac::Hmac<md5::Md5>;
|
|
|
|
impl Secure for FriendsUser {
|
|
async fn register(
|
|
&self,
|
|
station_urls: Vec<StationUrl>,
|
|
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
|
|
let cid = self.fm.next_cid();
|
|
Ok((
|
|
QResult::success(ErrorCode::Core_Unknown),
|
|
cid,
|
|
get_station_urls(&station_urls, self.addr, self.pid, cid).await?[0].clone(),
|
|
))
|
|
}
|
|
async fn register_ex(
|
|
&self,
|
|
station_urls: Vec<StationUrl>,
|
|
_data: Any,
|
|
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
|
|
info!("register");
|
|
self.register(station_urls).await
|
|
}
|
|
async fn replace_url(&self, _target: StationUrl, _dest: StationUrl) -> Result<(), ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
}
|
|
|
|
impl Secure for FriendsGuest {
|
|
async fn register(
|
|
&self,
|
|
station_urls: Vec<StationUrl>,
|
|
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
|
|
let cid = self.fm.next_cid();
|
|
Ok((
|
|
QResult::success(ErrorCode::Core_Unknown),
|
|
cid,
|
|
get_station_urls(&station_urls, self.addr, 100, cid).await?[0].clone(),
|
|
))
|
|
}
|
|
async fn register_ex(
|
|
&self,
|
|
station_urls: Vec<StationUrl>,
|
|
_data: Any,
|
|
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
|
|
info!("register");
|
|
self.register(station_urls).await
|
|
}
|
|
async fn replace_url(&self, _target: StationUrl, _dest: StationUrl) -> Result<(), ErrorCode> {
|
|
Err(ErrorCode::Core_NotImplemented)
|
|
}
|
|
}
|
|
|
|
impl Drop for FriendsUser {
|
|
fn drop(&mut self) {
|
|
let friends = mem::take(&mut self.maybe_remote_friend);
|
|
let users = friends.into_inner();
|
|
let pid = self.pid;
|
|
tokio::spawn(async move {
|
|
for user in users {
|
|
let Some(user) = user.1.upgrade() else {
|
|
continue;
|
|
};
|
|
|
|
user.remote
|
|
.process_nintendo_notification_event_2(NintendoNotificationEvent {
|
|
event_type: 10,
|
|
sender: pid,
|
|
data: Any::new(&NintendoNotificationEventGeneral {
|
|
param3: KerberosDateTime::now().0,
|
|
..Default::default()
|
|
})
|
|
.expect("type error"),
|
|
})
|
|
.await;
|
|
}
|
|
query!(
|
|
"update nintendo_network_accounts set last_online = now() where pid = $1",
|
|
pid
|
|
)
|
|
.execute(get_db())
|
|
.await
|
|
.ok();
|
|
});
|
|
}
|
|
}
|
|
|
|
impl AccountManagement for FriendsGuest {
|
|
async fn nintendo_create_account(
|
|
&self,
|
|
principal_name: String,
|
|
key: String,
|
|
groups: u32,
|
|
email: String,
|
|
auth_data: Any,
|
|
) -> Result<(PID, String), ErrorCode> {
|
|
println!("{}, {}, {}, {}", principal_name, key, groups, email);
|
|
|
|
let nex_token = if let Ok(extra_info) = auth_data.try_get_as::<AccountExtraInfo>() {
|
|
extra_info.nex_token
|
|
} else if let Ok(data) = auth_data.try_get_as::<NintendoCreateAccountData>() {
|
|
data.nex_token
|
|
} else {
|
|
return Err(ErrorCode::Authentication_InvalidParam);
|
|
};
|
|
let (pid, nex_key) = nex_account::decode_nexact_token(&nex_token).map_err(|e| {
|
|
log::error!("failed to decode token: {}", e);
|
|
log::info!("{:?}", nex_token);
|
|
ErrorCode::Authentication_InvalidParam
|
|
})?;
|
|
|
|
//let mac = derive_pid_hmac(data.nna_info.principal_basic_info.pid, &nexkey);
|
|
|
|
let mut client = grpc_client().await.map_err(|e| {
|
|
eprintln!("error occurred: {:?}", e);
|
|
ErrorCode::Core_Unknown
|
|
})?;
|
|
|
|
let new_account: ActCreateInfo = ActCreateInfo {
|
|
principal_name,
|
|
key: nex_key.into(),
|
|
email,
|
|
pid,
|
|
};
|
|
|
|
let nexkey = client
|
|
.create_new_sequential_or_update_and_get_account(new_account)
|
|
.await
|
|
.map_err(|e| ErrorCode::Core_Unknown)?
|
|
.into_inner();
|
|
|
|
if nexkey.key.len() != 16 {
|
|
log::error!("nex key was not 16 bytes long");
|
|
return Err(ErrorCode::Authentication_InvalidParam);
|
|
}
|
|
|
|
let nexkeyarray: [u8; 16] = nexkey.key.try_into().expect("how...?");
|
|
|
|
let mac = derive_pid_hmac(pid, &nexkeyarray);
|
|
|
|
let hex_str = hex::encode(mac);
|
|
|
|
return Ok((pid, hex_str));
|
|
}
|
|
}
|