This commit is contained in:
Hr. Vedel 2026-05-03 18:17:44 -07:00 committed by GitHub
commit 5a81c3e19d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 155 additions and 36 deletions

View File

@ -323,7 +323,7 @@ const std::vector<Account>& Account::RefreshAccounts()
result.emplace_back(account);
}
}
// we always force at least one account
if (result.empty())
{
@ -331,6 +331,11 @@ const std::vector<Account>& 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);

View File

@ -65,6 +65,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 +109,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 +132,11 @@ bool iosuAct_isAccountDataLoaded()
return _actAccountDataInitialized;
}
int iosuAct_getNumAccounts()
{
return _actAccountCount;
}
uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid)
{
NAPI::AuthInfo authInfo;
@ -154,10 +156,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 +174,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 +396,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)

View File

@ -121,4 +121,5 @@ struct iosuActCemuRequest_t
uint32 iosuAct_getAccountIdOfCurrentAccount();
bool iosuAct_isAccountDataLoaded();
bool iosuAct_isAccountDataLoaded();
int iosuAct_getNumAccounts();

View File

@ -6,8 +6,10 @@
#include "Cafe/OS/libs/nn_common.h"
#include "Cafe/CafeSystem.h"
#include "Common/CafeString.h"
sint32 numAccounts = 1;
#include "Common/FileStream.h"
#include "config/ActiveSettings.h"
#include "util/helpers/helpers.h"
#include "Cafe/Account/Account.h"
#define actPrepareRequest() \
StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \
@ -186,7 +188,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)
@ -267,6 +269,22 @@ void nnActExport_IsNetworkAccountEx(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, isNetAcc);
}
void nnActExport_IsPasswordCacheEnabledEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU8(slot, 0);
cemuLog_logDebug(LogType::Force, "nn_act.IsPasswordCacheEnabledEx({})", slot);
const uint32 persistentId = nn::act::GetPersistentIdEx(slot);
if (persistentId == 0)
{
osLib_returnFromFunction(hCPU, 0);
return;
}
const Account& account = Account::GetAccount(persistentId);
osLib_returnFromFunction(hCPU, account.IsPasswordCacheEnabled() ? 1 : 0);
}
void nnActExport_GetSimpleAddressId(PPCInterpreter_t* hCPU)
{
cemuLog_logDebug(LogType::Force, "nn_act.GetSimpleAddressId()");
@ -402,11 +420,72 @@ void nnActExport_GetMiiEx(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, r);
}
// Helper: write image bytes to the guest buffer and return the result code.
static uint32 ReturnMiiImage(uint32be* outImageSize, MEMPTR<uint8> buffer, uint32 bufferSize,
const uint8* data, uint32 dataSize)
{
if (outImageSize)
*outImageSize = dataSize;
if (!buffer.GetPtr() || bufferSize < dataSize)
return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12D80); // OutOfRange
memcpy(buffer.GetPtr(), data, dataSize);
return 0;
}
void nnActExport_GetMiiImageEx(PPCInterpreter_t* hCPU)
{
cemuLog_logDebug(LogType::Force, "GetMiiImageEx unimplemented");
// GetMiiImageEx(uint32* outImageSize, void* buffer, uint32 bufferSize, ACTMiiImageType imageType, uint8 slot)
ppcDefineParamU32BEPtr(outImageSize, 0);
ppcDefineParamMEMPTR(buffer, uint8, 1);
ppcDefineParamU32(bufferSize, 2);
ppcDefineParamU32(imageType, 3);
ppcDefineParamU8(slot, 4);
osLib_returnFromFunction(hCPU, 0);
cemuLog_logDebug(LogType::Force, "nn_act.GetMiiImageEx(outImageSize=0x{:08x} buffer=0x{:08x} bufferSize={} imageType={} slot={})",
hCPU->gpr[3], hCPU->gpr[4], bufferSize, imageType, slot);
// imageType maps directly to miiimgXX.dat in the account folder:
// FaceIcon (0) : 128x128 BGRA, raw TGA
// Expressions (1-6): 96x96 BGRA, zlib-compressed
// FullBody (7) : 270x360 BGRA, zlib-compressed (standing body render)
// FaceIconAlt (8) : 128x128 BGRA, zlib-compressed
if (imageType <= ACT_MII_IMAGE_TYPE_MAX)
{
uint32 persistentId = 0;
if (iosu::act::GetPersistentId(slot, &persistentId) && persistentId != 0)
{
fs::path datPath = ActiveSettings::GetMlcPath(
fmt::format("usr/save/system/act/{:08x}/miiimg{:02d}.dat", persistentId, imageType));
auto fileData = FileStream::LoadIntoMemory(datPath);
if (fileData.has_value())
{
if (imageType == (uint32)ACTMiiImageType::FaceIcon)
{
// FaceIcon (type 0) is stored as a raw TGA — serve it directly
uint32 r = ReturnMiiImage(outImageSize, buffer, bufferSize,
fileData->data(), (uint32)fileData->size());
osLib_returnFromFunction(hCPU, r);
return;
}
else
{
// All other types are zlib-compressed; decompress before serving
auto decompressed = zlibDecompress(*fileData);
if (decompressed.has_value())
{
uint32 r = ReturnMiiImage(outImageSize, buffer, bufferSize,
decompressed->data(), (uint32)decompressed->size());
osLib_returnFromFunction(hCPU, r);
return;
}
cemuLog_log(LogType::Force, "nn_act.GetMiiImageEx: failed to decompress miiimg{:02d}.dat", imageType);
}
}
}
}
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST));
}
void nnActExport_GetMiiName(PPCInterpreter_t* hCPU)
@ -418,7 +497,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 +646,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)
@ -705,6 +784,7 @@ namespace nn::act
osLib_addFunction("nn_act", "GetSlotNoEx__Q2_2nn3actFRC7ACTUuid", nnActExport_GetSlotNoEx);
osLib_addFunction("nn_act", "IsNetworkAccount__Q2_2nn3actFv", nnActExport_IsNetworkAccount);
osLib_addFunction("nn_act", "IsNetworkAccountEx__Q2_2nn3actFUc", nnActExport_IsNetworkAccountEx);
osLib_addFunction("nn_act", "IsPasswordCacheEnabledEx__Q2_2nn3actFUc", nnActExport_IsPasswordCacheEnabledEx);
// account id
osLib_addFunction("nn_act", "GetAccountId__Q2_2nn3actFPc", nnActExport_GetAccountId);
osLib_addFunction("nn_act", "GetAccountIdEx__Q2_2nn3actFPcUc", nnActExport_GetAccountIdEx);

View File

@ -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