1 // Copyright 2015 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <boost/serialization/shared_ptr.hpp>
6 #include <boost/serialization/unique_ptr.hpp>
7 #include <cryptopp/base64.h>
8 #include <cryptopp/hmac.h>
9 #include <cryptopp/sha.h>
10 #include "common/archives.h"
11 #include "common/common_paths.h"
12 #include "common/file_util.h"
13 #include "common/logging/log.h"
14 #include "common/string_util.h"
15 #include "core/core.h"
16 #include "core/file_sys/archive_systemsavedata.h"
17 #include "core/file_sys/directory_backend.h"
18 #include "core/file_sys/errors.h"
19 #include "core/file_sys/file_backend.h"
20 #include "core/hle/ipc_helpers.h"
21 #include "core/hle/kernel/process.h"
22 #include "core/hle/result.h"
23 #include "core/hle/service/cecd/cecd.h"
24 #include "core/hle/service/cecd/cecd_ndm.h"
25 #include "core/hle/service/cecd/cecd_s.h"
26 #include "core/hle/service/cecd/cecd_u.h"
27 #include "core/hle/service/cfg/cfg.h"
28 #include "fmt/format.h"
29 
30 SERVICE_CONSTRUCT_IMPL(Service::CECD::Module)
31 SERIALIZE_EXPORT_IMPL(Service::CECD::Module)
32 SERIALIZE_EXPORT_IMPL(Service::CECD::Module::SessionData)
33 
34 namespace Service::CECD {
35 
36 template <class Archive>
serialize(Archive & ar,const unsigned int)37 void Module::serialize(Archive& ar, const unsigned int) {
38     ar& cecd_system_save_data_archive;
39     ar& cecinfo_event;
40     ar& change_state_event;
41 }
42 SERIALIZE_IMPL(Module)
43 
44 using CecDataPathType = Module::CecDataPathType;
45 using CecOpenMode = Module::CecOpenMode;
46 using CecSystemInfoType = Module::CecSystemInfoType;
47 
Open(Kernel::HLERequestContext & ctx)48 void Module::Interface::Open(Kernel::HLERequestContext& ctx) {
49     IPC::RequestParser rp(ctx, 0x01, 3, 2);
50     const u32 ncch_program_id = rp.Pop<u32>();
51     const CecDataPathType path_type = rp.PopEnum<CecDataPathType>();
52     CecOpenMode open_mode;
53     open_mode.raw = rp.Pop<u32>();
54     rp.PopPID();
55 
56     FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data());
57     FileSys::Mode mode;
58     mode.read_flag.Assign(1);
59     mode.write_flag.Assign(1);
60     mode.create_flag.Assign(1);
61 
62     SessionData* session_data = GetSessionData(ctx.Session());
63     session_data->ncch_program_id = ncch_program_id;
64     session_data->open_mode.raw = open_mode.raw;
65     session_data->data_path_type = path_type;
66     session_data->path = path;
67 
68     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
69     switch (path_type) {
70     case CecDataPathType::RootDir:
71     case CecDataPathType::MboxDir:
72     case CecDataPathType::InboxDir:
73     case CecDataPathType::OutboxDir: {
74         auto dir_result = cecd->cecd_system_save_data_archive->OpenDirectory(path);
75         if (dir_result.Failed()) {
76             if (open_mode.create) {
77                 cecd->cecd_system_save_data_archive->CreateDirectory(path);
78                 rb.Push(RESULT_SUCCESS);
79             } else {
80                 LOG_DEBUG(Service_CECD, "Failed to open directory: {}", path.AsString());
81                 rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC,
82                                    ErrorSummary::NotFound, ErrorLevel::Status));
83             }
84             rb.Push<u32>(0); // Zero entries
85         } else {
86             constexpr u32 max_entries = 32; // reasonable value, just over max boxes 24
87             auto directory = std::move(dir_result).Unwrap();
88 
89             // Actual reading into vector seems to be required for entry count
90             std::vector<FileSys::Entry> entries(max_entries);
91             const u32 entry_count = directory->Read(max_entries, entries.data());
92 
93             LOG_DEBUG(Service_CECD, "Number of entries found: {}", entry_count);
94 
95             rb.Push(RESULT_SUCCESS);
96             rb.Push<u32>(entry_count); // Entry count
97             directory->Close();
98         }
99         break;
100     }
101     default: { // If not directory, then it is a file
102         auto file_result = cecd->cecd_system_save_data_archive->OpenFile(path, mode);
103         if (file_result.Failed()) {
104             LOG_DEBUG(Service_CECD, "Failed to open file: {}", path.AsString());
105             rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
106                                ErrorLevel::Status));
107             rb.Push<u32>(0); // No file size
108         } else {
109             session_data->file = std::move(file_result).Unwrap();
110             rb.Push(RESULT_SUCCESS);
111             rb.Push<u32>(static_cast<u32>(session_data->file->GetSize())); // Return file size
112         }
113 
114         if (path_type == CecDataPathType::MboxProgramId) {
115             std::vector<u8> program_id(8);
116             u64_le le_program_id = cecd->system.Kernel().GetCurrentProcess()->codeset->program_id;
117             std::memcpy(program_id.data(), &le_program_id, sizeof(u64));
118             session_data->file->Write(0, sizeof(u64), true, program_id.data());
119             session_data->file->Close();
120         }
121     }
122     }
123 
124     LOG_DEBUG(Service_CECD,
125               "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, "
126               "open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}",
127               ncch_program_id, path_type, path.AsString(), open_mode.raw, open_mode.unknown,
128               open_mode.read, open_mode.write, open_mode.create, open_mode.check);
129 }
130 
Read(Kernel::HLERequestContext & ctx)131 void Module::Interface::Read(Kernel::HLERequestContext& ctx) {
132     IPC::RequestParser rp(ctx, 0x02, 1, 2);
133     const u32 write_buffer_size = rp.Pop<u32>();
134     auto& write_buffer = rp.PopMappedBuffer();
135 
136     SessionData* session_data = GetSessionData(ctx.Session());
137     LOG_DEBUG(Service_CECD,
138               "SessionData: ncch_program_id={:#010x}, data_path_type={:#04x}, "
139               "path={}, open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}",
140               session_data->ncch_program_id, session_data->data_path_type,
141               session_data->path.AsString(), session_data->open_mode.raw,
142               session_data->open_mode.unknown, session_data->open_mode.read,
143               session_data->open_mode.write, session_data->open_mode.create,
144               session_data->open_mode.check);
145 
146     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
147     switch (session_data->data_path_type) {
148     case CecDataPathType::RootDir:
149     case CecDataPathType::MboxDir:
150     case CecDataPathType::InboxDir:
151     case CecDataPathType::OutboxDir:
152         rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC,
153                            ErrorSummary::NotFound, ErrorLevel::Status));
154         rb.Push<u32>(0); // No bytes read
155         break;
156     default: // If not directory, then it is a file
157         std::vector<u8> buffer(write_buffer_size);
158         const u32 bytes_read = static_cast<u32>(
159             session_data->file->Read(0, write_buffer_size, buffer.data()).Unwrap());
160 
161         write_buffer.Write(buffer.data(), 0, write_buffer_size);
162         session_data->file->Close();
163 
164         rb.Push(RESULT_SUCCESS);
165         rb.Push<u32>(bytes_read);
166     }
167     rb.PushMappedBuffer(write_buffer);
168 
169     LOG_DEBUG(Service_CECD, "called, write_buffer_size={:#x}, path={}", write_buffer_size,
170               session_data->path.AsString());
171 }
172 
ReadMessage(Kernel::HLERequestContext & ctx)173 void Module::Interface::ReadMessage(Kernel::HLERequestContext& ctx) {
174     IPC::RequestParser rp(ctx, 0x03, 4, 4);
175     const u32 ncch_program_id = rp.Pop<u32>();
176     const bool is_outbox = rp.Pop<bool>();
177     const u32 message_id_size = rp.Pop<u32>();
178     const u32 buffer_size = rp.Pop<u32>();
179     auto& message_id_buffer = rp.PopMappedBuffer();
180     auto& write_buffer = rp.PopMappedBuffer();
181 
182     FileSys::Mode mode;
183     mode.read_flag.Assign(1);
184 
185     std::vector<u8> id_buffer(message_id_size);
186     message_id_buffer.Read(id_buffer.data(), 0, message_id_size);
187 
188     FileSys::Path message_path =
189         cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg
190                                                    : CecDataPathType::InboxMsg,
191                                          ncch_program_id, id_buffer)
192             .data();
193 
194     auto message_result = cecd->cecd_system_save_data_archive->OpenFile(message_path, mode);
195 
196     IPC::RequestBuilder rb = rp.MakeBuilder(2, 4);
197     if (message_result.Succeeded()) {
198         auto message = std::move(message_result).Unwrap();
199         std::vector<u8> buffer(buffer_size);
200 
201         const u32 bytes_read =
202             static_cast<u32>(message->Read(0, buffer_size, buffer.data()).Unwrap());
203         write_buffer.Write(buffer.data(), 0, buffer_size);
204         message->Close();
205 
206         CecMessageHeader msg_header;
207         std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader));
208 
209         LOG_DEBUG(Service_CECD,
210                   "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, "
211                   "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, "
212                   "batch_id={:#010x}",
213                   msg_header.magic, msg_header.message_size, msg_header.header_size,
214                   msg_header.body_size, msg_header.title_id, msg_header.title_id2,
215                   msg_header.batch_id);
216         LOG_DEBUG(Service_CECD,
217                   "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, "
218                   "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, "
219                   "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, "
220                   "forward_count={:#04x}, user_data={:#06x}, ",
221                   msg_header.unknown_id, msg_header.version, msg_header.flag,
222                   msg_header.send_method, msg_header.is_unopen, msg_header.is_new,
223                   msg_header.sender_id, msg_header.sender_id2, msg_header.send_count,
224                   msg_header.forward_count, msg_header.user_data);
225 
226         rb.Push(RESULT_SUCCESS);
227         rb.Push<u32>(bytes_read);
228     } else {
229         rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
230                            ErrorLevel::Status));
231         rb.Push<u32>(0); // zero bytes read
232     }
233     rb.PushMappedBuffer(message_id_buffer);
234     rb.PushMappedBuffer(write_buffer);
235 
236     LOG_DEBUG(
237         Service_CECD,
238         "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}",
239         ncch_program_id, is_outbox, message_id_size, buffer_size);
240 }
241 
ReadMessageWithHMAC(Kernel::HLERequestContext & ctx)242 void Module::Interface::ReadMessageWithHMAC(Kernel::HLERequestContext& ctx) {
243     IPC::RequestParser rp(ctx, 0x04, 4, 6);
244     const u32 ncch_program_id = rp.Pop<u32>();
245     const bool is_outbox = rp.Pop<bool>();
246     const u32 message_id_size = rp.Pop<u32>();
247     const u32 buffer_size = rp.Pop<u32>();
248     auto& message_id_buffer = rp.PopMappedBuffer();
249     auto& hmac_key_buffer = rp.PopMappedBuffer();
250     auto& write_buffer = rp.PopMappedBuffer();
251 
252     FileSys::Mode mode;
253     mode.read_flag.Assign(1);
254 
255     std::vector<u8> id_buffer(message_id_size);
256     message_id_buffer.Read(id_buffer.data(), 0, message_id_size);
257 
258     FileSys::Path message_path =
259         cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg
260                                                    : CecDataPathType::InboxMsg,
261                                          ncch_program_id, id_buffer)
262             .data();
263 
264     auto message_result = cecd->cecd_system_save_data_archive->OpenFile(message_path, mode);
265 
266     IPC::RequestBuilder rb = rp.MakeBuilder(2, 6);
267     if (message_result.Succeeded()) {
268         auto message = std::move(message_result).Unwrap();
269         std::vector<u8> buffer(buffer_size);
270 
271         const u32 bytes_read =
272             static_cast<u32>(message->Read(0, buffer_size, buffer.data()).Unwrap());
273         write_buffer.Write(buffer.data(), 0, buffer_size);
274         message->Close();
275 
276         CecMessageHeader msg_header;
277         std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader));
278 
279         LOG_DEBUG(Service_CECD,
280                   "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, "
281                   "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, "
282                   "batch_id={:#010x}",
283                   msg_header.magic, msg_header.message_size, msg_header.header_size,
284                   msg_header.body_size, msg_header.title_id, msg_header.title_id2,
285                   msg_header.batch_id);
286         LOG_DEBUG(Service_CECD,
287                   "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, "
288                   "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, "
289                   "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, "
290                   "forward_count={:#04x}, user_data={:#06x}, ",
291                   msg_header.unknown_id, msg_header.version, msg_header.flag,
292                   msg_header.send_method, msg_header.is_unopen, msg_header.is_new,
293                   msg_header.sender_id, msg_header.sender_id2, msg_header.send_count,
294                   msg_header.forward_count, msg_header.user_data);
295 
296         std::vector<u8> hmac_digest(0x20);
297         std::memcpy(hmac_digest.data(),
298                     buffer.data() + msg_header.header_size + msg_header.body_size, 0x20);
299 
300         std::vector<u8> message_body(msg_header.body_size);
301         std::memcpy(message_body.data(), buffer.data() + msg_header.header_size,
302                     msg_header.body_size);
303 
304         using namespace CryptoPP;
305         SecByteBlock key(0x20);
306         hmac_key_buffer.Read(key.data(), 0, key.size());
307 
308         HMAC<SHA256> hmac(key, key.size());
309 
310         const bool verify_hmac =
311             hmac.VerifyDigest(hmac_digest.data(), message_body.data(), message_body.size());
312 
313         if (verify_hmac)
314             LOG_DEBUG(Service_CECD, "Verification succeeded");
315         else
316             LOG_DEBUG(Service_CECD, "Verification failed");
317 
318         rb.Push(RESULT_SUCCESS);
319         rb.Push<u32>(bytes_read);
320     } else {
321         rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
322                            ErrorLevel::Status));
323         rb.Push<u32>(0); // zero bytes read
324     }
325 
326     rb.PushMappedBuffer(message_id_buffer);
327     rb.PushMappedBuffer(hmac_key_buffer);
328     rb.PushMappedBuffer(write_buffer);
329 
330     LOG_DEBUG(
331         Service_CECD,
332         "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}",
333         ncch_program_id, is_outbox, message_id_size, buffer_size);
334 }
335 
Write(Kernel::HLERequestContext & ctx)336 void Module::Interface::Write(Kernel::HLERequestContext& ctx) {
337     IPC::RequestParser rp(ctx, 0x05, 1, 2);
338     const u32 read_buffer_size = rp.Pop<u32>();
339     auto& read_buffer = rp.PopMappedBuffer();
340 
341     SessionData* session_data = GetSessionData(ctx.Session());
342     LOG_DEBUG(Service_CECD,
343               "SessionData: ncch_program_id={:#010x}, data_path_type={:#04x}, "
344               "path={}, open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}",
345               session_data->ncch_program_id, session_data->data_path_type,
346               session_data->path.AsString(), session_data->open_mode.raw,
347               session_data->open_mode.unknown, session_data->open_mode.read,
348               session_data->open_mode.write, session_data->open_mode.create,
349               session_data->open_mode.check);
350 
351     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
352     switch (session_data->data_path_type) {
353     case CecDataPathType::RootDir:
354     case CecDataPathType::MboxDir:
355     case CecDataPathType::InboxDir:
356     case CecDataPathType::OutboxDir:
357         rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC,
358                            ErrorSummary::NotFound, ErrorLevel::Status));
359         break;
360     default: // If not directory, then it is a file
361         std::vector<u8> buffer(read_buffer_size);
362         read_buffer.Read(buffer.data(), 0, read_buffer_size);
363 
364         if (session_data->file->GetSize() != read_buffer_size) {
365             session_data->file->SetSize(read_buffer_size);
366         }
367 
368         if (session_data->open_mode.check) {
369             cecd->CheckAndUpdateFile(session_data->data_path_type, session_data->ncch_program_id,
370                                      buffer);
371         }
372 
373         [[maybe_unused]] const u32 bytes_written = static_cast<u32>(
374             session_data->file->Write(0, buffer.size(), true, buffer.data()).Unwrap());
375         session_data->file->Close();
376 
377         rb.Push(RESULT_SUCCESS);
378     }
379     rb.PushMappedBuffer(read_buffer);
380 
381     LOG_DEBUG(Service_CECD, "called, read_buffer_size={:#x}", read_buffer_size);
382 }
383 
WriteMessage(Kernel::HLERequestContext & ctx)384 void Module::Interface::WriteMessage(Kernel::HLERequestContext& ctx) {
385     IPC::RequestParser rp(ctx, 0x06, 4, 4);
386     const u32 ncch_program_id = rp.Pop<u32>();
387     const bool is_outbox = rp.Pop<bool>();
388     const u32 message_id_size = rp.Pop<u32>();
389     const u32 buffer_size = rp.Pop<u32>();
390     auto& read_buffer = rp.PopMappedBuffer();
391     auto& message_id_buffer = rp.PopMappedBuffer();
392 
393     FileSys::Mode mode;
394     mode.write_flag.Assign(1);
395     mode.create_flag.Assign(1);
396 
397     std::vector<u8> id_buffer(message_id_size);
398     message_id_buffer.Read(id_buffer.data(), 0, message_id_size);
399 
400     FileSys::Path message_path =
401         cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg
402                                                    : CecDataPathType::InboxMsg,
403                                          ncch_program_id, id_buffer)
404             .data();
405 
406     auto message_result = cecd->cecd_system_save_data_archive->OpenFile(message_path, mode);
407 
408     IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
409     if (message_result.Succeeded()) {
410         auto message = std::move(message_result).Unwrap();
411 
412         std::vector<u8> buffer(buffer_size);
413         read_buffer.Read(buffer.data(), 0, buffer_size);
414 
415         CecMessageHeader msg_header;
416         std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader));
417 
418         LOG_DEBUG(Service_CECD,
419                   "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, "
420                   "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, "
421                   "batch_id={:#010x}",
422                   msg_header.magic, msg_header.message_size, msg_header.header_size,
423                   msg_header.body_size, msg_header.title_id, msg_header.title_id2,
424                   msg_header.batch_id);
425         LOG_DEBUG(Service_CECD,
426                   "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, "
427                   "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, "
428                   "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, "
429                   "forward_count={:#04x}, user_data={:#06x}, ",
430                   msg_header.unknown_id, msg_header.version, msg_header.flag,
431                   msg_header.send_method, msg_header.is_unopen, msg_header.is_new,
432                   msg_header.sender_id, msg_header.sender_id2, msg_header.send_count,
433                   msg_header.forward_count, msg_header.user_data);
434 
435         [[maybe_unused]] const u32 bytes_written =
436             static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap());
437         message->Close();
438 
439         rb.Push(RESULT_SUCCESS);
440     } else {
441         rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
442                            ErrorLevel::Status));
443     }
444 
445     rb.PushMappedBuffer(read_buffer);
446     rb.PushMappedBuffer(message_id_buffer);
447 
448     LOG_DEBUG(
449         Service_CECD,
450         "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}",
451         ncch_program_id, is_outbox, message_id_size, buffer_size);
452 }
453 
WriteMessageWithHMAC(Kernel::HLERequestContext & ctx)454 void Module::Interface::WriteMessageWithHMAC(Kernel::HLERequestContext& ctx) {
455     IPC::RequestParser rp(ctx, 0x07, 4, 6);
456     const u32 ncch_program_id = rp.Pop<u32>();
457     const bool is_outbox = rp.Pop<bool>();
458     const u32 message_id_size = rp.Pop<u32>();
459     const u32 buffer_size = rp.Pop<u32>();
460     auto& read_buffer = rp.PopMappedBuffer();
461     auto& hmac_key_buffer = rp.PopMappedBuffer();
462     auto& message_id_buffer = rp.PopMappedBuffer();
463 
464     FileSys::Mode mode;
465     mode.write_flag.Assign(1);
466     mode.create_flag.Assign(1);
467 
468     std::vector<u8> id_buffer(message_id_size);
469     message_id_buffer.Read(id_buffer.data(), 0, message_id_size);
470 
471     FileSys::Path message_path =
472         cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg
473                                                    : CecDataPathType::InboxMsg,
474                                          ncch_program_id, id_buffer)
475             .data();
476 
477     auto message_result = cecd->cecd_system_save_data_archive->OpenFile(message_path, mode);
478 
479     IPC::RequestBuilder rb = rp.MakeBuilder(1, 6);
480     if (message_result.Succeeded()) {
481         auto message = std::move(message_result).Unwrap();
482 
483         std::vector<u8> buffer(buffer_size);
484         read_buffer.Read(buffer.data(), 0, buffer_size);
485 
486         CecMessageHeader msg_header;
487         std::memcpy(&msg_header, buffer.data(), sizeof(CecMessageHeader));
488 
489         LOG_DEBUG(Service_CECD,
490                   "magic={:#06x}, message_size={:#010x}, header_size={:#010x}, "
491                   "body_size={:#010x}, title_id={:#010x}, title_id_2={:#010x}, "
492                   "batch_id={:#010x}",
493                   msg_header.magic, msg_header.message_size, msg_header.header_size,
494                   msg_header.body_size, msg_header.title_id, msg_header.title_id2,
495                   msg_header.batch_id);
496         LOG_DEBUG(Service_CECD,
497                   "unknown_id={:#010x}, version={:#010x}, flag={:#04x}, "
498                   "send_method={:#04x}, is_unopen={:#04x}, is_new={:#04x}, "
499                   "sender_id={:#018x}, sender_id2={:#018x}, send_count={:#04x}, "
500                   "forward_count={:#04x}, user_data={:#06x}, ",
501                   msg_header.unknown_id, msg_header.version, msg_header.flag,
502                   msg_header.send_method, msg_header.is_unopen, msg_header.is_new,
503                   msg_header.sender_id, msg_header.sender_id2, msg_header.send_count,
504                   msg_header.forward_count, msg_header.user_data);
505 
506         const u32 hmac_offset = msg_header.header_size + msg_header.body_size;
507         const u32 hmac_size = 0x20;
508 
509         std::vector<u8> hmac_digest(hmac_size);
510         std::vector<u8> message_body(msg_header.body_size);
511         std::memcpy(message_body.data(), buffer.data() + msg_header.header_size,
512                     msg_header.body_size);
513 
514         using namespace CryptoPP;
515         SecByteBlock key(hmac_size);
516         hmac_key_buffer.Read(key.data(), 0, hmac_size);
517 
518         HMAC<SHA256> hmac(key, hmac_size);
519         hmac.CalculateDigest(hmac_digest.data(), message_body.data(), msg_header.body_size);
520         std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size);
521 
522         [[maybe_unused]] const u32 bytes_written =
523             static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap());
524         message->Close();
525 
526         rb.Push(RESULT_SUCCESS);
527     } else {
528         rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
529                            ErrorLevel::Status));
530     }
531 
532     rb.PushMappedBuffer(read_buffer);
533     rb.PushMappedBuffer(hmac_key_buffer);
534     rb.PushMappedBuffer(message_id_buffer);
535 
536     LOG_DEBUG(
537         Service_CECD,
538         "called, ncch_program_id={:#010x}, is_outbox={}, message_id_size={:#x}, buffer_size={:#x}",
539         ncch_program_id, is_outbox, message_id_size, buffer_size);
540 }
541 
Delete(Kernel::HLERequestContext & ctx)542 void Module::Interface::Delete(Kernel::HLERequestContext& ctx) {
543     IPC::RequestParser rp(ctx, 0x08, 4, 2);
544     const u32 ncch_program_id = rp.Pop<u32>();
545     const CecDataPathType path_type = rp.PopEnum<CecDataPathType>();
546     const bool is_outbox = rp.Pop<bool>();
547     const u32 message_id_size = rp.Pop<u32>();
548     auto& message_id_buffer = rp.PopMappedBuffer();
549 
550     FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data());
551     FileSys::Mode mode;
552     mode.write_flag.Assign(1);
553 
554     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
555     switch (path_type) {
556     case CecDataPathType::RootDir:
557     case CecDataPathType::MboxDir:
558     case CecDataPathType::InboxDir:
559     case CecDataPathType::OutboxDir:
560         rb.Push(cecd->cecd_system_save_data_archive->DeleteDirectoryRecursively(path));
561         break;
562     default: // If not directory, then it is a file
563         if (message_id_size == 0) {
564             rb.Push(cecd->cecd_system_save_data_archive->DeleteFile(path));
565         } else {
566             std::vector<u8> id_buffer(message_id_size);
567             message_id_buffer.Read(id_buffer.data(), 0, message_id_size);
568 
569             FileSys::Path message_path =
570                 cecd->GetCecDataPathTypeAsString(is_outbox ? CecDataPathType::OutboxMsg
571                                                            : CecDataPathType::InboxMsg,
572                                                  ncch_program_id, id_buffer)
573                     .data();
574             rb.Push(cecd->cecd_system_save_data_archive->DeleteFile(message_path));
575         }
576     }
577 
578     rb.PushMappedBuffer(message_id_buffer);
579 
580     LOG_DEBUG(Service_CECD,
581               "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, "
582               "is_outbox={}, message_id_size={:#x}",
583               ncch_program_id, path_type, path.AsString(), is_outbox, message_id_size);
584 }
585 
SetData(Kernel::HLERequestContext & ctx)586 void Module::Interface::SetData(Kernel::HLERequestContext& ctx) {
587     IPC::RequestParser rp(ctx, 0x09, 3, 2);
588     const u32 ncch_program_id = rp.Pop<u32>();
589     const u32 buffer_size = rp.Pop<u32>();
590     const u32 option = rp.Pop<u32>();
591     auto& read_buffer = rp.PopMappedBuffer();
592 
593     if (option == 2 && buffer_size > 0) { // update obindex?
594         FileSys::Path path(
595             cecd->GetCecDataPathTypeAsString(CecDataPathType::OutboxIndex, ncch_program_id).data());
596         FileSys::Mode mode;
597         mode.write_flag.Assign(1);
598         mode.create_flag.Assign(1);
599 
600         auto file_result = cecd->cecd_system_save_data_archive->OpenFile(path, mode);
601         if (file_result.Succeeded()) {
602             auto file = std::move(file_result).Unwrap();
603             std::vector<u8> buffer(buffer_size);
604             read_buffer.Read(buffer.data(), 0, buffer_size);
605 
606             cecd->CheckAndUpdateFile(CecDataPathType::OutboxIndex, ncch_program_id, buffer);
607 
608             file->Write(0, buffer.size(), true, buffer.data());
609             file->Close();
610         }
611     }
612 
613     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
614     rb.Push(RESULT_SUCCESS);
615     rb.PushMappedBuffer(read_buffer);
616 
617     LOG_DEBUG(Service_CECD, "called, ncch_program_id={:#010x}, buffer_size={:#x}, option={:#x}",
618               ncch_program_id, buffer_size, option);
619 }
620 
ReadData(Kernel::HLERequestContext & ctx)621 void Module::Interface::ReadData(Kernel::HLERequestContext& ctx) {
622     IPC::RequestParser rp(ctx, 0x0A, 3, 4);
623     const u32 dest_buffer_size = rp.Pop<u32>();
624     const CecSystemInfoType info_type = rp.PopEnum<CecSystemInfoType>();
625     const u32 param_buffer_size = rp.Pop<u32>();
626     auto& param_buffer = rp.PopMappedBuffer();
627     auto& dest_buffer = rp.PopMappedBuffer();
628 
629     // TODO: Other CecSystemInfoTypes
630     IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
631     std::vector<u8> buffer;
632     switch (info_type) {
633     case CecSystemInfoType::EulaVersion: {
634         auto cfg = Service::CFG::GetModule(cecd->system);
635         Service::CFG::EULAVersion version = cfg->GetEULAVersion();
636         dest_buffer.Write(&version, 0, sizeof(version));
637         break;
638     }
639     case CecSystemInfoType::Eula:
640         buffer = {0x01}; // Eula agreed
641         dest_buffer.Write(buffer.data(), 0, buffer.size());
642         break;
643     case CecSystemInfoType::ParentControl:
644         buffer = {0x00}; // No parent control
645         dest_buffer.Write(buffer.data(), 0, buffer.size());
646         break;
647     default:
648         LOG_ERROR(Service_CECD, "Unknown system info type={:#x}", info_type);
649     }
650 
651     rb.Push(RESULT_SUCCESS);
652     rb.PushMappedBuffer(param_buffer);
653     rb.PushMappedBuffer(dest_buffer);
654 
655     LOG_DEBUG(Service_CECD,
656               "called, dest_buffer_size={:#x}, info_type={:#x}, param_buffer_size={:#x}",
657               dest_buffer_size, info_type, param_buffer_size);
658 }
659 
Start(Kernel::HLERequestContext & ctx)660 void Module::Interface::Start(Kernel::HLERequestContext& ctx) {
661     IPC::RequestParser rp(ctx, 0x0B, 1, 0);
662     const CecCommand command = rp.PopEnum<CecCommand>();
663 
664     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
665     rb.Push(RESULT_SUCCESS);
666 
667     LOG_WARNING(Service_CECD, "(STUBBED) called, command={}", cecd->GetCecCommandAsString(command));
668 }
669 
Stop(Kernel::HLERequestContext & ctx)670 void Module::Interface::Stop(Kernel::HLERequestContext& ctx) {
671     IPC::RequestParser rp(ctx, 0x0C, 1, 0);
672     const CecCommand command = rp.PopEnum<CecCommand>();
673 
674     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
675     rb.Push(RESULT_SUCCESS);
676 
677     LOG_WARNING(Service_CECD, "(STUBBED) called, command={}", cecd->GetCecCommandAsString(command));
678 }
679 
GetCecInfoBuffer(Kernel::HLERequestContext & ctx)680 void Module::Interface::GetCecInfoBuffer(Kernel::HLERequestContext& ctx) {
681     IPC::RequestParser rp(ctx, 0x0D, 2, 2);
682     const u32 buffer_size = rp.Pop<u32>();
683     const u32 possible_info_type = rp.Pop<u32>();
684     auto& buffer = rp.PopMappedBuffer();
685 
686     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
687     rb.Push(RESULT_SUCCESS);
688     rb.PushMappedBuffer(buffer);
689 
690     LOG_DEBUG(Service_CECD, "called, buffer_size={}, possible_info_type={}", buffer_size,
691               possible_info_type);
692 }
693 
GetCecdState(Kernel::HLERequestContext & ctx)694 void Module::Interface::GetCecdState(Kernel::HLERequestContext& ctx) {
695     IPC::RequestParser rp(ctx, 0x0E, 0, 0);
696 
697     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
698     rb.Push(RESULT_SUCCESS);
699     rb.PushEnum(CecdState::NdmStatusIdle);
700 
701     LOG_WARNING(Service_CECD, "(STUBBED) called");
702 }
703 
GetCecInfoEventHandle(Kernel::HLERequestContext & ctx)704 void Module::Interface::GetCecInfoEventHandle(Kernel::HLERequestContext& ctx) {
705     IPC::RequestParser rp(ctx, 0x0F, 0, 0);
706 
707     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
708     rb.Push(RESULT_SUCCESS);
709     rb.PushCopyObjects(cecd->cecinfo_event);
710 
711     LOG_WARNING(Service_CECD, "(STUBBED) called");
712 }
713 
GetChangeStateEventHandle(Kernel::HLERequestContext & ctx)714 void Module::Interface::GetChangeStateEventHandle(Kernel::HLERequestContext& ctx) {
715     IPC::RequestParser rp(ctx, 0x10, 0, 0);
716 
717     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
718     rb.Push(RESULT_SUCCESS);
719     rb.PushCopyObjects(cecd->change_state_event);
720 
721     LOG_WARNING(Service_CECD, "(STUBBED) called");
722 }
723 
OpenAndWrite(Kernel::HLERequestContext & ctx)724 void Module::Interface::OpenAndWrite(Kernel::HLERequestContext& ctx) {
725     IPC::RequestParser rp(ctx, 0x11, 4, 4);
726     const u32 buffer_size = rp.Pop<u32>();
727     const u32 ncch_program_id = rp.Pop<u32>();
728     const CecDataPathType path_type = rp.PopEnum<CecDataPathType>();
729     CecOpenMode open_mode;
730     open_mode.raw = rp.Pop<u32>();
731     rp.PopPID();
732     auto& read_buffer = rp.PopMappedBuffer();
733 
734     FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data());
735     FileSys::Mode mode;
736     mode.write_flag.Assign(1);
737     mode.create_flag.Assign(1);
738 
739     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
740     switch (path_type) {
741     case CecDataPathType::RootDir:
742     case CecDataPathType::MboxDir:
743     case CecDataPathType::InboxDir:
744     case CecDataPathType::OutboxDir:
745         rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC,
746                            ErrorSummary::NotFound, ErrorLevel::Status));
747         break;
748     default: // If not directory, then it is a file
749         auto file_result = cecd->cecd_system_save_data_archive->OpenFile(path, mode);
750         if (file_result.Succeeded()) {
751             auto file = std::move(file_result).Unwrap();
752 
753             std::vector<u8> buffer(buffer_size);
754             read_buffer.Read(buffer.data(), 0, buffer_size);
755 
756             if (file->GetSize() != buffer_size) {
757                 file->SetSize(buffer_size);
758             }
759 
760             if (open_mode.check) {
761                 cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer);
762             }
763 
764             [[maybe_unused]] const u32 bytes_written =
765                 static_cast<u32>(file->Write(0, buffer.size(), true, buffer.data()).Unwrap());
766             file->Close();
767 
768             rb.Push(RESULT_SUCCESS);
769         } else {
770             rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
771                                ErrorLevel::Status));
772         }
773     }
774     rb.PushMappedBuffer(read_buffer);
775 
776     LOG_DEBUG(Service_CECD,
777               "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, buffer_size={:#x} "
778               "open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}",
779               ncch_program_id, path_type, path.AsString(), buffer_size, open_mode.raw,
780               open_mode.unknown, open_mode.read, open_mode.write, open_mode.create,
781               open_mode.check);
782 }
783 
OpenAndRead(Kernel::HLERequestContext & ctx)784 void Module::Interface::OpenAndRead(Kernel::HLERequestContext& ctx) {
785     IPC::RequestParser rp(ctx, 0x12, 4, 4);
786     const u32 buffer_size = rp.Pop<u32>();
787     const u32 ncch_program_id = rp.Pop<u32>();
788     const CecDataPathType path_type = rp.PopEnum<CecDataPathType>();
789     CecOpenMode open_mode;
790     open_mode.raw = rp.Pop<u32>();
791     rp.PopPID();
792     auto& write_buffer = rp.PopMappedBuffer();
793 
794     FileSys::Path path(cecd->GetCecDataPathTypeAsString(path_type, ncch_program_id).data());
795     FileSys::Mode mode;
796     mode.read_flag.Assign(1);
797 
798     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
799     switch (path_type) {
800     case CecDataPathType::RootDir:
801     case CecDataPathType::MboxDir:
802     case CecDataPathType::InboxDir:
803     case CecDataPathType::OutboxDir:
804         rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::CEC,
805                            ErrorSummary::NotFound, ErrorLevel::Status));
806         rb.Push<u32>(0); // No entries read
807         break;
808     default: // If not directory, then it is a file
809         auto file_result = cecd->cecd_system_save_data_archive->OpenFile(path, mode);
810         if (file_result.Succeeded()) {
811             auto file = std::move(file_result).Unwrap();
812             std::vector<u8> buffer(buffer_size);
813 
814             const u32 bytes_read =
815                 static_cast<u32>(file->Read(0, buffer_size, buffer.data()).Unwrap());
816             write_buffer.Write(buffer.data(), 0, buffer_size);
817             file->Close();
818 
819             rb.Push(RESULT_SUCCESS);
820             rb.Push<u32>(bytes_read);
821         } else {
822             rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::CEC, ErrorSummary::NotFound,
823                                ErrorLevel::Status));
824             rb.Push<u32>(0); // No bytes read
825         }
826     }
827     rb.PushMappedBuffer(write_buffer);
828 
829     LOG_DEBUG(Service_CECD,
830               "called, ncch_program_id={:#010x}, path_type={:#04x}, path={}, buffer_size={:#x} "
831               "open_mode: raw={:#x}, unknown={}, read={}, write={}, create={}, check={}",
832               ncch_program_id, path_type, path.AsString(), buffer_size, open_mode.raw,
833               open_mode.unknown, open_mode.read, open_mode.write, open_mode.create,
834               open_mode.check);
835 }
836 
EncodeBase64(const std::vector<u8> & in) const837 std::string Module::EncodeBase64(const std::vector<u8>& in) const {
838     using namespace CryptoPP;
839     using Name::EncodingLookupArray;
840     using Name::InsertLineBreaks;
841     using Name::Pad;
842 
843     std::string out;
844     Base64Encoder encoder;
845     AlgorithmParameters params =
846         MakeParameters(EncodingLookupArray(), (const byte*)base64_dict.data())(InsertLineBreaks(),
847                                                                                false)(Pad(), false);
848 
849     encoder.IsolatedInitialize(params);
850     encoder.Attach(new StringSink(out));
851     encoder.Put(in.data(), in.size());
852     encoder.MessageEnd();
853 
854     return out;
855 }
856 
GetCecDataPathTypeAsString(const CecDataPathType type,const u32 program_id,const std::vector<u8> & msg_id) const857 std::string Module::GetCecDataPathTypeAsString(const CecDataPathType type, const u32 program_id,
858                                                const std::vector<u8>& msg_id) const {
859     switch (type) {
860     case CecDataPathType::MboxList:
861         return "/CEC/MBoxList____";
862     case CecDataPathType::MboxInfo:
863         return fmt::format("/CEC/{:08x}/MBoxInfo____", program_id);
864     case CecDataPathType::InboxInfo:
865         return fmt::format("/CEC/{:08x}/InBox___/BoxInfo_____", program_id);
866     case CecDataPathType::OutboxInfo:
867         return fmt::format("/CEC/{:08x}/OutBox__/BoxInfo_____", program_id);
868     case CecDataPathType::OutboxIndex:
869         return fmt::format("/CEC/{:08x}/OutBox__/OBIndex_____", program_id);
870     case CecDataPathType::InboxMsg:
871         return fmt::format("/CEC/{:08x}/InBox___/_{}", program_id, EncodeBase64(msg_id));
872     case CecDataPathType::OutboxMsg:
873         return fmt::format("/CEC/{:08x}/OutBox__/_{}", program_id, EncodeBase64(msg_id));
874     case CecDataPathType::RootDir:
875         return "/CEC";
876     case CecDataPathType::MboxDir:
877         return fmt::format("/CEC/{:08x}", program_id);
878     case CecDataPathType::InboxDir:
879         return fmt::format("/CEC/{:08x}/InBox___", program_id);
880     case CecDataPathType::OutboxDir:
881         return fmt::format("/CEC/{:08x}/OutBox__", program_id);
882     case CecDataPathType::MboxData:
883     case CecDataPathType::MboxIcon:
884     case CecDataPathType::MboxTitle:
885     default:
886         return fmt::format("/CEC/{:08x}/MBoxData.{:03}", program_id, static_cast<u32>(type) - 100);
887     }
888 }
889 
GetCecCommandAsString(const CecCommand command) const890 std::string Module::GetCecCommandAsString(const CecCommand command) const {
891     switch (command) {
892     case CecCommand::None:
893         return "None";
894     case CecCommand::Start:
895         return "Start";
896     case CecCommand::ResetStart:
897         return "ResetStart";
898     case CecCommand::ReadyScan:
899         return "ReadyScan";
900     case CecCommand::ReadyScanWait:
901         return "ReadyScanWait";
902     case CecCommand::StartScan:
903         return "StartScan";
904     case CecCommand::Rescan:
905         return "Rescan";
906     case CecCommand::NdmResume:
907         return "NdmResume";
908     case CecCommand::NdmSuspend:
909         return "NdmSuspend";
910     case CecCommand::NdmSuspendImmediate:
911         return "NdmSuspendImmediate";
912     case CecCommand::StopWait:
913         return "StopWait";
914     case CecCommand::Stop:
915         return "Stop";
916     case CecCommand::StopForce:
917         return "StopForce";
918     case CecCommand::StopForceWait:
919         return "StopForceWait";
920     case CecCommand::ResetFilter:
921         return "ResetFilter";
922     case CecCommand::DaemonStop:
923         return "DaemonStop";
924     case CecCommand::DaemonStart:
925         return "DaemonStart";
926     case CecCommand::Exit:
927         return "Exit";
928     case CecCommand::OverBoss:
929         return "OverBoss";
930     case CecCommand::OverBossForce:
931         return "OverBossForce";
932     case CecCommand::OverBossForceWait:
933         return "OverBossForceWait";
934     case CecCommand::End:
935         return "End";
936     default:
937         return "Unknown";
938     }
939 }
940 
CheckAndUpdateFile(const CecDataPathType path_type,const u32 ncch_program_id,std::vector<u8> & file_buffer)941 void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_program_id,
942                                 std::vector<u8>& file_buffer) {
943     constexpr u32 max_num_boxes = 24;
944     constexpr u32 name_size = 16;      // fixed size 16 characters long
945     constexpr u32 valid_name_size = 8; // 8 characters are valid, the rest are null
946     const u32 file_size = static_cast<u32>(file_buffer.size());
947 
948     switch (path_type) {
949     case CecDataPathType::MboxList: {
950         CecMBoxListHeader mbox_list_header = {};
951         std::memcpy(&mbox_list_header, file_buffer.data(), sizeof(CecMBoxListHeader));
952 
953         LOG_DEBUG(Service_CECD, "CecMBoxList: magic={:#06x}, version={:#06x}, num_boxes={:#06x}",
954                   mbox_list_header.magic, mbox_list_header.version, mbox_list_header.num_boxes);
955 
956         if (file_size != sizeof(CecMBoxListHeader)) { // 0x18C
957             LOG_DEBUG(Service_CECD, "CecMBoxListHeader size is incorrect: {}", file_size);
958         }
959 
960         if (mbox_list_header.magic != 0x6868) { // 'hh'
961             if (mbox_list_header.magic == 0 || mbox_list_header.magic == 0xFFFF) {
962                 LOG_DEBUG(Service_CECD, "CecMBoxListHeader magic number is not set");
963             } else {
964                 LOG_DEBUG(Service_CECD, "CecMBoxListHeader magic number is incorrect: {}",
965                           mbox_list_header.magic);
966             }
967             std::memset(&mbox_list_header, 0, sizeof(CecMBoxListHeader));
968             mbox_list_header.magic = 0x6868;
969         }
970 
971         if (mbox_list_header.version != 0x01) { // Not quite sure if it is a version
972             if (mbox_list_header.version == 0)
973                 LOG_DEBUG(Service_CECD, "CecMBoxListHeader version is not set");
974             else
975                 LOG_DEBUG(Service_CECD, "CecMBoxListHeader version is incorrect: {}",
976                           mbox_list_header.version);
977             mbox_list_header.version = 0x01;
978         }
979 
980         if (mbox_list_header.num_boxes > 24) {
981             LOG_DEBUG(Service_CECD, "CecMBoxListHeader number of boxes is too large: {}",
982                       mbox_list_header.num_boxes);
983         } else {
984             std::vector<u8> name_buffer(name_size);
985             std::memset(name_buffer.data(), 0, name_size);
986 
987             if (ncch_program_id != 0) {
988                 std::string name = fmt::format("{:08x}", ncch_program_id);
989                 std::memcpy(name_buffer.data(), name.data(), name.size());
990 
991                 bool already_activated = false;
992                 for (auto i = 0; i < mbox_list_header.num_boxes; i++) {
993                     // Box names start at offset 0xC, are 16 char long, first 8 id, last 8 null
994                     if (std::memcmp(name_buffer.data(), &mbox_list_header.box_names[i],
995                                     valid_name_size) == 0) {
996                         LOG_DEBUG(Service_CECD, "Title already activated");
997                         already_activated = true;
998                     }
999                 };
1000 
1001                 if (!already_activated) {
1002                     if (mbox_list_header.num_boxes < max_num_boxes) { // max boxes
1003                         LOG_DEBUG(Service_CECD, "Adding title to mboxlist____: {}", name);
1004                         std::memcpy(&mbox_list_header.box_names[mbox_list_header.num_boxes],
1005                                     name_buffer.data(), name_size);
1006                         mbox_list_header.num_boxes++;
1007                     }
1008                 }
1009             } else { // ncch_program_id == 0, remove/update activated boxes
1010                 /// We need to read the /CEC directory to find out which titles, if any,
1011                 /// are activated. The num_of_titles = (total_read_count) - 1, to adjust for
1012                 /// the MBoxList____ file that is present in the directory as well.
1013                 FileSys::Path root_path(
1014                     GetCecDataPathTypeAsString(CecDataPathType::RootDir, 0).data());
1015 
1016                 auto dir_result = cecd_system_save_data_archive->OpenDirectory(root_path);
1017 
1018                 auto root_dir = std::move(dir_result).Unwrap();
1019                 std::vector<FileSys::Entry> entries(max_num_boxes + 1); // + 1 mboxlist
1020                 const u32 entry_count = root_dir->Read(max_num_boxes + 1, entries.data());
1021                 root_dir->Close();
1022 
1023                 LOG_DEBUG(Service_CECD, "Number of entries found in /CEC: {}", entry_count);
1024 
1025                 std::string mbox_list_name("MBoxList____");
1026                 std::string file_name;
1027                 std::u16string u16_filename;
1028 
1029                 // Loop through entries but don't add mboxlist____ to itself.
1030                 for (u32 i = 0; i < entry_count; i++) {
1031                     u16_filename = std::u16string(entries[i].filename);
1032                     file_name = Common::UTF16ToUTF8(u16_filename);
1033 
1034                     if (mbox_list_name.compare(file_name) != 0) {
1035                         LOG_DEBUG(Service_CECD, "Adding title to mboxlist____: {}", file_name);
1036                         std::memcpy(&mbox_list_header.box_names[mbox_list_header.num_boxes++],
1037                                     file_name.data(), valid_name_size);
1038                     }
1039                 }
1040             }
1041         }
1042         std::memcpy(file_buffer.data(), &mbox_list_header, sizeof(CecMBoxListHeader));
1043         break;
1044     }
1045     case CecDataPathType::MboxInfo: {
1046         CecMBoxInfoHeader mbox_info_header = {};
1047         std::memcpy(&mbox_info_header, file_buffer.data(), sizeof(CecMBoxInfoHeader));
1048 
1049         LOG_DEBUG(Service_CECD,
1050                   "CecMBoxInfoHeader: magic={:#06x}, program_id={:#010x}, "
1051                   "private_id={:#010x}, flag={:#04x}, flag2={:#04x}",
1052                   mbox_info_header.magic, mbox_info_header.program_id, mbox_info_header.private_id,
1053                   mbox_info_header.flag, mbox_info_header.flag2);
1054 
1055         if (file_size != sizeof(CecMBoxInfoHeader)) { // 0x60
1056             LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader size is incorrect: {}", file_size);
1057         }
1058 
1059         if (mbox_info_header.magic != 0x6363) { // 'cc'
1060             if (mbox_info_header.magic == 0)
1061                 LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader magic number is not set");
1062             else
1063                 LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader magic number is incorrect: {}",
1064                           mbox_info_header.magic);
1065             mbox_info_header.magic = 0x6363;
1066         }
1067 
1068         if (mbox_info_header.program_id != ncch_program_id) {
1069             if (mbox_info_header.program_id == 0)
1070                 LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader program id is not set");
1071             else
1072                 LOG_DEBUG(Service_CECD, "CecMBoxInfoHeader program id doesn't match current id: {}",
1073                           mbox_info_header.program_id);
1074         }
1075 
1076         std::memcpy(file_buffer.data(), &mbox_info_header, sizeof(CecMBoxInfoHeader));
1077         break;
1078     }
1079     case CecDataPathType::InboxInfo: {
1080         CecBoxInfoHeader inbox_info_header = {};
1081         std::memcpy(&inbox_info_header, file_buffer.data(), sizeof(CecBoxInfoHeader));
1082 
1083         LOG_DEBUG(Service_CECD,
1084                   "CecBoxInfoHeader: magic={:#06x}, box_info_size={:#010x}, "
1085                   "max_box_size={:#010x}, box_size={:#010x}, "
1086                   "max_message_num={:#010x}, message_num={:#010x}, "
1087                   "max_batch_size={:#010x}, max_message_size={:#010x}",
1088                   inbox_info_header.magic, inbox_info_header.box_info_size,
1089                   inbox_info_header.max_box_size, inbox_info_header.box_size,
1090                   inbox_info_header.max_message_num, inbox_info_header.message_num,
1091                   inbox_info_header.max_batch_size, inbox_info_header.max_message_size);
1092 
1093         if (inbox_info_header.magic != 0x6262) { // 'bb'
1094             if (inbox_info_header.magic == 0)
1095                 LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader magic number is not set");
1096             else
1097                 LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader magic number is incorrect: {}",
1098                           inbox_info_header.magic);
1099             inbox_info_header.magic = 0x6262;
1100         }
1101 
1102         if (inbox_info_header.box_info_size != file_size) {
1103             if (inbox_info_header.box_info_size == 0)
1104                 LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader box info size is not set");
1105             else
1106                 LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader box info size is incorrect:",
1107                           inbox_info_header.box_info_size);
1108             inbox_info_header.box_info_size = sizeof(CecBoxInfoHeader);
1109         }
1110 
1111         if (inbox_info_header.max_box_size == 0) {
1112             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max box size is not set");
1113         } else if (inbox_info_header.max_box_size > 0x100000) {
1114             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max box size is too large: {}",
1115                       inbox_info_header.max_box_size);
1116         }
1117 
1118         if (inbox_info_header.max_message_num == 0) {
1119             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message number is not set");
1120         } else if (inbox_info_header.max_message_num > 99) {
1121             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message number is too large: {}",
1122                       inbox_info_header.max_message_num);
1123         }
1124 
1125         if (inbox_info_header.max_message_size == 0) {
1126             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message size is not set");
1127         } else if (inbox_info_header.max_message_size > 0x019000) {
1128             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max message size is too large");
1129         }
1130 
1131         if (inbox_info_header.max_batch_size == 0) {
1132             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max batch size is not set");
1133             inbox_info_header.max_batch_size = inbox_info_header.max_message_num;
1134         } else if (inbox_info_header.max_batch_size != inbox_info_header.max_message_num) {
1135             LOG_DEBUG(Service_CECD, "CecInBoxInfoHeader max batch size != max message number");
1136         }
1137 
1138         std::memcpy(file_buffer.data(), &inbox_info_header, sizeof(CecBoxInfoHeader));
1139         break;
1140     }
1141     case CecDataPathType::OutboxInfo: {
1142         CecBoxInfoHeader outbox_info_header = {};
1143         std::memcpy(&outbox_info_header, file_buffer.data(), sizeof(CecBoxInfoHeader));
1144 
1145         LOG_DEBUG(Service_CECD,
1146                   "CecBoxInfoHeader: magic={:#06x}, box_info_size={:#010x}, "
1147                   "max_box_size={:#010x}, box_size={:#010x}, "
1148                   "max_message_num={:#010x}, message_num={:#010x}, "
1149                   "max_batch_size={:#010x}, max_message_size={:#010x}",
1150                   outbox_info_header.magic, outbox_info_header.box_info_size,
1151                   outbox_info_header.max_box_size, outbox_info_header.box_size,
1152                   outbox_info_header.max_message_num, outbox_info_header.message_num,
1153                   outbox_info_header.max_batch_size, outbox_info_header.max_message_size);
1154 
1155         if (outbox_info_header.magic != 0x6262) { // 'bb'
1156             if (outbox_info_header.magic == 0)
1157                 LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader magic number is not set");
1158             else
1159                 LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader magic number is incorrect: {}",
1160                           outbox_info_header.magic);
1161             outbox_info_header.magic = 0x6262;
1162         }
1163 
1164         if (outbox_info_header.box_info_size != file_buffer.size()) {
1165             if (outbox_info_header.box_info_size == 0)
1166                 LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader box info size is not set");
1167             else
1168                 LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader box info size is incorrect:",
1169                           outbox_info_header.box_info_size);
1170             outbox_info_header.box_info_size = sizeof(CecBoxInfoHeader);
1171             outbox_info_header.message_num = 0;
1172         }
1173 
1174         if (outbox_info_header.max_box_size == 0) {
1175             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max box size is not set");
1176         } else if (outbox_info_header.max_box_size > 0x100000) {
1177             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max box size is too large");
1178         }
1179 
1180         if (outbox_info_header.max_message_num == 0) {
1181             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message number is not set");
1182         } else if (outbox_info_header.max_message_num > 99) {
1183             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message number is too large");
1184         }
1185 
1186         if (outbox_info_header.max_message_size == 0) {
1187             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message size is not set");
1188         } else if (outbox_info_header.max_message_size > 0x019000) {
1189             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max message size is too large");
1190         }
1191 
1192         if (outbox_info_header.max_batch_size == 0) {
1193             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max batch size is not set");
1194             outbox_info_header.max_batch_size = outbox_info_header.max_message_num;
1195         } else if (outbox_info_header.max_batch_size != outbox_info_header.max_message_num) {
1196             LOG_DEBUG(Service_CECD, "CecOutBoxInfoHeader max batch size != max message number");
1197         }
1198 
1199         /// We need to read the /CEC/<id>/OutBox directory to find out which messages, if any,
1200         /// are present. The num_of_messages = (total_read_count) - 2, to adjust for
1201         /// the BoxInfo____ and OBIndex_____files that are present in the directory as well.
1202         FileSys::Path outbox_path(
1203             GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id).data());
1204 
1205         auto dir_result = cecd_system_save_data_archive->OpenDirectory(outbox_path);
1206 
1207         auto outbox_dir = std::move(dir_result).Unwrap();
1208         std::vector<FileSys::Entry> entries(outbox_info_header.max_message_num + 2);
1209         const u32 entry_count =
1210             outbox_dir->Read(outbox_info_header.max_message_num + 2, entries.data());
1211         outbox_dir->Close();
1212 
1213         LOG_DEBUG(Service_CECD, "Number of entries found in /OutBox: {}", entry_count);
1214         std::array<CecMessageHeader, 8> message_headers;
1215 
1216         std::string boxinfo_name("BoxInfo_____");
1217         std::string obindex_name("OBIndex_____");
1218         std::string file_name;
1219         std::u16string u16_filename;
1220 
1221         for (u32 i = 0; i < entry_count; i++) {
1222             u16_filename = std::u16string(entries[i].filename);
1223             file_name = Common::UTF16ToUTF8(u16_filename);
1224 
1225             if (boxinfo_name.compare(file_name) != 0 && obindex_name.compare(file_name) != 0) {
1226                 LOG_DEBUG(Service_CECD, "Adding message to BoxInfo_____: {}", file_name);
1227 
1228                 FileSys::Path message_path(
1229                     (GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id) + "/" +
1230                      file_name)
1231                         .data());
1232 
1233                 FileSys::Mode mode;
1234                 mode.read_flag.Assign(1);
1235 
1236                 auto message_result = cecd_system_save_data_archive->OpenFile(message_path, mode);
1237 
1238                 auto message = std::move(message_result).Unwrap();
1239                 const u32 message_size = static_cast<u32>(message->GetSize());
1240                 std::vector<u8> buffer(message_size);
1241 
1242                 message->Read(0, message_size, buffer.data()).Unwrap();
1243                 message->Close();
1244 
1245                 std::memcpy(&message_headers[outbox_info_header.message_num++], buffer.data(),
1246                             sizeof(CecMessageHeader));
1247             }
1248         }
1249 
1250         if (outbox_info_header.message_num > 0) {
1251             const u32 message_headers_size =
1252                 outbox_info_header.message_num * sizeof(CecMessageHeader);
1253 
1254             file_buffer.resize(sizeof(CecBoxInfoHeader) + message_headers_size, 0);
1255             outbox_info_header.box_info_size += message_headers_size;
1256 
1257             std::memcpy(file_buffer.data() + sizeof(CecBoxInfoHeader), &message_headers,
1258                         message_headers_size);
1259         }
1260 
1261         std::memcpy(file_buffer.data(), &outbox_info_header, sizeof(CecBoxInfoHeader));
1262         break;
1263     }
1264     case CecDataPathType::OutboxIndex: {
1265         CecOBIndexHeader obindex_header = {};
1266         std::memcpy(&obindex_header, file_buffer.data(), sizeof(CecOBIndexHeader));
1267 
1268         if (file_size < sizeof(CecOBIndexHeader)) { // 0x08, minimum size
1269             LOG_DEBUG(Service_CECD, "CecOBIndexHeader size is too small: {}", file_size);
1270         }
1271 
1272         if (obindex_header.magic != 0x6767) { // 'gg'
1273             if (obindex_header.magic == 0)
1274                 LOG_DEBUG(Service_CECD, "CecOBIndexHeader magic number is not set");
1275             else
1276                 LOG_DEBUG(Service_CECD, "CecOBIndexHeader magic number is incorrect: {}",
1277                           obindex_header.magic);
1278             obindex_header.magic = 0x6767;
1279         }
1280 
1281         if (obindex_header.message_num == 0) {
1282             if (file_size > sizeof(CecOBIndexHeader)) {
1283                 LOG_DEBUG(Service_CECD, "CecOBIndexHeader message number is not set");
1284                 obindex_header.message_num = (file_size % 8) - 1; // 8 byte message id - 1 header
1285             }
1286         } else if (obindex_header.message_num != (file_size % 8) - 1) {
1287             LOG_DEBUG(Service_CECD, "CecOBIndexHeader message number is incorrect: {}",
1288                       obindex_header.message_num);
1289             obindex_header.message_num = 0;
1290         }
1291 
1292         /// We need to read the /CEC/<id>/OutBox directory to find out which messages, if any,
1293         /// are present. The num_of_messages = (total_read_count) - 2, to adjust for
1294         /// the BoxInfo____ and OBIndex_____files that are present in the directory as well.
1295         FileSys::Path outbox_path(
1296             GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id).data());
1297 
1298         auto dir_result = cecd_system_save_data_archive->OpenDirectory(outbox_path);
1299 
1300         auto outbox_dir = std::move(dir_result).Unwrap();
1301         std::vector<FileSys::Entry> entries(8);
1302         const u32 entry_count = outbox_dir->Read(8, entries.data());
1303         outbox_dir->Close();
1304 
1305         LOG_DEBUG(Service_CECD, "Number of entries found in /OutBox: {}", entry_count);
1306         std::array<std::array<u8, 8>, 8> message_ids;
1307 
1308         std::string boxinfo_name("BoxInfo_____");
1309         std::string obindex_name("OBIndex_____");
1310         std::string file_name;
1311         std::u16string u16_filename;
1312 
1313         for (u32 i = 0; i < entry_count; i++) {
1314             u16_filename = std::u16string(entries[i].filename);
1315             file_name = Common::UTF16ToUTF8(u16_filename);
1316 
1317             if (boxinfo_name.compare(file_name) != 0 && obindex_name.compare(file_name) != 0) {
1318                 FileSys::Path message_path(
1319                     (GetCecDataPathTypeAsString(CecDataPathType::OutboxDir, ncch_program_id) + "/" +
1320                      file_name)
1321                         .data());
1322 
1323                 FileSys::Mode mode;
1324                 mode.read_flag.Assign(1);
1325 
1326                 auto message_result = cecd_system_save_data_archive->OpenFile(message_path, mode);
1327 
1328                 auto message = std::move(message_result).Unwrap();
1329                 const u32 message_size = static_cast<u32>(message->GetSize());
1330                 std::vector<u8> buffer(message_size);
1331 
1332                 message->Read(0, message_size, buffer.data()).Unwrap();
1333                 message->Close();
1334 
1335                 // Message id is at offset 0x20, and is 8 bytes
1336                 std::memcpy(&message_ids[obindex_header.message_num++], buffer.data() + 0x20, 8);
1337             }
1338         }
1339 
1340         if (obindex_header.message_num > 0) {
1341             const u32 message_ids_size = obindex_header.message_num * 8;
1342             file_buffer.resize(sizeof(CecOBIndexHeader) + message_ids_size);
1343             std::memcpy(file_buffer.data() + sizeof(CecOBIndexHeader), &message_ids,
1344                         message_ids_size);
1345         }
1346 
1347         std::memcpy(file_buffer.data(), &obindex_header, sizeof(CecOBIndexHeader));
1348         break;
1349     }
1350     case CecDataPathType::InboxMsg:
1351         break;
1352     case CecDataPathType::OutboxMsg:
1353         break;
1354     case CecDataPathType::RootDir:
1355     case CecDataPathType::MboxDir:
1356     case CecDataPathType::InboxDir:
1357     case CecDataPathType::OutboxDir:
1358         break;
1359     case CecDataPathType::MboxData:
1360     case CecDataPathType::MboxIcon:
1361     case CecDataPathType::MboxTitle:
1362     default: {
1363     }
1364     }
1365 }
1366 
SessionData()1367 Module::SessionData::SessionData() {}
1368 
~SessionData()1369 Module::SessionData::~SessionData() {
1370     if (file)
1371         file->Close();
1372 }
1373 
Interface(std::shared_ptr<Module> cecd,const char * name,u32 max_session)1374 Module::Interface::Interface(std::shared_ptr<Module> cecd, const char* name, u32 max_session)
1375     : ServiceFramework(name, max_session), cecd(std::move(cecd)) {}
1376 
Module(Core::System & system)1377 Module::Module(Core::System& system) : system(system) {
1378     using namespace Kernel;
1379     cecinfo_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "CECD::cecinfo_event");
1380     change_state_event =
1381         system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "CECD::change_state_event");
1382 
1383     const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
1384     FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
1385 
1386     // Open the SystemSaveData archive 0x00010026
1387     FileSys::Path archive_path(cecd_system_savedata_id);
1388     auto archive_result = systemsavedata_factory.Open(archive_path, 0);
1389 
1390     // If the archive didn't exist, create the files inside
1391     if (archive_result.Code() != FileSys::ERR_NOT_FORMATTED) {
1392         ASSERT_MSG(archive_result.Succeeded(), "Could not open the CECD SystemSaveData archive!");
1393         cecd_system_save_data_archive = std::move(archive_result).Unwrap();
1394     } else {
1395         // Format the archive to create the directories
1396         systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
1397 
1398         // Open it again to get a valid archive now that the folder exists
1399         cecd_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
1400 
1401         /// Now that the archive is formatted, we need to create the root CEC directory,
1402         /// eventlog.dat, and CEC/MBoxList____
1403         const FileSys::Path root_dir_path(
1404             GetCecDataPathTypeAsString(CecDataPathType::RootDir, 0).data());
1405         cecd_system_save_data_archive->CreateDirectory(root_dir_path);
1406 
1407         FileSys::Mode mode;
1408         mode.write_flag.Assign(1);
1409         mode.create_flag.Assign(1);
1410 
1411         /// eventlog.dat resides in the root of the archive beside the CEC directory
1412         /// Initially created, at offset 0x0, are bytes 0x01 0x41 0x12, followed by
1413         /// zeroes until offset 0x1000, where it changes to 0xDD until the end of file
1414         /// at offset 0x30d53. 0xDD means that the cec module hasn't written data to that
1415         /// region yet.
1416         FileSys::Path eventlog_path("/eventlog.dat");
1417 
1418         auto eventlog_result = cecd_system_save_data_archive->OpenFile(eventlog_path, mode);
1419 
1420         constexpr u32 eventlog_size = 0x30d54;
1421         auto eventlog = std::move(eventlog_result).Unwrap();
1422         std::vector<u8> eventlog_buffer(eventlog_size);
1423 
1424         std::memset(&eventlog_buffer[0], 0, 0x1000);
1425         eventlog_buffer[0] = 0x01;
1426         eventlog_buffer[1] = 0x41;
1427         eventlog_buffer[2] = 0x12;
1428 
1429         eventlog->Write(0, eventlog_size, true, eventlog_buffer.data());
1430         eventlog->Close();
1431 
1432         /// MBoxList____ resides within the root CEC/ directory.
1433         /// Initially created, at offset 0x0, are bytes 0x68 0x68 0x00 0x00 0x01, with 0x6868 'hh',
1434         /// being the magic number. The rest of the file is filled with zeroes, until the end of
1435         /// file at offset 0x18b
1436         FileSys::Path mboxlist_path(
1437             GetCecDataPathTypeAsString(CecDataPathType::MboxList, 0).data());
1438 
1439         auto mboxlist_result = cecd_system_save_data_archive->OpenFile(mboxlist_path, mode);
1440 
1441         constexpr u32 mboxlist_size = 0x18c;
1442         auto mboxlist = std::move(mboxlist_result).Unwrap();
1443         std::vector<u8> mboxlist_buffer(mboxlist_size);
1444 
1445         std::memset(&mboxlist_buffer[0], 0, mboxlist_size);
1446         mboxlist_buffer[0] = 0x68;
1447         mboxlist_buffer[1] = 0x68;
1448         // mboxlist_buffer[2-3] are already zeroed
1449         mboxlist_buffer[4] = 0x01;
1450 
1451         mboxlist->Write(0, mboxlist_size, true, mboxlist_buffer.data());
1452         mboxlist->Close();
1453     }
1454 }
1455 
1456 Module::~Module() = default;
1457 
InstallInterfaces(Core::System & system)1458 void InstallInterfaces(Core::System& system) {
1459     auto& service_manager = system.ServiceManager();
1460     auto cecd = std::make_shared<Module>(system);
1461     std::make_shared<CECD_NDM>(cecd)->InstallAsService(service_manager);
1462     std::make_shared<CECD_S>(cecd)->InstallAsService(service_manager);
1463     std::make_shared<CECD_U>(cecd)->InstallAsService(service_manager);
1464 }
1465 
1466 } // namespace Service::CECD
1467