Inital commit

This commit is contained in:
SIrHrVedel 2026-04-26 23:52:51 +02:00
parent 6f6c1299e2
commit 2639885b2b
8 changed files with 348 additions and 79 deletions

View File

@ -6,6 +6,8 @@
#include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "Common/FileStream.h" #include "Common/FileStream.h"
#include <boost/random/uniform_int.hpp> #include <boost/random/uniform_int.hpp>
#include <boost/nowide/convert.hpp>
#include <map>
#include <random> #include <random>
@ -300,11 +302,12 @@ void Account::SetMiiName(std::wstring_view name)
const std::vector<Account>& Account::RefreshAccounts() const std::vector<Account>& Account::RefreshAccounts()
{ {
std::vector<Account> result; // Step 1: Scan all account directories into a sorted map
const fs::path path = ActiveSettings::GetMlcPath("usr/save/system/act"); std::map<uint32, Account> accountMap;
if (fs::exists(path)) const fs::path act_path = ActiveSettings::GetMlcPath("usr/save/system/act");
if (fs::exists(act_path))
{ {
for (const auto& it : fs::directory_iterator(path)) for (const auto& it : fs::directory_iterator(act_path))
{ {
if (!fs::is_directory(it)) if (!fs::is_directory(it))
continue; continue;
@ -320,37 +323,201 @@ const std::vector<Account>& Account::RefreshAccounts()
Account account(persistent_id); Account account(persistent_id);
const auto error = account.Load(); const auto error = account.Load();
if (!error) if (!error)
result.emplace_back(account); accountMap.emplace(persistent_id, std::move(account));
} }
} }
// we always force at least one account std::vector<Account> result;
// Step 2: Place accounts in common.dat order
for (const uint32 id : ReadCommonDat())
{
auto it = accountMap.find(id);
if (it == accountMap.end())
{
cemuLog_log(LogType::Force,
"User with persistentId {:08x} does not exist, please do not delete users from the MLC manually. Use 'File > General Settings > Accounts' to delete users!", id);
continue;
}
result.emplace_back(std::move(it->second));
accountMap.erase(it);
}
// Step 3: Append remaining accounts not in common.dat, sorted by persistent ID
for (auto& [id, account] : accountMap)
result.emplace_back(std::move(account));
// Step 4: Always force at least one account
if (result.empty()) if (result.empty())
{ {
result.emplace_back(kMinPersistendId, L"default"); result.emplace_back(kMinPersistendId, L"default");
result.begin()->Save(); result.begin()->Save();
} }
s_account_list = result;
UpdatePersisidDat(); s_account_list = std::move(result);
// Write updated common.dat (also updates PersistentIdHead and DefaultAccountPersistentId)
std::vector<uint32> orderedIds;
orderedIds.reserve(s_account_list.size());
for (const auto& acc : s_account_list)
orderedIds.push_back(acc.GetPersistentId());
WriteCommonDat(orderedIds);
return s_account_list; return s_account_list;
} }
void Account::UpdatePersisidDat() std::vector<uint32> Account::ReadCommonDat()
{ {
const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); std::vector<uint32> result;
const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); const auto file_path = ActiveSettings::GetMlcPath("usr/save/system/act/common.dat");
std::ofstream f(file); if (!fs::exists(file_path))
if(f.is_open()) return result;
std::ifstream f(file_path);
if (!f.is_open())
return result;
std::string line;
while (std::getline(f, line))
{ {
f << "PersistentIdManager_20120607" << std::endl << "PersistentIdHead=" << std::hex << max_id << std::endl << std::endl; if (!boost::starts_with(line, "PersistentIdList="))
f.flush(); continue;
f.close();
std::string list = line.substr(sizeof("PersistentIdList=") - 1);
// Strip trailing \r if present
if (!list.empty() && list.back() == '\r')
list.pop_back();
// Entries are separated by literal "\0" (backslash + '0'). Stop at "0" (empty slot).
size_t pos = 0;
while (pos < list.size())
{
const size_t sep = list.find("\\0", pos);
const std::string entry = (sep == std::string::npos) ? list.substr(pos) : list.substr(pos, sep - pos);
if (entry.empty() || entry == "0")
break; // reached first empty slot — no real users after this
const auto id = ConvertString<uint32>(entry, 16);
if (id >= kMinPersistendId)
result.push_back(id);
if (sep == std::string::npos)
break;
pos = sep + 2; // skip past "\0"
}
break; // PersistentIdList line processed
} }
else return result;
cemuLog_log(LogType::Force, "Unable to save persisid.dat");
} }
void Account::WriteCommonDat(const std::vector<uint32>& orderedIds)
{
const auto file_path = ActiveSettings::GetMlcPath("usr/save/system/act/common.dat");
// Read existing file to preserve the header and all metadata lines
std::string header = "AccountManager_20120607";
std::vector<std::pair<std::string, std::string>> meta_lines;
if (fs::exists(file_path))
{
std::ifstream f(file_path);
if (f.is_open())
{
std::string line;
bool first_line = true;
while (std::getline(f, line))
{
if (!line.empty() && line.back() == '\r')
line.pop_back();
if (first_line)
{
header = line;
first_line = false;
continue;
}
if (line.empty() || boost::starts_with(line, "PersistentIdList="))
continue;
const auto eq = line.find('=');
if (eq != std::string::npos)
meta_lines.emplace_back(line.substr(0, eq), line.substr(eq + 1));
}
}
}
// Populate defaults if the file was absent or had no metadata
if (meta_lines.empty())
{
meta_lines.emplace_back("DefaultAccountPersistentId", "0");
meta_lines.emplace_back("CommonTransferableIdBase", "0");
meta_lines.emplace_back("CommonUuid", "0");
meta_lines.emplace_back("IsApplicationUpdateRequired", "0");
meta_lines.emplace_back("DefaultNnasType", "0");
meta_lines.emplace_back("DefaultNfsType", "0");
meta_lines.emplace_back("DefaultNfsNo", "1");
meta_lines.emplace_back("DefaultNnasDomain", "");
meta_lines.emplace_back("DefaultNnasNfsEnv", "L1");
}
// Helper to update an existing key or append a new one
auto set_meta = [&](const std::string& key, const std::string& val)
{
for (auto& [k, v] : meta_lines)
{
if (k == key)
{
v = val;
return;
}
}
meta_lines.emplace_back(key, val);
};
// Update DefaultAccountPersistentId to the currently active account
set_meta("DefaultAccountPersistentId", fmt::format("{:08x}", ActiveSettings::GetPersistentId()));
// Ensure the directory exists
const auto dir = file_path.parent_path();
if (!fs::exists(dir))
{
std::error_code ec;
fs::create_directories(dir, ec);
if (ec)
{
cemuLog_log(LogType::Force, "Unable to create directory for common.dat");
return;
}
}
std::ofstream f(file_path);
if (!f.is_open())
{
cemuLog_log(LogType::Force, "Unable to save common.dat");
return;
}
f << header << "\n";
// Write PersistentIdList: always exactly 12 slots, each followed by literal "\0"
f << "PersistentIdList=";
for (size_t i = 0; i < 12; i++)
{
if (i < orderedIds.size())
f << fmt::format("{:08x}", orderedIds[i]);
else
f << "0";
f << "\\0";
}
f << "\n";
for (const auto& [key, value] : meta_lines)
f << key << "=" << value << "\n";
f.flush();
}
bool Account::HasFreeAccountSlots() bool Account::HasFreeAccountSlots()
{ {
return s_account_list.size() < 12; return s_account_list.size() < 12;
@ -382,33 +549,11 @@ const Account& Account::GetCurrentAccount()
uint32 Account::GetNextPersistentId() uint32 Account::GetNextPersistentId()
{ {
uint32 result = kMinPersistendId; const auto it = std::max_element(s_account_list.cbegin(), s_account_list.cend(),
const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); [](const Account& acc1, const Account& acc2) { return acc1.GetPersistentId() < acc2.GetPersistentId(); });
if(fs::exists(file))
{
std::ifstream f(file);
if(f.is_open())
{
std::string line;
while(std::getline(f, line))
{
if(boost::starts_with(line, "PersistentIdHead="))
{
result = ConvertString<uint32>(line.data() + sizeof("PersistentIdHead=") - 1, 16);
break;
}
}
}
}
// 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()) if (it != s_account_list.cend())
return std::max(result, it->GetPersistentId() + 1); return it->GetPersistentId() + 1;
else return kMinPersistendId;
return result;
} }
fs::path Account::GetFileName(uint32 persistent_id) fs::path Account::GetFileName(uint32 persistent_id)

View File

@ -93,7 +93,8 @@ public:
// this will always return at least one account (default one) // this will always return at least one account (default one)
static const std::vector<Account>& RefreshAccounts(); static const std::vector<Account>& RefreshAccounts();
static void UpdatePersisidDat(); static std::vector<uint32> ReadCommonDat();
static void WriteCommonDat(const std::vector<uint32>& orderedIds);
[[nodiscard]] static bool HasFreeAccountSlots(); [[nodiscard]] static bool HasFreeAccountSlots();
[[nodiscard]] static const std::vector<Account>& GetAccounts(); [[nodiscard]] static const std::vector<Account>& GetAccounts();

View File

@ -65,6 +65,7 @@ struct actAccountData_t
actAccountData_t _actAccountData[IOSU_ACT_ACCOUNT_MAX_COUNT] = {}; actAccountData_t _actAccountData[IOSU_ACT_ACCOUNT_MAX_COUNT] = {};
bool _actAccountDataInitialized = false; bool _actAccountDataInitialized = false;
int _actAccountCount = 0;
void FillAccountData(const Account& account, const bool online_enabled, int index) void FillAccountData(const Account& account, const bool online_enabled, int index)
{ {
@ -108,26 +109,22 @@ void iosuAct_loadAccounts()
return; return;
const bool online_enabled = ActiveSettings::IsOnlineEnabled(); const bool online_enabled = ActiveSettings::IsOnlineEnabled();
const auto persistent_id = ActiveSettings::GetPersistentId();
// first account is always our selected one // Load all accounts in common.dat order; the active account keeps its natural position
int counter = 0; int counter = 0;
const auto& first_acc = Account::GetAccount(persistent_id); for (const auto& account : Account::GetAccounts())
FillAccountData(first_acc, online_enabled, counter); {
++counter; if (counter >= IOSU_ACT_ACCOUNT_MAX_COUNT)
// enable multiple accounts for cafe functions (badly tested) break;
//for (const auto& account : Account::GetAccounts()) FillAccountData(account, online_enabled, counter);
//{ ++counter;
// if (first_acc.GetPersistentId() != account.GetPersistentId()) }
// { _actAccountCount = counter;
// FillAccountData(account, online_enabled, counter);
// ++counter;
// }
//}
cemuLog_log(LogType::Force, "IOSU_ACT: using account {} in first slot", boost::nowide::narrow(first_acc.GetMiiName()));
_actAccountDataInitialized = true; _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() bool iosuAct_isAccountDataLoaded()
@ -135,6 +132,11 @@ bool iosuAct_isAccountDataLoaded()
return _actAccountDataInitialized; return _actAccountDataInitialized;
} }
int iosuAct_getNumAccounts()
{
return _actAccountCount;
}
uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid) uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid)
{ {
NAPI::AuthInfo authInfo; NAPI::AuthInfo authInfo;
@ -154,10 +156,17 @@ uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid)
sint32 iosuAct_getAccountIndexBySlot(uint8 slot) sint32 iosuAct_getAccountIndexBySlot(uint8 slot)
{ {
if (slot == iosu::act::ACT_SLOT_CURRENT) if (slot == iosu::act::ACT_SLOT_CURRENT || slot == 0xFF)
return 0; {
if (slot == 0xFF) // find the active account's actual index by persistent ID
return 0; // ? 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 != 0);
cemu_assert_debug(slot <= IOSU_ACT_ACCOUNT_MAX_COUNT); cemu_assert_debug(slot <= IOSU_ACT_ACCOUNT_MAX_COUNT);
return slot - 1; return slot - 1;
@ -165,8 +174,9 @@ sint32 iosuAct_getAccountIndexBySlot(uint8 slot)
uint32 iosuAct_getAccountIdOfCurrentAccount() uint32 iosuAct_getAccountIdOfCurrentAccount()
{ {
cemu_assert_debug(_actAccountData[0].isValid); const sint32 index = iosuAct_getAccountIndexBySlot(iosu::act::ACT_SLOT_CURRENT);
return _actAccountData[0].persistentId; cemu_assert_debug(_actAccountData[index].isValid);
return _actAccountData[index].persistentId;
} }
// IOSU act API interface // IOSU act API interface
@ -386,7 +396,13 @@ namespace iosu
{ {
uint8 getCurrentAccountSlot() 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) actAccountData_t* GetAccountBySlotNo(uint8 slotNo)

View File

@ -121,4 +121,5 @@ struct iosuActCemuRequest_t
uint32 iosuAct_getAccountIdOfCurrentAccount(); 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/OS/libs/nn_common.h"
#include "Cafe/CafeSystem.h" #include "Cafe/CafeSystem.h"
#include "Common/CafeString.h" #include "Common/CafeString.h"
#include "Common/FileStream.h"
sint32 numAccounts = 1; #include "config/ActiveSettings.h"
#include "util/helpers/helpers.h"
#include "Cafe/Account/Account.h"
#define actPrepareRequest() \ #define actPrepareRequest() \
StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \ StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \
@ -186,7 +188,7 @@ void nnActExport_CreateConsoleAccount(PPCInterpreter_t* hCPU)
void nnActExport_GetNumOfAccounts(PPCInterpreter_t* hCPU) void nnActExport_GetNumOfAccounts(PPCInterpreter_t* hCPU)
{ {
cemuLog_logDebug(LogType::Force, "nn_act.GetNumOfAccounts()"); cemuLog_logDebug(LogType::Force, "nn_act.GetNumOfAccounts()");
osLib_returnFromFunction(hCPU, numAccounts); // account count osLib_returnFromFunction(hCPU, iosuAct_getNumAccounts());
} }
void nnActExport_IsSlotOccupied(PPCInterpreter_t* hCPU) void nnActExport_IsSlotOccupied(PPCInterpreter_t* hCPU)
@ -267,6 +269,22 @@ void nnActExport_IsNetworkAccountEx(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, isNetAcc); 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 != 1)
{
osLib_returnFromFunction(hCPU, 0);
return;
}
const Account& account = Account::GetAccount(persistentId);
osLib_returnFromFunction(hCPU, account.IsPasswordCacheEnabled() ? 1 : 0);
}
void nnActExport_GetSimpleAddressId(PPCInterpreter_t* hCPU) void nnActExport_GetSimpleAddressId(PPCInterpreter_t* hCPU)
{ {
cemuLog_logDebug(LogType::Force, "nn_act.GetSimpleAddressId()"); cemuLog_logDebug(LogType::Force, "nn_act.GetSimpleAddressId()");
@ -402,11 +420,72 @@ void nnActExport_GetMiiEx(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, r); 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) 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) 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); uint32 r = nn::act::GetMiiEx(&miiData, iosu::act::ACT_SLOT_CURRENT);
// extract name // extract name
sint32 miiNameLength = 0; sint32 miiNameLength = 0;
for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++)
{ {
miiName[i] = miiData->miiName[i]; miiName[i] = miiData->miiName[i];
@ -567,8 +646,8 @@ void nnActExport_GetDefaultAccount(PPCInterpreter_t* hCPU)
void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU) void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU)
{ {
// id of active account // returns the 1-based slot number of the currently active account
osLib_returnFromFunction(hCPU, 1); // 1 is the first slot (0 is invalid) osLib_returnFromFunction(hCPU, iosu::act::getCurrentAccountSlot());
} }
void nnActExport_GetSlotNoEx(PPCInterpreter_t* hCPU) 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", "GetSlotNoEx__Q2_2nn3actFRC7ACTUuid", nnActExport_GetSlotNoEx);
osLib_addFunction("nn_act", "IsNetworkAccount__Q2_2nn3actFv", nnActExport_IsNetworkAccount); osLib_addFunction("nn_act", "IsNetworkAccount__Q2_2nn3actFv", nnActExport_IsNetworkAccount);
osLib_addFunction("nn_act", "IsNetworkAccountEx__Q2_2nn3actFUc", nnActExport_IsNetworkAccountEx); osLib_addFunction("nn_act", "IsNetworkAccountEx__Q2_2nn3actFUc", nnActExport_IsNetworkAccountEx);
osLib_addFunction("nn_act", "IsPasswordCacheEnabledEx__Q2_2nn3actFUc", nnActExport_IsPasswordCacheEnabledEx);
// account id // account id
osLib_addFunction("nn_act", "GetAccountId__Q2_2nn3actFPc", nnActExport_GetAccountId); osLib_addFunction("nn_act", "GetAccountId__Q2_2nn3actFPc", nnActExport_GetAccountId);
osLib_addFunction("nn_act", "GetAccountIdEx__Q2_2nn3actFPcUc", nnActExport_GetAccountIdEx); 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 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 nn
{ {
namespace act namespace act

View File

@ -1277,6 +1277,12 @@ void GeneralSettings2::StoreConfig()
// account // account
config.account.m_persistent_id = GetSelectedAccountPersistentId(); config.account.m_persistent_id = GetSelectedAccountPersistentId();
{
std::vector<uint32> orderedIds;
for (const auto& acc : Account::GetAccounts())
orderedIds.push_back(acc.GetPersistentId());
Account::WriteCommonDat(orderedIds);
}
// debug // debug
config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); config.crash_dump = (CrashDump)m_crash_dump->GetSelection();

View File

@ -957,6 +957,10 @@ void MainWindow::OnAccountSelect(wxCommandEvent& event)
auto& config = GetConfig(); auto& config = GetConfig();
config.account.m_persistent_id = accounts[index].GetPersistentId(); config.account.m_persistent_id = accounts[index].GetPersistentId();
// config.account.online_enabled.value = false; // reset online for safety // config.account.online_enabled.value = false; // reset online for safety
std::vector<uint32> orderedIds;
for (const auto& acc : accounts)
orderedIds.push_back(acc.GetPersistentId());
Account::WriteCommonDat(orderedIds);
GetConfigHandle().Save(); GetConfigHandle().Save();
} }