diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 1b0def84..8da817bc 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -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 diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index abfda232..a74a61b1 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -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 diff --git a/src/Cafe/IOSU/kernel/iosu_kernel.cpp b/src/Cafe/IOSU/kernel/iosu_kernel.cpp index 666e0373..3ea6d7da 100644 --- a/src/Cafe/IOSU/kernel/iosu_kernel.cpp +++ b/src/Cafe/IOSU/kernel/iosu_kernel.cpp @@ -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; } diff --git a/src/Cafe/IOSU/legacy/iosu_acp.cpp b/src/Cafe/IOSU/legacy/iosu_acp.cpp index 6a9e6b89..871cc495 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.cpp +++ b/src/Cafe/IOSU/legacy/iosu_acp.cpp @@ -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(); diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 413c1e68..de6ef9e0 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -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(); diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp deleted file mode 100644 index 212d42a0..00000000 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ /dev/null @@ -1,1057 +0,0 @@ -#include "iosu_boss.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config/ActiveSettings.h" -#include "config/NetworkSettings.h" -#include "curl/curl.h" -#include "openssl/bn.h" -#include "openssl/x509.h" -#include "openssl/ssl.h" - -#include "iosu_ioctl.h" -#include "Cafe/OS/libs/nn_common.h" -#include "iosu_act.h" -#include "iosu_crypto.h" -#include "util/crypto/aes128.h" -#include "config/CemuConfig.h" - -#include "util/helpers/helpers.h" -#include "Cemu/ncrypto/ncrypto.h" -#include "Cafe/CafeSystem.h" - -namespace iosu -{ - enum TurnState - { - kUnknown = 0, - kStopped = 1, - kStoppedByPolicyList = 2, - kWaitTime = 3, - kWaitRun = 4, - kWaitResume = 5, - kRunning = 6, - kFinished = 7, - kSuccess = 16, - kError = 17, - }; - - enum class ContentType - { - kUnknownContent, - kXmlContent, - kBinaryFile, - kText, - }; - - enum class FileType - { - kUnknownFile, - kAppData, - }; - - enum TaskSettingType : uint32 - { - kRawDlTaskSetting = 0x10000698, - }; - - struct TaskFile - { - TaskFile(std::string file_name, uint32 data_id, FileType type, std::string url, uint32 size) - : file_name(file_name), data_id(data_id), file_type(type), url(url), size(size) {} - - std::string file_name; - uint32 data_id; - FileType file_type; - std::string url; - uint32 size; - uint64 last_modified = 0; - }; - - struct TaskSetting - { - static const uint32 kBossCode = 0x7C0; - static const uint32 kBossCodeLen = 0x20; - - static const uint32 kURL = 0x48; - static const uint32 kURLLen = 0x100; - - static const uint32 kClientCert = 0x41; - static const uint32 kCACert = 0x188; - - static const uint32 kServiceToken = 0x590; - static const uint32 kServiceTokenLen = 0x200; - - static const uint32 kDirectorySizeLimit = 0x7F0; - static const uint32 kDirectoryName = 0x7C8; - static const uint32 kDirectoryNameLen = 0x8; - - static const uint32 kFileName = 0x7D0; - static const uint32 kNbdlFileName = 0x7F8; - static const uint32 kFileNameLen = 0x20; - - std::array settings; - uint32be taskType; // +0x1000 - }; - - static_assert(sizeof(TaskSetting) == 0x1004, "sizeof(TaskSetting_t)"); - - struct Task - { - char task_id[8]{}; - uint32 account_id; - uint64 title_id; - - TaskSetting task_settings; - - uint32 exec_count = 0; - std::shared_ptr curl; - uint64 content_length = 0; - uint64 processed_length = 0; - - uint32 turn_state = 0; - uint32 wait_state = 0; - - long http_status_code = 0; - ContentType content_type = ContentType::kUnknownContent; - - std::vector result_buffer; - - std::queue queued_files; - std::vector file_buffer; - uint32 processed_file_size = 0; - - Task(const char* id, uint32 account_id, uint64 title_id, TaskSetting* settings) - { - strncpy(task_id, id, sizeof(task_id)); - this->account_id = account_id; - this->title_id = title_id; - this->task_settings.settings = settings->settings; - this->task_settings.taskType = settings->taskType; - - curl = std::shared_ptr(curl_easy_init(), curl_easy_cleanup); - if(GetConfig().proxy_server.GetValue() != "") - { - curl_easy_setopt(curl.get(), CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); - } - } - }; - -#define FAD_ENTRY_MAX_COUNT (512) - - struct BossStorageFadEntry - { - char name[0x20]; - uint32be fileNameId; - uint32 ukn24; - uint32 ukn28; - uint32 ukn2C; - uint32 ukn30; - uint32be timestampRelated; // guessed - }; - - static_assert(sizeof(BossStorageFadEntry) == 0x38, "sizeof(BossStorageFadEntry)"); - - struct BossStorageFadFile - { - uint8 _00[0x08]; - BossStorageFadEntry entries[FAD_ENTRY_MAX_COUNT]; - }; - - static_assert(sizeof(BossStorageFadFile) == 28680, "sizeof(BossStorageFadFile)"); - - struct BossNbdlHeader - { - /* +0x00 */ - uint32be magic; - /* +0x04 */ - uint32be version; // guessed - /* +0x08 */ - uint16be ukn08; // must always be 1 - /* +0x0A */ - uint16be ukn0A; // must always be 2 - /* +0x0C */ - uint8 nonce[0xC]; - /* +0x18 */ - uint32 padding18; // unused - /* +0x1C */ - uint32 padding1C; // unused - /* +0x20 */ - struct - { - uint8 uknHashData[0x20]; - } encryptedHeader; - } ; - - static_assert(sizeof(BossNbdlHeader) == 0x40, "BossNbdlHeader has invalid size"); - static_assert(offsetof(BossNbdlHeader, encryptedHeader) == 0x20, "offsetof(BossNbdlHeader, encryptedHeader)"); - - - struct - { - bool is_initialized; - - std::vector tasks; - } g_boss = {}; - - /* - X-BOSS-Closed - X-BOSS-TitleId - X-Boss-UniqueId - �\r���\r�(X-BOSS-Digest - - LOAD:E01038B4 0000000C C %susr/boss/ - LOAD:E0103A6C 00000011 C %susr/boss/%08x/ - LOAD:E0103B04 00000016 C %susr/boss/%08x/%08x/ - LOAD:E0103B1C 0000001B C %susr/boss/%08x/%08x/user/ - LOAD:E0103B5C 00000020 C %susr/boss/%08x/%08x/user/%08x/ - LOAD:E0103B38 00000022 C %susr/boss/%08x/%08x/user/common/ - LOAD:E0103AC4 00000020 C %susr/boss/%08x/%08x/user/temp/ - - LOAD:E01106DC 0000000A C /dev/boss - - LOAD:05063698 0000001C C /vol/storage_mlc01/usr/boss - LOAD:E2299CA8 00000028 C /vol/storage_mlc01/usr/save/system/boss - */ - - template - curl_slist* append_header_param(struct curl_slist* list, const char* format, TArgs&& ... args) - { - return curl_slist_append(list, fmt::format(fmt::runtime(format), std::forward(args)...).c_str()); - } - - bool starts_with(const char* str, const char* pre) - { - const size_t len_string = strlen(str); - const size_t len_prefix = strlen(pre); - return len_string < len_prefix ? false : strncmp(pre, str, len_prefix) == 0; - } - - size_t task_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) - { - Task* task = (Task*)userdata; - const size_t writeByteSize = size * nmemb; - - //if (task->result_buffer.size() - task->processed_length < writeByteSize) - // task->result_buffer.resize(task->result_buffer.size() + writeByteSize); - //writeByteSize = min(writeByteSize, task->result_buffer.capacity() - task->processed_length); - - //cemuLog_logDebug(LogType::Force, "task_write_callback: {} (processed: {})", writeByteSize, task->processed_length); - if (writeByteSize > 0) - { - //memcpy(task->result_buffer.data() + task->processed_length, ptr, writeByteSize); - task->result_buffer.insert(task->result_buffer.end(), ptr, ptr + writeByteSize); - task->processed_length += writeByteSize; - } - - return writeByteSize; - } - - size_t task_download_header_callback(char* ptr, size_t size, size_t nitems, void* userdata) - { - //cemuLog_logDebug(LogType::Force, "\tHeader: {}", ptr); - - return size * nitems; - } - - size_t task_download_filecallback(char* ptr, size_t size, size_t nmemb, void* userdata) - { - Task* task = (Task*)userdata; - const size_t writeByteSize = size * nmemb; - //writeByteSize = min(writeByteSize, task->file_buffer.capacity() - task->processed_file_size); - if (writeByteSize > 0) - { - //memcpy(task->file_buffer.data() + task->processed_file_size, ptr, writeByteSize); - task->file_buffer.insert(task->file_buffer.end(), ptr, ptr + writeByteSize); - task->processed_file_size += writeByteSize; - } - return writeByteSize; - } - - size_t task_header_callback(char* ptr, size_t size, size_t nitems, void* userdata) - { - Task* task = (Task*)userdata; - if (starts_with(ptr, "Content-Length: ")) - { - task->content_length = strtol(&ptr[16], nullptr, 0); - task->result_buffer.clear(); - task->result_buffer.reserve(task->content_length); - task->processed_length = 0; - } - else if (starts_with(ptr, "Content-Type: ")) - { - const char* type = &ptr[14]; - if (starts_with(type, "application/xml") || starts_with(type, "text/xml")) - task->content_type = ContentType::kXmlContent; - else if (starts_with(type, "x-application/octet-stream")) - task->content_type = ContentType::kBinaryFile; - else if (starts_with(type, "text/html")) - task->content_type = ContentType::kText; - else - { - cemuLog_logDebug(LogType::Force, "task_header_callback: unknown content type > {}", type); - } - } - else if (starts_with(ptr, "Last-Modified: ")) - { - // TODO timestamp (?) - } - - //cemuLog_logDebug(LogType::Force, "task_header_callback: len {} ({}) and type {}", task->content_length, task->result_buffer.capacity(), task->content_type); - //cemuLog_logDebug(LogType::Force, "\t{}", ptr); - return size * nitems; - } - - static CURLcode task_sslctx_function(CURL* curl, void* sslctx, void* param) - { - auto task_settings = (TaskSetting*)param; - if (task_settings->taskType == kRawDlTaskSetting) - { - cemuLog_logDebug(LogType::Force, "sslctx_function: adding client cert: {}", (int)task_settings->settings[TaskSetting::kClientCert]); - if (!iosuCrypto_addClientCertificate(sslctx, task_settings->settings[TaskSetting::kClientCert])) - assert_dbg(); - - uint32 location = TaskSetting::kCACert; - for (int i = 0; i < 3; ++i) - { - if (task_settings->settings[location] != 0) - { - cemuLog_logDebug(LogType::Force, "sslctx_function: adding ca cert: {}", (int)task_settings->settings[location]); - if (!iosuCrypto_addCACertificate(sslctx, task_settings->settings[location])) - { - cemuLog_log(LogType::Force, "Failed to load CA certificate file"); - assert_dbg(); - } - } - - location += TaskSetting::kCACert; - } - } - else - { - if (!iosuCrypto_addCACertificate(sslctx, 105)) - { - cemuLog_log(LogType::Force, "Failed to load certificate file"); - assert_dbg(); - } - - if (!iosuCrypto_addClientCertificate(sslctx, 3)) - { - cemuLog_log(LogType::Force, "Failed to load client certificate file"); - assert_dbg(); - } - } - - SSL_CTX_set_cipher_list((SSL_CTX*)sslctx, "AES256-SHA"); - // TLS_RSA_WITH_AES_256_CBC_SHA (in CURL it's called rsa_aes_256_sha) - SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); - SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); - return CURLE_OK; - } - - auto get_task(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = std::find_if(g_boss.tasks.begin(), g_boss.tasks.end(), [taskId, accountId, titleId](const Task& task) - { - return 0 == strncmp(taskId, task.task_id, sizeof(Task::task_id)) && accountId == task.account_id && titleId == task. - title_id; - }); - - return it; - } - - bool parse_xml_content(Task& task) - { - tinyxml2::XMLDocument doc; - //cemuLog_log(LogType::Force, (char*)task.result_buffer.data()); - if (doc.Parse((const char*)task.result_buffer.data(), task.processed_length) != tinyxml2::XML_SUCCESS) - return false; - - for (tinyxml2::XMLElement* sheet = doc.FirstChildElement("TaskSheet"); sheet; sheet = sheet-> - NextSiblingElement("TaskSheet")) - { - const auto files = sheet->FirstChildElement("Files"); - if (!files) - continue; - - for (tinyxml2::XMLElement* file = files->FirstChildElement("File"); file; file = file->NextSiblingElement("File")) - { - auto file_name = file->FirstChildElement("Filename"); - if (!file_name) - continue; - - auto data_id = file->FirstChildElement("DataId"); - if (!data_id) - continue; - - auto type = file->FirstChildElement("Type"); - if (!type) - continue; - - auto url = file->FirstChildElement("Url"); - if (!url) - continue; - - auto size = file->FirstChildElement("Size"); - if (!size) - continue; - - FileType file_type; - if (0 == strcmp(type->GetText(), "AppData")) - file_type = FileType::kAppData; - else - { - file_type = FileType::kUnknownFile; - } - - task.queued_files.emplace(file_name->GetText(), data_id->IntText(), file_type, url->GetText(), size->IntText()); - } - } - - return true; - } - - const uint64 kTimeStampConvertSeconds = 946684800ULL; - BossStorageFadEntry* boss_storage_fad_find_entry(BossStorageFadFile& fad_file, uint32 data_id) - { - for (auto& entry : fad_file.entries) - { - if (entry.fileNameId == data_id) - return &entry; - } - - return nullptr; - } - - void boss_storage_fad_append_or_update(BossStorageFadFile& fad_file, const char* name, uint32 data_id, uint64 timestamp) - { - for (auto& entry : fad_file.entries) - { - if (entry.fileNameId == 0 || strcmp(entry.name, name) == 0) - { - entry.fileNameId = data_id; - strcpy(entry.name, name); - entry.timestampRelated = (uint32)(timestamp - kTimeStampConvertSeconds); // time since 2000 - return; - } - } - } - - uint32 task_run(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - if (it == g_boss.tasks.cend()) - { - //it->turn_state = kError; - //it->wait_state = TRUE; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); - } - - if (!ActiveSettings::IsOnlineEnabled()) - { - it->turn_state = kError; - it->wait_state = TRUE; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - cemuLog_logDebug(LogType::Force, "task run state: {} | exec: {} (tasks: {})", it->turn_state, it->exec_count, g_boss.tasks.size()); - it->turn_state = kRunning; - it->exec_count++; - - /* - https://nppl.app.nintendo.net/p01/policylist/1/1/AT - https://npts.app.nintendo.net/p01/tasksheet/1/zvGSM4kO***kKnpT/schdat2?c=XX&l=en - https://npts.app.nintendo.net/p01/tasksheet/1/zvGSM4kO***kKnpT/optdat2?c=XX&l=en - https://npts.app.nintendo.net/p01/tasksheet/1/8UsM86l***jFk8z/wood1?c=XX&l=en - https://npts.app.nintendo.net/p01/tasksheet/1/8UsM86l***kjFk8z/woodBGM?c=XX&l=en - - - https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s/%s?c=%s&l=%s - https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s?c=%s&l=%s - 1 == version - bossCode - initFile - - */ - - uint32 turnstate = kSuccess; - struct curl_slist* list_headerParam = nullptr; - list_headerParam = append_header_param(list_headerParam, "X-BOSS-Digest"); // ??? - list_headerParam = append_header_param(list_headerParam, "X-Boss-UniqueId: {:05x}", ((titleId >> 8) & 0xFFFFF)); // %05x - list_headerParam = append_header_param(list_headerParam, "X-BOSS-TitleId: /usr/packages/title/{:016x}", titleId); // "/usr/packages/title/%016llx" - - CURL* curl = it->curl.get(); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 2); -#ifdef CEMU_DEBUG_ASSERT - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); - char errbuf[CURL_ERROR_SIZE]{}; - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); -#else - curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); -#endif - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, task_write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &(*it)); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it)); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C); - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - if (IsNetworkServiceSSLDisabled(ActiveSettings::GetNetworkService())) - { - curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L); - } - else - { - curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, task_sslctx_function); - curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &it->task_settings); - curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0); - } - - std::string requestUrl; - if(it->task_settings.taskType == kRawDlTaskSetting) - { - char serviceToken[TaskSetting::kServiceTokenLen]; - strncpy(serviceToken, (char*)&it->task_settings.settings[TaskSetting::kServiceToken], TaskSetting::kServiceTokenLen); - list_headerParam = append_header_param(list_headerParam, "X-Nintendo-ServiceToken: {}", serviceToken); - - char url[TaskSetting::kURLLen + 1]{}; - strncpy(url, (char*)&it->task_settings.settings[TaskSetting::kURL], TaskSetting::kURLLen); - requestUrl.assign(url); - } - else - { - char languageCode[8]; - switch (GetConfig().console_language) - { - case CafeConsoleLanguage::JA: - strcpy(languageCode, "ja"); - break; - case CafeConsoleLanguage::EN: - strcpy(languageCode, "en"); - break; - case CafeConsoleLanguage::FR: - strcpy(languageCode, "fr"); - break; - case CafeConsoleLanguage::DE: - strcpy(languageCode, "de"); - break; - case CafeConsoleLanguage::IT: - strcpy(languageCode, "it"); - break; - case CafeConsoleLanguage::ES: - strcpy(languageCode, "es"); - break; - case CafeConsoleLanguage::ZH: - strcpy(languageCode, "zh"); - break; - case CafeConsoleLanguage::KO: - strcpy(languageCode, "ko"); - break; - case CafeConsoleLanguage::NL: - strcpy(languageCode, "nl"); - break; - case CafeConsoleLanguage::PT: - strcpy(languageCode, "pt"); - break; - case CafeConsoleLanguage::RU: - strcpy(languageCode, "ru"); - break; - case CafeConsoleLanguage::TW: - strcpy(languageCode, "tw"); // usually zh-tw? - break; - default: - strcpy(languageCode, "en"); - break; - } - - const char* countryCode = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); - - char boss_code[0x20]; - strncpy(boss_code, (char*)&it->task_settings.settings[TaskSetting::kBossCode], TaskSetting::kBossCodeLen); - - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Pretendo: - requestUrl = PretendoURLs::BOSSURL; - break; - case NetworkService::Custom: - requestUrl = GetNetworkConfig().urls.BOSS.GetValue(); - break; - case NetworkService::Nintendo: - default: - requestUrl = NintendoURLs::BOSSURL; - break; - } - requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), "1", boss_code, it->task_id, countryCode, languageCode)); - } - - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list_headerParam); - curl_easy_setopt(curl, CURLOPT_URL, requestUrl.c_str()); - - int curl_result = curl_easy_perform(curl); - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &it->http_status_code); - static_assert(sizeof(it->http_status_code) == sizeof(long)); - - //it->turn_state = kFinished; - - curl_slist_free_all(list_headerParam); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr); - - if (curl_result != CURLE_OK) - { -#ifdef CEMU_DEBUG_ASSERT - cemuLog_logDebug(LogType::Force, "curl error buff: {}", errbuf); -#endif - it->turn_state = kError; - it->wait_state = TRUE; - cemuLog_logDebug(LogType::Force, "task_run curl fail: {}", curl_result); - return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); - } - else - { - if (it->http_status_code != 200) - { - cemuLog_logDebug(LogType::Force, "BOSS task_run: Received unexpected HTTP response code"); - } - if (it->http_status_code == 404) - { - // todo - is this correct behavior? - it->turn_state = kError; - it->wait_state = TRUE; - cemuLog_logDebug(LogType::Force, "task_run failed due to 404 error"); - return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); - } - } - - switch (it->content_type) - { - case ContentType::kXmlContent: - parse_xml_content(*it); - break; - case ContentType::kBinaryFile: - - break; - case ContentType::kText: - cemuLog_logDebug(LogType::Force, "task_run returns text: {}", fmt::ptr(it->result_buffer.data())); - break; - } - - - if (!it->queued_files.empty()) - { - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_download_header_callback); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, task_download_filecallback); - - std::string taskIdStr = it->task_id; - - try - { - fs::path path = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}", (uint32)(it->title_id >> 32), (uint32)(it->title_id & 0xFFFFFFFF), taskIdStr); - if (!fs::exists(path)) - fs::create_directories(path); - - char targetFileName[TaskSetting::kFileNameLen + 1]{}; - strncpy(targetFileName, (char*)&it->task_settings.settings[TaskSetting::kNbdlFileName], TaskSetting::kFileNameLen); - cemuLog_logDebug(LogType::Force, "\tnbdl task target filename: \"{}\"", targetFileName); - const bool hasFileName = targetFileName[0] != '\0'; - - while (!it->queued_files.empty()) - { - auto file = it->queued_files.front(); - it->queued_files.pop(); - // download only specific file - if (hasFileName && file.file_name != targetFileName) - continue; - - it->processed_file_size = 0; - it->file_buffer.clear(); - it->file_buffer.reserve(file.size); - - // create/open fad db content - BossStorageFadFile fad_content{}; - fs::path db_file = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", (uint32)(it->title_id >> 32), (uint32)(it->title_id & 0xFFFFFFFF), it->account_id, taskIdStr); - if (!fs::exists(db_file)) - fs::create_directories(db_file); - - db_file /= "fad.db"; - std::ifstream fad_file(db_file, std::ios::in | std::ios::binary); - if (fad_file.is_open()) - { - if (!fad_file.read((char*)&fad_content, sizeof(BossStorageFadFile))) - fad_content = {}; - - fad_file.close(); - } - - auto currentEntry = boss_storage_fad_find_entry(fad_content, file.data_id); - //TODO deep dive into IOSU to figure out how caching actually works on th Wii U - if(currentEntry && fs::exists(path / fmt::format(L"{:08x}", file.data_id))) - { - uint64 timestamp = (uint64)currentEntry->timestampRelated + kTimeStampConvertSeconds; - curl_easy_setopt(curl, CURLOPT_TIMEVALUE, timestamp); - curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); - } - else - { - curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 0); - curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); - } - - curl_easy_setopt(curl, CURLOPT_FILETIME, 1L); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, task_download_header_callback); - curl_easy_setopt(curl, CURLOPT_URL, file.url.c_str()); - curl_result = curl_easy_perform(curl); - if (curl_result != CURLE_OK) - { - cemuLog_logDebug(LogType::Force, "task_run curl failed on file download ({}): {} > {}", curl_result, file.file_name, file.url); - if (hasFileName) - { - turnstate = kError; - break; - } - else - continue; - } - - long unmet = 1; - const CURLcode result = curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &unmet); - if(result == CURLE_OK && unmet == 1) - { - // file is already up2date - if (hasFileName) - break; - else - continue; - } - - if(it->processed_file_size != file.size) - { - cemuLog_logDebug(LogType::Force, "task_run file download size mismatch: {} > {} > {} from {} bytes", file.file_name, file.url, it->processed_file_size, file.size); - if (hasFileName) - { - turnstate = kError; - break; - } - else - continue; - } - - uint64 filetime; - curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime); - - // dunno about that one - it->processed_length += it->processed_file_size; - it->content_length += file.size; - - // bossAesKey = OTP.XorKey ^ bossXor - // bossXor: 33 AC 6D 15 C2 26 0A 91 3B BF 73 C3 55 D8 66 04 - uint8 bossAesKey[16] = { 0x39,0x70,0x57,0x35,0x58,0x70,0x34,0x58,0x37,0x41,0x7a,0x30,0x71,0x5a,0x70,0x74 }; // "9pW5Xp4X7Az0qZpt" - - BossNbdlHeader* nbdlHeader = (BossNbdlHeader*)it->file_buffer.data(); - - if (nbdlHeader->magic != 'boss') - break; - if (nbdlHeader->version != 0x20001) - break; - if (nbdlHeader->ukn08 != 1) - break; - if (nbdlHeader->ukn0A != 2) - break; - - // file must be padded to 16 byte alignment for AES decryption (padding is cut off after decryption) - const uint32 file_size = (it->processed_file_size + 0xF) & (~0xF); - if (file_size != it->processed_file_size) - { - it->file_buffer.resize(file_size); - nbdlHeader = (BossNbdlHeader*)it->file_buffer.data(); - } - - // prepare nonce for AES128-CTR - uint8 aesNonce[0x10]; - memset(aesNonce, 0, sizeof(aesNonce)); - memcpy(aesNonce, nbdlHeader->nonce, 0xC); - aesNonce[0xF] = 1; - - // decrypt header - AES128CTR_transform(it->file_buffer.data() + offsetof(BossNbdlHeader, encryptedHeader), sizeof(BossNbdlHeader::encryptedHeader), bossAesKey, aesNonce); - // decrypt everything else - AES128CTR_transform(it->file_buffer.data() + sizeof(BossNbdlHeader), file_size - sizeof(BossNbdlHeader), bossAesKey, aesNonce); - - try - { - // create file with content - fs::path file_path = path / fmt::format(L"{:08x}", file.data_id); - std::ofstream new_file(file_path, std::ios::out | std::ios::binary | std::ios::trunc); - new_file.write((char*)it->file_buffer.data() + sizeof(BossNbdlHeader), it->processed_file_size - sizeof(BossNbdlHeader)); - new_file.flush(); - new_file.close(); - - - boss_storage_fad_append_or_update(fad_content, file.file_name.c_str(), file.data_id, filetime); - std::ofstream fad_file_updated(db_file, std::ios::out | std::ios::binary | std::ios::trunc); - fad_file_updated.write((char*)&fad_content, sizeof(BossStorageFadFile)); - fad_file_updated.flush(); - fad_file_updated.close(); - } - catch (const std::exception& ex) - { - cemuLog_logDebug(LogType::Force, "file error: {}", ex.what()); - } - - if (hasFileName) - break; - } - } - catch (const std::exception& ex) - { - cemuLog_logDebug(LogType::Force, "dir error: {}", ex.what()); - } - } - - - if (it->task_settings.taskType == kRawDlTaskSetting) - { - char directoryName[TaskSetting::kDirectoryNameLen + 1]{}; - if (it->task_settings.settings[TaskSetting::kDirectoryName] != '\0') - strncpy(directoryName, (char*)&it->task_settings.settings[TaskSetting::kDirectoryName], TaskSetting::kDirectoryNameLen); - else - strncpy(directoryName, it->task_id, TaskSetting::kDirectoryNameLen); - - char fileName[TaskSetting::kFileNameLen + 1]{}; - strncpy(fileName, (char*)&it->task_settings.settings[TaskSetting::kFileName], TaskSetting::kFileNameLen); - - // mcl01\usr\boss\00050000\1018dd00\user\\\ - fs::path path = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/{:08x}", (uint32)(it->title_id >> 32), - (uint32)(it->title_id & 0xFFFFFFFF), iosuAct_getAccountIdOfCurrentAccount()); - path /= directoryName; - - if (!fs::exists(path)) - fs::create_directories(path); - - path /= fileName; - - std::ofstream file(path); - if (file.is_open()) - { - file.write((char*)it->result_buffer.data(), it->result_buffer.size()); - file.flush(); - file.close(); - } - } - - it->turn_state = turnstate; - it->wait_state = TRUE; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - - bool task_is_registered(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend(); - } - - bool task_wait(const char* taskId, uint32 accountId, uint64 titleId, uint32 wait_state, uint32 timeout = 0) - { - const auto it = get_task(taskId, accountId, titleId); - if (it == g_boss.tasks.cend()) - { - return false; - } - - const auto start = tick_cached(); - while (it->wait_state != wait_state) - { - if (timeout != 0 && (uint32)std::chrono::duration_cast(tick_cached() - start).count() >= timeout) - { - cemuLog_logDebug(LogType::Force, "task_wait: timeout reached -> {} seconds passed", timeout); - return false; - } - - std::this_thread::yield(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - return true; - } - - uint32 task_register(const char* taskId, uint32 accountId, uint64 titleId, void* settings) - { - g_boss.tasks.emplace_back(taskId, accountId, titleId, (TaskSetting*)settings); - g_boss.tasks[g_boss.tasks.size() - 1].turn_state = kWaitTime; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - uint32 task_register_immediate_run(const char* taskId, uint32 accountId, uint64 titleId, void* settings) - { - g_boss.tasks.emplace_back(taskId, accountId, titleId, (TaskSetting*)settings); - g_boss.tasks[g_boss.tasks.size() - 1].turn_state = kWaitRun; - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - void task_unregister(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - if (it != g_boss.tasks.cend()) - g_boss.tasks.erase(it); - } - - std::pair task_get_content_length(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->content_length) : std::make_pair(0u, (uint64)0); - } - - std::pair task_get_processed_length(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->processed_length) : std::make_pair(0u, (uint64)0); - } - - std::pair task_get_http_status_code(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->http_status_code) : std::make_pair(0u, (long)0); - } - - std::pair task_get_turn_state(const char* taskId, uint32 accountId, uint64 titleId) - { - const auto it = get_task(taskId, accountId, titleId); - return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->turn_state) : std::make_pair(0u, (uint32)0); - } - - uint32 task_stop_scheduling(const char* task_id, uint32 account_id, uint64 title_id) - { - const auto it = get_task(task_id, account_id, title_id); - if (it != g_boss.tasks.cend()) - { - it->turn_state = kStopped; - // todo actually cancel the task if currently running (curl call) - // curl_easy_pause() -> resume on start scheduling if paused - } - - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); - } - - void boss_thread() - { - SetThreadName("boss_thread"); - while (true) - { - const uint32 return_value = 0; // Ioctl return value - - ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_BOSS); - if (ioQueueEntry->request == IOSU_BOSS_REQUEST_CEMU) - { - iosuBossCemuRequest_t* cemu_request = (iosuBossCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); - cemu_request->returnCode = 0; - - uint64 title_id; - if (cemu_request->titleId == 0) - title_id = CafeSystem::GetForegroundTitleId(); - else - title_id = cemu_request->titleId; - - uint32 account_id; - if (cemu_request->accountId == 0) - account_id = iosuAct_getAccountIdOfCurrentAccount(); - else - account_id = cemu_request->accountId; - - if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_RUN) - { - cemu_request->returnCode = task_run(cemu_request->taskId, account_id, title_id); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH) - { - auto result = task_get_content_length(cemu_request->taskId, account_id, title_id); - cemu_request->u64.exec_count = std::get<0>(result); - cemu_request->u64.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH) - { - auto result = task_get_processed_length(cemu_request->taskId, account_id, title_id); - cemu_request->u64.exec_count = std::get<0>(result); - cemu_request->u64.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE) - { - auto result = task_get_http_status_code(cemu_request->taskId, account_id, title_id); - cemu_request->u32.exec_count = std::get<0>(result); - cemu_request->u32.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_TURN_STATE) - { - auto result = task_get_turn_state(cemu_request->taskId, account_id, title_id); - cemu_request->u32.exec_count = std::get<0>(result); - cemu_request->u32.result = std::get<1>(result); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_WAIT) - { - cemu_request->returnCode = task_wait(cemu_request->taskId, account_id, title_id, cemu_request->waitState, - cemu_request->timeout); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_REGISTER) - { - cemu_request->returnCode = task_register(cemu_request->taskId, account_id, title_id, cemu_request->settings); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_IS_REGISTERED) - { - cemu_request->returnCode = task_is_registered(cemu_request->taskId, account_id, title_id) ? TRUE : FALSE; - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN) - { - cemu_request->returnCode = task_register_immediate_run(cemu_request->taskId, account_id, title_id, - cemu_request->settings); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_UNREGISTER) - { - task_unregister(cemu_request->taskId, account_id, title_id); - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_START_SCHEDULING) - { - // we just run it no matter what - //if(cemu_request->bool_parameter) - cemu_request->returnCode = task_run(cemu_request->taskId, account_id, title_id); - /*else - { - const auto it = get_task(cemu_request->taskId, account_id, title_id); - if (it != g_boss.tasks.cend()) - { - it->turn_state = kWaitRun; - } - }*/ - } - else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_STOP_SCHEDULING) - { - cemu_request->returnCode = task_stop_scheduling(cemu_request->taskId, account_id, title_id); - } - else - assert_dbg(); - } - else - assert_dbg(); - - iosuIoctl_completeRequest(ioQueueEntry, return_value); - } - } - - void boss_init() - { - if (g_boss.is_initialized) - return; - - // start the boss thread - std::thread t(boss_thread); - t.detach(); - - g_boss.is_initialized = true; - } -} diff --git a/src/Cafe/IOSU/legacy/iosu_boss.h b/src/Cafe/IOSU/legacy/iosu_boss.h deleted file mode 100644 index 88fa94f7..00000000 --- a/src/Cafe/IOSU/legacy/iosu_boss.h +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/boss/boss_common.cpp b/src/Cafe/IOSU/nn/boss/boss_common.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/Cafe/IOSU/nn/boss/boss_common.h b/src/Cafe/IOSU/nn/boss/boss_common.h new file mode 100644 index 00000000..fcc66ebb --- /dev/null +++ b/src/Cafe/IOSU/nn/boss/boss_common.h @@ -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 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 vTablePtr{}; // 0x10 + + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator 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 s_VTable; + + uint32be accountId; + MEMPTR 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; // +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 vTablePtr; // +0x1000 + + struct VTableTaskSetting + { + VTableEntry rtti; + VTableEntry dtor; + VTableEntry RegisterPreprocess; // todo - double check the offset + VTableEntry unk1; + }; + static inline SysAllocator 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 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 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 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 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 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 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, + }; + + +} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/boss/boss_service.cpp b/src/Cafe/IOSU/nn/boss/boss_service.cpp new file mode 100644 index 00000000..cc508433 --- /dev/null +++ b/src/Cafe/IOSU/nn/boss/boss_service.cpp @@ -0,0 +1,1431 @@ +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "util/helpers/helpers.h" +#include "Cafe/Filesystem/fsc.h" + +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/IOSU/nn/iosu_nn_service.h" + +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/CafeSystem.h" +#include "config/ActiveSettings.h" + +#include "boss_service.h" +#include "boss_common.h" + +#include +#include +#include +#include +#include + +#include "Cemu/ncrypto/ncrypto.h" +#include "Cemu/napi/napi_helper.h" +#include "IOSU/legacy/iosu_crypto.h" +#include "util/crypto/aes128.h" + +namespace iosu::boss +{ + using namespace ::nn::boss; + + static constexpr nnResult RESULT_SUCCESS = 0x200080; + static constexpr nnResult RESULT_C0203880 = 0xC0203880; + static constexpr nnResult RESULT_NOTEXIST = 0xA021F900; + + static constexpr nnResult RESULT_STORAGE_NOTEXIST = 0xA025AA00; + static constexpr nnResult RESULT_FADENTRY_NOTEXIST = 0xA021FB00; + + template + void AppendHeaderParam(CurlRequestHelper& request, const char* fieldName, const char* format, TArgs&& ... args) + { + request.addHeaderField(fieldName, fmt::format(fmt::runtime(format), std::forward(args)...).c_str()); + } + + class StorageDatabase // FAD + { + static constexpr uint32 FAD_ENTRY_MAX_COUNT = 512; // max entries in a single FAD file + + public: + struct BossStorageFadEntry + { + CafeString<32> fileName; // 0x00 + uint32be fileId; // 0x20 + uint32 ukn24; // 0x24 + uint32 flags; // 0x28 + uint32 memo_2C; // 0x2C + uint64be entryCreationTimestamp; + // flags: + // 0x80000000 ReadFlag + // 0x40000000 DeleteProtectionFlag + // 0x20000000 New arrival flag? + }; + static_assert(sizeof(BossStorageFadEntry) == 0x38); + + struct BossStorageFadFile + { + uint8 _00[0x08]; + BossStorageFadEntry entries[FAD_ENTRY_MAX_COUNT]; + }; + static_assert(sizeof(BossStorageFadFile) == 28680); + + static bool CheckIfStorageExists(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId) + { + fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, bossDirectory.name2.c_str()); + std::error_code ec; + return fs::exists(fadPath, ec); + } + + nnResult Load(TaskId& taskId, uint64 titleId, uint32 persistentId) + { + // todo - if same FAD db already loaded then skip this + m_hasValidFadData = false; + memset(&m_fadData, 0, sizeof(m_fadData)); + fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, taskId.id.c_str()); + std::error_code ec; + if (!fs::exists(fadPath, ec)) + fs::create_directories(fadPath, ec); + fadPath /= "fad.db"; + m_fadFilePath = fadPath; + FileStream* fs = FileStream::openFile2(fadPath); + if (!fs) + return RESULT_STORAGE_NOTEXIST; + fs->readData(&m_fadData, sizeof(m_fadData)); + delete fs; + m_hasValidFadData = true; // the file may not exist yet, so consider it valid even if we cant open it + return RESULT_SUCCESS; + } + + nnResult CreateNewStorage(TaskId& taskId, uint64 titleId, uint32 persistentId) + { + memset(&m_fadData, 0, sizeof(m_fadData)); + fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, taskId.id.c_str()); + std::error_code ec; + if (!fs::exists(fadPath, ec)) + fs::create_directories(fadPath, ec); + m_fadFilePath = fadPath / "fad.db"; + FileStream* fs = FileStream::createFile2(m_fadFilePath); + if (fs) + { + fs->writeData(&m_fadData, sizeof(m_fadData)); + delete fs; + m_hasValidFadData = true; + } + else + { + cemuLog_log(LogType::Force, "Failed to create FAD data file at {}", _pathToUtf8(m_fadFilePath)); + return NN_RESULT_PLACEHOLDER_ERROR; + } + return RESULT_SUCCESS; + } + + void Store() + { + if (!m_hasValidFadData) + return; + FileStream* fs = FileStream::createFile2(m_fadFilePath); + if (fs) + { + fs->writeData(&m_fadData, sizeof(m_fadData)); + delete fs; + } + else + { + cemuLog_log(LogType::Force, "Failed to store FAD data to {}", m_fadFilePath.string()); + } + } + + void Clear() + { + m_hasValidFadData = false; + memset(&m_fadData, 0, sizeof(m_fadData)); + } + + void DeleteEntryByFileName(CafeString<32> fileName) + { + cemu_assert_debug(m_hasValidFadData); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (m_fadData.entries[i].fileName == fileName) + { + // shift remaining entries + memmove(m_fadData.entries + i, m_fadData.entries + i + 1, (FAD_ENTRY_MAX_COUNT - i - 1) * sizeof(BossStorageFadEntry)); + // reset last entry + memset(m_fadData.entries + FAD_ENTRY_MAX_COUNT - 1, 0, sizeof(BossStorageFadEntry)); + return; + } + } + } + + nnResult AddEntry(CafeString<32> fileName, uint32 fileId) + { + if (!fileId) + { + cemuLog_log(LogType::Force, "Cannot add BOSS file with fileId 0"); + return false; + } + if (fileName.empty()) + return false; + DeleteEntryByFileName(fileName); + cemu_assert_debug(m_hasValidFadData); + for (sint32 i=0; i& dataList) + { + cemu_assert_debug(m_hasValidFadData); + dataList.clear(); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (m_fadData.entries[i].fileId == 0) + break; // end of list + DataName dataName; + dataName.name = m_fadData.entries[i].fileName; + dataList.push_back(dataName); + } + } + + bool GetEntryByFilename(const DataName& fileName, BossStorageFadEntry& outEntry) + { + if (fileName.name.empty()) + return false; + cemu_assert_debug(m_hasValidFadData); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (m_fadData.entries[i].fileName == fileName.name) + { + outEntry = m_fadData.entries[i]; + return true; + } + if (m_fadData.entries[i].fileId == 0) + break; // end of list + } + return false; + } + + private: + BossStorageFadFile m_fadData; + bool m_hasValidFadData = false; + fs::path m_fadFilePath; + }; + StorageDatabase m_fadDb; + + class NsDataAccessor + { + public: + nnResult Open(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId, DataName& fileName) + { + m_isValid = false; + m_nsDataPath.clear(); + TaskId taskId(bossDirectory.name2.c_str()); + nnResult r = m_fadDb.Load(taskId, titleId, persistentId); + if (NN_RESULT_IS_FAILURE(r)) + return r; + if (!m_fadDb.GetEntryByFilename(fileName, m_fadEntry)) + return RESULT_FADENTRY_NOTEXIST; + m_nsDataPath = BuildNsDataPath(bossDirectory, titleId, persistentId, m_fadEntry.fileId); + m_isValid = true; + m_bossDirectory = bossDirectory; + m_titleId = titleId; + m_persistentId = persistentId; + m_fileName = fileName; + return RESULT_SUCCESS; + } + + void Close() + { + m_isValid = false; + } + + nnResult Delete() + { + cemu_assert_debug(m_isValid); // only call this after successful Open() + std::error_code ec; + if (!fs::remove(m_nsDataPath, ec) ) + cemuLog_log(LogType::Force, "Failed to delete BOSS file {}", _pathToUtf8(m_nsDataPath)); + // remove from FAD + TaskId taskId(m_bossDirectory.name2.c_str()); + nnResult r = m_fadDb.Load(taskId, m_titleId, m_persistentId); + if (NN_RESULT_IS_SUCCESS(r)) + m_fadDb.DeleteEntryByFileName(m_fileName.name); + m_isValid = false; + return RESULT_SUCCESS; + } + + bool Exists() const + { + if (!m_isValid) + return false; + // check if the file actually exists on the host filesystem + std::error_code ec; + if (m_nsDataPath.empty()) + return false; + bool r = fs::exists(m_nsDataPath, ec); + if (!r) + cemuLog_log(LogType::Force, "BOSS: File exists in FAD cache but not on disk {}. To fix this reset the SpotPass cache. In the menu under debug select \"Clear SpotPass cache\"", _pathToUtf8(m_nsDataPath)); + return r; + } + + nnResult GetSize(uint32& fileSize) const + { + cemu_assert_debug(m_isValid); // only call this after successful Open() + if (!m_isValid) + return NN_RESULT_PLACEHOLDER_ERROR; + std::unique_ptr fs(FileStream::openFile2(m_nsDataPath)); + if (!fs) + return NN_RESULT_PLACEHOLDER_ERROR; + fileSize = (uint32)fs->GetSize(); + return RESULT_SUCCESS; + } + + bool Read(void* buffer, uint32 size, uint32 offset, uint32& bytesRead) + { + if (!m_isValid) + return false; + std::unique_ptr fs(FileStream::openFile2(m_nsDataPath)); + if (!fs) + return false; + fs->SetPosition(offset); + bytesRead = (uint32)fs->readData(buffer, size); + return true; + } + + private: + fs::path BuildNsDataPath(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId, uint32 dataId) + { + return ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), bossDirectory.name2.c_str(), dataId); + } + + bool m_isValid{false}; + StorageDatabase::BossStorageFadEntry m_fadEntry; + fs::path m_nsDataPath; + // if m_isValid is true, these hold the current file information: + DirectoryName m_bossDirectory; + uint64 m_titleId{}; + uint32 m_persistentId{}; + DataName m_fileName; + }; + + NsDataAccessor m_nsDataAccessor; + + class RegisteredTask + { + public: + RegisteredTask(const TaskId& taskId, const TaskSettingCore& taskSettings) + : m_taskId(taskId), m_taskSettings(taskSettings) + { + m_persistentId = iosuAct_getAccountIdOfCurrentAccount(); + } + + nnResult Run() + { + std::unique_lock _l(m_mutex); + cemu_assert_debug(m_taskState == TaskState::Initial || m_taskState == TaskState::Done); + m_taskTurnState = TaskTurnState::Ready; + m_taskState = TaskState::Ready; + return RESULT_SUCCESS; + } + + TaskState GetState() + { + std::unique_lock _l(m_mutex); + return m_taskState; + } + + TaskTurnState GetTurnState() + { + std::unique_lock _l(m_mutex); + return m_taskTurnState; + } + + sint32 GetHttpStatusCode() const + { + return m_httpStatusCode; + } + + uint32 GetContentLength() const + { + return m_contentLength; + } + + uint32 GetProcessedLength() const + { + return m_contentLength; // todo - unlike content length, this value is getting updated as the download happens. But for now we just return the content length + } + + nnResult TaskDoRequest(CURL* curl) + { + std::unique_lock _l(m_mutex); + if (m_taskState != TaskState::Ready) + { + cemuLog_log(LogType::Force, "Task {} is not ready to run, current state: {}", m_taskId.id.c_str(), static_cast(m_taskState)); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + m_taskState = TaskState::Running; + m_taskTurnState = TaskTurnState::Running; + m_contentLength = 0; + _l.unlock(); + if (!ActiveSettings::IsOnlineEnabled()) + { + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + uint64 titleId = CafeSystem::GetForegroundTitleId(); + // construct request URL + std::string requestUrl; + if (!m_taskSettings.url.empty()) + requestUrl.assign(m_taskSettings.url.c_str()); // custom url + else + { + switch (ActiveSettings::GetNetworkService()) + { + case NetworkService::Pretendo: + requestUrl = PretendoURLs::BOSSURL; + break; + case NetworkService::Custom: + requestUrl = GetNetworkConfig().urls.BOSS.GetValue(); + break; + case NetworkService::Nintendo: + default: + requestUrl = NintendoURLs::BOSSURL; + break; + } + } + char languageCode[8]; + switch (GetConfig().console_language) + { + case CafeConsoleLanguage::JA: + strcpy(languageCode, "ja"); + break; + case CafeConsoleLanguage::EN: + strcpy(languageCode, "en"); + break; + case CafeConsoleLanguage::FR: + strcpy(languageCode, "fr"); + break; + case CafeConsoleLanguage::DE: + strcpy(languageCode, "de"); + break; + case CafeConsoleLanguage::IT: + strcpy(languageCode, "it"); + break; + case CafeConsoleLanguage::ES: + strcpy(languageCode, "es"); + break; + case CafeConsoleLanguage::ZH: + strcpy(languageCode, "zh"); + break; + case CafeConsoleLanguage::KO: + strcpy(languageCode, "ko"); + break; + case CafeConsoleLanguage::NL: + strcpy(languageCode, "nl"); + break; + case CafeConsoleLanguage::PT: + strcpy(languageCode, "pt"); + break; + case CafeConsoleLanguage::RU: + strcpy(languageCode, "ru"); + break; + case CafeConsoleLanguage::TW: + strcpy(languageCode, "tw"); // usually zh-tw? + break; + default: + strcpy(languageCode, "en"); + break; + } + const char* countryCode = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); + if (m_taskSettings.taskType == TaskType::NbdlDataListTaskSetting) + { + // use bossCode (and fileName?) as part of the URL + requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), "1", m_taskSettings.nbdl.bossCode.c_str(), m_taskId.id.c_str(), countryCode, languageCode)); + cemu_assert_unimplemented(); + cemuLog_logDebug(LogType::Force, "IOSU-BOSS: Unsupported task type: {}", static_cast(m_taskSettings.taskType.value())); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + m_contentLength = 0; + m_httpStatusCode = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + else if (m_taskSettings.taskType == TaskType::NbdlTaskSetting) + { + // todo - language and country code come from the task settings + cemu_assert_debug(m_taskSettings.nbdl.fileName.empty()); // todo - changes the url + const char* apiVersion = "1"; + requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), apiVersion, m_taskSettings.nbdl.bossCode.c_str(), m_taskId.id.c_str(), countryCode, languageCode)); + } + else if (m_taskSettings.taskType == TaskType::PlayReportSetting) + { + cemuLog_logDebug(LogType::Force, "IOSU-BOSS: PlayReport tasks are not implemented yet"); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + m_contentLength = 0; + m_httpStatusCode = 200; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + else + { + cemuLog_logDebug(LogType::Force, "IOSU-BOSS: Unknown task type: {}", static_cast(m_taskSettings.taskType.value())); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + m_contentLength = 0; + m_httpStatusCode = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + CurlRequestHelper request; + request.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::CUSTOM); + // add certs + SetupSSL(request); + // parameters + AppendHeaderParam(request, "X-BOSS-Digest:", ""); // todo - implement this + AppendHeaderParam(request, "X-Boss-UniqueId", "{:05x}", ((titleId >> 8) & 0xFFFFF)); + AppendHeaderParam(request, "X-BOSS-TitleId", "{:016x}", titleId); + if (!m_taskSettings.serviceToken.empty()) + AppendHeaderParam(request, "X-Nintendo-ServiceToken", "{}", m_taskSettings.serviceToken.c_str()); + + if (!request.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed BOSS request")); + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + sint32 httpStatusCode = request.GetHTTPStatusCode(); + m_httpStatusCode = httpStatusCode; + m_contentLength = request.getReceivedData().size(); // note - for Nbdl tasks which length is returned? + + { + if (httpStatusCode != 200) + { + cemuLog_logDebug(LogType::Force, "BOSS task_run: Received unexpected HTTP response code {}", httpStatusCode); + cemuLog_logDebug(LogType::Force, "URL: {}", requestUrl); + } + if (httpStatusCode == 404) + { + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneError; + cemuLog_logDebug(LogType::Force, "BOSS task_run: Received 404 Not Found for URL: {}", requestUrl); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + } + + // for Nbdl tasks get the list of files to download + if (m_taskSettings.taskType == TaskType::NbdlTaskSetting) + { + std::vector queuedFiles = ParseNbdlTasksheet(request.getReceivedData()); + cemuLog_log(LogType::Force, "SpotPass - NbdlTask has {} files to download:", queuedFiles.size()); + for (const auto& file : queuedFiles) + { + DownloadNbdlFile(file); + } + } + + if (m_taskSettings.taskType == TaskType::RawDlTaskSetting_1 || m_taskSettings.taskType == TaskType::RawDlTaskSetting_3 || m_taskSettings.taskType == TaskType::RawDlTaskSetting_9) + { + cemu_assert_unimplemented(); + } + { + _l.lock(); + m_taskState = TaskState::Done; + m_taskTurnState = TaskTurnState::DoneSuccess; + } + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + std::recursive_mutex& GetMutex() { return m_mutex; } + private: + struct NbdlQueuedFile + { + NbdlQueuedFile(std::string_view url, std::string_view fileName, uint32 dataId, uint32 fileType, uint32 size) + : url(url), fileName(fileName), dataId(dataId), fileType(fileType), size(size) + {} + + std::string url; + std::string fileName; + uint32 dataId; + uint32 fileType; // 0 = AppData, 1 = Message, 2 = Unknown + uint32 size; + }; + + std::vector ParseNbdlTasksheet(std::span xmlContent) + { + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_buffer(xmlContent.data(), xmlContent.size()); + if (!result) + return {}; + std::vector fileList; + for (pugi::xml_node sheet = doc.child("TaskSheet"); sheet; sheet = sheet.next_sibling("TaskSheet")) + { + pugi::xml_node serviceStatus = sheet.child("ServiceStatus"); + if (strcmp(serviceStatus.child_value(), "close") == 0) + { + // service is closed, skip all files + cemuLog_log(LogType::Force, "BossNbdl: Service is closed, skipping all files in tasksheet"); + continue; + } + else if (strcmp(serviceStatus.child_value(), "open") != 0) + { + cemuLog_log(LogType::Force, "BossNbdl: Unknown ServiceStatus value: {}", serviceStatus.child_value()); + } + // read and verify titleId + pugi::xml_node nodeTitleId = sheet.child("TitleId"); + if (nodeTitleId) + { + uint64 titleId; + TitleIdParser::ParseFromStr(nodeTitleId.child_value(), titleId); + if (titleId != CafeSystem::GetForegroundTitleId()) + cemuLog_log(LogType::Force, "BossNbdl: TitleId in tasksheet ({:016x}) does not match foreground titleId ({:016x})", titleId, CafeSystem::GetForegroundTitleId()); + } + else + { + cemuLog_log(LogType::Force, "BossNbdl: Missing TitleId field in tasksheet"); + } + // read and verify taskId + pugi::xml_node nodeTaskId = sheet.child("TaskId"); + if (nodeTaskId) + { + if (strcmp(nodeTaskId.child_value(), m_taskId.id.c_str()) != 0) + cemuLog_log(LogType::Force, "BossNbdl: TaskId in tasksheet ({}) does not match current taskId ({})", nodeTaskId.child_value(), m_taskId.id.c_str()); + } + else + { + cemuLog_log(LogType::Force, "BossNbdl: Missing TaskId field in tasksheet"); + } + + // read files list + pugi::xml_node files = sheet.child("Files"); + if (!files) + continue; + for (pugi::xml_node file = files.child("File"); file; file = file.next_sibling("File")) + { + pugi::xml_node fileName = file.child("Filename"); + if (!fileName) + continue; + pugi::xml_node dataId = file.child("DataId"); + if (!dataId) + continue; + pugi::xml_node url = file.child("Url"); + if (!url) + continue; + pugi::xml_node size = file.child("Size"); + if (!size) + continue; + pugi::xml_node type = file.child("Type"); + if (!type) + continue; + uint32 fileType; + if (strcmp(type.child_value(), "AppData") == 0) + fileType = 0; + else if (strcmp(type.child_value(), "Message") == 0) + fileType = 1; + else + { + cemuLog_log(LogType::Force, "BossNbdl: Unknown file Type value: {}", type.child_value()); + fileType = 0; + } + + if (!file.child("AutoDelete").empty()) + cemuLog_log(LogType::Force, "BossNbdl: Field AutoDelete is set but not supported. Value: {}", file.child_value("AutoDelete")); + + // fields and categories which we dont handle yet: + // Attributes, Attribute1, Attribute2, Attribute3 + // Notify, Conditions, SecInterval, LED, Played + + fileList.emplace_back( + url.child_value(), + fileName.child_value(), + dataId.text().as_int(), + fileType, + size.text().as_int() + ); + } + } + return fileList; + } + + void SetupSSL(CurlRequestHelper& request) + { + request.ClearCaCertIds(); + request.ClearClientCertIds(); + for (sint32 i=0; i<3; i++) + { + if (m_taskSettings.internalCaCert[i] != 0) + request.AddCaCertId(m_taskSettings.internalCaCert[i]); + } + request.AddCaCertId(105); + if (m_taskSettings.internalClientCert != 0) + request.AddCaCertId(m_taskSettings.internalClientCert); + else + request.AddCaCertId(3); // whats the default for each task type? + cemu_assert_debug(m_taskSettings.caCert[0].empty()); + cemu_assert_debug(m_taskSettings.caCert[1].empty()); + cemu_assert_debug(m_taskSettings.caCert[2].empty()); + cemu_assert_debug(m_taskSettings.clientCertName.empty()); + } + + void DownloadNbdlFile(const NbdlQueuedFile& nbdlFile) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); // todo - use titleId from task settings? + fs::path dataFilePath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), m_taskId.id.c_str(), nbdlFile.dataId); + + // if the file already exists locally, skip download + // but still add the entry to the FAD db + std::error_code ec; + if (fs::exists(dataFilePath)) + { + cemuLog_log(LogType::Force, "\t- {} (DataId:{:08x} {}KB) (Skipping, already downloaded)", nbdlFile.fileName, nbdlFile.dataId, (nbdlFile.size+1023)/1024); + TrackDownloadedNbdlFile(nbdlFile); + return; + } + cemuLog_log(LogType::Force, "\t- {} (DataId:{:08x} {}KB)", nbdlFile.fileName, nbdlFile.dataId, (nbdlFile.size+1023)/1024); + + CurlRequestHelper request; + request.initate(ActiveSettings::GetNetworkService(), nbdlFile.url, CurlRequestHelper::SERVER_SSL_CONTEXT::CUSTOM); + SetupSSL(request); + + bool r = request.submitRequest(); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to download SpotPass nbdl file: {}", nbdlFile.fileName); + return; + } + + // decrypt and store file + std::vector receivedData = request.getReceivedData(); + struct BossNbdlHeader + { + /* +0x00 */ uint32be magic; + /* +0x04 */ uint32be version; // guessed + /* +0x08 */ uint16be ukn08; // must always be 1 + /* +0x0A */ uint16be ukn0A; // must always be 2 + /* +0x0C */ uint8 nonce[0xC]; + /* +0x18 */ uint32 padding18; // unused + /* +0x1C */ uint32 padding1C; // unused + /* +0x20 */ + struct + { + uint8 uknHashData[0x20]; + } encryptedHeader; + }; + if (receivedData.size() < sizeof(BossNbdlHeader)) + { + cemuLog_log(LogType::Force, "Received SpotPass nbdl file is too small: {} bytes", receivedData.size()); + return; + } + BossNbdlHeader nbdlHeader; + memcpy(&nbdlHeader, receivedData.data(), sizeof(BossNbdlHeader)); + if (nbdlHeader.magic != 'boss' || nbdlHeader.version != 0x20001 || nbdlHeader.ukn08 != 1 || nbdlHeader.ukn0A != 2) + { + cemuLog_log(LogType::Force, "Received SpotPass nbdl file has incorrect header"); + return; + } + // file must be padded to 16 byte alignment for AES decryption (padding is cut off after decryption) + size_t originalFileSize = receivedData.size(); + const uint32 paddedFileSize = (originalFileSize + 0xF) & (~0xF); + receivedData.resize(paddedFileSize); + // extra validation + if (originalFileSize != nbdlFile.size) + { + cemuLog_log(LogType::Force, "SpotPass nbdl file size mismatch, expected {} bytes but got {} bytes", nbdlFile.size, originalFileSize); + return; + } + if (nbdlFile.fileName.find("..") != std::string::npos) + { + cemuLog_log(LogType::Force, "SpotPass nbdl filename contains suspicious characters"); + return; + } + // prepare nonce for AES128-CTR + uint8 aesNonce[0x10]; + memset(aesNonce, 0, sizeof(aesNonce)); + memcpy(aesNonce, nbdlHeader.nonce, 0xC); + aesNonce[0xF] = 1; + // decrypt + uint8 bossAesKey[16] = { 0x39,0x70,0x57,0x35,0x58,0x70,0x34,0x58,0x37,0x41,0x7a,0x30,0x71,0x5a,0x70,0x74 }; + memset(aesNonce, 0, sizeof(aesNonce)); + memcpy(aesNonce, nbdlHeader.nonce, 0xC); + aesNonce[0xF] = 1; + AES128CTR_transform(receivedData.data() + offsetof(BossNbdlHeader, encryptedHeader), receivedData.size() - sizeof(BossNbdlHeader::encryptedHeader), bossAesKey, aesNonce); + // get HMAC from header + uint8 fileHMAC[32]; + memcpy(fileHMAC, &((BossNbdlHeader*)receivedData.data())->encryptedHeader.uknHashData, 32); + // write decrypted data to filesystem using a temporary path + fs::path tmpDataPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/.{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), m_taskId.id.c_str(), nbdlFile.dataId); + if (!fs::exists(tmpDataPath.parent_path())) + fs::create_directories(tmpDataPath.parent_path(), ec); + FileStream* fs = FileStream::createFile2(tmpDataPath); + if (fs) + { + fs->writeData(receivedData.data() + sizeof(BossNbdlHeader), originalFileSize - sizeof(BossNbdlHeader)); + delete fs; + } + else + { + cemuLog_log(LogType::Force, "Failed to create temporary SpotPass nbdl file: {}", _pathToUtf8(tmpDataPath)); + return; + } + // to make sure that the file actually stored correctly to disk we read it back and verify the hash + auto readbackFileData = FileStream::LoadIntoMemory(tmpDataPath); + uint8 hmacHash[32]; + unsigned int hmacHashLen = 32; + HMAC(EVP_sha256(), "uyr5I8pu4ycq2pOT3D53Bp0n7jK8eyjLO5U20ocUNdN5muwUcC4By881UXECeM08", 64, readbackFileData->data(), originalFileSize - sizeof(BossNbdlHeader), hmacHash, &hmacHashLen); + if (memcmp(hmacHash, fileHMAC, 32) != 0) + { + // failed to verify hash + cemuLog_log(LogType::Force, "Failed to verify hash for SpotPass nbdl file: {}", _pathToUtf8(tmpDataPath)); + fs::remove(tmpDataPath, ec); + return; + } + // rename temporary file to final path + fs::rename(tmpDataPath, dataFilePath, ec); + // update FAD entry + TrackDownloadedNbdlFile(nbdlFile); + } + + void TrackDownloadedNbdlFile(const NbdlQueuedFile& nbdlFile) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult storageResult = m_fadDb.Load(m_taskId, titleId, m_persistentId); + if (storageResult == RESULT_STORAGE_NOTEXIST) + storageResult = m_fadDb.CreateNewStorage(m_taskId, titleId, m_persistentId); + if (NN_RESULT_IS_FAILURE(storageResult)) + { + cemuLog_log(LogType::Force, "Failed to create FAD database for task {}: {:08x}", m_taskId.id.c_str(), storageResult); + return; + } + CafeString<32> filenameStr; + filenameStr.assign(nbdlFile.fileName); + m_fadDb.AddEntry(filenameStr, nbdlFile.dataId); + m_fadDb.Store(); + // todo - DIDX and ref database + } + + TaskId m_taskId; + uint32 m_persistentId; + TaskSettingCore m_taskSettings; + std::recursive_mutex m_mutex; + TaskState m_taskState{ TaskState::Initial }; + TaskTurnState m_taskTurnState{ TaskTurnState::Ukn }; + sint32 m_httpStatusCode{ 0 }; + uint32 m_contentLength{ 0 }; // content length of the last request + }; + + class BossDaemon + { + public: + void Start() + { + if (m_threadRunning.exchange(true)) + return; + m_bossDaemonThread = std::thread(&BossDaemon::BossDaemonThread, this); + } + + void Stop() + { + if (!m_threadRunning.exchange(false)) + return; + m_bossDaemonThread.join(); + } + + void RegisterTask(const TaskSettingCore& taskSetting) + { + std::unique_lock _l(m_taskMtx); + if (m_registeredTasks.find(taskSetting.taskId) != m_registeredTasks.end()) + { + // task already registered. Return error? + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Task {} is already registered", taskSetting.taskId.id.c_str()); + return; + } + m_registeredTasks.try_emplace(taskSetting.taskId, std::make_shared(taskSetting.taskId, taskSetting)); + } + + void UnregisterTask(uint32 persistentId, const TaskId& taskId) + { + std::unique_lock _l(m_taskMtx); + auto it = m_registeredTasks.find(taskId); + if (it != m_registeredTasks.end()) + { + m_registeredTasks.erase(it); + } + else + { + // todo - return error + } + } + + bool TaskIsRegistered(uint32 persistentId, const TaskId& taskId) + { + std::unique_lock _l(m_taskMtx); + return m_registeredTasks.find(taskId) != m_registeredTasks.end(); + } + + void TaskRun(uint32 persistentId, const TaskId& taskId) + { + // todo - there is an extra parameter for controlling background/foreground running? + std::shared_ptr registeredTask = GetRegisteredTask2(persistentId, taskId); + if (registeredTask) + { + cemuLog_log(LogType::Force, "IOSU-Boss: Running task {}", taskId.id.c_str()); + registeredTask->Run(); + } + else + { + cemuLog_log(LogType::Force, "IOSU-Boss: Trying to run task {} which has not been registered", taskId.id.c_str()); + // todo - return error -> 0xA021F900 + } + } + + void StartScheduleTask(uint32 persistentId, const TaskId& taskId, bool runImmediately) + { + if (runImmediately) + { + // proper implementation is still todo + // handling automatic scheduling and task state transitions is quite complex + // for now we only run the task once + std::shared_ptr registeredTask = GetRegisteredTask2(persistentId, taskId); + TaskState state = registeredTask->GetState(); + if (state == TaskState::Stopped || state == TaskState::Initial) + { + registeredTask->Run(); + } + else + { + cemuLog_logDebug(LogType::Force, "StartScheduleTask(): No support for scheduling tasks that aren't in stopped state"); + } + } + else + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unsupported StartScheduling called for task {}", taskId.id.c_str()); + } + } + + void StopScheduleTask(uint32 persistentId, const TaskId& taskId) + { + // todo + // task scheduling is not yet implemented so this does nothing for now + } + + std::shared_ptr GetRegisteredTask2(const uint32 persistentId, const TaskId& taskId) + { + std::unique_lock _l(m_taskMtx); + auto it = m_registeredTasks.find(taskId); // todo - check persistentId as well (make it part of the key?) + if (it != m_registeredTasks.end()) + return it->second; + return nullptr; + } + + private: + void BossDaemonThread() + { + CURL* curl = curl_easy_init(); + while ( m_threadRunning ) + { + // check for tasks to run + { + std::shared_ptr task = GetNextRunableTask(); + if (task) + { + task->TaskDoRequest(curl); + cemu_assert_debug(task->GetState() != TaskState::Ready); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + curl_easy_cleanup(curl); + } + + std::shared_ptr GetNextRunableTask() + { + std::unique_lock _l(m_taskMtx); + for (auto& task : m_registeredTasks) + { + if (task.second->GetState() == TaskState::Ready) + return task.second; + } + return nullptr; + } + + std::thread m_bossDaemonThread; + std::atomic_bool m_threadRunning{ false }; + // task list + std::mutex m_taskMtx; + std::map> m_registeredTasks; + }s_bossDaemon; + + class BossMainService : public iosu::nn::IPCService + { + public: + BossMainService() : iosu::nn::IPCService("/dev/boss") {} + + nnResult ServiceCall(IPCServiceCall& serviceCall) override + { + if (serviceCall.GetServiceId() == 0) + { + switch (static_cast(serviceCall.GetCommandId())) + { + case BossCommandId::TaskIsRegistered: + return HandleTaskIsRegistered(serviceCall); + case BossCommandId::TaskRegisterA: + return HandleTaskRegister(serviceCall, false); + case BossCommandId::TaskRegisterForImmediateRunA: + return HandleTaskRegister(serviceCall, true); + case BossCommandId::TaskUnregister: + return HandleTaskUnregister(serviceCall); + case BossCommandId::TaskRun: + return HandleTaskRun(serviceCall); + case BossCommandId::TaskStartScheduling: + return HandleTaskStartScheduling(serviceCall); + case BossCommandId::TaskStopScheduling: + return HandleTaskStopScheduling(serviceCall); + case BossCommandId::TaskWaitA: + return HandleTaskWait(serviceCall); + case BossCommandId::TaskGetTurnState: + return HandleTaskGetTurnState(serviceCall); + case BossCommandId::TaskGetHttpStatusCodeA: + return HandleTaskGetHttpStatusCodeA(serviceCall); + case BossCommandId::TaskGetContentLength: + return HandleTaskGetContentLength(serviceCall); + case BossCommandId::TaskGetProcessedLength: + return HandleTaskGetProcessedLength(serviceCall); + case BossCommandId::UknA7: + return HandleUknA7(serviceCall); + case BossCommandId::DeleteDataRelated: + return HandleDeleteDataRelated(serviceCall); + case BossCommandId::StorageExist: + return HandleStorageExist(serviceCall); + case BossCommandId::StorageGetDataList: + return HandleStorageGetDataList(serviceCall); + case BossCommandId::NsDataExist: + return HandleNsDataExist(serviceCall); + case BossCommandId::NsDataRead: + return HandleNsDataRead(serviceCall); + case BossCommandId::NsDataGetSize: + return HandleNsDataGetSize(serviceCall); + case BossCommandId::NsDataDeleteFile: + return HandleNsDataDeleteFile(serviceCall, false); + case BossCommandId::NsDataDeleteFileWithHistory: + return HandleNsDataDeleteFile(serviceCall, true); + case BossCommandId::NsFinalize: + return HandleNsFinalize(serviceCall); + case BossCommandId::TitleSetNewArrivalFlag: + return HandleTitleSetNewArrivalFlag(serviceCall); + default: + cemuLog_log(LogType::Force, "Unsupported service call to /dev/boss"); + cemu_assert_unimplemented(); + break; + } + } + else + { + cemu_assert_suspicious(); + } + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + // helper function to get proper persistentId + uint32 BossGetPersistentId(uint32 persistentId) + { + if (persistentId != 0) + return persistentId; + uint32 currentPersistentId; + bool r = iosu::act::GetPersistentId(iosu::act::ACT_SLOT_CURRENT, ¤tPersistentId);; + if (!r) + return 0; + return currentPersistentId; + } + + nnResult HandleTaskIsRegistered(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + bool isRegistered = s_bossDaemon.TaskIsRegistered(persistentId, taskId); + // write response + serviceCall.WriteResponse(isRegistered ? 1 : 0); + return RESULT_SUCCESS; + } + + nnResult HandleTaskRegister(IPCServiceCall& serviceCall, bool forImmediateRun) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + IPCServiceCall::UnalignedBuffer taskSettingsBuffer = serviceCall.ReadUnalignedInputBufferInfo(); + TaskSettingCore taskSetting = taskSettingsBuffer.ReadType(); // bugged.. (but on the submission side) + if (taskSetting.persistentId == 0) + taskSetting.persistentId = persistentId; + taskSetting.taskId = taskId; + s_bossDaemon.RegisterTask(taskSetting); + return RESULT_SUCCESS; + } + + nnResult HandleTaskUnregister(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + s_bossDaemon.UnregisterTask(persistentId, taskId); + return RESULT_SUCCESS; + } + + nnResult HandleTaskRun(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + bool isForegroundRun = serviceCall.ReadParameter() != 0; // todo - handle this + s_bossDaemon.TaskRun(persistentId, taskId); + return RESULT_SUCCESS; + } + + nnResult HandleTaskStartScheduling(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + uint8 startImmediately = serviceCall.ReadParameter(); + s_bossDaemon.StartScheduleTask(persistentId, taskId, startImmediately != 0); + return RESULT_SUCCESS; + } + + nnResult HandleTaskStopScheduling(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + s_bossDaemon.StopScheduleTask(persistentId, taskId); + return RESULT_SUCCESS; + } + + nnResult HandleTaskWait(IPCServiceCall& serviceCall) + { + static_assert(sizeof(betype) == 4); + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + TaskWaitState waitState = serviceCall.ReadParameter>(); + uint32 timeout = serviceCall.ReadParameter(); + std::chrono::steady_clock::time_point endTime; + if (timeout != 0) + endTime = std::chrono::steady_clock::now() + std::chrono::seconds(timeout); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + cemu_assert_debug(registeredTask != nullptr); + if (!registeredTask) + return RESULT_NOTEXIST; + while (true) + { + // todo - make async + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + TaskState currentState = registeredTask->GetState(); + // does the state match the wait state? + if (waitState == TaskWaitState::Done) + { + if (currentState == TaskState::Done) + { + serviceCall.WriteResponse(1); // 0 -> timeout, 1 -> no timeout + return RESULT_SUCCESS; + } + } + else + { + cemuLog_log(LogType::Force, "IOSU-Boss: Unknown task wait state {}", static_cast(waitState)); + serviceCall.WriteResponse(0); + return RESULT_SUCCESS; + } + // check for timeout + if (timeout != 0 && std::chrono::steady_clock::now() >= endTime) + { + serviceCall.WriteResponse(0); + return RESULT_SUCCESS; // which error to return here? + } + } + } + + nnResult HandleTaskGetTurnState(IPCServiceCall& serviceCall) + { + static_assert(sizeof(betype) == 4); + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + TaskTurnState turnState = registeredTask ? registeredTask->GetTurnState() : TaskTurnState::Ukn; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse>(turnState); + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + nnResult HandleTaskGetHttpStatusCodeA(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + sint32 httpStatusCode = registeredTask ? registeredTask->GetHttpStatusCode() : 0; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse(httpStatusCode); + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + nnResult HandleTaskGetContentLength(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + uint32 contentLength = registeredTask ? registeredTask->GetContentLength() : 0; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse(contentLength); + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + nnResult HandleTaskGetProcessedLength(IPCServiceCall& serviceCall) + { + uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + TaskId taskId = serviceCall.ReadParameter(); + std::shared_ptr registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); + uint32 processedLength = registeredTask ? registeredTask->GetProcessedLength() : 0; + serviceCall.WriteResponse(1); // execution counter - todo + serviceCall.WriteResponse(processedLength); // this the actual length? + if (!registeredTask) + return RESULT_NOTEXIST; + return RESULT_SUCCESS; + } + + /* Storage */ + + nnResult HandleStorageExist(IPCServiceCall& serviceCall) + { + static_assert(sizeof(DirectoryName) == 8); + static_assert(sizeof(betype) == 4); + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + DirectoryName directoryName = serviceCall.ReadParameter(); + StorageKind storageKind = serviceCall.ReadParameter>(); + if (!persistentId) + return RESULT_C0203880; + uint64 titleId = CafeSystem::GetForegroundTitleId(); + // todo - investigate how IOSU handles this + bool storageExists = true; + if (storageKind == StorageKind::StorageNbdl) + { + // but for now we only check if the fad.db file exists for nbdl + storageExists = m_fadDb.CheckIfStorageExists(directoryName, titleId, persistentId); + } + else + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled storage kind {}", static_cast(storageKind)); + cemu_assert_unimplemented(); + } + serviceCall.WriteResponse(storageExists ? 1 : 0); + return RESULT_SUCCESS; + } + + nnResult HandleStorageGetDataList(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); + cemu_assert_debug(ukn == 0); + IPCServiceCall::UnalignedBuffer dataListOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo(); + uint32 startOffset = serviceCall.ReadParameter(); // guessed + cemu_assert_debug(startOffset == 0); // todo - implement offset + size_t dataListSize = dataListOutputBuffer.GetSize() / sizeof(DataName); + std::vector dataListTmp; + uint64 titleId = CafeSystem::GetForegroundTitleId(); + // open FAD database + TaskId taskId(directoryName.name2.c_str()); + nnResult r = m_fadDb.Load(taskId, titleId, persistentId); + if (NN_RESULT_IS_FAILURE(r)) + return r; + m_fadDb.GetDataList(dataListTmp); + if (dataListTmp.size() > dataListSize) + dataListTmp.resize(dataListSize); + // write to output + dataListOutputBuffer.WriteData(dataListTmp.data(), dataListTmp.size() * sizeof(DataName)); + serviceCall.WriteResponse((uint32)dataListTmp.size()); + serviceCall.WriteResponse(1); // extra byte to indicate success or failure + return RESULT_SUCCESS; + } + + /* NsData */ + + nnResult HandleNsDataExist(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + return r; + bool nsDataExists = m_nsDataAccessor.Exists(); + serviceCall.WriteResponse(nsDataExists); + return RESULT_SUCCESS; + } + + nnResult HandleNsDataRead(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + IPCServiceCall::UnalignedBuffer dataOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo(); + uint64 readOffset = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + { + serviceCall.WriteResponse(0); // should we still write the bytes read in case of failure? + return r; + } + uint32 bytesRead = 0; + std::vector tmpBuffer; + tmpBuffer.resize(dataOutputBuffer.GetSize()); + bool rRead = m_nsDataAccessor.Read(tmpBuffer.data(), tmpBuffer.size(), readOffset, bytesRead); + dataOutputBuffer.WriteData(tmpBuffer.data(), tmpBuffer.size()); + serviceCall.WriteResponse(bytesRead); + // todo - handle rRead + return RESULT_SUCCESS; + } + + nnResult HandleNsDataGetSize(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + return r; + uint32 fileSize = 0; + r = m_nsDataAccessor.GetSize(fileSize); + serviceCall.WriteResponse(fileSize); + return r; + } + + nnResult HandleNsDataDeleteFile(IPCServiceCall& serviceCall, bool withHistory) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); + if (NN_RESULT_IS_FAILURE(r)) + return r; + // todo - handle withHistory + return m_nsDataAccessor.Delete(); + } + + nnResult HandleNsFinalize(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + DirectoryName directoryName = serviceCall.ReadParameter(); + uint32 ukn = serviceCall.ReadParameter(); // storage kind? + DataName fileName = serviceCall.ReadParameter(); + m_nsDataAccessor.Close(); + return RESULT_SUCCESS; + } + + /* Title */ + + nnResult HandleTitleSetNewArrivalFlag(IPCServiceCall& serviceCall) + { + auto persistentId = BossGetPersistentId(serviceCall.ReadParameter()); + if (!persistentId) + return RESULT_C0203880; + uint8 flagValue = serviceCall.ReadParameter(); + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled command TitleSetNewArrivalFlag"); + return RESULT_SUCCESS; + } + + /* stubbed opcodes */ + nnResult HandleUknA7(IPCServiceCall& serviceCall) + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled command A7"); + // no response data + return RESULT_SUCCESS; + } + + nnResult HandleDeleteDataRelated(IPCServiceCall& serviceCall) + { + cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled delete command"); + // no response data + return RESULT_SUCCESS; + } + + + }; + + BossMainService s_bossService; + + class : public ::IOSUModule + { + void TitleStart() override + { + s_bossService.Start(); + s_bossDaemon.Start(); + } + void TitleStop() override + { + s_bossService.Stop(); + m_fadDb.Clear(); + m_nsDataAccessor.Close(); + } + }sIOSUModuleNNBOSS; + + IOSUModule* GetModule() + { + return static_cast(&sIOSUModuleNNBOSS); + } +} diff --git a/src/Cafe/IOSU/nn/boss/boss_service.h b/src/Cafe/IOSU/nn/boss/boss_service.h new file mode 100644 index 00000000..f52f5d4d --- /dev/null +++ b/src/Cafe/IOSU/nn/boss/boss_service.h @@ -0,0 +1,7 @@ +#pragma once +#include "Cafe/IOSU/iosu_types_common.h" + +namespace iosu::boss +{ + IOSUModule* GetModule(); +} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index c888b4fb..53468950 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -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{ 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(vecIn->basePhys).GetPtr(); - IPCServiceResponse* serviceResponse = MEMPTR(vecOut->basePhys).GetPtr(); + IPCServiceRequestHeader* serviceRequest = MEMPTR(vecIn->basePhys).GetPtr(); + IPCServiceResponseHeader* serviceResponse = MEMPTR(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 (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(vecIn[0].basePhys).GetPtr() + sizeof(IPCServiceRequestHeader), vecIn[0].size - sizeof(IPCServiceRequestHeader)); + serviceCall.AddOutputStream(MEMPTR(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(vecIn[i].basePhys).GetPtr(), vecIn[i].size); + for (size_t i = 1; i < numOut; i++) + serviceCall.AddOutputStream(MEMPTR(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; } diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.h b/src/Cafe/IOSU/nn/iosu_nn_service.h index d7d4cb01..94bd03f5 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.h +++ b/src/Cafe/IOSU/nn/iosu_nn_service.h @@ -73,25 +73,239 @@ namespace iosu SysAllocator _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 + 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 + 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 head, std::span middle, std::span 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 + 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, headSize); + memcpy(headPtr, data, bytesToCopy); + size -= bytesToCopy; + if (size > 0) + { + bytesToCopy = std::min(size, middleSize); + memcpy(middlePtr, (uint8_t*)data + headSize, bytesToCopy); + size -= bytesToCopy; + } + if (size > 0) + { + bytesToCopy = std::min(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 + T ReadParameter() + { + // read only from stream 0 for now + return m_paramStreamArray[0].ReadParameter(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(); + // 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(); + // 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 + void WriteResponse(const T& value) + { + m_responseStreamArray[0].Write(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; diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp index ce85048e..681889dc 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp @@ -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 diff --git a/src/Cafe/OS/libs/nn_ac/nn_ac.h b/src/Cafe/OS/libs/nn_ac/nn_ac.h index b8827200..520e1452 100644 --- a/src/Cafe/OS/libs/nn_ac/nn_ac.h +++ b/src/Cafe/OS/libs/nn_ac/nn_ac.h @@ -1 +1,6 @@ -void nnAc_load(); \ No newline at end of file +void nnAc_load(); + +namespace nn_ac +{ + nnResult IsApplicationConnected(uint8be* connected); +} diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index 2a05fa7a..c440858d 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -1,42 +1,34 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/nn_client_service.h" #include "Cafe/OS/libs/nn_act/nn_act.h" -#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/nn_ac/nn_ac.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" -#include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" +#include "Common/CafeString.h" +#include "Cafe/IOSU/nn/boss/boss_common.h" namespace nn { - typedef uint32 Result; namespace boss { -#define bossPrepareRequest() \ -StackAllocator _buf_bossRequest; \ -StackAllocator _buf_bufferVector; \ -iosuBossCemuRequest_t* bossRequest = _buf_bossRequest.GetPointer(); \ -ioBufferVector_t* bossBufferVector = _buf_bufferVector.GetPointer(); \ -memset(bossRequest, 0, sizeof(iosuBossCemuRequest_t)); \ -memset(bossBufferVector, 0, sizeof(ioBufferVector_t)); \ -bossBufferVector->buffer = (uint8*)bossRequest; + IPCServiceClient s_ipcClient; + + static constexpr BossResult resultSuccess = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + static constexpr BossResult resultInvalidParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); + static constexpr BossResult resultUknA0220300 = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_BOSS, 0x20300); SysAllocator g_mutex; sint32 g_initCounter = 0; bool g_isInitialized = false; - struct VTableEntry - { - uint16be offsetA{0}; - uint16be offsetB{0}; - MEMPTR ptr; - }; - static_assert(sizeof(VTableEntry) == 8); - #define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) + SysAllocator s_ipcBuffer; + constexpr uint32 BOSS_MEM_MAGIC = 0xCAFE4321; template @@ -62,15 +54,15 @@ bossBufferVector->buffer = (uint8*)bossRequest; coreinit::_weak_MEMFreeToDefaultHeap(basePtr); } - Result Initialize() // Initialize__Q2_2nn4bossFv + BossResult Initialize() // Initialize__Q2_2nn4bossFv { coreinit::OSLockMutex(&g_mutex); - Result result = 0; + BossResult result = 0; if(g_initCounter == 0) { g_isInitialized = true; - // IPC init here etc. - result = 0x200080; // init result + s_ipcClient.Initialize("/dev/boss", s_ipcBuffer.GetPtr(), s_ipcBuffer.GetByteSize()); + result = resultSuccess; } g_initCounter++; coreinit::OSUnlockMutex(&g_mutex); @@ -89,8 +81,8 @@ bossBufferVector->buffer = (uint8*)bossRequest; cemuLog_log(LogType::Force, "nn_boss: Finalize() called without corresponding Initialize()"); if(g_initCounter == 1) { + s_ipcClient.Shutdown(); g_isInitialized = false; - // IPC deinit here etc. } g_initCounter--; coreinit::OSUnlockMutex(&g_mutex); @@ -98,579 +90,400 @@ bossBufferVector->buffer = (uint8*)bossRequest; uint32 GetBossState(PPCInterpreter_t* hCPU) { - cemuLog_logDebug(LogType::Force, "nn_boss.GetBossState() - stub"); + cemuLog_logDebugOnce(LogType::Force, "nn_boss.GetBossState() - stub"); return 7; } - struct TitleId + /* TitleId */ + + bool TitleId::IsValid(TitleId* _thisptr) { - uint64be u64{}; + return _thisptr->u64 != 0; + } - static TitleId* ctor(TitleId* _thisptr, uint64 titleId) + TitleId* TitleId::ctorDefault(TitleId* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = 0; + return _thisptr; + } + + TitleId* TitleId::ctorFromTitleId(TitleId* _thisptr, uint64 titleId) // __ct__Q3_2nn4boss7TitleIDFUL + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId; + return _thisptr; + } + + TitleId* TitleId::ctorCopy(TitleId* _thisptr, TitleId* titleId) // __ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId->u64; + return _thisptr; + } + + bool TitleId::operator_ne(TitleId* _thisptr, TitleId* titleId) + { + return _thisptr->u64 != titleId->u64; + } + /* TaskId */ + + TaskId* TaskId::ctorDefault(TaskId* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->id.data[0] = 0; + return _thisptr; + } + + TaskId* TaskId::ctorFromString(TaskId* _thisptr, const char* taskId) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->id.assign(taskId); + return _thisptr; + } + + /* Title */ + + Title* Title::ctor(Title* _this) + { + if (!_this) + _this = boss_new(); + *_this = {}; + _this->vTablePtr = s_titleVTable; + return _this; + } + + void Title::dtor(Title* _this, uint32 options) + { + if (_this && (options & 1)) + boss_delete(_this); + } + + void Title::InitVTable() + { + s_titleVTable->rtti.ptr = nullptr; // todo + s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); + } + + /* DirectoryName */ + + DirectoryName* DirectoryName::ctor(DirectoryName* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<DirectoryName>(); + _thisptr->name2.ClearAllBytes(); + return _thisptr; + } + + const char* DirectoryName::operator_const_char(DirectoryName* _thisptr) + { + return _thisptr->name2.c_str(); + } + + /* BossAccount */ + + BossAccount* BossAccount::ctor(BossAccount* _this, uint32 accountId) + { + if (!_this) + _this = boss_new<BossAccount>(); + _this->accountId = accountId; + _this->vTablePtr = s_VTable; + return _this; + } + + void BossAccount::dtor(BossAccount* _this, uint32 options) + { + if(_this && options & 1) + boss_delete(_this); + } + + void BossAccount::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); + } + + /* TaskSetting */ + + TaskSetting* TaskSetting::ctor(TaskSetting* _thisptr) + { + if(!_thisptr) + _thisptr = boss_new<TaskSetting>(); + _thisptr->vTablePtr = s_VTable; + InitializeSetting(_thisptr); + return _thisptr; + } + + void TaskSetting::dtor(TaskSetting* _this, uint32 options) + { + if(options & 1) + boss_delete(_this); + } + + bool TaskSetting::IsPrivileged(TaskSetting* _thisptr) + { + const TaskType taskType = _thisptr->taskType; + return taskType == TaskType::RawDlTaskSetting_1 || taskType == TaskType::RawDlTaskSetting_9 || taskType == TaskType::PlayLogUploadTaskSetting; + } + + void TaskSetting::InitializeSetting(TaskSetting* _thisptr) + { + memset(_thisptr, 0x00, 0x1000); + _thisptr->persistentId = 0; + _thisptr->ukn08 = 0; + _thisptr->ukn0C = 0; + _thisptr->priority = 125; + _thisptr->intervalB = 28800; + _thisptr->lifeTime.high = 0; + _thisptr->lifeTime.low = 0x76A700; + } + + void TaskSetting::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } + + /* NetTaskSetting */ + + NetTaskSetting* NetTaskSetting::ctor(NetTaskSetting* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<NetTaskSetting>(); + TaskSetting::ctor(_thisptr); + _thisptr->httpTimeout = 120; + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + BossResult NetTaskSetting::AddCaCert(NetTaskSetting* _thisptr, const char* name) + { + if(name == nullptr || strnlen(name, 0x80) == 0x80) { - if (!_thisptr) - _thisptr = boss_new<TitleId>(); - _thisptr->u64 = titleId; - return _thisptr; + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddCaCert: name size is invalid"); + return resultInvalidParam; } - static TitleId* ctor(TitleId* _thisptr) - { - return ctor(_thisptr, 0); - } + cemu_assert_unimplemented(); - static bool IsValid(TitleId* _thisptr) - { - return _thisptr->u64 != 0; - } + return 0xA0220D00; + } - static TitleId* ctor1(TitleId* _thisptr, uint32 filler, uint64 titleId) - { - return ctor(_thisptr); - } + BossResult NetTaskSetting::SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) // input is uint8[512]? + { + memcpy(_thisptr->serviceToken.data, serviceToken, _thisptr->serviceToken.Size()); + return resultSuccess; + } - static TitleId* ctor2(TitleId* _thisptr, uint32 filler, uint64 titleId) + BossResult NetTaskSetting::AddInternalCaCert(NetTaskSetting* _thisptr, char certId) + { + for(int i = 0; i < 3; i++) { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_ctor2(0x{:x})", MEMPTR(_thisptr).GetMPTR()); - if (!_thisptr) + if(_thisptr->internalCaCert[i] == 0) { - // _thisptr = new Task_t - assert_dbg(); + _thisptr->internalCaCert[i] = (uint8)certId; + return resultSuccess; } - - _thisptr->u64 = titleId; - return _thisptr; } + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: Cannot store more than 3 certificates"); + return 0xA0220D00; + } - static TitleId* ctor3(TitleId* _thisptr, TitleId* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_cctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); - if (!_thisptr) - _thisptr = boss_new<TitleId>(); - _thisptr->u64 = titleId->u64; - return _thisptr; - } - - static bool operator_ne(TitleId* _thisptr, TitleId* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_operator_ne(0x{:x})", MEMPTR(_thisptr).GetMPTR()); - return _thisptr->u64 != titleId->u64; - } - }; - static_assert(sizeof(TitleId) == 8); - - struct TaskId + void NetTaskSetting::SetInternalClientCert(NetTaskSetting* _thisptr, char certId) { - char id[0x8]{}; + _thisptr->internalClientCert = (uint8)certId; + } - static TaskId* ctor(TaskId* _thisptr) - { - if(!_thisptr) - _thisptr = boss_new<TaskId>(); - _thisptr->id[0] = '\0'; - return _thisptr; - } - }; - static_assert(sizeof(TaskId) == 8); - - struct Title + void NetTaskSetting::InitVTable() { - uint32be accountId{}; // 0x00 - TitleId titleId{}; // 0x8 - MEMPTR<void> vTablePtr{}; // 0x10 + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } - struct VTable - { - VTableEntry rtti; - VTableEntry dtor; - }; - static inline SysAllocator<VTable> s_titleVTable; + /* NbdlTaskSetting */ - static Title* ctor(Title* _this) - { - if (!_this) - _this = boss_new<Title>(); - *_this = {}; - _this->vTablePtr = s_titleVTable; - return _this; - } - - static void dtor(Title* _this, uint32 options) - { - if (_this && (options & 1)) - boss_delete(_this); - } - - static void InitVTable() - { - s_titleVTable->rtti.ptr = nullptr; // todo - s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); - } - }; - static_assert(sizeof(Title) == 0x18); - - struct DirectoryName + NbdlTaskSetting* NbdlTaskSetting::ctor(NbdlTaskSetting* _thisptr) { - char name[0x8]{}; + if (!_thisptr) + _thisptr = boss_new<NbdlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } - static DirectoryName* ctor(DirectoryName* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<DirectoryName>(); - memset(_thisptr->name, 0x00, 0x8); - return _thisptr; - } - - static const char* operator_const_char(DirectoryName* _thisptr) - { - return _thisptr->name; - } - }; - 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 + BossResult NbdlTaskSetting::Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* bossDirectory) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 { - struct VTable - { - VTableEntry rtti; - VTableEntry dtor; - }; - static inline SysAllocator<VTable> s_VTable; + if (!bossCode || !_thisptr->nbdl.bossCode.CanHoldString(bossCode)) + return resultInvalidParam; + if (bossDirectory && !_thisptr->nbdl.bossDirectory.CanHoldString(bossDirectory)) + return resultInvalidParam; // directory is optional, but if a string is passed it must fit + _thisptr->nbdl.bossCode.assign(bossCode); + if (bossDirectory) + _thisptr->nbdl.bossDirectory.assign(bossDirectory); + _thisptr->nbdl.directorySizeLimitHigh = (uint32be)(directorySizeLimit >> 32); + _thisptr->nbdl.directorySizeLimitLow = (uint32be)(directorySizeLimit & 0xFFFFFFFF); + _thisptr->taskType = TaskType::NbdlTaskSetting; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + } - uint32be accountId; - MEMPTR<void> vTablePtr; - - static BossAccount* ctor(BossAccount* _this, uint32 accountId) - { - if (!_this) - _this = boss_new<BossAccount>(); - _this->accountId = accountId; - _this->vTablePtr = s_VTable; - return _this; - } - - static void dtor(BossAccount* _this, uint32 options) - { - if(_this && options & 1) - boss_delete(_this); - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); - } - - }; - static_assert(sizeof(BossAccount) == 8); - - struct TaskSetting + BossResult NbdlTaskSetting::SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) { - static const uint32 kBossCode = 0x7C0; - static const uint32 kBossCodeLen = 0x20; + if (!fileName || !_thisptr->nbdl.fileName.CanHoldString(fileName)) + return resultInvalidParam; + _thisptr->nbdl.fileName.assign(fileName); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + } - static const uint32 kDirectorySizeLimit = 0x7F0; - static const uint32 kDirectoryName = 0x7E0; - static const uint32 kDirectoryNameLen = 0x8; - - //static const uint32 kFileName = 0x7F8; - static const uint32 kNbdlFileName = 0x7F8; - static const uint32 kFileNameLen = 0x20; - - static const uint32 kURL = 0x48; - static const uint32 kURLLen = 0x100; - - static const uint32 kClientCert = 0x41; - static const uint32 kCACert = 0x188; - - static const uint32 kServiceToken = 0x590; - static const uint32 kServiceTokenLen = 0x200; - - uint8 settings[0x1000]; - MEMPTR<void> vTablePtr; // +0x1000 - - struct VTableTaskSetting - { - VTableEntry rtti; - VTableEntry dtor; - VTableEntry RegisterPreprocess; - VTableEntry unk1; - }; - static inline SysAllocator<VTableTaskSetting> s_VTable; - - static TaskSetting* ctor(TaskSetting* _thisptr) - { - if(!_thisptr) - _thisptr = boss_new<TaskSetting>(); - _thisptr->vTablePtr = s_VTable; - InitializeSetting(_thisptr); - return _thisptr; - } - - static void dtor(TaskSetting* _this, uint32 options) - { - cemuLog_logDebug(LogType::Force, "nn::boss::TaskSetting::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); - if(options & 1) - boss_delete(_this); - } - - static bool IsPrivileged(TaskSetting* _thisptr) - { - const uint16 value = *(uint16be*)&_thisptr->settings[0x28]; - return value == 1 || value == 9 || value == 5; - } - - static void InitializeSetting(TaskSetting* _thisptr) - { - memset(_thisptr, 0x00, sizeof(TaskSetting::settings)); - *(uint32*)&_thisptr->settings[0x0C] = 0; - *(uint8*)&_thisptr->settings[0x2A] = 0x7D; // timeout? - *(uint32*)&_thisptr->settings[0x30] = 0x7080; - *(uint32*)&_thisptr->settings[0x8] = 0; - *(uint32*)&_thisptr->settings[0x38] = 0; - *(uint32*)&_thisptr->settings[0x3C] = 0x76A700; - *(uint32*)&_thisptr->settings[0] = 0x76A700; - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - } - }; - static_assert(sizeof(TaskSetting) == 0x1004); - static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000); - - struct NetTaskSetting : TaskSetting + void NbdlTaskSetting::InitVTable() { - // 0x188 cert1 + 0x188 cert2 + 0x188 cert3 - // 0x190 AddCaCert (3times) char cert[0x80]; - // SetConnectionSetting - // SetFirstLastModifiedTime + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } - struct VTableNetTaskSetting : public VTableTaskSetting - { }; - static inline SysAllocator<VTableNetTaskSetting> s_VTable; + /* RawUlTaskSetting */ - static Result AddCaCert(NetTaskSetting* _thisptr, const char* name) - { - if(name == nullptr || strnlen(name, 0x80) == 0x80) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddCaCert: name size is invalid"); - return 0xC0203780; - } - - cemu_assert_unimplemented(); - - return 0xA0220D00; - } - - static NetTaskSetting* ctor(NetTaskSetting* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<NetTaskSetting>(); - TaskSetting::ctor(_thisptr); - *(uint32*)&_thisptr->settings[0x18C] = 0x78; - _thisptr->vTablePtr = s_VTable; - return _thisptr; - } - - static Result SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetServiceToken(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); - cemuLog_logDebug(LogType::Force, "\t->{}", fmt::ptr(serviceToken)); - memcpy(&_thisptr->settings[TaskSetting::kServiceToken], serviceToken, TaskSetting::kServiceTokenLen); - return 0x200080; - } - - static Result AddInternalCaCert(NetTaskSetting* _thisptr, char certId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); - - uint32 location = TaskSetting::kCACert; - for(int i = 0; i < 3; ++i) - { - if(_thisptr->settings[location] == 0) - { - _thisptr->settings[location] = (uint8)certId; - return 0x200080; - } - - location += TaskSetting::kCACert; - } - - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: can't store certificate"); - return 0xA0220D00; - } - - static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetInternalClientCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); - _thisptr->settings[TaskSetting::kClientCert] = (uint8)certId; - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - } - }; - static_assert(sizeof(NetTaskSetting) == 0x1004); - - struct NbdlTaskSetting : NetTaskSetting + RawUlTaskSetting* RawUlTaskSetting::ctor(RawUlTaskSetting* _thisptr) { - struct VTableNbdlTaskSetting : public VTableNetTaskSetting - { - VTableEntry rttiNetTaskSetting; // unknown - }; - static_assert(sizeof(VTableNbdlTaskSetting) == 8*5); - static inline SysAllocator<VTableNbdlTaskSetting> s_VTable; + if (!_thisptr) + _thisptr = boss_new<RawUlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + _thisptr->ukRaw1 = 0; + _thisptr->ukRaw2 = 0; + _thisptr->ukRaw3 = 0; + memset(_thisptr->rawSpace, 0x00, 0x200); + return _thisptr; + } - static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<NbdlTaskSetting>(); - NetTaskSetting::ctor(_thisptr); - _thisptr->vTablePtr = s_VTable; - return _thisptr; - } - - static Result Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* directoryName) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 - { - if(!bossCode || strnlen(bossCode, TaskSetting::kBossCodeLen) == TaskSetting::kBossCodeLen) - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - - if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - - strncpy((char*)&_thisptr->settings[TaskSetting::kBossCode], bossCode, TaskSetting::kBossCodeLen); - - *(uint64be*)&_thisptr->settings[TaskSetting::kDirectorySizeLimit] = directorySizeLimit; // uint64be - if(directoryName) - strncpy((char*)&_thisptr->settings[TaskSetting::kDirectoryName], directoryName, TaskSetting::kDirectoryNameLen); - - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); - } - - static Result SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_t_SetFileName(0x{:08x}, {})", MEMPTR(_thisptr).GetMPTR(), fileName ? fileName : "\"\""); - if (!fileName || strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - - strncpy((char*)&_thisptr->settings[TaskSetting::kNbdlFileName], fileName, TaskSetting::kFileNameLen); - // also sets byte at +0x817 to zero? - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(NbdlTaskSetting) == 0x1004); - - struct RawUlTaskSetting : NetTaskSetting + void RawUlTaskSetting::dtor(RawUlTaskSetting* _this, uint32 options) { - uint32be ukRaw1; // 0x1004 - uint32be ukRaw2; // 0x1008 - uint32be ukRaw3; // 0x100C - uint8 rawSpace[0x200]; // 0x1010 + cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); + } - struct VTableRawUlTaskSetting : public VTableNetTaskSetting - { - VTableEntry rttiNetTaskSetting; // unknown - }; - static_assert(sizeof(VTableRawUlTaskSetting) == 8*5); - static inline SysAllocator<VTableRawUlTaskSetting> s_VTable; - - static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr) - { - if (!_thisptr) - _thisptr = boss_new<RawUlTaskSetting>(); - NetTaskSetting::ctor(_thisptr); - _thisptr->vTablePtr = s_VTable; - _thisptr->ukRaw1 = 0; - _thisptr->ukRaw2 = 0; - _thisptr->ukRaw3 = 0; - memset(_thisptr->rawSpace, 0x00, 0x200); - return _thisptr; - } - - static void dtor(RawUlTaskSetting* _this, uint32 options) - { - cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(RawUlTaskSetting) == 0x1210); - - struct RawDlTaskSetting : NetTaskSetting + void RawUlTaskSetting::InitVTable() { - struct VTableRawDlTaskSetting : public VTableNetTaskSetting - { - VTableEntry rttiNetTaskSetting; // unknown - }; - static_assert(sizeof(VTableRawDlTaskSetting) == 8*5); - static inline SysAllocator<VTableRawDlTaskSetting> s_VTable; + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } - static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr) - { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(_thisptr).GetMPTR()); - if (!_thisptr) - _thisptr = boss_new<RawDlTaskSetting>(); - NetTaskSetting::ctor(_thisptr); - _thisptr->vTablePtr = s_VTable; - return _thisptr; - } + /* RawDlTaskSetting */ - static Result Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) - { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_Initialize(0x{:x}, 0x{:x}, {}, {}, 0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); - if (!url) - { - return 0xC0203780; - } - - if (strnlen(url, TaskSetting::kURLLen) == TaskSetting::kURLLen) - { - return 0xC0203780; - } - - cemuLog_logDebug(LogType::Force, "\t-> url: {}", url); - - if (fileName && strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) - { - return 0xC0203780; - } - - if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) - { - return 0xC0203780; - } - - strncpy((char*)_thisptr + TaskSetting::kURL, url, TaskSetting::kURLLen); - _thisptr->settings[0x147] = '\0'; - - if (fileName) - strncpy((char*)_thisptr + 0x7D0, fileName, TaskSetting::kFileNameLen); - else - strncpy((char*)_thisptr + 0x7D0, "rawcontent.dat", TaskSetting::kFileNameLen); - _thisptr->settings[0x7EF] = '\0'; - - cemuLog_logDebug(LogType::Force, "\t-> filename: {}", (char*)_thisptr + 0x7D0); - - if (directoryName) - { - strncpy((char*)_thisptr + 0x7C8, directoryName, TaskSetting::kDirectoryNameLen); - _thisptr->settings[0x7CF] = '\0'; - cemuLog_logDebug(LogType::Force, "\t-> directoryName: {}", (char*)_thisptr + 0x7C8); - } - - _thisptr->settings[0x7C0] = newArrival; - _thisptr->settings[0x7C1] = led; - *(uint16be*)&_thisptr->settings[0x28] = 0x3; - return 0x200080; - } - - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(RawDlTaskSetting) == 0x1004); - - struct PlayReportSetting : RawUlTaskSetting + RawDlTaskSetting* RawDlTaskSetting::ctor(RawDlTaskSetting* _thisptr) { - MEMPTR<uint8> ukn1210_ptr; // 0x1210 - uint32be ukn1214_size; // 0x1214 - uint32be ukPlay3; // 0x1218 - uint32be ukPlay4; // 0x121C + if (!_thisptr) + _thisptr = boss_new<RawDlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } - struct VTablePlayReportSetting : public VTableRawUlTaskSetting - {}; - static_assert(sizeof(VTablePlayReportSetting) == 8*5); - static inline SysAllocator<VTablePlayReportSetting> s_VTable; + BossResult RawDlTaskSetting::Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* bossDirectory) + { + if (!url || !_thisptr->url.CanHoldString(url)) + return resultInvalidParam; + cemuLog_logDebug(LogType::Force, "RawDlTaskSetting::Initialize url: {}", url); + if (fileName && !_thisptr->rawDl.fileName.CanHoldString(fileName)) + return resultInvalidParam; // fileName is optional, but if a string is passed it must fit + if (!bossDirectory || !_thisptr->rawDl.bossDirectory.CanHoldString(bossDirectory)) + return resultInvalidParam; - static PlayReportSetting* ctor(PlayReportSetting* _this) + _thisptr->url.assign(url); + _thisptr->rawDl.fileName.assign(fileName ? fileName : "rawcontent.dat"); + _thisptr->rawDl.bossDirectory.assign(bossDirectory); + _thisptr->rawDl.newArrival = newArrival; + _thisptr->rawDl.led = led; + _thisptr->taskType = TaskType::RawDlTaskSetting_3; + return resultSuccess; + } + + void RawDlTaskSetting::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + + /* PlayReportSetting */ + + PlayReportSetting* PlayReportSetting::ctor(PlayReportSetting* _this) + { + if(!_this) + _this = boss_new<PlayReportSetting>(); + RawUlTaskSetting::ctor(_this); + _this->vTablePtr = s_VTable; + _this->ukn1210_ptr = nullptr; + _this->ukn1214_size = 0; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; + return _this; + } + + void PlayReportSetting::dtor(PlayReportSetting* _this, uint32 options) + { + RawUlTaskSetting::dtor(_this, 0); + if(options&1) + boss_delete(_this->ukn1210_ptr.GetPtr()); + } + + void PlayReportSetting::Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) + { + if (!ptr || size == 0 || size > 0x19000) { - if(!_this) - _this = boss_new<PlayReportSetting>(); - RawUlTaskSetting::ctor(_this); - _this->vTablePtr = s_VTable; - _this->ukn1210_ptr = nullptr; - _this->ukn1214_size = 0; - _this->ukPlay3 = 0; - _this->ukPlay4 = 0; - return _this; + cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); + return; } - static void dtor(PlayReportSetting* _this, uint32 options) - { - RawUlTaskSetting::dtor(_this, 0); - if(options&1) - boss_delete(_this->ukn1210_ptr.GetPtr()); - } + *ptr = 0; - static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) - { - if(!ptr || size == 0 || size > 0x19000) - { - cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); - return; - } + _this->taskType = TaskType::PlayReportSetting; + _this->mode |= 3; + _this->rawUl.optionValue |= 2; + _this->permission |= 0xA; - *ptr = 0; + _this->ukn1210_ptr = ptr; + _this->ukn1214_size = size; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; - *(uint16be*)&_this->settings[0x28] = 6; - *(uint16be*)&_this->settings[0x2B] |= 0x3; - *(uint16be*)&_this->settings[0x2C] |= 0xA; - *(uint32be*)&_this->settings[0x7C0] |= 2; + _this->AddInternalCaCert(_this, 102); + _this->SetInternalClientCert(_this, 1); - _this->ukn1210_ptr = ptr; - _this->ukn1214_size = size; - _this->ukPlay3 = 0; - _this->ukPlay4 = 0; + // todo - incomplete + } - // TODO - } + bool PlayReportSetting::Set(PlayReportSetting* _this, const char* keyname, uint32 value) + { + // TODO + return true; + } - static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value) - { - // TODO - return true; - } + void PlayReportSetting::InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } - static void InitVTable() - { - s_VTable->rtti.ptr = nullptr; // todo - s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); - s_VTable->RegisterPreprocess.ptr = nullptr; // todo - s_VTable->unk1.ptr = nullptr; // todo - s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo - } - }; - static_assert(sizeof(PlayReportSetting) == 0x1220); + /* Task */ struct Task { @@ -681,44 +494,42 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static inline SysAllocator<VTableTask> s_vTable; - uint32be accountId; // 0x00 + uint32be persistentId; // 0x00 uint32be uk2; // 0x04 TaskId taskId; // 0x08 TitleId titleId; // 0x10 MEMPTR<VTableTask> vTablePtr; // 0x18 uint32be padding; // 0x1C - static Result Initialize1(Task* _thisptr, const char* taskId, uint32 accountId) // Initialize__Q3_2nn4boss4TaskFPCcUi + static BossResult Initialize1(Task* _thisptr, const char* taskId, uint32 persistentId) // Initialize__Q3_2nn4boss4TaskFPCcUi { - if(!taskId || strnlen(taskId, 0x8) == 8) - { - return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - } - _thisptr->accountId = accountId; - strncpy(_thisptr->taskId.id, taskId, 0x08); + if (!taskId || !_thisptr->taskId.id.CanHoldString(taskId)) + return resultInvalidParam; + _thisptr->persistentId = persistentId; + _thisptr->taskId.id.assign(taskId); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - static Result Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc + static BossResult Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc { - const uint32 accountId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); - return Initialize1(_thisptr, taskId, accountId); + const uint32 persistentId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); + return Initialize1(_thisptr, taskId, persistentId); } - static Result Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc + static BossResult Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc { return Initialize1(_thisptr, taskId, 0); } - static Task* ctor2(Task* _thisptr, const char* taskId, uint32 accountId) // __ct__Q3_2nn4boss4TaskFPCcUi + static Task* ctor2(Task* _thisptr, const char* taskId, uint32 persistentId) // __ct__Q3_2nn4boss4TaskFPCcUi { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); - auto r = Initialize1(_thisptr, taskId, accountId); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); + auto r = Initialize1(_thisptr, taskId, persistentId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; } @@ -727,10 +538,10 @@ bossBufferVector->buffer = (uint8*)bossRequest; { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize2(_thisptr, slot, taskId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; @@ -740,10 +551,10 @@ bossBufferVector->buffer = (uint8*)bossRequest; { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize3(_thisptr, taskId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; @@ -753,208 +564,168 @@ bossBufferVector->buffer = (uint8*)bossRequest; { if (!_thisptr) _thisptr = boss_new<Task>(); - _thisptr->accountId = 0; + _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; - TaskId::ctor(&_thisptr->taskId); - TitleId::ctor(&_thisptr->titleId, 0); + TaskId::ctorDefault(&_thisptr->taskId); + TitleId::ctorFromTitleId(&_thisptr->titleId, 0); memset(&_thisptr->taskId, 0x00, sizeof(TaskId)); return _thisptr; } static void dtor(Task* _this, uint32 options) // __dt__Q3_2nn4boss4TaskFv { - cemuLog_logDebug(LogType::Force, "nn::boss::Task::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); // todo - Task::Finalize if(options & 1) boss_delete(_this); } - static Result Run(Task* _thisptr, bool isForegroundRun) + static BossResult Run(Task* _thisptr, bool isForegroundRun) { - if (isForegroundRun != 0) + uint8be isConnected = 0; + nn_ac::IsApplicationConnected(&isConnected); + if (isForegroundRun && !isConnected) { - cemuLog_logDebug(LogType::Force, "export_Run foreground run"); + cemuLog_logDebug(LogType::Force, "nn::boss::Task::Run: Application is not connected, returning error"); + return 0xA0223A00; } - - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_RUN; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->bool_parameter = isForegroundRun != 0; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRun)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParam<uint8>(isForegroundRun ? 1 : 0); + return serviceCall.Submit(); } - static Result StartScheduling(Task* _thisptr, uint8 executeImmediately) + static BossResult StartScheduling(Task* _thisptr, uint8 executeImmediately) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_START_SCHEDULING; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->bool_parameter = executeImmediately != 0; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskStartScheduling)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParam<uint8be>(executeImmediately); + return serviceCall.Submit(); } - static Result StopScheduling(Task* _thisptr) + static nnResult StopScheduling(Task* _thisptr) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_STOP_SCHEDULING; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskStopScheduling)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + return serviceCall.Submit(); } - static Result IsRegistered(Task* _thisptr) + static bool IsRegistered(Task* _thisptr) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_IS_REGISTERED; - bossRequest->accountId = _thisptr->accountId; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->taskId = _thisptr->taskId.id; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return bossRequest->returnCode; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskIsRegistered)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return false; + return serviceCall.ReadResponse<uint8be>() != 0; } - static Result Wait(Task* _thisptr, uint32 timeout, uint32 waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState + static bool Wait(Task* _thisptr, uint32 timeout, TaskWaitState waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_WAIT; - bossRequest->titleId = _thisptr->titleId.u64; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->timeout = timeout; - bossRequest->waitState = waitState; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return bossRequest->returnCode; + static_assert(sizeof(TaskSettingCore) == 0xC00); + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskWaitA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParam<betype<TaskWaitState>>(waitState); + serviceCall.WriteParam<uint32be>(timeout); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return false; + uint8 isNotTimeout = serviceCall.ReadResponse<uint8be>(); + return isNotTimeout; } - static Result RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting + static BossResult RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->settings = settings; - bossRequest->uk1 = 0xC00; - - if (TaskSetting::IsPrivileged(settings)) - bossRequest->titleId = _thisptr->titleId.u64; - - Result result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - return result; + static_assert(sizeof(TaskSettingCore) == 0xC00); + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRegisterForImmediateRunA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParamBuffer(settings, sizeof(TaskSettingCore)); + return serviceCall.Submit(); } - static Result Unregister(Task* _thisptr) + static BossResult Unregister(Task* _thisptr) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_UNREGISTER; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->titleId = _thisptr->titleId.u64; - - const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - return result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskUnregister)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + return serviceCall.Submit(); } - static Result Register(Task* _thisptr, TaskSetting* settings) + static BossResult Register(Task* _thisptr, TaskSetting* settings) { + static_assert(sizeof(TaskSettingCore) == 0xC00); if (!settings) { cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); // settings should never be zero return 0; } - - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN; - bossRequest->accountId = _thisptr->accountId; - bossRequest->taskId = _thisptr->taskId.id; - bossRequest->settings = settings; - bossRequest->uk1 = 0xC00; - - if(TaskSetting::IsPrivileged(settings)) - bossRequest->titleId = _thisptr->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - return bossRequest->returnCode; + // todo - missing vcall which leads to nn::boss::PlayReportSetting::RegisterPreprocess (and other functions?) being called? + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRegisterA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + serviceCall.WriteParamBuffer(settings, sizeof(TaskSettingCore)); + return serviceCall.Submit(); } - static uint32 GetTurnState(Task* _this, uint32be* executionCountOut) + static TaskTurnState GetTurnState(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_TURN_STATE; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u32.exec_count; - - return bossRequest->u32.result; - // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetTurnState)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return (TaskTurnState)0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<betype<TaskTurnState>>(); } - static uint64 GetContentLength(Task* _this, uint32be* executionCountOut) + static uint64 GetContentLength(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u64.exec_count; - - return bossRequest->u64.result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetContentLength)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<uint64be>(); } - static uint64 GetProcessedLength(Task* _this, uint32be* executionCountOut) + static uint64 GetProcessedLength(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u64.exec_count; - return bossRequest->u64.result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetProcessedLength)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<uint64be>(); } - static uint32 GetHttpStatusCode(Task* _this, uint32be* executionCountOut) + static uint32 GetHttpStatusCode(Task* _thisptr, uint32be* executionCounter) { - bossPrepareRequest(); - bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE; - bossRequest->accountId = _this->accountId; - bossRequest->taskId = _this->taskId.id; - bossRequest->titleId = _this->titleId.u64; - - __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - - if (executionCountOut) - *executionCountOut = bossRequest->u32.exec_count; - - return bossRequest->u32.result; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetHttpStatusCodeA)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<TaskId>(_thisptr->taskId); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + uint32 executionCount = serviceCall.ReadResponse<uint32be>(); + if (executionCounter) + *executionCounter = executionCount; + return serviceCall.ReadResponse<uint32be>(); } static void InitVTable() @@ -1030,14 +801,11 @@ bossBufferVector->buffer = (uint8*)bossRequest; static uint32 Initialize(AlmightyTask* _thisptr, TitleId* titleId, const char* taskId, uint32 accountId) { if (!_thisptr) - return 0xc0203780; - - _thisptr->accountId = accountId; + return resultInvalidParam; + _thisptr->persistentId = accountId; _thisptr->titleId.u64 = titleId->u64; - strncpy(_thisptr->taskId.id, taskId, 8); - _thisptr->taskId.id[7] = 0x00; - - return 0x200080; + _thisptr->taskId.id.assign(taskId); + return resultSuccess; } static void InitVTable() @@ -1049,40 +817,34 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static_assert(sizeof(AlmightyTask) == 0x20); - struct DataName + DataName* DataName::ctor(DataName* _this) { - char name[32]; + if(!_this) + _this = boss_new<DataName>(); + _this->name.ClearAllBytes(); + return _this; + } - static DataName* ctor(DataName* _this) // __ct__Q3_2nn4boss8DataNameFv - { - if(!_this) - _this = boss_new<DataName>(); - memset(_this->name, 0, sizeof(name)); - return _this; - } - - static const char* operator_const_char(DataName* _this) // __opPCc__Q3_2nn4boss8DataNameCFv - { - return _this->name; - } - }; - static_assert(sizeof(DataName) == 0x20); + const char* DataName::operator_const_char(DataName* _this) + { + return _this->name.c_str(); + } struct BossStorageFadEntry { - char name[32]; - uint32be fileNameId; + CafeString<32> name; + uint32be dataId; uint32 ukn24; uint32 ukn28; uint32 ukn2C; uint32 ukn30; - uint32be timestampRelated; // guessed + uint32be timestampRelated; // unsure }; - -#define FAD_ENTRY_MAX_COUNT 512 + static_assert(sizeof(BossStorageFadEntry) == 0x38); struct Storage { + static_assert(sizeof(DirectoryName) == 8); struct VTableStorage { VTableEntry rtti; @@ -1096,10 +858,10 @@ bossBufferVector->buffer = (uint8*)bossRequest; kStorageKind_RawDl, }; - /* +0x00 */ uint32be accountId; + /* +0x00 */ uint32be persistentId; /* +0x04 */ uint32be storageKind; /* +0x08 */ uint8 ukn08Array[3]; - /* +0x0B */ char storageName[8]; + /* +0x0B */ DirectoryName storageName; uint8 ukn13; uint8 ukn14; uint8 ukn15; @@ -1120,41 +882,23 @@ bossBufferVector->buffer = (uint8*)bossRequest; static void dtor(nn::boss::Storage* _this, uint32 options) // __dt__Q3_2nn4boss7StorageFv { - cemuLog_logDebug(LogType::Force, "nn::boss::Storage::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); Finalize(_this); if(options & 1) boss_delete(_this); } - static void nnBossStorage_prepareTitleId(Storage* storage) + static BossResult Initialize(Storage* _thisptr, const char* storageName, uint32 accountId, StorageKind type) { - if (storage->titleId.u64 != 0) - return; - storage->titleId.u64 = CafeSystem::GetForegroundTitleId(); - } - - static Result Initialize(Storage* _thisptr, const char* dirName, uint32 accountId, StorageKind type) - { - if (!dirName) - return 0xC0203780; - - cemuLog_logDebug(LogType::Force, "boss::Storage::Initialize({}, 0x{:08x}, {})", dirName, accountId, type); - + if (!storageName) + return resultInvalidParam; _thisptr->storageKind = type; _thisptr->titleId.u64 = 0; - - memset(_thisptr->storageName, 0, 0x8); - strncpy(_thisptr->storageName, dirName, 0x8); - _thisptr->storageName[7] = '\0'; - - _thisptr->accountId = accountId; - - nnBossStorage_prepareTitleId(_thisptr); // usually not done like this - - return 0x200080; + _thisptr->storageName.name2.assign(storageName); + _thisptr->persistentId = accountId; + return resultSuccess; } - static Result Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) + static BossResult Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) { return Initialize(_thisptr, dirName, 0, type); } @@ -1164,110 +908,34 @@ bossBufferVector->buffer = (uint8*)bossRequest; memset(_this, 0, sizeof(Storage)); // todo - not all fields might be cleared } - static Result GetDataList(nn::boss::Storage* storage, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 + static bool Exist(nn::boss::Storage* _thisptr) { - // initialize titleId of storage if not already done - nnBossStorage_prepareTitleId(storage); - - if(startIndex >= FAD_ENTRY_MAX_COUNT) { - *outputEntryCount = 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::StorageExist)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storageKind); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) return 0; - } - - // load fad.db - BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 validEntryCount = 0; - for (sint32 i = startIndex; i < FAD_ENTRY_MAX_COUNT; i++) - { - if( fadTable[i].name[0] == '\0' ) - continue; - memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); - validEntryCount++; - if (validEntryCount >= maxEntries) - break; - } - *outputEntryCount = validEntryCount; - free(fadTable); - } - else - { - // could not load fad table - *outputEntryCount = 0; - } - return 0; // todo + return serviceCall.ReadResponse<uint8be>(); } - static bool Exist(nn::boss::Storage* storage) + static nnResult GetDataList(nn::boss::Storage* _thisptr, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 { - cemuLog_logDebug(LogType::Force, "nn_boss::Storage::Exist() TODO"); - return true; - } - - /* FAD access */ - - static FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage* storage, uint32 fileNameId) - { - char storageFilePath[1024]; - sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); - sint32 fscStatus; - FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); - return fscStorageFile; - } - - static BossStorageFadEntry* nnBossStorageFad_getTable(nn::boss::Storage* storage) - { - const auto accountId = ActiveSettings::GetPersistentId(); - char fadPath[1024]; - sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); - - sint32 fscStatus; - FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (!fscFadFile) - { - return nullptr; - } - // skip first 8 bytes - fsc_setFileSeek(fscFadFile, 8); - // read entries - BossStorageFadEntry* fadTable = (BossStorageFadEntry*)malloc(sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); - memset(fadTable, 0, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); - fsc_readFile(fscFadFile, fadTable, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); - fsc_close(fscFadFile); - return fadTable; - } - - // Find index of entry by name. Returns -1 if not found - static sint32 nnBossStorageFad_getIndexByName(BossStorageFadEntry* fadTable, char* name) - { - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) - { - if (fadTable[i].name[0] == '\0') - continue; - if (strncmp(name, fadTable[i].name, 0x20) == 0) - { - return i; - } - } - return -1; - } - - static bool nnBossStorageFad_getEntryByName(nn::boss::Storage* storage, char* name, BossStorageFadEntry* fadEntry) - { - BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); - if (entryIndex >= 0) - { - memcpy(fadEntry, fadTable + entryIndex, sizeof(BossStorageFadEntry)); - free(fadTable); - return true; - } - free(fadTable); - } - return false; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::StorageGetDataList)); + serviceCall.WriteParam<uint32be>(_thisptr->persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storageKind); + serviceCall.WriteResponseBuffer(dataList, sizeof(DataName) * maxEntries); + serviceCall.WriteParam<uint32be>(startIndex); + nnResult r = serviceCall.Submit(); + uint32be outputCount = serviceCall.ReadResponse<uint32be>(); + bool isSuccessByte = serviceCall.ReadResponse<uint8be>() != 0; + if (!isSuccessByte) + return resultUknA0220300; + if (outputEntryCount) + *outputEntryCount = outputCount; + return r; } static void InitVTable() @@ -1294,7 +962,6 @@ bossBufferVector->buffer = (uint8*)bossRequest; static AlmightyStorage* ctor(AlmightyStorage* _thisptr) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_ctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); if (!_thisptr) _thisptr = boss_new<AlmightyStorage>(); Storage::ctor1(_thisptr); @@ -1304,18 +971,16 @@ bossBufferVector->buffer = (uint8*)bossRequest; static uint32 Initialize(AlmightyStorage* _thisptr, TitleId* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_Initialize(0x{:x})", MEMPTR(_thisptr).GetMPTR()); if (!_thisptr) - return 0xc0203780; + return resultInvalidParam; - _thisptr->accountId = accountId; + _thisptr->persistentId = accountId; _thisptr->storageKind = storageKind; _thisptr->titleId.u64 = titleId->u64; - strncpy(_thisptr->storageName, storageName, 8); - _thisptr->storageName[0x7] = 0x00; + _thisptr->storageName.name2.assign(storageName); - return 0x200080; + return resultSuccess; } static void InitVTable() @@ -1338,9 +1003,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static inline SysAllocator<VTableNsData> s_vTable; - /* +0x00 */ char name[0x20]; // DataName ? - /* +0x20 */ nn::boss::Storage storage; - /* +0x48 */ uint64 readIndex; + /* +0x00 */ DataName name; + /* +0x20 */ Storage storage; + /* +0x48 */ uint64be currentSeek; /* +0x50 */ MEMPTR<void> vTablePtr; /* +0x54 */ uint32 ukn54; @@ -1349,9 +1014,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; if (!_this) _this = boss_new<NsData>(); _this->vTablePtr = s_vTable; - memset(_this->name, 0, sizeof(_this->name)); + DataName::ctor(&_this->name); _this->storage.ctor1(&_this->storage); - _this->readIndex = 0; + _this->currentSeek = 0; return _this; } @@ -1363,42 +1028,35 @@ bossBufferVector->buffer = (uint8*)bossRequest; boss_delete(_this); } - static Result Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) + static BossResult Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) { if(dataName == nullptr) { if (storage->storageKind != 1) { - return 0xC0203780; + return resultInvalidParam; } } - _this->storage.accountId = storage->accountId; + _this->storage.persistentId = storage->persistentId; _this->storage.storageKind = storage->storageKind; - memcpy(_this->storage.ukn08Array, storage->ukn08Array, 3); - memcpy(_this->storage.storageName, storage->storageName, 8); - - _this->storage.titleId.u64 = storage->titleId.u64; - + _this->storage.storageName = storage->storageName; + _this->storage.titleId = storage->titleId; _this->storage = *storage; if (dataName != nullptr || storage->storageKind != 1) - strncpy(_this->name, dataName, 0x20); + _this->name.name.assign(dataName); else - strncpy(_this->name, "rawcontent.dat", 0x20); - _this->name[0x1F] = '\0'; + _this->name.name.assign("rawcontent.dat"); + _this->currentSeek = 0; - _this->readIndex = 0; - - cemuLog_logDebug(LogType::Force, "initialize: {}", _this->name); - - return 0x200080; + return resultSuccess; } static std::string _GetPath(NsData* nsData) { - uint32 accountId = nsData->storage.accountId; + uint32 accountId = nsData->storage.persistentId; if (accountId == 0) accountId = iosuAct_getAccountIdOfCurrentAccount(); @@ -1407,204 +1065,120 @@ bossBufferVector->buffer = (uint8*)bossRequest; title_id = CafeSystem::GetForegroundTitleId(); fs::path path = fmt::format("cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId); - path /= nsData->storage.storageName; - path /= nsData->name; + path /= nsData->storage.storageName.name2.c_str(); + path /= nsData->name.c_str(); return path.string(); } - static Result DeleteRealFileWithHistory(NsData* nsData) + static BossResult DeleteRealFile(NsData* _thisptr) { - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - // todo - cemuLog_log(LogType::Force, "BOSS NBDL: Unsupported delete"); - } - else - { - sint32 fscStatus = FSC_STATUS_OK; - std::string filePath = _GetPath(nsData).c_str(); - fsc_remove((char*)filePath.c_str(), &fscStatus); - if (fscStatus != 0) - cemuLog_log(LogType::Force, "Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); - } - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataDeleteFile)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + return serviceCall.Submit(); } - static uint32 Exist(NsData* nsData) + static BossResult DeleteRealFileWithHistory(NsData* _thisptr) { - bool fileExists = false; - if(nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - // check if name is present in fad table - BossStorageFadEntry* fadTable = nn::boss::Storage::nnBossStorageFad_getTable(&nsData->storage); - if (fadTable) - { - fileExists = nn::boss::Storage::nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; - cemuLog_logDebug(LogType::Force, "\t({}) -> {}", nsData->name, fileExists); - free(fadTable); - } - } - else - { - sint32 fscStatus; - auto fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); - if (fscStorageFile != nullptr) - { - fileExists = true; - fsc_close(fscStorageFile); - } - } - return fileExists?1:0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataDeleteFileWithHistory)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + return serviceCall.Submit(); } - static uint64 GetSize(NsData* nsData) + static uint32 Exist(NsData* _thisptr) { - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - BossStorageFadEntry fadEntry; - if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) - { - cemuLog_log(LogType::Force, "BOSS storage cant find file {}", nsData->name); - return 0; - } - // open file - fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - - if (fscStorageFile == nullptr) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {}", nsData->name); + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataExist)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) return 0; - } + return serviceCall.ReadResponse<uint8be>(); + } - // get size - const sint32 fileSize = fsc_getFileSize(fscStorageFile); - // close file - fsc_close(fscStorageFile); - return fileSize; + static uint64 GetSize(NsData* _thisptr) + { + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataGetSize)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return 0; + return serviceCall.ReadResponse<uint64be>(); } static uint64 GetCreatedTime(NsData* nsData) { cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0"); uint64 createdTime = 0; + // cmdId 0x97 + // probably uses FS stat query return createdTime; } - static uint32 nnBossNsData_read(NsData* nsData, uint64be* sizeOutBE, void* buffer, sint32 length) + static sint32 ReadWithSizeOut(NsData* _thisptr, uint64be* sizeOut, uint8* buffer, sint32 length) { - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - BossStorageFadEntry fadEntry; - if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) - { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // open file - fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - - if (!fscStorageFile) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // verify read is within bounds - sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; - sint32 readBytes = length; - if (readEndOffset > fileSize) - { - readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); - cemu_assert_debug(readBytes != 0); - } - // read - fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); - fsc_readFile(fscStorageFile, buffer, readBytes); - nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); - - // close file - fsc_close(fscStorageFile); - if (sizeOutBE) - *sizeOutBE = readBytes; - return 0; + IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataRead)); + serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); + serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); + serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); + serviceCall.WriteParam<DataName>(_thisptr->name); + serviceCall.WriteResponseBuffer(buffer, length); + uint64 readOffset = _thisptr->currentSeek; + serviceCall.WriteParam<uint64be>(readOffset); + nnResult r = serviceCall.Submit(); + if (NN_RESULT_IS_FAILURE(r)) + return r; + uint64 numReadBytes = serviceCall.ReadResponse<uint64be>(); + _thisptr->currentSeek += numReadBytes; + cemu_assert(sizeOut); + cemu_assert(numReadBytes <= length); + *sizeOut = numReadBytes; + return r; } -#define NSDATA_SEEK_MODE_BEGINNING (0) - - static uint32 nnBossNsData_seek(NsData* nsData, uint64 seek, uint32 mode) + static sint32 Read(NsData* _thisptr, uint8* buffer, sint32 length) { - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) - { - BossStorageFadEntry fadEntry; - if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) - { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // open file - fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } + uint64be bytesRead = 0; + return ReadWithSizeOut(_thisptr, &bytesRead, buffer, length); + } - if (fscStorageFile == nullptr) + enum class NsDataSeekMode + { + Beginning = 0, + Relative = 1, + Ending = 2 + }; + + static BossResult Seek(NsData* _thisptr, sint64 seekPos, NsDataSeekMode mode) + { + if (mode == NsDataSeekMode::Beginning) { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code + _thisptr->currentSeek = seekPos; } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // handle seek - if (mode == NSDATA_SEEK_MODE_BEGINNING) + else if (mode == NsDataSeekMode::Relative) { - seek = std::min(seek, (uint64)fileSize); - nsData->readIndex = _swapEndianU64((uint64)seek); + _thisptr->currentSeek += seekPos; + } + else if (mode == NsDataSeekMode::Ending) + { + uint64 fileSize = GetSize(_thisptr); + _thisptr->currentSeek = fileSize + seekPos; } else { cemu_assert_unimplemented(); } - fsc_close(fscStorageFile); - return 0; - } - - static sint32 Read(NsData* nsData, uint8* buffer, sint32 length) - { - cemuLog_logDebug(LogType::Force, "nsData read (filename {})", nsData->name); - return nnBossNsData_read(nsData, nullptr, buffer, length); - } - - static sint32 ReadWithSizeOut(NsData* nsData, uint64be* sizeOut, uint8* buffer, sint32 length) - { - uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); - cemuLog_logDebug(LogType::Force, "nsData readWithSizeOut (filename {} length 0x{:x}) Result: {} Sizeout: {:x}", nsData->name, length, r, _swapEndianU64(*sizeOut)); - return r; - } - - static Result Seek(NsData* nsData, uint64 seekPos, uint32 mode) - { - uint32 r = nnBossNsData_seek(nsData, seekPos, mode); - cemuLog_logDebug(LogType::Force, "nsData seek (filename {} seek 0x{:x}) Result: {}", nsData->name, (uint32)seekPos, r); - return r; + return resultSuccess; } static void InitVTable() @@ -1631,6 +1205,10 @@ void nnBoss_load() cafeExportRegisterFunc(nn::boss::IsInitialized, "nn_boss", "IsInitialized__Q2_2nn4bossFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Finalize, "nn_boss", "Finalize__Q2_2nn4bossFv", LogType::NN_BOSS); + // taskId + cafeExportRegisterFunc(nn::boss::TaskId::ctorDefault, "nn_boss", "__ct__Q3_2nn4boss6TaskIDFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TaskId::ctorFromString, "nn_boss", "__ct__Q3_2nn4boss6TaskIDFPCc", LogType::NN_BOSS); + // task nn::boss::Task::InitVTable(); cafeExportRegisterFunc(nn::boss::Task::ctor1, "nn_boss", "__ct__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); @@ -1695,9 +1273,9 @@ void nnBoss_load() // cafeExportMakeWrapper<nn::boss::Title::SetNewArrivalFlagOff>("nn_boss", "SetNewArrivalFlagOff__Q3_2nn4boss5TitleFv"); SMM bookmarks // TitleId - cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::TitleId::ctor3, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctorDefault, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctorFromTitleId, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctorCopy, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); // DataName @@ -1739,6 +1317,7 @@ void nnBoss_load() cafeExportRegisterFunc(nn::boss::NsData::ctor, "nn_boss", "__ct__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::dtor, "nn_boss", "__dt__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Initialize, "nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFile, "nn_boss", "DeleteRealFile__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFileWithHistory, "nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Exist, "nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::GetSize, "nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); diff --git a/src/Cafe/OS/libs/nn_client_service.h b/src/Cafe/OS/libs/nn_client_service.h new file mode 100644 index 00000000..fa4ed5cd --- /dev/null +++ b/src/Cafe/OS/libs/nn_client_service.h @@ -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; +}; diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 6a01e75a..ecd2544e 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -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); diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 88b4f320..65a0c5ee 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -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(); diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index 182c5371..ce11e2fe 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -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; diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h index adfe7393..bc01711b 100644 --- a/src/Cemu/napi/napi_helper.h +++ b/src/Cemu/napi/napi_helper.h @@ -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 diff --git a/src/Common/CafeString.h b/src/Common/CafeString.h index 57fc72da..7a851f68 100644 --- a/src/Common/CafeString.h +++ b/src/Common/CafeString.h @@ -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]; }; diff --git a/src/gui/wxgui/MainWindow.cpp b/src/gui/wxgui/MainWindow.cpp index a0f176c9..c17bdb03 100644 --- a/src/gui/wxgui/MainWindow.cpp +++ b/src/gui/wxgui/MainWindow.cpp @@ -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 diff --git a/src/gui/wxgui/MainWindow.h b/src/gui/wxgui/MainWindow.h index 07464188..0e80b16d 100644 --- a/src/gui/wxgui/MainWindow.h +++ b/src/gui/wxgui/MainWindow.h @@ -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);