nn_boss: Reimplementation

This is a full rewrite of our nn_boss (SpotPass) implementation. The previous code was based on a lot of incorrect guesswork so rather than updating that it made more sense to redo it all.

In short what changed:
- More API implemented than before, but nn_boss is very complex so we are still missing stuff (e.g. PlayReports and Task scheduling)
- Avoids redownloading nbdl files if they are already present locally (matches IOSU behavior)
- The API should be more robust in general and file hashes are now verified
- Emulated IOSU interface is compatible with nn_boss.rpl
- Added an UI option to clear the SpotPass cache
This commit is contained in:
Exzap 2025-06-14 12:59:30 +02:00
parent 3f6974fc95
commit 4fa0df6dcf
24 changed files with 3232 additions and 2223 deletions

View file

@ -238,8 +238,6 @@ add_library(CemuCafe
IOSU/legacy/iosu_acp.h IOSU/legacy/iosu_acp.h
IOSU/legacy/iosu_act.cpp IOSU/legacy/iosu_act.cpp
IOSU/legacy/iosu_act.h IOSU/legacy/iosu_act.h
IOSU/legacy/iosu_boss.cpp
IOSU/legacy/iosu_boss.h
IOSU/legacy/iosu_crypto.cpp IOSU/legacy/iosu_crypto.cpp
IOSU/legacy/iosu_crypto.h IOSU/legacy/iosu_crypto.h
IOSU/legacy/iosu_fpd.cpp IOSU/legacy/iosu_fpd.cpp
@ -252,6 +250,10 @@ add_library(CemuCafe
IOSU/legacy/iosu_nim.h IOSU/legacy/iosu_nim.h
IOSU/nn/iosu_nn_service.cpp IOSU/nn/iosu_nn_service.cpp
IOSU/nn/iosu_nn_service.h IOSU/nn/iosu_nn_service.h
IOSU/nn/boss/boss_common.cpp
IOSU/nn/boss/boss_common.h
IOSU/nn/boss/boss_service.cpp
IOSU/nn/boss/boss_service.h
IOSU/PDM/iosu_pdm.cpp IOSU/PDM/iosu_pdm.cpp
IOSU/PDM/iosu_pdm.h IOSU/PDM/iosu_pdm.h
IOSU/ODM/iosu_odm.cpp IOSU/ODM/iosu_odm.cpp
@ -418,6 +420,7 @@ add_library(CemuCafe
OS/libs/nn_ccr/nn_ccr.h OS/libs/nn_ccr/nn_ccr.h
OS/libs/nn_cmpt/nn_cmpt.cpp OS/libs/nn_cmpt/nn_cmpt.cpp
OS/libs/nn_cmpt/nn_cmpt.h OS/libs/nn_cmpt/nn_cmpt.h
OS/libs/nn_client_service.h
OS/libs/nn_common.h OS/libs/nn_common.h
OS/libs/nn_ec/nn_ec.cpp OS/libs/nn_ec/nn_ec.cpp
OS/libs/nn_ec/nn_ec.h OS/libs/nn_ec/nn_ec.h

View file

@ -33,10 +33,10 @@
#include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "Cafe/IOSU/legacy/iosu_mcp.h" #include "Cafe/IOSU/legacy/iosu_mcp.h"
#include "Cafe/IOSU/legacy/iosu_acp.h" #include "Cafe/IOSU/legacy/iosu_acp.h"
#include "Cafe/IOSU/legacy/iosu_boss.h"
#include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/legacy/iosu_nim.h"
#include "Cafe/IOSU/PDM/iosu_pdm.h" #include "Cafe/IOSU/PDM/iosu_pdm.h"
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" #include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
#include "Cafe/IOSU/nn/boss/boss_service.h"
// IOSU initializer functions // IOSU initializer functions
#include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/IOSU/kernel/iosu_kernel.h"
@ -551,6 +551,7 @@ namespace CafeSystem
iosu::fpd::GetModule(), iosu::fpd::GetModule(),
iosu::pdm::GetModule(), iosu::pdm::GetModule(),
iosu::ccr_nfc::GetModule(), iosu::ccr_nfc::GetModule(),
iosu::boss::GetModule()
}; };
// initialize all subsystems which are persistent and don't depend on a game running // initialize all subsystems which are persistent and don't depend on a game running
@ -589,7 +590,6 @@ namespace CafeSystem
iosu::iosuMcp_init(); iosu::iosuMcp_init();
iosu::mcp::Init(); iosu::mcp::Init();
iosu::iosuAcp_init(); iosu::iosuAcp_init();
iosu::boss_init();
iosu::nim::Initialize(); iosu::nim::Initialize();
iosu::odm::Initialize(); iosu::odm::Initialize();
// init Cafe OS // init Cafe OS

View file

@ -659,7 +659,10 @@ namespace iosu
for (uint32 i = 0; i < numIn + numOut; i++) for (uint32 i = 0; i < numIn + numOut; i++)
{ {
if (vec[i].baseVirt == nullptr && vec[i].size != 0) if (vec[i].baseVirt == nullptr && vec[i].size != 0)
{
cemuLog_logDebug(LogType::Force, "IPC Ioctlv failed because baseVirt is null but size is not 0");
return IOS_ERROR_INVALID; return IOS_ERROR_INVALID;
}
// todo - check for valid pointer range // todo - check for valid pointer range
vec[i].basePhys = vec[i].baseVirt; vec[i].basePhys = vec[i].baseVirt;
} }

View file

@ -782,7 +782,7 @@ namespace iosu
public: public:
AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {} AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {}
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override nnResult ServiceCall(IPCServiceCall& serviceCall) override
{ {
cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main"); cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main");
cemu_assert_unimplemented(); cemu_assert_unimplemented();

View file

@ -603,7 +603,7 @@ namespace iosu
public: public:
ActService() : iosu::nn::IPCService("/dev/act") {} ActService() : iosu::nn::IPCService("/dev/act") {}
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override nnResult ServiceCall(IPCServiceCall& serviceCall) override
{ {
cemuLog_log(LogType::Force, "Unsupported service call to /dev/act"); cemuLog_log(LogType::Force, "Unsupported service call to /dev/act");
cemu_assert_unimplemented(); cemu_assert_unimplemented();

File diff suppressed because it is too large Load diff

View file

@ -1,54 +0,0 @@
#pragma once
#include "iosu_ioctl.h"
// custom dev/boss protocol (Cemu only)
#define IOSU_BOSS_REQUEST_CEMU (0xEE)
typedef struct
{
uint32 requestCode;
// input
uint32 accountId;
char* taskId;
bool bool_parameter;
uint64 titleId;
uint32 timeout;
uint32 waitState;
uint32 uk1;
void* settings;
// output
uint32 returnCode; // ACP return value
union
{
struct
{
uint32 exec_count;
uint32 result;
} u32;
struct
{
uint32 exec_count;
uint64 result;
} u64;
};
}iosuBossCemuRequest_t;
#define IOSU_NN_BOSS_TASK_RUN (0x01)
#define IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH (0x02)
#define IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH (0x03)
#define IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE (0x04)
#define IOSU_NN_BOSS_TASK_GET_TURN_STATE (0x05)
#define IOSU_NN_BOSS_TASK_WAIT (0x06)
#define IOSU_NN_BOSS_TASK_REGISTER (0x07)
#define IOSU_NN_BOSS_TASK_IS_REGISTERED (0x08)
#define IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN (0x09)
#define IOSU_NN_BOSS_TASK_UNREGISTER (0x0A)
#define IOSU_NN_BOSS_TASK_START_SCHEDULING (0x0B)
#define IOSU_NN_BOSS_TASK_STOP_SCHEDULING (0x0C)
namespace iosu
{
void boss_init();
}

View file

View file

@ -0,0 +1,431 @@
#pragma once
#include "Common/CafeString.h"
namespace nn::boss
{
typedef uint32 BossResult;
struct VTableEntry
{
uint16be offsetA{0};
uint16be offsetB{0};
MEMPTR<void> ptr;
};
static_assert(sizeof(VTableEntry) == 8);
struct TitleId
{
uint64be u64{};
static bool IsValid(TitleId* _thisptr);
static TitleId* ctorDefault(TitleId* _thisptr);
static TitleId* ctorFromTitleId(TitleId* _thisptr, uint64 titleId); // __ct__Q3_2nn4boss7TitleIDFUL
static TitleId* ctorCopy(TitleId* _thisptr, TitleId* titleId); // __ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID
static bool operator_ne(TitleId* _thisptr, TitleId* titleId);
};
static_assert(sizeof(TitleId) == 8);
struct TaskId
{
CafeString<8> id;
TaskId() = default;
TaskId(const char* taskId) { id.assign(taskId); }
static TaskId* ctorDefault(TaskId* _thisptr);
static TaskId* ctorFromString(TaskId* _thisptr, const char* taskId);
//auto operator<=>(const TaskId& other) const = default;
std::strong_ordering operator<=>(const TaskId& other) const noexcept {
return id <=> other.id; // Delegate to CafeString's operator<=>
}
};
static_assert(sizeof(TaskId) == 8);
struct Title
{
uint32be accountId{}; // 0x00
TitleId titleId{}; // 0x8
MEMPTR<void> vTablePtr{}; // 0x10
struct VTable
{
VTableEntry rtti;
VTableEntry dtor;
};
static inline SysAllocator<VTable> s_titleVTable;
static Title* ctor(Title* _this);
static void dtor(Title* _this, uint32 options);
static void InitVTable();
};
static_assert(sizeof(Title) == 0x18);
struct DirectoryName
{
CafeString<8> name2;
static DirectoryName* ctor(DirectoryName* _thisptr);
static const char* operator_const_char(DirectoryName* _thisptr);
};
static_assert(sizeof(DirectoryName) == 8);
struct BossAccount // the actual class name is "Account" and while the boss namespace helps us separate this from Account(.h) we use an alternative name to avoid confusion
{
struct VTable
{
VTableEntry rtti;
VTableEntry dtor;
};
static inline SysAllocator<VTable> s_VTable;
uint32be accountId;
MEMPTR<void> vTablePtr;
static BossAccount* ctor(BossAccount* _this, uint32 accountId);
static void dtor(BossAccount* _this, uint32 options);
static void InitVTable();
};
static_assert(sizeof(BossAccount) == 8);
enum class TaskWaitState : uint32 // for Task::Wait()
{
Done = 1,
};
enum class TaskTurnState : uint32
{
Ukn = 0,
Stopped = 1,
Ready = 4,
Running = 6,
Done = 7, // how does this differ from DoneSuccess?
DoneSuccess = 16,
DoneError = 17
};
enum class TaskState : uint8
{
Initial = 0,
Stopped = 1,
Ready = 4, // waiting for turn to run
Running = 6,
Done = 7,
};
enum class TaskType : uint16
{
NbdlTaskSetting = 2,
RawDlTaskSetting_1 = 1,
RawDlTaskSetting_3 = 3,
RawDlTaskSetting_9 = 9,
RawUlTaskSetting = 4,
PlayLogUploadTaskSetting = 5,
PlayReportSetting = 6,
DataStoreDownloadSetting = 8,
NbdlDataListTaskSetting = 10
};
struct TaskSettingCore // the setting struct as used by IOSU
{
struct LifeTime
{
uint32be high;
uint32be low;
};
uint32be persistentId;
uint32be ukn04;
uint32be ukn08;
uint32be ukn0C;
uint32be ukn10;
uint32be ukn14;
uint32be ukn18;
uint32be ukn1C;
TaskId taskId; // +0x20
betype<TaskType> taskType; // +0x28
uint8be priority; // +0x2A
uint8be mode; // +0x2B
uint8be permission; // +0x2C
uint16be intervalA; // +0x2E
uint32be intervalB; // +0x30
uint32be unk34; // +0x34 - could be padding
LifeTime lifeTime; // +0x38 - this is a 64bit value, but the whole struct has a size of 0x1004 and doesnt preserve the alignment in an array. Its probably handled as two 32bit values?
uint8be httpProtocol; // +0x40
uint8be internalClientCert; // +0x41
uint16be httpOption; // +0x42
uint8 ukn44[0x48 - 0x44]; // padding?
CafeString<256> url; // +0x48
CafeString<64> lastModifiedTime; // +0x148
uint8be internalCaCert[3]; // +0x188
uint8 ukn18B; // +0x18B - padding?
uint32be httpTimeout; // +0x18C
CafeString<128> caCert[3]; // +0x190 - 3 entries, each 0x80 bytes
CafeString<128> clientCertName; // +0x310
CafeString<128> clientCertKey; // +0x390
struct HttpHeader
{
CafeString<32> name; // +0x00
CafeString<64> value; // +0x20
};
HttpHeader httpHeaders[3]; // +0x410
CafeString<96> httpQueryString; // +0x530
CafeString<512> serviceToken; // +0x590
uint8 ukn790[0x7C0 - 0x790];
// after 0x7C0 the task-specific fields seem to start?
union
{
struct
{
uint32be optionValue; // +0x7C0
CafeString<32> largeHttpHeaderKey; // +0x7C4
CafeString<512> largeHttpHeaderValue; // +0x7E4
}rawUl; // RawUlTaskSetting
struct
{
uint32be optionValue; // +0x7C0
CafeString<32> largeHttpHeaderKey; // +0x7C4
CafeString<512> largeHttpHeaderValue; // +0x7E4
// inherits the above values from RawUlTaskSetting
// the play report fields are too large to fit into the available space, so instead they are passed via their own system call that attaches it to the task. See PlayReportSetting::RegisterPreprocess
}playReportSetting;
struct
{
uint8be newArrival; // +0x7C0
uint8be led; // +0x7C1
uint8be ukn7C2[6]; // +0x7C2 - padding?
CafeString<8> bossDirectory; // +0x7C8
CafeString<32> fileName; // +0x7D0 Not 100% sure this is fileName
}rawDl; // RawDlTaskSetting
struct
{
CafeString<32> bossCode; // +0x7C0
CafeString<8> bossDirectory; // +0x7E0
uint32be ukn7E8;
uint32be ukn7EC;
uint32be directorySizeLimitHigh; // +0x7F0
uint32be directorySizeLimitLow; // +0x7F4
CafeString<32> fileName; // +0x7F8
// more fields here...
}nbdl;
struct
{
uint8 finalPadding[0xC00 - 0x7C0];
}paddedBlock;
};
};
static_assert(sizeof(TaskSettingCore) == 0xC00);
struct TaskSetting : public TaskSettingCore
{
uint8 paddingC00[0x1000 - 0xC00];
MEMPTR<void> vTablePtr; // +0x1000
struct VTableTaskSetting
{
VTableEntry rtti;
VTableEntry dtor;
VTableEntry RegisterPreprocess; // todo - double check the offset
VTableEntry unk1;
};
static inline SysAllocator<VTableTaskSetting> s_VTable;
static TaskSetting* ctor(TaskSetting* _thisptr);
static void dtor(TaskSetting* _this, uint32 options);
static bool IsPrivileged(TaskSetting* _thisptr);
static void InitializeSetting(TaskSetting* _thisptr);
static void InitVTable();
};
static_assert(offsetof(TaskSetting, priority) == 0x2A);
static_assert(offsetof(TaskSetting, mode) == 0x2B);
static_assert(offsetof(TaskSetting, permission) == 0x2C);
static_assert(offsetof(TaskSetting, intervalA) == 0x2E);
static_assert(offsetof(TaskSetting, intervalB) == 0x30);
static_assert(offsetof(TaskSetting, lifeTime) == 0x38);
static_assert(offsetof(TaskSetting, httpProtocol) == 0x40);
static_assert(offsetof(TaskSetting, internalClientCert) == 0x41);
static_assert(offsetof(TaskSetting, httpOption) == 0x42);
static_assert(offsetof(TaskSetting, url) == 0x48);
static_assert(offsetof(TaskSetting, lastModifiedTime) == 0x148);
static_assert(offsetof(TaskSetting, internalCaCert) == 0x188);
static_assert(offsetof(TaskSetting, httpTimeout) == 0x18C);
static_assert(offsetof(TaskSetting, caCert) == 0x190);
static_assert(offsetof(TaskSetting, clientCertName) == 0x310);
static_assert(offsetof(TaskSetting, clientCertKey) == 0x390);
static_assert(offsetof(TaskSetting, httpHeaders) == 0x410);
static_assert(offsetof(TaskSetting, httpQueryString) == 0x530);
static_assert(offsetof(TaskSetting, serviceToken) == 0x590);
// rawUl
static_assert(offsetof(TaskSetting, rawUl.optionValue) == 0x7C0);
static_assert(offsetof(TaskSetting, rawUl.largeHttpHeaderKey) == 0x7C4);
static_assert(offsetof(TaskSetting, rawUl.largeHttpHeaderValue) == 0x7E4);
// rawDl
static_assert(offsetof(TaskSetting, rawDl.newArrival) == 0x7C0);
static_assert(offsetof(TaskSetting, rawDl.bossDirectory) == 0x7C8);
static_assert(offsetof(TaskSetting, rawDl.fileName) == 0x7D0);
// nbdl
static_assert(offsetof(TaskSetting, nbdl.bossCode) == 0x7C0);
static_assert(offsetof(TaskSetting, nbdl.bossDirectory) == 0x7E0);
static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000);
static_assert(sizeof(TaskSetting) == 0x1004);
/* NetTaskSetting */
struct NetTaskSetting : TaskSetting
{
struct VTableNetTaskSetting : public VTableTaskSetting
{ };
static inline SysAllocator<VTableNetTaskSetting> s_VTable;
static NetTaskSetting* ctor(NetTaskSetting* _thisptr);
static BossResult AddCaCert(NetTaskSetting* _thisptr, const char* name);
static BossResult SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken);
static BossResult AddInternalCaCert(NetTaskSetting* _thisptr, char certId);
static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId);
static void InitVTable();
};
static_assert(sizeof(NetTaskSetting) == 0x1004);
/* NbdlTaskSetting */
struct NbdlTaskSetting : NetTaskSetting
{
struct VTableNbdlTaskSetting : public VTableNetTaskSetting
{
VTableEntry rttiNetTaskSetting; // unknown
};
static_assert(sizeof(VTableNbdlTaskSetting) == 8*5);
static inline SysAllocator<VTableNbdlTaskSetting> s_VTable;
static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr);
static BossResult Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* bossDirectory);
static BossResult SetFileName(NbdlTaskSetting* _thisptr, const char* fileName);
static void InitVTable();
};
static_assert(sizeof(NbdlTaskSetting) == 0x1004);
/* RawUlTaskSetting */
struct RawUlTaskSetting : NetTaskSetting
{
uint32be ukRaw1; // 0x1004
uint32be ukRaw2; // 0x1008
uint32be ukRaw3; // 0x100C
uint8 rawSpace[0x200]; // 0x1010
struct VTableRawUlTaskSetting : public VTableNetTaskSetting
{
VTableEntry rttiNetTaskSetting; // unknown
};
static_assert(sizeof(VTableRawUlTaskSetting) == 8*5);
static inline SysAllocator<VTableRawUlTaskSetting> s_VTable;
static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr);
static void dtor(RawUlTaskSetting* _this, uint32 options);
static void InitVTable();
};
static_assert(sizeof(RawUlTaskSetting) == 0x1210);
/* RawDlTaskSetting */
struct RawDlTaskSetting : NetTaskSetting
{
struct VTableRawDlTaskSetting : public VTableNetTaskSetting
{
VTableEntry rttiNetTaskSetting; // unknown
};
static_assert(sizeof(VTableRawDlTaskSetting) == 8*5);
static inline SysAllocator<VTableRawDlTaskSetting> s_VTable;
static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr);
static BossResult Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* bossDirectory);
static void InitVTable();
};
static_assert(sizeof(RawDlTaskSetting) == 0x1004);
/* PlayReportSetting */
struct PlayReportSetting : RawUlTaskSetting
{
MEMPTR<uint8> ukn1210_ptr; // 0x1210
uint32be ukn1214_size; // 0x1214
uint32be ukPlay3; // 0x1218
uint32be ukPlay4; // 0x121C
struct VTablePlayReportSetting : public VTableRawUlTaskSetting
{};
static_assert(sizeof(VTablePlayReportSetting) == 8*5);
static inline SysAllocator<VTablePlayReportSetting> s_VTable;
static PlayReportSetting* ctor(PlayReportSetting* _this);
static void dtor(PlayReportSetting* _this, uint32 options);
static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size);
static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value);
static void InitVTable();
};
static_assert(sizeof(PlayReportSetting) == 0x1220);
/* Storage */
enum class StorageKind : uint32
{
StorageNbdl = 0,
StorageRawDl = 1,
};
struct DataName
{
CafeString<32> name;
static DataName* ctor(DataName* _this); // __ct__Q3_2nn4boss8DataNameFv
static const char* operator_const_char(DataName* _this); // __opPCc__Q3_2nn4boss8DataNameCFv
const char* c_str() const
{
return name.c_str();
}
};
static_assert(sizeof(DataName) == 32);
/* IPC commands for /dev/boss */
enum class BossCommandId : uint32
{
// task operations
TaskIsRegistered = 0x69,
TaskRegisterA = 0x6A,
TaskRegisterForImmediateRunA = 0x6B,
TaskUnregister = 0x6D,
TaskRun = 0x78,
TaskStartScheduling = 0x77,
TaskStopScheduling = 0x79,
TaskWaitA = 0x7A,
TaskGetHttpStatusCodeA = 0x7C,
TaskGetTurnState = 0x7E,
TaskGetContentLength = 0x82,
TaskGetProcessedLength = 0x83,
// storage operations
StorageExist = 0x87,
StorageGetDataList = 0xB0,
// NsData operations
NsDataExist = 0x90,
NsDataRead = 0x93,
NsDataGetSize = 0x96,
NsDataDeleteFile = 0xA7,
NsDataDeleteFileWithHistory = 0xA8,
NsFinalize = 0xB3,
// Title operations
TitleSetNewArrivalFlag = 0x9E,
// most (if not all?) opcodes seem to have a secondary form with an additional titleId parameter in the high range around 0x150 and serviceId 1
// unknown commands
UknA7 = 0xA5,
DeleteDataRelated = 0xA6,
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
#pragma once
#include "Cafe/IOSU/iosu_types_common.h"
namespace iosu::boss
{
IOSUModule* GetModule();
}

View file

@ -188,18 +188,43 @@ namespace iosu
else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV)
{ {
uint32 requestId = cmd->args[0]; uint32 requestId = cmd->args[0];
uint32 numIn = cmd->args[1]; uint32 numOut = cmd->args[1];
uint32 numOut = cmd->args[2]; uint32 numIn = cmd->args[2];
IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr();
IPCIoctlVector* vecIn = vec + numIn; IPCIoctlVector* vecOut = vec + 0; // out buffers come first
IPCIoctlVector* vecOut = vec + 0; IPCIoctlVector* vecIn = vec + numOut;
cemu_assert(numIn > 0 && numOut > 0);
cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull()); cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull());
IPCServiceRequest* serviceRequest = MEMPTR<IPCServiceRequest>(vecIn->basePhys).GetPtr(); IPCServiceRequestHeader* serviceRequest = MEMPTR<IPCServiceRequestHeader>(vecIn->basePhys).GetPtr();
IPCServiceResponse* serviceResponse = MEMPTR<IPCServiceResponse>(vecOut->basePhys).GetPtr(); IPCServiceResponseHeader* serviceResponse = MEMPTR<IPCServiceResponseHeader>(vecOut->basePhys).GetPtr();
IOSDevHandle clientHandle = 0; // todo
IPCServiceCall serviceCall(clientHandle, serviceRequest->serviceId, serviceRequest->commandId);
#if 0
// log all buffers
cemuLog_log(LogType::Force, "IPC ServiceCall. In: {}, Out: {}, ServiceId: {}, CommandId: {} (0x{:x})", numIn, numOut, serviceRequest->serviceId, serviceRequest->commandId, serviceRequest->commandId);
for (size_t i = 0; i <numOut+numIn; i++)
{
cemuLog_log(LogType::Force, "");
cemuLog_log(LogType::Force, "Buffer {} - BasePhys: {}, Size: {}", i, vec[i].basePhys, vec[i].size);
cemuLog_logHexDump(LogType::Force, MEMPTR<uint8>(vec[i].basePhys).GetPtr(), vec[i].size, 16);
}
#endif
// parameter and response data is appended directly after the headers, so we add the streams without the headers
serviceCall.AddInputStream(MEMPTR<uint8>(vecIn[0].basePhys).GetPtr() + sizeof(IPCServiceRequestHeader), vecIn[0].size - sizeof(IPCServiceRequestHeader));
serviceCall.AddOutputStream(MEMPTR<uint8>(vecOut[0].basePhys).GetPtr() + sizeof(IPCServiceResponseHeader), vecOut[0].size - sizeof(IPCServiceResponseHeader));
// add remaining input/output buffers
for (size_t i = 1; i < numIn; i++)
serviceCall.AddInputStream(MEMPTR<uint8>(vecIn[i].basePhys).GetPtr(), vecIn[i].size);
for (size_t i = 1; i < numOut; i++)
serviceCall.AddOutputStream(MEMPTR<uint8>(vecOut[i].basePhys).GetPtr(), vecOut[i].size);
serviceResponse->nnResultCode = (uint32)ServiceCall(serviceCall);
serviceResponse->nnResultCode = (uint32)ServiceCall(serviceRequest->serviceId, nullptr, nullptr);
IOS_ResourceReply(cmd, IOS_ERROR_OK); IOS_ResourceReply(cmd, IOS_ERROR_OK);
continue; continue;
} }

View file

@ -73,25 +73,239 @@ namespace iosu
SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer; SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer;
}; };
struct IPCServiceRequest // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, /dev/boss and most nn services
{
uint32be ukn00;
uint32be serviceId;
uint32be ukn08;
uint32be commandId;
};
static_assert(sizeof(IPCServiceRequest) == 0x10);
struct IPCServiceResponse
{
uint32be nnResultCode;
};
// a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, ?
class IPCService class IPCService
{ {
struct IPCServiceRequestHeader
{
uint32be ukn00;
uint32be serviceId;
uint32be ukn08;
uint32be commandId;
};
static_assert(sizeof(IPCServiceRequestHeader) == 0x10);
struct IPCServiceResponseHeader
{
uint32be nnResultCode;
};
public: public:
class IPCParameterStream // input stream for parameters
{
public:
IPCParameterStream() = default;
IPCParameterStream(void* data, uint32 size) : m_data((uint8_t*)data), m_size(size) {}
template<typename T>
T ReadParameter(bool& hasError)
{
hasError = false;
if (m_readIndex + sizeof(T) > m_size)
{
hasError = true;
return T{};
}
T value;
memcpy(&value, &m_data[m_readIndex], sizeof(T));
m_readIndex += sizeof(T);
return value;
}
uint8* GetData() { return m_data; }
uint32 GetSize() const { return m_size; }
private:
uint8* m_data{nullptr};
uint32 m_size{0};
uint32 m_readIndex{0};
};
class IPCResponseStream // output stream for response data
{
public:
IPCResponseStream() = default;
IPCResponseStream(void* data, uint32 size) : m_data((uint8*)data), m_size(size) {}
template<typename T>
void Write(const T& value)
{
if (m_writtenSize + sizeof(T) > m_size)
{
m_hasError = true;
return;
}
memcpy(&m_data[m_writtenSize], &value, sizeof(T));
m_writtenSize += sizeof(T);
}
uint8* GetData() { return m_data; }
uint32 GetSize() const { return m_size; }
private:
uint8* m_data{nullptr};
uint32 m_writtenSize{0};
uint32 m_size{0};
bool m_hasError{false};
};
class IPCServiceCall
{
struct LargeBufferHeader
{
uint32be alignedSize; // size of the aligned (middle) part of the buffer
uint8be ukn4;
uint8be ukn5;
uint8be headSize;
uint8be tailSize;
};
static_assert(sizeof(LargeBufferHeader) == 8);
public:
struct UnalignedBuffer
{
UnalignedBuffer(bool isOutput, std::span<uint8> head, std::span<uint8> middle, std::span<uint8> tail) : m_isOutput(isOutput)
{
headPtr = head.data();
headSize = (uint32)head.size();
middlePtr = middle.data();
middleSize = (uint32)middle.size();
tailPtr = tail.data();
tailSize = (uint32)tail.size();
};
template<typename T>
T ReadType()
{
cemu_assert(!m_isOutput);
T v;
memcpy((uint8_t*)&v, headPtr, headSize);
memcpy((uint8_t*)&v + headSize, middlePtr, middleSize);
memcpy((uint8_t*)&v + headSize + middleSize, tailPtr, tailSize);
return v;
}
void WriteData(void* data, size_t size)
{
if (size == 0)
return;
cemu_assert(m_isOutput);
cemu_assert((headSize + middleSize + tailSize) >= size);
size_t bytesToCopy = std::min<size_t>(size, headSize);
memcpy(headPtr, data, bytesToCopy);
size -= bytesToCopy;
if (size > 0)
{
bytesToCopy = std::min<size_t>(size, middleSize);
memcpy(middlePtr, (uint8_t*)data + headSize, bytesToCopy);
size -= bytesToCopy;
}
if (size > 0)
{
bytesToCopy = std::min<size_t>(size, tailSize);
memcpy(tailPtr, (uint8_t*)data + headSize + middleSize, bytesToCopy);
}
}
size_t GetSize() const
{
return headSize + middleSize + tailSize;
}
private:
void* headPtr;
uint32 headSize;
void* middlePtr; // aligned
uint32 middleSize;
void* tailPtr;
uint32 tailSize;
bool m_isOutput;
};
IPCServiceCall(IOSDevHandle clientHandle, uint32 serviceId, uint32 commandId) : m_clientHandle(clientHandle), m_serviceId(serviceId), m_commandId(commandId)
{
}
void AddInputStream(void* data, uint32 size)
{
cemu_assert(m_paramStreamArraySize < 4);
m_paramStreamArray[m_paramStreamArraySize++] = IPCParameterStream(data, size);
}
void AddOutputStream(void* data, uint32 size)
{
cemu_assert(m_responseStreamArraySize < 4);
m_responseStreamArray[m_responseStreamArraySize++] = IPCResponseStream(data, size);
}
uint32 GetServiceId() const
{
return m_serviceId;
}
uint32 GetCommandId() const
{
return m_commandId;
}
template<typename T>
T ReadParameter()
{
// read only from stream 0 for now
return m_paramStreamArray[0].ReadParameter<T>(m_hasError);
}
UnalignedBuffer ReadUnalignedInputBufferInfo()
{
// how large/unaligned buffers work:
// Instead of appending the data into the parameter stream, there are two separate buffers created:
// 1. A ioctl vector that points directly to the aligned part of the original buffer. Both the pointer and the size are aligned
// 2. A ioctl vector with an allocated up-to-128byte buffer that holds any unaligned head or tail data (e.g. anything that isn't occupying the full 64 byte alignment)
// The buffer layout is then also serialized into the parameter stream
LargeBufferHeader header = ReadParameter<LargeBufferHeader>();
// get aligned buffer part
void* alignedBuffer = m_paramStreamArray[m_inputBufferIndex+0].GetData();
cemu_assert(m_paramStreamArray[m_inputBufferIndex+0].GetSize() == header.alignedSize);
// get head and tail buffer parts
uint8* unalignedDataBuffer = m_paramStreamArray[m_inputBufferIndex+1].GetData();
cemu_assert((header.headSize + header.tailSize) <= m_paramStreamArray[m_inputBufferIndex+1].GetSize());
UnalignedBuffer largeBuffer(false, {(uint8*)unalignedDataBuffer, header.headSize}, {(uint8*)alignedBuffer, header.alignedSize}, {(uint8*)unalignedDataBuffer + header.headSize, header.tailSize});
m_inputBufferIndex += 2; // if there is no unaligned data then are both buffers still present?
return largeBuffer;
}
UnalignedBuffer ReadUnalignedOutputBufferInfo()
{
LargeBufferHeader header = ReadParameter<LargeBufferHeader>();
// get aligned buffer part
void* alignedBuffer = m_responseStreamArray[m_outputBufferIndex+0].GetData();
cemu_assert(m_responseStreamArray[m_outputBufferIndex+0].GetSize() == header.alignedSize);
// get head and tail buffer parts
uint8* unalignedDataBuffer = m_responseStreamArray[m_outputBufferIndex+1].GetData();
cemu_assert((header.headSize + header.tailSize) <= m_responseStreamArray[m_outputBufferIndex+1].GetSize());
UnalignedBuffer largeBuffer(true, {(uint8*)unalignedDataBuffer, header.headSize}, {(uint8*)alignedBuffer, header.alignedSize}, {(uint8*)unalignedDataBuffer + header.headSize, header.tailSize});
m_outputBufferIndex += 2; // if there is no unaligned data then are both buffers still present?
return largeBuffer;
}
template<typename T>
void WriteResponse(const T& value)
{
m_responseStreamArray[0].Write<T>(value);
}
private:
IOSDevHandle m_clientHandle;
uint32 m_serviceId;
uint32 m_commandId;
IPCParameterStream m_paramStreamArray[4]{};
size_t m_paramStreamArraySize{0};
IPCResponseStream m_responseStreamArray[4]{};
size_t m_responseStreamArraySize{0};
bool m_hasError{false};
sint8 m_inputBufferIndex{1};
sint8 m_outputBufferIndex{1};
};
IPCService(std::string_view devicePath) : m_devicePath(devicePath) {}; IPCService(std::string_view devicePath) : m_devicePath(devicePath) {};
virtual ~IPCService() {}; virtual ~IPCService() {};
@ -105,7 +319,7 @@ namespace iosu
} }
virtual nnResult ServiceCall(uint32 serviceId, void* request, void* response) virtual nnResult ServiceCall(IPCServiceCall& serviceCall)
{ {
cemu_assert_unimplemented(); cemu_assert_unimplemented();
return 0; return 0;

View file

@ -161,7 +161,7 @@ void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 src
if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight ) if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight )
{ {
cemu_assert_debug(false); cemuLog_logDebugOnce(LogType::Force, "GX2CopySurface: Mismatching mip resolution");
return; return;
} }
// handle format // handle format

View file

@ -1 +1,6 @@
void nnAc_load(); void nnAc_load();
namespace nn_ac
{
nnResult IsApplicationConnected(uint8be* connected);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,261 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_IPC.h"
#include <boost/container/static_vector.hpp>
class IPCServiceClient
{
public:
class IPCServiceCall
{
struct IOVectorBuffer
{
IOVectorBuffer() = default;
IOVectorBuffer(uint8* ptr, uint32 size, bool isExternalBuffer = false) : ptr(ptr), size(size), isExternalBuffer(isExternalBuffer) {}
uint8* ptr;
uint32 size;
bool isExternalBuffer{false}; // buffer provided by caller
};
public:
IPCServiceCall(IPCServiceClient& client, uint32 serviceId, uint32 commandId) : m_client(client)
{
// allocate a parameter and response buffer
IPCBuffer* cmdBuf = client.AllocateCommandBuffer();
m_paramBuffers.emplace_back(cmdBuf->data, sizeof(IPCBuffer));
cmdBuf = client.AllocateCommandBuffer();
m_responseBuffers.emplace_back(cmdBuf->data, sizeof(IPCBuffer));
// write the request header
WriteParam<uint32be>(0); // ukn00
WriteParam<uint32be>(serviceId); // serviceId
WriteParam<uint32be>(0); // ukn08
WriteParam<uint32be>(commandId); // commandId
}
~IPCServiceCall()
{
for (auto& buf : m_paramBuffers)
{
if (buf.isExternalBuffer)
continue;
m_client.ReleaseCommandBuffer((IPCBuffer*)buf.ptr);
}
for (auto& buf : m_responseBuffers)
{
if (buf.isExternalBuffer)
continue;
m_client.ReleaseCommandBuffer((IPCBuffer*)buf.ptr);
}
}
IPCServiceCall(const IPCServiceCall&) = delete;
IPCServiceCall& operator=(const IPCServiceCall&) = delete;
template<typename T>
void WriteParam(const T& value)
{
cemu_assert(m_paramWriteIndex + sizeof(T) <= m_paramBuffers[0].size);
memcpy(m_paramBuffers[0].ptr + m_paramWriteIndex, &value, sizeof(T));
m_paramWriteIndex += sizeof(T);
}
// ptr and size defines an input buffer (PPC->IOSU)
void WriteParamBuffer(MEMPTR<void> ptr, uint32 size)
{
WriteInOutBuffer(MEMPTR<uint8>(ptr), size, false);
}
// ptr and size defines an output buffer (IOSU->PPC)
void WriteResponseBuffer(MEMPTR<void> ptr, uint32 size)
{
WriteInOutBuffer(MEMPTR<uint8>(ptr), size, true);
}
nnResult Submit()
{
StackAllocator<IPCIoctlVector, 16> vectorArray;
uint32 ioVecCount = m_paramBuffers.size() + m_responseBuffers.size();
cemu_assert(ioVecCount <= 16);
// output buffers come first
for (size_t i = 0; i < m_responseBuffers.size(); ++i)
{
vectorArray[i].baseVirt = MEMPTR<uint8>(m_responseBuffers[i].ptr);
vectorArray[i].basePhys = nullptr;
vectorArray[i].size = m_responseBuffers[i].size;
}
// input buffers
for (size_t i = 0; i < m_paramBuffers.size(); ++i)
{
vectorArray[m_responseBuffers.size() + i].baseVirt = MEMPTR<uint8>(m_paramBuffers[i].ptr);
vectorArray[m_responseBuffers.size() + i].basePhys = nullptr;
vectorArray[m_responseBuffers.size() + i].size = m_paramBuffers[i].size;
}
IOS_ERROR r = coreinit::IOS_Ioctlv(m_client.GetDevHandle(), 0, m_responseBuffers.size(), m_paramBuffers.size(), vectorArray.GetPointer());
if ( (r&0x80000000) != 0)
{
cemu_assert_unimplemented(); // todo - handle submission errors
}
uint32be resultCode = ReadResponse<uint32be>();
if (!NN_RESULT_IS_FAILURE((uint32)resultCode))
{
for (auto& bufCopy : m_bufferCopiesOut)
{
memcpy(bufCopy.dst, bufCopy.src, bufCopy.size);
}
}
return static_cast<nnResult>(resultCode);
}
template<typename T>
T ReadResponse()
{
cemu_assert(m_responseReadIndex + sizeof(T) <= m_responseBuffers[0].size);
T value;
memcpy(&value, m_responseBuffers[0].ptr + m_responseReadIndex, sizeof(T));
m_responseReadIndex += sizeof(T);
return value;
}
private:
struct BufferCopyOut
{
BufferCopyOut(void* dst, void* src, uint32 size) : dst(dst), src(src), size(size) {}
void* dst;
void* src;
uint32 size;
};
void WriteInOutBuffer(MEMPTR<uint8> ptr, uint32 size, bool isOutput)
{
uint32 headSize = (0x40 - (ptr.GetMPTR()&0x3F))&0x3F;
headSize = std::min<uint32>(headSize, size);
uint32 alignedSize = size - headSize;
uint32 tailSize = alignedSize - (alignedSize&~0x3F);
alignedSize -= tailSize;
// verify
cemu_assert_debug(headSize + alignedSize + tailSize == size);
cemu_assert_debug(alignedSize == 0 || ((ptr.GetMPTR()+headSize)&0x3F) == 0);
cemu_assert_debug(tailSize == 0 || ((ptr.GetMPTR()+headSize+alignedSize)&0x3F) == 0);
if (isOutput)
cemu_assert(m_responseBuffers.size()+2 <= m_responseBuffers.capacity());
else
cemu_assert(m_paramBuffers.size()+2 <= m_paramBuffers.capacity());
IOVectorBuffer alignedBuffer;
alignedBuffer.ptr = ptr + headSize;
alignedBuffer.size = alignedSize;
alignedBuffer.isExternalBuffer = true;
if (isOutput)
m_responseBuffers.emplace_back(alignedBuffer);
else
m_paramBuffers.emplace_back(alignedBuffer);
IPCBuffer* headAndTailBuffer = m_client.AllocateCommandBuffer();
IOVectorBuffer headAndTail;
headAndTail.ptr = headAndTailBuffer->data + (0x40 - headSize);
headAndTail.size = 128 - (0x40 - headSize);
headAndTail.isExternalBuffer = false;
if (headSize > 0)
{
if (isOutput)
m_bufferCopiesOut.emplace_back(ptr, headAndTailBuffer->data + 0x40 - headSize, headSize);
else
memcpy(headAndTailBuffer->data + 0x40 - headSize, ptr, headSize);
}
if (tailSize > 0)
{
if (isOutput)
m_bufferCopiesOut.emplace_back(ptr + headSize + alignedSize, headAndTailBuffer->data + 0x40, tailSize);
else
memcpy(headAndTailBuffer->data + 0x40, ptr + headSize + alignedSize, tailSize);
}
if (isOutput)
m_responseBuffers.emplace_back(headAndTail);
else
m_paramBuffers.emplace_back(headAndTail);
// serialize into parameter stream
WriteParam<uint32be>(alignedSize);
WriteParam<uint8be>(0); // ukn4
WriteParam<uint8be>(0); // ukn5
WriteParam<uint8be>((uint8)headSize);
WriteParam<uint8be>((uint8)tailSize);
}
IPCServiceClient& m_client;
boost::container::static_vector<IOVectorBuffer, 8> m_paramBuffers;
boost::container::static_vector<IOVectorBuffer, 8> m_responseBuffers;
sint32 m_paramWriteIndex{0};
sint32 m_responseReadIndex{0};
boost::container::static_vector<BufferCopyOut, 16> m_bufferCopiesOut;
};
IPCServiceClient()
{
}
~IPCServiceClient()
{
Shutdown();
}
void Initialize(std::string_view devicePath, uint8_t* buffer, uint32_t bufferSize)
{
m_devicePath = devicePath;
m_buffer = buffer;
m_bufferSize = bufferSize;
static_assert(sizeof(IPCBuffer) == 256);
size_t numCommandBuffers = m_bufferSize / sizeof(IPCBuffer);
m_commandBuffersFree.resize(numCommandBuffers);
for (size_t i = 0; i < numCommandBuffers; ++i)
{
m_commandBuffersFree[i] = reinterpret_cast<IPCBuffer*>(m_buffer + i * sizeof(IPCBuffer));
}
m_clientHandle = coreinit::IOS_Open(m_devicePath.c_str(), 0);
}
void Shutdown()
{
if (m_clientHandle != 0)
{
coreinit::IOS_Close(m_clientHandle);
m_clientHandle = 0;
}
}
IPCServiceCall Begin(uint32_t serviceId, uint32_t commandId)
{
return IPCServiceCall(*this, serviceId, commandId);
}
IOSDevHandle GetDevHandle() const
{
cemu_assert(m_clientHandle != 0);
return m_clientHandle;
}
private:
struct IPCBuffer
{
uint8 data[256];
};
IPCBuffer* AllocateCommandBuffer()
{
cemu_assert(m_commandBuffersFree.size() > 0);
IPCBuffer* buf = m_commandBuffersFree.back();
m_commandBuffersFree.pop_back();
return buf;
}
void ReleaseCommandBuffer(IPCBuffer* buffer)
{
m_commandBuffersFree.emplace_back(buffer);
}
private:
std::string m_devicePath;
IOSDevHandle m_clientHandle{0};
uint8_t* m_buffer{nullptr};
uint32_t m_bufferSize{0};
std::vector<IPCBuffer*> m_commandBuffersFree;
};

View file

@ -224,6 +224,24 @@ bool cemuLog_log(LogType type, std::u8string_view text)
return cemuLog_log(type, s); return cemuLog_log(type, s);
} }
void cemuLog_logHexDump(LogType type, const void* data, size_t size, size_t lineSize)
{
const uint8* dataU8 = static_cast<const uint8*>(data);
std::vector<char> hexLine;
hexLine.resize(6 + lineSize * 3 + 1, ' ');
for (size_t i = 0; i < size; i += lineSize)
{
sprintf(hexLine.data(), "%04X: ", (int)i);
size_t remainingSize = std::min<size_t>(lineSize, size - i);
for (size_t j=0; j<remainingSize; j++)
sprintf(hexLine.data() + 6 + j * 3, "%02X ", dataU8[j]);
dataU8 += remainingSize;
if (remainingSize == lineSize)
hexLine.resize(6 + remainingSize * 3 + 1);
cemuLog_log(type, hexLine.data());
}
}
void cemuLog_waitForFlush() void cemuLog_waitForFlush()
{ {
cemuLog_createLogFile(false); cemuLog_createLogFile(false);

View file

@ -125,6 +125,9 @@ bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args)
#define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } } #define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } }
// utility function for logging binary data as a hex dump
void cemuLog_logHexDump(LogType type, const void* data, size_t size, size_t lineSize = 16);
// cafe lib calls // cafe lib calls
bool cemuLog_advancedPPCLoggingEnabled(); bool cemuLog_advancedPPCLoggingEnabled();

View file

@ -23,7 +23,7 @@ CURLcode _sslctx_function_NUS(CURL* curl, void* sslctx, void* param)
{ {
cemuLog_log(LogType::Force, "Invalid CA certificate (102)"); cemuLog_log(LogType::Force, "Invalid CA certificate (102)");
} }
if (iosuCrypto_addCACertificate(sslctx, 0x69) == false) if (iosuCrypto_addCACertificate(sslctx, 105) == false)
{ {
cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); cemuLog_log(LogType::Force, "Invalid CA certificate (105)");
} }
@ -79,7 +79,6 @@ CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param)
cemuLog_log(LogType::Force, "Olive client certificate error"); cemuLog_log(LogType::Force, "Olive client certificate error");
} }
// NSSLAddServerPKIGroups(sslCtx, 3, &x, &y);
{ {
std::vector<sint16> certGroups = { std::vector<sint16> certGroups = {
100, 101, 102, 103, 104, 105, 100, 101, 102, 103, 104, 105,
@ -99,6 +98,32 @@ CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param)
return CURLE_OK; return CURLE_OK;
} }
CURLcode _sslctx_function_CUSTOM(CURL* curl, void* sslctx, void* param)
{
CurlRequestHelper* requestHelper = (CurlRequestHelper*)param;
for (auto& caCertId : requestHelper->GetCaCertIds())
{
if (iosuCrypto_addCACertificate(sslctx, caCertId) == false)
{
cemuLog_log(LogType::Force, "Invalid CA certificate ({})", caCertId);
}
}
for (auto& clientCertId : requestHelper->GetClientCertIds())
{
if (iosuCrypto_addCACertificate(sslctx, clientCertId) == false)
{
cemuLog_log(LogType::Force, "Invalid client certificate ({})", clientCertId);
}
}
SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2);
if (requestHelper->GetClientCertIds().empty())
SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_NONE, nullptr);
else
SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr);
return CURLE_OK;
}
CurlRequestHelper::CurlRequestHelper() CurlRequestHelper::CurlRequestHelper()
{ {
m_curl = curl_easy_init(); m_curl = curl_easy_init();
@ -162,6 +187,11 @@ void CurlRequestHelper::initate(NetworkService service, std::string url, SERVER_
curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_OLIVE); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_OLIVE);
curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL);
} }
else if (sslContext == SERVER_SSL_CONTEXT::CUSTOM)
{
curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_CUSTOM);
curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, this);
}
else else
{ {
cemu_assert(false); cemu_assert(false);
@ -218,7 +248,8 @@ bool CurlRequestHelper::submitRequest(bool isPost)
// post // post
if (isPost) if (isPost)
{ {
if (!m_isUsingMultipartFormData) { if (!m_isUsingMultipartFormData)
{
curl_easy_setopt(m_curl, CURLOPT_POST, 1); curl_easy_setopt(m_curl, CURLOPT_POST, 1);
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data());
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size());
@ -242,9 +273,14 @@ bool CurlRequestHelper::submitRequest(bool isPost)
// check response code // check response code
long httpCode = 0; long httpCode = 0;
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode);
m_httpStatusCode = httpCode;
if (httpCode != 200) if (httpCode != 200)
{ {
cemuLog_log(LogType::Force, "HTTP request received response {} but expected 200", httpCode); cemuLog_log(LogType::Force, "HTTP request received response {} but expected 200", httpCode);
char* effectiveUrl = nullptr;
curl_easy_getinfo(m_curl, CURLINFO_EFFECTIVE_URL, &effectiveUrl);
if (effectiveUrl)
cemuLog_log(LogType::Force, "Request: {}", effectiveUrl);
// error status codes (4xx or 5xx range) are always considered a failed request, except for code 400 which is usually returned as a response to failed logins etc. // error status codes (4xx or 5xx range) are always considered a failed request, except for code 400 which is usually returned as a response to failed logins etc.
if (httpCode >= 400 && httpCode <= 599 && httpCode != 400) if (httpCode >= 400 && httpCode <= 599 && httpCode != 400)
return false; return false;

View file

@ -28,6 +28,7 @@ public:
IDBE, // idbe-wup. IDBE, // idbe-wup.
TAGAYA, // tagaya.wup.shop.nintendo.net TAGAYA, // tagaya.wup.shop.nintendo.net
OLIVE, // olv. OLIVE, // olv.
CUSTOM, // use cert parameters
}; };
CurlRequestHelper(); CurlRequestHelper();
@ -56,6 +57,41 @@ public:
m_isUsingMultipartFormData = isUsingMultipartFormData; m_isUsingMultipartFormData = isUsingMultipartFormData;
} }
void ClearCaCertIds()
{
m_caCertIds.clear();
}
void ClearClientCertIds()
{
m_clientCertIds.clear();
}
void AddCaCertId(sint32 caCertId)
{
m_caCertIds.emplace_back(caCertId);
}
void AddClientCertId(sint32 clientCertId)
{
m_clientCertIds.emplace_back(clientCertId);
}
std::vector<sint32> GetCaCertIds() const
{
return m_caCertIds;
}
std::vector<sint32> GetClientCertIds() const
{
return m_clientCertIds;
}
sint32 GetHTTPStatusCode() const
{
return m_httpStatusCode;
}
private: private:
static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
@ -69,6 +105,11 @@ private:
void* m_writeCallbackUserData{}; void* m_writeCallbackUserData{};
bool m_isUsingMultipartFormData = false; bool m_isUsingMultipartFormData = false;
// cert data
std::vector<sint32> m_caCertIds;
std::vector<sint32> m_clientCertIds;
// result
sint32 m_httpStatusCode{ 0 };
}; };
class CurlSOAPHelper // todo - make this use CurlRequestHelper class CurlSOAPHelper // todo - make this use CurlRequestHelper

View file

@ -7,12 +7,23 @@ template <size_t N>
class CafeString // fixed buffer size, null-terminated, PPC char class CafeString // fixed buffer size, null-terminated, PPC char
{ {
public: public:
constexpr static size_t Size()
{
return N;
}
// checks whether the string and a null terminator can fit into the buffer
bool CanHoldString(std::string_view sv) const
{
return sv.size() < N;
}
bool assign(std::string_view sv) bool assign(std::string_view sv)
{ {
if (sv.size()+1 >= N) if (sv.size() >= N)
{ {
memcpy(data, sv.data(), sv.size()-1); memcpy(data, sv.data(), N-1);
data[sv.size()-1] = '\0'; data[N-1] = '\0';
return false; return false;
} }
memcpy(data, sv.data(), sv.size()); memcpy(data, sv.data(), sv.size());
@ -20,11 +31,50 @@ class CafeString // fixed buffer size, null-terminated, PPC char
return true; return true;
} }
const char* c_str() void Copy(CafeString<N>& other)
{
memcpy(data, other.data, N);
}
bool empty() const
{
return data[0] == '\0';
}
const char* c_str() const
{ {
return (const char*)data; return (const char*)data;
} }
void ClearAllBytes()
{
memset(data, 0, N);
}
auto operator<=>(const CafeString<N>& other) const
{
for (size_t i = 0; i < N; i++)
{
if (data[i] != other.data[i])
return data[i] <=> other.data[i];
if (data[i] == '\0')
return std::strong_ordering::equal;
}
return std::strong_ordering::equal;
}
bool operator==(const CafeString<N>& other) const
{
for (size_t i = 0; i < N; i++)
{
if (data[i] != other.data[i])
return false;
if (data[i] == '\0')
return true;
}
return true;
}
uint8be data[N]; uint8be data[N];
}; };

View file

@ -81,6 +81,7 @@ enum
MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER,
MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER,
MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER,
MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE,
MAINFRAME_MENU_ID_FILE_EXIT, MAINFRAME_MENU_ID_FILE_EXIT,
MAINFRAME_MENU_ID_FILE_END_EMULATION, MAINFRAME_MENU_ID_FILE_END_EMULATION,
MAINFRAME_MENU_ID_FILE_RECENT_0, MAINFRAME_MENU_ID_FILE_RECENT_0,
@ -178,6 +179,7 @@ EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate)
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder)
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder)
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder)
EVT_MENU(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, MainWindow::OnClearSpotPassCache)
EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit)
EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu)
EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu)
@ -696,8 +698,15 @@ void MainWindow::OnOpenFolder(wxCommandEvent& event)
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath())); wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath()));
else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER) else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER)
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache"))); wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache")));
}
void MainWindow::OnClearSpotPassCache(wxCommandEvent& event)
{
fs::path bossPath = ActiveSettings::GetMlcPath("usr/boss");
fs::remove_all(bossPath);
// recreate usr/boss/
fs::create_directory(bossPath);
wxMessageBox(_("SpotPass cache cleared"));
} }
void MainWindow::OnInstallUpdate(wxCommandEvent& event) void MainWindow::OnInstallUpdate(wxCommandEvent& event)
@ -2142,11 +2151,14 @@ void MainWindow::RecreateMenu()
#endif #endif
} }
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("Open Cemu folder"));
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("Open MLC folder"));
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder"));
m_fileMenu->AppendSeparator(); m_fileMenu->AppendSeparator();
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, _("Clear Spot&Pass cache"), wxEmptyString);
if (m_game_launched)
m_fileMenu->Enable(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, false);
m_fileMenu->AppendSeparator();
m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit"));
m_menuBar->Append(m_fileMenu, _("&File")); m_menuBar->Append(m_fileMenu, _("&File"));
// options->account submenu // options->account submenu

View file

@ -94,6 +94,7 @@ public:
void OnClose(wxCloseEvent& event); void OnClose(wxCloseEvent& event);
void OnFileMenu(wxCommandEvent& event); void OnFileMenu(wxCommandEvent& event);
void OnOpenFolder(wxCommandEvent& event); void OnOpenFolder(wxCommandEvent& event);
void OnClearSpotPassCache(wxCommandEvent& event);
void OnLaunchFromFile(wxLaunchGameEvent& event); void OnLaunchFromFile(wxLaunchGameEvent& event);
void OnInstallUpdate(wxCommandEvent& event); void OnInstallUpdate(wxCommandEvent& event);
void OnFileExit(wxCommandEvent& event); void OnFileExit(wxCommandEvent& event);