mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-12-12 10:37:02 +00:00
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:
parent
3f6974fc95
commit
4fa0df6dcf
24 changed files with 3232 additions and 2223 deletions
|
|
@ -238,8 +238,6 @@ add_library(CemuCafe
|
|||
IOSU/legacy/iosu_acp.h
|
||||
IOSU/legacy/iosu_act.cpp
|
||||
IOSU/legacy/iosu_act.h
|
||||
IOSU/legacy/iosu_boss.cpp
|
||||
IOSU/legacy/iosu_boss.h
|
||||
IOSU/legacy/iosu_crypto.cpp
|
||||
IOSU/legacy/iosu_crypto.h
|
||||
IOSU/legacy/iosu_fpd.cpp
|
||||
|
|
@ -252,6 +250,10 @@ add_library(CemuCafe
|
|||
IOSU/legacy/iosu_nim.h
|
||||
IOSU/nn/iosu_nn_service.cpp
|
||||
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.h
|
||||
IOSU/ODM/iosu_odm.cpp
|
||||
|
|
@ -418,6 +420,7 @@ add_library(CemuCafe
|
|||
OS/libs/nn_ccr/nn_ccr.h
|
||||
OS/libs/nn_cmpt/nn_cmpt.cpp
|
||||
OS/libs/nn_cmpt/nn_cmpt.h
|
||||
OS/libs/nn_client_service.h
|
||||
OS/libs/nn_common.h
|
||||
OS/libs/nn_ec/nn_ec.cpp
|
||||
OS/libs/nn_ec/nn_ec.h
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@
|
|||
#include "Cafe/IOSU/legacy/iosu_crypto.h"
|
||||
#include "Cafe/IOSU/legacy/iosu_mcp.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/PDM/iosu_pdm.h"
|
||||
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
|
||||
#include "Cafe/IOSU/nn/boss/boss_service.h"
|
||||
|
||||
// IOSU initializer functions
|
||||
#include "Cafe/IOSU/kernel/iosu_kernel.h"
|
||||
|
|
@ -551,6 +551,7 @@ namespace CafeSystem
|
|||
iosu::fpd::GetModule(),
|
||||
iosu::pdm::GetModule(),
|
||||
iosu::ccr_nfc::GetModule(),
|
||||
iosu::boss::GetModule()
|
||||
};
|
||||
|
||||
// initialize all subsystems which are persistent and don't depend on a game running
|
||||
|
|
@ -589,7 +590,6 @@ namespace CafeSystem
|
|||
iosu::iosuMcp_init();
|
||||
iosu::mcp::Init();
|
||||
iosu::iosuAcp_init();
|
||||
iosu::boss_init();
|
||||
iosu::nim::Initialize();
|
||||
iosu::odm::Initialize();
|
||||
// init Cafe OS
|
||||
|
|
|
|||
|
|
@ -659,7 +659,10 @@ namespace iosu
|
|||
for (uint32 i = 0; i < numIn + numOut; i++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
// todo - check for valid pointer range
|
||||
vec[i].basePhys = vec[i].baseVirt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -782,7 +782,7 @@ namespace iosu
|
|||
public:
|
||||
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");
|
||||
cemu_assert_unimplemented();
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ namespace iosu
|
|||
public:
|
||||
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");
|
||||
cemu_assert_unimplemented();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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();
|
||||
}
|
||||
0
src/Cafe/IOSU/nn/boss/boss_common.cpp
Normal file
0
src/Cafe/IOSU/nn/boss/boss_common.cpp
Normal file
431
src/Cafe/IOSU/nn/boss/boss_common.h
Normal file
431
src/Cafe/IOSU/nn/boss/boss_common.h
Normal 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,
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
1431
src/Cafe/IOSU/nn/boss/boss_service.cpp
Normal file
1431
src/Cafe/IOSU/nn/boss/boss_service.cpp
Normal file
File diff suppressed because it is too large
Load diff
7
src/Cafe/IOSU/nn/boss/boss_service.h
Normal file
7
src/Cafe/IOSU/nn/boss/boss_service.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
#include "Cafe/IOSU/iosu_types_common.h"
|
||||
|
||||
namespace iosu::boss
|
||||
{
|
||||
IOSUModule* GetModule();
|
||||
}
|
||||
|
|
@ -188,18 +188,43 @@ namespace iosu
|
|||
else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV)
|
||||
{
|
||||
uint32 requestId = cmd->args[0];
|
||||
uint32 numIn = cmd->args[1];
|
||||
uint32 numOut = cmd->args[2];
|
||||
uint32 numOut = cmd->args[1];
|
||||
uint32 numIn = cmd->args[2];
|
||||
IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr();
|
||||
IPCIoctlVector* vecIn = vec + numIn;
|
||||
IPCIoctlVector* vecOut = vec + 0;
|
||||
IPCIoctlVector* vecOut = vec + 0; // out buffers come first
|
||||
IPCIoctlVector* vecIn = vec + numOut;
|
||||
|
||||
cemu_assert(numIn > 0 && numOut > 0);
|
||||
cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull());
|
||||
|
||||
IPCServiceRequest* serviceRequest = MEMPTR<IPCServiceRequest>(vecIn->basePhys).GetPtr();
|
||||
IPCServiceResponse* serviceResponse = MEMPTR<IPCServiceResponse>(vecOut->basePhys).GetPtr();
|
||||
IPCServiceRequestHeader* serviceRequest = MEMPTR<IPCServiceRequestHeader>(vecIn->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);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,25 +73,239 @@ namespace iosu
|
|||
SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer;
|
||||
};
|
||||
|
||||
struct IPCServiceRequest
|
||||
{
|
||||
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, ?
|
||||
// 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
|
||||
class IPCService
|
||||
{
|
||||
struct IPCServiceRequestHeader
|
||||
{
|
||||
uint32be ukn00;
|
||||
uint32be serviceId;
|
||||
uint32be ukn08;
|
||||
uint32be commandId;
|
||||
};
|
||||
|
||||
static_assert(sizeof(IPCServiceRequestHeader) == 0x10);
|
||||
|
||||
struct IPCServiceResponseHeader
|
||||
{
|
||||
uint32be nnResultCode;
|
||||
};
|
||||
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) {};
|
||||
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();
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 src
|
|||
|
||||
if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight )
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
cemuLog_logDebugOnce(LogType::Force, "GX2CopySurface: Mismatching mip resolution");
|
||||
return;
|
||||
}
|
||||
// handle format
|
||||
|
|
|
|||
|
|
@ -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
261
src/Cafe/OS/libs/nn_client_service.h
Normal file
261
src/Cafe/OS/libs/nn_client_service.h
Normal 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;
|
||||
};
|
||||
|
|
@ -224,6 +224,24 @@ bool cemuLog_log(LogType type, std::u8string_view text)
|
|||
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()
|
||||
{
|
||||
cemuLog_createLogFile(false);
|
||||
|
|
|
|||
|
|
@ -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__); } }
|
||||
|
||||
// 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
|
||||
bool cemuLog_advancedPPCLoggingEnabled();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ CURLcode _sslctx_function_NUS(CURL* curl, void* sslctx, void* param)
|
|||
{
|
||||
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)");
|
||||
}
|
||||
|
|
@ -79,7 +79,6 @@ CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param)
|
|||
cemuLog_log(LogType::Force, "Olive client certificate error");
|
||||
}
|
||||
|
||||
// NSSLAddServerPKIGroups(sslCtx, 3, &x, &y);
|
||||
{
|
||||
std::vector<sint16> certGroups = {
|
||||
100, 101, 102, 103, 104, 105,
|
||||
|
|
@ -99,6 +98,32 @@ CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param)
|
|||
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()
|
||||
{
|
||||
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_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
|
||||
{
|
||||
cemu_assert(false);
|
||||
|
|
@ -218,7 +248,8 @@ bool CurlRequestHelper::submitRequest(bool isPost)
|
|||
// post
|
||||
if (isPost)
|
||||
{
|
||||
if (!m_isUsingMultipartFormData) {
|
||||
if (!m_isUsingMultipartFormData)
|
||||
{
|
||||
curl_easy_setopt(m_curl, CURLOPT_POST, 1);
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size());
|
||||
|
|
@ -242,9 +273,14 @@ bool CurlRequestHelper::submitRequest(bool isPost)
|
|||
// check response code
|
||||
long httpCode = 0;
|
||||
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||
m_httpStatusCode = httpCode;
|
||||
if (httpCode != 200)
|
||||
{
|
||||
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.
|
||||
if (httpCode >= 400 && httpCode <= 599 && httpCode != 400)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public:
|
|||
IDBE, // idbe-wup.
|
||||
TAGAYA, // tagaya.wup.shop.nintendo.net
|
||||
OLIVE, // olv.
|
||||
CUSTOM, // use cert parameters
|
||||
};
|
||||
|
||||
CurlRequestHelper();
|
||||
|
|
@ -56,6 +57,41 @@ public:
|
|||
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:
|
||||
static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
|
||||
|
||||
|
|
@ -69,6 +105,11 @@ private:
|
|||
void* m_writeCallbackUserData{};
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,12 +7,23 @@ template <size_t N>
|
|||
class CafeString // fixed buffer size, null-terminated, PPC char
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (sv.size()+1 >= N)
|
||||
if (sv.size() >= N)
|
||||
{
|
||||
memcpy(data, sv.data(), sv.size()-1);
|
||||
data[sv.size()-1] = '\0';
|
||||
memcpy(data, sv.data(), N-1);
|
||||
data[N-1] = '\0';
|
||||
return false;
|
||||
}
|
||||
memcpy(data, sv.data(), sv.size());
|
||||
|
|
@ -20,11 +31,50 @@ class CafeString // fixed buffer size, null-terminated, PPC char
|
|||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ enum
|
|||
MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER,
|
||||
MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER,
|
||||
MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER,
|
||||
MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE,
|
||||
MAINFRAME_MENU_ID_FILE_EXIT,
|
||||
MAINFRAME_MENU_ID_FILE_END_EMULATION,
|
||||
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_MLC_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_END_EMULATION, 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()));
|
||||
else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER)
|
||||
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)
|
||||
|
|
@ -2142,11 +2151,14 @@ void MainWindow::RecreateMenu()
|
|||
#endif
|
||||
}
|
||||
|
||||
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_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_SHADERCACHE_FOLDER, _("Open &shader cache folder"));
|
||||
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_menuBar->Append(m_fileMenu, _("&File"));
|
||||
// options->account submenu
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ public:
|
|||
void OnClose(wxCloseEvent& event);
|
||||
void OnFileMenu(wxCommandEvent& event);
|
||||
void OnOpenFolder(wxCommandEvent& event);
|
||||
void OnClearSpotPassCache(wxCommandEvent& event);
|
||||
void OnLaunchFromFile(wxLaunchGameEvent& event);
|
||||
void OnInstallUpdate(wxCommandEvent& event);
|
||||
void OnFileExit(wxCommandEvent& event);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue