diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt index fb2ba3588..db40f06cb 100644 --- a/externals/cryptopp/CMakeLists.txt +++ b/externals/cryptopp/CMakeLists.txt @@ -131,6 +131,7 @@ set(cryptopp_SOURCES cryptopp/algparam.cpp cryptopp/asn.cpp cryptopp/authenc.cpp + cryptopp/base64.cpp cryptopp/basecode.cpp cryptopp/ccm.cpp cryptopp/crc-simd.cpp diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp index b3434c6dd..860be4e35 100644 --- a/src/core/hle/service/cecd/cecd.cpp +++ b/src/core/hle/service/cecd/cecd.cpp @@ -2,22 +2,690 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include +#include "common/file_util.h" #include "common/logging/log.h" +#include "common/string_util.h" +#include "core/file_sys/archive_systemsavedata.h" +#include "core/file_sys/directory_backend.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/file_backend.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" #include "core/hle/result.h" #include "core/hle/service/cecd/cecd.h" #include "core/hle/service/cecd/cecd_ndm.h" #include "core/hle/service/cecd/cecd_s.h" #include "core/hle/service/cecd/cecd_u.h" +#include "fmt/format.h" namespace Service::CECD { -void Module::Interface::GetCecStateAbbreviated(Kernel::HLERequestContext& ctx) { +using CecDataPathType = Module::CecDataPathType; +using CecOpenMode = Module::CecOpenMode; +using CecSystemInfoType = Module::CecSystemInfoType; + +void Module::Interface::Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x01, 3, 2); + const u32 ncch_program_id = rp.Pop(); + const CecDataPathType path_type = rp.PopEnum(); + CecOpenMode open_mode; + open_mode.raw = rp.Pop(); + rp.PopPID(); + + FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data()); + FileSys::Mode mode; + mode.read_flag.Assign(1); + mode.write_flag.Assign(1); + mode.create_flag.Assign(1); + + SessionData* session_data = GetSessionData(ctx.Session()); + session_data->ncch_program_id = ncch_program_id; + session_data->open_mode.raw = open_mode.raw; + session_data->data_path_type = path_type; + session_data->path = path; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + switch (path_type) { + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: { + auto dir_result = + Service::FS::OpenDirectoryFromArchive(cecd->cecd_system_save_data_archive, path); + if (dir_result.Failed()) { + if (open_mode.create) { + Service::FS::CreateDirectoryFromArchive(cecd->cecd_system_save_data_archive, path); + rb.Push(RESULT_SUCCESS); + } else { + LOG_DEBUG(Service_CECD, "Failed to open directory: {}", path.AsString()); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, + ErrorSummary::NotFound, ErrorLevel::Status)); + } + rb.Push(0); // Zero entries + } else { + constexpr u32 max_entries = 32; // reasonable value, just over max boxes 24 + auto directory = dir_result.Unwrap(); + + // Actual reading into vector seems to be required for entry count + std::vector entries(max_entries); + const u32 entry_count = directory->backend->Read(max_entries, entries.data()); + + LOG_DEBUG(Service_CECD, "Number of entries found: {}", entry_count); + + rb.Push(RESULT_SUCCESS); + rb.Push(entry_count); // Entry count + directory->backend->Close(); + } + break; + } + default: { // If not directory, then it is a file + auto file_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, path, mode); + if (file_result.Failed()) { + LOG_DEBUG(Service_CECD, "Failed to open file: {}", path.AsString()); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + rb.Push(0); // No file size + } else { + session_data->file = std::move(file_result.Unwrap()); + rb.Push(RESULT_SUCCESS); + rb.Push(session_data->file->backend->GetSize()); // Return file size + } + + if (path_type == CecDataPathType::MboxProgramId) { + std::vector program_id(8); + u64_le le_program_id = Kernel::g_current_process->codeset->program_id; + std::memcpy(program_id.data(), &le_program_id, sizeof(u64)); + session_data->file->backend->Write(0, sizeof(u64), true, program_id.data()); + session_data->file->backend->Close(); + } + } + } + + LOG_DEBUG(Service_CECD, + "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, " + "open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}", + ncch_program_id, static_cast(path_type), path.AsString(), open_mode.raw, + open_mode.unknown, open_mode.read, open_mode.write, open_mode.create, + open_mode.check); +} + +void Module::Interface::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x02, 1, 2); + const u32 write_buffer_size = rp.Pop(); + auto& write_buffer = rp.PopMappedBuffer(); + + SessionData* session_data = GetSessionData(ctx.Session()); + LOG_DEBUG(Service_CECD, + "SessionData: ncch_program_id={:#010x}, data_path_type={:#04x}, " + "path={}, open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}", + session_data->ncch_program_id, static_cast(session_data->data_path_type), + session_data->path.AsString(), session_data->open_mode.raw, + session_data->open_mode.unknown, session_data->open_mode.read, + session_data->open_mode.write, session_data->open_mode.create, + session_data->open_mode.check); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + switch (session_data->data_path_type) { + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC, + ErrorSummary::NotFound, ErrorLevel::Status)); + rb.Push(0); // No bytes read + break; + default: // If not directory, then it is a file + std::vector buffer(write_buffer_size); + const u32 bytes_read = + session_data->file->backend->Read(0, write_buffer_size, buffer.data()).Unwrap(); + + write_buffer.Write(buffer.data(), 0, write_buffer_size); + session_data->file->backend->Close(); + + rb.Push(RESULT_SUCCESS); + rb.Push(bytes_read); + } + rb.PushMappedBuffer(write_buffer); + + LOG_DEBUG(Service_CECD, "called, write_buffer_size={:#x}, path={}", write_buffer_size, + session_data->path.AsString()); +} + +void Module::Interface::ReadMessage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x03, 4, 4); + const u32 ncch_program_id = rp.Pop(); + const bool is_outbox = rp.Pop(); + const u32 message_id_size = rp.Pop(); + const u32 buffer_size = rp.Pop(); + auto& message_id_buffer = rp.PopMappedBuffer(); + auto& write_buffer = rp.PopMappedBuffer(); + + FileSys::Mode mode; + mode.read_flag.Assign(1); + + std::vector id_buffer(message_id_size); + message_id_buffer.Read(id_buffer.data(), 0, message_id_size); + + FileSys::Path message_path = + cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg + : CecDataPathType::InboxMsg, + ncch_program_id, id_buffer) + .data(); + + auto message_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, message_path, mode); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 4); + if (message_result.Succeeded()) { + auto message = message_result.Unwrap(); + std::vector buffer(buffer_size); + + const u32 bytes_read = message->backend->Read(0, buffer_size, buffer.data()).Unwrap(); + write_buffer.Write(buffer.data(), 0, buffer_size); + message->backend->Close(); + + CecMessageHeader msg_header; + std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader)); + + LOG_DEBUG(Service_CECD, + "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, " + "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, " + "batch_id={:#010x}", + msg_header.magic, msg_header.message_size, msg_header.header_size, + msg_header.body_size, msg_header.title_id, msg_header.title_id2, + msg_header.batch_id); + LOG_DEBUG(Service_CECD, + "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, " + "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, " + "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, " + "forward_count={:#04x}, user_data={:#06x}, ", + msg_header.unknown_id, msg_header.version, msg_header.flag, + msg_header.send_method, msg_header.is_unopen, msg_header.is_new, + msg_header.sender_id, msg_header.sender_id2, msg_header.send_count, + msg_header.forward_count, msg_header.user_data); + + rb.Push(RESULT_SUCCESS); + rb.Push(bytes_read); + } else { + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + rb.Push(0); // zero bytes read + } + rb.PushMappedBuffer(message_id_buffer); + rb.PushMappedBuffer(write_buffer); + + LOG_DEBUG( + Service_CECD, + "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}", + ncch_program_id, is_outbox, message_id_size, buffer_size); +} + +void Module::Interface::ReadMessageWithHMAC(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x04, 4, 6); + const u32 ncch_program_id = rp.Pop(); + const bool is_outbox = rp.Pop(); + const u32 message_id_size = rp.Pop(); + const u32 buffer_size = rp.Pop(); + auto& message_id_buffer = rp.PopMappedBuffer(); + auto& hmac_key_buffer = rp.PopMappedBuffer(); + auto& write_buffer = rp.PopMappedBuffer(); + + FileSys::Mode mode; + mode.read_flag.Assign(1); + + std::vector id_buffer(message_id_size); + message_id_buffer.Read(id_buffer.data(), 0, message_id_size); + + FileSys::Path message_path = + cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg + : CecDataPathType::InboxMsg, + ncch_program_id, id_buffer) + .data(); + + auto message_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, message_path, mode); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 6); + if (message_result.Succeeded()) { + auto message = message_result.Unwrap(); + std::vector buffer(buffer_size); + + const u32 bytes_read = message->backend->Read(0, buffer_size, buffer.data()).Unwrap(); + write_buffer.Write(buffer.data(), 0, buffer_size); + message->backend->Close(); + + CecMessageHeader msg_header; + std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader)); + + LOG_DEBUG(Service_CECD, + "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, " + "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, " + "batch_id={:#010x}", + msg_header.magic, msg_header.message_size, msg_header.header_size, + msg_header.body_size, msg_header.title_id, msg_header.title_id2, + msg_header.batch_id); + LOG_DEBUG(Service_CECD, + "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, " + "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, " + "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, " + "forward_count={:#04x}, user_data={:#06x}, ", + msg_header.unknown_id, msg_header.version, msg_header.flag, + msg_header.send_method, msg_header.is_unopen, msg_header.is_new, + msg_header.sender_id, msg_header.sender_id2, msg_header.send_count, + msg_header.forward_count, msg_header.user_data); + + std::vector hmac_digest(0x20); + std::memcpy(hmac_digest.data(), + buffer.data() + msg_header.header_size + msg_header.body_size, 0x20); + + std::vector message_body(msg_header.body_size); + std::memcpy(message_body.data(), buffer.data() + msg_header.header_size, + msg_header.body_size); + + using namespace CryptoPP; + SecByteBlock key(0x20); + hmac_key_buffer.Read(key.data(), 0, key.size()); + + HMAC hmac(key, key.size()); + + const bool verify_hmac = + hmac.VerifyDigest(hmac_digest.data(), message_body.data(), message_body.size()); + + if (verify_hmac) + LOG_DEBUG(Service_CECD, "Verification succeeded"); + else + LOG_DEBUG(Service_CECD, "Verification failed"); + + rb.Push(RESULT_SUCCESS); + rb.Push(bytes_read); + } else { + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + rb.Push(0); // zero bytes read + } + + rb.PushMappedBuffer(message_id_buffer); + rb.PushMappedBuffer(hmac_key_buffer); + rb.PushMappedBuffer(write_buffer); + + LOG_DEBUG( + Service_CECD, + "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}", + ncch_program_id, is_outbox, message_id_size, buffer_size); +} + +void Module::Interface::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x05, 1, 2); + const u32 read_buffer_size = rp.Pop(); + auto& read_buffer = rp.PopMappedBuffer(); + + SessionData* session_data = GetSessionData(ctx.Session()); + LOG_DEBUG(Service_CECD, + "SessionData: ncch_program_id={:#010x}, data_path_type={:#04x}, " + "path={}, open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}", + session_data->ncch_program_id, static_cast(session_data->data_path_type), + session_data->path.AsString(), session_data->open_mode.raw, + session_data->open_mode.unknown, session_data->open_mode.read, + session_data->open_mode.write, session_data->open_mode.create, + session_data->open_mode.check); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + switch (session_data->data_path_type) { + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC, + ErrorSummary::NotFound, ErrorLevel::Status)); + break; + default: // If not directory, then it is a file + std::vector buffer(read_buffer_size); + read_buffer.Read(buffer.data(), 0, read_buffer_size); + + if (session_data->file->backend->GetSize() != read_buffer_size) { + session_data->file->backend->SetSize(read_buffer_size); + } + + if (session_data->open_mode.check) { + cecd->CheckAndUpdateFile(session_data->data_path_type, session_data->ncch_program_id, + buffer); + } + + const u32 bytes_written = + session_data->file->backend->Write(0, buffer.size(), true, buffer.data()).Unwrap(); + session_data->file->backend->Close(); + + rb.Push(RESULT_SUCCESS); + } + rb.PushMappedBuffer(read_buffer); + + LOG_DEBUG(Service_CECD, "called, read_buffer_size={:#x}", read_buffer_size); +} + +void Module::Interface::WriteMessage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x06, 4, 4); + const u32 ncch_program_id = rp.Pop(); + const bool is_outbox = rp.Pop(); + const u32 message_id_size = rp.Pop(); + const u32 buffer_size = rp.Pop(); + auto& read_buffer = rp.PopMappedBuffer(); + auto& message_id_buffer = rp.PopMappedBuffer(); + + FileSys::Mode mode; + mode.write_flag.Assign(1); + mode.create_flag.Assign(1); + + std::vector id_buffer(message_id_size); + message_id_buffer.Read(id_buffer.data(), 0, message_id_size); + + FileSys::Path message_path = + cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg + : CecDataPathType::InboxMsg, + ncch_program_id, id_buffer) + .data(); + + auto message_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, message_path, mode); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + if (message_result.Succeeded()) { + auto message = message_result.Unwrap(); + + std::vector buffer(buffer_size); + read_buffer.Read(buffer.data(), 0, buffer_size); + + CecMessageHeader msg_header; + std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader)); + + LOG_DEBUG(Service_CECD, + "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, " + "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, " + "batch_id={:#010x}", + msg_header.magic, msg_header.message_size, msg_header.header_size, + msg_header.body_size, msg_header.title_id, msg_header.title_id2, + msg_header.batch_id); + LOG_DEBUG(Service_CECD, + "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, " + "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, " + "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, " + "forward_count={:#04x}, user_data={:#06x}, ", + msg_header.unknown_id, msg_header.version, msg_header.flag, + msg_header.send_method, msg_header.is_unopen, msg_header.is_new, + msg_header.sender_id, msg_header.sender_id2, msg_header.send_count, + msg_header.forward_count, msg_header.user_data); + + const u32 bytes_written = + message->backend->Write(0, buffer_size, true, buffer.data()).Unwrap(); + message->backend->Close(); + + rb.Push(RESULT_SUCCESS); + } else { + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + } + + rb.PushMappedBuffer(read_buffer); + rb.PushMappedBuffer(message_id_buffer); + + LOG_DEBUG( + Service_CECD, + "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}", + ncch_program_id, is_outbox, message_id_size, buffer_size); +} + +void Module::Interface::WriteMessageWithHMAC(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x07, 4, 6); + const u32 ncch_program_id = rp.Pop(); + const bool is_outbox = rp.Pop(); + const u32 message_id_size = rp.Pop(); + const u32 buffer_size = rp.Pop(); + auto& read_buffer = rp.PopMappedBuffer(); + auto& hmac_key_buffer = rp.PopMappedBuffer(); + auto& message_id_buffer = rp.PopMappedBuffer(); + + FileSys::Mode mode; + mode.write_flag.Assign(1); + mode.create_flag.Assign(1); + + std::vector id_buffer(message_id_size); + message_id_buffer.Read(id_buffer.data(), 0, message_id_size); + + FileSys::Path message_path = + cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg + : CecDataPathType::InboxMsg, + ncch_program_id, id_buffer) + .data(); + + auto message_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, message_path, mode); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 6); + if (message_result.Succeeded()) { + auto message = message_result.Unwrap(); + + std::vector buffer(buffer_size); + read_buffer.Read(buffer.data(), 0, buffer_size); + + CecMessageHeader msg_header; + std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader)); + + LOG_DEBUG(Service_CECD, + "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, " + "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, " + "batch_id={:#010x}", + msg_header.magic, msg_header.message_size, msg_header.header_size, + msg_header.body_size, msg_header.title_id, msg_header.title_id2, + msg_header.batch_id); + LOG_DEBUG(Service_CECD, + "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, " + "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, " + "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, " + "forward_count={:#04x}, user_data={:#06x}, ", + msg_header.unknown_id, msg_header.version, msg_header.flag, + msg_header.send_method, msg_header.is_unopen, msg_header.is_new, + msg_header.sender_id, msg_header.sender_id2, msg_header.send_count, + msg_header.forward_count, msg_header.user_data); + + const u32 hmac_offset = msg_header.header_size + msg_header.body_size; + const u32 hmac_size = 0x20; + + std::vector hmac_digest(hmac_size); + std::vector message_body(msg_header.body_size); + std::memcpy(message_body.data(), buffer.data() + msg_header.header_size, + msg_header.body_size); + + using namespace CryptoPP; + SecByteBlock key(hmac_size); + hmac_key_buffer.Read(key.data(), 0, hmac_size); + + HMAC hmac(key, hmac_size); + hmac.CalculateDigest(hmac_digest.data(), message_body.data(), msg_header.body_size); + std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size); + + const u32 bytes_written = + message->backend->Write(0, buffer_size, true, buffer.data()).Unwrap(); + message->backend->Close(); + + rb.Push(RESULT_SUCCESS); + } else { + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + } + + rb.PushMappedBuffer(read_buffer); + rb.PushMappedBuffer(hmac_key_buffer); + rb.PushMappedBuffer(message_id_buffer); + + LOG_DEBUG( + Service_CECD, + "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}", + ncch_program_id, is_outbox, message_id_size, buffer_size); +} + +void Module::Interface::Delete(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x08, 4, 2); + const u32 ncch_program_id = rp.Pop(); + const CecDataPathType path_type = rp.PopEnum(); + const bool is_outbox = rp.Pop(); + const u32 message_id_size = rp.Pop(); + auto& message_id_buffer = rp.PopMappedBuffer(); + + FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data()); + FileSys::Mode mode; + mode.write_flag.Assign(1); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + switch (path_type) { + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: + rb.Push(Service::FS::DeleteDirectoryRecursivelyFromArchive( + cecd->cecd_system_save_data_archive, path)); + break; + default: // If not directory, then it is a file + if (message_id_size == 0) { + rb.Push(Service::FS::DeleteFileFromArchive(cecd->cecd_system_save_data_archive, path)); + } else { + std::vector id_buffer(message_id_size); + message_id_buffer.Read(id_buffer.data(), 0, message_id_size); + + FileSys::Path message_path = + cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg + : CecDataPathType::InboxMsg, + ncch_program_id, id_buffer) + .data(); + rb.Push(Service::FS::DeleteFileFromArchive(cecd->cecd_system_save_data_archive, + message_path)); + } + } + + rb.PushMappedBuffer(message_id_buffer); + + LOG_DEBUG(Service_CECD, + "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, " + "is_outbox={}, message_id_size={:#x}", + ncch_program_id, static_cast(path_type), path.AsString(), is_outbox, + message_id_size); +} + +void Module::Interface::SetData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x09, 3, 2); + const u32 ncch_program_id = rp.Pop(); + const u32 buffer_size = rp.Pop(); + const u32 option = rp.Pop(); + auto& read_buffer = rp.PopMappedBuffer(); + + if (option == 2 && buffer_size > 0) { // update obindex? + FileSys::Path path( + cecd->GetCecDataPathTypeAsString(CecDataPathType::OutboxIndex, ncch_program_id).data()); + FileSys::Mode mode; + mode.write_flag.Assign(1); + mode.create_flag.Assign(1); + + auto file_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, path, mode); + if (file_result.Succeeded()) { + auto file = file_result.Unwrap(); + std::vector buffer(buffer_size); + read_buffer.Read(buffer.data(), 0, buffer_size); + + cecd->CheckAndUpdateFile(CecDataPathType::OutboxIndex, ncch_program_id, buffer); + + file->backend->Write(0, buffer.size(), true, buffer.data()); + file->backend->Close(); + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(read_buffer); + + LOG_DEBUG(Service_CECD, "called, ncch_program_id={:#010x}, buffer_size={:#x}, option={:#x}", + ncch_program_id, buffer_size, option); +} + +void Module::Interface::ReadData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0A, 3, 4); + const u32 dest_buffer_size = rp.Pop(); + const CecSystemInfoType info_type = rp.PopEnum(); + const u32 param_buffer_size = rp.Pop(); + auto& param_buffer = rp.PopMappedBuffer(); + auto& dest_buffer = rp.PopMappedBuffer(); + + // TODO: Other CecSystemInfoTypes + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + std::vector buffer; + switch (info_type) { + case CecSystemInfoType::EulaVersion: // TODO: Read config Eula version + buffer = {0xFF, 0xFF}; + dest_buffer.Write(buffer.data(), 0, buffer.size()); + break; + case CecSystemInfoType::Eula: + buffer = {0x01}; // Eula agreed + dest_buffer.Write(buffer.data(), 0, buffer.size()); + break; + case CecSystemInfoType::ParentControl: + buffer = {0x00}; // No parent control + dest_buffer.Write(buffer.data(), 0, buffer.size()); + break; + default: + LOG_ERROR(Service_CECD, "Unknown system info type={:#x}", static_cast(info_type)); + } + + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(param_buffer); + rb.PushMappedBuffer(dest_buffer); + + LOG_DEBUG(Service_CECD, + "called, dest_buffer_size={:#x}, info_type={:#x}, param_buffer_size={:#x}", + dest_buffer_size, static_cast(info_type), param_buffer_size); +} + +void Module::Interface::Start(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0B, 1, 0); + const CecCommand command = rp.PopEnum(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_CECD, "(STUBBED) called, command={}", cecd->GetCecCommandAsString(command)); +} + +void Module::Interface::Stop(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0C, 1, 0); + const CecCommand command = rp.PopEnum(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_CECD, "(STUBBED) called, command={}", cecd->GetCecCommandAsString(command)); +} + +void Module::Interface::GetCecInfoBuffer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0D, 2, 2); + const u32 buffer_size = rp.Pop(); + const u32 possible_info_type = rp.Pop(); + auto& buffer = rp.PopMappedBuffer(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(buffer); + + LOG_DEBUG(Service_CECD, "called, buffer_size={}, possible_info_type={}", buffer_size, + possible_info_type); +} + +void Module::Interface::GetCecdState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x0E, 0, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - rb.PushEnum(CecStateAbbreviated::CEC_STATE_ABBREV_IDLE); + rb.PushEnum(CecdState::NdmStatusIdle); LOG_WARNING(Service_CECD, "(STUBBED) called"); } @@ -42,6 +710,661 @@ void Module::Interface::GetChangeStateEventHandle(Kernel::HLERequestContext& ctx LOG_WARNING(Service_CECD, "(STUBBED) called"); } +void Module::Interface::OpenAndWrite(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x11, 4, 4); + const u32 buffer_size = rp.Pop(); + const u32 ncch_program_id = rp.Pop(); + const CecDataPathType path_type = rp.PopEnum(); + CecOpenMode open_mode; + open_mode.raw = rp.Pop(); + rp.PopPID(); + auto& read_buffer = rp.PopMappedBuffer(); + + FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data()); + FileSys::Mode mode; + mode.write_flag.Assign(1); + mode.create_flag.Assign(1); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + switch (path_type) { + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC, + ErrorSummary::NotFound, ErrorLevel::Status)); + break; + default: // If not directory, then it is a file + auto file_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, path, mode); + if (file_result.Succeeded()) { + auto file = file_result.Unwrap(); + + std::vector buffer(buffer_size); + read_buffer.Read(buffer.data(), 0, buffer_size); + + if (file->backend->GetSize() != buffer_size) { + file->backend->SetSize(buffer_size); + } + + if (open_mode.check) { + cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer); + } + + const u32 bytes_written = + file->backend->Write(0, buffer.size(), true, buffer.data()).Unwrap(); + file->backend->Close(); + + rb.Push(RESULT_SUCCESS); + } else { + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + } + } + rb.PushMappedBuffer(read_buffer); + + LOG_DEBUG(Service_CECD, + "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, buffer_size={:#x} " + "open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}", + ncch_program_id, static_cast(path_type), path.AsString(), buffer_size, + open_mode.raw, open_mode.unknown, open_mode.read, open_mode.write, open_mode.create, + open_mode.check); +} + +void Module::Interface::OpenAndRead(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x12, 4, 4); + const u32 buffer_size = rp.Pop(); + const u32 ncch_program_id = rp.Pop(); + const CecDataPathType path_type = rp.PopEnum(); + CecOpenMode open_mode; + open_mode.raw = rp.Pop(); + rp.PopPID(); + auto& write_buffer = rp.PopMappedBuffer(); + + FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data()); + FileSys::Mode mode; + mode.read_flag.Assign(1); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + switch (path_type) { + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC, + ErrorSummary::NotFound, ErrorLevel::Status)); + rb.Push(0); // No entries read + break; + default: // If not directory, then it is a file + auto file_result = + Service::FS::OpenFileFromArchive(cecd->cecd_system_save_data_archive, path, mode); + if (file_result.Succeeded()) { + auto file = file_result.Unwrap(); + std::vector buffer(buffer_size); + + const u32 bytes_read = file->backend->Read(0, buffer_size, buffer.data()).Unwrap(); + write_buffer.Write(buffer.data(), 0, buffer_size); + file->backend->Close(); + + rb.Push(RESULT_SUCCESS); + rb.Push(bytes_read); + } else { + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound, + ErrorLevel::Status)); + rb.Push(0); // No bytes read + } + } + rb.PushMappedBuffer(write_buffer); + + LOG_DEBUG(Service_CECD, + "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, buffer_size={:#x} " + "open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}", + ncch_program_id, static_cast(path_type), path.AsString(), buffer_size, + open_mode.raw, open_mode.unknown, open_mode.read, open_mode.write, open_mode.create, + open_mode.check); +} + +std::string Module::EncodeBase64(const std::vector& in) const { + using namespace CryptoPP; + using Name::EncodingLookupArray; + using Name::InsertLineBreaks; + using Name::Pad; + + std::string out; + Base64Encoder encoder; + AlgorithmParameters params = + MakeParameters(EncodingLookupArray(), (const byte*)base64_dict.data())(InsertLineBreaks(), + false)(Pad(), false); + + encoder.IsolatedInitialize(params); + encoder.Attach(new StringSink(out)); + encoder.Put(in.data(), in.size()); + encoder.MessageEnd(); + + return out; +} + +std::string Module::GetCecDataPathTypeAsString(const CecDataPathType type, const u32 program_id, + const std::vector& msg_id) const { + switch (type) { + case CecDataPathType::MboxList: + return "/CEC/MBoxList____"; + case CecDataPathType::MboxInfo: + return fmt::format("/CEC/{:08x}/MBoxInfo____", program_id); + case CecDataPathType::InboxInfo: + return fmt::format("/CEC/{:08x}/InBox___/BoxInfo_____", program_id); + case CecDataPathType::OutboxInfo: + return fmt::format("/CEC/{:08x}/OutBox__/BoxInfo_____", program_id); + case CecDataPathType::OutboxIndex: + return fmt::format("/CEC/{:08x}/OutBox__/OBIndex_____", program_id); + case CecDataPathType::InboxMsg: + return fmt::format("/CEC/{:08x}/InBox___/_{}", program_id, EncodeBase64(msg_id)); + case CecDataPathType::OutboxMsg: + return fmt::format("/CEC/{:08x}/OutBox__/_{}", program_id, EncodeBase64(msg_id)); + case CecDataPathType::RootDir: + return "/CEC"; + case CecDataPathType::MboxDir: + return fmt::format("/CEC/{:08x}", program_id); + case CecDataPathType::InboxDir: + return fmt::format("/CEC/{:08x}/InBox___", program_id); + case CecDataPathType::OutboxDir: + return fmt::format("/CEC/{:08x}/OutBox__", program_id); + case CecDataPathType::MboxData: + case CecDataPathType::MboxIcon: + case CecDataPathType::MboxTitle: + default: + return fmt::format("/CEC/{:08x}/MBoxData.{:03}", program_id, static_cast(type) - 100); + } +} + +std::string Module::GetCecCommandAsString(const CecCommand command) const { + switch (command) { + case CecCommand::None: + return "None"; + case CecCommand::Start: + return "Start"; + case CecCommand::ResetStart: + return "ResetStart"; + case CecCommand::ReadyScan: + return "ReadyScan"; + case CecCommand::ReadyScanWait: + return "ReadyScanWait"; + case CecCommand::StartScan: + return "StartScan"; + case CecCommand::Rescan: + return "Rescan"; + case CecCommand::NdmResume: + return "NdmResume"; + case CecCommand::NdmSuspend: + return "NdmSuspend"; + case CecCommand::NdmSuspendImmediate: + return "NdmSuspendImmediate"; + case CecCommand::StopWait: + return "StopWait"; + case CecCommand::Stop: + return "Stop"; + case CecCommand::StopForce: + return "StopForce"; + case CecCommand::StopForceWait: + return "StopForceWait"; + case CecCommand::ResetFilter: + return "ResetFilter"; + case CecCommand::DaemonStop: + return "DaemonStop"; + case CecCommand::DaemonStart: + return "DaemonStart"; + case CecCommand::Exit: + return "Exit"; + case CecCommand::OverBoss: + return "OverBoss"; + case CecCommand::OverBossForce: + return "OverBossForce"; + case CecCommand::OverBossForceWait: + return "OverBossForceWait"; + case CecCommand::End: + return "End"; + default: + return "Unknown"; + } +} + +void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_program_id, + std::vector& file_buffer) { + constexpr u32 max_num_boxes = 24; + constexpr u32 name_size = 16; // fixed size 16 characters long + constexpr u32 valid_name_size = 8; // 8 characters are valid, the rest are null + const u32 file_size = file_buffer.size(); + + switch (path_type) { + case CecDataPathType::MboxList: { + CecMBoxListHeader mbox_list_header = {}; + std::memcpy(&mbox_list_header, file_buffer.data(), sizeof(CecMBoxListHeader)); + + LOG_DEBUG(Service_CECD, "CecMBoxList: magic={:#06x}, version={:#06x}, num_boxes={:#06x}", + mbox_list_header.magic, mbox_list_header.version, mbox_list_header.num_boxes); + + if (file_size != sizeof(CecMBoxListHeader)) { // 0x18C + LOG_DEBUG(Service_CECD, "CecMBoxListHeader size is incorrect: {}", file_size); + } + + if (mbox_list_header.magic != 0x6868) { // 'hh' + if (mbox_list_header.magic == 0 || mbox_list_header.magic == 0xFFFF) { + LOG_DEBUG(Service_CECD, "CecMBoxListHeader magic number is not set"); + } else { + LOG_DEBUG(Service_CECD, "CecMBoxListHeader magic number is incorrect: {}", + mbox_list_header.magic); + } + std::memset(&mbox_list_header, 0, sizeof(CecMBoxListHeader)); + mbox_list_header.magic = 0x6868; + } + + if (mbox_list_header.version != 0x01) { // Not quite sure if it is a version + if (mbox_list_header.version == 0) + LOG_DEBUG(Service_CECD, "CecMBoxListHeader version is not set"); + else + LOG_DEBUG(Service_CECD, "CecMBoxListHeader version is incorrect: {}", + mbox_list_header.version); + mbox_list_header.version = 0x01; + } + + if (mbox_list_header.num_boxes > 24) { + LOG_DEBUG(Service_CECD, "CecMBoxListHeader number of boxes is too large: {}", + mbox_list_header.num_boxes); + } else { + std::vector name_buffer(name_size); + std::memset(name_buffer.data(), 0, name_size); + + if (ncch_program_id != 0) { + std::string name = fmt::format("{:08x}", ncch_program_id); + std::memcpy(name_buffer.data(), name.data(), name.size()); + + bool already_activated = false; + for (auto i = 0; i < mbox_list_header.num_boxes; i++) { + // Box names start at offset 0xC, are 16 char long, first 8 id, last 8 null + if (std::memcmp(name_buffer.data(), &mbox_list_header.box_names[i], + valid_name_size) == 0) { + LOG_DEBUG(Service_CECD, "Title already activated"); + already_activated = true; + } + }; + + if (!already_activated) { + if (mbox_list_header.num_boxes < max_num_boxes) { // max boxes + LOG_DEBUG(Service_CECD, "Adding title to mboxlist____: {}", name); + std::memcpy(&mbox_list_header.box_names[mbox_list_header.num_boxes], + name_buffer.data(), name_size); + mbox_list_header.num_boxes++; + } + } + } else { // ncch_program_id == 0, remove/update activated boxes + /// We need to read the /CEC directory to find out which titles, if any, + /// are activated. The num_of_titles = (total_read_count) - 1, to adjust for + /// the MBoxList____ file that is present in the directory as well. + FileSys::Path root_path( + GetCecDataPathTypeAsString(CecDataPathType::RootDir, 0).data()); + + auto dir_result = + Service::FS::OpenDirectoryFromArchive(cecd_system_save_data_archive, root_path); + + auto root_dir = dir_result.Unwrap(); + std::vector entries(max_num_boxes + 1); // + 1 mboxlist + const u32 entry_count = root_dir->backend->Read(max_num_boxes + 1, entries.data()); + root_dir->backend->Close(); + + LOG_DEBUG(Service_CECD, "Number of entries found in /CEC: {}", entry_count); + + std::string mbox_list_name("MBoxList____"); + std::string file_name; + std::u16string u16_filename; + + // Loop through entries but don't add mboxlist____ to itself. + for (auto i = 0; i < entry_count; i++) { + u16_filename = std::u16string(entries[i].filename); + file_name = Common::UTF16ToUTF8(u16_filename); + + if (mbox_list_name.compare(file_name) != 0) { + LOG_DEBUG(Service_CECD, "Adding title to mboxlist____: {}", file_name); + std::memcpy(&mbox_list_header.box_names[mbox_list_header.num_boxes++], + file_name.data(), valid_name_size); + } + } + } + } + std::memcpy(file_buffer.data(), &mbox_list_header, sizeof(CecMBoxListHeader)); + break; + } + case CecDataPathType::MboxInfo: { + CecMBoxInfoHeader mbox_info_header = {}; + std::memcpy(&mbox_info_header, file_buffer.data(), sizeof(CecMBoxInfoHeader)); + + LOG_DEBUG(Service_CECD, + "CecMBoxInfoHeader: magic={:#06x}, program_id={:#010x}, " + "private_id={:#010x}, flag={:#04x}, flag2={:#04x}", + mbox_info_header.magic, mbox_info_header.program_id, mbox_info_header.private_id, + mbox_info_header.flag, mbox_info_header.flag2); + + if (file_size != sizeof(CecMBoxInfoHeader)) { // 0x60 + LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader size is incorrect: {}", file_size); + } + + if (mbox_info_header.magic != 0x6363) { // 'cc' + if (mbox_info_header.magic == 0) + LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader magic number is not set"); + else + LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader magic number is incorrect: {}", + mbox_info_header.magic); + mbox_info_header.magic = 0x6363; + } + + if (mbox_info_header.program_id != ncch_program_id) { + if (mbox_info_header.program_id == 0) + LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader program id is not set"); + else + LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader program id doesn't match current id: {}", + mbox_info_header.program_id); + } + + std::memcpy(file_buffer.data(), &mbox_info_header, sizeof(CecMBoxInfoHeader)); + break; + } + case CecDataPathType::InboxInfo: { + CecBoxInfoHeader inbox_info_header = {}; + std::memcpy(&inbox_info_header, file_buffer.data(), sizeof(CecBoxInfoHeader)); + + LOG_DEBUG(Service_CECD, + "CecBoxInfoHeader: magic={:#06x}, box_info_size={:#010x}, " + "max_box_size={:#010x}, box_size={:#010x}, " + "max_message_num={:#010x}, message_num={:#010x}, " + "max_batch_size={:#010x}, max_message_size={:#010x}", + inbox_info_header.magic, inbox_info_header.box_info_size, + inbox_info_header.max_box_size, inbox_info_header.box_size, + inbox_info_header.max_message_num, inbox_info_header.message_num, + inbox_info_header.max_batch_size, inbox_info_header.max_message_size); + + if (inbox_info_header.magic != 0x6262) { // 'bb' + if (inbox_info_header.magic == 0) + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader magic number is not set"); + else + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader magic number is incorrect: {}", + inbox_info_header.magic); + inbox_info_header.magic = 0x6262; + } + + if (inbox_info_header.box_info_size != file_size) { + if (inbox_info_header.box_info_size == 0) + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader box info size is not set"); + else + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader box info size is incorrect:", + inbox_info_header.box_info_size); + inbox_info_header.box_info_size = sizeof(CecBoxInfoHeader); + } + + if (inbox_info_header.max_box_size == 0) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max box size is not set"); + } else if (inbox_info_header.max_box_size > 0x100000) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max box size is too large: {}", + inbox_info_header.max_box_size); + } + + if (inbox_info_header.max_message_num == 0) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message number is not set"); + } else if (inbox_info_header.max_message_num > 99) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message number is too large: {}", + inbox_info_header.max_message_num); + } + + if (inbox_info_header.max_message_size == 0) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message size is not set"); + } else if (inbox_info_header.max_message_size > 0x019000) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message size is too large"); + } + + if (inbox_info_header.max_batch_size == 0) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max batch size is not set"); + inbox_info_header.max_batch_size = inbox_info_header.max_message_num; + } else if (inbox_info_header.max_batch_size != inbox_info_header.max_message_num) { + LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max batch size != max message number"); + } + + std::memcpy(file_buffer.data(), &inbox_info_header, sizeof(CecBoxInfoHeader)); + break; + } + case CecDataPathType::OutboxInfo: { + CecBoxInfoHeader outbox_info_header = {}; + std::memcpy(&outbox_info_header, file_buffer.data(), sizeof(CecBoxInfoHeader)); + + LOG_DEBUG(Service_CECD, + "CecBoxInfoHeader: magic={:#06x}, box_info_size={:#010x}, " + "max_box_size={:#010x}, box_size={:#010x}, " + "max_message_num={:#010x}, message_num={:#010x}, " + "max_batch_size={:#010x}, max_message_size={:#010x}", + outbox_info_header.magic, outbox_info_header.box_info_size, + outbox_info_header.max_box_size, outbox_info_header.box_size, + outbox_info_header.max_message_num, outbox_info_header.message_num, + outbox_info_header.max_batch_size, outbox_info_header.max_message_size); + + if (outbox_info_header.magic != 0x6262) { // 'bb' + if (outbox_info_header.magic == 0) + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader magic number is not set"); + else + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader magic number is incorrect: {}", + outbox_info_header.magic); + outbox_info_header.magic = 0x6262; + } + + if (outbox_info_header.box_info_size != file_buffer.size()) { + if (outbox_info_header.box_info_size == 0) + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader box info size is not set"); + else + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader box info size is incorrect:", + outbox_info_header.box_info_size); + outbox_info_header.box_info_size = sizeof(CecBoxInfoHeader); + outbox_info_header.message_num = 0; + } + + if (outbox_info_header.max_box_size == 0) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max box size is not set"); + } else if (outbox_info_header.max_box_size > 0x100000) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max box size is too large"); + } + + if (outbox_info_header.max_message_num == 0) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message number is not set"); + } else if (outbox_info_header.max_message_num > 99) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message number is too large"); + } + + if (outbox_info_header.max_message_size == 0) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message size is not set"); + } else if (outbox_info_header.max_message_size > 0x019000) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message size is too large"); + } + + if (outbox_info_header.max_batch_size == 0) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max batch size is not set"); + outbox_info_header.max_batch_size = outbox_info_header.max_message_num; + } else if (outbox_info_header.max_batch_size != outbox_info_header.max_message_num) { + LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max batch size != max message number"); + } + + /// We need to read the /CEC//OutBox directory to find out which messages, if any, + /// are present. The num_of_messages = (total_read_count) - 2, to adjust for + /// the BoxInfo____ and OBIndex_____files that are present in the directory as well. + FileSys::Path outbox_path( + GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id).data()); + + auto dir_result = + Service::FS::OpenDirectoryFromArchive(cecd_system_save_data_archive, outbox_path); + + auto outbox_dir = dir_result.Unwrap(); + std::vector entries(outbox_info_header.max_message_num + 2); + const u32 entry_count = + outbox_dir->backend->Read(outbox_info_header.max_message_num + 2, entries.data()); + outbox_dir->backend->Close(); + + LOG_DEBUG(Service_CECD, "Number of entries found in /OutBox: {}", entry_count); + std::array message_headers; + + std::string boxinfo_name("BoxInfo_____"); + std::string obindex_name("OBIndex_____"); + std::string file_name; + std::u16string u16_filename; + + for (auto i = 0; i < entry_count; i++) { + u16_filename = std::u16string(entries[i].filename); + file_name = Common::UTF16ToUTF8(u16_filename); + + if (boxinfo_name.compare(file_name) != 0 && obindex_name.compare(file_name) != 0) { + LOG_DEBUG(Service_CECD, "Adding message to BoxInfo_____: {}", file_name); + + FileSys::Path message_path( + (GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id) + "/" + + file_name) + .data()); + + FileSys::Mode mode; + mode.read_flag.Assign(1); + + auto message_result = Service::FS::OpenFileFromArchive( + cecd_system_save_data_archive, message_path, mode); + + auto message = message_result.Unwrap(); + const u32 message_size = message->backend->GetSize(); + std::vector buffer(message_size); + + message->backend->Read(0, message_size, buffer.data()).Unwrap(); + message->backend->Close(); + + std::memcpy(&message_headers[outbox_info_header.message_num++], buffer.data(), + sizeof(CecMessageHeader)); + } + } + + if (outbox_info_header.message_num > 0) { + const u32 message_headers_size = + outbox_info_header.message_num * sizeof(CecMessageHeader); + + file_buffer.resize(sizeof(CecBoxInfoHeader) + message_headers_size, 0); + outbox_info_header.box_info_size += message_headers_size; + + std::memcpy(file_buffer.data() + sizeof(CecBoxInfoHeader), &message_headers, + message_headers_size); + } + + std::memcpy(file_buffer.data(), &outbox_info_header, sizeof(CecBoxInfoHeader)); + break; + } + case CecDataPathType::OutboxIndex: { + CecOBIndexHeader obindex_header = {}; + std::memcpy(&obindex_header, file_buffer.data(), sizeof(CecOBIndexHeader)); + + if (file_size < sizeof(CecOBIndexHeader)) { // 0x08, minimum size + LOG_DEBUG(Service_CECD, "CecOBIndexHeader size is too small: {}", file_size); + } + + if (obindex_header.magic != 0x6767) { // 'gg' + if (obindex_header.magic == 0) + LOG_DEBUG(Service_CECD, "CecOBIndexHeader magic number is not set"); + else + LOG_DEBUG(Service_CECD, "CecOBIndexHeader magic number is incorrect: {}", + obindex_header.magic); + obindex_header.magic = 0x6767; + } + + if (obindex_header.message_num == 0) { + if (file_size > sizeof(CecOBIndexHeader)) { + LOG_DEBUG(Service_CECD, "CecOBIndexHeader message number is not set"); + obindex_header.message_num = (file_size % 8) - 1; // 8 byte message id - 1 header + } + } else if (obindex_header.message_num != (file_size % 8) - 1) { + LOG_DEBUG(Service_CECD, "CecOBIndexHeader message number is incorrect: {}", + obindex_header.message_num); + obindex_header.message_num = 0; + } + + /// We need to read the /CEC//OutBox directory to find out which messages, if any, + /// are present. The num_of_messages = (total_read_count) - 2, to adjust for + /// the BoxInfo____ and OBIndex_____files that are present in the directory as well. + FileSys::Path outbox_path( + GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id).data()); + + auto dir_result = + Service::FS::OpenDirectoryFromArchive(cecd_system_save_data_archive, outbox_path); + + auto outbox_dir = dir_result.Unwrap(); + std::vector entries(8); + const u32 entry_count = outbox_dir->backend->Read(8, entries.data()); + outbox_dir->backend->Close(); + + LOG_DEBUG(Service_CECD, "Number of entries found in /OutBox: {}", entry_count); + std::array, 8> message_ids; + + std::string boxinfo_name("BoxInfo_____"); + std::string obindex_name("OBIndex_____"); + std::string file_name; + std::u16string u16_filename; + + for (auto i = 0; i < entry_count; i++) { + u16_filename = std::u16string(entries[i].filename); + file_name = Common::UTF16ToUTF8(u16_filename); + + if (boxinfo_name.compare(file_name) != 0 && obindex_name.compare(file_name) != 0) { + FileSys::Path message_path( + (GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id) + "/" + + file_name) + .data()); + + FileSys::Mode mode; + mode.read_flag.Assign(1); + + auto message_result = Service::FS::OpenFileFromArchive( + cecd_system_save_data_archive, message_path, mode); + + auto message = message_result.Unwrap(); + const u32 message_size = message->backend->GetSize(); + std::vector buffer(message_size); + + message->backend->Read(0, message_size, buffer.data()).Unwrap(); + message->backend->Close(); + + // Message id is at offset 0x20, and is 8 bytes + std::memcpy(&message_ids[obindex_header.message_num++], buffer.data() + 0x20, 8); + } + } + + if (obindex_header.message_num > 0) { + const u32 message_ids_size = obindex_header.message_num * 8; + file_buffer.resize(sizeof(CecOBIndexHeader) + message_ids_size); + std::memcpy(file_buffer.data() + sizeof(CecOBIndexHeader), &message_ids, + message_ids_size); + } + + std::memcpy(file_buffer.data(), &obindex_header, sizeof(CecOBIndexHeader)); + break; + } + case CecDataPathType::InboxMsg: + break; + case CecDataPathType::OutboxMsg: + break; + case CecDataPathType::RootDir: + case CecDataPathType::MboxDir: + case CecDataPathType::InboxDir: + case CecDataPathType::OutboxDir: + break; + case CecDataPathType::MboxData: + case CecDataPathType::MboxIcon: + case CecDataPathType::MboxTitle: + default: {} + } +} + +Module::SessionData::SessionData() {} + +Module::SessionData::~SessionData() { + if (file) + file->backend->Close(); +} + Module::Interface::Interface(std::shared_ptr cecd, const char* name, u32 max_session) : ServiceFramework(name, max_session), cecd(std::move(cecd)) {} @@ -49,6 +1372,85 @@ Module::Module() { using namespace Kernel; cecinfo_event = Event::Create(Kernel::ResetType::OneShot, "CECD::cecinfo_event"); change_state_event = Event::Create(Kernel::ResetType::OneShot, "CECD::change_state_event"); + + // Open the SystemSaveData archive 0x00010026 + FileSys::Path archive_path(cecd_system_savedata_id); + auto archive_result = + Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); + + // If the archive didn't exist, create the files inside + if (archive_result.Code() == FileSys::ERR_NOT_FORMATTED) { + // Format the archive to create the directories + Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SystemSaveData, + FileSys::ArchiveFormatInfo(), archive_path); + + // Open it again to get a valid archive now that the folder exists + archive_result = + Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); + + /// Now that the archive is formatted, we need to create the root CEC directory, + /// eventlog.dat, and CEC/MBoxList____ + const FileSys::Path root_dir_path( + GetCecDataPathTypeAsString(CecDataPathType::RootDir, 0).data()); + Service::FS::CreateDirectoryFromArchive(*archive_result, root_dir_path); + + FileSys::Mode mode; + mode.write_flag.Assign(1); + mode.create_flag.Assign(1); + + /// eventlog.dat resides in the root of the archive beside the CEC directory + /// Initially created, at offset 0x0, are bytes 0x01 0x41 0x12, followed by + /// zeroes until offset 0x1000, where it changes to 0xDD until the end of file + /// at offset 0x30d53. 0xDD means that the cec module hasn't written data to that + /// region yet. + FileSys::Path eventlog_path("/eventlog.dat"); + + auto eventlog_result = + Service::FS::OpenFileFromArchive(*archive_result, eventlog_path, mode); + + constexpr u32 eventlog_size = 0x30d54; + auto eventlog = eventlog_result.Unwrap(); + std::vector eventlog_buffer(eventlog_size); + + std::memset(&eventlog_buffer[0], 0, 0x1000); + eventlog_buffer[0] = 0x01; + eventlog_buffer[1] = 0x41; + eventlog_buffer[2] = 0x12; + + eventlog->backend->Write(0, eventlog_size, true, eventlog_buffer.data()); + eventlog->backend->Close(); + + /// MBoxList____ resides within the root CEC/ directory. + /// Initially created, at offset 0x0, are bytes 0x68 0x68 0x00 0x00 0x01, with 0x6868 'hh', + /// being the magic number. The rest of the file is filled with zeroes, until the end of + /// file at offset 0x18b + FileSys::Path mboxlist_path( + GetCecDataPathTypeAsString(CecDataPathType::MboxList, 0).data()); + + auto mboxlist_result = + Service::FS::OpenFileFromArchive(*archive_result, mboxlist_path, mode); + + constexpr u32 mboxlist_size = 0x18c; + auto mboxlist = mboxlist_result.Unwrap(); + std::vector mboxlist_buffer(mboxlist_size); + + std::memset(&mboxlist_buffer[0], 0, mboxlist_size); + mboxlist_buffer[0] = 0x68; + mboxlist_buffer[1] = 0x68; + // mboxlist_buffer[2-3] are already zeroed + mboxlist_buffer[4] = 0x01; + + mboxlist->backend->Write(0, mboxlist_size, true, mboxlist_buffer.data()); + mboxlist->backend->Close(); + } + ASSERT_MSG(archive_result.Succeeded(), "Could not open the CECD SystemSaveData archive!"); + + cecd_system_save_data_archive = *archive_result; +} + +Module::~Module() { + if (cecd_system_save_data_archive) + Service::FS::CloseArchive(cecd_system_save_data_archive); } void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/cecd/cecd.h b/src/core/hle/service/cecd/cecd.h index 2db74be4d..a9ca65ff2 100644 --- a/src/core/hle/service/cecd/cecd.h +++ b/src/core/hle/service/cecd/cecd.h @@ -4,46 +4,487 @@ #pragma once +#include "common/bit_field.h" +#include "common/common_funcs.h" #include "core/hle/kernel/event.h" +#include "core/hle/service/fs/archive.h" #include "core/hle/service/service.h" namespace Service::CECD { -enum class CecStateAbbreviated : u32 { - CEC_STATE_ABBREV_IDLE = 1, ///< Corresponds to CEC_STATE_IDLE - CEC_STATE_ABBREV_NOT_LOCAL = 2, ///< Corresponds to CEC_STATEs *FINISH*, *POST, and OVER_BOSS - CEC_STATE_ABBREV_SCANNING = 3, ///< Corresponds to CEC_STATE_SCANNING - CEC_STATE_ABBREV_WLREADY = - 4, ///< Corresponds to CEC_STATE_WIRELESS_READY when some unknown bool is true - CEC_STATE_ABBREV_OTHER = 5, ///< Corresponds to CEC_STATEs besides *FINISH*, *POST, and - /// OVER_BOSS and those listed here -}; - class Module final { public: Module(); - ~Module() = default; + ~Module(); - class Interface : public ServiceFramework { + enum class CecCommand : u32 { + None = 0, + Start = 1, + ResetStart = 2, + ReadyScan = 3, + ReadyScanWait = 4, + StartScan = 5, + Rescan = 6, + NdmResume = 7, + NdmSuspend = 8, + NdmSuspendImmediate = 9, + StopWait = 0x0A, + Stop = 0x0B, + StopForce = 0x0C, + StopForceWait = 0x0D, + ResetFilter = 0x0E, + DaemonStop = 0x0F, + DaemonStart = 0x10, + Exit = 0x11, + OverBoss = 0x12, + OverBossForce = 0x13, + OverBossForceWait = 0x14, + End = 0x15, + }; + + /** + * CecDataPathType possible missing values; need to figure out placement + * + * data:/CEC/TMP + * data:/CEC/test + */ + enum class CecDataPathType : u32 { + Invalid = 0, + MboxList = 1, /// data:/CEC/MBoxList____ + MboxInfo = 2, /// data:/CEC//MBoxInfo____ + InboxInfo = 3, /// data:/CEC//InBox___/BoxInfo_____ + OutboxInfo = 4, /// data:/CEC//OutBox__/BoxInfo_____ + OutboxIndex = 5, /// data:/CEC//OutBox__/OBIndex_____ + InboxMsg = 6, /// data:/CEC//InBox___/_ + OutboxMsg = 7, /// data:/CEC//OutBox__/_ + RootDir = 10, /// data:/CEC + MboxDir = 11, /// data:/CEC/ + InboxDir = 12, /// data:/CEC//InBox___ + OutboxDir = 13, /// data:/CEC//OutBox__ + MboxData = 100, /// data:/CEC//MBoxData.0 + MboxIcon = 101, /// data:/CEC//MBoxData.001 + MboxTitle = 110, /// data:/CEC//MBoxData.010 + MboxProgramId = 150, /// data:/CEC//MBoxData.050 + }; + + enum class CecState : u32 { + None = 0, + Init = 1, + WirelessParamSetup = 2, + WirelessReady = 3, + WirelessStartConfig = 4, + Scan = 5, + Scanning = 6, + Connect = 7, + Connecting = 8, + Connected = 9, + ConnectTcp = 10, + ConnectingTcp = 11, + ConnectedTcp = 12, + Negotiation = 13, + SendRecvStart = 14, + SendRecvInit = 15, + SendReady = 16, + ReceiveReady = 17, + Receive = 18, + ConnectionFinishTcp = 19, + ConnectionFinish = 20, + SendPost = 21, + ReceivePost = 22, + Finishing = 23, + Finish = 24, + OverBoss = 25, + Idle = 26 + }; + + enum class CecSystemInfoType : u32 { EulaVersion = 1, Eula = 2, ParentControl = 3 }; + + struct CecBoxInfoHeader { + u16_le magic; // 0x6262 'bb' + INSERT_PADDING_BYTES(2); + u32_le box_info_size; + u32_le max_box_size; + u32_le box_size; + u32_le max_message_num; + u32_le message_num; + u32_le max_batch_size; + u32_le max_message_size; + }; + static_assert(sizeof(CecBoxInfoHeader) == 0x20, "CecBoxInfoHeader struct has incorrect size."); + + struct CecMBoxInfoHeader { + u16_le magic; // 0x6363 'cc' + INSERT_PADDING_BYTES(2); + u32_le program_id; + u32_le private_id; + u8 flag; + u8 flag2; + INSERT_PADDING_BYTES(2); + std::array hmac_key; + INSERT_PADDING_BYTES(4); + struct Time { + u32_le year; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + u8 millisecond; + u8 microsecond; + u8 padding; + } last_accessed; + INSERT_PADDING_BYTES(4); + Time last_received; + INSERT_PADDING_BYTES(4); + Time unknown_time; + }; + static_assert(sizeof(CecMBoxInfoHeader) == 0x60, + "CecMBoxInfoHeader struct has incorrect size."); + + struct CecMBoxListHeader { + u16_le magic; // 0x6868 'hh' + INSERT_PADDING_BYTES(2); + u16_le version; // 0x01 00, maybe activated flag? + INSERT_PADDING_BYTES(2); + u16_le num_boxes; // 24 max + INSERT_PADDING_BYTES(2); + std::array, 24> box_names; // 16 char names, 24 boxes + }; + static_assert(sizeof(CecMBoxListHeader) == 0x18C, + "CecMBoxListHeader struct has incorrect size."); + + struct CecMessageHeader { + u16_le magic; // 0x6060 `` + INSERT_PADDING_BYTES(2); + u32_le message_size; + u32_le header_size; + u32_le body_size; + + u32_le title_id; + u32_le title_id2; + u32_le batch_id; + u32_le unknown_id; + + std::array message_id; + u32_le version; + std::array message_id2; + u8 flag; + u8 send_method; + u8 is_unopen; + u8 is_new; + u64_le sender_id; + u64_le sender_id2; + struct Time { + u32_le year; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + u8 millisecond; + u8 microsecond; + u8 padding; + } send_time, recv_time, create_time; + u8 send_count; + u8 forward_count; + u16_le user_data; + }; + static_assert(sizeof(CecMessageHeader) == 0x70, "CecMessageHeader struct has incorrect size."); + + struct CecOBIndexHeader { + u16_le magic; // 0x6767 'gg' + INSERT_PADDING_BYTES(2); + u32_le message_num; + // Array of messageid's 8 bytes each, same as CecMessageHeader.message_id[8] + }; + static_assert(sizeof(CecOBIndexHeader) == 0x08, "CecOBIndexHeader struct has incorrect size."); + + enum class CecdState : u32 { + NdmStatusWorking = 0, + NdmStatusIdle = 1, + NdmStatusSuspending = 2, + NdmStatusSuspended = 3, + }; + + union CecOpenMode { + u32 raw; + BitField<0, 1, u32> unknown; // 1 delete? + BitField<1, 1, u32> read; // 2 + BitField<2, 1, u32> write; // 4 + BitField<3, 1, u32> create; // 8 + BitField<4, 1, u32> check; // 16 maybe validate sig? + BitField<30, 1, u32> unk_flag; + }; + + enum class CecTest : u32 { + CEC_TEST_000 = 0, + CEC_TEST_001 = 1, + CEC_TEST_002 = 2, + CEC_TEST_003 = 3, + CEC_TEST_004 = 4, + CEC_TEST_005 = 5, + CEC_TEST_006 = 6, + }; + + /// Opening a file and reading/writing can be handled by two different functions + /// So, we need to pass that file data around + struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { + SessionData(); + ~SessionData(); + + u32 ncch_program_id; + CecDataPathType data_path_type; + CecOpenMode open_mode; + FileSys::Path path; + + std::shared_ptr file; + }; + + class Interface : public ServiceFramework { public: Interface(std::shared_ptr cecd, const char* name, u32 max_session); ~Interface() = default; protected: /** - * GetCecStateAbbreviated service function + * CECD::Open service function * Inputs: - * 0: 0x000E0000 + * 0 : Header Code[0x000100C2] + * 1 : NCCH Program ID + * 2 : Path type + * 3 : File open flag + * 4 : Descriptor for process ID + * 5 : Placeholder for process ID + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : File size? + */ + void Open(Kernel::HLERequestContext& ctx); + + /** + * CECD::Read service function + * Inputs: + * 0 : Header Code[0x00020042] + * 1 : Buffer size (unused) + * 2 : Descriptor for mapping a write-only buffer in the target process + * 3 : Buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Read size + * 3 : Descriptor for mapping a write-only buffer in the target process + * 4 : Buffer address + */ + void Read(Kernel::HLERequestContext& ctx); + + /** + * CECD::ReadMessage service function + * Inputs: + * 0 : Header Code[0x00030104] + * 1 : NCCH Program ID + * 2 : bool is_outbox + * 3 : Message ID size (unused, always 8) + * 4 : Buffer size (unused) + * 5 : Descriptor for mapping a read-only buffer in the target process + * 6 : Message ID address + * 7 : Descriptor for mapping a write-only buffer in the target process + * 8 : Buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Read size + * 3 : Descriptor for mapping a read-only buffer in the target process + * 4 : Message ID address + * 5 : Descriptor for mapping a write-only buffer in the target process + * 6 : Buffer address + */ + void ReadMessage(Kernel::HLERequestContext& ctx); + + /** + * CECD::ReadMessageWithHMAC service function + * Inputs: + * 0 : Header Code[0x00040106] + * 1 : NCCH Program ID + * 2 : bool is_outbox + * 3 : Message ID size(unused, always 8) + * 4 : Buffer size(unused) + * 5 : Descriptor for mapping a read-only buffer in the target process + * 6 : Message ID address + * 7 : Descriptor for mapping a read-only buffer in the target process + * 8 : HMAC key address + * 9 : Descriptor for mapping a write-only buffer in the target process + * 10 : Buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Read size + * 3 : Descriptor for mapping a read-only buffer in the target process + * 4 : Message ID address + * 5 : Descriptor for mapping a read-only buffer in the target process + * 6 : HMAC key address + * 7 : Descriptor for mapping a write-only buffer in the target process + * 8 : Buffer address + */ + void ReadMessageWithHMAC(Kernel::HLERequestContext& ctx); + + /** + * CECD::Write service function + * Inputs: + * 0 : Header Code[0x00050042] + * 1 : Buffer size(unused) + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Buffer address + */ + void Write(Kernel::HLERequestContext& ctx); + + /** + * CECD::WriteMessage service function + * Inputs: + * 0 : Header Code[0x00060104] + * 1 : NCCH Program ID + * 2 : bool is_outbox + * 3 : Message ID size(unused, always 8) + * 4 : Buffer size(unused) + * 5 : Descriptor for mapping a read-only buffer in the target process + * 6 : Buffer address + * 7 : Descriptor for mapping a read/write buffer in the target process + * 8 : Message ID address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Buffer address + * 4 : Descriptor for mapping a read/write buffer in the target process + * 5 : Message ID address + */ + void WriteMessage(Kernel::HLERequestContext& ctx); + + /** + * CECD::WriteMessageWithHMAC service function + * Inputs: + * 0 : Header Code[0x00070106] + * 1 : NCCH Program ID + * 2 : bool is_outbox + * 3 : Message ID size(unused, always 8) + * 4 : Buffer size(unused) + * 5 : Descriptor for mapping a read-only buffer in the target process + * 6 : Buffer address + * 7 : Descriptor for mapping a read-only buffer in the target process + * 8 : HMAC key address + * 9 : Descriptor for mapping a read/write buffer in the target process + * 10 : Message ID address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Buffer address + * 4 : Descriptor for mapping a read-only buffer in the target process + * 5 : HMAC key address + * 6 : Descriptor for mapping a read/write buffer in the target process + * 7 : Message ID address + */ + void WriteMessageWithHMAC(Kernel::HLERequestContext& ctx); + + /** + * CECD::Delete service function + * Inputs: + * 0 : Header Code[0x00080102] + * 1 : NCCH Program ID + * 2 : Path type + * 3 : bool is_outbox + * 4 : Message ID size (unused) + * 5 : Descriptor for mapping a read-only buffer in the target process + * 6 : Message ID address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Message ID address + */ + void Delete(Kernel::HLERequestContext& ctx); + + /** + * CECD::SetData service function + * Inputs: + * 0 : Header Code[0x000900C2] + * 1 : NCCH Program ID + * 2 : Path type + * 3 : bool is_outbox + * 4 : Descriptor for mapping a read-only buffer in the target process + * 5 : Message ID address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Message ID address + */ + void SetData(Kernel::HLERequestContext& ctx); + + /** + * CECD::ReadData service function + * Inputs: + * 0 : Header Code[0x000A00C4] + * 1 : Destination buffer size (unused) + * 2 : Info type + * 3 : Param buffer size (unused) + * 4 : Descriptor for mapping a read-only buffer in the target process + * 5 : Param buffer address + * 6 : Descriptor for mapping a write-only buffer in the target process + * 7 : Destination buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Param buffer address + * 4 : Descriptor for mapping a write-only buffer in the target process + * 5 : Destination buffer address + */ + void ReadData(Kernel::HLERequestContext& ctx); + + /** + * CECD::Start service function + * Inputs: + * 0 : Header Code[0x000B0040] + * 1 : Command + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void Start(Kernel::HLERequestContext& ctx); + + /** + * CECD::Stop service function + * Inputs: + * 0 : Header Code[0x000C0040] + * 1 : Command + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void Stop(Kernel::HLERequestContext& ctx); + + /** + * CECD::GetCecInfoBuffer service function + * Inputs: + * 0 : Header Code[0x000D0082] + * 1 : unknown + * 2 : unknown, buffer size? + * 3 : Descriptor for mapping a write-only buffer in the target process + * 4 : Destination buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2-3 : MappedBuffer + */ + void GetCecInfoBuffer(Kernel::HLERequestContext& ctx); + + /** + * GetCecdState service function + * Inputs: + * 0: Header Code[0x000E0000] * Outputs: * 1: ResultCode - * 2: CecStateAbbreviated + * 2: CecdState */ - void GetCecStateAbbreviated(Kernel::HLERequestContext& ctx); + void GetCecdState(Kernel::HLERequestContext& ctx); /** * GetCecInfoEventHandle service function * Inputs: - * 0: 0x000F0000 + * 0: Header Code[0x000F0000] * Outputs: * 1: ResultCode * 3: Event Handle @@ -53,18 +494,111 @@ public: /** * GetChangeStateEventHandle service function * Inputs: - * 0: 0x00100000 + * 0: Header Code[0x00100000] * Outputs: * 1: ResultCode * 3: Event Handle */ void GetChangeStateEventHandle(Kernel::HLERequestContext& ctx); + /** + * CECD::OpenAndWrite service function + * Inputs: + * 0 : Header Code[0x00110104] + * 1 : Buffer size (unused) + * 2 : NCCH Program ID + * 3 : Path type + * 4 : File open flag + * 5 : Descriptor for process ID + * 6 : Placeholder for process ID + * 7 : Descriptor for mapping a read-only buffer in the target process + * 8 : Buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Descriptor for mapping a read-only buffer in the target process + * 3 : Buffer address + */ + void OpenAndWrite(Kernel::HLERequestContext& ctx); + + /** + * CECD::OpenAndRead service function + * Inputs: + * 0 : Header Code[0x00120104] + * 1 : Buffer size (unused) + * 2 : NCCH Program ID + * 3 : Path type + * 4 : File open flag + * 5 : Descriptor for process ID + * 6 : Placeholder for process ID + * 7 : Descriptor for mapping a write-only buffer in the target process + * 8 : Buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Toal bytes read + * 3 : Descriptor for mapping a write-only buffer in the target process + * 4 : Buffer address + */ + void OpenAndRead(Kernel::HLERequestContext& ctx); + + /** + * CECD::GetEventLog service function + * Inputs: + * 0 : Header Code[0x001E0082] + * 1 : unknown + * 2 : unknown + * 3 : buffer descriptor + * 4 : buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : unknown + */ + void GetEventLog(Kernel::HLERequestContext& ctx); + + /** + * CECD::GetEventLogStart service function + * Inputs: + * 0 : Header Code[0x001F0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : unknown + */ + void GetEventLogStart(Kernel::HLERequestContext& ctx); + + /** + * GetCecInfoEventHandleSys service function + * Inputs: + * 0: Header Code[0x40020002] + * Outputs: + * 1: ResultCode + * 3: Event Handle + */ + void GetCecInfoEventHandleSys(Kernel::HLERequestContext& ctx); + private: std::shared_ptr cecd; }; private: + /// String used by cecd for base64 encoding found in the sysmodule disassembly + const std::string base64_dict = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; + + const std::vector cecd_system_savedata_id = {0x00, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x01, 0x00}; + + /// Encoding function used for the message id + std::string EncodeBase64(const std::vector& in) const; + + std::string GetCecDataPathTypeAsString(const CecDataPathType type, const u32 program_id, + const std::vector& msg_id = std::vector()) const; + + std::string GetCecCommandAsString(const CecCommand command) const; + + void CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_program_id, + std::vector& file_buffer); + + Service::FS::ArchiveHandle cecd_system_save_data_archive; + Kernel::SharedPtr cecinfo_event; Kernel::SharedPtr change_state_event; }; diff --git a/src/core/hle/service/cecd/cecd_s.cpp b/src/core/hle/service/cecd/cecd_s.cpp index a17278b29..3395c405d 100644 --- a/src/core/hle/service/cecd/cecd_s.cpp +++ b/src/core/hle/service/cecd/cecd_s.cpp @@ -11,22 +11,28 @@ CECD_S::CECD_S(std::shared_ptr cecd) static const FunctionInfo functions[] = { // cecd:u shared commands // clang-format off - {0x000100C2, nullptr, "OpenRawFile"}, - {0x00020042, nullptr, "ReadRawFile"}, - {0x00030104, nullptr, "ReadMessage"}, - {0x00040106, nullptr, "ReadMessageWithHMAC"}, - {0x00050042, nullptr, "WriteRawFile"}, - {0x00060104, nullptr, "WriteMessage"}, - {0x00070106, nullptr, "WriteMessageWithHMAC"}, - {0x00080102, nullptr, "Delete"}, - {0x000A00C4, nullptr, "GetSystemInfo"}, - {0x000B0040, nullptr, "RunCommand"}, - {0x000C0040, nullptr, "RunCommandAlt"}, - {0x000E0000, &CECD_S::GetCecStateAbbreviated, "GetCecStateAbbreviated"}, + {0x000100C2, &CECD_S::Open, "Open"}, + {0x00020042, &CECD_S::Read, "Read"}, + {0x00030104, &CECD_S::ReadMessage, "ReadMessage"}, + {0x00040106, &CECD_S::ReadMessageWithHMAC, "ReadMessageWithHMAC"}, + {0x00050042, &CECD_S::Write, "Write"}, + {0x00060104, &CECD_S::WriteMessage, "WriteMessage"}, + {0x00070106, &CECD_S::WriteMessageWithHMAC, "WriteMessageWithHMAC"}, + {0x00080102, &CECD_S::Delete, "Delete"}, + {0x000900C2, &CECD_S::SetData, "SetData"}, + {0x000A00C4, &CECD_S::ReadData, "ReadData"}, + {0x000B0040, &CECD_S::Start, "Start"}, + {0x000C0040, &CECD_S::Stop, "Stop"}, + {0x000D0082, &CECD_S::GetCecInfoBuffer, "GetCecInfoBuffer"}, + {0x000E0000, &CECD_S::GetCecdState, "GetCecdState"}, {0x000F0000, &CECD_S::GetCecInfoEventHandle, "GetCecInfoEventHandle"}, {0x00100000, &CECD_S::GetChangeStateEventHandle, "GetChangeStateEventHandle"}, - {0x00110104, nullptr, "OpenAndWrite"}, - {0x00120104, nullptr, "OpenAndRead"}, + {0x00110104, &CECD_S::OpenAndWrite, "OpenAndWrite"}, + {0x00120104, &CECD_S::OpenAndRead, "OpenAndRead"}, + {0x001E0082, nullptr, "GetEventLog"}, + {0x001F0000, nullptr, "GetEventLogStart"}, + // cecd:s commands + {0x04020002, nullptr, "GetCecInfoEventHandleSys"}, // clang-format on }; diff --git a/src/core/hle/service/cecd/cecd_u.cpp b/src/core/hle/service/cecd/cecd_u.cpp index 5d392848e..81fcd1019 100644 --- a/src/core/hle/service/cecd/cecd_u.cpp +++ b/src/core/hle/service/cecd/cecd_u.cpp @@ -11,22 +11,26 @@ CECD_U::CECD_U(std::shared_ptr cecd) static const FunctionInfo functions[] = { // cecd:u shared commands // clang-format off - {0x000100C2, nullptr, "OpenRawFile"}, - {0x00020042, nullptr, "ReadRawFile"}, - {0x00030104, nullptr, "ReadMessage"}, - {0x00040106, nullptr, "ReadMessageWithHMAC"}, - {0x00050042, nullptr, "WriteRawFile"}, - {0x00060104, nullptr, "WriteMessage"}, - {0x00070106, nullptr, "WriteMessageWithHMAC"}, - {0x00080102, nullptr, "Delete"}, - {0x000A00C4, nullptr, "GetSystemInfo"}, - {0x000B0040, nullptr, "RunCommand"}, - {0x000C0040, nullptr, "RunCommandAlt"}, - {0x000E0000, &CECD_U::GetCecStateAbbreviated, "GetCecStateAbbreviated"}, + {0x000100C2, &CECD_U::Open, "Open"}, + {0x00020042, &CECD_U::Read, "Read"}, + {0x00030104, &CECD_U::ReadMessage, "ReadMessage"}, + {0x00040106, &CECD_U::ReadMessageWithHMAC, "ReadMessageWithHMAC"}, + {0x00050042, &CECD_U::Write, "Write"}, + {0x00060104, &CECD_U::WriteMessage, "WriteMessage"}, + {0x00070106, &CECD_U::WriteMessageWithHMAC, "WriteMessageWithHMAC"}, + {0x00080102, &CECD_U::Delete, "Delete"}, + {0x000900C2, &CECD_U::SetData, "SetData"}, + {0x000A00C4, &CECD_U::ReadData, "ReadData"}, + {0x000B0040, &CECD_U::Start, "Start"}, + {0x000C0040, &CECD_U::Stop, "Stop"}, + {0x000D0082, &CECD_U::GetCecInfoBuffer, "GetCecInfoBuffer"}, + {0x000E0000, &CECD_U::GetCecdState, "GetCecdState"}, {0x000F0000, &CECD_U::GetCecInfoEventHandle, "GetCecInfoEventHandle"}, {0x00100000, &CECD_U::GetChangeStateEventHandle, "GetChangeStateEventHandle"}, - {0x00110104, nullptr, "OpenAndWrite"}, - {0x00120104, nullptr, "OpenAndRead"}, + {0x00110104, &CECD_U::OpenAndWrite, "OpenAndWrite"}, + {0x00120104, &CECD_U::OpenAndRead, "OpenAndRead"}, + {0x001E0082, nullptr, "GetEventLog"}, + {0x001F0000, nullptr, "GetEventLogStart"}, // clang-format on };