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