diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp index 6f576ae1..b22cf274 100644 --- a/src/Cafe/Account/Account.cpp +++ b/src/Cafe/Account/Account.cpp @@ -323,7 +323,7 @@ const std::vector& Account::RefreshAccounts() result.emplace_back(account); } } - + // we always force at least one account if (result.empty()) { @@ -331,6 +331,11 @@ const std::vector& Account::RefreshAccounts() result.begin()->Save(); } + std::sort(result.begin(), result.end(), [](const Account& a, const Account& b) + { + return a.GetPersistentId() < b.GetPersistentId(); + }); + s_account_list = result; UpdatePersisidDat(); return s_account_list; @@ -351,6 +356,7 @@ void Account::UpdatePersisidDat() cemuLog_log(LogType::Force, "Unable to save persisid.dat"); } + bool Account::HasFreeAccountSlots() { return s_account_list.size() < 12; @@ -400,10 +406,10 @@ uint32 Account::GetNextPersistentId() } } } - + // next id ++result; - + const auto it = std::max_element(s_account_list.cbegin(), s_account_list.cend(), [](const Account& acc1, const Account& acc2) {return acc1.GetPersistentId() < acc2.GetPersistentId(); }); if (it != s_account_list.cend()) return std::max(result, it->GetPersistentId() + 1); diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 11e613ac..ca60045c 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -11,6 +11,7 @@ #include "Cafe/Account/Account.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" +#include "Common/FileStream.h" #include "Cemu/napi/napi.h" #include "Cemu/ncrypto/ncrypto.h" @@ -65,6 +66,7 @@ struct actAccountData_t actAccountData_t _actAccountData[IOSU_ACT_ACCOUNT_MAX_COUNT] = {}; bool _actAccountDataInitialized = false; +int _actAccountCount = 0; void FillAccountData(const Account& account, const bool online_enabled, int index) { @@ -108,26 +110,22 @@ void iosuAct_loadAccounts() return; const bool online_enabled = ActiveSettings::IsOnlineEnabled(); - const auto persistent_id = ActiveSettings::GetPersistentId(); - // first account is always our selected one + // Load all accounts in order of persistantId; the active account keeps its natural position int counter = 0; - const auto& first_acc = Account::GetAccount(persistent_id); - FillAccountData(first_acc, online_enabled, counter); - ++counter; - // enable multiple accounts for cafe functions (badly tested) - //for (const auto& account : Account::GetAccounts()) - //{ - // if (first_acc.GetPersistentId() != account.GetPersistentId()) - // { - // FillAccountData(account, online_enabled, counter); - // ++counter; - // } - //} - - cemuLog_log(LogType::Force, "IOSU_ACT: using account {} in first slot", boost::nowide::narrow(first_acc.GetMiiName())); - + for (const auto& account : Account::GetAccounts()) + { + if (counter >= IOSU_ACT_ACCOUNT_MAX_COUNT) + break; + FillAccountData(account, online_enabled, counter); + ++counter; + } + _actAccountCount = counter; _actAccountDataInitialized = true; + + const uint8 activeSlot = iosu::act::getCurrentAccountSlot(); + const auto& active_acc = Account::GetAccount(ActiveSettings::GetPersistentId()); + cemuLog_log(LogType::Force, "IOSU_ACT: loaded {} account(s), using {} in slot {}", counter, boost::nowide::narrow(std::wstring(active_acc.GetMiiName())), activeSlot); } bool iosuAct_isAccountDataLoaded() @@ -135,6 +133,11 @@ bool iosuAct_isAccountDataLoaded() return _actAccountDataInitialized; } +int iosuAct_getNumAccounts() +{ + return _actAccountCount; +} + uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid) { NAPI::AuthInfo authInfo; @@ -154,10 +157,17 @@ uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid) sint32 iosuAct_getAccountIndexBySlot(uint8 slot) { - if (slot == iosu::act::ACT_SLOT_CURRENT) - return 0; - if (slot == 0xFF) - return 0; // ? + if (slot == iosu::act::ACT_SLOT_CURRENT || slot == 0xFF) + { + // find the active account's actual index by persistent ID + const uint32 persistent_id = ActiveSettings::GetPersistentId(); + for (int i = 0; i < _actAccountCount; i++) + { + if (_actAccountData[i].isValid && _actAccountData[i].persistentId == persistent_id) + return i; + } + return 0; // fallback + } cemu_assert_debug(slot != 0); cemu_assert_debug(slot <= IOSU_ACT_ACCOUNT_MAX_COUNT); return slot - 1; @@ -165,8 +175,9 @@ sint32 iosuAct_getAccountIndexBySlot(uint8 slot) uint32 iosuAct_getAccountIdOfCurrentAccount() { - cemu_assert_debug(_actAccountData[0].isValid); - return _actAccountData[0].persistentId; + const sint32 index = iosuAct_getAccountIndexBySlot(iosu::act::ACT_SLOT_CURRENT); + cemu_assert_debug(_actAccountData[index].isValid); + return _actAccountData[index].persistentId; } // IOSU act API interface @@ -386,7 +397,13 @@ namespace iosu { uint8 getCurrentAccountSlot() { - return 1; + const uint32 persistent_id = ActiveSettings::GetPersistentId(); + for (int i = 0; i < _actAccountCount; i++) + { + if (_actAccountData[i].isValid && _actAccountData[i].persistentId == persistent_id) + return (uint8)(i + 1); // slots are 1-based + } + return 1; // fallback } actAccountData_t* GetAccountBySlotNo(uint8 slotNo) @@ -538,6 +555,44 @@ namespace iosu return true; } + uint32 GetMiiImage(uint8 slot, uint32 imageType, void* outBuf, uint32 bufferSize, uint32* outImageSize) + { + uint32 persistentId = 0; + if (!GetPersistentId(slot, &persistentId)) + return ACTResult_AccountDoesNotExist; + + static constexpr uint32 kMaxMiiImageBytes = 2 * 1024 * 1024; + + const auto filePath = ActiveSettings::GetMlcPath( + fmt::format("usr/save/system/act/{:08x}/miiimg{:02d}.dat", persistentId, imageType)); + + // reject oversized files before allocating. Prevents loading multi-GB crafted files into RAM + std::error_code ec; + if (fs::file_size(filePath, ec) > kMaxMiiImageBytes || ec) + return ACTResult_AccountDoesNotExist; + + auto fileData = FileStream::LoadIntoMemory(filePath); + if (!fileData.has_value()) + return ACTResult_AccountDoesNotExist; + + // type 0 (FaceIcon) is stored raw; all others are zlib-compressed + if (imageType != 0) + { + fileData = zlibDecompress(*fileData); + if (!fileData.has_value()) + return ACTResult_AccountDoesNotExist; + } + + if (fileData->size() > kMaxMiiImageBytes) + return ACTResult_AccountDoesNotExist; + + *outImageSize = (uint32)fileData->size(); + if (bufferSize < *outImageSize) + return ACTResult_OutOfRange; + memcpy(outBuf, fileData->data(), fileData->size()); + return 0; + } + nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen) { if (accountSlot != ACT_SLOT_CURRENT) diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index 6d5c5c26..127361d8 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -50,6 +50,7 @@ namespace iosu bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getCountryIndex(uint8 slot, uint32* countryIndex); bool GetPersistentId(uint8 slot, uint32* persistentId); + uint32 GetMiiImage(uint8 slot, uint32 imageType, void* outBuf, uint32 bufferSize, uint32* outImageSize); std::string getAccountId2(uint8 slot); @@ -121,4 +122,5 @@ struct iosuActCemuRequest_t uint32 iosuAct_getAccountIdOfCurrentAccount(); -bool iosuAct_isAccountDataLoaded(); \ No newline at end of file +bool iosuAct_isAccountDataLoaded(); +int iosuAct_getNumAccounts(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index fdeee430..177b37af 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -7,8 +7,6 @@ #include "Cafe/CafeSystem.h" #include "Common/CafeString.h" -sint32 numAccounts = 1; - #define actPrepareRequest() \ StackAllocator _buf_actRequest; \ StackAllocator _buf_bufferVector; \ @@ -23,6 +21,10 @@ StackAllocator _buf_actRequest; \ iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); +// Returned by ACT functions when a required output pointer is null (matches native RPL behaviour) +static const uint32 ACTResult_NullPointer = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12C80); +static const uint32 ACTResult_AccountDoesNotExist = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST); + uint32 getNNReturnCode(uint32 iosError, iosuActCemuRequest_t* actRequest) { if ((iosError & 0x80000000) != 0) @@ -109,7 +111,7 @@ namespace act return result; } - + uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) { memset(token, 0, sizeof(independentServiceToken_t)); @@ -134,14 +136,36 @@ namespace act sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo) { - if (!pOutOffset) - return 0xc0712c80; + return ACTResult_NullPointer; *pOutOffset = GetUtcOffset(); return 0; } + uint32 IsPasswordCacheEnabledEx(uint8 slot) + { + // todo: read the value from the individual account via IOSU + return 1; + } + + uint32 IsPasswordCacheEnabled() + { + return IsPasswordCacheEnabledEx(ACT_SLOT_CURRENT); + } + + uint32 GetMiiImageEx(uint32be* outImageSize, MEMPTR buffer, uint32 bufferSize, uint32 imageType, uint8 slot) + { + if (!outImageSize || !buffer.GetPtr()) + return ACTResult_NullPointer; + if (imageType > ACT_MII_IMAGE_TYPE_MAX) + return ACTResult_AccountDoesNotExist; + uint32 size = 0; + uint32 r = iosu::act::GetMiiImage(slot, imageType, buffer.GetPtr(), bufferSize, &size); + *outImageSize = size; + return r; + } + nnResult GetTimeZoneId(CafeString<65>* outTimezoneId) { // return a placeholder timezone id for now @@ -186,7 +210,7 @@ void nnActExport_CreateConsoleAccount(PPCInterpreter_t* hCPU) void nnActExport_GetNumOfAccounts(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetNumOfAccounts()"); - osLib_returnFromFunction(hCPU, numAccounts); // account count + osLib_returnFromFunction(hCPU, iosuAct_getNumAccounts()); } void nnActExport_IsSlotOccupied(PPCInterpreter_t* hCPU) @@ -402,13 +426,6 @@ void nnActExport_GetMiiEx(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, r); } -void nnActExport_GetMiiImageEx(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "GetMiiImageEx unimplemented"); - - osLib_returnFromFunction(hCPU, 0); -} - void nnActExport_GetMiiName(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetMiiName(0x{:08x})", hCPU->gpr[3]); @@ -418,7 +435,7 @@ void nnActExport_GetMiiName(PPCInterpreter_t* hCPU) uint32 r = nn::act::GetMiiEx(&miiData, iosu::act::ACT_SLOT_CURRENT); // extract name - sint32 miiNameLength = 0; + sint32 miiNameLength = 0; for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) { miiName[i] = miiData->miiName[i]; @@ -567,8 +584,8 @@ void nnActExport_GetDefaultAccount(PPCInterpreter_t* hCPU) void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU) { - // id of active account - osLib_returnFromFunction(hCPU, 1); // 1 is the first slot (0 is invalid) + // returns the 1-based slot number of the currently active account + osLib_returnFromFunction(hCPU, iosu::act::getCurrentAccountSlot()); } void nnActExport_GetSlotNoEx(PPCInterpreter_t* hCPU) @@ -731,7 +748,7 @@ namespace nn::act osLib_addFunction("nn_act", "GetMii__Q2_2nn3actFP12FFLStoreData", nnActExport_GetMii); osLib_addFunction("nn_act", "GetMiiEx__Q2_2nn3actFP12FFLStoreDataUc", nnActExport_GetMiiEx); - osLib_addFunction("nn_act", "GetMiiImageEx__Q2_2nn3actFPUiPvUi15ACTMiiImageTypeUc", nnActExport_GetMiiImageEx); + cafeExportRegisterFunc(nn::act::GetMiiImageEx, "nn_act", "GetMiiImageEx__Q2_2nn3actFPUiPvUi15ACTMiiImageTypeUc", LogType::Placeholder); osLib_addFunction("nn_act", "GetMiiName__Q2_2nn3actFPw", nnActExport_GetMiiName); osLib_addFunction("nn_act", "GetMiiNameEx__Q2_2nn3actFPwUc", nnActExport_GetMiiNameEx); @@ -754,6 +771,8 @@ namespace nn::act osLib_addFunction("nn_act", "AcquirePrincipalIdByAccountId__Q2_2nn3actFPUiPA17_CcUi", nnActExport_AcquirePrincipalIdByAccountId); cafeExportRegisterFunc(nn::act::GetErrorCode, "nn_act", "GetErrorCode__Q2_2nn3actFRCQ2_2nn6Result", LogType::Placeholder); + cafeExportRegisterFunc(nn::act::IsPasswordCacheEnabled, "nn_act", "IsPasswordCacheEnabled__Q2_2nn3actFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::act::IsPasswordCacheEnabledEx, "nn_act", "IsPasswordCacheEnabledEx__Q2_2nn3actFUc", LogType::Placeholder); // placeholders / incomplete implementations osLib_addFunction("nn_act", "HasNfsAccount__Q2_2nn3actFv", nnActExport_HasNfsAccount); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.h b/src/Cafe/OS/libs/nn_act/nn_act.h index 5096fef3..3b1568f4 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.h +++ b/src/Cafe/OS/libs/nn_act/nn_act.h @@ -9,6 +9,22 @@ struct independentServiceToken_t }; static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size +// Passed to GetMiiImageEx. Each value maps to miiimgXX.dat in the account folder. +// Sizes and storage formats confirmed from real Wii U MLC dumps. +enum class ACTMiiImageType : uint32 +{ + FaceIcon = 0, // 128x128 BGRA TGA, stored raw (miiimg00.dat) + FaceExpression1 = 1, // 96x96 BGRA TGA, zlib-compressed (miiimg01.dat) + FaceExpression2 = 2, // 96x96 BGRA TGA, zlib-compressed (miiimg02.dat) + FaceExpression3 = 3, // 96x96 BGRA TGA, zlib-compressed (miiimg03.dat) + FaceExpression4 = 4, // 96x96 BGRA TGA, zlib-compressed (miiimg04.dat) + FaceExpression5 = 5, // 96x96 BGRA TGA, zlib-compressed (miiimg05.dat) + FaceExpression6 = 6, // 96x96 BGRA TGA, zlib-compressed (miiimg06.dat) + FullBody = 7, // 270x360 BGRA TGA, zlib-compressed (miiimg07.dat) - full standing body render + FaceIconAlt = 8, // 128x128 BGRA TGA, zlib-compressed (miiimg08.dat) +}; +static constexpr uint32 ACT_MII_IMAGE_TYPE_MAX = 8; + namespace nn { namespace act