Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/Cafe/Account/Account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,19 @@ const std::vector<Account>& Account::RefreshAccounts()
result.emplace_back(account);
}
}

// we always force at least one account
if (result.empty())
{
result.emplace_back(kMinPersistendId, L"default");
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
103 changes: 79 additions & 24 deletions src/Cafe/IOSU/legacy/iosu_act.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -108,33 +110,34 @@ 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()
{
return _actAccountDataInitialized;
}

int iosuAct_getNumAccounts()
{
return _actAccountCount;
}

uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid)
{
NAPI::AuthInfo authInfo;
Expand All @@ -154,19 +157,27 @@ 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;
}

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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/Cafe/IOSU/legacy/iosu_act.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -121,4 +122,5 @@ struct iosuActCemuRequest_t

uint32 iosuAct_getAccountIdOfCurrentAccount();

bool iosuAct_isAccountDataLoaded();
bool iosuAct_isAccountDataLoaded();
int iosuAct_getNumAccounts();
53 changes: 36 additions & 17 deletions src/Cafe/OS/libs/nn_act/nn_act.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
#include "Cafe/CafeSystem.h"
#include "Common/CafeString.h"

sint32 numAccounts = 1;

#define actPrepareRequest() \
StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \
StackAllocator<ioBufferVector_t> _buf_bufferVector; \
Expand All @@ -23,6 +21,10 @@ StackAllocator<iosuActCemuRequest_t> _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)
Expand Down Expand Up @@ -109,7 +111,7 @@ namespace act

return result;
}

uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds)
{
memset(token, 0, sizeof(independentServiceToken_t));
Expand All @@ -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<uint8> 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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]);
Expand All @@ -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];
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions src/Cafe/OS/libs/nn_act/nn_act.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down