mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Implement NEWS service (#7377)
This commit is contained in:
		
							parent
							
								
									549fdd0736
								
							
						
					
					
						commit
						89e13a85a7
					
				
					 8 changed files with 1249 additions and 85 deletions
				
			
		|  | @ -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
 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue