From d840591872ac79b00217f01d48c7594d12e4628e Mon Sep 17 00:00:00 2001 From: SIrHrVedel <38987957+SirHrVedel@users.noreply.github.com> Date: Wed, 13 May 2026 12:10:23 +0200 Subject: [PATCH] Move getMiiImage logic to iosu + improvments and fixes --- src/Cafe/IOSU/legacy/iosu_act.cpp | 39 +++++++++++++ src/Cafe/IOSU/legacy/iosu_act.h | 1 + src/Cafe/OS/libs/nn_act/nn_act.cpp | 93 ++++++------------------------ 3 files changed, 58 insertions(+), 75 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index c0e5d89d..ca60045c 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -11,6 +11,7 @@ #include "Cafe/Account/Account.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" +#include "Common/FileStream.h" #include "Cemu/napi/napi.h" #include "Cemu/ncrypto/ncrypto.h" @@ -554,6 +555,44 @@ namespace iosu return true; } + uint32 GetMiiImage(uint8 slot, uint32 imageType, void* outBuf, uint32 bufferSize, uint32* outImageSize) + { + uint32 persistentId = 0; + if (!GetPersistentId(slot, &persistentId)) + return ACTResult_AccountDoesNotExist; + + static constexpr uint32 kMaxMiiImageBytes = 2 * 1024 * 1024; + + const auto filePath = ActiveSettings::GetMlcPath( + fmt::format("usr/save/system/act/{:08x}/miiimg{:02d}.dat", persistentId, imageType)); + + // reject oversized files before allocating. Prevents loading multi-GB crafted files into RAM + std::error_code ec; + if (fs::file_size(filePath, ec) > kMaxMiiImageBytes || ec) + return ACTResult_AccountDoesNotExist; + + auto fileData = FileStream::LoadIntoMemory(filePath); + if (!fileData.has_value()) + return ACTResult_AccountDoesNotExist; + + // type 0 (FaceIcon) is stored raw; all others are zlib-compressed + if (imageType != 0) + { + fileData = zlibDecompress(*fileData); + if (!fileData.has_value()) + return ACTResult_AccountDoesNotExist; + } + + if (fileData->size() > kMaxMiiImageBytes) + return ACTResult_AccountDoesNotExist; + + *outImageSize = (uint32)fileData->size(); + if (bufferSize < *outImageSize) + return ACTResult_OutOfRange; + memcpy(outBuf, fileData->data(), fileData->size()); + return 0; + } + nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen) { if (accountSlot != ACT_SLOT_CURRENT) diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index 4bfd0dd0..127361d8 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -50,6 +50,7 @@ namespace iosu bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getCountryIndex(uint8 slot, uint32* countryIndex); bool GetPersistentId(uint8 slot, uint32* persistentId); + uint32 GetMiiImage(uint8 slot, uint32 imageType, void* outBuf, uint32 bufferSize, uint32* outImageSize); std::string getAccountId2(uint8 slot); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 167ff969..177b37af 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -6,9 +6,6 @@ #include "Cafe/OS/libs/nn_common.h" #include "Cafe/CafeSystem.h" #include "Common/CafeString.h" -#include "Common/FileStream.h" -#include "config/ActiveSettings.h" -#include "util/helpers/helpers.h" #define actPrepareRequest() \ StackAllocator _buf_actRequest; \ @@ -24,6 +21,10 @@ StackAllocator _buf_actRequest; \ iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); +// Returned by ACT functions when a required output pointer is null (matches native RPL behaviour) +static const uint32 ACTResult_NullPointer = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12C80); +static const uint32 ACTResult_AccountDoesNotExist = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST); + uint32 getNNReturnCode(uint32 iosError, iosuActCemuRequest_t* actRequest) { if ((iosError & 0x80000000) != 0) @@ -135,9 +136,8 @@ namespace act sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo) { - if (!pOutOffset) - return 0xc0712c80; + return ACTResult_NullPointer; *pOutOffset = GetUtcOffset(); return 0; @@ -145,7 +145,6 @@ namespace act uint32 IsPasswordCacheEnabledEx(uint8 slot) { - // this is currently a hack. // todo: read the value from the individual account via IOSU return 1; } @@ -155,6 +154,18 @@ namespace act return IsPasswordCacheEnabledEx(ACT_SLOT_CURRENT); } + uint32 GetMiiImageEx(uint32be* outImageSize, MEMPTR buffer, uint32 bufferSize, uint32 imageType, uint8 slot) + { + if (!outImageSize || !buffer.GetPtr()) + return ACTResult_NullPointer; + if (imageType > ACT_MII_IMAGE_TYPE_MAX) + return ACTResult_AccountDoesNotExist; + uint32 size = 0; + uint32 r = iosu::act::GetMiiImage(slot, imageType, buffer.GetPtr(), bufferSize, &size); + *outImageSize = size; + return r; + } + nnResult GetTimeZoneId(CafeString<65>* outTimezoneId) { // return a placeholder timezone id for now @@ -415,74 +426,6 @@ 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) -{ - // 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); - - 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) { cemuLog_logDebug(LogType::Force, "GetMiiName(0x{:08x})", hCPU->gpr[3]); @@ -805,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);