Implement NEWS service (#7377)

This commit is contained in:
Daniel López Guimaraes 2024-01-24 18:21:48 +01:00 committed by GitHub
parent 549fdd0736
commit 89e13a85a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1249 additions and 85 deletions

View File

@ -115,6 +115,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, Y2R) \
SUB(Service, PS) \
SUB(Service, PLGLDR) \
SUB(Service, NEWS) \
CLS(HW) \
SUB(HW, Memory) \
SUB(HW, LCD) \

View File

@ -82,6 +82,7 @@ enum class Class : u8 {
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
Service_PS, ///< The PS (Process) service
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
Service_NEWS, ///< The NEWS (Notifications) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation

View File

@ -2,18 +2,765 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <fmt/format.h>
#include "common/archives.h"
#include "common/assert.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/file_backend.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/result.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/hle/service/news/news.h"
#include "core/hle/service/news/news_s.h"
#include "core/hle/service/news/news_u.h"
#include "core/hle/service/service.h"
SERVICE_CONSTRUCT_IMPL(Service::NEWS::Module)
namespace Service::NEWS {
namespace ErrCodes {
enum {
/// This error is returned if either the NewsDB header or the header for a notification ID is
/// invalid
InvalidHeader = 5,
};
}
constexpr Result ErrorInvalidHeader = // 0xC8A12805
Result(ErrCodes::InvalidHeader, ErrorModule::News, ErrorSummary::InvalidState,
ErrorLevel::Status);
constexpr std::array<u8, 8> news_system_savedata_id{
0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x01, 0x00,
};
template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) {
ar& db;
ar& notification_ids;
ar& automatic_sync_flag;
ar& news_system_save_data_archive;
}
SERIALIZE_IMPL(Module)
void Module::Interface::AddNotificationImpl(Kernel::HLERequestContext& ctx, bool news_s) {
IPC::RequestParser rp(ctx);
const u32 header_size = rp.Pop<u32>();
const u32 message_size = rp.Pop<u32>();
const u32 image_size = rp.Pop<u32>();
u32 process_id;
if (!news_s) {
process_id = rp.PopPID();
LOG_INFO(Service_NEWS,
"called header_size=0x{:x}, message_size=0x{:x}, image_size=0x{:x}, process_id={}",
header_size, message_size, image_size, process_id);
} else {
LOG_INFO(Service_NEWS, "called header_size=0x{:x}, message_size=0x{:x}, image_size=0x{:x}",
header_size, message_size, image_size);
}
auto header_buffer = rp.PopMappedBuffer();
auto message_buffer = rp.PopMappedBuffer();
auto image_buffer = rp.PopMappedBuffer();
NotificationHeader header{};
header_buffer.Read(&header, 0,
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(header_size)));
std::vector<u8> message(message_size);
message_buffer.Read(message.data(), 0, message.size());
std::vector<u8> image(image_size);
image_buffer.Read(image.data(), 0, image.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 6);
SCOPE_EXIT({
rb.PushMappedBuffer(header_buffer);
rb.PushMappedBuffer(message_buffer);
rb.PushMappedBuffer(image_buffer);
});
if (!news_s) {
// Set the program_id using the input process ID
auto fs_user = news->system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
ASSERT_MSG(fs_user != nullptr, "fs:USER service is missing.");
auto program_info_result = fs_user->GetProgramLaunchInfo(process_id);
if (program_info_result.Failed()) {
rb.Push(program_info_result.Code());
return;
}
header.program_id = program_info_result.Unwrap().program_id;
// The date_time is set by the sysmodule on news:u requests
auto& share_page = news->system.Kernel().GetSharedPageHandler();
header.date_time = share_page.GetSystemTimeSince2000();
}
const auto save_result = news->SaveNotification(&header, header_size, message, image);
if (R_FAILED(save_result)) {
rb.Push(save_result);
return;
}
// Mark the DB header new notification flag
if ((news->db.header.flags & 1) == 0) {
news->db.header.flags |= 1;
const auto db_result = news->SaveNewsDBSavedata();
if (R_FAILED(db_result)) {
rb.Push(db_result);
return;
}
}
rb.Push(ResultSuccess);
}
void Module::Interface::AddNotification(Kernel::HLERequestContext& ctx) {
AddNotificationImpl(ctx, false);
}
void Module::Interface::AddNotificationSystem(Kernel::HLERequestContext& ctx) {
AddNotificationImpl(ctx, true);
}
void Module::Interface::ResetNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_INFO(Service_NEWS, "called");
// Cleanup the sorted notification IDs
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
news->notification_ids[i] = i;
}
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
FileSys::Path archive_path(news_system_savedata_id);
// Format the SystemSaveData archive 0x00010035
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
// NOTE: The original sysmodule doesn't clear the News DB in memory
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_INFO(Service_NEWS, "called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(static_cast<u32>(news->GetTotalNotifications()));
}
void Module::Interface::SetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called size=0x{:x}", size);
NewsDBHeader header{};
input_buffer.Read(&header, 0, std::min(sizeof(NewsDBHeader), static_cast<std::size_t>(size)));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(news->SetNewsDBHeader(&header, size));
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::SetNotificationHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
NotificationHeader header{};
input_buffer.Read(&header, 0,
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(size)));
const auto result = news->SetNotificationHeader(notification_index, &header, size);
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout status of the
// source program on SpotPass with the boss:P command 0x00040600C0 (possibly named
// SetOptoutFlagPrivileged?) using the program_id and flag_optout as parameter
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(result);
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::SetNotificationMessage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> data(size);
input_buffer.Read(data.data(), 0, data.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(news->SetNotificationMessage(notification_index, data));
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::SetNotificationImage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto input_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> data(size);
input_buffer.Read(data.data(), 0, data.size());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(news->SetNotificationImage(notification_index, data));
rb.PushMappedBuffer(input_buffer);
}
void Module::Interface::GetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called size=0x{:x}", size);
NewsDBHeader header{};
const auto result = news->GetNewsDBHeader(&header, size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
} else {
const auto copied_size = result.Unwrap();
output_buffer.Write(&header, 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
rb.PushMappedBuffer(output_buffer);
}
void Module::Interface::GetNotificationHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
NotificationHeader header{};
const auto result = news->GetNotificationHeader(notification_index, &header, size);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
return;
}
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout flag of the
// header with the result of boss:P command 0x0004070080 (possibly named
// GetOptoutFlagPrivileged?) using the program_id as parameter
const auto copied_size = result.Unwrap();
output_buffer.Write(&header, 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
void Module::Interface::GetNotificationMessage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> message(size);
const auto result = news->GetNotificationMessage(notification_index, message);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
return;
}
const auto copied_size = result.Unwrap();
output_buffer.Write(message.data(), 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
void Module::Interface::GetNotificationImage(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
std::vector<u8> image(size);
const auto result = news->GetNotificationImage(notification_index, image);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
if (result.Failed()) {
rb.Push(result.Code());
rb.Push<u32>(0);
return;
}
const auto copied_size = result.Unwrap();
output_buffer.Write(image.data(), 0, copied_size);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(copied_size));
}
void Module::Interface::SetAutomaticSyncFlag(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u8 flag = rp.Pop<u8>();
LOG_INFO(Service_NEWS, "called flag=0x{:x}", flag);
news->automatic_sync_flag = flag;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void Module::Interface::SetNotificationHeaderOther(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 notification_index = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
NotificationHeader header{};
output_buffer.Read(&header, 0,
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(size)));
const auto result = news->SetNotificationHeaderOther(notification_index, &header, size);
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout status of the
// source program on SpotPass with the boss:P command 0x00040600C0 (possibly named
// SetOptoutFlagPrivileged?) using the program_id and flag_optout as parameter
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(result);
rb.PushMappedBuffer(output_buffer);
}
void Module::Interface::WriteNewsDBSavedata(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_INFO(Service_NEWS, "called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(news->SaveNewsDBSavedata());
}
void Module::Interface::GetTotalArrivedNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_WARNING(Service_NEWS, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(0); // Total number of pending BOSS notifications to be synced
}
std::size_t Module::GetTotalNotifications() {
return std::count_if(
notification_ids.begin(), notification_ids.end(),
[this](const u32 notification_id) { return db.notifications[notification_id].IsValid(); });
}
ResultVal<std::size_t> Module::GetNewsDBHeader(NewsDBHeader* header, const std::size_t size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
const std::size_t copy_size = std::min(sizeof(NewsDBHeader), size);
std::memcpy(header, &db.header, copy_size);
return copy_size;
}
ResultVal<std::size_t> Module::GetNotificationHeader(const u32 notification_index,
NotificationHeader* header,
const std::size_t size) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
if (!db.notifications[notification_id].IsValid()) {
return ErrorInvalidHeader;
}
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
std::memcpy(header, &db.notifications[notification_id], copy_size);
return copy_size;
}
ResultVal<std::size_t> Module::GetNotificationMessage(const u32 notification_index,
std::span<u8> message) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
if (!db.notifications[notification_id].IsValid()) {
return ErrorInvalidHeader;
}
std::string message_file = fmt::format("/news{:03d}.txt", notification_id);
const auto result = LoadFileFromSavedata(message_file, message);
if (result.Failed()) {
return result.Code();
}
return result.Unwrap();
}
ResultVal<std::size_t> Module::GetNotificationImage(const u32 notification_index,
std::span<u8> image) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
if (!db.notifications[notification_id].IsValid()) {
return ErrorInvalidHeader;
}
std::string image_file = fmt::format("/news{:03d}.mpo", notification_id);
const auto result = LoadFileFromSavedata(image_file, image);
if (result.Failed()) {
return result.Code();
}
return result.Unwrap();
}
Result Module::SetNewsDBHeader(const NewsDBHeader* header, const std::size_t size) {
const std::size_t copy_size = std::min(sizeof(NewsDBHeader), static_cast<std::size_t>(size));
std::memcpy(&db.header, header, copy_size);
return SaveNewsDBSavedata();
}
Result Module::SetNotificationHeader(const u32 notification_index, const NotificationHeader* header,
const std::size_t size) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
std::memcpy(&db.notifications[notification_id], header, copy_size);
return SaveNewsDBSavedata();
}
Result Module::SetNotificationHeaderOther(const u32 notification_index,
const NotificationHeader* header,
const std::size_t size) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
std::memcpy(&db.notifications[notification_id], header, copy_size);
return ResultSuccess;
}
Result Module::SetNotificationMessage(const u32 notification_index, std::span<const u8> message) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::string message_file = fmt::format("/news{:03d}.txt", notification_id);
return SaveFileToSavedata(message_file, message);
}
Result Module::SetNotificationImage(const u32 notification_index, std::span<const u8> image) {
if (notification_index >= MAX_NOTIFICATIONS) {
return ErrorInvalidHeader;
}
const u32 notification_id = notification_ids[notification_index];
const std::string image_file = fmt::format("/news{:03d}.mpo", notification_id);
return SaveFileToSavedata(image_file, image);
}
Result Module::SaveNotification(const NotificationHeader* header, const std::size_t header_size,
std::span<const u8> message, std::span<const u8> image) {
if (!db.header.IsValid()) {
return ErrorInvalidHeader;
}
if (!header->IsValid()) {
return ErrorInvalidHeader;
}
u32 notification_count = static_cast<u32>(GetTotalNotifications());
// If we have reached the limit of 100 notifications, delete the oldest one
if (notification_count >= MAX_NOTIFICATIONS) {
LOG_WARNING(Service_NEWS,
"Notification limit has been reached. Deleting oldest notification ID: {}",
notification_ids[0]);
R_TRY(DeleteNotification(notification_ids[0]));
notification_count--;
}
// Check if there is enough space for storing the new notification data. The header is already
// allocated with the News DB
const u64 needed_space = static_cast<u64>(message.size() + image.size());
while (notification_count > 0) {
const u64 free_space = news_system_save_data_archive->GetFreeBytes();
if (needed_space <= free_space) {
break;
}
LOG_WARNING(Service_NEWS, "Not enough space available. Deleting oldest notification ID: {}",
notification_ids[0]);
// If we don't have space, delete old notifications until we do
R_TRY(DeleteNotification(notification_ids[0]));
notification_count--;
}
LOG_DEBUG(Service_NEWS, "New notification: notification_id={}, title={}",
notification_ids[notification_count], Common::UTF16BufferToUTF8(header->title));
if (!image.empty()) {
R_TRY(SetNotificationImage(notification_count, image));
}
if (!message.empty()) {
R_TRY(SetNotificationMessage(notification_count, message));
}
R_TRY(SetNotificationHeader(notification_count, header, header_size));
// Sort the notifications after saving
std::sort(notification_ids.begin(), notification_ids.end(),
[this](const u32 first_id, const u32 second_id) -> bool {
return CompareNotifications(first_id, second_id);
});
return ResultSuccess;
}
Result Module::DeleteNotification(const u32 notification_id) {
bool deleted = false;
// Check if the input notification ID exists, and clear it
if (db.notifications[notification_id].IsValid()) {
db.notifications[notification_id] = {};
R_TRY(SaveNewsDBSavedata());
deleted = true;
}
// Cleanup images and messages for invalid notifications
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
if (!db.notifications[i].IsValid()) {
const std::string image_file = fmt::format("/news{:03d}.mpo", i);
auto result = news_system_save_data_archive->DeleteFile(image_file);
if (R_FAILED(result) && result != FileSys::ResultFileNotFound) {
return result;
}
const std::string message_file = fmt::format("/news{:03d}.txt", i);
result = news_system_save_data_archive->DeleteFile(message_file);
if (R_FAILED(result) && result != FileSys::ResultFileNotFound) {
return result;
}
}
}
// If the input notification ID was deleted, reorder the notification IDs list
if (deleted) {
std::sort(notification_ids.begin(), notification_ids.end(),
[this](const u32 first_id, const u32 second_id) -> bool {
return CompareNotifications(first_id, second_id);
});
}
return ResultSuccess;
}
Result Module::LoadNewsDBSavedata() {
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
// Open the SystemSaveData archive 0x00010035
FileSys::Path archive_path(news_system_savedata_id);
auto archive_result = systemsavedata_factory.Open(archive_path, 0);
// If the archive didn't exist, create the files inside
if (archive_result.Code() == FileSys::ResultNotFound) {
// 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
news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
} else {
ASSERT_MSG(archive_result.Succeeded(), "Could not open the NEWS SystemSaveData archive!");
news_system_save_data_archive = std::move(archive_result).Unwrap();
}
const std::string news_db_file = "/news.db";
auto news_result =
LoadFileFromSavedata(news_db_file, std::span{reinterpret_cast<u8*>(&db), sizeof(NewsDB)});
// Read the file if it already exists
if (news_result.Failed()) {
// Create the file immediately if it doesn't exist
db.header = {.valid = 1};
news_result = SaveFileToSavedata(
news_db_file, std::span{reinterpret_cast<const u8*>(&db), sizeof(NewsDB)});
} else {
// Sort the notifications from the file
std::sort(notification_ids.begin(), notification_ids.end(),
[this](const u32 first_id, const u32 second_id) -> bool {
return CompareNotifications(first_id, second_id);
});
}
return news_result.Code();
}
Result Module::SaveNewsDBSavedata() {
return SaveFileToSavedata("/news.db",
std::span{reinterpret_cast<const u8*>(&db), sizeof(NewsDB)});
}
ResultVal<std::size_t> Module::LoadFileFromSavedata(std::string filename, std::span<u8> buffer) {
FileSys::Mode mode = {};
mode.read_flag.Assign(1);
FileSys::Path path(filename);
auto result = news_system_save_data_archive->OpenFile(path, mode);
if (result.Failed()) {
return result.Code();
}
auto file = std::move(result).Unwrap();
const auto bytes_read = file->Read(0, buffer.size(), buffer.data());
file->Close();
ASSERT_MSG(bytes_read.Succeeded(), "could not read file");
return bytes_read.Unwrap();
}
Result Module::SaveFileToSavedata(std::string filename, std::span<const u8> buffer) {
FileSys::Mode mode = {};
mode.write_flag.Assign(1);
mode.create_flag.Assign(1);
FileSys::Path path(filename);
auto result = news_system_save_data_archive->OpenFile(path, mode);
ASSERT_MSG(result.Succeeded(), "could not open file");
auto file = std::move(result).Unwrap();
file->Write(0, buffer.size(), 1, buffer.data());
file->Close();
return ResultSuccess;
}
bool Module::CompareNotifications(const u32 first_id, const u32 second_id) {
// Notification IDs are sorted by date time, with valid notifications being first.
// This is done so that other system applications like the News applet can easily
// iterate over the notifications with an incrementing index.
ASSERT(first_id < MAX_NOTIFICATIONS && second_id < MAX_NOTIFICATIONS);
if (!db.notifications[first_id].IsValid()) {
return false;
}
if (!db.notifications[second_id].IsValid()) {
return true;
}
return db.notifications[first_id].date_time < db.notifications[second_id].date_time;
}
Module::Interface::Interface(std::shared_ptr<Module> news, const char* name, u32 max_session)
: ServiceFramework(name, max_session), news(std::move(news)) {}
Module::Module(Core::System& system_) : system(system_) {
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
notification_ids[i] = i;
}
LoadNewsDBSavedata();
}
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
std::make_shared<NEWS_S>()->InstallAsService(service_manager);
std::make_shared<NEWS_U>()->InstallAsService(service_manager);
auto news = std::make_shared<Module>(system);
std::make_shared<NEWS_S>(news)->InstallAsService(service_manager);
std::make_shared<NEWS_U>(news)->InstallAsService(service_manager);
}
} // namespace Service::NEWS

View File

@ -4,12 +4,482 @@
#pragma once
#include "core/file_sys/archive_backend.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::NEWS {
constexpr u32 MAX_NOTIFICATIONS = 100;
struct NewsDBHeader {
u8 valid;
u8 flags;
INSERT_PADDING_BYTES(0xE);
bool IsValid() const {
return valid == 1;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& valid;
ar& flags;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NewsDBHeader) == 0x10, "News DB Header structure size is wrong");
struct NotificationHeader {
u8 flag_valid;
u8 flag_read;
u8 flag_jpeg;
u8 flag_boss;
u8 flag_optout;
u8 flag_url;
u8 flag_unk0x6;
INSERT_PADDING_BYTES(0x1);
u64_le program_id;
u32_le ns_data_id; // Only used in BOSS notifications
u32_le version; // Only used in BOSS notifications
u64_le jump_param;
INSERT_PADDING_BYTES(0x8);
u64_le date_time;
std::array<u16_le, 0x20> title;
bool IsValid() const {
return flag_valid == 1;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& flag_valid;
ar& flag_read;
ar& flag_jpeg;
ar& flag_boss;
ar& flag_optout;
ar& flag_url;
ar& flag_unk0x6;
ar& program_id;
ar& ns_data_id;
ar& version;
ar& jump_param;
ar& date_time;
ar& title;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NotificationHeader) == 0x70, "Notification Header structure size is wrong");
struct NewsDB {
NewsDBHeader header;
std::array<NotificationHeader, MAX_NOTIFICATIONS> notifications;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& header;
ar& notifications;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NewsDB) == 0x2BD0, "News DB structure size is wrong");
class Module final {
public:
explicit Module(Core::System& system_);
~Module() = default;
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> news, const char* name, u32 max_session);
~Interface() = default;
private:
void AddNotificationImpl(Kernel::HLERequestContext& ctx, bool news_s);
protected:
/**
* AddNotification NEWS:U service function.
* Inputs:
* 0 : 0x000100C8
* 1 : Header size
* 2 : Message size
* 3 : Image size
* 4 : PID Translation Header (0x20)
* 5 : Caller PID
* 6 : Header Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 7 : Header Buffer Pointer
* 8 : Message Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 9 : Message Buffer Pointer
* 10 : Image Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 11 : Image Buffer Pointer
* Outputs:
* 0 : 0x00010046
* 1 : Result of function, 0 on success, otherwise error code
*/
void AddNotification(Kernel::HLERequestContext& ctx);
/**
* AddNotification NEWS:S service function.
* Inputs:
* 0 : 0x000100C6
* 1 : Header size
* 2 : Message size
* 3 : Image size
* 4 : Header Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 5 : Header Buffer Pointer
* 6 : Message Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 7 : Message Buffer Pointer
* 8 : Image Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 9 : Image Buffer Pointer
* Outputs:
* 0 : 0x00010046
* 1 : Result of function, 0 on success, otherwise error code
*/
void AddNotificationSystem(Kernel::HLERequestContext& ctx);
/**
* ResetNotifications service function.
* Inputs:
* 0 : 0x00040000
* Outputs:
* 0 : 0x00040040
* 1 : Result of function, 0 on success, otherwise error code
*/
void ResetNotifications(Kernel::HLERequestContext& ctx);
/**
* GetTotalNotifications service function.
* Inputs:
* 0 : 0x00050000
* Outputs:
* 0 : 0x00050080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of notifications
*/
void GetTotalNotifications(Kernel::HLERequestContext& ctx);
/**
* SetNewsDBHeader service function.
* Inputs:
* 0 : 0x00060042
* 1 : Size
* 2 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 3 : Input Buffer Pointer
* Outputs:
* 0 : 0x00060042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNewsDBHeader(Kernel::HLERequestContext& ctx);
/**
* SetNotificationHeader service function.
* Inputs:
* 0 : 0x00070082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00070042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationHeader(Kernel::HLERequestContext& ctx);
/**
* SetNotificationMessage service function.
* Inputs:
* 0 : 0x00080082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00080042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationMessage(Kernel::HLERequestContext& ctx);
/**
* SetNotificationImage service function.
* Inputs:
* 0 : 0x00090082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00090042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationImage(Kernel::HLERequestContext& ctx);
/**
* GetNewsDBHeader service function.
* Inputs:
* 0 : 0x000A0042
* 1 : Size
* 2 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 3 : Output Buffer Pointer
* Outputs:
* 0 : 0x000A0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNewsDBHeader(Kernel::HLERequestContext& ctx);
/**
* GetNotificationHeader service function.
* Inputs:
* 0 : 0x000B0082
* 1 : Notification index
* 2 : Size
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 4 : Output Buffer Pointer
* Outputs:
* 0 : 0x000B0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNotificationHeader(Kernel::HLERequestContext& ctx);
/**
* GetNotificationMessage service function.
* Inputs:
* 0 : 0x000C0082
* 1 : Notification index
* 2 : Size
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 4 : Output Buffer Pointer
* Outputs:
* 0 : 0x000C0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNotificationMessage(Kernel::HLERequestContext& ctx);
/**
* GetNotificationImage service function.
* Inputs:
* 0 : 0x000D0082
* 1 : Notification index
* 2 : Size
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 4 : Output Buffer Pointer
* Outputs:
* 0 : 0x000D0082
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNotificationImage(Kernel::HLERequestContext& ctx);
/**
* SetAutomaticSyncFlag service function.
* Inputs:
* 0 : 0x00110040
* 1 : Flag
* Outputs:
* 0 : 0x00110040
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetAutomaticSyncFlag(Kernel::HLERequestContext& ctx);
/**
* SetNotificationHeaderOther service function.
* Inputs:
* 0 : 0x00120082
* 1 : Notification index
* 2 : Size
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
* 4 : Input Buffer Pointer
* Outputs:
* 0 : 0x00120042
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetNotificationHeaderOther(Kernel::HLERequestContext& ctx);
/**
* WriteNewsDBSavedata service function.
* Inputs:
* 0 : 0x00130000
* Outputs:
* 0 : 0x00130040
* 1 : Result of function, 0 on success, otherwise error code
*/
void WriteNewsDBSavedata(Kernel::HLERequestContext& ctx);
/**
* GetTotalArrivedNotifications service function.
* Inputs:
* 0 : 0x00140000
* Outputs:
* 0 : 0x00140080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of pending notifications to be synced
*/
void GetTotalArrivedNotifications(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> news;
};
private:
/**
* Gets the total number of notifications
* @returns Number of notifications
*/
std::size_t GetTotalNotifications();
/**
* Loads the News DB into the given buffer
* @param header The header buffer
* @param size The size of the header buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNewsDBHeader(NewsDBHeader* header, const std::size_t size);
/**
* Loads the header for a notification ID into the given buffer
* @param notification_index The index of the notification ID
* @param header The header buffer
* @param size The size of the header buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNotificationHeader(const u32 notification_index,
NotificationHeader* header,
const std::size_t size);
/**
* Opens the message file for a notification ID and loads it to the message buffer
* @param notification_index The index of the notification ID
* @param mesasge The message buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNotificationMessage(const u32 notification_index,
std::span<u8> message);
/**
* Opens the image file for a notification ID and loads it to the image buffer
* @param notification_index The index of the notification ID
* @param image The image buffer
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> GetNotificationImage(const u32 notification_index, std::span<u8> image);
/**
* Modifies the header for the News DB in memory and saves the News DB file
* @param header The database header
* @param size The amount of bytes to copy from the header
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNewsDBHeader(const NewsDBHeader* header, const std::size_t size);
/**
* Modifies the header for a notification ID on memory and saves the News DB file
* @param notification_index The index of the notification ID
* @param header The notification header
* @param size The amount of bytes to copy from the header
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationHeader(const u32 notification_index, const NotificationHeader* header,
const std::size_t size);
/**
* Modifies the header for a notification ID on memory. The News DB file isn't updated
* @param notification_index The index of the notification ID
* @param header The notification header
* @param size The amount of bytes to copy from the header
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationHeaderOther(const u32 notification_index,
const NotificationHeader* header, const std::size_t size);
/**
* Sets a given message to a notification ID
* @param notification_index The index of the notification ID
* @param message The notification message
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationMessage(const u32 notification_index, std::span<const u8> message);
/**
* Sets a given image to a notification ID
* @param notification_index The index of the notification ID
* @param image The notification image
* @returns Result indicating the result of the operation, 0 on success
*/
Result SetNotificationImage(const u32 notification_index, std::span<const u8> image);
/**
* Creates a new notification with the given data and saves all the contents
* @param header The notification header
* @param header_size The amount of bytes to copy from the header
* @param message The notification message
* @param image The notification image
* @returns Result indicating the result of the operation, 0 on success
*/
Result SaveNotification(const NotificationHeader* header, const std::size_t header_size,
std::span<const u8> message, std::span<const u8> image);
/**
* Deletes the given notification ID from the database
* @param notification_id The notification ID to delete
* @returns Result indicating the result of the operation, 0 on success
*/
Result DeleteNotification(const u32 notification_id);
/**
* Opens the news.db savedata file and load it to the memory buffer. If the file or the savedata
* don't exist, they are created
* @returns Result indicating the result of the operation, 0 on success
*/
Result LoadNewsDBSavedata();
/**
* Writes the news.db savedata file to the the NEWS system savedata
* @returns Result indicating the result of the operation, 0 on success
*/
Result SaveNewsDBSavedata();
/**
* Opens the file with the given filename inside the NEWS system savedata
* @param filename The file to open
* @param buffer The buffer to output the contents on
* @returns Number of bytes read, or error code
*/
ResultVal<std::size_t> LoadFileFromSavedata(std::string filename, std::span<u8> buffer);
/**
* Writes the file with the given filename inside the NEWS system savedata
* @param filename The output file
* @param buffer The buffer to read the contents from
* @returns Result indicating the result of the operation, 0 on success
*/
Result SaveFileToSavedata(std::string filename, std::span<const u8> buffer);
bool CompareNotifications(const u32 first_id, const u32 second_id);
private:
Core::System& system;
NewsDB db{};
std::array<u32, MAX_NOTIFICATIONS> notification_ids; // Notifications ordered by date time
u8 automatic_sync_flag;
std::unique_ptr<FileSys::ArchiveBackend> news_system_save_data_archive;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
void InstallInterfaces(Core::System& system);
} // namespace Service::NEWS
SERVICE_CONSTRUCT(Service::NEWS::Module)
BOOST_CLASS_EXPORT_KEY(Service::NEWS::Module)

View File

@ -3,63 +3,33 @@
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/news/news_s.h"
SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_S)
namespace Service::NEWS {
struct NewsDbHeader {
u8 unknown_one;
u8 flags;
INSERT_PADDING_BYTES(0xE);
};
static_assert(sizeof(NewsDbHeader) == 0x10, "News DB Header structure size is wrong");
void NEWS_S::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_WARNING(Service, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(0);
}
void NEWS_S::GetNewsDBHeader(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto size = rp.Pop<u32>();
auto output_buffer = rp.PopMappedBuffer();
LOG_WARNING(Service, "(STUBBED) called size={}", size);
NewsDbHeader dummy = {.unknown_one = 1, .flags = 0};
output_buffer.Write(&dummy, 0, std::min(sizeof(NewsDbHeader), static_cast<std::size_t>(size)));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(size);
}
NEWS_S::NEWS_S() : ServiceFramework("news:s", 2) {
NEWS_S::NEWS_S(std::shared_ptr<Module> news) : Module::Interface(std::move(news), "news:s", 2) {
const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "AddNotification"},
{0x0001, &NEWS_S::AddNotificationSystem, "AddNotification"},
{0x0004, &NEWS_S::ResetNotifications, "ResetNotifications"},
{0x0005, &NEWS_S::GetTotalNotifications, "GetTotalNotifications"},
{0x0006, nullptr, "SetNewsDBHeader"},
{0x0007, nullptr, "SetNotificationHeader"},
{0x0008, nullptr, "SetNotificationMessage"},
{0x0009, nullptr, "SetNotificationImage"},
{0x0006, &NEWS_S::SetNewsDBHeader, "SetNewsDBHeader"},
{0x0007, &NEWS_S::SetNotificationHeader, "SetNotificationHeader"},
{0x0008, &NEWS_S::SetNotificationMessage, "SetNotificationMessage"},
{0x0009, &NEWS_S::SetNotificationImage, "SetNotificationImage"},
{0x000A, &NEWS_S::GetNewsDBHeader, "GetNewsDBHeader"},
{0x000B, nullptr, "GetNotificationHeader"},
{0x000C, nullptr, "GetNotificationMessage"},
{0x000D, nullptr, "GetNotificationImage"},
{0x000B, &NEWS_S::GetNotificationHeader, "GetNotificationHeader"},
{0x000C, &NEWS_S::GetNotificationMessage, "GetNotificationMessage"},
{0x000D, &NEWS_S::GetNotificationImage, "GetNotificationImage"},
{0x000E, nullptr, "SetInfoLEDPattern"},
{0x0012, nullptr, "GetNotificationHeaderOther"},
{0x0013, nullptr, "WriteNewsDBSavedata"},
{0x000F, nullptr, "SyncArrivedNotifications"},
{0x0010, nullptr, "SyncOneArrivedNotification"},
{0x0011, &NEWS_S::SetAutomaticSyncFlag, "SetAutomaticSyncFlag"},
{0x0012, &NEWS_S::SetNotificationHeaderOther, "SetNotificationHeaderOther"},
{0x0013, &NEWS_S::WriteNewsDBSavedata, "WriteNewsDBSavedata"},
{0x0014, &NEWS_S::GetTotalArrivedNotifications, "GetTotalArrivedNotifications"},
// clang-format on
};
RegisterHandlers(functions);

View File

@ -4,44 +4,19 @@
#pragma once
#include <memory>
#include "core/hle/service/service.h"
#include "core/hle/service/news/news.h"
namespace Service::NEWS {
class NEWS_S final : public ServiceFramework<NEWS_S> {
class NEWS_S final : public Module::Interface {
public:
NEWS_S();
explicit NEWS_S(std::shared_ptr<Module> news);
private:
/**
* GetTotalNotifications service function.
* Inputs:
* 0 : 0x00050000
* Outputs:
* 0 : 0x00050080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of notifications
*/
void GetTotalNotifications(Kernel::HLERequestContext& ctx);
/**
* GetNewsDBHeader service function.
* Inputs:
* 0 : 0x000A0042
* 1 : Size
* 2 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
* 3 : Output Buffer Pointer
* Outputs:
* 0 : 0x000A0080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Actual Size
*/
void GetNewsDBHeader(Kernel::HLERequestContext& ctx);
SERVICE_SERIALIZATION_SIMPLE
SERVICE_SERIALIZATION(NEWS_S, news, Module)
};
} // namespace Service::NEWS
BOOST_CLASS_EXPORT_KEY(Service::NEWS::NEWS_S)
BOOST_SERIALIZATION_CONSTRUCT(Service::NEWS::NEWS_S)

View File

@ -9,10 +9,10 @@ SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_U)
namespace Service::NEWS {
NEWS_U::NEWS_U() : ServiceFramework("news:u", 1) {
NEWS_U::NEWS_U(std::shared_ptr<Module> news) : Module::Interface(std::move(news), "news:u", 1) {
const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "AddNotification"},
{0x0001, &NEWS_U::AddNotification, "AddNotification"},
// clang-format on
};
RegisterHandlers(functions);

View File

@ -4,19 +4,19 @@
#pragma once
#include <memory>
#include "core/hle/service/service.h"
#include "core/hle/service/news/news.h"
namespace Service::NEWS {
class NEWS_U final : public ServiceFramework<NEWS_U> {
class NEWS_U final : public Module::Interface {
public:
NEWS_U();
explicit NEWS_U(std::shared_ptr<Module> news);
private:
SERVICE_SERIALIZATION_SIMPLE
SERVICE_SERIALIZATION(NEWS_U, news, Module)
};
} // namespace Service::NEWS
BOOST_CLASS_EXPORT_KEY(Service::NEWS::NEWS_U)
BOOST_SERIALIZATION_CONSTRUCT(Service::NEWS::NEWS_U)