Squash commits during rebase again:

Final things from review comments

Clang format

Shorten property code (similar to #6883) and move to utils

Move all additional helper functions into a 'utils' file. Simplify some things as requested in review

Minor code changes from review before rebase

fix misusing std span

Fix leftovers from rebase, and null-terminator problem with download string-view

Change downloadbossdatafromurl to take in string_views, make getting the list of files more dynamic

Fix error in linux builds and cleanup

Squash commits during rebase:

Some changes as per review and cleanup

More changes as per review

Changes as per review

Futures cannot be copied, remove stubbed warning on implemented calls, remove unneeded task_status simulation, simplify getting task_status and duration

Implement downloading tasks in background

Very final changes from review; and use common for converting strings

FInal changes for review

Attempt to fix codecvt error

Use references when any_casting

Update boost submodule to use master again, refactor how properties work, other minor changes per review

Fix operator overload error on linux/mingw

Make some changes as requested by review; change boost submodule url temporarily to use boost.url

Fix for android build

Fixes android builds when web services are enabled, like in #6555

Avoid crashes when urls are invalid

clang-format

Return error status properly on task fail

Fix implementation of gettaskstate, gettaskstatus and gettaskservicestatus

Fix mingw build error

Add support for reading tasks from boss save data databases.

clang-format

Implement storing task properties

Fix missing includes and add references in loops

Change task_id_list to map, initial implementation of task properties, minor refactor

Remove the dependency on the newer behavior of std erase to fix android building

Fix compilation on android and other platforms when web services are not enabled

Fix clang-format errors

Add support for downloading and decrypting spotpass data directly from nintendo servers

Fix windows implicit conversion error again

Fix comment

Fix filter in NsDataIdList; Finish GetNsDataHeaderInfo; Implement basic support for registering tasks and checking if they exist TODO actually read and write from boss savedata dbs

Add boss extdata to archive.h so the lle boss module can function properly

Implement ReadNsData and partially implement GetNsDataHeaderInfo and GetNsDataLastUpdate; MK7 now reads spotpass data and successfully boots!

Made requested changes; added filtering; removed readnsdata implementation

Add partial implementations of GetNsDataIdList(1/2/3) and ReadNsData

Add zeroed array of nsdataid entries, run clang-format

Check the spotpass extdata directory to determine number of ns output entries

Check for PLvPWAA

Only set the number of output entries in GetNsDataIdList1 to 1 if PLvPWAA is detected.

Fix plvpwaa dlc error

Return 1 for the number of output entries in the GetNsDataIdList1 stub. This fixes the extra content for Professor Layton vs Phoenix Wright Ace Attorney as the game expects the boss extdata to not be empty. Might break other games if they attempt to do anything with the ns data. (although the readnsdata and deletensdata methods are both still stubbed)
This commit is contained in:
Rokkubro 2023-04-13 23:33:21 +10:00
parent 79ea06b226
commit b5ff2440f6
10 changed files with 1358 additions and 109 deletions

View file

@ -236,6 +236,8 @@ add_library(citra_core STATIC
hle/service/boss/boss_p.h hle/service/boss/boss_p.h
hle/service/boss/boss_u.cpp hle/service/boss/boss_u.cpp
hle/service/boss/boss_u.h hle/service/boss/boss_u.h
hle/service/boss/spotpass_utils.cpp
hle/service/boss/spotpass_utils.h
hle/service/cam/cam.cpp hle/service/cam/cam.cpp
hle/service/cam/cam.h hle/service/cam/cam.h
hle/service/cam/cam_c.cpp hle/service/cam/cam_c.cpp

View file

@ -41,6 +41,7 @@ class Path {
public: public:
Path() : type(LowPathType::Invalid) {} Path() : type(LowPathType::Invalid) {}
Path(const char* path) : type(LowPathType::Char), string(path) {} Path(const char* path) : type(LowPathType::Char), string(path) {}
Path(std::string path) : type(LowPathType::Char), string(std::move(path)) {}
Path(std::vector<u8> binary_data) : type(LowPathType::Binary), binary(std::move(binary_data)) {} Path(std::vector<u8> binary_data) : type(LowPathType::Binary), binary(std::move(binary_data)) {}
template <std::size_t size> template <std::size_t size>
Path(const std::array<u8, size>& binary_data) Path(const std::array<u8, size>& binary_data)

View file

@ -217,7 +217,13 @@ Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) {
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location, ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location,
bool shared) bool shared)
: shared(shared), mount_point(GetExtDataContainerPath(mount_location, shared)) { : shared(shared), boss(false), mount_point(GetExtDataContainerPath(mount_location, shared)) {
LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point);
}
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location,
bool shared, bool boss)
: shared(shared), boss(boss), mount_point(GetExtDataContainerPath(mount_location, shared)) {
LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point); LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point);
} }
@ -242,7 +248,8 @@ Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) {
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path, ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
u64 program_id) { u64 program_id) {
std::string fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + "user/"; std::string fullpath =
GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + (boss ? "boss/" : "user/");
if (!FileUtil::Exists(fullpath)) { if (!FileUtil::Exists(fullpath)) {
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData. // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist. // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.

View file

@ -19,6 +19,7 @@ namespace FileSys {
class ArchiveFactory_ExtSaveData final : public ArchiveFactory { class ArchiveFactory_ExtSaveData final : public ArchiveFactory {
public: public:
ArchiveFactory_ExtSaveData(const std::string& mount_point, bool shared); ArchiveFactory_ExtSaveData(const std::string& mount_point, bool shared);
ArchiveFactory_ExtSaveData(const std::string& mount_point, bool shared, bool boss);
std::string GetName() const override { std::string GetName() const override {
return "ExtSaveData"; return "ExtSaveData";
@ -44,6 +45,7 @@ public:
private: private:
bool shared; ///< Whether this archive represents an ExtSaveData archive or a SharedExtSaveData bool shared; ///< Whether this archive represents an ExtSaveData archive or a SharedExtSaveData
/// archive /// archive
bool boss; ///< Whether this archive is a spotpass archive or not
/** /**
* This holds the full directory path for this archive, it is only set after a successful call * This holds the full directory path for this archive, it is only set after a successful call

View file

@ -2,25 +2,152 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "common/logging/log.h" #include <common/common_paths.h>
#include <core/file_sys/archive_systemsavedata.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include "common/string_util.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/file_backend.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/result.h"
#include "core/hle/service/boss/boss.h" #include "core/hle/service/boss/boss.h"
#include "core/hle/service/boss/boss_p.h" #include "core/hle/service/boss/boss_p.h"
#include "core/hle/service/boss/boss_u.h" #include "core/hle/service/boss/boss_u.h"
#include "core/hw/aes/key.h"
namespace Service::BOSS { namespace Service::BOSS {
void Module::Interface::InitializeSession(Kernel::HLERequestContext& ctx) { void Module::Interface::InitializeSession(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u64 programID = rp.Pop<u64>(); const u64 provided_program_id = rp.Pop<u64>();
rp.PopPID(); rp.PopPID();
util.cur_props = BossTaskProperties();
// I'm putting this here for now because I don't know where else to put it;
// the BOSS service saves data in its BOSS_A(Archive? A list of program ids and some
// properties that are keyed on program), BOSS_SS (Saved Strings? Includes the url and the
// other string properties, and also some other properties?, keyed on task_id) and BOSS_SV
// (Saved Values? Includes task id and most properties, keyed on task_id) databases in the
// following format: A four byte header (always 00 80 34 12?) followed by any number of
// 0x800(BOSS_A) and 0xC00(BOSS_SS and BOSS_SV) entries.
if (provided_program_id != 0) {
util.program_id = provided_program_id;
}
LOG_DEBUG(Service_BOSS, "called, program_id={:#018X}", util.program_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
// We can always return success from this as the boss dbs are not properly used atm
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_BOSS, "(STUBBED) programID={:#018X}", programID); const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
// Open the SystemSaveData archive 0x00010034
FileSys::Path archive_path(BOSS_SYSTEM_SAVEDATA_ID);
auto archive_result = systemsavedata_factory.Open(archive_path, 0);
std::unique_ptr<FileSys::ArchiveBackend> boss_system_save_data_archive;
// If the archive didn't exist, create the files inside
if (archive_result.Code() == FileSys::ERROR_NOT_FOUND) {
// Format the archive to create the directories
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
// Open it again to get a valid archive now that the folder exists
auto create_archive_result = systemsavedata_factory.Open(archive_path, 0);
if (create_archive_result.Succeeded()) {
boss_system_save_data_archive = std::move(create_archive_result).Unwrap();
} else {
LOG_ERROR(Service_BOSS, "Could not open boss savedata");
return;
}
} else if (archive_result.Succeeded()) {
boss_system_save_data_archive = std::move(archive_result).Unwrap();
} else {
LOG_ERROR(Service_BOSS, "Could not open boss savedata");
return;
}
FileSys::Path boss_a_path("/BOSS_A.db");
FileSys::Mode open_mode = {};
open_mode.read_flag.Assign(1);
auto boss_a_result = boss_system_save_data_archive->OpenFile(boss_a_path, open_mode);
// Read the file if it already exists
if (boss_a_result.Succeeded()) {
auto boss_a = std::move(boss_a_result).Unwrap();
const u64 boss_a_size = boss_a->GetSize();
// Check the file has a valid size (multiple of the number of entries, plus header)
if (!(boss_a_size > BOSS_SAVE_HEADER_SIZE &&
((boss_a_size - BOSS_SAVE_HEADER_SIZE) % BOSS_A_ENTRY_SIZE) == 0)) {
} else {
u64 num_entries = (boss_a_size - BOSS_SAVE_HEADER_SIZE) / BOSS_A_ENTRY_SIZE;
// Print the program ids of the entries
for (u64 i = 0; i < num_entries; i++) {
u64 entry_offset = i * BOSS_A_ENTRY_SIZE + BOSS_SAVE_HEADER_SIZE;
u64 prog_id;
boss_a->Read(entry_offset, sizeof(prog_id), reinterpret_cast<u8*>(&prog_id));
LOG_DEBUG(Service_BOSS, "Id in entry {} is {:#018X}", i, prog_id);
}
}
}
FileSys::Path boss_sv_path("/BOSS_SV.db");
auto boss_sv_result = boss_system_save_data_archive->OpenFile(boss_sv_path, open_mode);
FileSys::Path boss_ss_path("/BOSS_SS.db");
auto boss_ss_result = boss_system_save_data_archive->OpenFile(boss_ss_path, open_mode);
// Read the files if they already exist
if (boss_sv_result.Succeeded() && boss_ss_result.Succeeded()) {
auto boss_sv = std::move(boss_sv_result).Unwrap();
auto boss_ss = std::move(boss_ss_result).Unwrap();
// Check the file length is valid. Both files should have the same number of entries
if (!(boss_sv->GetSize() > BOSS_SAVE_HEADER_SIZE &&
((boss_sv->GetSize() - BOSS_SAVE_HEADER_SIZE) % BOSS_S_ENTRY_SIZE) == 0 &&
boss_sv->GetSize() == boss_ss->GetSize())) {
LOG_WARNING(Service_BOSS, "Boss dbs have incorrect size");
return;
} else {
u64 num_entries = (boss_sv->GetSize() - BOSS_SAVE_HEADER_SIZE) / BOSS_S_ENTRY_SIZE;
for (u64 i = 0; i < num_entries; i++) {
u64 entry_offset = i * BOSS_S_ENTRY_SIZE + BOSS_SAVE_HEADER_SIZE;
// Print the program id and task id to debug
u64 prog_id;
boss_sv->Read(entry_offset + BOSS_S_PROG_ID_OFFSET, sizeof(prog_id),
reinterpret_cast<u8*>(&prog_id));
LOG_DEBUG(Service_BOSS, "Id sv in entry {} is {:#018X}", i, prog_id);
std::string task_id(TASK_ID_SIZE, 0);
boss_sv->Read(entry_offset + BOSS_S_TASK_ID_OFFSET, TASK_ID_SIZE,
reinterpret_cast<u8*>(task_id.data()));
LOG_DEBUG(Service_BOSS, "Task id in entry {} is {}", i, task_id);
std::vector<u8> url(URL_SIZE);
boss_ss->Read(entry_offset + BOSS_S_URL_OFFSET, URL_SIZE, url.data());
LOG_DEBUG(Service_BOSS, "Url for task {} is {}", task_id,
std::string_view(reinterpret_cast<char*>(url.data()), url.size()));
// If the task is for the current program, store the download url for the session.
// In the future we could store more properties, including from the sv db.
if (prog_id == util.program_id) {
LOG_DEBUG(Service_BOSS, "Storing download url");
util.cur_props.props[PropertyID::Url] = url;
if (util.task_id_list.contains(task_id)) {
LOG_WARNING(Service_BOSS, "Task id already in list, will be replaced");
util.task_id_list.erase(task_id);
}
util.task_id_list.emplace(task_id, std::move(util.cur_props));
util.cur_props = BossTaskProperties();
}
}
}
}
} }
void Module::Interface::SetStorageInfo(Kernel::HLERequestContext& ctx) { void Module::Interface::SetStorageInfo(Kernel::HLERequestContext& ctx) {
@ -131,12 +258,22 @@ void Module::Interface::RegisterTask(Kernel::HLERequestContext& ctx) {
const u8 unk_param3 = rp.Pop<u8>(); const u8 unk_param3 = rp.Pop<u8>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
if (util.task_id_list.contains(task_id)) {
LOG_WARNING(Service_BOSS, "Task id already in list, will be replaced");
util.task_id_list.erase(task_id);
}
util.task_id_list.emplace(task_id, std::move(util.cur_props));
util.cur_props = BossTaskProperties();
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", LOG_DEBUG(Service_BOSS, "called, size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", size,
size, unk_param2, unk_param3); unk_param2, unk_param3);
} }
void Module::Interface::UnregisterTask(Kernel::HLERequestContext& ctx) { void Module::Interface::UnregisterTask(Kernel::HLERequestContext& ctx) {
@ -145,11 +282,31 @@ void Module::Interface::UnregisterTask(Kernel::HLERequestContext& ctx) {
const u8 unk_param2 = rp.Pop<u8>(); const u8 unk_param2 = rp.Pop<u8>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
ResultCode result = RESULT_FAILED;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
rb.Push(result);
rb.PushMappedBuffer(buffer);
return;
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
if (util.task_id_list.erase(task_id) == 0) {
LOG_WARNING(Service_BOSS, "Task Id not in list");
} else {
LOG_DEBUG(Service_BOSS, "Task Id erased");
result = RESULT_SUCCESS;
}
}
rb.Push(result);
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}", size, unk_param2); LOG_DEBUG(Service_BOSS, "called, size={:#010X}, unk_param2={:#04X}", size, unk_param2);
} }
void Module::Interface::ReconfigureTask(Kernel::HLERequestContext& ctx) { void Module::Interface::ReconfigureTask(Kernel::HLERequestContext& ctx) {
@ -168,10 +325,37 @@ void Module::Interface::ReconfigureTask(Kernel::HLERequestContext& ctx) {
void Module::Interface::GetTaskIdList(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskIdList(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u16 num_task_ids = static_cast<u16>(util.task_id_list.size());
util.cur_props.props[PropertyID::TotalTasks] = num_task_ids;
LOG_DEBUG(Service_BOSS, "Prepared total_tasks = {}", num_task_ids);
u16 num_returned_task_ids = 0;
std::vector<std::array<u8, TASK_ID_SIZE>> task_ids(TASKIDLIST_SIZE / TASK_ID_SIZE);
for (const auto& iter : util.task_id_list) {
const std::string_view cur_task_id = iter.first;
if (cur_task_id.size() > TASK_ID_SIZE ||
num_returned_task_ids >= TASKIDLIST_SIZE / TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "task id {} too long or too many task ids", cur_task_id);
} else {
std::memcpy(task_ids[num_returned_task_ids].data(), cur_task_id.data(), TASK_ID_SIZE);
num_returned_task_ids++;
LOG_TRACE(Service_BOSS, "wrote task id {}", cur_task_id);
}
}
const auto task_list_prop =
std::get_if<std::vector<u8>>(&util.cur_props.props[PropertyID::TaskIdList]);
if (task_list_prop && task_list_prop->size() == TASKIDLIST_SIZE) {
std::memcpy(task_list_prop->data(), task_ids.data(), TASKIDLIST_SIZE);
LOG_DEBUG(Service_BOSS, "wrote out {} task ids", num_returned_task_ids);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_BOSS, "(STUBBED) called"); LOG_DEBUG(Service_BOSS, "called");
} }
void Module::Interface::GetStepIdList(Kernel::HLERequestContext& ctx) { void Module::Interface::GetStepIdList(Kernel::HLERequestContext& ctx) {
@ -194,16 +378,18 @@ void Module::Interface::GetNsDataIdList(Kernel::HLERequestContext& ctx) {
const u32 start_ns_data_id = rp.Pop<u32>(); const u32 start_ns_data_id = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u16>(0); /// Actual number of output entries rb.Push<u16>(entries_count); /// Actual number of output entries
rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list. rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list.
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, LOG_DEBUG(Service_BOSS,
"(STUBBED) filter={:#010X}, max_entries={:#010X}, " "filter={:#010X}, max_entries={:#010X}, "
"word_index_start={:#06X}, start_ns_data_id={:#010X}", "word_index_start={:#06X}, start_ns_data_id={:#010X}",
filter, max_entries, word_index_start, start_ns_data_id); filter, max_entries, word_index_start, start_ns_data_id);
} }
void Module::Interface::GetNsDataIdList1(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataIdList1(Kernel::HLERequestContext& ctx) {
@ -214,16 +400,18 @@ void Module::Interface::GetNsDataIdList1(Kernel::HLERequestContext& ctx) {
const u32 start_ns_data_id = rp.Pop<u32>(); const u32 start_ns_data_id = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u16>(0); /// Actual number of output entries rb.Push<u16>(entries_count); /// Actual number of output entries
rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list. rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list.
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, LOG_DEBUG(Service_BOSS,
"(STUBBED) filter={:#010X}, max_entries={:#010X}, " "filter={:#010X}, max_entries={:#010X}, "
"word_index_start={:#06X}, start_ns_data_id={:#010X}", "word_index_start={:#06X}, start_ns_data_id={:#010X}",
filter, max_entries, word_index_start, start_ns_data_id); filter, max_entries, word_index_start, start_ns_data_id);
} }
void Module::Interface::GetNsDataIdList2(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataIdList2(Kernel::HLERequestContext& ctx) {
@ -234,16 +422,18 @@ void Module::Interface::GetNsDataIdList2(Kernel::HLERequestContext& ctx) {
const u32 start_ns_data_id = rp.Pop<u32>(); const u32 start_ns_data_id = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u16>(0); /// Actual number of output entries rb.Push<u16>(entries_count); /// Actual number of output entries
rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list. rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list.
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, LOG_DEBUG(Service_BOSS,
"(STUBBED) filter={:#010X}, max_entries={:#010X}, " "filter={:#010X}, max_entries={:#010X}, "
"word_index_start={:#06X}, start_ns_data_id={:#010X}", "word_index_start={:#06X}, start_ns_data_id={:#010X}",
filter, max_entries, word_index_start, start_ns_data_id); filter, max_entries, word_index_start, start_ns_data_id);
} }
void Module::Interface::GetNsDataIdList3(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataIdList3(Kernel::HLERequestContext& ctx) {
@ -254,29 +444,38 @@ void Module::Interface::GetNsDataIdList3(Kernel::HLERequestContext& ctx) {
const u32 start_ns_data_id = rp.Pop<u32>(); const u32 start_ns_data_id = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u16>(0); /// Actual number of output entries rb.Push<u16>(entries_count); /// Actual number of output entries
rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list. rb.Push<u16>(0); /// Last word-index copied to output in the internal NsDataId list.
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, LOG_DEBUG(Service_BOSS,
"(STUBBED) filter={:#010X}, max_entries={:#010X}, " "filter={:#010X}, max_entries={:#010X}, "
"word_index_start={:#06X}, start_ns_data_id={:#010X}", "word_index_start={:#06X}, start_ns_data_id={:#010X}",
filter, max_entries, word_index_start, start_ns_data_id); filter, max_entries, word_index_start, start_ns_data_id);
} }
void Module::Interface::SendProperty(Kernel::HLERequestContext& ctx) { void Module::Interface::SendProperty(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u16 property_id = rp.Pop<u16>(); const PropertyID property_id = static_cast<PropertyID>(rp.Pop<u16>());
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); LOG_DEBUG(Service_BOSS, "called, property_id={:#06X}, size={:#010X}", property_id, size);
rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) property_id={:#06X}, size={:#010X}", property_id, size); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
ResultCode result = RESULT_FAILED;
if (util.ReadWriteProperties(false, property_id, size, buffer)) {
result = RESULT_SUCCESS;
}
rb.Push(result);
rb.PushMappedBuffer(buffer);
} }
void Module::Interface::SendPropertyHandle(Kernel::HLERequestContext& ctx) { void Module::Interface::SendPropertyHandle(Kernel::HLERequestContext& ctx) {
@ -292,16 +491,22 @@ void Module::Interface::SendPropertyHandle(Kernel::HLERequestContext& ctx) {
void Module::Interface::ReceiveProperty(Kernel::HLERequestContext& ctx) { void Module::Interface::ReceiveProperty(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u16 property_id = rp.Pop<u16>(); const PropertyID property_id = static_cast<PropertyID>(rp.Pop<u16>());
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_BOSS, "called, property_id={:#06X}, size={:#010X}", property_id, size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(size); /// Should be actual read size
rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) property_id={:#06X}, size={:#010X}", property_id, size); ResultCode result = RESULT_FAILED;
if (util.ReadWriteProperties(true, property_id, size, buffer)) {
result = RESULT_SUCCESS;
}
rb.Push(result);
rb.Push<u32>(size); // The size of the property per id, not how much data
rb.PushMappedBuffer(buffer);
} }
void Module::Interface::UpdateTaskInterval(Kernel::HLERequestContext& ctx) { void Module::Interface::UpdateTaskInterval(Kernel::HLERequestContext& ctx) {
@ -323,6 +528,14 @@ void Module::Interface::UpdateTaskCount(Kernel::HLERequestContext& ctx) {
const u32 unk_param2 = rp.Pop<u32>(); const u32 unk_param2 = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
@ -348,6 +561,14 @@ void Module::Interface::GetTaskCount(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
}
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0); // stub 0 ( 32bit value) rb.Push<u32>(0); // stub 0 ( 32bit value)
@ -361,9 +582,13 @@ void Module::Interface::GetTaskServiceStatus(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
// Not sure what this is but it's not the task status. Maybe it's the status of the service
// after running the task?
u8 task_service_status = 1;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u8>(0); // stub 0 ( 8bit value) rb.Push<u8>(task_service_status); // stub 1 ( 8bit value) this is not taskstatus
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size);
@ -374,23 +599,46 @@ void Module::Interface::StartTask(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
if (!util.task_id_list.contains(task_id)) {
LOG_WARNING(Service_BOSS, "Task Id {} not found", task_id);
} else {
util.task_id_list[task_id].times_checked = 0;
if (const auto* url_prop = util.task_id_list[task_id].props.contains(PropertyID::Url)
? std::get_if<std::vector<u8>>(
&util.task_id_list[task_id].props[PropertyID::Url])
: nullptr;
url_prop && url_prop->size() == URL_SIZE) {
const char* url_pointer = reinterpret_cast<const char*>(url_prop->data());
std::string_view url(url_pointer, strnlen(url_pointer, URL_SIZE));
std::string_view file_name(util.task_id_list.find(task_id)->first.c_str(),
strnlen(task_id.c_str(), TASK_ID_SIZE));
util.task_id_list[task_id].download_task =
std::async(std::launch::async, DownloadBossDataFromURL, url, file_name,
util.program_id, util.extdata_id);
} else {
LOG_ERROR(Service_BOSS, "URL property is invalid");
}
}
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); LOG_DEBUG(Service_BOSS, "size={:#010X}", size);
} }
void Module::Interface::StartTaskImmediate(Kernel::HLERequestContext& ctx) { void Module::Interface::StartTaskImmediate(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); LOG_WARNING(Service_BOSS, "StartTaskImmediate called");
const u32 size = rp.Pop<u32>(); // StartTask and StartTaskImmediate do much the same thing
auto& buffer = rp.PopMappedBuffer(); StartTask(ctx);
LOG_DEBUG(Service_BOSS, "called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size);
} }
void Module::Interface::CancelTask(Kernel::HLERequestContext& ctx) { void Module::Interface::CancelTask(Kernel::HLERequestContext& ctx) {
@ -398,6 +646,14 @@ void Module::Interface::CancelTask(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
@ -418,17 +674,30 @@ void Module::Interface::GetTaskFinishHandle(Kernel::HLERequestContext& ctx) {
void Module::Interface::GetTaskState(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
const u8 state = rp.Pop<u8>(); const s8 state = rp.Pop<u8>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
TaskStatus task_status = TaskStatus::Failed;
u32 duration;
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
duration = 0;
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
std::tie(task_status, duration) = util.GetTaskStatusAndDuration(task_id, false);
}
IPC::RequestBuilder rb = rp.MakeBuilder(4, 2); IPC::RequestBuilder rb = rp.MakeBuilder(4, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u8>(0); /// TaskStatus rb.Push<u8>(static_cast<u8>(task_status)); /// TaskStatus
rb.Push<u32>(0); /// Current state value for task PropertyID 0x4 rb.Push<u32>(duration); /// Current state value for task PropertyID 0x4
rb.Push<u8>(0); /// unknown, usually 0 rb.Push<u8>(0); /// unknown, usually 0
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, state={:#06X}", size, state); LOG_DEBUG(Service_BOSS, "size={:#010X}, state={:#06X}", size, state);
} }
void Module::Interface::GetTaskResult(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskResult(Kernel::HLERequestContext& ctx) {
@ -436,14 +705,30 @@ void Module::Interface::GetTaskResult(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
TaskStatus task_status = TaskStatus::Failed;
u32 duration;
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
duration = 0;
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
std::tie(task_status, duration) = util.GetTaskStatusAndDuration(task_id, true);
}
IPC::RequestBuilder rb = rp.MakeBuilder(4, 2); IPC::RequestBuilder rb = rp.MakeBuilder(4, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u8>(0); // stub 0 (8 bit value) // This might be task_status; however it is considered a failure if
rb.Push<u32>(0); // stub 0 (32 bit value) // anything other than 0 is returned, apps won't call this method
rb.Push<u8>(0); // stub 0 (8 bit value) // unless they have previously determined the task has ended
rb.Push<u8>(static_cast<u8>(task_status));
rb.Push<u32>(duration); // return duration (number of times to check)
rb.Push<u8>(0); // stub 0 (8 bit value)
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); LOG_DEBUG(Service_BOSS, "size={:#010X}", size);
} }
void Module::Interface::GetTaskCommErrorCode(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskCommErrorCode(Kernel::HLERequestContext& ctx) {
@ -451,6 +736,17 @@ void Module::Interface::GetTaskCommErrorCode(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
if (!util.task_id_list.contains(task_id)) {
LOG_WARNING(Service_BOSS, "Could not find task_id in list");
}
}
IPC::RequestBuilder rb = rp.MakeBuilder(4, 2); IPC::RequestBuilder rb = rp.MakeBuilder(4, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0); // stub 0 (32 bit value) rb.Push<u32>(0); // stub 0 (32 bit value)
@ -468,13 +764,25 @@ void Module::Interface::GetTaskStatus(Kernel::HLERequestContext& ctx) {
const u8 unk_param3 = rp.Pop<u8>(); const u8 unk_param3 = rp.Pop<u8>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
TaskStatus task_status;
if (size > TASK_ID_SIZE) {
LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8");
task_status = TaskStatus::Failed;
} else {
std::string task_id(size, 0);
buffer.Read(task_id.data(), 0, size);
LOG_DEBUG(Service_BOSS, "Read task id {}", task_id);
task_status = util.GetTaskStatusAndDuration(task_id, false).first;
}
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u8>(0); // stub 0 (8 bit value) rb.Push<u8>(static_cast<u8>(task_status)); // return current task status
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", LOG_DEBUG(Service_BOSS, "size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", size,
size, unk_param2, unk_param3); unk_param2, unk_param3);
} }
void Module::Interface::GetTaskError(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskError(Kernel::HLERequestContext& ctx) {
@ -515,18 +823,105 @@ void Module::Interface::DeleteNsData(Kernel::HLERequestContext& ctx) {
} }
void Module::Interface::GetNsDataHeaderInfo(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataHeaderInfo(Kernel::HLERequestContext& ctx) {
NsDataHeaderInfo info{};
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u32 ns_data_id = rp.Pop<u32>(); info.ns_data_id = rp.Pop<u32>();
const u8 type = rp.Pop<u8>(); const NsDataHeaderInfoType type = static_cast<NsDataHeaderInfoType>(rp.Pop<u8>());
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
ResultCode result = RESULT_FAILED;
u32 zero = 0;
std::optional<NsDataEntry> entry = util.GetNsDataEntryFromID(info.ns_data_id);
if (entry.has_value()) {
info.program_id = entry->header.program_id;
info.datatype = entry->header.datatype;
info.payload_size = entry->header.payload_size;
info.version = entry->header.version;
switch (type) {
case NsDataHeaderInfoType::ProgramId:
if (size != sizeof(info.program_id)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&info.program_id, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS, "Wrote out program id {}", info.program_id);
break;
case NsDataHeaderInfoType::Unknown:
if (size != sizeof(u32)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&zero, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS, "Wrote out unknown as zero");
break;
case NsDataHeaderInfoType::Datatype:
if (size != sizeof(info.datatype)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&info.datatype, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS, "Wrote out content datatype {}", info.datatype);
break;
case NsDataHeaderInfoType::PayloadSize:
if (size != sizeof(info.payload_size)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&info.payload_size, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS, "Wrote out payload size {}", info.payload_size);
break;
case NsDataHeaderInfoType::NsDataId:
if (size != sizeof(info.ns_data_id)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&info.ns_data_id, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS, "Wrote out NsDataID {}", info.ns_data_id);
break;
case NsDataHeaderInfoType::Version:
if (size != sizeof(info.version)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&info.version, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS, "Wrote out version {}", info.version);
break;
case NsDataHeaderInfoType::Everything:
if (size != sizeof(info)) {
LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type);
break;
}
buffer.Write(&info, 0, size);
result = RESULT_SUCCESS;
LOG_DEBUG(Service_BOSS,
"Wrote out unknown with program id {:#018X}, unknown zero, "
"datatype {:#010X}, "
"payload size {:#010X}, NsDataID {:#010X}, version "
"{:#010X} and unknown zero",
info.program_id, info.datatype, info.payload_size, info.ns_data_id,
info.version);
break;
default:
LOG_WARNING(Service_BOSS, "Unknown header info type {}", type);
result = RESULT_FAILED;
}
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS); rb.Push(result);
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, type={:#04X}, size={:#010X}", LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, type={:#04X}, size={:#010X}", info.ns_data_id,
ns_data_id, type, size); type, size);
} }
void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) { void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) {
@ -536,14 +931,73 @@ void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) {
const u32 size = rp.Pop<u32>(); const u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer(); auto& buffer = rp.PopMappedBuffer();
// This is the error code for NsDataID not found
ResultCode result = RESULT_FAILED;
u32 read_size = 0;
FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory(
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true);
const FileSys::Path boss_path{GetBossDataDir(util.extdata_id)};
auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(size); /// Should be actual read size std::optional<NsDataEntry> entry = util.GetNsDataEntryFromID(ns_data_id);
rb.Push<u32>(0); /// unknown if (!archive_result.Succeeded() || !entry.has_value()) {
LOG_WARNING(Service_BOSS, "Opening Spotpass Extdata failed.");
rb.Push(result);
rb.Push<u32>(read_size);
rb.Push<u32>(0);
rb.PushMappedBuffer(buffer);
LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id,
offset, size);
return;
}
LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!");
auto boss_archive = std::move(archive_result).Unwrap().get();
FileSys::Path file_path = fmt::format("/{}", entry->filename);
FileSys::Mode mode{};
mode.read_flag.Assign(1);
auto file_result = boss_archive->OpenFile(file_path, mode);
if (!file_result.Succeeded()) {
LOG_WARNING(Service_BOSS, "Opening Spotpass file failed.");
rb.Push(result);
rb.Push<u32>(read_size);
rb.Push<u32>(0);
rb.PushMappedBuffer(buffer);
LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id,
offset, size);
return;
}
auto file = std::move(file_result).Unwrap();
LOG_DEBUG(Service_BOSS, "Opening Spotpass file succeeded!");
if (entry->header.payload_size < size + offset) {
LOG_WARNING(Service_BOSS,
"Request to read {:#010X} bytes at offset {:#010X}, payload "
"length is {:#010X}",
size, offset, u32(entry->header.payload_size));
rb.Push(result);
rb.Push<u32>(read_size);
rb.Push<u32>(0);
rb.PushMappedBuffer(buffer);
LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id,
offset, size);
return;
}
std::vector<u8> ns_data_array(size);
file->Read(BOSS_HEADER_LENGTH + offset, size, ns_data_array.data());
buffer.Write(ns_data_array.data(), 0, size);
result = RESULT_SUCCESS;
read_size = size;
LOG_DEBUG(Service_BOSS, "Read {:#010X} bytes from file {}", read_size, entry->filename);
rb.Push(result);
rb.Push<u32>(read_size); /// Should be actual read size
rb.Push<u32>(0); /// unknown
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id,
ns_data_id, offset, size); offset, size);
} }
void Module::Interface::SetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx) { void Module::Interface::SetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx) {
@ -571,38 +1025,46 @@ void Module::Interface::GetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx)
void Module::Interface::SetNsDataNewFlag(Kernel::HLERequestContext& ctx) { void Module::Interface::SetNsDataNewFlag(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u32 unk_param1 = rp.Pop<u32>(); const u32 ns_data_id = rp.Pop<u32>();
ns_data_new_flag = rp.Pop<u8>(); ns_data_new_flag = rp.Pop<u8>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010X}, ns_data_new_flag={:#04X}", unk_param1, LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, ns_data_new_flag={:#04X}", ns_data_id,
ns_data_new_flag); ns_data_new_flag);
} }
void Module::Interface::GetNsDataNewFlag(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataNewFlag(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u32 unk_param1 = rp.Pop<u32>(); const u32 ns_data_id = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u8>(ns_data_new_flag); rb.Push<u8>(ns_data_new_flag);
LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010X}, ns_data_new_flag={:#04X}", unk_param1, LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, ns_data_new_flag={:#04X}", ns_data_id,
ns_data_new_flag); ns_data_new_flag);
} }
void Module::Interface::GetNsDataLastUpdate(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataLastUpdate(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u32 unk_param1 = rp.Pop<u32>(); const u32 ns_data_id = rp.Pop<u32>();
u32 last_update = 0;
std::optional<NsDataEntry> entry = util.GetNsDataEntryFromID(ns_data_id);
if (entry.has_value()) {
last_update = entry->header.download_date;
LOG_DEBUG(Service_BOSS, "Last update: {}", last_update);
}
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0); // stub 0 (32bit value) rb.Push<u32>(0);
rb.Push<u32>(0); // stub 0 (32bit value) rb.Push<u32>(last_update); // return the download date from the ns data
LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010X}", unk_param1); LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}", ns_data_id);
} }
void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) { void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) {
@ -699,18 +1161,8 @@ void Module::Interface::GetTaskProperty0(Kernel::HLERequestContext& ctx) {
} }
void Module::Interface::RegisterImmediateTask(Kernel::HLERequestContext& ctx) { void Module::Interface::RegisterImmediateTask(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); RegisterTask(ctx);
const u32 size = rp.Pop<u32>(); LOG_DEBUG(Service_BOSS, "called");
const u8 unk_param2 = rp.Pop<u8>();
const u8 unk_param3 = rp.Pop<u8>();
auto& buffer = rp.PopMappedBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
rb.PushMappedBuffer(buffer);
LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}",
size, unk_param2, unk_param3);
} }
void Module::Interface::SetTaskQuery(Kernel::HLERequestContext& ctx) { void Module::Interface::SetTaskQuery(Kernel::HLERequestContext& ctx) {
@ -879,10 +1331,10 @@ void Module::Interface::SetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ct
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
LOG_WARNING( LOG_WARNING(Service_BOSS,
Service_BOSS, "(STUBBED) programID={:#018X}, unk_param1={:#010X}, "
"(STUBBED) programID={:#018X}, unk_param1={:#010X}, ns_data_new_flag_privileged={:#04X}", "ns_data_new_flag_privileged={:#04X}",
programID, unk_param1, ns_data_new_flag_privileged); programID, unk_param1, ns_data_new_flag_privileged);
} }
void Module::Interface::GetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ctx) {
@ -894,16 +1346,20 @@ void Module::Interface::GetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ct
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u8>(ns_data_new_flag_privileged); rb.Push<u8>(ns_data_new_flag_privileged);
LOG_WARNING( LOG_WARNING(Service_BOSS,
Service_BOSS, "(STUBBED) programID={:#018X}, unk_param1={:#010X}, "
"(STUBBED) programID={:#018X}, unk_param1={:#010X}, ns_data_new_flag_privileged={:#04X}", "ns_data_new_flag_privileged={:#04X}",
programID, unk_param1, ns_data_new_flag_privileged); programID, unk_param1, ns_data_new_flag_privileged);
} }
Module::Interface::Interface(std::shared_ptr<Module> boss, const char* name, u32 max_session) Module::Interface::Interface(std::shared_ptr<Module> boss, const char* name, u32 max_session)
: ServiceFramework(name, max_session), boss(std::move(boss)) {} : ServiceFramework(name, max_session), boss(std::move(boss)) {
util = Util();
this->boss->loader.ReadProgramId(util.program_id);
this->boss->loader.ReadExtdataId(util.extdata_id);
}
Module::Module(Core::System& system) { Module::Module(Core::System& system) : loader(system.GetAppLoader()) {
using namespace Kernel; using namespace Kernel;
// TODO: verify ResetType // TODO: verify ResetType
task_finish_event = task_finish_event =

View file

@ -4,10 +4,16 @@
#pragma once #pragma once
#include <future>
#include <memory> #include <memory>
#include <variant>
#include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/shared_ptr.hpp>
#include <core/loader/loader.h>
#include "core/file_sys/archive_backend.h"
#include "core/file_sys/directory_backend.h"
#include "core/global.h" #include "core/global.h"
#include "core/hle/kernel/event.h" #include "core/hle/kernel/event.h"
#include "core/hle/service/boss/spotpass_utils.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
namespace Core { namespace Core {
@ -15,10 +21,10 @@ class System;
} }
namespace Service::BOSS { namespace Service::BOSS {
class Module final { class Module final {
public: public:
explicit Module(Core::System& system); explicit Module(Core::System& system);
Loader::AppLoader& loader;
~Module() = default; ~Module() = default;
class Interface : public ServiceFramework<Interface> { class Interface : public ServiceFramework<Interface> {
@ -963,6 +969,8 @@ public:
u8 ns_data_new_flag_privileged; u8 ns_data_new_flag_privileged;
u8 output_flag; u8 output_flag;
Util util;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int) { void serialize(Archive& ar, const unsigned int) {
ar& new_arrival_flag; ar& new_arrival_flag;

View file

@ -0,0 +1,556 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#ifdef ENABLE_WEB_SERVICE
#if defined(__ANDROID__)
#include <ifaddrs.h>
#endif
#include <httplib.h>
#ifdef WIN32
// Needed to prevent conflicts with system macros when httplib is included on windows
#undef CreateEvent
#undef CreateFile
#undef ERROR_NOT_FOUND
#undef ERROR_FILE_NOT_FOUND
#undef ERROR_PATH_NOT_FOUND
#undef ERROR_ALREADY_EXISTS
#endif
#endif
#include <boost/url/src.hpp>
#include <common/common_paths.h>
#include <core/file_sys/archive_systemsavedata.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/file_backend.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/boss/boss.h"
#include "core/hw/aes/key.h"
namespace Service::BOSS {
FileSys::Path GetBossDataDir(u64 extdata_id) {
const u32 high = static_cast<u32>(extdata_id >> 32);
const u32 low = static_cast<u32>(extdata_id & 0xFFFFFFFF);
return FileSys::ConstructExtDataBinaryPath(1, high, low);
}
bool WriteBossFile(u64 extdata_id, FileSys::Path file_path, u64 size, const u8* buffer,
std::string_view description) {
FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory(
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true);
const FileSys::Path boss_path{GetBossDataDir(extdata_id)};
auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0);
if (!archive_result.Succeeded()) {
LOG_WARNING(Service_BOSS, "Extdata opening failed");
return false;
}
LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!");
auto boss_archive = std::move(archive_result).Unwrap().get();
auto file_create_result = boss_archive->CreateFile(file_path, size);
if (file_create_result.is_error) {
LOG_WARNING(Service_BOSS, "{} could not be created, it may already exist", description);
}
FileSys::Mode file_open_mode = {};
file_open_mode.write_flag.Assign(1);
auto file_result = boss_archive->OpenFile(file_path, file_open_mode);
if (!file_result.Succeeded()) {
LOG_WARNING(Service_BOSS, "Could not open {} for writing", description);
return false;
}
auto file = std::move(file_result).Unwrap();
file->Write(0, size, true, buffer);
file->Close();
return true;
}
bool SendNewsMessage(std::vector<u8> decrypted_data, const u32 payload_size, u64 extdata_id,
std::string_view file_name_str) {
// TODO: Actually add a notification to the news service
// Looks like it has some sort of header(only 0x60 bytes, datetime and unknown at 0x20
// missing?), https://www.3dbrew.org/wiki/NEWSS:AddNotification#Header_structure , then
// the message, then the image
LOG_INFO(Service_BOSS, "News message was received");
constexpr u32 news_header_size = 0x60;
constexpr u32 news_title_offset = 0x20;
constexpr u32 news_title_size = news_header_size - news_title_offset;
constexpr u32 news_message_size = 0x1780;
constexpr u32 news_total_size = news_header_size + news_message_size;
struct NewsMessage {
INSERT_PADDING_BYTES(news_title_offset);
std::array<u8, news_title_size> news_title;
std::array<u8, news_message_size> news_message;
};
static_assert(sizeof(NewsMessage) == news_total_size, "NewsMessage has incorrect size");
if (payload_size < news_total_size) {
LOG_ERROR(Service_BOSS, "Payload is too short to contain news message");
return false;
}
NewsMessage* message =
reinterpret_cast<NewsMessage*>(decrypted_data.data() + BOSS_ENTIRE_HEADER_LENGTH);
std::u16string_view news_title_string(reinterpret_cast<char16_t*>(message->news_title.data()),
news_title_size / 2);
std::u16string_view news_message_string(
reinterpret_cast<char16_t*>(message->news_message.data()), news_message_size / 2);
LOG_INFO(Service_BOSS, "News title is: {}", Common::UTF16ToUTF8(news_title_string));
LOG_INFO(Service_BOSS, "News message is:\n{}", Common::UTF16ToUTF8(news_message_string));
if (payload_size > news_total_size) {
LOG_INFO(Service_BOSS, "Image is present in news, dumping...");
if (!WriteBossFile(extdata_id, fmt::format("{}_news_image.jpg", file_name_str),
payload_size - news_total_size,
decrypted_data.data() + BOSS_ENTIRE_HEADER_LENGTH + news_total_size,
"image file")) {
LOG_WARNING(Service_BOSS, "Could not write image file");
}
}
return true;
}
bool DecryptBossData(const BossPayloadHeader* payload_header, const u32 data_size,
const u8* encrypted_data, u8* decrypted_data) {
// AES details here: https://www.3dbrew.org/wiki/SpotPass#Content_Container
// IV is data in payload + 32 bit Big Endian 1
const u32_be one = 1;
std::vector<u8> iv(sizeof(payload_header->iv_start) + sizeof(one));
std::memcpy(iv.data(), payload_header->iv_start.data(), sizeof(payload_header->iv_start));
std::memcpy(iv.data() + sizeof(payload_header->iv_start), &one, sizeof(one));
LOG_DEBUG(Service_BOSS, "IV is {:#018X}{:16X}",
static_cast<u64>(*reinterpret_cast<u64_be*>(iv.data())),
static_cast<u64>(*reinterpret_cast<u64_be*>(iv.data() + sizeof(u64_be))));
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
HW::AES::AESKey key = HW::AES::GetNormalKey(0x38);
if (key == HW::AES::AESKey{}) {
LOG_WARNING(Service_BOSS, "AES Key 0x38 not found");
return false;
}
aes.SetKeyWithIV(key.data(), CryptoPP::AES::BLOCKSIZE, iv.data());
aes.ProcessData(decrypted_data, encrypted_data, data_size);
return true;
}
bool DownloadBossDataFromURL(std::string_view url, std::string_view file_name, u64 program_id,
u64 extdata_id) {
#ifdef ENABLE_WEB_SERVICE
const auto url_parse_result = boost::urls::parse_uri(url);
const std::string file_name_str = fmt::format("/{}", file_name);
if (url_parse_result.has_error()) {
LOG_ERROR(Service_BOSS, "Invalid URL {}", url);
return false;
}
const auto url_parsed = url_parse_result.value();
const std::string scheme = url_parsed.scheme();
const std::string host = url_parsed.host();
const std::string path = url_parsed.path();
LOG_DEBUG(Service_BOSS, "Scheme is {}, host is {}, path is {}", scheme, host, path);
const std::unique_ptr<httplib::Client> client =
std::make_unique<httplib::Client>(fmt::format("{}://{}", scheme, host));
httplib::Request request{
.method = "GET",
.path = path,
// Needed when httplib is included on android
.matches = httplib::Match(),
};
client->set_follow_location(true);
client->enable_server_certificate_verification(false);
const auto result = client->send(request);
if (!result) {
LOG_ERROR(Service_BOSS, "GET to {}://{}{} returned error {}", scheme, host, path,
httplib::to_string(result.error()));
return false;
}
const auto& response = result.value();
if (response.status >= 400) {
LOG_ERROR(Service_BOSS, "GET to {}://{}{} returned error status code: {}", scheme, host,
path, response.status);
return false;
}
if (!response.headers.contains("content-type")) {
LOG_ERROR(Service_BOSS, "GET to {}://{}{} returned no content", scheme, host, path);
return false;
}
if (response.body.size() < BOSS_PAYLOAD_HEADER_LENGTH) {
LOG_WARNING(Service_BOSS, "Payload size of {} too short for boss payload",
response.body.size());
return false;
}
const BossPayloadHeader* payload_header =
reinterpret_cast<const BossPayloadHeader*>(response.body.data());
if (BOSS_MAGIC != payload_header->boss) {
LOG_WARNING(Service_BOSS, "Start of file is not '{}', it's '{}'", BOSS_MAGIC,
static_cast<u32>(payload_header->boss));
return false;
}
if (payload_header->magic != BOSS_PAYLOAD_MAGIC) {
LOG_WARNING(Service_BOSS, "Magic number mismatch, expecting {}, found {}",
BOSS_PAYLOAD_MAGIC, static_cast<u32>(payload_header->magic));
return false;
}
if (payload_header->filesize != response.body.size()) {
LOG_WARNING(Service_BOSS, "Expecting response to be size {}, actual size is {}",
static_cast<u32>(payload_header->filesize), response.body.size());
return false;
}
// Temporarily also write payload (maybe for re-implementing spotpass when it goes down in the
// future?)
if (!WriteBossFile(extdata_id, fmt::format("{}_payload", file_name_str), response.body.size(),
reinterpret_cast<const u8*>(response.body.data()), "payload file")) {
LOG_WARNING(Service_BOSS, "Could not write payload file");
}
// end payload block
const u32 data_size = payload_header->filesize - BOSS_PAYLOAD_HEADER_LENGTH;
std::vector<u8> decrypted_data(data_size);
if (!DecryptBossData(
payload_header, data_size,
reinterpret_cast<const u8*>(response.body.data() + BOSS_PAYLOAD_HEADER_LENGTH),
decrypted_data.data())) {
LOG_ERROR(Service_BOSS, "Could not decrypt payload");
return false;
}
// Temporarily also write raw data
if (!WriteBossFile(extdata_id, fmt::format("{}_raw_data", file_name_str), decrypted_data.size(),
decrypted_data.data(), "raw data file")) {
LOG_WARNING(Service_BOSS, "Could not write raw data file");
}
// end raw data block
if (decrypted_data.size() < BOSS_ENTIRE_HEADER_LENGTH) {
LOG_WARNING(Service_BOSS, "Payload size to small to be boss data: {}",
decrypted_data.size());
return false;
}
BossHeader header{};
std::memcpy(&header.program_id, decrypted_data.data() + BOSS_CONTENT_HEADER_LENGTH,
BOSS_HEADER_LENGTH - BOSS_EXTDATA_HEADER_LENGTH);
const u32 payload_size = static_cast<u32>(decrypted_data.size() - BOSS_ENTIRE_HEADER_LENGTH);
if (header.payload_size != payload_size) {
LOG_WARNING(Service_BOSS, "Payload has incorrect size, was expecting {}, found {}",
static_cast<u32>(header.payload_size), payload_size);
return false;
}
if (program_id != header.program_id) {
LOG_WARNING(Service_BOSS, "Mismatched program id, was expecting {:#018X}, found {:#018X}",
program_id, u64(header.program_id));
if (header.program_id == NEWS_PROG_ID) {
SendNewsMessage(decrypted_data, payload_size, extdata_id, file_name_str);
}
return false;
}
std::vector<u8> data_file(BOSS_HEADER_LENGTH + payload_size);
header.header_length = BOSS_EXTDATA_HEADER_LENGTH;
std::memcpy(data_file.data(), &header, BOSS_HEADER_LENGTH);
std::memcpy(data_file.data() + BOSS_HEADER_LENGTH,
decrypted_data.data() + BOSS_ENTIRE_HEADER_LENGTH, payload_size);
if (!WriteBossFile(extdata_id, file_name_str, data_file.size(), data_file.data(),
"spotpass file")) {
LOG_WARNING(Service_BOSS, "Could not write spotpass file");
}
return true;
#else
LOG_ERROR(Service_BOSS, "Cannot download data as web services are not enabled");
return false;
#endif
}
std::vector<NsDataEntry> Util::GetNsDataEntries() {
std::vector<NsDataEntry> ns_data;
std::vector<FileSys::Entry> boss_files = GetBossExtDataFiles();
FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory(
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true);
const FileSys::Path boss_path{GetBossDataDir(extdata_id)};
auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0);
if (!archive_result.Succeeded()) {
LOG_ERROR(Service_BOSS, "Extdata opening failed");
return ns_data;
}
LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!");
auto boss_archive = std::move(archive_result).Unwrap().get();
for (const auto& cur_file : boss_files) {
if (cur_file.is_directory || cur_file.file_size < BOSS_HEADER_LENGTH) {
LOG_WARNING(Service_BOSS, "Spotpass extdata contains directory or file is too short");
continue;
}
NsDataEntry entry{};
entry.filename = Common::UTF16ToUTF8(cur_file.filename);
const FileSys::Path file_path = fmt::format("/{}", entry.filename);
LOG_DEBUG(Service_BOSS, "Spotpass filename={}", entry.filename);
FileSys::Mode mode{};
mode.read_flag.Assign(1);
auto file_result = boss_archive->OpenFile(file_path, mode);
if (!file_result.Succeeded()) {
LOG_WARNING(Service_BOSS, "Opening Spotpass file failed.");
continue;
}
auto file = std::move(file_result).Unwrap();
LOG_DEBUG(Service_BOSS, "Opening Spotpass file succeeded!");
file->Read(0, BOSS_HEADER_LENGTH, reinterpret_cast<u8*>(&entry.header));
// Extdata header should have size 0x18:
// https://www.3dbrew.org/wiki/SpotPass#Payload_Content_Header
if (entry.header.header_length != BOSS_EXTDATA_HEADER_LENGTH) {
LOG_WARNING(Service_BOSS,
"Incorrect header length or non-spotpass file; expected {:#010X}, "
"found {:#010X}",
BOSS_EXTDATA_HEADER_LENGTH, entry.header.header_length);
continue;
}
if (entry.header.program_id != program_id) {
LOG_WARNING(Service_BOSS,
"Mismatched program ID in spotpass data. Was expecting "
"{:#018X}, found {:#018X}",
program_id, u64(entry.header.program_id));
continue;
}
// Check the payload size is correct, excluding header
if (entry.header.payload_size != cur_file.file_size - BOSS_HEADER_LENGTH) {
LOG_WARNING(Service_BOSS,
"Mismatched file size, was expecting {:#010X}, found {:#010X}",
u32(entry.header.payload_size), cur_file.file_size - BOSS_HEADER_LENGTH);
continue;
}
LOG_DEBUG(
Service_BOSS, "Datatype is {:#010X}, Payload size is {:#010X}, NsDataID is {:#010X}",
static_cast<u32>(entry.header.datatype), static_cast<u32>(entry.header.payload_size),
static_cast<u32>(entry.header.ns_data_id));
ns_data.push_back(entry);
}
return ns_data;
}
std::vector<FileSys::Entry> Util::GetBossExtDataFiles() {
std::vector<FileSys::Entry> boss_files;
FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory(
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true);
const FileSys::Path boss_path{GetBossDataDir(extdata_id)};
auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0);
if (!archive_result.Succeeded()) {
LOG_WARNING(Service_BOSS, "Extdata opening failed");
return boss_files;
}
LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!");
auto boss_archive = std::move(archive_result).Unwrap().get();
auto dir_result = boss_archive->OpenDirectory(DIR_SEP);
if (!dir_result.Succeeded()) {
LOG_WARNING(Service_BOSS, "Extdata directory opening failed");
return boss_files;
}
LOG_DEBUG(Service_BOSS, "Spotpass Extdata directory opened successfully!");
const auto dir = std::move(dir_result).Unwrap();
// Keep reading the directory 32 files at a time until all files have been checked
constexpr u32 files_to_read = 32;
u32 entry_count = 0;
size_t i = 0;
do {
boss_files.resize(boss_files.size() + files_to_read);
entry_count = dir->Read(files_to_read, boss_files.data() + (i * files_to_read));
} while (files_to_read <= entry_count && ++i);
LOG_DEBUG(Service_BOSS, "Spotpass Extdata directory contains {} files",
i * files_to_read + entry_count);
boss_files.resize(i * files_to_read + entry_count);
return boss_files;
}
u16 Util::GetOutputEntries(u32 filter, u32 max_entries, Kernel::MappedBuffer& buffer) {
std::vector<NsDataEntry> ns_data = GetNsDataEntries();
std::vector<u32> output_entries;
for (const auto& cur_entry : ns_data) {
const u16 datatype_high =
static_cast<u16>(static_cast<u32>(cur_entry.header.datatype) >> 16);
const u16 datatype_low =
static_cast<u16>(static_cast<u32>(cur_entry.header.datatype) & 0xFFFF);
const u16 filter_high = static_cast<u16>(filter >> 16);
const u16 filter_low = static_cast<u16>(filter & 0xFFFF);
if (filter != 0xFFFFFFFF &&
(filter_high != datatype_high || (filter_low & datatype_low) == 0)) {
LOG_DEBUG(
Service_BOSS,
"Filtered out NsDataID {:#010X}; failed filter {:#010X} with datatype {:#010X}",
static_cast<u32>(cur_entry.header.ns_data_id), filter,
static_cast<u32>(cur_entry.header.datatype));
continue;
}
if (output_entries.size() >= max_entries) {
LOG_WARNING(Service_BOSS, "Reached maximum number of entries");
break;
}
output_entries.push_back(cur_entry.header.ns_data_id);
}
buffer.Write(output_entries.data(), 0, sizeof(u32) * output_entries.size());
LOG_DEBUG(Service_BOSS, "{} usable entries returned", output_entries.size());
return static_cast<u16>(output_entries.size());
}
std::pair<TaskStatus, u32> Util::GetTaskStatusAndDuration(std::string task_id,
bool wait_on_result) {
// Default duration is zero -> means no more runs of the task are allowed
u32 duration = 0;
if (!task_id_list.contains(task_id)) {
LOG_WARNING(Service_BOSS, "Could not find task_id in list");
return {TaskStatus::Failed, duration};
}
LOG_DEBUG(Service_BOSS, "Found currently running task id");
task_id_list[task_id].times_checked++;
// Get the duration from the task if available
if (const auto* dur_prop =
task_id_list[task_id].props.contains(PropertyID::Duration)
? std::get_if<u32>(&task_id_list[task_id].props[PropertyID::Duration])
: nullptr;
dur_prop) {
duration = *dur_prop;
}
if (task_id_list[task_id].download_task.valid()) {
LOG_DEBUG(Service_BOSS, "Task is still running");
auto status = task_id_list[task_id].download_task.wait_for(std::chrono::microseconds(0));
if (status == std::future_status::ready || wait_on_result) {
LOG_DEBUG(Service_BOSS,
wait_on_result ? "Waiting for result..." : "Task just finished");
task_id_list[task_id].task_result = task_id_list[task_id].download_task.get();
if (task_id_list[task_id].task_result) {
LOG_DEBUG(Service_BOSS, "Task ran successfully");
return {TaskStatus::Success, duration};
}
LOG_WARNING(Service_BOSS, "Task failed");
return {TaskStatus::Failed, duration};
}
LOG_DEBUG(Service_BOSS, "Task is still running");
return {TaskStatus::Running, duration};
}
LOG_DEBUG(Service_BOSS, "Task has finished running or is invalid");
if (task_id_list[task_id].task_result) {
LOG_DEBUG(Service_BOSS, "Task ran successfully");
return {TaskStatus::Success, duration};
}
LOG_WARNING(Service_BOSS, "Task failed");
return {TaskStatus::Failed, duration};
}
std::optional<NsDataEntry> Util::GetNsDataEntryFromID(u32 ns_data_id) {
std::vector<NsDataEntry> ns_data = GetNsDataEntries();
const auto entry_iter = std::find_if(ns_data.begin(), ns_data.end(), [ns_data_id](auto entry) {
return entry.header.ns_data_id == ns_data_id;
});
if (entry_iter == ns_data.end()) {
LOG_WARNING(Service_BOSS, "Could not find NsData with ID {:#010X}", ns_data_id);
return std::nullopt;
}
return *entry_iter;
}
bool Util::ReadWriteProperties(bool write, PropertyID property_id, u32 size,
Kernel::MappedBuffer& buffer) {
if (!cur_props.props.contains(property_id)) {
LOG_ERROR(Service_BOSS, "Unknown property with id {:#06X}", property_id);
return false;
}
auto& prop = cur_props.props[property_id];
auto readwrite_pod = [&]<typename T>(T& cur_prop) {
static_assert(std::is_trivial<T>::value,
"Only trivial types are allowed for readwrite_pod");
if (size != sizeof(cur_prop)) {
LOG_ERROR(Service_BOSS, "Unexpected size of property {:#06x}, was expecting {}, got {}",
property_id, sizeof(cur_prop), size);
}
if (write) {
buffer.Write(&cur_prop, 0, size);
} else {
T new_prop = 0;
buffer.Read(&new_prop, 0, size);
prop = new_prop;
LOG_DEBUG(Service_BOSS, "Read property {:#06X}, value {:#010X}", property_id, new_prop);
}
};
auto readwrite_vector = [&]<typename T>(std::vector<T>& cur_prop) {
if (size != cur_prop.size() * sizeof(T)) {
LOG_ERROR(Service_BOSS, "Unexpected size of property {:#06x}, was expecting {}, got {}",
property_id, cur_prop.size(), size);
}
LOG_DEBUG(Service_BOSS,
"Vector property contains {} elements of size {} for a total size of {}",
cur_prop.size(), sizeof(T), size);
if (write) {
buffer.Write(cur_prop.data(), 0, size);
} else {
std::vector<T> new_prop(cur_prop.size());
buffer.Read(new_prop.data(), 0, size);
prop = new_prop;
if (sizeof(T) == sizeof(u8)) {
LOG_DEBUG(Service_BOSS, "Read property {:#06X}, value {}", property_id,
std::string_view(reinterpret_cast<char*>(new_prop.data()), size));
} else if (sizeof(T) == sizeof(u32) && new_prop.size() == CERTIDLIST_SIZE) {
LOG_DEBUG(Service_BOSS, "Read property {:#06X}, values {:#010X},{:#010X},{:#010X}",
property_id, new_prop[0], new_prop[1], new_prop[2]);
}
}
};
if (const auto char_prop = std::get_if<u8>(&prop))
readwrite_pod(*char_prop);
else if (const auto short_prop = std::get_if<u16>(&prop))
readwrite_pod(*short_prop);
else if (const auto int_prop = std::get_if<u32>(&prop))
readwrite_pod(*int_prop);
else if (const auto charvec_prop = std::get_if<std::vector<u8>>(&prop))
readwrite_vector(*charvec_prop);
else if (const auto intvec_prop = std::get_if<std::vector<u32>>(&prop))
readwrite_vector(*intvec_prop);
return true;
}
} // namespace Service::BOSS

View file

@ -0,0 +1,212 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <future>
#include <memory>
#include <variant>
#include <boost/serialization/shared_ptr.hpp>
#include <core/loader/loader.h>
#include "core/file_sys/archive_backend.h"
#include "core/file_sys/directory_backend.h"
#include "core/global.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/service.h"
namespace Service::BOSS {
// File header info from
// https://www.3dbrew.org/wiki/SpotPass#Payload_Content_Header
// So the total header is only 52 bytes long
constexpr u32 BOSS_HEADER_LENGTH = 0x34;
// 52 bytes doesn't align nicely into 8-byte words
#pragma pack(push, 4)
struct BossHeader {
u8 header_length;
INSERT_PADDING_BYTES(11);
u32_be unknown;
u32_be download_date;
INSERT_PADDING_BYTES(4);
u64_be program_id;
INSERT_PADDING_BYTES(4);
u32_be datatype;
u32_be payload_size;
u32_be ns_data_id;
u32_be version;
};
#pragma pack(pop)
static_assert(sizeof(BossHeader) == 0x34, "BossHeader has incorrect size");
// Payload header info from
// https://www.3dbrew.org/wiki/SpotPass#Content_Container
// So the total header is only 40 bytes long
constexpr u32 BOSS_PAYLOAD_HEADER_LENGTH = 0x28;
constexpr u32 BOSS_MAGIC = Loader::MakeMagic('b', 'o', 's', 's');
constexpr u32 BOSS_PAYLOAD_MAGIC = 0x10001;
constexpr u64 NEWS_PROG_ID = 0x0004013000003502;
// 40 bytes doesn't align nicely into 8-byte words either
#pragma pack(push, 4)
struct BossPayloadHeader {
u32_le boss;
u32_be magic;
u32_be filesize;
u64_be release_date;
u16_be one;
INSERT_PADDING_BYTES(2);
u16_be hash_type;
u16_be rsa_size;
std::array<u8, 0xC> iv_start;
};
#pragma pack(pop)
static_assert(sizeof(BossPayloadHeader) == 0x28, "BossPayloadHeader has incorrect size");
constexpr u32 BOSS_CONTENT_HEADER_LENGTH = 0x132;
constexpr u32 BOSS_HEADER_WITH_HASH_LENGTH = 0x13C;
constexpr u32 BOSS_ENTIRE_HEADER_LENGTH = BOSS_CONTENT_HEADER_LENGTH + BOSS_HEADER_WITH_HASH_LENGTH;
constexpr u32 BOSS_EXTDATA_HEADER_LENGTH = 0x18;
constexpr u32 BOSS_A_ENTRY_SIZE = 0x800;
constexpr u32 BOSS_S_ENTRY_SIZE = 0xC00;
constexpr u32 BOSS_SAVE_HEADER_SIZE = 4;
constexpr u32 BOSS_S_PROG_ID_OFFSET = 0x10;
constexpr u32 BOSS_S_TASK_ID_OFFSET = 0x18;
constexpr u32 BOSS_S_URL_OFFSET = 0x21C;
struct NsDataEntry {
std::string filename;
BossHeader header;
};
constexpr ResultCode RESULT_FAILED(1);
constexpr u8 TASK_ID_SIZE = 8;
enum class NsDataHeaderInfoType : u8 {
ProgramId,
Unknown,
Datatype,
PayloadSize,
NsDataId,
Version,
Everything,
};
struct NsDataHeaderInfo {
u64 program_id;
INSERT_PADDING_BYTES(4);
u32 datatype;
u32 payload_size;
u32 ns_data_id;
u32 version;
INSERT_PADDING_BYTES(4);
};
static_assert(sizeof(NsDataHeaderInfo) == 0x20, "NsDataHeaderInfo has incorrect size");
enum class TaskStatus : u8 {
Success = 0,
Running = 2,
NotStarted = 5,
Failed = 7,
};
enum class PropertyID : u16 {
Interval = 0x03,
Duration = 0x04,
Url = 0x07,
Headers = 0x0D,
CertId = 0x0E,
CertIdList = 0x0F,
LoadCert = 0x10,
LoadRootCert = 0x11,
TotalTasks = 0x35,
TaskIdList = 0x36,
};
constexpr size_t URL_SIZE = 0x200;
constexpr size_t HEADERS_SIZE = 0x360;
constexpr size_t CERTIDLIST_SIZE = 3;
constexpr size_t TASKIDLIST_SIZE = 0x400;
struct BossTaskProperties {
std::future<bool> download_task;
bool task_result;
u32 times_checked;
std::map<PropertyID, std::variant<u8, u16, u32, std::vector<u8>, std::vector<u32>>> props{
{static_cast<PropertyID>(0x00), u8()},
{static_cast<PropertyID>(0x01), u8()},
{static_cast<PropertyID>(0x02), u32()},
// interval
{PropertyID::Interval, u32()},
// duration
{PropertyID::Duration, u32()},
{static_cast<PropertyID>(0x05), u8()},
{static_cast<PropertyID>(0x06), u8()},
// url
{PropertyID::Url, std::vector<u8>(URL_SIZE)},
{static_cast<PropertyID>(0x08), u32()},
{static_cast<PropertyID>(0x09), u8()},
{static_cast<PropertyID>(0x0A), std::vector<u8>(0x100)},
{static_cast<PropertyID>(0x0B), std::vector<u8>(0x200)},
{static_cast<PropertyID>(0x0C), u32()},
// headers
{PropertyID::Headers, std::vector<u8>(HEADERS_SIZE)},
// certid
{PropertyID::CertId, u32()},
// certidlist
{PropertyID::CertIdList, std::vector<u32>(CERTIDLIST_SIZE)},
// loadcert (bool)
{PropertyID::LoadCert, u8()},
// loadrootcert (bool)
{PropertyID::LoadRootCert, u8()},
{static_cast<PropertyID>(0x12), u8()},
{static_cast<PropertyID>(0x13), u32()},
{static_cast<PropertyID>(0x14), u32()},
{static_cast<PropertyID>(0x15), std::vector<u8>(0x40)},
{static_cast<PropertyID>(0x16), u32()},
{static_cast<PropertyID>(0x18), u8()},
{static_cast<PropertyID>(0x19), u8()},
{static_cast<PropertyID>(0x1A), u8()},
{static_cast<PropertyID>(0x1B), u32()},
{static_cast<PropertyID>(0x1C), u32()},
// totaltasks
{PropertyID::TotalTasks, u16()},
// taskidlist
{PropertyID::TaskIdList, std::vector<u8>(TASKIDLIST_SIZE)},
{static_cast<PropertyID>(0x3B), u32()},
{static_cast<PropertyID>(0x3E), std::vector<u8>(0x200)},
{static_cast<PropertyID>(0x3F), u8()},
};
};
constexpr std::array<u8, 8> BOSS_SYSTEM_SAVEDATA_ID{
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x01, 0x00,
};
constexpr std::array<u8, 4> BOSS_SYSTEM_SAVEDATA_HEADER{0x00, 0x80, 0x34, 0x12};
FileSys::Path GetBossDataDir(u64 extdata_id);
bool DownloadBossDataFromURL(std::string_view url, std::string_view file_name, u64 program_id,
u64 extdata_id);
class Util {
public:
std::vector<NsDataEntry> GetNsDataEntries();
std::vector<FileSys::Entry> GetBossExtDataFiles();
u16 GetOutputEntries(u32 filter, u32 max_entries, Kernel::MappedBuffer& buffer);
bool ReadWriteProperties(bool write, PropertyID property_id, u32 size,
Kernel::MappedBuffer& buffer);
std::optional<NsDataEntry> GetNsDataEntryFromID(u32 ns_data_id);
std::pair<TaskStatus, u32> GetTaskStatusAndDuration(std::string task_id, bool wait_on_result);
public:
u64 program_id;
u64 extdata_id;
std::map<std::string, BossTaskProperties> task_id_list;
BossTaskProperties cur_props;
};
} // namespace Service::BOSS

View file

@ -353,6 +353,10 @@ void ArchiveManager::RegisterArchiveTypes() {
std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(sdmc_directory, false); std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(sdmc_directory, false);
RegisterArchiveType(std::move(extsavedata_factory), ArchiveIdCode::ExtSaveData); RegisterArchiveType(std::move(extsavedata_factory), ArchiveIdCode::ExtSaveData);
auto bossextsavedata_factory =
std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(sdmc_directory, false, true);
RegisterArchiveType(std::move(bossextsavedata_factory), ArchiveIdCode::BossExtSaveData);
auto sharedextsavedata_factory = auto sharedextsavedata_factory =
std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(nand_directory, true); std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(nand_directory, true);
RegisterArchiveType(std::move(sharedextsavedata_factory), ArchiveIdCode::SharedExtSaveData); RegisterArchiveType(std::move(sharedextsavedata_factory), ArchiveIdCode::SharedExtSaveData);

View file

@ -37,6 +37,7 @@ enum class ArchiveIdCode : u32 {
SaveData = 0x00000004, SaveData = 0x00000004,
ExtSaveData = 0x00000006, ExtSaveData = 0x00000006,
SharedExtSaveData = 0x00000007, SharedExtSaveData = 0x00000007,
BossExtSaveData = 0x12345678,
SystemSaveData = 0x00000008, SystemSaveData = 0x00000008,
SDMC = 0x00000009, SDMC = 0x00000009,
SDMCWriteOnly = 0x0000000A, SDMCWriteOnly = 0x0000000A,