summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/hle/service/mii/mii.cpp357
-rw-r--r--src/core/hle/service/mii/mii_database.cpp142
-rw-r--r--src/core/hle/service/mii/mii_database.h66
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_database_manager.h58
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp410
-rw-r--r--src/core/hle/service/mii/mii_manager.h82
-rw-r--r--src/core/hle/service/mii/mii_result.h9
-rw-r--r--src/core/hle/service/mii/mii_types.h11
-rw-r--r--src/core/hle/service/mii/mii_util.h26
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp6
-rw-r--r--src/core/hle/service/mii/types/char_info.h106
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp213
-rw-r--r--src/core/hle/service/mii/types/core_data.h7
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp83
-rw-r--r--src/core/hle/service/mii/types/store_data.h19
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp78
-rw-r--r--src/core/hle/service/nfc/common/device.cpp8
19 files changed, 1861 insertions, 244 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4e1f1f47a..d0f76e57e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -596,6 +596,10 @@ add_library(core STATIC
596 hle/service/mii/types/ver3_store_data.h 596 hle/service/mii/types/ver3_store_data.h
597 hle/service/mii/mii.cpp 597 hle/service/mii/mii.cpp
598 hle/service/mii/mii.h 598 hle/service/mii/mii.h
599 hle/service/mii/mii_database.cpp
600 hle/service/mii/mii_database.h
601 hle/service/mii/mii_database_manager.cpp
602 hle/service/mii/mii_database_manager.h
599 hle/service/mii/mii_manager.cpp 603 hle/service/mii/mii_manager.cpp
600 hle/service/mii/mii_manager.h 604 hle/service/mii/mii_manager.h
601 hle/service/mii/mii_result.h 605 hle/service/mii/mii_result.h
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 3b83c5ed7..8de806cfb 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -8,6 +8,9 @@
8#include "core/hle/service/mii/mii.h" 8#include "core/hle/service/mii/mii.h"
9#include "core/hle/service/mii/mii_manager.h" 9#include "core/hle/service/mii/mii_manager.h"
10#include "core/hle/service/mii/mii_result.h" 10#include "core/hle/service/mii/mii_result.h"
11#include "core/hle/service/mii/types/char_info.h"
12#include "core/hle/service/mii/types/store_data.h"
13#include "core/hle/service/mii/types/ver3_store_data.h"
11#include "core/hle/service/server_manager.h" 14#include "core/hle/service/server_manager.h"
12#include "core/hle/service/service.h" 15#include "core/hle/service/service.h"
13 16
@@ -27,29 +30,31 @@ public:
27 {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, 30 {5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
28 {6, &IDatabaseService::BuildRandom, "BuildRandom"}, 31 {6, &IDatabaseService::BuildRandom, "BuildRandom"},
29 {7, &IDatabaseService::BuildDefault, "BuildDefault"}, 32 {7, &IDatabaseService::BuildDefault, "BuildDefault"},
30 {8, nullptr, "Get2"}, 33 {8, &IDatabaseService::Get2, "Get2"},
31 {9, nullptr, "Get3"}, 34 {9, &IDatabaseService::Get3, "Get3"},
32 {10, nullptr, "UpdateLatest1"}, 35 {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"},
33 {11, nullptr, "FindIndex"}, 36 {11, &IDatabaseService::FindIndex, "FindIndex"},
34 {12, nullptr, "Move"}, 37 {12, &IDatabaseService::Move, "Move"},
35 {13, nullptr, "AddOrReplace"}, 38 {13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
36 {14, nullptr, "Delete"}, 39 {14, &IDatabaseService::Delete, "Delete"},
37 {15, nullptr, "DestroyFile"}, 40 {15, &IDatabaseService::DestroyFile, "DestroyFile"},
38 {16, nullptr, "DeleteFile"}, 41 {16, &IDatabaseService::DeleteFile, "DeleteFile"},
39 {17, nullptr, "Format"}, 42 {17, &IDatabaseService::Format, "Format"},
40 {18, nullptr, "Import"}, 43 {18, nullptr, "Import"},
41 {19, nullptr, "Export"}, 44 {19, nullptr, "Export"},
42 {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, 45 {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"},
43 {21, &IDatabaseService::GetIndex, "GetIndex"}, 46 {21, &IDatabaseService::GetIndex, "GetIndex"},
44 {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, 47 {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
45 {23, &IDatabaseService::Convert, "Convert"}, 48 {23, &IDatabaseService::Convert, "Convert"},
46 {24, nullptr, "ConvertCoreDataToCharInfo"}, 49 {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"},
47 {25, nullptr, "ConvertCharInfoToCoreData"}, 50 {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"},
48 {26, nullptr, "Append"}, 51 {26, &IDatabaseService::Append, "Append"},
49 }; 52 };
50 // clang-format on 53 // clang-format on
51 54
52 RegisterHandlers(functions); 55 RegisterHandlers(functions);
56
57 manager.Initialize(metadata);
53 } 58 }
54 59
55private: 60private:
@@ -80,10 +85,10 @@ private:
80 IPC::RequestParser rp{ctx}; 85 IPC::RequestParser rp{ctx};
81 const auto source_flag{rp.PopRaw<SourceFlag>()}; 86 const auto source_flag{rp.PopRaw<SourceFlag>()};
82 87
83 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
84
85 const u32 mii_count = manager.GetCount(metadata, source_flag); 88 const u32 mii_count = manager.GetCount(metadata, source_flag);
86 89
90 LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
91
87 IPC::ResponseBuilder rb{ctx, 3}; 92 IPC::ResponseBuilder rb{ctx, 3};
88 rb.Push(ResultSuccess); 93 rb.Push(ResultSuccess);
89 rb.Push(mii_count); 94 rb.Push(mii_count);
@@ -94,16 +99,17 @@ private:
94 const auto source_flag{rp.PopRaw<SourceFlag>()}; 99 const auto source_flag{rp.PopRaw<SourceFlag>()};
95 const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; 100 const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
96 101
97 LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
98
99 u32 mii_count{}; 102 u32 mii_count{};
100 std::vector<CharInfoElement> char_info_elements(output_size); 103 std::vector<CharInfoElement> char_info_elements(output_size);
101 Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag); 104 const auto result = manager.Get(metadata, char_info_elements, mii_count, source_flag);
102 105
103 if (mii_count != 0) { 106 if (mii_count != 0) {
104 ctx.WriteBuffer(char_info_elements); 107 ctx.WriteBuffer(char_info_elements);
105 } 108 }
106 109
110 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
111 output_size, mii_count);
112
107 IPC::ResponseBuilder rb{ctx, 3}; 113 IPC::ResponseBuilder rb{ctx, 3};
108 rb.Push(result); 114 rb.Push(result);
109 rb.Push(mii_count); 115 rb.Push(mii_count);
@@ -114,16 +120,17 @@ private:
114 const auto source_flag{rp.PopRaw<SourceFlag>()}; 120 const auto source_flag{rp.PopRaw<SourceFlag>()};
115 const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; 121 const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
116 122
117 LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
118
119 u32 mii_count{}; 123 u32 mii_count{};
120 std::vector<CharInfo> char_info(output_size); 124 std::vector<CharInfo> char_info(output_size);
121 Result result = manager.Get(metadata, char_info, mii_count, source_flag); 125 const auto result = manager.Get(metadata, char_info, mii_count, source_flag);
122 126
123 if (mii_count != 0) { 127 if (mii_count != 0) {
124 ctx.WriteBuffer(char_info); 128 ctx.WriteBuffer(char_info);
125 } 129 }
126 130
131 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
132 output_size, mii_count);
133
127 IPC::ResponseBuilder rb{ctx, 3}; 134 IPC::ResponseBuilder rb{ctx, 3};
128 rb.Push(result); 135 rb.Push(result);
129 rb.Push(mii_count); 136 rb.Push(mii_count);
@@ -134,7 +141,7 @@ private:
134 const auto char_info{rp.PopRaw<CharInfo>()}; 141 const auto char_info{rp.PopRaw<CharInfo>()};
135 const auto source_flag{rp.PopRaw<SourceFlag>()}; 142 const auto source_flag{rp.PopRaw<SourceFlag>()};
136 143
137 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 144 LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
138 145
139 CharInfo new_char_info{}; 146 CharInfo new_char_info{};
140 const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); 147 const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag);
@@ -146,7 +153,7 @@ private:
146 153
147 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 154 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
148 rb.Push(ResultSuccess); 155 rb.Push(ResultSuccess);
149 rb.PushRaw<CharInfo>(new_char_info); 156 rb.PushRaw(new_char_info);
150 } 157 }
151 158
152 void BuildRandom(HLERequestContext& ctx) { 159 void BuildRandom(HLERequestContext& ctx) {
@@ -180,14 +187,14 @@ private:
180 187
181 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 188 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
182 rb.Push(ResultSuccess); 189 rb.Push(ResultSuccess);
183 rb.PushRaw<CharInfo>(char_info); 190 rb.PushRaw(char_info);
184 } 191 }
185 192
186 void BuildDefault(HLERequestContext& ctx) { 193 void BuildDefault(HLERequestContext& ctx) {
187 IPC::RequestParser rp{ctx}; 194 IPC::RequestParser rp{ctx};
188 const auto index{rp.Pop<u32>()}; 195 const auto index{rp.Pop<u32>()};
189 196
190 LOG_INFO(Service_Mii, "called with index={}", index); 197 LOG_DEBUG(Service_Mii, "called with index={}", index);
191 198
192 if (index > 5) { 199 if (index > 5) {
193 IPC::ResponseBuilder rb{ctx, 2}; 200 IPC::ResponseBuilder rb{ctx, 2};
@@ -200,7 +207,239 @@ private:
200 207
201 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 208 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
202 rb.Push(ResultSuccess); 209 rb.Push(ResultSuccess);
203 rb.PushRaw<CharInfo>(char_info); 210 rb.PushRaw(char_info);
211 }
212
213 void Get2(HLERequestContext& ctx) {
214 IPC::RequestParser rp{ctx};
215 const auto source_flag{rp.PopRaw<SourceFlag>()};
216 const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()};
217
218 u32 mii_count{};
219 std::vector<StoreDataElement> store_data_elements(output_size);
220 const auto result = manager.Get(metadata, store_data_elements, mii_count, source_flag);
221
222 if (mii_count != 0) {
223 ctx.WriteBuffer(store_data_elements);
224 }
225
226 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
227 output_size, mii_count);
228
229 IPC::ResponseBuilder rb{ctx, 3};
230 rb.Push(result);
231 rb.Push(mii_count);
232 }
233
234 void Get3(HLERequestContext& ctx) {
235 IPC::RequestParser rp{ctx};
236 const auto source_flag{rp.PopRaw<SourceFlag>()};
237 const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()};
238
239 u32 mii_count{};
240 std::vector<StoreData> store_data(output_size);
241 const auto result = manager.Get(metadata, store_data, mii_count, source_flag);
242
243 if (mii_count != 0) {
244 ctx.WriteBuffer(store_data);
245 }
246
247 LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
248 output_size, mii_count);
249
250 IPC::ResponseBuilder rb{ctx, 3};
251 rb.Push(result);
252 rb.Push(mii_count);
253 }
254
255 void UpdateLatest1(HLERequestContext& ctx) {
256 IPC::RequestParser rp{ctx};
257 const auto store_data{rp.PopRaw<StoreData>()};
258 const auto source_flag{rp.PopRaw<SourceFlag>()};
259
260 LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
261
262 Result result = ResultSuccess;
263 if (!is_system) {
264 result = ResultPermissionDenied;
265 }
266
267 StoreData new_store_data{};
268 if (result.IsSuccess()) {
269 result = manager.UpdateLatest(metadata, new_store_data, store_data, source_flag);
270 }
271
272 if (result.IsFailure()) {
273 IPC::ResponseBuilder rb{ctx, 2};
274 rb.Push(result);
275 return;
276 }
277
278 IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)};
279 rb.Push(ResultSuccess);
280 rb.PushRaw<StoreData>(new_store_data);
281 }
282
283 void FindIndex(HLERequestContext& ctx) {
284 IPC::RequestParser rp{ctx};
285 const auto create_id{rp.PopRaw<Common::UUID>()};
286 const auto is_special{rp.PopRaw<bool>()};
287
288 LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
289 create_id.FormattedString(), is_special);
290
291 const s32 index = manager.FindIndex(create_id, is_special);
292
293 IPC::ResponseBuilder rb{ctx, 3};
294 rb.Push(ResultSuccess);
295 rb.Push(index);
296 }
297
298 void Move(HLERequestContext& ctx) {
299 IPC::RequestParser rp{ctx};
300 const auto create_id{rp.PopRaw<Common::UUID>()};
301 const auto new_index{rp.PopRaw<s32>()};
302
303 LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(),
304 new_index);
305
306 Result result = ResultSuccess;
307 if (!is_system) {
308 result = ResultPermissionDenied;
309 }
310
311 if (result.IsSuccess()) {
312 const u32 count = manager.GetCount(metadata, SourceFlag::Database);
313 if (new_index < 0 || new_index >= static_cast<s32>(count)) {
314 result = ResultInvalidArgument;
315 }
316 }
317
318 if (result.IsSuccess()) {
319 result = manager.Move(metadata, new_index, create_id);
320 }
321
322 IPC::ResponseBuilder rb{ctx, 2};
323 rb.Push(result);
324 }
325
326 void AddOrReplace(HLERequestContext& ctx) {
327 IPC::RequestParser rp{ctx};
328 const auto store_data{rp.PopRaw<StoreData>()};
329
330 LOG_INFO(Service_Mii, "called");
331
332 Result result = ResultSuccess;
333
334 if (!is_system) {
335 result = ResultPermissionDenied;
336 }
337
338 if (result.IsSuccess()) {
339 result = manager.AddOrReplace(metadata, store_data);
340 }
341
342 IPC::ResponseBuilder rb{ctx, 2};
343 rb.Push(result);
344 }
345
346 void Delete(HLERequestContext& ctx) {
347 IPC::RequestParser rp{ctx};
348 const auto create_id{rp.PopRaw<Common::UUID>()};
349
350 LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString());
351
352 Result result = ResultSuccess;
353
354 if (!is_system) {
355 result = ResultPermissionDenied;
356 }
357
358 if (result.IsSuccess()) {
359 result = manager.Delete(metadata, create_id);
360 }
361
362 IPC::ResponseBuilder rb{ctx, 2};
363 rb.Push(result);
364 }
365
366 void DestroyFile(HLERequestContext& ctx) {
367 // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
368 const bool is_db_test_mode_enabled = false;
369
370 LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
371
372 Result result = ResultSuccess;
373
374 if (!is_db_test_mode_enabled) {
375 result = ResultTestModeOnly;
376 }
377
378 if (result.IsSuccess()) {
379 result = manager.DestroyFile(metadata);
380 }
381
382 IPC::ResponseBuilder rb{ctx, 2};
383 rb.Push(result);
384 }
385
386 void DeleteFile(HLERequestContext& ctx) {
387 // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
388 const bool is_db_test_mode_enabled = false;
389
390 LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
391
392 Result result = ResultSuccess;
393
394 if (!is_db_test_mode_enabled) {
395 result = ResultTestModeOnly;
396 }
397
398 if (result.IsSuccess()) {
399 result = manager.DeleteFile();
400 }
401
402 IPC::ResponseBuilder rb{ctx, 2};
403 rb.Push(result);
404 }
405
406 void Format(HLERequestContext& ctx) {
407 // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
408 const bool is_db_test_mode_enabled = false;
409
410 LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
411
412 Result result = ResultSuccess;
413
414 if (!is_db_test_mode_enabled) {
415 result = ResultTestModeOnly;
416 }
417
418 if (result.IsSuccess()) {
419 result = manager.Format(metadata);
420 }
421
422 IPC::ResponseBuilder rb{ctx, 2};
423 rb.Push(result);
424 }
425
426 void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) {
427 LOG_DEBUG(Service_Mii, "called");
428
429 bool is_broken_with_clear_flag = false;
430 Result result = ResultSuccess;
431
432 if (!is_system) {
433 result = ResultPermissionDenied;
434 }
435
436 if (result.IsSuccess()) {
437 is_broken_with_clear_flag = manager.IsBrokenWithClearFlag(metadata);
438 }
439
440 IPC::ResponseBuilder rb{ctx, 3};
441 rb.Push(result);
442 rb.Push<u8>(is_broken_with_clear_flag);
204 } 443 }
205 444
206 void GetIndex(HLERequestContext& ctx) { 445 void GetIndex(HLERequestContext& ctx) {
@@ -236,13 +475,53 @@ private:
236 LOG_INFO(Service_Mii, "called"); 475 LOG_INFO(Service_Mii, "called");
237 476
238 CharInfo char_info{}; 477 CharInfo char_info{};
239 manager.ConvertV3ToCharInfo(char_info, mii_v3); 478 const auto result = manager.ConvertV3ToCharInfo(char_info, mii_v3);
240 479
241 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 480 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
242 rb.Push(ResultSuccess); 481 rb.Push(result);
482 rb.PushRaw<CharInfo>(char_info);
483 }
484
485 void ConvertCoreDataToCharInfo(HLERequestContext& ctx) {
486 IPC::RequestParser rp{ctx};
487 const auto core_data{rp.PopRaw<CoreData>()};
488
489 LOG_INFO(Service_Mii, "called");
490
491 CharInfo char_info{};
492 const auto result = manager.ConvertCoreDataToCharInfo(char_info, core_data);
493
494 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
495 rb.Push(result);
243 rb.PushRaw<CharInfo>(char_info); 496 rb.PushRaw<CharInfo>(char_info);
244 } 497 }
245 498
499 void ConvertCharInfoToCoreData(HLERequestContext& ctx) {
500 IPC::RequestParser rp{ctx};
501 const auto char_info{rp.PopRaw<CharInfo>()};
502
503 LOG_INFO(Service_Mii, "called");
504
505 CoreData core_data{};
506 const auto result = manager.ConvertCharInfoToCoreData(core_data, char_info);
507
508 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
509 rb.Push(result);
510 rb.PushRaw<CoreData>(core_data);
511 }
512
513 void Append(HLERequestContext& ctx) {
514 IPC::RequestParser rp{ctx};
515 const auto char_info{rp.PopRaw<CharInfo>()};
516
517 LOG_INFO(Service_Mii, "called");
518
519 const auto result = manager.Append(metadata, char_info);
520
521 IPC::ResponseBuilder rb{ctx, 2};
522 rb.Push(result);
523 }
524
246 MiiManager manager{}; 525 MiiManager manager{};
247 DatabaseSessionMetadata metadata{}; 526 DatabaseSessionMetadata metadata{};
248 bool is_system{}; 527 bool is_system{};
@@ -278,9 +557,9 @@ public:
278 explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { 557 explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
279 // clang-format off 558 // clang-format off
280 static const FunctionInfo functions[] = { 559 static const FunctionInfo functions[] = {
281 {0, nullptr, "Initialize"}, 560 {0, &MiiImg::Initialize, "Initialize"},
282 {10, nullptr, "Reload"}, 561 {10, nullptr, "Reload"},
283 {11, nullptr, "GetCount"}, 562 {11, &MiiImg::GetCount, "GetCount"},
284 {12, nullptr, "IsEmpty"}, 563 {12, nullptr, "IsEmpty"},
285 {13, nullptr, "IsFull"}, 564 {13, nullptr, "IsFull"},
286 {14, nullptr, "GetAttribute"}, 565 {14, nullptr, "GetAttribute"},
@@ -297,6 +576,22 @@ public:
297 576
298 RegisterHandlers(functions); 577 RegisterHandlers(functions);
299 } 578 }
579
580private:
581 void Initialize(HLERequestContext& ctx) {
582 LOG_INFO(Service_Mii, "called");
583
584 IPC::ResponseBuilder rb{ctx, 2};
585 rb.Push(ResultSuccess);
586 }
587
588 void GetCount(HLERequestContext& ctx) {
589 LOG_DEBUG(Service_Mii, "called");
590
591 IPC::ResponseBuilder rb{ctx, 3};
592 rb.Push(ResultSuccess);
593 rb.Push(0);
594 }
300}; 595};
301 596
302void LoopProcess(Core::System& system) { 597void LoopProcess(Core::System& system) {
diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp
new file mode 100644
index 000000000..3803e58e2
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.cpp
@@ -0,0 +1,142 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/mii/mii_database.h"
5#include "core/hle/service/mii/mii_result.h"
6#include "core/hle/service/mii/mii_util.h"
7
8namespace Service::Mii {
9
10u8 NintendoFigurineDatabase::GetDatabaseLength() const {
11 return database_length;
12}
13
14bool NintendoFigurineDatabase::IsFull() const {
15 return database_length >= MaxDatabaseLength;
16}
17
18StoreData NintendoFigurineDatabase::Get(std::size_t index) const {
19 StoreData store_data = miis.at(index);
20
21 // This hack is to make external database dumps compatible
22 store_data.SetDeviceChecksum();
23
24 return store_data;
25}
26
27u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const {
28 if (magic == MiiMagic) {
29 return GetDatabaseLength();
30 }
31
32 u32 mii_count{};
33 for (std::size_t index = 0; index < mii_count; ++index) {
34 const auto& store_data = Get(index);
35 if (!store_data.IsSpecial()) {
36 mii_count++;
37 }
38 }
39
40 return mii_count;
41}
42
43bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index,
44 const Common::UUID& create_id) const {
45 for (std::size_t index = 0; index < database_length; ++index) {
46 if (miis[index].GetCreateId() == create_id) {
47 out_index = static_cast<u32>(index);
48 return true;
49 }
50 }
51
52 return false;
53}
54
55Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) {
56 if (current_index == new_index) {
57 return ResultNotUpdated;
58 }
59
60 const StoreData store_data = miis[current_index];
61
62 if (new_index > current_index) {
63 // Shift left
64 const u32 index_diff = new_index - current_index;
65 for (std::size_t i = 0; i < index_diff; i++) {
66 miis[current_index + i] = miis[current_index + i + 1];
67 }
68 } else {
69 // Shift right
70 const u32 index_diff = current_index - new_index;
71 for (std::size_t i = 0; i < index_diff; i++) {
72 miis[current_index - i] = miis[current_index - i - 1];
73 }
74 }
75
76 miis[new_index] = store_data;
77 crc = GenerateDatabaseCrc();
78 return ResultSuccess;
79}
80
81void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) {
82 miis[index] = store_data;
83 crc = GenerateDatabaseCrc();
84}
85
86void NintendoFigurineDatabase::Add(const StoreData& store_data) {
87 miis[database_length] = store_data;
88 database_length++;
89 crc = GenerateDatabaseCrc();
90}
91
92void NintendoFigurineDatabase::Delete(u32 index) {
93 // Shift left
94 const s32 new_database_size = database_length - 1;
95 if (static_cast<s32>(index) < new_database_size) {
96 for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) {
97 miis[i] = miis[i + 1];
98 }
99 }
100
101 database_length = static_cast<u8>(new_database_size);
102 crc = GenerateDatabaseCrc();
103}
104
105void NintendoFigurineDatabase::CleanDatabase() {
106 miis = {};
107 version = 1;
108 magic = DatabaseMagic;
109 database_length = 0;
110 crc = GenerateDatabaseCrc();
111}
112
113void NintendoFigurineDatabase::CorruptCrc() {
114 crc = GenerateDatabaseCrc();
115 crc = ~crc;
116}
117
118Result NintendoFigurineDatabase::CheckIntegrity() {
119 if (magic != DatabaseMagic) {
120 return ResultInvalidDatabaseSignature;
121 }
122
123 if (version != 1) {
124 return ResultInvalidDatabaseVersion;
125 }
126
127 if (crc != GenerateDatabaseCrc()) {
128 return ResultInvalidDatabaseChecksum;
129 }
130
131 if (database_length >= MaxDatabaseLength) {
132 return ResultInvalidDatabaseLength;
133 }
134
135 return ResultSuccess;
136}
137
138u16 NintendoFigurineDatabase::GenerateDatabaseCrc() {
139 return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc));
140}
141
142} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h
new file mode 100644
index 000000000..3bd240f93
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.h
@@ -0,0 +1,66 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7#include "core/hle/service/mii/types/store_data.h"
8
9namespace Service::Mii {
10
11constexpr std::size_t MaxDatabaseLength{100};
12constexpr u32 MiiMagic{0xa523b78f};
13constexpr u32 DatabaseMagic{0x4244464e}; // NFDB
14
15class NintendoFigurineDatabase {
16public:
17 /// Returns the total mii count.
18 u8 GetDatabaseLength() const;
19
20 /// Returns true if database is full.
21 bool IsFull() const;
22
23 /// Returns the mii of the specified index.
24 StoreData Get(std::size_t index) const;
25
26 /// Returns the total mii count. Ignoring special mii.
27 u32 GetCount(const DatabaseSessionMetadata& metadata) const;
28
29 /// Returns the index of a mii. If the mii isn't found returns false.
30 bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const;
31
32 /// Moves the location of a specific mii.
33 Result Move(u32 current_index, u32 new_index);
34
35 /// Replaces mii with new data.
36 void Replace(u32 index, const StoreData& store_data);
37
38 /// Adds a new mii to the end of the database.
39 void Add(const StoreData& store_data);
40
41 /// Removes mii from database and shifts left the remainding data.
42 void Delete(u32 index);
43
44 /// Deletes all contents with a fresh database
45 void CleanDatabase();
46
47 /// Intentionally sets a bad checksum
48 void CorruptCrc();
49
50 /// Returns success if database is valid otherwise returns the corresponding error code.
51 Result CheckIntegrity();
52
53private:
54 /// Returns the checksum of the database
55 u16 GenerateDatabaseCrc();
56
57 u32 magic{}; // 'NFDB'
58 std::array<StoreData, MaxDatabaseLength> miis{};
59 u8 version{};
60 u8 database_length{};
61 u16 crc{};
62};
63static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98,
64 "NintendoFigurineDatabase has incorrect size.");
65
66}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp
new file mode 100644
index 000000000..c39898594
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.cpp
@@ -0,0 +1,420 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/string_util.h"
10
11#include "core/hle/service/mii/mii_database_manager.h"
12#include "core/hle/service/mii/mii_result.h"
13#include "core/hle/service/mii/mii_util.h"
14#include "core/hle/service/mii/types/char_info.h"
15#include "core/hle/service/mii/types/store_data.h"
16
17namespace Service::Mii {
18const char* DbFileName = "MiiDatabase.dat";
19
20DatabaseManager::DatabaseManager() {}
21
22Result DatabaseManager::MountSaveData() {
23 if (!is_save_data_mounted) {
24 system_save_dir =
25 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
26 if (is_test_db) {
27 system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
28 "system/save/8000000000000031";
29 }
30
31 // mount point should be "mii:"
32
33 if (!Common::FS::CreateDirs(system_save_dir)) {
34 return ResultUnknown;
35 }
36 }
37
38 is_save_data_mounted = true;
39 return ResultSuccess;
40}
41
42Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
43 is_database_broken = false;
44 if (!is_save_data_mounted) {
45 return ResultInvalidArgument;
46 }
47
48 database.CleanDatabase();
49 update_counter++;
50 metadata.update_counter = update_counter;
51
52 const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
53 Common::FS::FileType::BinaryFile};
54
55 if (!db_file.IsOpen()) {
56 return SaveDatabase();
57 }
58
59 if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
60 is_database_broken = true;
61 }
62
63 if (db_file.Read(database) != 1) {
64 is_database_broken = true;
65 }
66
67 if (is_database_broken) {
68 // Dragons happen here for simplicity just clean the database
69 LOG_ERROR(Service_Mii, "Mii database is corrupted");
70 database.CleanDatabase();
71 return ResultUnknown;
72 }
73
74 const auto result = database.CheckIntegrity();
75
76 if (result.IsError()) {
77 LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
78 database.CleanDatabase();
79 return ResultSuccess;
80 }
81
82 LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
83 database.GetDatabaseLength());
84 return ResultSuccess;
85}
86
87bool DatabaseManager::IsFullDatabase() const {
88 return database.GetDatabaseLength() == MaxDatabaseLength;
89}
90
91bool DatabaseManager::IsModified() const {
92 return is_moddified;
93}
94
95u64 DatabaseManager::GetUpdateCounter() const {
96 return update_counter;
97}
98
99u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
100 const u32 database_size = database.GetDatabaseLength();
101 if (metadata.magic == MiiMagic) {
102 return database_size;
103 }
104
105 // Special mii can't be used. Skip those.
106
107 u32 mii_count{};
108 for (std::size_t index = 0; index < database_size; ++index) {
109 const auto& store_data = database.Get(index);
110 if (store_data.IsSpecial()) {
111 continue;
112 }
113 mii_count++;
114 }
115
116 return mii_count;
117}
118
119void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
120 const DatabaseSessionMetadata& metadata) const {
121 if (metadata.magic == MiiMagic) {
122 out_store_data = database.Get(index);
123 return;
124 }
125
126 // The index refeers to the mii index without special mii.
127 // Search on the database until we find it
128
129 u32 virtual_index = 0;
130 const u32 database_size = database.GetDatabaseLength();
131 for (std::size_t i = 0; i < database_size; ++i) {
132 const auto& store_data = database.Get(i);
133 if (store_data.IsSpecial()) {
134 continue;
135 }
136 if (virtual_index == index) {
137 out_store_data = store_data;
138 return;
139 }
140 virtual_index++;
141 }
142
143 // This function doesn't fail. It returns the first mii instead
144 out_store_data = database.Get(0);
145}
146
147Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
148 bool is_special) const {
149 u32 index{};
150 const bool is_found = database.GetIndexByCreatorId(index, create_id);
151
152 if (!is_found) {
153 return ResultNotFound;
154 }
155
156 if (is_special) {
157 out_index = index;
158 return ResultSuccess;
159 }
160
161 if (database.Get(index).IsSpecial()) {
162 return ResultNotFound;
163 }
164
165 out_index = 0;
166
167 if (index < 1) {
168 return ResultSuccess;
169 }
170
171 for (std::size_t i = 0; i <= index; ++i) {
172 if (database.Get(i).IsSpecial()) {
173 continue;
174 }
175 out_index++;
176 }
177 return ResultSuccess;
178}
179
180Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
181 const Common::UUID& create_id) const {
182 u32 index{};
183 const bool is_found = database.GetIndexByCreatorId(index, create_id);
184
185 if (!is_found) {
186 return ResultNotFound;
187 }
188
189 if (metadata.magic == MiiMagic) {
190 out_index = index;
191 return ResultSuccess;
192 }
193
194 if (database.Get(index).IsSpecial()) {
195 return ResultNotFound;
196 }
197
198 out_index = 0;
199
200 if (index < 1) {
201 return ResultSuccess;
202 }
203
204 // The index refeers to the mii index without special mii.
205 // Search on the database until we find it
206
207 for (std::size_t i = 0; i <= index; ++i) {
208 const auto& store_data = database.Get(i);
209 if (store_data.IsSpecial()) {
210 continue;
211 }
212 out_index++;
213 }
214 return ResultSuccess;
215}
216
217Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
218 const Common::UUID& create_id) const {
219 const auto database_size = database.GetDatabaseLength();
220
221 if (database_size >= 1) {
222 u32 virtual_index{};
223 for (std::size_t i = 0; i < database_size; ++i) {
224 const StoreData& store_data = database.Get(i);
225 if (store_data.IsSpecial()) {
226 continue;
227 }
228 if (virtual_index == new_index) {
229 const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
230 if (!is_found) {
231 return ResultNotFound;
232 }
233 if (store_data.IsSpecial()) {
234 return ResultInvalidOperation;
235 }
236 return ResultSuccess;
237 }
238 virtual_index++;
239 }
240 }
241
242 const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
243 if (!is_found) {
244 return ResultNotFound;
245 }
246 const StoreData& store_data = database.Get(out_index);
247 if (store_data.IsSpecial()) {
248 return ResultInvalidOperation;
249 }
250 return ResultSuccess;
251}
252
253Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
254 const Common::UUID& create_id) {
255 u32 current_index{};
256 if (metadata.magic == MiiMagic) {
257 const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
258 if (!is_found) {
259 return ResultNotFound;
260 }
261 } else {
262 const auto result = FindMoveIndex(current_index, new_index, create_id);
263 if (result.IsError()) {
264 return result;
265 }
266 }
267
268 const auto result = database.Move(current_index, new_index);
269 if (result.IsFailure()) {
270 return result;
271 }
272
273 is_moddified = true;
274 update_counter++;
275 metadata.update_counter = update_counter;
276 return ResultSuccess;
277}
278
279Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
280 const StoreData& store_data) {
281 if (store_data.IsValid() != ValidationResult::NoErrors) {
282 return ResultInvalidStoreData;
283 }
284 if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
285 return ResultInvalidOperation;
286 }
287
288 u32 index{};
289 const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
290 if (is_found) {
291 const StoreData& old_store_data = database.Get(index);
292
293 if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
294 return ResultInvalidOperation;
295 }
296
297 database.Replace(index, store_data);
298 } else {
299 if (database.IsFull()) {
300 return ResultDatabaseFull;
301 }
302
303 database.Add(store_data);
304 }
305
306 is_moddified = true;
307 update_counter++;
308 metadata.update_counter = update_counter;
309 return ResultSuccess;
310}
311
312Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
313 u32 index{};
314 const bool is_found = database.GetIndexByCreatorId(index, create_id);
315 if (!is_found) {
316 return ResultNotFound;
317 }
318
319 if (metadata.magic != MiiMagic) {
320 const auto& store_data = database.Get(index);
321 if (store_data.IsSpecial()) {
322 return ResultInvalidOperation;
323 }
324 }
325
326 database.Delete(index);
327
328 is_moddified = true;
329 update_counter++;
330 metadata.update_counter = update_counter;
331 return ResultSuccess;
332}
333
334Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
335 if (char_info.Verify() != ValidationResult::NoErrors) {
336 return ResultInvalidCharInfo2;
337 }
338 if (char_info.GetType() == 1) {
339 return ResultInvalidCharInfoType;
340 }
341
342 u32 index{};
343 StoreData store_data{};
344
345 // Loop until the mii we created is not on the database
346 do {
347 store_data.BuildWithCharInfo(char_info);
348 } while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
349
350 const Result result = store_data.Restore();
351
352 if (result.IsSuccess() || result == ResultNotUpdated) {
353 return AddOrReplace(metadata, store_data);
354 }
355
356 return result;
357}
358
359Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
360 database.CorruptCrc();
361
362 is_moddified = true;
363 update_counter++;
364 metadata.update_counter = update_counter;
365
366 const auto result = SaveDatabase();
367 database.CleanDatabase();
368
369 return result;
370}
371
372Result DatabaseManager::DeleteFile() {
373 const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
374 // TODO: Return proper FS error here
375 return result ? ResultSuccess : ResultUnknown;
376}
377
378void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
379 database.CleanDatabase();
380 is_moddified = true;
381 update_counter++;
382 metadata.update_counter = update_counter;
383}
384
385Result DatabaseManager::SaveDatabase() {
386 // TODO: Replace unknown error codes with proper FS error codes when available
387
388 if (!Common::FS::Exists(system_save_dir / DbFileName)) {
389 if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
390 LOG_ERROR(Service_Mii, "Failed to create mii database");
391 return ResultUnknown;
392 }
393 }
394
395 const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
396 if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
397 if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
398 LOG_ERROR(Service_Mii, "Failed to delete mii database");
399 return ResultUnknown;
400 }
401 if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
402 LOG_ERROR(Service_Mii, "Failed to create mii database");
403 return ResultUnknown;
404 }
405 }
406
407 const Common::FS::IOFile db_file{system_save_dir / DbFileName,
408 Common::FS::FileAccessMode::ReadWrite,
409 Common::FS::FileType::BinaryFile};
410
411 if (db_file.Write(database) != 1) {
412 LOG_ERROR(Service_Mii, "Failed to save mii database");
413 return ResultUnknown;
414 }
415
416 is_moddified = false;
417 return ResultSuccess;
418}
419
420} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h
new file mode 100644
index 000000000..52c32be82
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.h
@@ -0,0 +1,58 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/fs/fs.h"
7#include "core/hle/result.h"
8#include "core/hle/service/mii/mii_database.h"
9
10namespace Service::Mii {
11class CharInfo;
12class StoreData;
13
14class DatabaseManager {
15public:
16 DatabaseManager();
17 Result MountSaveData();
18 Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
19
20 bool IsFullDatabase() const;
21 bool IsModified() const;
22 u64 GetUpdateCounter() const;
23
24 void Get(StoreData& out_store_data, std::size_t index,
25 const DatabaseSessionMetadata& metadata) const;
26 u32 GetCount(const DatabaseSessionMetadata& metadata) const;
27
28 Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
29 Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
30 const Common::UUID& create_id) const;
31 Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
32
33 Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
34 const Common::UUID& create_id);
35 Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
36 Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
37 Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
38
39 Result DestroyFile(DatabaseSessionMetadata& metadata);
40 Result DeleteFile();
41 void Format(DatabaseSessionMetadata& metadata);
42
43 Result SaveDatabase();
44
45private:
46 // This is the global value of
47 // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
48 bool is_test_db{};
49
50 bool is_moddified{};
51 bool is_save_data_mounted{};
52 u64 update_counter{};
53 NintendoFigurineDatabase database{};
54
55 std::filesystem::path system_save_dir{};
56};
57
58}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 292d63777..a5a2a9232 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,38 +1,63 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <cstring>
5#include <random>
6
7#include "common/assert.h"
8#include "common/logging/log.h" 4#include "common/logging/log.h"
9#include "common/string_util.h" 5#include "core/hle/service/mii/mii_database_manager.h"
10
11#include "core/hle/service/acc/profile_manager.h"
12#include "core/hle/service/mii/mii_manager.h" 6#include "core/hle/service/mii/mii_manager.h"
13#include "core/hle/service/mii/mii_result.h" 7#include "core/hle/service/mii/mii_result.h"
14#include "core/hle/service/mii/mii_util.h" 8#include "core/hle/service/mii/mii_util.h"
9#include "core/hle/service/mii/types/char_info.h"
15#include "core/hle/service/mii/types/core_data.h" 10#include "core/hle/service/mii/types/core_data.h"
16#include "core/hle/service/mii/types/raw_data.h" 11#include "core/hle/service/mii/types/raw_data.h"
12#include "core/hle/service/mii/types/store_data.h"
13#include "core/hle/service/mii/types/ver3_store_data.h"
17 14
18namespace Service::Mii { 15namespace Service::Mii {
19constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; 16constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
20 17
21MiiManager::MiiManager() {} 18MiiManager::MiiManager() {}
22 19
20Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) {
21 database_manager.MountSaveData();
22 database_manager.Initialize(metadata, is_broken_with_clear_flag);
23 return ResultSuccess;
24}
25
26void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
27 StoreData store_data{};
28 store_data.BuildDefault(index);
29 out_char_info.SetFromStoreData(store_data);
30}
31
32void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
33 StoreData store_data{};
34 store_data.BuildBase(gender);
35 out_char_info.SetFromStoreData(store_data);
36}
37
38void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
39 StoreData store_data{};
40 store_data.BuildRandom(age, gender, race);
41 out_char_info.SetFromStoreData(store_data);
42}
43
44bool MiiManager::IsFullDatabase() const {
45 return database_manager.IsFullDatabase();
46}
47
48void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const {
49 metadata.interface_version = version;
50}
51
23bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { 52bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
24 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 53 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
25 return false; 54 return false;
26 } 55 }
27 56
28 const auto metadata_update_counter = metadata.update_counter; 57 const u64 metadata_update_counter = metadata.update_counter;
29 metadata.update_counter = update_counter; 58 const u64 database_update_counter = database_manager.GetUpdateCounter();
30 return metadata_update_counter != update_counter; 59 metadata.update_counter = database_update_counter;
31} 60 return metadata_update_counter != database_update_counter;
32
33bool MiiManager::IsFullDatabase() const {
34 // TODO(bunnei): We don't implement the Mii database, so it cannot be full
35 return false;
36} 61}
37 62
38u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { 63u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
@@ -41,72 +66,343 @@ u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag sou
41 mii_count += DefaultMiiCount; 66 mii_count += DefaultMiiCount;
42 } 67 }
43 if ((source_flag & SourceFlag::Database) != SourceFlag::None) { 68 if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
44 // TODO(bunnei): We don't implement the Mii database, but when we do, update this 69 mii_count += database_manager.GetCount(metadata);
45 } 70 }
46 return mii_count; 71 return mii_count;
47} 72}
48 73
49Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, 74Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index,
50 const CharInfo& char_info, SourceFlag source_flag) { 75 const Common::UUID& create_id) {
51 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 76 const auto result = database_manager.Move(metadata, index, create_id);
77
78 if (result.IsFailure()) {
79 return result;
80 }
81
82 if (!database_manager.IsModified()) {
83 return ResultNotUpdated;
84 }
85
86 return database_manager.SaveDatabase();
87}
88
89Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) {
90 const auto result = database_manager.AddOrReplace(metadata, store_data);
91
92 if (result.IsFailure()) {
93 return result;
94 }
95
96 if (!database_manager.IsModified()) {
97 return ResultNotUpdated;
98 }
99
100 return database_manager.SaveDatabase();
101}
102
103Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
104 const auto result = database_manager.Delete(metadata, create_id);
105
106 if (result.IsFailure()) {
107 return result;
108 }
109
110 if (!database_manager.IsModified()) {
111 return ResultNotUpdated;
112 }
113
114 return database_manager.SaveDatabase();
115}
116
117s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const {
118 s32 index{};
119 const auto result = database_manager.FindIndex(index, create_id, is_special);
120 if (result.IsError()) {
121 index = -1;
122 }
123 return index;
124}
125
126Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
127 s32& out_index) const {
128 if (char_info.Verify() != ValidationResult::NoErrors) {
129 return ResultInvalidCharInfo;
130 }
131
132 s32 index{};
133 Result result = {};
134 // FindIndex(index);
135
136 if (result.IsError()) {
137 return ResultNotFound;
138 }
139
140 if (index == -1) {
52 return ResultNotFound; 141 return ResultNotFound;
53 } 142 }
54 143
55 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 144 out_index = index;
56 return ResultNotFound; 145 return ResultSuccess;
57} 146}
58 147
59void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { 148Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
60 StoreData store_data{}; 149 const auto result = database_manager.Append(metadata, char_info);
61 store_data.BuildDefault(index); 150
62 out_char_info.SetFromStoreData(store_data); 151 if (result.IsError()) {
152 return ResultNotFound;
153 }
154
155 if (!database_manager.IsModified()) {
156 return ResultNotUpdated;
157 }
158
159 return database_manager.SaveDatabase();
63} 160}
64 161
65void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { 162bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) {
163 const bool is_broken = is_broken_with_clear_flag;
164 if (is_broken_with_clear_flag) {
165 is_broken_with_clear_flag = false;
166 database_manager.Format(metadata);
167 database_manager.SaveDatabase();
168 }
169 return is_broken;
170}
171
172Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) {
173 is_broken_with_clear_flag = true;
174 return database_manager.DestroyFile(metadata);
175}
176
177Result MiiManager::DeleteFile() {
178 return database_manager.DeleteFile();
179}
180
181Result MiiManager::Format(DatabaseSessionMetadata& metadata) {
182 database_manager.Format(metadata);
183
184 if (!database_manager.IsModified()) {
185 return ResultNotUpdated;
186 }
187 return database_manager.SaveDatabase();
188}
189
190Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
191 if (!mii_v3.IsValid()) {
192 return ResultInvalidCharInfo;
193 }
194
66 StoreData store_data{}; 195 StoreData store_data{};
67 store_data.BuildBase(gender); 196 mii_v3.BuildToStoreData(store_data);
197 const auto name = store_data.GetNickname();
198 if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
199 store_data.SetInvalidName();
200 }
201
68 out_char_info.SetFromStoreData(store_data); 202 out_char_info.SetFromStoreData(store_data);
203 return ResultSuccess;
69} 204}
70 205
71void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { 206Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info,
207 const CoreData& core_data) const {
208 if (core_data.IsValid() != ValidationResult::NoErrors) {
209 return ResultInvalidCharInfo;
210 }
211
72 StoreData store_data{}; 212 StoreData store_data{};
73 store_data.BuildRandom(age, gender, race); 213 store_data.BuildWithCoreData(core_data);
214 const auto name = store_data.GetNickname();
215 if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
216 store_data.SetInvalidName();
217 }
218
74 out_char_info.SetFromStoreData(store_data); 219 out_char_info.SetFromStoreData(store_data);
220 return ResultSuccess;
221}
222
223Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data,
224 const CharInfo& char_info) const {
225 if (char_info.Verify() != ValidationResult::NoErrors) {
226 return ResultInvalidCharInfo;
227 }
228
229 out_core_data.BuildFromCharInfo(char_info);
230 const auto name = out_core_data.GetNickname();
231 if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) {
232 out_core_data.SetNickname(out_core_data.GetInvalidNickname());
233 }
234
235 return ResultSuccess;
75} 236}
76 237
77void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { 238Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
239 const CharInfo& char_info, SourceFlag source_flag) const {
240 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
241 return ResultNotFound;
242 }
243
244 if (metadata.IsInterfaceVersionSupported(1)) {
245 if (char_info.Verify() != ValidationResult::NoErrors) {
246 return ResultInvalidCharInfo;
247 }
248 }
249
250 u32 index{};
251 Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId());
252
253 if (result.IsError()) {
254 return result;
255 }
256
78 StoreData store_data{}; 257 StoreData store_data{};
79 mii_v3.BuildToStoreData(store_data); 258 database_manager.Get(store_data, index, metadata);
259
260 if (store_data.GetType() != char_info.GetType()) {
261 return ResultNotFound;
262 }
263
80 out_char_info.SetFromStoreData(store_data); 264 out_char_info.SetFromStoreData(store_data);
265
266 if (char_info == out_char_info) {
267 return ResultNotUpdated;
268 }
269
270 return ResultSuccess;
271}
272
273Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
274 const StoreData& store_data, SourceFlag source_flag) const {
275 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
276 return ResultNotFound;
277 }
278
279 if (metadata.IsInterfaceVersionSupported(1)) {
280 if (store_data.IsValid() != ValidationResult::NoErrors) {
281 return ResultInvalidCharInfo;
282 }
283 }
284
285 u32 index{};
286 Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId());
287
288 if (result.IsError()) {
289 return result;
290 }
291
292 database_manager.Get(out_store_data, index, metadata);
293
294 if (out_store_data.GetType() != store_data.GetType()) {
295 return ResultNotFound;
296 }
297
298 if (store_data == out_store_data) {
299 return ResultNotUpdated;
300 }
301
302 return ResultSuccess;
81} 303}
82 304
83Result MiiManager::Get(const DatabaseSessionMetadata& metadata, 305Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
84 std::span<CharInfoElement> out_elements, u32& out_count, 306 std::span<CharInfoElement> out_elements, u32& out_count,
85 SourceFlag source_flag) { 307 SourceFlag source_flag) const {
86 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 308 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
87 return BuildDefault(out_elements, out_count, source_flag); 309 return BuildDefault(out_elements, out_count, source_flag);
88 } 310 }
89 311
90 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 312 const auto mii_count = database_manager.GetCount(metadata);
313
314 for (std::size_t index = 0; index < mii_count; ++index) {
315 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
316 return ResultInvalidArgumentSize;
317 }
318
319 StoreData store_data{};
320 database_manager.Get(store_data, index, metadata);
321
322 out_elements[out_count].source = Source::Database;
323 out_elements[out_count].char_info.SetFromStoreData(store_data);
324 out_count++;
325 }
91 326
92 // Include default Mii at the end of the list 327 // Include default Mii at the end of the list
93 return BuildDefault(out_elements, out_count, source_flag); 328 return BuildDefault(out_elements, out_count, source_flag);
94} 329}
95 330
96Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, 331Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
97 u32& out_count, SourceFlag source_flag) { 332 u32& out_count, SourceFlag source_flag) const {
98 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 333 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
99 return BuildDefault(out_char_info, out_count, source_flag); 334 return BuildDefault(out_char_info, out_count, source_flag);
100 } 335 }
101 336
102 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 337 const auto mii_count = database_manager.GetCount(metadata);
338
339 for (std::size_t index = 0; index < mii_count; ++index) {
340 if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
341 return ResultInvalidArgumentSize;
342 }
343
344 StoreData store_data{};
345 database_manager.Get(store_data, index, metadata);
346
347 out_char_info[out_count].SetFromStoreData(store_data);
348 out_count++;
349 }
103 350
104 // Include default Mii at the end of the list 351 // Include default Mii at the end of the list
105 return BuildDefault(out_char_info, out_count, source_flag); 352 return BuildDefault(out_char_info, out_count, source_flag);
106} 353}
107 354
355Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
356 std::span<StoreDataElement> out_elements, u32& out_count,
357 SourceFlag source_flag) const {
358 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
359 return BuildDefault(out_elements, out_count, source_flag);
360 }
361
362 const auto mii_count = database_manager.GetCount(metadata);
363
364 for (std::size_t index = 0; index < mii_count; ++index) {
365 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
366 return ResultInvalidArgumentSize;
367 }
368
369 StoreData store_data{};
370 database_manager.Get(store_data, index, metadata);
371
372 out_elements[out_count].store_data = store_data;
373 out_elements[out_count].source = Source::Database;
374 out_count++;
375 }
376
377 // Include default Mii at the end of the list
378 return BuildDefault(out_elements, out_count, source_flag);
379}
380
381Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
382 u32& out_count, SourceFlag source_flag) const {
383 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
384 return BuildDefault(out_store_data, out_count, source_flag);
385 }
386
387 const auto mii_count = database_manager.GetCount(metadata);
388
389 for (std::size_t index = 0; index < mii_count; ++index) {
390 if (out_store_data.size() <= static_cast<std::size_t>(out_count)) {
391 return ResultInvalidArgumentSize;
392 }
393
394 StoreData store_data{};
395 database_manager.Get(store_data, index, metadata);
396
397 out_store_data[out_count] = store_data;
398 out_count++;
399 }
400
401 // Include default Mii at the end of the list
402 return BuildDefault(out_store_data, out_count, source_flag);
403}
108Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, 404Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
109 SourceFlag source_flag) { 405 SourceFlag source_flag) const {
110 if ((source_flag & SourceFlag::Default) == SourceFlag::None) { 406 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
111 return ResultSuccess; 407 return ResultSuccess;
112 } 408 }
@@ -129,7 +425,7 @@ Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& ou
129} 425}
130 426
131Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, 427Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
132 SourceFlag source_flag) { 428 SourceFlag source_flag) const {
133 if ((source_flag & SourceFlag::Default) == SourceFlag::None) { 429 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
134 return ResultSuccess; 430 return ResultSuccess;
135 } 431 }
@@ -150,23 +446,41 @@ Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_coun
150 return ResultSuccess; 446 return ResultSuccess;
151} 447}
152 448
153Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, 449Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count,
154 s32& out_index) { 450 SourceFlag source_flag) const {
155 451 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
156 if (char_info.Verify() != ValidationResult::NoErrors) { 452 return ResultSuccess;
157 return ResultInvalidCharInfo;
158 } 453 }
159 454
160 constexpr u32 INVALID_INDEX{0xFFFFFFFF}; 455 for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
456 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
457 return ResultInvalidArgumentSize;
458 }
161 459
162 out_index = INVALID_INDEX; 460 out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index));
461 out_elements[out_count].source = Source::Default;
462 out_count++;
463 }
163 464
164 // TODO(bunnei): We don't implement the Mii database, so we can't have an index 465 return ResultSuccess;
165 return ResultNotFound;
166} 466}
167 467
168void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) { 468Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
169 metadata.interface_version = version; 469 SourceFlag source_flag) const {
470 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
471 return ResultSuccess;
472 }
473
474 for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
475 if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
476 return ResultInvalidArgumentSize;
477 }
478
479 out_char_info[out_count].BuildDefault(static_cast<u32>(index));
480 out_count++;
481 }
482
483 return ResultSuccess;
170} 484}
171 485
172} // namespace Service::Mii 486} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index a2e7a6d73..48d8e8bb7 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -3,47 +3,85 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <vector> 6#include <span>
7 7
8#include "core/hle/result.h" 8#include "core/hle/result.h"
9#include "core/hle/service/mii/mii_database_manager.h"
9#include "core/hle/service/mii/mii_types.h" 10#include "core/hle/service/mii/mii_types.h"
10#include "core/hle/service/mii/types/char_info.h"
11#include "core/hle/service/mii/types/store_data.h"
12#include "core/hle/service/mii/types/ver3_store_data.h"
13 11
14namespace Service::Mii { 12namespace Service::Mii {
13class CharInfo;
14class CoreData;
15class StoreData;
16class Ver3StoreData;
15 17
16// The Mii manager is responsible for loading and storing the Miis to the database in NAND along 18struct CharInfoElement;
17// with providing an easy interface for HLE emulation of the mii service. 19struct StoreDataElement;
20
21// The Mii manager is responsible for handling mii operations along with providing an easy interface
22// for HLE emulation of the mii service.
18class MiiManager { 23class MiiManager {
19public: 24public:
20 MiiManager(); 25 MiiManager();
26 Result Initialize(DatabaseSessionMetadata& metadata);
21 27
22 bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; 28 // Auto generated mii
29 void BuildDefault(CharInfo& out_char_info, u32 index) const;
30 void BuildBase(CharInfo& out_char_info, Gender gender) const;
31 void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
23 32
33 // Database operations
24 bool IsFullDatabase() const; 34 bool IsFullDatabase() const;
35 void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const;
36 bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
25 u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; 37 u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
26 Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, 38 Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id);
27 const CharInfo& char_info, SourceFlag source_flag); 39 Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data);
40 Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
41 s32 FindIndex(const Common::UUID& create_id, bool is_special) const;
42 Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
43 s32& out_index) const;
44 Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
45
46 // Test database operations
47 bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata);
48 Result DestroyFile(DatabaseSessionMetadata& metadata);
49 Result DeleteFile();
50 Result Format(DatabaseSessionMetadata& metadata);
51
52 // Mii conversions
53 Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
54 Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const;
55 Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const;
56 Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
57 const CharInfo& char_info, SourceFlag source_flag) const;
58 Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
59 const StoreData& store_data, SourceFlag source_flag) const;
60
61 // Overloaded getters
28 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, 62 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
29 u32& out_count, SourceFlag source_flag); 63 u32& out_count, SourceFlag source_flag) const;
30 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, 64 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
31 u32& out_count, SourceFlag source_flag); 65 u32& out_count, SourceFlag source_flag) const;
32 void BuildDefault(CharInfo& out_char_info, u32 index) const; 66 Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements,
33 void BuildBase(CharInfo& out_char_info, Gender gender) const; 67 u32& out_count, SourceFlag source_flag) const;
34 void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; 68 Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
35 void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; 69 u32& out_count, SourceFlag source_flag) const;
36 std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
37 Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
38 s32& out_index);
39 void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
40 70
41private: 71private:
42 Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, 72 Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
43 SourceFlag source_flag); 73 SourceFlag source_flag) const;
44 Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag); 74 Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
75 SourceFlag source_flag) const;
76 Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count,
77 SourceFlag source_flag) const;
78 Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
79 SourceFlag source_flag) const;
80
81 DatabaseManager database_manager{};
45 82
46 u64 update_counter{}; 83 // This should be a global value
84 bool is_broken_with_clear_flag{};
47}; 85};
48 86
49}; // namespace Service::Mii 87}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h
index 021cb76da..e2c36e556 100644
--- a/src/core/hle/service/mii/mii_result.h
+++ b/src/core/hle/service/mii/mii_result.h
@@ -1,4 +1,4 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#pragma once 4#pragma once
@@ -13,8 +13,15 @@ constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
13constexpr Result ResultNotFound{ErrorModule::Mii, 4}; 13constexpr Result ResultNotFound{ErrorModule::Mii, 4};
14constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; 14constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
15constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; 15constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
16constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101};
17constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103};
18constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104};
19constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105};
20constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107};
16constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; 21constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
17constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; 22constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
18constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; 23constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
24constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204};
25constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205};
19 26
20}; // namespace Service::Mii 27}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h
index 611ff4f81..f43efd83c 100644
--- a/src/core/hle/service/mii/mii_types.h
+++ b/src/core/hle/service/mii/mii_types.h
@@ -1,4 +1,4 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#pragma once 4#pragma once
@@ -13,6 +13,7 @@
13 13
14namespace Service::Mii { 14namespace Service::Mii {
15 15
16constexpr std::size_t MaxNameSize = 10;
16constexpr u8 MaxHeight = 127; 17constexpr u8 MaxHeight = 127;
17constexpr u8 MaxBuild = 127; 18constexpr u8 MaxBuild = 127;
18constexpr u8 MaxType = 1; 19constexpr u8 MaxType = 1;
@@ -26,14 +27,14 @@ constexpr u8 MaxEyebrowScale = 8;
26constexpr u8 MaxEyebrowAspect = 6; 27constexpr u8 MaxEyebrowAspect = 6;
27constexpr u8 MaxEyebrowRotate = 11; 28constexpr u8 MaxEyebrowRotate = 11;
28constexpr u8 MaxEyebrowX = 12; 29constexpr u8 MaxEyebrowX = 12;
29constexpr u8 MaxEyebrowY = 18; 30constexpr u8 MaxEyebrowY = 15;
30constexpr u8 MaxNoseScale = 8; 31constexpr u8 MaxNoseScale = 8;
31constexpr u8 MaxNoseY = 18; 32constexpr u8 MaxNoseY = 18;
32constexpr u8 MaxMouthScale = 8; 33constexpr u8 MaxMouthScale = 8;
33constexpr u8 MaxMoutAspect = 6; 34constexpr u8 MaxMoutAspect = 6;
34constexpr u8 MaxMouthY = 18; 35constexpr u8 MaxMouthY = 18;
35constexpr u8 MaxMustacheScale = 8; 36constexpr u8 MaxMustacheScale = 8;
36constexpr u8 MasMustacheY = 16; 37constexpr u8 MaxMustacheY = 16;
37constexpr u8 MaxGlassScale = 7; 38constexpr u8 MaxGlassScale = 7;
38constexpr u8 MaxGlassY = 20; 39constexpr u8 MaxGlassY = 20;
39constexpr u8 MaxMoleScale = 8; 40constexpr u8 MaxMoleScale = 8;
@@ -599,12 +600,12 @@ enum class ValidationResult : u32 {
599 InvalidRegionMove = 0x31, 600 InvalidRegionMove = 0x31,
600 InvalidCreateId = 0x32, 601 InvalidCreateId = 0x32,
601 InvalidName = 0x33, 602 InvalidName = 0x33,
603 InvalidChecksum = 0x34,
602 InvalidType = 0x35, 604 InvalidType = 0x35,
603}; 605};
604 606
605struct Nickname { 607struct Nickname {
606 static constexpr std::size_t MaxNameSize = 10; 608 std::array<char16_t, MaxNameSize> data{};
607 std::array<char16_t, MaxNameSize> data;
608 609
609 // Checks for null or dirty strings 610 // Checks for null or dirty strings
610 bool IsValid() const { 611 bool IsValid() const {
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h
index ddb544c23..3534fa31d 100644
--- a/src/core/hle/service/mii/mii_util.h
+++ b/src/core/hle/service/mii/mii_util.h
@@ -28,6 +28,32 @@ public:
28 return Common::swap16(static_cast<u16>(crc)); 28 return Common::swap16(static_cast<u16>(crc));
29 } 29 }
30 30
31 static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) {
32 constexpr u16 magic{0x1021};
33 s32 crc{};
34
35 for (std::size_t i = 0; i < uuid.uuid.size(); i++) {
36 for (std::size_t j = 0; j < 8; j++) {
37 crc <<= 1;
38 if ((crc & 0x10000) != 0) {
39 crc = crc ^ magic;
40 }
41 }
42 crc ^= uuid.uuid[i];
43 }
44
45 // As much as this looks wrong this is what N's does
46
47 for (std::size_t i = 0; i < data_size * 8; i++) {
48 crc <<= 1;
49 if ((crc & 0x10000) != 0) {
50 crc = crc ^ magic;
51 }
52 }
53
54 return Common::swap16(static_cast<u16>(crc));
55 }
56
31 static Common::UUID MakeCreateId() { 57 static Common::UUID MakeCreateId() {
32 return Common::UUID::MakeRandomRFC4122V4(); 58 return Common::UUID::MakeRandomRFC4122V4();
33 } 59 }
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp
index bb948c628..e90124af4 100644
--- a/src/core/hle/service/mii/types/char_info.cpp
+++ b/src/core/hle/service/mii/types/char_info.cpp
@@ -37,7 +37,7 @@ void CharInfo::SetFromStoreData(const StoreData& store_data) {
37 eyebrow_aspect = store_data.GetEyebrowAspect(); 37 eyebrow_aspect = store_data.GetEyebrowAspect();
38 eyebrow_rotate = store_data.GetEyebrowRotate(); 38 eyebrow_rotate = store_data.GetEyebrowRotate();
39 eyebrow_x = store_data.GetEyebrowX(); 39 eyebrow_x = store_data.GetEyebrowX();
40 eyebrow_y = store_data.GetEyebrowY(); 40 eyebrow_y = store_data.GetEyebrowY() + 3;
41 nose_type = store_data.GetNoseType(); 41 nose_type = store_data.GetNoseType();
42 nose_scale = store_data.GetNoseScale(); 42 nose_scale = store_data.GetNoseScale();
43 nose_y = store_data.GetNoseY(); 43 nose_y = store_data.GetNoseY();
@@ -150,7 +150,7 @@ ValidationResult CharInfo::Verify() const {
150 if (eyebrow_x > MaxEyebrowX) { 150 if (eyebrow_x > MaxEyebrowX) {
151 return ValidationResult::InvalidEyebrowX; 151 return ValidationResult::InvalidEyebrowX;
152 } 152 }
153 if (eyebrow_y > MaxEyebrowY) { 153 if (eyebrow_y - 3 > MaxEyebrowY) {
154 return ValidationResult::InvalidEyebrowY; 154 return ValidationResult::InvalidEyebrowY;
155 } 155 }
156 if (nose_type > NoseType::Max) { 156 if (nose_type > NoseType::Max) {
@@ -189,7 +189,7 @@ ValidationResult CharInfo::Verify() const {
189 if (mustache_scale > MaxMustacheScale) { 189 if (mustache_scale > MaxMustacheScale) {
190 return ValidationResult::InvalidMustacheScale; 190 return ValidationResult::InvalidMustacheScale;
191 } 191 }
192 if (mustache_y > MasMustacheY) { 192 if (mustache_y > MaxMustacheY) {
193 return ValidationResult::InvalidMustacheY; 193 return ValidationResult::InvalidMustacheY;
194 } 194 }
195 if (glass_type > GlassType::Max) { 195 if (glass_type > GlassType::Max) {
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h
index d069b221f..d0c457fd5 100644
--- a/src/core/hle/service/mii/types/char_info.h
+++ b/src/core/hle/service/mii/types/char_info.h
@@ -70,59 +70,59 @@ public:
70 bool operator==(const CharInfo& info); 70 bool operator==(const CharInfo& info);
71 71
72private: 72private:
73 Common::UUID create_id; 73 Common::UUID create_id{};
74 Nickname name; 74 Nickname name{};
75 u16 null_terminator; 75 u16 null_terminator{};
76 FontRegion font_region; 76 FontRegion font_region{};
77 FavoriteColor favorite_color; 77 FavoriteColor favorite_color{};
78 Gender gender; 78 Gender gender{};
79 u8 height; 79 u8 height{};
80 u8 build; 80 u8 build{};
81 u8 type; 81 u8 type{};
82 u8 region_move; 82 u8 region_move{};
83 FacelineType faceline_type; 83 FacelineType faceline_type{};
84 FacelineColor faceline_color; 84 FacelineColor faceline_color{};
85 FacelineWrinkle faceline_wrinkle; 85 FacelineWrinkle faceline_wrinkle{};
86 FacelineMake faceline_make; 86 FacelineMake faceline_make{};
87 HairType hair_type; 87 HairType hair_type{};
88 CommonColor hair_color; 88 CommonColor hair_color{};
89 HairFlip hair_flip; 89 HairFlip hair_flip{};
90 EyeType eye_type; 90 EyeType eye_type{};
91 CommonColor eye_color; 91 CommonColor eye_color{};
92 u8 eye_scale; 92 u8 eye_scale{};
93 u8 eye_aspect; 93 u8 eye_aspect{};
94 u8 eye_rotate; 94 u8 eye_rotate{};
95 u8 eye_x; 95 u8 eye_x{};
96 u8 eye_y; 96 u8 eye_y{};
97 EyebrowType eyebrow_type; 97 EyebrowType eyebrow_type{};
98 CommonColor eyebrow_color; 98 CommonColor eyebrow_color{};
99 u8 eyebrow_scale; 99 u8 eyebrow_scale{};
100 u8 eyebrow_aspect; 100 u8 eyebrow_aspect{};
101 u8 eyebrow_rotate; 101 u8 eyebrow_rotate{};
102 u8 eyebrow_x; 102 u8 eyebrow_x{};
103 u8 eyebrow_y; 103 u8 eyebrow_y{};
104 NoseType nose_type; 104 NoseType nose_type{};
105 u8 nose_scale; 105 u8 nose_scale{};
106 u8 nose_y; 106 u8 nose_y{};
107 MouthType mouth_type; 107 MouthType mouth_type{};
108 CommonColor mouth_color; 108 CommonColor mouth_color{};
109 u8 mouth_scale; 109 u8 mouth_scale{};
110 u8 mouth_aspect; 110 u8 mouth_aspect{};
111 u8 mouth_y; 111 u8 mouth_y{};
112 CommonColor beard_color; 112 CommonColor beard_color{};
113 BeardType beard_type; 113 BeardType beard_type{};
114 MustacheType mustache_type; 114 MustacheType mustache_type{};
115 u8 mustache_scale; 115 u8 mustache_scale{};
116 u8 mustache_y; 116 u8 mustache_y{};
117 GlassType glass_type; 117 GlassType glass_type{};
118 CommonColor glass_color; 118 CommonColor glass_color{};
119 u8 glass_scale; 119 u8 glass_scale{};
120 u8 glass_y; 120 u8 glass_y{};
121 MoleType mole_type; 121 MoleType mole_type{};
122 u8 mole_scale; 122 u8 mole_scale{};
123 u8 mole_x; 123 u8 mole_x{};
124 u8 mole_y; 124 u8 mole_y{};
125 u8 padding; 125 u8 padding{};
126}; 126};
127static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); 127static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
128static_assert(std::has_unique_object_representations_v<CharInfo>, 128static_assert(std::has_unique_object_representations_v<CharInfo>,
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
index 659288b51..465c6293a 100644
--- a/src/core/hle/service/mii/types/core_data.cpp
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -3,6 +3,7 @@
3 3
4#include "common/assert.h" 4#include "common/assert.h"
5#include "core/hle/service/mii/mii_util.h" 5#include "core/hle/service/mii/mii_util.h"
6#include "core/hle/service/mii/types/char_info.h"
6#include "core/hle/service/mii/types/core_data.h" 7#include "core/hle/service/mii/types/core_data.h"
7#include "core/hle/service/mii/types/raw_data.h" 8#include "core/hle/service/mii/types/raw_data.h"
8 9
@@ -185,9 +186,211 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
185 SetMoleY(20); 186 SetMoleY(20);
186} 187}
187 188
188u32 CoreData::IsValid() const { 189void CoreData::BuildFromCharInfo(const CharInfo& char_info) {
189 // TODO: Complete this 190 name = char_info.GetNickname();
190 return 0; 191 SetFontRegion(char_info.GetFontRegion());
192 SetFavoriteColor(char_info.GetFavoriteColor());
193 SetGender(char_info.GetGender());
194 SetHeight(char_info.GetHeight());
195 SetBuild(char_info.GetBuild());
196 SetType(char_info.GetType());
197 SetRegionMove(char_info.GetRegionMove());
198 SetFacelineType(char_info.GetFacelineType());
199 SetFacelineColor(char_info.GetFacelineColor());
200 SetFacelineWrinkle(char_info.GetFacelineWrinkle());
201 SetFacelineMake(char_info.GetFacelineMake());
202 SetHairType(char_info.GetHairType());
203 SetHairColor(char_info.GetHairColor());
204 SetHairFlip(char_info.GetHairFlip());
205 SetEyeType(char_info.GetEyeType());
206 SetEyeColor(char_info.GetEyeColor());
207 SetEyeScale(char_info.GetEyeScale());
208 SetEyeAspect(char_info.GetEyeAspect());
209 SetEyeRotate(char_info.GetEyeRotate());
210 SetEyeX(char_info.GetEyeX());
211 SetEyeY(char_info.GetEyeY());
212 SetEyebrowType(char_info.GetEyebrowType());
213 SetEyebrowColor(char_info.GetEyebrowColor());
214 SetEyebrowScale(char_info.GetEyebrowScale());
215 SetEyebrowAspect(char_info.GetEyebrowAspect());
216 SetEyebrowRotate(char_info.GetEyebrowRotate());
217 SetEyebrowX(char_info.GetEyebrowX());
218 SetEyebrowY(char_info.GetEyebrowY() - 3);
219 SetNoseType(char_info.GetNoseType());
220 SetNoseScale(char_info.GetNoseScale());
221 SetNoseY(char_info.GetNoseY());
222 SetMouthType(char_info.GetMouthType());
223 SetMouthColor(char_info.GetMouthColor());
224 SetMouthScale(char_info.GetMouthScale());
225 SetMouthAspect(char_info.GetMouthAspect());
226 SetMouthY(char_info.GetMouthY());
227 SetBeardColor(char_info.GetBeardColor());
228 SetBeardType(char_info.GetBeardType());
229 SetMustacheType(char_info.GetMustacheType());
230 SetMustacheScale(char_info.GetMustacheScale());
231 SetMustacheY(char_info.GetMustacheY());
232 SetGlassType(char_info.GetGlassType());
233 SetGlassColor(char_info.GetGlassColor());
234 SetGlassScale(char_info.GetGlassScale());
235 SetGlassY(char_info.GetGlassY());
236 SetMoleType(char_info.GetMoleType());
237 SetMoleScale(char_info.GetMoleScale());
238 SetMoleX(char_info.GetMoleX());
239 SetMoleY(char_info.GetMoleY());
240}
241
242ValidationResult CoreData::IsValid() const {
243 if (!name.IsValid()) {
244 return ValidationResult::InvalidName;
245 }
246 if (GetFontRegion() > FontRegion::Max) {
247 return ValidationResult::InvalidFont;
248 }
249 if (GetFavoriteColor() > FavoriteColor::Max) {
250 return ValidationResult::InvalidColor;
251 }
252 if (GetGender() > Gender::Max) {
253 return ValidationResult::InvalidGender;
254 }
255 if (GetHeight() > MaxHeight) {
256 return ValidationResult::InvalidHeight;
257 }
258 if (GetBuild() > MaxBuild) {
259 return ValidationResult::InvalidBuild;
260 }
261 if (GetType() > MaxType) {
262 return ValidationResult::InvalidType;
263 }
264 if (GetRegionMove() > MaxRegionMove) {
265 return ValidationResult::InvalidRegionMove;
266 }
267 if (GetFacelineType() > FacelineType::Max) {
268 return ValidationResult::InvalidFacelineType;
269 }
270 if (GetFacelineColor() > FacelineColor::Max) {
271 return ValidationResult::InvalidFacelineColor;
272 }
273 if (GetFacelineWrinkle() > FacelineWrinkle::Max) {
274 return ValidationResult::InvalidFacelineWrinkle;
275 }
276 if (GetFacelineMake() > FacelineMake::Max) {
277 return ValidationResult::InvalidFacelineMake;
278 }
279 if (GetHairType() > HairType::Max) {
280 return ValidationResult::InvalidHairType;
281 }
282 if (GetHairColor() > CommonColor::Max) {
283 return ValidationResult::InvalidHairColor;
284 }
285 if (GetHairFlip() > HairFlip::Max) {
286 return ValidationResult::InvalidHairFlip;
287 }
288 if (GetEyeType() > EyeType::Max) {
289 return ValidationResult::InvalidEyeType;
290 }
291 if (GetEyeColor() > CommonColor::Max) {
292 return ValidationResult::InvalidEyeColor;
293 }
294 if (GetEyeScale() > MaxEyeScale) {
295 return ValidationResult::InvalidEyeScale;
296 }
297 if (GetEyeAspect() > MaxEyeAspect) {
298 return ValidationResult::InvalidEyeAspect;
299 }
300 if (GetEyeRotate() > MaxEyeRotate) {
301 return ValidationResult::InvalidEyeRotate;
302 }
303 if (GetEyeX() > MaxEyeX) {
304 return ValidationResult::InvalidEyeX;
305 }
306 if (GetEyeY() > MaxEyeY) {
307 return ValidationResult::InvalidEyeY;
308 }
309 if (GetEyebrowType() > EyebrowType::Max) {
310 return ValidationResult::InvalidEyebrowType;
311 }
312 if (GetEyebrowColor() > CommonColor::Max) {
313 return ValidationResult::InvalidEyebrowColor;
314 }
315 if (GetEyebrowScale() > MaxEyebrowScale) {
316 return ValidationResult::InvalidEyebrowScale;
317 }
318 if (GetEyebrowAspect() > MaxEyebrowAspect) {
319 return ValidationResult::InvalidEyebrowAspect;
320 }
321 if (GetEyebrowRotate() > MaxEyebrowRotate) {
322 return ValidationResult::InvalidEyebrowRotate;
323 }
324 if (GetEyebrowX() > MaxEyebrowX) {
325 return ValidationResult::InvalidEyebrowX;
326 }
327 if (GetEyebrowY() > MaxEyebrowY) {
328 return ValidationResult::InvalidEyebrowY;
329 }
330 if (GetNoseType() > NoseType::Max) {
331 return ValidationResult::InvalidNoseType;
332 }
333 if (GetNoseScale() > MaxNoseScale) {
334 return ValidationResult::InvalidNoseScale;
335 }
336 if (GetNoseY() > MaxNoseY) {
337 return ValidationResult::InvalidNoseY;
338 }
339 if (GetMouthType() > MouthType::Max) {
340 return ValidationResult::InvalidMouthType;
341 }
342 if (GetMouthColor() > CommonColor::Max) {
343 return ValidationResult::InvalidMouthColor;
344 }
345 if (GetMouthScale() > MaxMouthScale) {
346 return ValidationResult::InvalidMouthScale;
347 }
348 if (GetMouthAspect() > MaxMoutAspect) {
349 return ValidationResult::InvalidMouthAspect;
350 }
351 if (GetMouthY() > MaxMouthY) {
352 return ValidationResult::InvalidMouthY;
353 }
354 if (GetBeardColor() > CommonColor::Max) {
355 return ValidationResult::InvalidBeardColor;
356 }
357 if (GetBeardType() > BeardType::Max) {
358 return ValidationResult::InvalidBeardType;
359 }
360 if (GetMustacheType() > MustacheType::Max) {
361 return ValidationResult::InvalidMustacheType;
362 }
363 if (GetMustacheScale() > MaxMustacheScale) {
364 return ValidationResult::InvalidMustacheScale;
365 }
366 if (GetMustacheY() > MaxMustacheY) {
367 return ValidationResult::InvalidMustacheY;
368 }
369 if (GetGlassType() > GlassType::Max) {
370 return ValidationResult::InvalidGlassType;
371 }
372 if (GetGlassColor() > CommonColor::Max) {
373 return ValidationResult::InvalidGlassColor;
374 }
375 if (GetGlassScale() > MaxGlassScale) {
376 return ValidationResult::InvalidGlassScale;
377 }
378 if (GetGlassY() > MaxGlassY) {
379 return ValidationResult::InvalidGlassY;
380 }
381 if (GetMoleType() > MoleType::Max) {
382 return ValidationResult::InvalidMoleType;
383 }
384 if (GetMoleScale() > MaxMoleScale) {
385 return ValidationResult::InvalidMoleScale;
386 }
387 if (GetMoleX() > MaxMoleX) {
388 return ValidationResult::InvalidMoleX;
389 }
390 if (GetMoleY() > MaxMoleY) {
391 return ValidationResult::InvalidMoleY;
392 }
393 return ValidationResult::NoErrors;
191} 394}
192 395
193void CoreData::SetFontRegion(FontRegion value) { 396void CoreData::SetFontRegion(FontRegion value) {
@@ -314,8 +517,8 @@ void CoreData::SetNoseY(u8 value) {
314 data.nose_y.Assign(value); 517 data.nose_y.Assign(value);
315} 518}
316 519
317void CoreData::SetMouthType(u8 value) { 520void CoreData::SetMouthType(MouthType value) {
318 data.mouth_type.Assign(value); 521 data.mouth_type.Assign(static_cast<u32>(value));
319} 522}
320 523
321void CoreData::SetMouthColor(CommonColor value) { 524void CoreData::SetMouthColor(CommonColor value) {
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h
index cebcd2ee4..8897e4f3b 100644
--- a/src/core/hle/service/mii/types/core_data.h
+++ b/src/core/hle/service/mii/types/core_data.h
@@ -6,6 +6,7 @@
6#include "core/hle/service/mii/mii_types.h" 6#include "core/hle/service/mii/mii_types.h"
7 7
8namespace Service::Mii { 8namespace Service::Mii {
9class CharInfo;
9 10
10struct StoreDataBitFields { 11struct StoreDataBitFields {
11 union { 12 union {
@@ -100,8 +101,9 @@ class CoreData {
100public: 101public:
101 void SetDefault(); 102 void SetDefault();
102 void BuildRandom(Age age, Gender gender, Race race); 103 void BuildRandom(Age age, Gender gender, Race race);
104 void BuildFromCharInfo(const CharInfo& char_info);
103 105
104 u32 IsValid() const; 106 ValidationResult IsValid() const;
105 107
106 void SetFontRegion(FontRegion value); 108 void SetFontRegion(FontRegion value);
107 void SetFavoriteColor(FavoriteColor value); 109 void SetFavoriteColor(FavoriteColor value);
@@ -134,7 +136,7 @@ public:
134 void SetNoseType(NoseType value); 136 void SetNoseType(NoseType value);
135 void SetNoseScale(u8 value); 137 void SetNoseScale(u8 value);
136 void SetNoseY(u8 value); 138 void SetNoseY(u8 value);
137 void SetMouthType(u8 value); 139 void SetMouthType(MouthType value);
138 void SetMouthColor(CommonColor value); 140 void SetMouthColor(CommonColor value);
139 void SetMouthScale(u8 value); 141 void SetMouthScale(u8 value);
140 void SetMouthAspect(u8 value); 142 void SetMouthAspect(u8 value);
@@ -212,5 +214,6 @@ private:
212 Nickname name{}; 214 Nickname name{};
213}; 215};
214static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); 216static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
217static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable.");
215 218
216}; // namespace Service::Mii 219}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp
index 8fce636c7..127221fdb 100644
--- a/src/core/hle/service/mii/types/store_data.cpp
+++ b/src/core/hle/service/mii/types/store_data.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/hle/service/mii/mii_result.h"
4#include "core/hle/service/mii/mii_util.h" 5#include "core/hle/service/mii/mii_util.h"
5#include "core/hle/service/mii/types/raw_data.h" 6#include "core/hle/service/mii/types/raw_data.h"
6#include "core/hle/service/mii/types/store_data.h" 7#include "core/hle/service/mii/types/store_data.h"
@@ -35,13 +36,13 @@ void StoreData::BuildDefault(u32 mii_index) {
35 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); 36 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
36 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); 37 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
37 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); 38 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
38 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); 39 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
39 40
40 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); 41 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
41 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); 42 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
42 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); 43 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
43 44
44 core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); 45 core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
45 core_data.SetMouthColor( 46 core_data.SetMouthColor(
46 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); 47 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
47 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); 48 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
@@ -75,10 +76,8 @@ void StoreData::BuildDefault(u32 mii_index) {
75 core_data.SetType(static_cast<u8>(default_mii.type)); 76 core_data.SetType(static_cast<u8>(default_mii.type));
76 core_data.SetNickname(default_mii.nickname); 77 core_data.SetNickname(default_mii.nickname);
77 78
78 const auto device_id = MiiUtil::GetDeviceId();
79 create_id = MiiUtil::MakeCreateId(); 79 create_id = MiiUtil::MakeCreateId();
80 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 80 SetChecksum();
81 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
82} 81}
83 82
84void StoreData::BuildBase(Gender gender) { 83void StoreData::BuildBase(Gender gender) {
@@ -109,13 +108,13 @@ void StoreData::BuildBase(Gender gender) {
109 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); 108 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
110 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); 109 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
111 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); 110 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
112 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); 111 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
113 112
114 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); 113 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
115 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); 114 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
116 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); 115 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
117 116
118 core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); 117 core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
119 core_data.SetMouthColor( 118 core_data.SetMouthColor(
120 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); 119 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
121 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); 120 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
@@ -149,37 +148,51 @@ void StoreData::BuildBase(Gender gender) {
149 core_data.SetType(static_cast<u8>(default_mii.type)); 148 core_data.SetType(static_cast<u8>(default_mii.type));
150 core_data.SetNickname(default_mii.nickname); 149 core_data.SetNickname(default_mii.nickname);
151 150
152 const auto device_id = MiiUtil::GetDeviceId();
153 create_id = MiiUtil::MakeCreateId(); 151 create_id = MiiUtil::MakeCreateId();
154 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 152 SetChecksum();
155 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
156} 153}
157 154
158void StoreData::BuildRandom(Age age, Gender gender, Race race) { 155void StoreData::BuildRandom(Age age, Gender gender, Race race) {
159 core_data.BuildRandom(age, gender, race); 156 core_data.BuildRandom(age, gender, race);
160 const auto device_id = MiiUtil::GetDeviceId();
161 create_id = MiiUtil::MakeCreateId(); 157 create_id = MiiUtil::MakeCreateId();
162 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 158 SetChecksum();
163 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
164} 159}
165 160
166void StoreData::SetInvalidName() { 161void StoreData::BuildWithCharInfo(const CharInfo& char_info) {
167 const auto& invalid_name = core_data.GetInvalidNickname(); 162 core_data.BuildFromCharInfo(char_info);
163 create_id = MiiUtil::MakeCreateId();
164 SetChecksum();
165}
166
167void StoreData::BuildWithCoreData(const CoreData& in_core_data) {
168 core_data = in_core_data;
169 create_id = MiiUtil::MakeCreateId();
170 SetChecksum();
171}
172
173Result StoreData::Restore() {
174 // TODO: Implement this
175 return ResultNotUpdated;
176}
177
178ValidationResult StoreData::IsValid() const {
179 if (core_data.IsValid() != ValidationResult::NoErrors) {
180 return core_data.IsValid();
181 }
182 if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) {
183 return ValidationResult::InvalidChecksum;
184 }
168 const auto device_id = MiiUtil::GetDeviceId(); 185 const auto device_id = MiiUtil::GetDeviceId();
169 core_data.SetNickname(invalid_name); 186 if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) {
170 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); 187 return ValidationResult::InvalidChecksum;
171 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); 188 }
189 return ValidationResult::NoErrors;
172} 190}
173 191
174bool StoreData::IsSpecial() const { 192bool StoreData::IsSpecial() const {
175 return GetType() == 1; 193 return GetType() == 1;
176} 194}
177 195
178u32 StoreData::IsValid() const {
179 // TODO: complete this
180 return 0;
181}
182
183void StoreData::SetFontRegion(FontRegion value) { 196void StoreData::SetFontRegion(FontRegion value) {
184 core_data.SetFontRegion(value); 197 core_data.SetFontRegion(value);
185} 198}
@@ -304,7 +317,7 @@ void StoreData::SetNoseY(u8 value) {
304 core_data.SetNoseY(value); 317 core_data.SetNoseY(value);
305} 318}
306 319
307void StoreData::SetMouthType(u8 value) { 320void StoreData::SetMouthType(MouthType value) {
308 core_data.SetMouthType(value); 321 core_data.SetMouthType(value);
309} 322}
310 323
@@ -380,6 +393,26 @@ void StoreData::SetNickname(Nickname value) {
380 core_data.SetNickname(value); 393 core_data.SetNickname(value);
381} 394}
382 395
396void StoreData::SetInvalidName() {
397 const auto& invalid_name = core_data.GetInvalidNickname();
398 core_data.SetNickname(invalid_name);
399 SetChecksum();
400}
401
402void StoreData::SetChecksum() {
403 SetDataChecksum();
404 SetDeviceChecksum();
405}
406
407void StoreData::SetDataChecksum() {
408 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID));
409}
410
411void StoreData::SetDeviceChecksum() {
412 const auto device_id = MiiUtil::GetDeviceId();
413 device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData));
414}
415
383Common::UUID StoreData::GetCreateId() const { 416Common::UUID StoreData::GetCreateId() const {
384 return create_id; 417 return create_id;
385} 418}
@@ -585,7 +618,7 @@ Nickname StoreData::GetNickname() const {
585} 618}
586 619
587bool StoreData::operator==(const StoreData& data) { 620bool StoreData::operator==(const StoreData& data) {
588 bool is_identical = data.core_data.IsValid() == 0; 621 bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors;
589 is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; 622 is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
590 is_identical &= GetCreateId() == data.GetCreateId(); 623 is_identical &= GetCreateId() == data.GetCreateId();
591 is_identical &= GetFontRegion() == data.GetFontRegion(); 624 is_identical &= GetFontRegion() == data.GetFontRegion();
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h
index 224c32cf8..ed5dfb949 100644
--- a/src/core/hle/service/mii/types/store_data.h
+++ b/src/core/hle/service/mii/types/store_data.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/result.h"
6#include "core/hle/service/mii/mii_types.h" 7#include "core/hle/service/mii/mii_types.h"
7#include "core/hle/service/mii/types/core_data.h" 8#include "core/hle/service/mii/types/core_data.h"
8 9
@@ -10,17 +11,16 @@ namespace Service::Mii {
10 11
11class StoreData { 12class StoreData {
12public: 13public:
13 // nn::mii::detail::StoreDataRaw::BuildDefault
14 void BuildDefault(u32 mii_index); 14 void BuildDefault(u32 mii_index);
15 // nn::mii::detail::StoreDataRaw::BuildDefault
16
17 void BuildBase(Gender gender); 15 void BuildBase(Gender gender);
18 // nn::mii::detail::StoreDataRaw::BuildRandom
19 void BuildRandom(Age age, Gender gender, Race race); 16 void BuildRandom(Age age, Gender gender, Race race);
17 void BuildWithCharInfo(const CharInfo& char_info);
18 void BuildWithCoreData(const CoreData& in_core_data);
19 Result Restore();
20 20
21 bool IsSpecial() const; 21 ValidationResult IsValid() const;
22 22
23 u32 IsValid() const; 23 bool IsSpecial() const;
24 24
25 void SetFontRegion(FontRegion value); 25 void SetFontRegion(FontRegion value);
26 void SetFavoriteColor(FavoriteColor value); 26 void SetFavoriteColor(FavoriteColor value);
@@ -53,7 +53,7 @@ public:
53 void SetNoseType(NoseType value); 53 void SetNoseType(NoseType value);
54 void SetNoseScale(u8 value); 54 void SetNoseScale(u8 value);
55 void SetNoseY(u8 value); 55 void SetNoseY(u8 value);
56 void SetMouthType(u8 value); 56 void SetMouthType(MouthType value);
57 void SetMouthColor(CommonColor value); 57 void SetMouthColor(CommonColor value);
58 void SetMouthScale(u8 value); 58 void SetMouthScale(u8 value);
59 void SetMouthAspect(u8 value); 59 void SetMouthAspect(u8 value);
@@ -73,6 +73,9 @@ public:
73 void SetMoleY(u8 value); 73 void SetMoleY(u8 value);
74 void SetNickname(Nickname nickname); 74 void SetNickname(Nickname nickname);
75 void SetInvalidName(); 75 void SetInvalidName();
76 void SetChecksum();
77 void SetDataChecksum();
78 void SetDeviceChecksum();
76 79
77 Common::UUID GetCreateId() const; 80 Common::UUID GetCreateId() const;
78 FontRegion GetFontRegion() const; 81 FontRegion GetFontRegion() const;
@@ -135,6 +138,8 @@ private:
135 u16 device_crc{}; 138 u16 device_crc{};
136}; 139};
137static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); 140static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
141static_assert(std::is_trivially_copyable_v<StoreData>,
142 "StoreData type must be trivially copyable.");
138 143
139struct StoreDataElement { 144struct StoreDataElement {
140 StoreData store_data{}; 145 StoreData store_data{};
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
index 1c28e0b1b..a019cc9f7 100644
--- a/src/core/hle/service/mii/types/ver3_store_data.cpp
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -22,12 +22,6 @@ void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
22void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { 22void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
23 out_store_data.BuildBase(Gender::Male); 23 out_store_data.BuildBase(Gender::Male);
24 24
25 if (!IsValid()) {
26 return;
27 }
28
29 // TODO: We are ignoring a bunch of data from the mii_v3
30
31 out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); 25 out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
32 out_store_data.SetFavoriteColor( 26 out_store_data.SetFavoriteColor(
33 static_cast<FavoriteColor>(mii_information.favorite_color.Value())); 27 static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
@@ -36,65 +30,71 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
36 30
37 out_store_data.SetNickname(mii_name); 31 out_store_data.SetNickname(mii_name);
38 out_store_data.SetFontRegion( 32 out_store_data.SetFontRegion(
39 static_cast<FontRegion>(static_cast<u8>(region_information.font_region))); 33 static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value())));
40 34
41 out_store_data.SetFacelineType( 35 out_store_data.SetFacelineType(
42 static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); 36 static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
43 out_store_data.SetFacelineColor( 37 out_store_data.SetFacelineColor(
44 static_cast<FacelineColor>(appearance_bits1.faceline_color.Value())); 38 RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value()));
45 out_store_data.SetFacelineWrinkle( 39 out_store_data.SetFacelineWrinkle(
46 static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); 40 static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
47 out_store_data.SetFacelineMake( 41 out_store_data.SetFacelineMake(
48 static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); 42 static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
49 43
50 out_store_data.SetHairType(static_cast<HairType>(hair_type)); 44 out_store_data.SetHairType(static_cast<HairType>(hair_type));
51 out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value())); 45 out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value()));
52 out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); 46 out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
53 47
54 out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); 48 out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
55 out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value())); 49 out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value()));
56 out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale)); 50 out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value()));
57 out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect)); 51 out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value()));
58 out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate)); 52 out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value()));
59 out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x)); 53 out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value()));
60 out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y)); 54 out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value()));
61 55
62 out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); 56 out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
63 out_store_data.SetEyebrowColor( 57 out_store_data.SetEyebrowColor(
64 static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value())); 58 RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value()));
65 out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale)); 59 out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value()));
66 out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect)); 60 out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value()));
67 out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate)); 61 out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value()));
68 out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x)); 62 out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value()));
69 out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y)); 63 out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3));
70 64
71 out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); 65 out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
72 out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale)); 66 out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value()));
73 out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y)); 67 out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value()));
74 68
75 out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type)); 69 out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value()));
76 out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value())); 70 out_store_data.SetMouthColor(
77 out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale)); 71 RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value()));
78 out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect)); 72 out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value()));
79 out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y)); 73 out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value()));
74 out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value()));
80 75
81 out_store_data.SetMustacheType( 76 out_store_data.SetMustacheType(
82 static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); 77 static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
83 out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale)); 78 out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value()));
84 out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y)); 79 out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value()));
85 80
86 out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); 81 out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
87 out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value())); 82 out_store_data.SetBeardColor(
83 RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value()));
88 84
85 // Glass type is compatible as it is. It doesn't need a table
89 out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); 86 out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
90 out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value())); 87 out_store_data.SetGlassColor(
91 out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale)); 88 RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value()));
92 out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y)); 89 out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value()));
90 out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value()));
93 91
94 out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); 92 out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
95 out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale)); 93 out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value()));
96 out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x)); 94 out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value()));
97 out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y)); 95 out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value()));
96
97 out_store_data.SetChecksum();
98} 98}
99 99
100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { 100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
@@ -220,7 +220,7 @@ u32 Ver3StoreData::IsValid() const {
220 220
221 is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); 221 is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
222 is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); 222 is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
223 is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY); 223 is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY);
224 224
225 is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); 225 is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
226 is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); 226 is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
@@ -228,7 +228,7 @@ u32 Ver3StoreData::IsValid() const {
228 is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); 228 is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
229 is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); 229 is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
230 is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); 230 is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
231 is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale); 231 is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY);
232 232
233 is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); 233 is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
234 is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); 234 is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 674d2e4b2..05951d8cb 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -439,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
439 439
440 device_state = DeviceState::TagMounted; 440 device_state = DeviceState::TagMounted;
441 mount_target = mount_target_; 441 mount_target = mount_target_;
442
442 return ResultSuccess; 443 return ResultSuccess;
443} 444}
444 445
@@ -716,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info
716 return ResultRegistrationIsNotInitialized; 717 return ResultRegistrationIsNotInitialized;
717 } 718 }
718 719
719 Service::Mii::MiiManager manager; 720 Mii::StoreData store_data{};
720 const auto& settings = tag_data.settings; 721 const auto& settings = tag_data.settings;
722 tag_data.owner_mii.BuildToStoreData(store_data);
721 723
722 // TODO: Validate and complete this data 724 // TODO: Validate and complete this data
723 register_info = { 725 register_info = {
724 .mii_store_data = {}, 726 .mii_store_data = store_data,
725 .creation_date = settings.init_date.GetWriteDate(), 727 .creation_date = settings.init_date.GetWriteDate(),
726 .amiibo_name = GetAmiiboName(settings), 728 .amiibo_name = GetAmiiboName(settings),
727 .font_region = settings.settings.font_region, 729 .font_region = settings.settings.font_region,
@@ -1372,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
1372 1374
1373 // Convert from utf16 to utf8 1375 // Convert from utf16 to utf8
1374 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); 1376 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
1375 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); 1377 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1);
1376 1378
1377 return amiibo_name; 1379 return amiibo_name;
1378} 1380}