Cemu/src/Cafe/IOSU/nn/boss/boss_service.cpp
goeiecool9999 5520613dc3 nn_boss: Check for nullptr
Fixes a crash in Super Smash Bros.
2025-12-05 23:57:57 +01:00

1433 lines
49 KiB
C++

#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 <pugixml.hpp>
#include <curl/curl.h>
#include <openssl/x509.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#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 <typename ... TArgs>
void AppendHeaderParam(CurlRequestHelper& request, const char* fieldName, const char* format, TArgs&& ... args)
{
request.addHeaderField(fieldName, fmt::format(fmt::runtime(format), std::forward<TArgs>(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<FAD_ENTRY_MAX_COUNT; i++)
{
if (m_fadData.entries[i].fileId == 0)
{
m_fadData.entries[i].fileName = fileName;
m_fadData.entries[i].fileId = fileId;
m_fadData.entries[i].memo_2C = 0;
m_fadData.entries[i].entryCreationTimestamp = 0;
m_fadData.entries[i].ukn24 = 0;
m_fadData.entries[i].flags = 0;
return 0x200080;
}
}
cemuLog_log(LogType::Force, "FAD file {} is full", _pathToUtf8(m_fadFilePath));
return 0xA021FB00;
}
void GetDataList(std::vector<DataName>& 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<FileStream> 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<FileStream> 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<uint32>(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<uint32>(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<uint32>(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<NbdlQueuedFile> 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<NbdlQueuedFile> ParseNbdlTasksheet(std::span<uint8> xmlContent)
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_buffer(xmlContent.data(), xmlContent.size());
if (!result)
return {};
std::vector<NbdlQueuedFile> 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<uint8> 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<RegisteredTask>(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> 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> registeredTask = GetRegisteredTask2(persistentId, taskId);
if(!registeredTask)
return;
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<RegisteredTask> 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<RegisteredTask> 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<RegisteredTask> 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<TaskId, std::shared_ptr<RegisteredTask>> 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<BossCommandId>(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, &currentPersistentId);;
if (!r)
return 0;
return currentPersistentId;
}
nnResult HandleTaskIsRegistered(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
bool isRegistered = s_bossDaemon.TaskIsRegistered(persistentId, taskId);
// write response
serviceCall.WriteResponse<uint8be>(isRegistered ? 1 : 0);
return RESULT_SUCCESS;
}
nnResult HandleTaskRegister(IPCServiceCall& serviceCall, bool forImmediateRun)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
IPCServiceCall::UnalignedBuffer taskSettingsBuffer = serviceCall.ReadUnalignedInputBufferInfo();
TaskSettingCore taskSetting = taskSettingsBuffer.ReadType<TaskSettingCore>(); // 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<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
s_bossDaemon.UnregisterTask(persistentId, taskId);
return RESULT_SUCCESS;
}
nnResult HandleTaskRun(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
bool isForegroundRun = serviceCall.ReadParameter<uint8be>() != 0; // todo - handle this
s_bossDaemon.TaskRun(persistentId, taskId);
return RESULT_SUCCESS;
}
nnResult HandleTaskStartScheduling(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
uint8 startImmediately = serviceCall.ReadParameter<uint8be>();
s_bossDaemon.StartScheduleTask(persistentId, taskId, startImmediately != 0);
return RESULT_SUCCESS;
}
nnResult HandleTaskStopScheduling(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
s_bossDaemon.StopScheduleTask(persistentId, taskId);
return RESULT_SUCCESS;
}
nnResult HandleTaskWait(IPCServiceCall& serviceCall)
{
static_assert(sizeof(betype<TaskWaitState>) == 4);
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
TaskWaitState waitState = serviceCall.ReadParameter<betype<TaskWaitState>>();
uint32 timeout = serviceCall.ReadParameter<uint32be>();
std::chrono::steady_clock::time_point endTime;
if (timeout != 0)
endTime = std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
std::shared_ptr<RegisteredTask> 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<uint8be>(1); // 0 -> timeout, 1 -> no timeout
return RESULT_SUCCESS;
}
}
else
{
cemuLog_log(LogType::Force, "IOSU-Boss: Unknown task wait state {}", static_cast<uint32>(waitState));
serviceCall.WriteResponse<uint8be>(0);
return RESULT_SUCCESS;
}
// check for timeout
if (timeout != 0 && std::chrono::steady_clock::now() >= endTime)
{
serviceCall.WriteResponse<uint8be>(0);
return RESULT_SUCCESS; // which error to return here?
}
}
}
nnResult HandleTaskGetTurnState(IPCServiceCall& serviceCall)
{
static_assert(sizeof(betype<TaskTurnState>) == 4);
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId);
TaskTurnState turnState = registeredTask ? registeredTask->GetTurnState() : TaskTurnState::Ukn;
serviceCall.WriteResponse<uint32be>(1); // execution counter - todo
serviceCall.WriteResponse<betype<TaskTurnState>>(turnState);
if (!registeredTask)
return RESULT_NOTEXIST;
return RESULT_SUCCESS;
}
nnResult HandleTaskGetHttpStatusCodeA(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId);
sint32 httpStatusCode = registeredTask ? registeredTask->GetHttpStatusCode() : 0;
serviceCall.WriteResponse<sint32be>(1); // execution counter - todo
serviceCall.WriteResponse<sint32be>(httpStatusCode);
if (!registeredTask)
return RESULT_NOTEXIST;
return RESULT_SUCCESS;
}
nnResult HandleTaskGetContentLength(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId);
uint32 contentLength = registeredTask ? registeredTask->GetContentLength() : 0;
serviceCall.WriteResponse<uint32be>(1); // execution counter - todo
serviceCall.WriteResponse<uint64be>(contentLength);
if (!registeredTask)
return RESULT_NOTEXIST;
return RESULT_SUCCESS;
}
nnResult HandleTaskGetProcessedLength(IPCServiceCall& serviceCall)
{
uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
TaskId taskId = serviceCall.ReadParameter<TaskId>();
std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId);
uint32 processedLength = registeredTask ? registeredTask->GetProcessedLength() : 0;
serviceCall.WriteResponse<uint32be>(1); // execution counter - todo
serviceCall.WriteResponse<uint64be>(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<StorageKind>) == 4);
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
StorageKind storageKind = serviceCall.ReadParameter<betype<StorageKind>>();
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<uint32>(storageKind));
cemu_assert_unimplemented();
}
serviceCall.WriteResponse<uint8be>(storageExists ? 1 : 0);
return RESULT_SUCCESS;
}
nnResult HandleStorageGetDataList(IPCServiceCall& serviceCall)
{
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
uint32 ukn = serviceCall.ReadParameter<uint32be>();
cemu_assert_debug(ukn == 0);
IPCServiceCall::UnalignedBuffer dataListOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo();
uint32 startOffset = serviceCall.ReadParameter<uint32be>(); // guessed
cemu_assert_debug(startOffset == 0); // todo - implement offset
size_t dataListSize = dataListOutputBuffer.GetSize() / sizeof(DataName);
std::vector<DataName> 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<uint32be>((uint32)dataListTmp.size());
serviceCall.WriteResponse<uint8be>(1); // extra byte to indicate success or failure
return RESULT_SUCCESS;
}
/* NsData */
nnResult HandleNsDataExist(IPCServiceCall& serviceCall)
{
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind?
DataName fileName = serviceCall.ReadParameter<DataName>();
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<uint8be>(nsDataExists);
return RESULT_SUCCESS;
}
nnResult HandleNsDataRead(IPCServiceCall& serviceCall)
{
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind?
DataName fileName = serviceCall.ReadParameter<DataName>();
IPCServiceCall::UnalignedBuffer dataOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo();
uint64 readOffset = serviceCall.ReadParameter<uint64be>();
uint64 titleId = CafeSystem::GetForegroundTitleId();
nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName);
if (NN_RESULT_IS_FAILURE(r))
{
serviceCall.WriteResponse<uint64be>(0); // should we still write the bytes read in case of failure?
return r;
}
uint32 bytesRead = 0;
std::vector<uint8> tmpBuffer;
tmpBuffer.resize(dataOutputBuffer.GetSize());
bool rRead = m_nsDataAccessor.Read(tmpBuffer.data(), tmpBuffer.size(), readOffset, bytesRead);
dataOutputBuffer.WriteData(tmpBuffer.data(), tmpBuffer.size());
serviceCall.WriteResponse<uint64be>(bytesRead);
// todo - handle rRead
return RESULT_SUCCESS;
}
nnResult HandleNsDataGetSize(IPCServiceCall& serviceCall)
{
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind?
DataName fileName = serviceCall.ReadParameter<DataName>();
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<uint64be>(fileSize);
return r;
}
nnResult HandleNsDataDeleteFile(IPCServiceCall& serviceCall, bool withHistory)
{
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind?
DataName fileName = serviceCall.ReadParameter<DataName>();
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<uint32be>());
if (!persistentId)
return RESULT_C0203880;
DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>();
uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind?
DataName fileName = serviceCall.ReadParameter<DataName>();
m_nsDataAccessor.Close();
return RESULT_SUCCESS;
}
/* Title */
nnResult HandleTitleSetNewArrivalFlag(IPCServiceCall& serviceCall)
{
auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>());
if (!persistentId)
return RESULT_C0203880;
uint8 flagValue = serviceCall.ReadParameter<uint8be>();
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<IOSUModule*>(&sIOSUModuleNNBOSS);
}
}