From 2639885b2b9916eaa47a57f40c00ce9e565b1ffd Mon Sep 17 00:00:00 2001 From: SIrHrVedel Date: Sun, 26 Apr 2026 23:52:51 +0200 Subject: [PATCH 1/8] Inital commit --- src/Cafe/Account/Account.cpp | 235 +++++++++++++++++++++++------ src/Cafe/Account/Account.h | 3 +- src/Cafe/IOSU/legacy/iosu_act.cpp | 64 +++++--- src/Cafe/IOSU/legacy/iosu_act.h | 3 +- src/Cafe/OS/libs/nn_act/nn_act.cpp | 96 +++++++++++- src/Cafe/OS/libs/nn_act/nn_act.h | 16 ++ src/gui/wxgui/GeneralSettings2.cpp | 6 + src/gui/wxgui/MainWindow.cpp | 4 + 8 files changed, 348 insertions(+), 79 deletions(-) diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp index 6f576ae1..0d7dc575 100644 --- a/src/Cafe/Account/Account.cpp +++ b/src/Cafe/Account/Account.cpp @@ -6,6 +6,8 @@ #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Common/FileStream.h" #include +#include +#include #include @@ -300,11 +302,12 @@ void Account::SetMiiName(std::wstring_view name) const std::vector& Account::RefreshAccounts() { - std::vector result; - const fs::path path = ActiveSettings::GetMlcPath("usr/save/system/act"); - if (fs::exists(path)) + // Step 1: Scan all account directories into a sorted map + std::map accountMap; + 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)) continue; @@ -320,37 +323,201 @@ const std::vector& Account::RefreshAccounts() Account account(persistent_id); const auto error = account.Load(); if (!error) - result.emplace_back(account); + accountMap.emplace(persistent_id, std::move(account)); } } - - // we always force at least one account + + std::vector 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()) { result.emplace_back(kMinPersistendId, L"default"); 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 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; } -void Account::UpdatePersisidDat() +std::vector Account::ReadCommonDat() { - const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); - const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); - std::ofstream f(file); - if(f.is_open()) + std::vector result; + const auto file_path = ActiveSettings::GetMlcPath("usr/save/system/act/common.dat"); + if (!fs::exists(file_path)) + 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; - f.flush(); - f.close(); + if (!boost::starts_with(line, "PersistentIdList=")) + continue; + + 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(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 - cemuLog_log(LogType::Force, "Unable to save persisid.dat"); + return result; } +void Account::WriteCommonDat(const std::vector& 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> 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() { return s_account_list.size() < 12; @@ -382,33 +549,11 @@ const Account& Account::GetCurrentAccount() uint32 Account::GetNextPersistentId() { - uint32 result = kMinPersistendId; - const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); - 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(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(); }); + 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); - else - return result; + return it->GetPersistentId() + 1; + return kMinPersistendId; } fs::path Account::GetFileName(uint32 persistent_id) diff --git a/src/Cafe/Account/Account.h b/src/Cafe/Account/Account.h index c9abd395..e9e80a49 100644 --- a/src/Cafe/Account/Account.h +++ b/src/Cafe/Account/Account.h @@ -93,7 +93,8 @@ public: // this will always return at least one account (default one) static const std::vector& RefreshAccounts(); - static void UpdatePersisidDat(); + static std::vector ReadCommonDat(); + static void WriteCommonDat(const std::vector& orderedIds); [[nodiscard]] static bool HasFreeAccountSlots(); [[nodiscard]] static const std::vector& GetAccounts(); diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 11e613ac..6d726743 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -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 common.dat order; 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) diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index 6d5c5c26..4bfd0dd0 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -121,4 +121,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..f2e160cb 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -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 _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 != 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) { 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 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); 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 diff --git a/src/gui/wxgui/GeneralSettings2.cpp b/src/gui/wxgui/GeneralSettings2.cpp index 003bd130..b78cb03f 100644 --- a/src/gui/wxgui/GeneralSettings2.cpp +++ b/src/gui/wxgui/GeneralSettings2.cpp @@ -1277,6 +1277,12 @@ void GeneralSettings2::StoreConfig() // account config.account.m_persistent_id = GetSelectedAccountPersistentId(); + { + std::vector orderedIds; + for (const auto& acc : Account::GetAccounts()) + orderedIds.push_back(acc.GetPersistentId()); + Account::WriteCommonDat(orderedIds); + } // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index 4ef4fe08..2f881110 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -957,6 +957,10 @@ void MainWindow::OnAccountSelect(wxCommandEvent& event) auto& config = GetConfig(); config.account.m_persistent_id = accounts[index].GetPersistentId(); // config.account.online_enabled.value = false; // reset online for safety + std::vector orderedIds; + for (const auto& acc : accounts) + orderedIds.push_back(acc.GetPersistentId()); + Account::WriteCommonDat(orderedIds); GetConfigHandle().Save(); } From 125df7bbfd64152ef6cce50776d946bb0f558f04 Mon Sep 17 00:00:00 2001 From: SIrHrVedel Date: Mon, 27 Apr 2026 01:10:16 +0200 Subject: [PATCH 2/8] Fix persistantId check in IsPasswordCacheEnabled --- src/Cafe/OS/libs/nn_act/nn_act.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index f2e160cb..9262670e 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -275,7 +275,7 @@ void nnActExport_IsPasswordCacheEnabledEx(PPCInterpreter_t* hCPU) cemuLog_logDebug(LogType::Force, "nn_act.IsPasswordCacheEnabledEx({})", slot); const uint32 persistentId = nn::act::GetPersistentIdEx(slot); - if (persistentId != 1) + if (persistentId == 0) { osLib_returnFromFunction(hCPU, 0); return; From 4c70da2508632c22817b08c357eaceb0107619ea Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Mon, 27 Apr 2026 02:44:19 +0200 Subject: [PATCH 3/8] Whoopsies: Restore persisId.dat code --- src/Cafe/Account/Account.cpp | 232 +++++++---------------------------- src/Cafe/Account/Account.h | 3 +- 2 files changed, 45 insertions(+), 190 deletions(-) diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp index 0d7dc575..09f667d7 100644 --- a/src/Cafe/Account/Account.cpp +++ b/src/Cafe/Account/Account.cpp @@ -6,8 +6,6 @@ #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Common/FileStream.h" #include -#include -#include #include @@ -302,12 +300,11 @@ void Account::SetMiiName(std::wstring_view name) const std::vector& Account::RefreshAccounts() { - // Step 1: Scan all account directories into a sorted map - std::map accountMap; - const fs::path act_path = ActiveSettings::GetMlcPath("usr/save/system/act"); - if (fs::exists(act_path)) + std::vector result; + const fs::path path = ActiveSettings::GetMlcPath("usr/save/system/act"); + if (fs::exists(path)) { - for (const auto& it : fs::directory_iterator(act_path)) + for (const auto& it : fs::directory_iterator(path)) { if (!fs::is_directory(it)) continue; @@ -323,198 +320,35 @@ const std::vector& Account::RefreshAccounts() Account account(persistent_id); const auto error = account.Load(); if (!error) - accountMap.emplace(persistent_id, std::move(account)); + result.emplace_back(account); } } - std::vector 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 + // we always force at least one account if (result.empty()) { result.emplace_back(kMinPersistendId, L"default"); result.begin()->Save(); } - - s_account_list = std::move(result); - - // Write updated common.dat (also updates PersistentIdHead and DefaultAccountPersistentId) - std::vector orderedIds; - orderedIds.reserve(s_account_list.size()); - for (const auto& acc : s_account_list) - orderedIds.push_back(acc.GetPersistentId()); - WriteCommonDat(orderedIds); - + s_account_list = result; + UpdatePersisidDat(); return s_account_list; } -std::vector Account::ReadCommonDat() +void Account::UpdatePersisidDat() { - std::vector result; - const auto file_path = ActiveSettings::GetMlcPath("usr/save/system/act/common.dat"); - if (!fs::exists(file_path)) - return result; - - std::ifstream f(file_path); - if (!f.is_open()) - return result; - - std::string line; - while (std::getline(f, line)) + const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); + const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); + std::ofstream f(file); + if(f.is_open()) { - if (!boost::starts_with(line, "PersistentIdList=")) - continue; - - 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(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 + f << "PersistentIdManager_20120607" << std::endl << "PersistentIdHead=" << std::hex << max_id << std::endl << std::endl; + f.flush(); + f.close(); } - return result; -} - -void Account::WriteCommonDat(const std::vector& 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> 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(); + else + cemuLog_log(LogType::Force, "Unable to save persisid.dat"); } @@ -549,11 +383,33 @@ const Account& Account::GetCurrentAccount() uint32 Account::GetNextPersistentId() { - 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(); }); + uint32 result = kMinPersistendId; + const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); + 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(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()) - return it->GetPersistentId() + 1; - return kMinPersistendId; + return std::max(result, it->GetPersistentId() + 1); + else + return result; } fs::path Account::GetFileName(uint32 persistent_id) diff --git a/src/Cafe/Account/Account.h b/src/Cafe/Account/Account.h index e9e80a49..c9abd395 100644 --- a/src/Cafe/Account/Account.h +++ b/src/Cafe/Account/Account.h @@ -93,8 +93,7 @@ public: // this will always return at least one account (default one) static const std::vector& RefreshAccounts(); - static std::vector ReadCommonDat(); - static void WriteCommonDat(const std::vector& orderedIds); + static void UpdatePersisidDat(); [[nodiscard]] static bool HasFreeAccountSlots(); [[nodiscard]] static const std::vector& GetAccounts(); From 059e2f80727697210a523960fc0d08f2b3433f22 Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Mon, 27 Apr 2026 02:51:23 +0200 Subject: [PATCH 4/8] Accidentally reversed all the common.dat code. Restore it. --- src/Cafe/Account/Account.cpp | 187 +++++++++++++++++++++++++++++++++-- src/Cafe/Account/Account.h | 2 + 2 files changed, 182 insertions(+), 7 deletions(-) diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp index 09f667d7..d7dcbf5c 100644 --- a/src/Cafe/Account/Account.cpp +++ b/src/Cafe/Account/Account.cpp @@ -6,6 +6,8 @@ #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Common/FileStream.h" #include +#include +#include #include @@ -300,11 +302,12 @@ void Account::SetMiiName(std::wstring_view name) const std::vector& Account::RefreshAccounts() { - std::vector result; - const fs::path path = ActiveSettings::GetMlcPath("usr/save/system/act"); - if (fs::exists(path)) + // Step 1: Scan all account directories into a sorted map + std::map accountMap; + 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)) continue; @@ -320,22 +323,192 @@ const std::vector& Account::RefreshAccounts() Account account(persistent_id); const auto error = account.Load(); if (!error) - result.emplace_back(account); + accountMap.emplace(persistent_id, std::move(account)); } } - // we always force at least one account + std::vector 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()) { result.emplace_back(kMinPersistendId, L"default"); result.begin()->Save(); } - s_account_list = result; + s_account_list = std::move(result); + + // Write updated common.dat ordering and persisid.dat + std::vector orderedIds; + orderedIds.reserve(s_account_list.size()); + for (const auto& acc : s_account_list) + orderedIds.push_back(acc.GetPersistentId()); + WriteCommonDat(orderedIds); UpdatePersisidDat(); + return s_account_list; } +std::vector Account::ReadCommonDat() +{ + std::vector result; + const auto file_path = ActiveSettings::GetMlcPath("usr/save/system/act/common.dat"); + if (!fs::exists(file_path)) + return result; + + std::ifstream f(file_path); + if (!f.is_open()) + return result; + + std::string line; + while (std::getline(f, line)) + { + if (!boost::starts_with(line, "PersistentIdList=")) + continue; + + std::string list = line.substr(sizeof("PersistentIdList=") - 1); + 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; + + const auto id = ConvertString(entry, 16); + if (id >= kMinPersistendId) + result.push_back(id); + + if (sep == std::string::npos) + break; + pos = sep + 2; + } + break; + } + return result; +} + +void Account::WriteCommonDat(const std::vector& 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> 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"); + } + + // Update DefaultAccountPersistentId to the currently active account + for (auto& [k, v] : meta_lines) + { + if (k == "DefaultAccountPersistentId") + { + v = fmt::format("{:08x}", ActiveSettings::GetPersistentId()); + break; + } + } + + // 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(); +} + void Account::UpdatePersisidDat() { const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); diff --git a/src/Cafe/Account/Account.h b/src/Cafe/Account/Account.h index c9abd395..b6b154d4 100644 --- a/src/Cafe/Account/Account.h +++ b/src/Cafe/Account/Account.h @@ -94,6 +94,8 @@ public: // this will always return at least one account (default one) static const std::vector& RefreshAccounts(); static void UpdatePersisidDat(); + static std::vector ReadCommonDat(); + static void WriteCommonDat(const std::vector& orderedIds); [[nodiscard]] static bool HasFreeAccountSlots(); [[nodiscard]] static const std::vector& GetAccounts(); From 27a48b644da4d5822aa62466d2e095d077772250 Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:28:21 +0200 Subject: [PATCH 5/8] Revert "Accidentally reversed all the common.dat code. Restore it." This reverts commit 059e2f80727697210a523960fc0d08f2b3433f22. --- src/Cafe/Account/Account.cpp | 187 ++--------------------------------- src/Cafe/Account/Account.h | 2 - 2 files changed, 7 insertions(+), 182 deletions(-) diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp index d7dcbf5c..09f667d7 100644 --- a/src/Cafe/Account/Account.cpp +++ b/src/Cafe/Account/Account.cpp @@ -6,8 +6,6 @@ #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Common/FileStream.h" #include -#include -#include #include @@ -302,12 +300,11 @@ void Account::SetMiiName(std::wstring_view name) const std::vector& Account::RefreshAccounts() { - // Step 1: Scan all account directories into a sorted map - std::map accountMap; - const fs::path act_path = ActiveSettings::GetMlcPath("usr/save/system/act"); - if (fs::exists(act_path)) + std::vector result; + const fs::path path = ActiveSettings::GetMlcPath("usr/save/system/act"); + if (fs::exists(path)) { - for (const auto& it : fs::directory_iterator(act_path)) + for (const auto& it : fs::directory_iterator(path)) { if (!fs::is_directory(it)) continue; @@ -323,192 +320,22 @@ const std::vector& Account::RefreshAccounts() Account account(persistent_id); const auto error = account.Load(); if (!error) - accountMap.emplace(persistent_id, std::move(account)); + result.emplace_back(account); } } - std::vector 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 + // we always force at least one account if (result.empty()) { result.emplace_back(kMinPersistendId, L"default"); result.begin()->Save(); } - s_account_list = std::move(result); - - // Write updated common.dat ordering and persisid.dat - std::vector orderedIds; - orderedIds.reserve(s_account_list.size()); - for (const auto& acc : s_account_list) - orderedIds.push_back(acc.GetPersistentId()); - WriteCommonDat(orderedIds); + s_account_list = result; UpdatePersisidDat(); - return s_account_list; } -std::vector Account::ReadCommonDat() -{ - std::vector result; - const auto file_path = ActiveSettings::GetMlcPath("usr/save/system/act/common.dat"); - if (!fs::exists(file_path)) - return result; - - std::ifstream f(file_path); - if (!f.is_open()) - return result; - - std::string line; - while (std::getline(f, line)) - { - if (!boost::starts_with(line, "PersistentIdList=")) - continue; - - std::string list = line.substr(sizeof("PersistentIdList=") - 1); - 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; - - const auto id = ConvertString(entry, 16); - if (id >= kMinPersistendId) - result.push_back(id); - - if (sep == std::string::npos) - break; - pos = sep + 2; - } - break; - } - return result; -} - -void Account::WriteCommonDat(const std::vector& 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> 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"); - } - - // Update DefaultAccountPersistentId to the currently active account - for (auto& [k, v] : meta_lines) - { - if (k == "DefaultAccountPersistentId") - { - v = fmt::format("{:08x}", ActiveSettings::GetPersistentId()); - break; - } - } - - // 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(); -} - void Account::UpdatePersisidDat() { const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); diff --git a/src/Cafe/Account/Account.h b/src/Cafe/Account/Account.h index b6b154d4..c9abd395 100644 --- a/src/Cafe/Account/Account.h +++ b/src/Cafe/Account/Account.h @@ -94,8 +94,6 @@ public: // this will always return at least one account (default one) static const std::vector& RefreshAccounts(); static void UpdatePersisidDat(); - static std::vector ReadCommonDat(); - static void WriteCommonDat(const std::vector& orderedIds); [[nodiscard]] static bool HasFreeAccountSlots(); [[nodiscard]] static const std::vector& GetAccounts(); From 88f2e06328fdc79d65f34b6930c51d25af6757c4 Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:38:11 +0200 Subject: [PATCH 6/8] Remove write to common.dat --- src/gui/wxgui/GeneralSettings2.cpp | 6 ------ src/gui/wxgui/MainWindow.cpp | 4 ---- 2 files changed, 10 deletions(-) diff --git a/src/gui/wxgui/GeneralSettings2.cpp b/src/gui/wxgui/GeneralSettings2.cpp index b78cb03f..003bd130 100644 --- a/src/gui/wxgui/GeneralSettings2.cpp +++ b/src/gui/wxgui/GeneralSettings2.cpp @@ -1277,12 +1277,6 @@ void GeneralSettings2::StoreConfig() // account config.account.m_persistent_id = GetSelectedAccountPersistentId(); - { - std::vector orderedIds; - for (const auto& acc : Account::GetAccounts()) - orderedIds.push_back(acc.GetPersistentId()); - Account::WriteCommonDat(orderedIds); - } // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index 2f881110..4ef4fe08 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -957,10 +957,6 @@ void MainWindow::OnAccountSelect(wxCommandEvent& event) auto& config = GetConfig(); config.account.m_persistent_id = accounts[index].GetPersistentId(); // config.account.online_enabled.value = false; // reset online for safety - std::vector orderedIds; - for (const auto& acc : accounts) - orderedIds.push_back(acc.GetPersistentId()); - Account::WriteCommonDat(orderedIds); GetConfigHandle().Save(); } From 0fbee649a1a55c3b37120b25ea2c4eb87d02c6ea Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:48:34 +0200 Subject: [PATCH 7/8] Ensure account ordering are sorted accending by their persistantId --- src/Cafe/Account/Account.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp index 09f667d7..b22cf274 100644 --- a/src/Cafe/Account/Account.cpp +++ b/src/Cafe/Account/Account.cpp @@ -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; From 6aba0724b0523aed441e5a65ec6c453bd1b4466e Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:58:13 +0200 Subject: [PATCH 8/8] Update comment for loadAccounts to reflect the removal of the common.dat logic --- src/Cafe/IOSU/legacy/iosu_act.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 6d726743..c0e5d89d 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -110,7 +110,7 @@ void iosuAct_loadAccounts() const bool online_enabled = ActiveSettings::IsOnlineEnabled(); - // Load all accounts in common.dat order; the active account keeps its natural position + // Load all accounts in order of persistantId; the active account keeps its natural position int counter = 0; for (const auto& account : Account::GetAccounts()) {