#include "iosu_ioctl.h" #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/OS/libs/coreinit/coreinit.h" uint32 memory_getVirtualOffsetFromPointer(void* ptr); // remove once we updated everything to MEMPTR SysAllocator _fpdAsyncLoginRetCode; SysAllocator _fpdAsyncAddFriendRetCode; std::mutex g_friend_notification_mutex; std::vector< std::pair > g_friend_notifications; namespace iosu { namespace fpd { struct { bool isThreadStarted; bool isInitialized2; NexFriends* nexFriendSession; // notification handler MPTR notificationFunc; MPTR notificationCustomParam; uint32 notificationMask; // login callback struct { MPTR func; MPTR customParam; }asyncLoginCallback; // current state nexPresenceV2 myPresence; }g_fpd = {}; void notificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) { forceLogDebug_printf("Friends::Notification %02x pid %08x", type, pid); if(GetConfig().notification.friends) { 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(msg_format, name), 5000); } } } if (g_fpd.notificationFunc == MPTR_NULL) return; uint32 notificationFlag = 1 << (type - 1); if ( (notificationFlag&g_fpd.notificationMask) == 0 ) return; coreinitAsyncCallback_add(g_fpd.notificationFunc, 3, type, pid, g_fpd.notificationCustomParam); } void convertMultiByteStringToBigEndianWidechar(const char* input, uint16be* output, sint32 maxOutputLength) { std::basic_string 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_t* 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_t* 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 startFriendSession() { 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()) { // 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; g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); // start session uint32 hostIp; inet_pton(AF_INET, nexTokenResult.nexToken.host, &hostIp); 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); forceLog_printf("IOSU_FPD: Created friend server session"); } else { forceLogDebug_printf("IOSU_FPD: Failed to acquire nex token for friend server"); } } void handleRequest_GetFriendList(iosuFpdCemuRequest_t* fpdCemuRequest, bool getAll) { if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0; fpdCemuRequest->resultU32.u32 = 0; // zero entries returned return; } uint32 temporaryPidList[800]; uint32 pidCount = 0; g_fpd.nexFriendSession->getFriendPIDs(temporaryPidList, &pidCount, fpdCemuRequest->getFriendList.startIndex, std::min(sizeof(temporaryPidList) / sizeof(temporaryPidList[0]), fpdCemuRequest->getFriendList.maxCount), getAll); uint32be* pidListOutput = fpdCemuRequest->getFriendList.pidList.GetPtr(); if (pidListOutput) { for (uint32 i = 0; i < pidCount; i++) pidListOutput[i] = temporaryPidList[i]; } fpdCemuRequest->returnCode = 0; fpdCemuRequest->resultU32.u32 = pidCount; } void handleRequest_GetFriendRequestList(iosuFpdCemuRequest_t* fpdCemuRequest) { if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0; fpdCemuRequest->resultU32.u32 = 0; // zero entries returned return; } uint32 temporaryPidList[800]; uint32 pidCount = 0; // get only incoming friend requests g_fpd.nexFriendSession->getFriendRequestPIDs(temporaryPidList, &pidCount, fpdCemuRequest->getFriendList.startIndex, std::min(sizeof(temporaryPidList) / sizeof(temporaryPidList[0]), fpdCemuRequest->getFriendList.maxCount), true, false); uint32be* pidListOutput = fpdCemuRequest->getFriendList.pidList.GetPtr(); if (pidListOutput) { for (uint32 i = 0; i < pidCount; i++) pidListOutput[i] = temporaryPidList[i]; } fpdCemuRequest->returnCode = 0; fpdCemuRequest->resultU32.u32 = pidCount; } void setFriendDataFromNexFriend(friendData_t* friendData, nexFriend* frd) { memset(friendData, 0, sizeof(friendData_t)); // 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.ukn0E4 = 0; friendData->friendExtraData.isOnline = frd->presence.isOnline != 0 ? 1 : 0; friendData->friendExtraData.gameKeyTitleId = _swapEndianU64(frd->presence.gameKey.titleId); friendData->friendExtraData.gameKeyUkn = frd->presence.gameKey.ukn; friendData->friendExtraData.statusMessage[0] = '\0'; // 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.uknDate218.year = 2018; friendData->friendExtraData.uknDate218.day = 1; friendData->friendExtraData.uknDate218.month = 1; friendData->friendExtraData.uknDate218.hour = 1; friendData->friendExtraData.uknDate218.minute = 1; friendData->friendExtraData.uknDate218.second = 1; convertFPDTimestampToDate(frd->lastOnlineTimestamp, &friendData->friendExtraData.lastOnline); } void setFriendDataFromNexFriendRequest(friendData_t* friendData, nexFriendRequest* frdReq, bool isIncoming) { memset(friendData, 0, sizeof(friendData_t)); // 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_t 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 = _swapEndianU64(frdReq->message.messageId); //find the value for 'markedAsReceived' ///* +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 setFriendRequestFromNexFriendRequest(friendRequest_t* friendRequest, nexFriendRequest* frdReq, bool isIncoming) { memset(friendRequest, 0, sizeof(friendRequest_t)); 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); } void handleRequest_GetFriendListEx(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } for (uint32 i = 0; i < fpdCemuRequest->getFriendListEx.count; i++) { uint32 pid = fpdCemuRequest->getFriendListEx.pidList[i]; friendData_t* friendData = fpdCemuRequest->getFriendListEx.friendData.GetPtr() + i; nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { setFriendDataFromNexFriend(friendData, &frd); continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { setFriendDataFromNexFriendRequest(friendData, &frdReq, incoming); continue; } fpdCemuRequest->returnCode = 0x80000000; return; } } void handleRequest_GetFriendRequestListEx(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } for (uint32 i = 0; i < fpdCemuRequest->getFriendRequestListEx.count; i++) { uint32 pid = fpdCemuRequest->getFriendListEx.pidList[i]; friendRequest_t* friendRequest = fpdCemuRequest->getFriendRequestListEx.friendRequest.GetPtr() + i; nexFriendRequest frdReq; bool incoming = false; if (!g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { cemuLog_log(LogType::Force, "Failed to get friend request"); fpdCemuRequest->returnCode = 0x80000000; return; } setFriendRequestFromNexFriendRequest(friendRequest, &frdReq, incoming); } } typedef struct { MPTR funcMPTR; MPTR customParam; }fpAsyncCallback_t; typedef struct { nexPrincipalBasicInfo* principalBasicInfo; uint32* pidList; sint32 count; friendBasicInfo_t* friendBasicInfo; fpAsyncCallback_t fpCallback; }getBasicInfoAsyncParams_t; SysAllocator _fpCallbackResultArray; // use a ring buffer of results to avoid overwriting the result when multiple callbacks are queued at the same time sint32 fpCallbackResultIndex = 0; void handleFPCallback(fpAsyncCallback_t* fpCallback, uint32 resultCode) { fpCallbackResultIndex = (fpCallbackResultIndex + 1) % 32; uint32* resultPtr = _fpCallbackResultArray.GetPtr() + fpCallbackResultIndex; *resultPtr = resultCode; coreinitAsyncCallback_add(fpCallback->funcMPTR, 2, memory_getVirtualOffsetFromPointer(resultPtr), fpCallback->customParam); } void handleFPCallback2(MPTR funcMPTR, MPTR custom, uint32 resultCode) { fpCallbackResultIndex = (fpCallbackResultIndex + 1) % 32; uint32* resultPtr = _fpCallbackResultArray.GetPtr() + fpCallbackResultIndex; *resultPtr = resultCode; coreinitAsyncCallback_add(funcMPTR, 2, memory_getVirtualOffsetFromPointer(resultPtr), custom); } void handleResultCB_GetBasicInfoAsync(NexFriends* nexFriends, uint32 result, void* custom) { getBasicInfoAsyncParams_t* cbInfo = (getBasicInfoAsyncParams_t*)custom; if (result != 0) { handleFPCallback(&cbInfo->fpCallback, 0x80000000); // todo - properly translate internal error to nn::fp error code free(cbInfo->principalBasicInfo); free(cbInfo->pidList); free(cbInfo); return; } // convert PrincipalBasicInfo into friendBasicInfo for (sint32 i = 0; i < cbInfo->count; i++) { friendBasicInfo_t* basicInfo = cbInfo->friendBasicInfo + i; nexPrincipalBasicInfo* principalBasicInfo = cbInfo->principalBasicInfo + i; memset(basicInfo, 0, sizeof(friendBasicInfo_t)); basicInfo->pid = principalBasicInfo->principalId; strcpy(basicInfo->nnid, principalBasicInfo->nnid); convertMultiByteStringToBigEndianWidechar(principalBasicInfo->mii.miiNickname, basicInfo->screenname, sizeof(basicInfo->screenname) / sizeof(uint16be)); memcpy(basicInfo->miiData, principalBasicInfo->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 } // success handleFPCallback(&cbInfo->fpCallback, 0x00000000); free(cbInfo->principalBasicInfo); free(cbInfo->pidList); free(cbInfo); } void handleRequest_GetBasicInfoAsync(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } sint32 count = fpdCemuRequest->getBasicInfo.count; nexPrincipalBasicInfo* principalBasicInfo = new nexPrincipalBasicInfo[count]; uint32* pidList = (uint32*)malloc(sizeof(uint32)*count); for (sint32 i = 0; i < count; i++) pidList[i] = fpdCemuRequest->getBasicInfo.pidList[i]; getBasicInfoAsyncParams_t* cbInfo = (getBasicInfoAsyncParams_t*)malloc(sizeof(getBasicInfoAsyncParams_t)); cbInfo->principalBasicInfo = principalBasicInfo; cbInfo->pidList = pidList; cbInfo->count = count; cbInfo->friendBasicInfo = fpdCemuRequest->getBasicInfo.basicInfo.GetPtr(); cbInfo->fpCallback.funcMPTR = fpdCemuRequest->getBasicInfo.funcPtr; cbInfo->fpCallback.customParam = fpdCemuRequest->getBasicInfo.custom; g_fpd.nexFriendSession->requestPrincipleBaseInfoByPID(principalBasicInfo, pidList, count, handleResultCB_GetBasicInfoAsync, cbInfo); } void handleResponse_addOrRemoveFriend(uint32 errorCode, MPTR funcMPTR, MPTR custom) { if (errorCode == 0) { handleFPCallback2(funcMPTR, custom, 0); g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list } else handleFPCallback2(funcMPTR, custom, 0x80000000); } void handleRequest_RemoveFriendAsync(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } g_fpd.nexFriendSession->removeFriend(fpdCemuRequest->addOrRemoveFriend.pid, std::bind(handleResponse_addOrRemoveFriend, std::placeholders::_1, fpdCemuRequest->addOrRemoveFriend.funcPtr, fpdCemuRequest->addOrRemoveFriend.custom)); } void handleResponse_MarkFriendRequestAsReceivedAsync(uint32 errorCode, MPTR funcMPTR, MPTR custom) { if (errorCode == 0) handleFPCallback2(funcMPTR, custom, 0); else handleFPCallback2(funcMPTR, custom, 0x80000000); } void handleRequest_MarkFriendRequestAsReceivedAsync(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } // convert messageId list to little endian uint64 messageIds[100]; sint32 count = 0; for (uint32 i = 0; i < fpdCemuRequest->markFriendRequest.count; i++) { uint64 mid = _swapEndianU64(fpdCemuRequest->markFriendRequest.messageIdList.GetPtr()[i]); if (mid == 0) { forceLogDebug_printf("MarkFriendRequestAsReceivedAsync - Invalid messageId"); continue; } messageIds[count] = mid; count++; if (count >= sizeof(messageIds)/sizeof(messageIds[0])) break; } // skipped for now g_fpd.nexFriendSession->markFriendRequestsAsReceived(messageIds, count, std::bind(handleResponse_MarkFriendRequestAsReceivedAsync, std::placeholders::_1, fpdCemuRequest->markFriendRequest.funcPtr, fpdCemuRequest->markFriendRequest.custom)); } void handleResponse_cancelMyFriendRequest(uint32 errorCode, uint32 pid, MPTR funcMPTR, MPTR custom) { if (errorCode == 0) { handleFPCallback2(funcMPTR, custom, 0); } else handleFPCallback2(funcMPTR, custom, 0x80000000); } void handleRequest_CancelFriendRequestAsync(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } // find friend request with matching pid nexFriendRequest frq; bool isIncoming; if (g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, fpdCemuRequest->cancelOrAcceptFriendRequest.messageId)) { g_fpd.nexFriendSession->removeFriend(frq.principalInfo.principalId, std::bind(handleResponse_cancelMyFriendRequest, std::placeholders::_1, frq.principalInfo.principalId, fpdCemuRequest->cancelOrAcceptFriendRequest.funcPtr, fpdCemuRequest->cancelOrAcceptFriendRequest.custom)); } else { fpdCemuRequest->returnCode = 0x80000000; return; } } void handleResponse_acceptFriendRequest(uint32 errorCode, uint32 pid, MPTR funcMPTR, MPTR custom) { if (errorCode == 0) { handleFPCallback2(funcMPTR, custom, 0); g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list } else handleFPCallback2(funcMPTR, custom, 0x80000000); } void handleRequest_AcceptFriendRequestAsync(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } // find friend request with matching pid nexFriendRequest frq; bool isIncoming; if (g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, fpdCemuRequest->cancelOrAcceptFriendRequest.messageId)) { g_fpd.nexFriendSession->acceptFriendRequest(fpdCemuRequest->cancelOrAcceptFriendRequest.messageId, std::bind(handleResponse_acceptFriendRequest, std::placeholders::_1, frq.principalInfo.principalId, fpdCemuRequest->cancelOrAcceptFriendRequest.funcPtr, fpdCemuRequest->cancelOrAcceptFriendRequest.custom)); } else { fpdCemuRequest->returnCode = 0x80000000; return; } } void handleResponse_addFriendRequest(uint32 errorCode, MPTR funcMPTR, MPTR custom) { if (errorCode == 0) { handleFPCallback2(funcMPTR, custom, 0); g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list } else handleFPCallback2(funcMPTR, custom, 0x80000000); } void handleRequest_AddFriendRequest(iosuFpdCemuRequest_t* fpdCemuRequest) { fpdCemuRequest->returnCode = 0; if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) { fpdCemuRequest->returnCode = 0x80000000; return; } uint16be* input = fpdCemuRequest->addFriendRequest.message.GetPtr(); size_t inputLen = 0; while (input[inputLen] != 0) inputLen++; std::string msg = StringHelpers::ToUtf8({ input, inputLen }); g_fpd.nexFriendSession->addFriendRequest(fpdCemuRequest->addFriendRequest.pid, msg.data(), std::bind(handleResponse_addFriendRequest, std::placeholders::_1, fpdCemuRequest->addFriendRequest.funcPtr, fpdCemuRequest->addFriendRequest.custom)); } // called once a second to handle state checking and updates of the friends service void iosuFpd_updateFriendsService() { if (g_fpd.nexFriendSession) { g_fpd.nexFriendSession->update(); if (g_fpd.asyncLoginCallback.func != MPTR_NULL) { if (g_fpd.nexFriendSession->isOnline()) { *_fpdAsyncLoginRetCode.GetPtr() = 0x00000000; coreinitAsyncCallback_add(g_fpd.asyncLoginCallback.func, 2, _fpdAsyncLoginRetCode.GetMPTR(), g_fpd.asyncLoginCallback.customParam); g_fpd.asyncLoginCallback.func = MPTR_NULL; g_fpd.asyncLoginCallback.customParam = MPTR_NULL; } } } } void iosuFpd_thread() { SetThreadName("iosuFpd_thread"); while (true) { uint32 returnValue = 0; // Ioctl return value ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithTimeout(IOS_DEVICE_FPD, 1000); if (ioQueueEntry == nullptr) { iosuFpd_updateFriendsService(); continue; } if (ioQueueEntry->request == IOSU_FPD_REQUEST_CEMU) { iosuFpdCemuRequest_t* fpdCemuRequest = (iosuFpdCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); if (fpdCemuRequest->requestCode == IOSU_FPD_INITIALIZE) { if (g_fpd.isInitialized2 == false) { if(ActiveSettings::IsOnlineEnabled()) startFriendSession(); g_fpd.isInitialized2 = true; } } else if (fpdCemuRequest->requestCode == IOSU_FPD_SET_NOTIFICATION_HANDLER) { g_fpd.notificationFunc = fpdCemuRequest->setNotificationHandler.funcPtr; g_fpd.notificationCustomParam = fpdCemuRequest->setNotificationHandler.custom; g_fpd.notificationMask = fpdCemuRequest->setNotificationHandler.notificationMask; } else if (fpdCemuRequest->requestCode == IOSU_FPD_LOGIN_ASYNC) { if (g_fpd.nexFriendSession) { g_fpd.asyncLoginCallback.func = fpdCemuRequest->loginAsync.funcPtr; g_fpd.asyncLoginCallback.customParam = fpdCemuRequest->loginAsync.custom; } else { // offline mode *_fpdAsyncLoginRetCode.GetPtr() = 0; // if we return 0x80000000 here then Splatoon softlocks during boot coreinitAsyncCallback_add(fpdCemuRequest->loginAsync.funcPtr, 2, _fpdAsyncLoginRetCode.GetMPTR(), fpdCemuRequest->loginAsync.custom); } } else if (fpdCemuRequest->requestCode == IOSU_FPD_IS_ONLINE) { fpdCemuRequest->resultU32.u32 = g_fpd.nexFriendSession ? g_fpd.nexFriendSession->isOnline() : 0; } else if (fpdCemuRequest->requestCode == IOSU_FPD_IS_PREFERENCE_VALID) { fpdCemuRequest->resultU32.u32 = 1; // todo (if this returns 0, the friend app will show the first-time-setup screen and ask the user to configure preferences) } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_PRINCIPAL_ID) { uint8 slot = iosu::act::getCurrentAccountSlot(); iosu::act::getPrincipalId(slot, &fpdCemuRequest->resultU32.u32); } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_ACCOUNT_ID) { char* accountId = (char*)fpdCemuRequest->common.ptr.GetPtr(); uint8 slot = iosu::act::getCurrentAccountSlot(); if (iosu::act::getAccountId(slot, accountId) == false) fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code else fpdCemuRequest->returnCode = 0; } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_MII) { FFLData_t* fflData = (FFLData_t*)fpdCemuRequest->common.ptr.GetPtr(); uint8 slot = iosu::act::getCurrentAccountSlot(); if (iosu::act::getMii(slot, fflData) == false) fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code else fpdCemuRequest->returnCode = 0; } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_SCREENNAME) { uint16be* screennameOutput = (uint16be*)fpdCemuRequest->common.ptr.GetPtr(); uint8 slot = iosu::act::getCurrentAccountSlot(); uint16 screennameTemp[ACT_NICKNAME_LENGTH]; if (iosu::act::getScreenname(slot, screennameTemp)) { for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) { screennameOutput[i] = screennameTemp[i]; } screennameOutput[ACT_NICKNAME_LENGTH] = '\0'; // length is ACT_NICKNAME_LENGTH+1 fpdCemuRequest->returnCode = 0; } else { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code screennameOutput[0] = '\0'; } } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_ACCOUNT_ID) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } fpdCemuRequest->returnCode = 0; for (sint32 i = 0; i < fpdCemuRequest->getFriendAccountId.count; i++) { char* nnidOutput = fpdCemuRequest->getFriendAccountId.accountIds.GetPtr()+i*17; uint32 pid = fpdCemuRequest->getFriendAccountId.pidList[i]; if (g_fpd.nexFriendSession == nullptr) { nnidOutput[0] = '\0'; continue; } nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { strcpy(nnidOutput, frd.nnaInfo.principalInfo.nnid); continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { strcpy(nnidOutput, frdReq.principalInfo.nnid); continue; } nnidOutput[0] = '\0'; } } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_SCREENNAME) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } fpdCemuRequest->returnCode = 0; for (sint32 i = 0; i < fpdCemuRequest->getFriendScreenname.count; i++) { uint16be* screennameOutput = fpdCemuRequest->getFriendScreenname.nameList.GetPtr()+i*11; uint32 pid = fpdCemuRequest->getFriendScreenname.pidList[i]; if(fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = 0; if (g_fpd.nexFriendSession == nullptr) { screennameOutput[0] = '\0'; continue; } nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { convertMultiByteStringToBigEndianWidechar(frd.nnaInfo.principalInfo.mii.miiNickname, screennameOutput, 11); if (fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = frd.nnaInfo.principalInfo.regionGuessed; continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { convertMultiByteStringToBigEndianWidechar(frdReq.principalInfo.mii.miiNickname, screennameOutput, 11); if (fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = frdReq.principalInfo.regionGuessed; continue; } screennameOutput[0] = '\0'; } } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_MII) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } fpdCemuRequest->returnCode = 0; for (sint32 i = 0; i < fpdCemuRequest->getFriendMii.count; i++) { uint8* miiOutput = fpdCemuRequest->getFriendMii.miiList + i * FFL_SIZE; uint32 pid = fpdCemuRequest->getFriendMii.pidList[i]; if (g_fpd.nexFriendSession == nullptr) { memset(miiOutput, 0, FFL_SIZE); continue; } 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; } memset(miiOutput, 0, FFL_SIZE); } } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_PRESENCE) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } fpdCemuRequest->returnCode = 0; for (sint32 i = 0; i < fpdCemuRequest->getFriendPresence.count; i++) { friendPresence_t* presenceOutput = (friendPresence_t*)(fpdCemuRequest->getFriendPresence.presenceList + i * sizeof(friendPresence_t)); memset(presenceOutput, 0, sizeof(friendPresence_t)); uint32 pid = fpdCemuRequest->getFriendPresence.pidList[i]; if (g_fpd.nexFriendSession == nullptr) { continue; } nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { presenceOutput->isOnline = frd.presence.isOnline ? 1 : 0; presenceOutput->isValid = 1; 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); continue; } } } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_RELATIONSHIP) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } fpdCemuRequest->returnCode = 0; for (sint32 i = 0; i < fpdCemuRequest->getFriendRelationship.count; i++) { uint8* relationshipOutput = (fpdCemuRequest->getFriendRelationship.relationshipList + i); uint32 pid = fpdCemuRequest->getFriendRelationship.pidList[i]; *relationshipOutput = RELATIONSHIP_INVALID; if (g_fpd.nexFriendSession == nullptr) { continue; } 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; } } } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST) { handleRequest_GetFriendList(fpdCemuRequest, false); } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIENDREQUEST_LIST) { handleRequest_GetFriendRequestList(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST_ALL) { handleRequest_GetFriendList(fpdCemuRequest, true); } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST_EX) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } handleRequest_GetFriendListEx(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIENDREQUEST_LIST_EX) { if (g_fpd.nexFriendSession == nullptr) { fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code iosuIoctl_completeRequest(ioQueueEntry, returnValue); return; } handleRequest_GetFriendRequestListEx(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_ADD_FRIEND) { // todo - figure out how this works *_fpdAsyncAddFriendRetCode.GetPtr() = 0; coreinitAsyncCallback_add(fpdCemuRequest->addOrRemoveFriend.funcPtr, 2, _fpdAsyncAddFriendRetCode.GetMPTR(), fpdCemuRequest->addOrRemoveFriend.custom); } else if (fpdCemuRequest->requestCode == IOSU_FPD_ADD_FRIEND_REQUEST) { handleRequest_AddFriendRequest(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_REMOVE_FRIEND_ASYNC) { handleRequest_RemoveFriendAsync(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC) { handleRequest_MarkFriendRequestAsReceivedAsync(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC) { handleRequest_CancelFriendRequestAsync(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC) { handleRequest_AcceptFriendRequestAsync(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_BASIC_INFO_ASYNC) { handleRequest_GetBasicInfoAsync(fpdCemuRequest); } else if (fpdCemuRequest->requestCode == IOSU_FPD_UPDATE_GAMEMODE) { gameMode_t* gameMode = fpdCemuRequest->updateGameMode.gameMode.GetPtr(); uint16be* gameModeMessage = fpdCemuRequest->updateGameMode.gameModeMessage.GetPtr(); g_fpd.myPresence.joinFlagMask = gameMode->joinFlagMask; g_fpd.myPresence.joinAvailability = (uint8)(uint32)gameMode->matchmakeType; g_fpd.myPresence.gameId = gameMode->joinGameId; g_fpd.myPresence.gameMode = gameMode->joinGameMode; g_fpd.myPresence.hostPid = gameMode->hostPid; g_fpd.myPresence.groupId = gameMode->groupId; memcpy(g_fpd.myPresence.appSpecificData, gameMode->appSpecificData, 0x14); if (g_fpd.nexFriendSession) { g_fpd.nexFriendSession->updateMyPresence(g_fpd.myPresence); } fpdCemuRequest->returnCode = 0; } else cemu_assert_unimplemented(); } else assert_dbg(); iosuIoctl_completeRequest(ioQueueEntry, returnValue); } return; } void Initialize() { if (g_fpd.isThreadStarted) return; std::thread t1(iosuFpd_thread); t1.detach(); g_fpd.isThreadStarted = true; } } }