Move getMiiImage logic to iosu + improvments and fixes

This commit is contained in:
SIrHrVedel 2026-05-13 12:10:23 +02:00
parent 3aa4744a04
commit d840591872
3 changed files with 58 additions and 75 deletions

View File

@ -11,6 +11,7 @@
#include "Cafe/Account/Account.h" #include "Cafe/Account/Account.h"
#include "config/ActiveSettings.h" #include "config/ActiveSettings.h"
#include "util/helpers/helpers.h" #include "util/helpers/helpers.h"
#include "Common/FileStream.h"
#include "Cemu/napi/napi.h" #include "Cemu/napi/napi.h"
#include "Cemu/ncrypto/ncrypto.h" #include "Cemu/ncrypto/ncrypto.h"
@ -554,6 +555,44 @@ namespace iosu
return true; 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) nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen)
{ {
if (accountSlot != ACT_SLOT_CURRENT) if (accountSlot != ACT_SLOT_CURRENT)

View File

@ -50,6 +50,7 @@ namespace iosu
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]);
bool getCountryIndex(uint8 slot, uint32* countryIndex); bool getCountryIndex(uint8 slot, uint32* countryIndex);
bool GetPersistentId(uint8 slot, uint32* persistentId); bool GetPersistentId(uint8 slot, uint32* persistentId);
uint32 GetMiiImage(uint8 slot, uint32 imageType, void* outBuf, uint32 bufferSize, uint32* outImageSize);
std::string getAccountId2(uint8 slot); std::string getAccountId2(uint8 slot);

View File

@ -6,9 +6,6 @@
#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"
#include "config/ActiveSettings.h"
#include "util/helpers/helpers.h"
#define actPrepareRequest() \ #define actPrepareRequest() \
StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \ StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \
@ -24,6 +21,10 @@ StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \
iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \
memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); 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) uint32 getNNReturnCode(uint32 iosError, iosuActCemuRequest_t* actRequest)
{ {
if ((iosError & 0x80000000) != 0) if ((iosError & 0x80000000) != 0)
@ -135,9 +136,8 @@ namespace act
sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo) sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo)
{ {
if (!pOutOffset) if (!pOutOffset)
return 0xc0712c80; return ACTResult_NullPointer;
*pOutOffset = GetUtcOffset(); *pOutOffset = GetUtcOffset();
return 0; return 0;
@ -145,7 +145,6 @@ namespace act
uint32 IsPasswordCacheEnabledEx(uint8 slot) uint32 IsPasswordCacheEnabledEx(uint8 slot)
{ {
// this is currently a hack.
// todo: read the value from the individual account via IOSU // todo: read the value from the individual account via IOSU
return 1; return 1;
} }
@ -155,6 +154,18 @@ namespace act
return IsPasswordCacheEnabledEx(ACT_SLOT_CURRENT); 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) nnResult GetTimeZoneId(CafeString<65>* outTimezoneId)
{ {
// return a placeholder timezone id for now // return a placeholder timezone id for now
@ -415,74 +426,6 @@ 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)
{
// 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) void nnActExport_GetMiiName(PPCInterpreter_t* hCPU)
{ {
cemuLog_logDebug(LogType::Force, "GetMiiName(0x{:08x})", hCPU->gpr[3]); 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", "GetMii__Q2_2nn3actFP12FFLStoreData", nnActExport_GetMii);
osLib_addFunction("nn_act", "GetMiiEx__Q2_2nn3actFP12FFLStoreDataUc", nnActExport_GetMiiEx); 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", "GetMiiName__Q2_2nn3actFPw", nnActExport_GetMiiName);
osLib_addFunction("nn_act", "GetMiiNameEx__Q2_2nn3actFPwUc", nnActExport_GetMiiNameEx); osLib_addFunction("nn_act", "GetMiiNameEx__Q2_2nn3actFPwUc", nnActExport_GetMiiNameEx);