mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-12-11 16:37:02 +00:00
1433 lines
49 KiB
C++
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, ¤tPersistentId);;
|
|
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);
|
|
}
|
|
}
|