mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-12 09:48:30 +12:00
1535 lines
62 KiB
C++
1535 lines
62 KiB
C++
#include "iosu_act.h"
|
|
#include "iosu_fpd.h"
|
|
#include "Cemu/nex/nex.h"
|
|
#include "Cemu/nex/nexFriends.h"
|
|
#include "util/helpers/helpers.h"
|
|
#include "config/CemuConfig.h"
|
|
#include "Cafe/CafeSystem.h"
|
|
#include "config/ActiveSettings.h"
|
|
#include "Cemu/napi/napi.h"
|
|
#include "util/helpers/StringHelpers.h"
|
|
#include "Cafe/IOSU/iosu_types_common.h"
|
|
#include "Cafe/IOSU/nn/iosu_nn_service.h"
|
|
|
|
#include "Common/CafeString.h"
|
|
|
|
std::mutex g_friend_notification_mutex;
|
|
std::vector< std::pair<std::string, int> > g_friend_notifications;
|
|
|
|
namespace iosu
|
|
{
|
|
namespace fpd
|
|
{
|
|
using NotificationRunningId = uint64;
|
|
|
|
struct NotificationEntry
|
|
{
|
|
NotificationEntry(uint64 index, NexFriends::NOTIFICATION_TYPE type, uint32 pid) : timestamp(std::chrono::steady_clock::now()), runningId(index), type(type), pid(pid) {}
|
|
std::chrono::steady_clock::time_point timestamp;
|
|
NotificationRunningId runningId;
|
|
NexFriends::NOTIFICATION_TYPE type;
|
|
uint32 pid;
|
|
};
|
|
|
|
class
|
|
{
|
|
public:
|
|
void TrackNotification(NexFriends::NOTIFICATION_TYPE type, uint32 pid)
|
|
{
|
|
std::unique_lock _l(m_mtxNotificationQueue);
|
|
m_notificationQueue.emplace_back(m_notificationQueueIndex++, type, pid);
|
|
}
|
|
|
|
void RemoveExpired()
|
|
{
|
|
// remove entries older than 10 seconds
|
|
std::chrono::steady_clock::time_point expireTime = std::chrono::steady_clock::now() - std::chrono::seconds(10);
|
|
std::erase_if(m_notificationQueue, [expireTime](const auto& notification) {
|
|
return notification.timestamp < expireTime;
|
|
});
|
|
}
|
|
|
|
std::optional<NotificationEntry> GetNextNotification(NotificationRunningId& previousRunningId)
|
|
{
|
|
std::unique_lock _l(m_mtxNotificationQueue);
|
|
auto it = std::lower_bound(m_notificationQueue.begin(), m_notificationQueue.end(), previousRunningId, [](const auto& notification, const auto& runningId) {
|
|
return notification.runningId <= runningId;
|
|
});
|
|
size_t itIndex = it - m_notificationQueue.begin();
|
|
if(it == m_notificationQueue.end())
|
|
return std::nullopt;
|
|
previousRunningId = it->runningId;
|
|
return *it;
|
|
}
|
|
|
|
private:
|
|
std::vector<NotificationEntry> m_notificationQueue;
|
|
std::mutex m_mtxNotificationQueue;
|
|
std::atomic_uint64_t m_notificationQueueIndex{1};
|
|
}g_NotificationQueue;
|
|
|
|
struct
|
|
{
|
|
bool isThreadStarted;
|
|
bool isInitialized2;
|
|
NexFriends* nexFriendSession;
|
|
std::mutex mtxFriendSession;
|
|
// session state
|
|
std::atomic_bool sessionStarted{false};
|
|
// current state
|
|
nexPresenceV2 myPresence;
|
|
}g_fpd = {};
|
|
|
|
void OverlayNotificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid)
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "Friends::Notification {:02x} pid {:08x}", type, pid);
|
|
if(!GetConfig().notification.friends)
|
|
return;
|
|
std::unique_lock lock(g_friend_notification_mutex);
|
|
std::string message;
|
|
if(type == NexFriends::NOTIFICATION_TYPE::NOTIFICATION_TYPE_ONLINE)
|
|
{
|
|
g_friend_notifications.emplace_back("Connected to friend service", 5000);
|
|
if(g_fpd.nexFriendSession && g_fpd.nexFriendSession->getPendingFriendRequestCount() > 0)
|
|
g_friend_notifications.emplace_back(fmt::format("You have {} pending friend request(s)", g_fpd.nexFriendSession->getPendingFriendRequestCount()), 5000);
|
|
}
|
|
else
|
|
{
|
|
std::string msg_format;
|
|
switch(type)
|
|
{
|
|
case NexFriends::NOTIFICATION_TYPE_ONLINE: break;
|
|
case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGIN: msg_format = "{} is now online"; break;
|
|
case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGOFF: msg_format = "{} is now offline"; break;
|
|
case NexFriends::NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE: break;
|
|
case NexFriends::NOTIFICATION_TYPE_ADDED_FRIEND: msg_format = "{} has been added to your friend list"; break;
|
|
case NexFriends::NOTIFICATION_TYPE_REMOVED_FRIEND: msg_format = "{} has been removed from your friend list"; break;
|
|
case NexFriends::NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST: break;
|
|
case NexFriends::NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST: break;
|
|
case NexFriends::NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST: msg_format = "{} wants to add you to his friend list"; break;
|
|
case NexFriends::NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST: break;
|
|
default: ;
|
|
}
|
|
if (!msg_format.empty())
|
|
{
|
|
std::string name = fmt::format("{:#x}", pid);
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
const std::string tmp = g_fpd.nexFriendSession->getAccountNameByPid(pid);
|
|
if (!tmp.empty())
|
|
name = tmp;
|
|
}
|
|
g_friend_notifications.emplace_back(fmt::format(fmt::runtime(msg_format), name), 5000);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid)
|
|
{
|
|
OverlayNotificationHandler(type, pid);
|
|
g_NotificationQueue.TrackNotification(type, pid);
|
|
}
|
|
|
|
void convertMultiByteStringToBigEndianWidechar(const char* input, uint16be* output, sint32 maxOutputLength)
|
|
{
|
|
std::basic_string<uint16be> beStr = StringHelpers::FromUtf8(input);
|
|
if (beStr.size() >= maxOutputLength - 1)
|
|
beStr.resize(maxOutputLength-1);
|
|
for (size_t i = 0; i < beStr.size(); i++)
|
|
output[i] = beStr[i];
|
|
output[beStr.size()] = '\0';
|
|
}
|
|
|
|
void convertFPDTimestampToDate(uint64 timestamp, FPDDate* fpdDate)
|
|
{
|
|
// if the timestamp is zero then still return a valid date
|
|
if (timestamp == 0)
|
|
{
|
|
fpdDate->second = 0;
|
|
fpdDate->minute = 0;
|
|
fpdDate->hour = 0;
|
|
fpdDate->day = 1;
|
|
fpdDate->month = 1;
|
|
fpdDate->year = 1970;
|
|
return;
|
|
}
|
|
fpdDate->second = (uint8)((timestamp) & 0x3F);
|
|
fpdDate->minute = (uint8)((timestamp >> 6) & 0x3F);
|
|
fpdDate->hour = (uint8)((timestamp >> 12) & 0x1F);
|
|
fpdDate->day = (uint8)((timestamp >> 17) & 0x1F);
|
|
fpdDate->month = (uint8)((timestamp >> 22) & 0xF);
|
|
fpdDate->year = (uint16)((timestamp >> 26));
|
|
}
|
|
|
|
uint64 convertDateToFPDTimestamp(FPDDate* fpdDate)
|
|
{
|
|
uint64 t = 0;
|
|
t |= (uint64)fpdDate->second;
|
|
t |= ((uint64)fpdDate->minute<<6);
|
|
t |= ((uint64)fpdDate->hour<<12);
|
|
t |= ((uint64)fpdDate->day<<17);
|
|
t |= ((uint64)fpdDate->month<<22);
|
|
t |= ((uint64)(uint16)fpdDate->year<<26);
|
|
return t;
|
|
}
|
|
|
|
void NexPresenceToGameMode(const nexPresenceV2* presence, GameMode* gameMode)
|
|
{
|
|
memset(gameMode, 0, sizeof(GameMode));
|
|
gameMode->joinFlagMask = presence->joinFlagMask;
|
|
gameMode->matchmakeType = presence->joinAvailability;
|
|
gameMode->joinGameId = presence->gameId;
|
|
gameMode->joinGameMode = presence->gameMode;
|
|
gameMode->hostPid = presence->hostPid;
|
|
gameMode->groupId = presence->groupId;
|
|
memcpy(gameMode->appSpecificData, presence->appSpecificData, 0x14);
|
|
}
|
|
|
|
void GameModeToNexPresence(const GameMode* gameMode, nexPresenceV2* presence)
|
|
{
|
|
*presence = {};
|
|
presence->joinFlagMask = gameMode->joinFlagMask;
|
|
presence->joinAvailability = (uint8)(uint32)gameMode->matchmakeType;
|
|
presence->gameId = gameMode->joinGameId;
|
|
presence->gameMode = gameMode->joinGameMode;
|
|
presence->hostPid = gameMode->hostPid;
|
|
presence->groupId = gameMode->groupId;
|
|
memcpy(presence->appSpecificData, gameMode->appSpecificData, 0x14);
|
|
}
|
|
|
|
void NexFriendToFPDFriendData(const nexFriend* frd, FriendData* friendData)
|
|
{
|
|
memset(friendData, 0, sizeof(FriendData));
|
|
// setup friend data
|
|
friendData->type = 1; // friend
|
|
friendData->pid = frd->nnaInfo.principalInfo.principalId;
|
|
memcpy(friendData->mii, frd->nnaInfo.principalInfo.mii.miiData, FFL_SIZE);
|
|
strcpy((char*)friendData->nnid, frd->nnaInfo.principalInfo.nnid);
|
|
// screenname
|
|
convertMultiByteStringToBigEndianWidechar(frd->nnaInfo.principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be));
|
|
|
|
friendData->friendExtraData.isOnline = frd->presence.isOnline != 0 ? 1 : 0;
|
|
|
|
friendData->friendExtraData.gameKey.titleId = frd->presence.gameKey.titleId;
|
|
friendData->friendExtraData.gameKey.ukn08 = frd->presence.gameKey.ukn;
|
|
NexPresenceToGameMode(&frd->presence, &friendData->friendExtraData.gameMode);
|
|
|
|
auto fixed_presence_msg = '\0' + frd->presence.msg; // avoid first character of comment from being cut off
|
|
friendData->friendExtraData.gameModeDescription.assignFromUTF8(fixed_presence_msg);
|
|
|
|
auto fixed_comment = '\0' + frd->comment.commentString; // avoid first character of comment from being cut off
|
|
friendData->friendExtraData.comment.assignFromUTF8(fixed_comment);
|
|
|
|
// set valid dates
|
|
friendData->uknDate.year = 2018;
|
|
friendData->uknDate.day = 1;
|
|
friendData->uknDate.month = 1;
|
|
friendData->uknDate.hour = 1;
|
|
friendData->uknDate.minute = 1;
|
|
friendData->uknDate.second = 1;
|
|
|
|
friendData->friendExtraData.approvalTime.year = 2018;
|
|
friendData->friendExtraData.approvalTime.day = 1;
|
|
friendData->friendExtraData.approvalTime.month = 1;
|
|
friendData->friendExtraData.approvalTime.hour = 1;
|
|
friendData->friendExtraData.approvalTime.minute = 1;
|
|
friendData->friendExtraData.approvalTime.second = 1;
|
|
|
|
convertFPDTimestampToDate(frd->lastOnlineTimestamp, &friendData->friendExtraData.lastOnline);
|
|
}
|
|
|
|
void NexFriendRequestToFPDFriendData(const nexFriendRequest* frdReq, bool isIncoming, FriendData* friendData)
|
|
{
|
|
memset(friendData, 0, sizeof(FriendData));
|
|
// setup friend data
|
|
friendData->type = 0; // friend request
|
|
friendData->pid = frdReq->principalInfo.principalId;
|
|
memcpy(friendData->mii, frdReq->principalInfo.mii.miiData, FFL_SIZE);
|
|
strcpy((char*)friendData->nnid, frdReq->principalInfo.nnid);
|
|
// screenname
|
|
convertMultiByteStringToBigEndianWidechar(frdReq->principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be));
|
|
|
|
convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendData->requestExtraData.comment, sizeof(friendData->requestExtraData.comment) / sizeof(uint16be));
|
|
|
|
FPDDate expireDate;
|
|
convertFPDTimestampToDate(frdReq->message.expireTimestamp, &expireDate);
|
|
|
|
bool isProvisional = frdReq->message.expireTimestamp == 0;
|
|
|
|
//friendData->requestExtraData.ukn0A8 = 0; // no change?
|
|
//friendData->requestExtraData.ukn0A0 = 0; // if not set -> provisional friend request
|
|
//friendData->requestExtraData.ukn0A4 = isProvisional ? 0 : 123; // no change?
|
|
|
|
friendData->requestExtraData.messageId = frdReq->message.messageId;
|
|
|
|
///* +0x0A8 */ uint8 ukn0A8;
|
|
///* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed)
|
|
///* +0x0AA */ uint16be comment[0x40];
|
|
///* +0x12A */ uint8 ukn12A; // ingame name language? (guessed)
|
|
///* +0x12B */ uint8 _padding12B;
|
|
|
|
// set valid dates
|
|
|
|
friendData->uknDate.year = 2018;
|
|
friendData->uknDate.day = 20;
|
|
friendData->uknDate.month = 4;
|
|
friendData->uknDate.hour = 12;
|
|
friendData->uknDate.minute = 1;
|
|
friendData->uknDate.second = 1;
|
|
|
|
friendData->requestExtraData.uknData0.year = 2018;
|
|
friendData->requestExtraData.uknData0.day = 24;
|
|
friendData->requestExtraData.uknData0.month = 4;
|
|
friendData->requestExtraData.uknData0.hour = 1;
|
|
friendData->requestExtraData.uknData0.minute = 1;
|
|
friendData->requestExtraData.uknData0.second = 1;
|
|
|
|
// this is the date used for 'Expires in'
|
|
convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendData->requestExtraData.uknData1);
|
|
}
|
|
|
|
void NexFriendRequestToFPDFriendRequest(const nexFriendRequest* frdReq, bool isIncoming, FriendRequest* friendRequest)
|
|
{
|
|
memset(friendRequest, 0, sizeof(FriendRequest));
|
|
|
|
friendRequest->pid = frdReq->principalInfo.principalId;
|
|
|
|
strncpy((char*)friendRequest->nnid, frdReq->principalInfo.nnid, sizeof(friendRequest->nnid));
|
|
friendRequest->nnid[sizeof(friendRequest->nnid) - 1] = '\0';
|
|
|
|
memcpy(friendRequest->miiData, frdReq->principalInfo.mii.miiData, sizeof(friendRequest->miiData));
|
|
|
|
convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendRequest->message, sizeof(friendRequest->message) / sizeof(friendRequest->message[0]));
|
|
convertMultiByteStringToBigEndianWidechar(frdReq->principalInfo.mii.miiNickname, friendRequest->screenname, sizeof(friendRequest->screenname) / sizeof(friendRequest->screenname[0]));
|
|
|
|
friendRequest->isMarkedAsReceived = 1;
|
|
|
|
friendRequest->ukn98 = _swapEndianU64(frdReq->message.messageId);
|
|
|
|
convertFPDTimestampToDate(0, &friendRequest->uknDate);
|
|
convertFPDTimestampToDate(0, &friendRequest->uknDate2);
|
|
convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendRequest->expireDate);
|
|
}
|
|
|
|
struct FPProfile
|
|
{
|
|
uint8be country;
|
|
uint8be area;
|
|
uint16be unused;
|
|
};
|
|
static_assert(sizeof(FPProfile) == 4);
|
|
|
|
struct SelfPresence
|
|
{
|
|
uint8be ukn[0x130]; // todo
|
|
};
|
|
static_assert(sizeof(SelfPresence) == 0x130);
|
|
|
|
struct SelfPlayingGame
|
|
{
|
|
uint8be ukn0[0x10];
|
|
};
|
|
static_assert(sizeof(SelfPlayingGame) == 0x10);
|
|
|
|
static const auto FPResult_Ok = 0;
|
|
static const auto FPResult_InvalidIPCParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_FP, 0x680);
|
|
static const auto FPResult_RequestFailed = BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); // figure out proper error code
|
|
static const auto FPResult_Aborted = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_FP, 0x3480);
|
|
|
|
class FPDService : public iosu::nn::IPCSimpleService
|
|
{
|
|
|
|
struct NotificationAsyncRequest
|
|
{
|
|
NotificationAsyncRequest(IPCCommandBody* cmd, uint32 maxNumEntries, FPDNotification* notificationsOut, uint32be* countOut)
|
|
: cmd(cmd), maxNumEntries(maxNumEntries), notificationsOut(notificationsOut), countOut(countOut)
|
|
{
|
|
}
|
|
|
|
IPCCommandBody* cmd;
|
|
uint32 maxNumEntries;
|
|
FPDNotification* notificationsOut;
|
|
uint32be* countOut;
|
|
};
|
|
|
|
struct FPDClient
|
|
{
|
|
bool hasLoggedIn{false};
|
|
uint32 notificationMask{0};
|
|
NotificationRunningId prevRunningId{0};
|
|
std::vector<NotificationAsyncRequest> notificationRequests;
|
|
};
|
|
|
|
// storage for async IPC requests
|
|
std::vector<IPCCommandBody*> m_asyncLoginRequests;
|
|
std::vector<FPDClient*> m_clients;
|
|
|
|
public:
|
|
FPDService() : iosu::nn::IPCSimpleService("/dev/fpd") {}
|
|
|
|
std::string GetThreadName() override
|
|
{
|
|
return "IOSUModule::FPD";
|
|
}
|
|
|
|
void StartService() override
|
|
{
|
|
cemu_assert_debug(m_asyncLoginRequests.empty());
|
|
}
|
|
|
|
void StopService() override
|
|
{
|
|
m_asyncLoginRequests.clear();
|
|
for(auto& it : m_clients)
|
|
delete it;
|
|
m_clients.clear();
|
|
}
|
|
|
|
void* CreateClientObject() override
|
|
{
|
|
FPDClient* client = new FPDClient();
|
|
m_clients.push_back(client);
|
|
return client;
|
|
}
|
|
|
|
void DestroyClientObject(void* clientObject) override
|
|
{
|
|
FPDClient* client = (FPDClient*)clientObject;
|
|
std::erase(m_clients, client);
|
|
delete client;
|
|
}
|
|
|
|
void SendQueuedNotifications(FPDClient* client)
|
|
{
|
|
if (client->notificationRequests.empty())
|
|
return;
|
|
if (client->notificationRequests.size() > 1)
|
|
cemuLog_log(LogType::Force, "FPD: More than one simultanous notification query not supported");
|
|
NotificationAsyncRequest& request = client->notificationRequests[0];
|
|
uint32 numNotifications = 0;
|
|
while(numNotifications < request.maxNumEntries)
|
|
{
|
|
auto notification = g_NotificationQueue.GetNextNotification(client->prevRunningId);
|
|
if (!notification)
|
|
break;
|
|
uint32 flag = 1 << static_cast<uint32>(notification->type);
|
|
if((client->notificationMask & flag) == 0)
|
|
continue;
|
|
request.notificationsOut[numNotifications].type = static_cast<uint32>(notification->type);
|
|
request.notificationsOut[numNotifications].pid = notification->pid;
|
|
numNotifications++;
|
|
}
|
|
if (numNotifications == 0)
|
|
return;
|
|
*request.countOut = numNotifications;
|
|
ServiceCallAsyncRespond(request.cmd, FPResult_Ok);
|
|
client->notificationRequests.erase(client->notificationRequests.begin());
|
|
}
|
|
|
|
void TimerUpdate() override
|
|
{
|
|
// called once a second while service is running
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (!g_fpd.nexFriendSession)
|
|
return;
|
|
g_fpd.nexFriendSession->update();
|
|
while(!m_asyncLoginRequests.empty())
|
|
{
|
|
if(g_fpd.nexFriendSession->isOnline())
|
|
{
|
|
ServiceCallAsyncRespond(m_asyncLoginRequests.front(), FPResult_Ok);
|
|
m_asyncLoginRequests.erase(m_asyncLoginRequests.begin());
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
// handle notification responses
|
|
g_NotificationQueue.RemoveExpired();
|
|
for(auto& client : m_clients)
|
|
SendQueuedNotifications(client);
|
|
}
|
|
|
|
uint32 ServiceCall(void* clientObject, uint32 requestId, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) override
|
|
{
|
|
// for /dev/fpd input and output vectors are swapped
|
|
std::swap(vecIn, vecOut);
|
|
std::swap(numVecIn, numVecOut);
|
|
|
|
FPDClient* fpdClient = (FPDClient*)clientObject;
|
|
switch(static_cast<FPD_REQUEST_ID>(requestId))
|
|
{
|
|
case FPD_REQUEST_ID::SetNotificationMask:
|
|
return CallHandler_SetNotificationMask(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetNotificationAsync:
|
|
return CallHandler_GetNotificationAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::SetLedEventMask:
|
|
cemuLog_logDebug(LogType::Force, "[/dev/fpd] SetLedEventMask is todo");
|
|
return FPResult_Ok;
|
|
case FPD_REQUEST_ID::LoginAsync:
|
|
return CallHandler_LoginAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::HasLoggedIn:
|
|
return CallHandler_HasLoggedIn(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::IsOnline:
|
|
return CallHandler_IsOnline(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyPrincipalId:
|
|
return CallHandler_GetMyPrincipalId(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyAccountId:
|
|
return CallHandler_GetMyAccountId(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyScreenName:
|
|
return CallHandler_GetMyScreenName(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyMii:
|
|
return CallHandler_GetMyMii(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyProfile:
|
|
return CallHandler_GetMyProfile(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyPresence:
|
|
return CallHandler_GetMyPresence(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyComment:
|
|
return CallHandler_GetMyComment(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyPreference:
|
|
return CallHandler_GetMyPreference(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetMyPlayingGame:
|
|
return CallHandler_GetMyPlayingGame(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendAccountId:
|
|
return CallHandler_GetFriendAccountId(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendScreenName:
|
|
return CallHandler_GetFriendScreenName(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendMii:
|
|
return CallHandler_GetFriendMii(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendPresence:
|
|
return CallHandler_GetFriendPresence(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendRelationship:
|
|
return CallHandler_GetFriendRelationship(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendList:
|
|
return CallHandler_GetFriendList_GetFriendListAll(fpdClient, vecIn, numVecIn, vecOut, numVecOut, false);
|
|
case FPD_REQUEST_ID::GetFriendListAll:
|
|
return CallHandler_GetFriendList_GetFriendListAll(fpdClient, vecIn, numVecIn, vecOut, numVecOut, true);
|
|
case FPD_REQUEST_ID::GetFriendRequestList:
|
|
return CallHandler_GetFriendRequestList(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendRequestListEx:
|
|
return CallHandler_GetFriendRequestListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetBlackList:
|
|
return CallHandler_GetBlackList(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetFriendListEx:
|
|
return CallHandler_GetFriendListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::UpdateCommentAsync:
|
|
return CallHandler_UpdateCommentAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::UpdatePreferenceAsync:
|
|
return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync:
|
|
return CallHandler_AddFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::AcceptFriendRequestAsync:
|
|
return CallHandler_AcceptFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::DeleteFriendRequestAsync:
|
|
return CallHandler_DeleteFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::CancelFriendRequestAsync:
|
|
return CallHandler_CancelFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::MarkFriendRequestsAsReceivedAsync:
|
|
return CallHandler_MarkFriendRequestsAsReceivedAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::RemoveFriendAsync:
|
|
return CallHandler_RemoveFriendAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::DeleteFriendFlagsAsync:
|
|
return CallHandler_DeleteFriendFlagsAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetBasicInfoAsync:
|
|
return CallHandler_GetBasicInfoAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::CheckSettingStatusAsync:
|
|
return CallHandler_CheckSettingStatusAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::IsPreferenceValid:
|
|
return CallHandler_IsPreferenceValid(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::GetRequestBlockSettingAsync:
|
|
return CallHandler_GetRequestBlockSettingAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::AddFriendAsyncByPid:
|
|
return CallHandler_AddFriendAsyncByPid(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
case FPD_REQUEST_ID::UpdateGameModeVariation1:
|
|
case FPD_REQUEST_ID::UpdateGameModeVariation2:
|
|
return CallHandler_UpdateGameMode(fpdClient, vecIn, numVecIn, vecOut, numVecOut);
|
|
default:
|
|
cemuLog_log(LogType::Force, "Unsupported service call {} to /dev/fpd", requestId);
|
|
return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0);
|
|
}
|
|
}
|
|
|
|
#define DeclareInputPtr(__Name, __T, __count, __vecIndex) if(sizeof(__T)*(__count) != vecIn[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T* __Name = ((__T*)vecIn[__vecIndex].basePhys.GetPtr())
|
|
#define DeclareInput(__Name, __T, __vecIndex) if(sizeof(__T) != vecIn[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T __Name = *((__T*)vecIn[__vecIndex].basePhys.GetPtr())
|
|
#define DeclareOutputPtr(__Name, __T, __count, __vecIndex) if(sizeof(__T)*(__count) != vecOut[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T* __Name = ((__T*)vecOut[__vecIndex].basePhys.GetPtr())
|
|
|
|
template<typename T>
|
|
static nnResult WriteValueOutput(IPCIoctlVector* vec, const T& value)
|
|
{
|
|
if(vec->size != sizeof(T))
|
|
return FPResult_InvalidIPCParam;
|
|
*(T*)vec->basePhys.GetPtr() = value;
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_SetNotificationMask(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(notificationMask, uint32be, 0);
|
|
fpdClient->notificationMask = notificationMask;
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetNotificationAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 2)
|
|
return FPResult_InvalidIPCParam;
|
|
if((vecOut[0].size % sizeof(FPDNotification)) != 0 || vecOut[0].size < sizeof(FPDNotification))
|
|
{
|
|
cemuLog_log(LogType::Force, "FPD GetNotificationAsync: Unexpected output size");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
DeclareOutputPtr(countOut, uint32be, 1, 1);
|
|
uint32 maxCount = vecOut[0].size / sizeof(FPDNotification);
|
|
DeclareOutputPtr(notificationList, FPDNotification, maxCount, 0);
|
|
fpdClient->notificationRequests.emplace_back(cmd, maxCount, notificationList, countOut);
|
|
SendQueuedNotifications(fpdClient); // if any notifications are queued, send them immediately
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_LoginAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!ActiveSettings::IsOnlineEnabled())
|
|
{
|
|
// not online, fail immediately
|
|
return FPResult_Ok; // Splatoon expects this to always return success otherwise it will softlock. This should be FPResult_Aborted?
|
|
}
|
|
StartFriendSession();
|
|
fpdClient->hasLoggedIn = true;
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
m_asyncLoginRequests.emplace_back(cmd);
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_HasLoggedIn(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
return WriteValueOutput<uint32be>(vecOut, fpdClient->hasLoggedIn ? 1 : 0);
|
|
}
|
|
|
|
nnResult CallHandler_IsOnline(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
bool isOnline = g_fpd.nexFriendSession ? g_fpd.nexFriendSession->isOnline() : false;
|
|
return WriteValueOutput<uint32be>(vecOut, isOnline?1:0);
|
|
}
|
|
|
|
nnResult CallHandler_GetMyPrincipalId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
uint8 slot = iosu::act::getCurrentAccountSlot();
|
|
uint32 pid = 0;
|
|
iosu::act::getPrincipalId(slot, &pid);
|
|
return WriteValueOutput<uint32be>(vecOut, pid);
|
|
}
|
|
|
|
nnResult CallHandler_GetMyAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
uint8 slot = iosu::act::getCurrentAccountSlot();
|
|
std::string accountId = iosu::act::getAccountId2(slot);
|
|
if(vecOut->size != ACT_ACCOUNTID_LENGTH)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyAccountId: Unexpected output size");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
if(accountId.length() > ACT_ACCOUNTID_LENGTH-1)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyAccountId: AccountID is too long");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
if(accountId.empty())
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyAccountId: AccountID is empty");
|
|
return FPResult_InvalidIPCParam; // should return 0xC0C00800 ?
|
|
}
|
|
char* outputStr = (char*)vecOut->basePhys.GetPtr();
|
|
memset(outputStr, 0, ACT_ACCOUNTID_LENGTH);
|
|
memcpy(outputStr, accountId.data(), accountId.length());
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetMyScreenName(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
uint8 slot = iosu::act::getCurrentAccountSlot();
|
|
if(vecOut->size != ACT_NICKNAME_SIZE*sizeof(uint16be))
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyScreenName: Unexpected output size");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
uint16 screenname[ACT_NICKNAME_SIZE]{0};
|
|
bool r = iosu::act::getScreenname(slot, screenname);
|
|
if (!r)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyScreenName: Screenname is empty");
|
|
return FPResult_InvalidIPCParam; // should return 0xC0C00800 ?
|
|
}
|
|
uint16be* outputStr = (uint16be*)vecOut->basePhys.GetPtr();
|
|
for(sint32 i = 0; i < ACT_NICKNAME_SIZE; i++)
|
|
outputStr[i] = screenname[i];
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetMyMii(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
uint8 slot = iosu::act::getCurrentAccountSlot();
|
|
if(vecOut->size != FFL_SIZE)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyMii: Unexpected output size");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
bool r = iosu::act::getMii(slot, (FFLData_t*)vecOut->basePhys.GetPtr());
|
|
if (!r)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyMii: Mii is empty");
|
|
return FPResult_InvalidIPCParam; // should return 0xC0C00800 ?
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetMyProfile(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
uint8 slot = iosu::act::getCurrentAccountSlot();
|
|
FPProfile profile{0};
|
|
// todo
|
|
cemuLog_log(LogType::Force, "GetMyProfile is todo");
|
|
return WriteValueOutput<FPProfile>(vecOut, profile);
|
|
}
|
|
|
|
nnResult CallHandler_GetMyPresence(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
uint8 slot = iosu::act::getCurrentAccountSlot();
|
|
SelfPresence selfPresence{0};
|
|
cemuLog_log(LogType::Force, "GetMyPresence is todo");
|
|
return WriteValueOutput<SelfPresence>(vecOut, selfPresence);
|
|
}
|
|
|
|
nnResult CallHandler_GetMyComment(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
std::basic_string<uint16be> myComment;
|
|
if(g_fpd.nexFriendSession)
|
|
{
|
|
if(vecOut->size != MY_COMMENT_LENGTH * sizeof(uint16be))
|
|
{
|
|
cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
nexComment myNexComment;
|
|
g_fpd.nexFriendSession->getMyComment(myNexComment);
|
|
myComment = StringHelpers::FromUtf8(myNexComment.commentString);
|
|
}
|
|
myComment.insert(0, 1, '\0');
|
|
memcpy(vecOut->basePhys.GetPtr(), myComment.c_str(), MY_COMMENT_LENGTH * sizeof(uint16be));
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
FPDPreference selfPreference{0};
|
|
if(g_fpd.nexFriendSession)
|
|
{
|
|
nexPrincipalPreference nexPreference;
|
|
g_fpd.nexFriendSession->getMyPreference(nexPreference);
|
|
selfPreference.showOnline = nexPreference.showOnline;
|
|
selfPreference.showGame = nexPreference.showGame;
|
|
selfPreference.blockFriendRequests = nexPreference.blockFriendRequests;
|
|
selfPreference.ukn = 0;
|
|
}
|
|
else
|
|
memset(&selfPreference, 0, sizeof(FPDPreference));
|
|
return WriteValueOutput<FPDPreference>(vecOut, selfPreference);
|
|
}
|
|
|
|
nnResult CallHandler_GetMyPlayingGame(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
GameKey selfPlayingGame
|
|
{
|
|
CafeSystem::GetForegroundTitleId(),
|
|
CafeSystem::GetForegroundTitleVersion(),
|
|
{0,0,0,0,0,0}
|
|
};
|
|
if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) != 0x00050000)
|
|
{
|
|
selfPlayingGame.titleId = 0;
|
|
selfPlayingGame.ukn08 = 0;
|
|
}
|
|
return WriteValueOutput<GameKey>(vecOut, selfPlayingGame);
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
// todo - online check
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, uint32be, count, 0);
|
|
DeclareOutputPtr(accountId, CafeString<ACT_ACCOUNTID_LENGTH>, count, 0);
|
|
memset(accountId, 0, ACT_ACCOUNTID_LENGTH * count);
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
for (uint32 i = 0; i < count; i++)
|
|
{
|
|
const uint32 pid = pidList[i];
|
|
auto& nnidOutput = accountId[i];
|
|
nexFriend frd;
|
|
nexFriendRequest frdReq;
|
|
if (g_fpd.nexFriendSession->getFriendByPID(frd, pid))
|
|
{
|
|
nnidOutput.assign(frd.nnaInfo.principalInfo.nnid);
|
|
continue;
|
|
}
|
|
bool incoming = false;
|
|
if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid))
|
|
{
|
|
nnidOutput.assign(frdReq.principalInfo.nnid);
|
|
continue;
|
|
}
|
|
cemuLog_log(LogType::Force, "GetFriendAccountId: PID {} not found", pid);
|
|
}
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendScreenName(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
static_assert(sizeof(CafeWideString<ACT_NICKNAME_SIZE>) == 11*2);
|
|
if(numVecIn != 3 || numVecOut != 2)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, uint32be, count, 0);
|
|
DeclareInput(replaceNonAscii, uint8be, 2);
|
|
DeclareOutputPtr(nameList, CafeWideString<ACT_NICKNAME_SIZE>, count, 0);
|
|
uint8be* languageList = nullptr;
|
|
if(vecOut[1].size > 0) // languageList is optional
|
|
{
|
|
DeclareOutputPtr(_languageList, uint8be, count, 1);
|
|
languageList = _languageList;
|
|
}
|
|
memset(nameList, 0, ACT_NICKNAME_SIZE * sizeof(CafeWideString<ACT_NICKNAME_SIZE>));
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
for (uint32 i = 0; i < count; i++)
|
|
{
|
|
const uint32 pid = pidList[i];
|
|
CafeWideString<ACT_NICKNAME_SIZE>& screennameOutput = nameList[i];
|
|
if (languageList)
|
|
languageList[i] = 0; // unknown
|
|
nexFriend frd;
|
|
nexFriendRequest frdReq;
|
|
if (g_fpd.nexFriendSession->getFriendByPID(frd, pid))
|
|
{
|
|
screennameOutput.assignFromUTF8(frd.nnaInfo.principalInfo.mii.miiNickname);
|
|
if (languageList)
|
|
languageList[i] = frd.nnaInfo.principalInfo.regionGuessed;
|
|
continue;
|
|
}
|
|
bool incoming = false;
|
|
if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid))
|
|
{
|
|
screennameOutput.assignFromUTF8(frdReq.principalInfo.mii.miiNickname);
|
|
if (languageList)
|
|
languageList[i] = frdReq.principalInfo.regionGuessed;
|
|
continue;
|
|
}
|
|
cemuLog_log(LogType::Force, "GetFriendScreenName: PID {} not found", pid);
|
|
}
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendMii(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, uint32be, count, 0);
|
|
DeclareOutputPtr(miiList, FFLData_t, count, 0);
|
|
memset(miiList, 0, sizeof(FFLData_t) * count);
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
for (uint32 i = 0; i < count; i++)
|
|
{
|
|
const uint32 pid = pidList[i];
|
|
FFLData_t& miiOutput = miiList[i];
|
|
nexFriend frd;
|
|
nexFriendRequest frdReq;
|
|
if (g_fpd.nexFriendSession->getFriendByPID(frd, pid))
|
|
{
|
|
memcpy(&miiOutput, frd.nnaInfo.principalInfo.mii.miiData, FFL_SIZE);
|
|
continue;
|
|
}
|
|
bool incoming = false;
|
|
if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid))
|
|
{
|
|
memcpy(&miiOutput, frdReq.principalInfo.mii.miiData, FFL_SIZE);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendPresence(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, uint32be, count, 0);
|
|
DeclareOutputPtr(presenceList, FriendPresence, count, 0);
|
|
memset(presenceList, 0, sizeof(FriendPresence) * count);
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
for (uint32 i = 0; i < count; i++)
|
|
{
|
|
FriendPresence& presenceOutput = presenceList[i];
|
|
const uint32 pid = pidList[i];
|
|
nexFriend frd;
|
|
if (g_fpd.nexFriendSession->getFriendByPID(frd, pid))
|
|
{
|
|
presenceOutput.isOnline = frd.presence.isOnline ? 1 : 0;
|
|
presenceOutput.isValid = 1;
|
|
// todo - region and subregion
|
|
presenceOutput.gameMode.joinFlagMask = frd.presence.joinFlagMask;
|
|
presenceOutput.gameMode.matchmakeType = frd.presence.joinAvailability;
|
|
presenceOutput.gameMode.joinGameId = frd.presence.gameId;
|
|
presenceOutput.gameMode.joinGameMode = frd.presence.gameMode;
|
|
presenceOutput.gameMode.hostPid = frd.presence.hostPid;
|
|
presenceOutput.gameMode.groupId = frd.presence.groupId;
|
|
|
|
memcpy(presenceOutput.gameMode.appSpecificData, frd.presence.appSpecificData, 0x14);
|
|
}
|
|
else
|
|
{
|
|
cemuLog_log(LogType::Force, "GetFriendPresence: PID {} not found", pid);
|
|
}
|
|
}
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendRelationship(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if(numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
// todo - check for valid session (same for all GetFriend* functions)
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, uint32be, count, 0);
|
|
DeclareOutputPtr(relationshipList, uint8be, count, 0); // correct?
|
|
for(uint32 i=0; i<count; i++)
|
|
relationshipList[i] = RELATIONSHIP_INVALID;
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
for (uint32 i = 0; i < count; i++)
|
|
{
|
|
const uint32 pid = pidList[i];
|
|
uint8be& relationshipOutput = relationshipList[i];
|
|
nexFriend frd;
|
|
nexFriendRequest frdReq;
|
|
bool incoming;
|
|
if (g_fpd.nexFriendSession->getFriendByPID(frd, pid))
|
|
{
|
|
relationshipOutput = RELATIONSHIP_FRIEND;
|
|
continue;
|
|
}
|
|
else if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid))
|
|
{
|
|
if (incoming)
|
|
relationshipOutput = RELATIONSHIP_FRIENDREQUEST_IN;
|
|
else
|
|
relationshipOutput = RELATIONSHIP_FRIENDREQUEST_OUT;
|
|
}
|
|
}
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendList_GetFriendListAll(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut, bool isAll)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if(numVecIn != 2 || numVecOut != 2)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(startIndex, uint32be, 0);
|
|
DeclareInput(maxCount, uint32be, 1);
|
|
if (maxCount * sizeof(FriendPID) != vecOut[0].size || vecOut[0].basePhys.IsNull())
|
|
{
|
|
cemuLog_log(LogType::Force, "GetFriendListAll: pid list buffer size is incorrect");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
if (!g_fpd.nexFriendSession)
|
|
return WriteValueOutput<uint32be>(vecOut+1, 0);
|
|
betype<FriendPID>* pidList = (betype<FriendPID>*)vecOut[0].basePhys.GetPtr();
|
|
std::vector<FriendPID> temporaryPidList;
|
|
temporaryPidList.resize(std::min<size_t>(maxCount, 500));
|
|
uint32 pidCount = 0;
|
|
g_fpd.nexFriendSession->getFriendPIDs(temporaryPidList.data(), &pidCount, startIndex, temporaryPidList.size(), isAll);
|
|
std::copy(temporaryPidList.begin(), temporaryPidList.begin() + pidCount, pidList);
|
|
return WriteValueOutput<uint32be>(vecOut+1, pidCount);
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendRequestList(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if(numVecIn != 2 || numVecOut != 2)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(startIndex, uint32be, 0);
|
|
DeclareInput(maxCount, uint32be, 1);
|
|
if(maxCount * sizeof(FriendPID) != vecOut[0].size || vecOut[0].basePhys.IsNull())
|
|
{
|
|
cemuLog_log(LogType::Force, "GetFriendRequestList: pid list buffer size is incorrect");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
if (!g_fpd.nexFriendSession)
|
|
return WriteValueOutput<uint32be>(vecOut+1, 0);
|
|
betype<FriendPID>* pidList = (betype<FriendPID>*)vecOut[0].basePhys.GetPtr();
|
|
std::vector<FriendPID> temporaryPidList;
|
|
temporaryPidList.resize(std::min<size_t>(maxCount, 500));
|
|
uint32 pidCount = 0;
|
|
g_fpd.nexFriendSession->getFriendRequestPIDs(temporaryPidList.data(), &pidCount, startIndex, temporaryPidList.size(), true, false);
|
|
std::copy(temporaryPidList.begin(), temporaryPidList.begin() + pidCount, pidList);
|
|
return WriteValueOutput<uint32be>(vecOut+1, pidCount);
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendRequestListEx(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if(numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, uint32be, count, 0);
|
|
DeclareOutputPtr(friendRequests, FriendRequest, count, 0);
|
|
memset(friendRequests, 0, sizeof(FriendRequest) * count);
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_Ok;
|
|
for(uint32 i=0; i<count; i++)
|
|
{
|
|
nexFriendRequest frdReq;
|
|
bool incoming = false;
|
|
if (!g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pidList[i]))
|
|
{
|
|
cemuLog_log(LogType::Force, "GetFriendRequestListEx: Failed to get friend request");
|
|
return FPResult_RequestFailed;
|
|
}
|
|
NexFriendRequestToFPDFriendRequest(&frdReq, incoming, friendRequests + i);
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_GetBlackList(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if(numVecIn != 2 || numVecOut != 2)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(startIndex, uint32be, 0);
|
|
DeclareInput(maxCount, uint32be, 1);
|
|
if(maxCount * sizeof(FriendPID) != vecOut[0].size)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetBlackList: pid list buffer size is incorrect");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
if (!g_fpd.nexFriendSession)
|
|
return WriteValueOutput<uint32be>(vecOut+1, 0);
|
|
betype<FriendPID>* pidList = (betype<FriendPID>*)vecOut[0].basePhys.GetPtr();
|
|
// todo!
|
|
cemuLog_logDebug(LogType::Force, "GetBlackList is todo");
|
|
uint32 countOut = 0;
|
|
|
|
return WriteValueOutput<uint32be>(vecOut+1, countOut);
|
|
}
|
|
|
|
nnResult CallHandler_GetFriendListEx(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if(numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, betype<FriendPID>, count, 0);
|
|
if(count * sizeof(FriendPID) != vecIn[0].size)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetFriendListEx: pid input list buffer size is incorrect");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
if(count * sizeof(FriendData) != vecOut[0].size)
|
|
{
|
|
cemuLog_log(LogType::Force, "GetFriendListEx: Friend output list buffer size is incorrect");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
FriendData* friendOutput = (FriendData*)vecOut[0].basePhys.GetPtr();
|
|
memset(friendOutput, 0, sizeof(FriendData) * count);
|
|
if (g_fpd.nexFriendSession)
|
|
{
|
|
for (uint32 i = 0; i < count; i++)
|
|
{
|
|
uint32 pid = pidList[i];
|
|
FriendData* friendData = friendOutput + i;
|
|
nexFriend frd;
|
|
nexFriendRequest frdReq;
|
|
if (g_fpd.nexFriendSession->getFriendByPID(frd, pid))
|
|
{
|
|
NexFriendToFPDFriendData(&frd, friendData);
|
|
continue;
|
|
}
|
|
bool incoming = false;
|
|
if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid))
|
|
{
|
|
NexFriendRequestToFPDFriendData(&frdReq, incoming, friendData);
|
|
continue;
|
|
}
|
|
cemuLog_logDebug(LogType::Force, "GetFriendListEx: Failed to find friend or request with pid {}", pid);
|
|
memset(friendData, 0, sizeof(FriendData));
|
|
}
|
|
}
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
static void NexBasicInfoToBasicInfo(const nexPrincipalBasicInfo& nexBasicInfo, FriendBasicInfo& basicInfo)
|
|
{
|
|
memset(&basicInfo, 0, sizeof(FriendBasicInfo));
|
|
basicInfo.pid = nexBasicInfo.principalId;
|
|
strcpy(basicInfo.nnid, nexBasicInfo.nnid);
|
|
|
|
convertMultiByteStringToBigEndianWidechar(nexBasicInfo.mii.miiNickname, basicInfo.screenname, sizeof(basicInfo.screenname) / sizeof(uint16be));
|
|
memcpy(basicInfo.miiData, nexBasicInfo.mii.miiData, FFL_SIZE);
|
|
|
|
basicInfo.uknDate90.day = 1;
|
|
basicInfo.uknDate90.month = 1;
|
|
basicInfo.uknDate90.hour = 1;
|
|
basicInfo.uknDate90.minute = 1;
|
|
basicInfo.uknDate90.second = 1;
|
|
|
|
// unknown values not set:
|
|
// ukn15
|
|
// ukn2E
|
|
// ukn2F
|
|
}
|
|
|
|
nnResult CallHandler_GetBasicInfoAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidListBE, betype<FriendPID>, count, 0);
|
|
DeclareOutputPtr(basicInfoList, FriendBasicInfo, count, 0);
|
|
if (!g_fpd.nexFriendSession)
|
|
{
|
|
memset(basicInfoList, 0, sizeof(FriendBasicInfo) * sizeof(count));
|
|
return FPResult_Ok;
|
|
}
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
std::vector<uint32> pidList;
|
|
std::copy(pidListBE, pidListBE + count, std::back_inserter(pidList));
|
|
g_fpd.nexFriendSession->requestPrincipleBaseInfoByPID(pidList.data(), count, [cmd, basicInfoList, count](NexFriends::RpcErrorCode result, std::span<nexPrincipalBasicInfo> basicInfo) -> void {
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
cemu_assert_debug(basicInfo.size() == count);
|
|
for(uint32 i = 0; i < count; i++)
|
|
NexBasicInfoToBasicInfo(basicInfo[i], basicInfoList[i]);
|
|
ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_UpdateCommentAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
uint32 messageLength = vecIn[0].size / sizeof(uint16be);
|
|
DeclareInputPtr(newComment, uint16be, messageLength, 0);
|
|
if (messageLength == 0 || newComment[messageLength-1] != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "UpdateCommentAsync: Message must contain at least a null-termination character");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
|
|
auto utf8_comment = StringHelpers::ToUtf8(newComment, messageLength);
|
|
nexComment temporaryComment;
|
|
temporaryComment.ukn0 = 0;
|
|
temporaryComment.commentString = utf8_comment;
|
|
temporaryComment.ukn1 = 0;
|
|
|
|
g_fpd.nexFriendSession->updateCommentAsync(temporaryComment, [cmd](NexFriends::RpcErrorCode result) {
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInputPtr(newPreference, FPDPreference, 1, 0);
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
g_fpd.nexFriendSession->updatePreferencesAsync(nexPrincipalPreference(newPreference->showOnline != 0 ? 1 : 0, newPreference->showGame != 0 ? 1 : 0, newPreference->blockFriendRequests != 0 ? 1 : 0), [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_AddFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 2 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInputPtr(playRecord, RecentPlayRecordEx, 1, 0);
|
|
uint32 msgLength = vecIn[1].size/sizeof(uint16be);
|
|
DeclareInputPtr(msgBE, uint16be, msgLength, 1);
|
|
if(msgLength == 0 || msgBE[msgLength-1] != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "AddFriendRequestAsync: Message must contain at least a null-termination character and end with one");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
std::string msg = StringHelpers::ToUtf8({ msgBE, msgLength-1 });
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
g_fpd.nexFriendSession->addFriendRequest(playRecord->pid, msg.data(), [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_AcceptFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(requestId, uint64be, 0);
|
|
nexFriendRequest frq;
|
|
bool isIncoming;
|
|
if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId))
|
|
return FPResult_RequestFailed;
|
|
if(!isIncoming)
|
|
{
|
|
cemuLog_log(LogType::Force, "AcceptFriendRequestAsync: Trying to accept outgoing friend request");
|
|
return FPResult_RequestFailed;
|
|
}
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
g_fpd.nexFriendSession->acceptFriendRequest(requestId, [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
return ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_DeleteFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
// reject incoming friend request
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(requestId, uint64be, 0);
|
|
nexFriendRequest frq;
|
|
bool isIncoming;
|
|
if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId))
|
|
return FPResult_RequestFailed;
|
|
if(!isIncoming)
|
|
{
|
|
cemuLog_log(LogType::Force, "CancelFriendRequestAsync: Trying to block outgoing friend request");
|
|
return FPResult_RequestFailed;
|
|
}
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
g_fpd.nexFriendSession->deleteFriendRequest(requestId, [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
return ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_CancelFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
// retract outgoing friend request
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(requestId, uint64be, 0);
|
|
nexFriendRequest frq;
|
|
bool isIncoming;
|
|
if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId))
|
|
return FPResult_RequestFailed;
|
|
if(isIncoming)
|
|
{
|
|
cemuLog_log(LogType::Force, "CancelFriendRequestAsync: Trying to cancel incoming friend request");
|
|
return FPResult_RequestFailed;
|
|
}
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
g_fpd.nexFriendSession->removeFriend(frq.principalInfo.principalId, [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
return ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_MarkFriendRequestsAsReceivedAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 2 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(requestIdsBE, uint64be, count, 0);
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
// endian convert
|
|
std::vector<uint64> requestIds;
|
|
std::copy(requestIdsBE, requestIdsBE + count, std::back_inserter(requestIds));
|
|
g_fpd.nexFriendSession->markFriendRequestsAsReceived(requestIds.data(), requestIds.size(), [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
return ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_RemoveFriendAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(pid, uint32be, 0);
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
g_fpd.nexFriendSession->removeFriend(pid, [cmd](NexFriends::RpcErrorCode result){
|
|
if (result != NexFriends::ERR_NONE)
|
|
return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed);
|
|
return ServiceCallAsyncRespond(cmd, FPResult_Ok);
|
|
});
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_DeleteFriendFlagsAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
if (numVecIn != 3 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(pid, uint32be, 0);
|
|
cemuLog_logDebug(LogType::Force, "DeleteFriendFlagsAsync is todo");
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_CheckSettingStatusAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if (numVecIn != 0 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
if (vecOut[0].size != sizeof(uint8be))
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
IPCCommandBody* cmd = ServiceCallDelayCurrentResponse();
|
|
|
|
// for now we respond immediately
|
|
uint8 settingsStatus = 1; // todo - figure out what this status means
|
|
auto r = WriteValueOutput<uint8be>(vecOut, settingsStatus);
|
|
ServiceCallAsyncRespond(cmd, r);
|
|
cemuLog_log(LogType::Force, "CheckSettingStatusAsync is todo");
|
|
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_IsPreferenceValid(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if (numVecIn != 0 || numVecOut != 1)
|
|
return 0;
|
|
if (!g_fpd.nexFriendSession)
|
|
return 0;
|
|
// we currently automatically put the preferences into a valid state on session creation if they are not set yet
|
|
return WriteValueOutput<uint32be>(vecOut, 1); // if we return 0, the friend app will show the first time setup screen
|
|
}
|
|
|
|
nnResult CallHandler_GetRequestBlockSettingAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) // todo
|
|
{
|
|
if (numVecIn != 2 || numVecOut != 1)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(count, uint32be, 1);
|
|
DeclareInputPtr(pidList, betype<FriendPID>, count, 0);
|
|
DeclareOutputPtr(settingList, uint8be, count, 0);
|
|
cemuLog_log(LogType::Force, "GetRequestBlockSettingAsync is todo");
|
|
|
|
for (uint32 i = 0; i < count; i++)
|
|
settingList[i] = 0;
|
|
// implementation is todo. Used by friend list app when adding a friend
|
|
// 0 means not blocked. Friend app will continue with GetBasicInformation()
|
|
// 1 means blocked. Friend app will continue with AddFriendAsync to add the user as a provisional friend
|
|
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_AddFriendAsyncByPid(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if (numVecIn != 1 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInput(pid, uint32be, 0);
|
|
cemuLog_log(LogType::Force, "AddFriendAsyncByPid is todo");
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
nnResult CallHandler_UpdateGameMode(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
|
{
|
|
if (numVecIn != 2 || numVecOut != 0)
|
|
return FPResult_InvalidIPCParam;
|
|
if (!g_fpd.nexFriendSession)
|
|
return FPResult_RequestFailed;
|
|
DeclareInputPtr(gameMode, iosu::fpd::GameMode, 1, 0);
|
|
uint32 messageLength = vecIn[1].size / sizeof(uint16be);
|
|
if(messageLength == 0 || (vecIn[1].size%sizeof(uint16be)) != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "UpdateGameMode: Message must contain at least a null-termination character");
|
|
return FPResult_InvalidIPCParam;
|
|
}
|
|
DeclareInputPtr(gameModeMessage, uint16be, messageLength, 1);
|
|
messageLength--;
|
|
GameModeToNexPresence(gameMode, &g_fpd.myPresence);
|
|
g_fpd.nexFriendSession->updateMyPresence(g_fpd.myPresence);
|
|
// todo - message
|
|
return FPResult_Ok;
|
|
}
|
|
|
|
void StartFriendSession()
|
|
{
|
|
bool expected = false;
|
|
if (!g_fpd.sessionStarted.compare_exchange_strong(expected, true))
|
|
return;
|
|
cemu_assert(!g_fpd.nexFriendSession);
|
|
NAPI::AuthInfo authInfo;
|
|
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
|
|
NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, 0x0005001010001C00, 0x0000, 0x00003200);
|
|
if (!nexTokenResult.isValid())
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "IOSU_FPD: Failed to acquire nex token for friend server");
|
|
g_fpd.myPresence.isOnline = 0;
|
|
return;
|
|
}
|
|
// get values needed for friend session
|
|
uint32 myPid;
|
|
uint8 currentSlot = iosu::act::getCurrentAccountSlot();
|
|
iosu::act::getPrincipalId(currentSlot, &myPid);
|
|
char accountId[256] = { 0 };
|
|
iosu::act::getAccountId(currentSlot, accountId);
|
|
FFLData_t miiData;
|
|
act::getMii(currentSlot, &miiData);
|
|
uint16 screenName[ACT_NICKNAME_LENGTH + 1] = { 0 };
|
|
act::getScreenname(currentSlot, screenName);
|
|
uint32 countryCode = 0;
|
|
act::getCountryIndex(currentSlot, &countryCode);
|
|
// init presence
|
|
g_fpd.myPresence.isOnline = 1;
|
|
if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000)
|
|
{
|
|
g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId();
|
|
g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion();
|
|
}
|
|
else
|
|
{
|
|
g_fpd.myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others
|
|
g_fpd.myPresence.gameKey.ukn = 0;
|
|
}
|
|
// resolve potential domain to IP address
|
|
struct addrinfo hints = {0}, *addrs;
|
|
hints.ai_family = AF_INET;
|
|
const int status = getaddrinfo(nexTokenResult.nexToken.host, NULL, &hints, &addrs);
|
|
if (status != 0)
|
|
{
|
|
cemuLog_log(LogType::Force, "IOSU_FPD: Failed to resolve hostname {}", nexTokenResult.nexToken.host);
|
|
return;
|
|
}
|
|
char addrstr[NI_MAXHOST];
|
|
getnameinfo(addrs->ai_addr, addrs->ai_addrlen, addrstr, sizeof addrstr, NULL, 0, NI_NUMERICHOST);
|
|
cemuLog_log(LogType::Force, "IOSU_FPD: Resolved IP for hostname {}, {}", nexTokenResult.nexToken.host, addrstr);
|
|
// start session
|
|
const uint32_t hostIp = ((struct sockaddr_in*)addrs->ai_addr)->sin_addr.s_addr;
|
|
freeaddrinfo(addrs);
|
|
g_fpd.mtxFriendSession.lock();
|
|
g_fpd.nexFriendSession = new NexFriends(hostIp, nexTokenResult.nexToken.port, "ridfebb9", myPid, nexTokenResult.nexToken.nexPassword, nexTokenResult.nexToken.token, accountId, (uint8*)&miiData, (wchar_t*)screenName, (uint8)countryCode, g_fpd.myPresence);
|
|
g_fpd.nexFriendSession->setNotificationHandler(NotificationHandler);
|
|
g_fpd.mtxFriendSession.unlock();
|
|
cemuLog_log(LogType::Force, "IOSU_FPD: Created friend server session");
|
|
}
|
|
|
|
void StopFriendSession()
|
|
{
|
|
std::unique_lock _l(g_fpd.mtxFriendSession);
|
|
bool expected = true;
|
|
if (!g_fpd.sessionStarted.compare_exchange_strong(expected, false) )
|
|
return;
|
|
delete g_fpd.nexFriendSession;
|
|
g_fpd.nexFriendSession = nullptr;
|
|
}
|
|
|
|
private:
|
|
|
|
|
|
};
|
|
|
|
FPDService gFPDService;
|
|
|
|
class : public ::IOSUModule
|
|
{
|
|
void TitleStart() override
|
|
{
|
|
gFPDService.Start();
|
|
gFPDService.SetTimerUpdate(1000); // call TimerUpdate() once a second
|
|
}
|
|
void TitleStop() override
|
|
{
|
|
gFPDService.StopFriendSession();
|
|
gFPDService.Stop();
|
|
}
|
|
}sIOSUModuleNNFPD;
|
|
|
|
IOSUModule* GetModule()
|
|
{
|
|
return static_cast<IOSUModule*>(&sIOSUModuleNNFPD);
|
|
}
|
|
|
|
}
|
|
}
|
|
|